home · contact · privacy
Extend BookingLine validations, differentiate class into two cases.
authorChristian Heller <c.heller@plomlompom.de>
Mon, 20 Jan 2025 21:47:01 +0000 (22:47 +0100)
committerChristian Heller <c.heller@plomlompom.de>
Mon, 20 Jan 2025 21:47:01 +0000 (22:47 +0100)
ledger.py

index 8c2d48bc6b77d3356aad27af9895c5e585d514b5..6085397393d9dbb835d434e108f941c1ff1c3669 100755 (executable)
--- a/ledger.py
+++ b/ledger.py
@@ -1,6 +1,7 @@
 #!/usr/bin/env python3
 """Viewer for ledger .dat files."""
-from decimal import Decimal
+from datetime import date as dt_date
+from decimal import Decimal, InvalidOperation as DecimalInvalidOperation
 from os import environ
 from pathlib import Path
 from sys import exit as sys_exit
@@ -27,7 +28,7 @@ class DatLine:
     @property
     def is_intro(self) -> bool:
         """Return if intro line of a Booking."""
-        return self.booking_line.is_intro if self.booking_line else False
+        return isinstance(self.booking_line, IntroLine)
 
     @property
     def booking_id(self) -> int:
@@ -55,15 +56,33 @@ class DatLine:
 class BookingLine:
     """Parsed code part of a DatLine belonging to a Booking."""
 
-    def __init__(self, booking_id: int, code: str, as_intro: bool = False
-                 ) -> None:
+    def __init__(self, booking_id: int) -> None:
         self.error = ''
         self.booking_id = booking_id
-        self.is_intro = as_intro
-        if self.is_intro:
-            if code[0].isspace():
-                self.error = 'intro line indented'
+
+
+class IntroLine(BookingLine):
+    """First line of a Booking, expected to carry date etc."""
+
+    def __init__(self, booking_id: int, code: str) -> None:
+        super().__init__(booking_id)
+        if code[0].isspace():
+            self.error = 'intro line indented'
+        toks = code.lstrip().split(maxsplit=1)
+        if len(toks) != 2:
+            self.error = 'illegal number of tokens'
             return
+        try:
+            dt_date.fromisoformat(toks[0])
+        except ValueError:
+            self.error = 'not starting with properly formatted legal date'
+
+
+class TransactionLine(BookingLine):
+    """Non-first Booking line, expected to carry value movement."""
+
+    def __init__(self, booking_id: int, code: str) -> None:
+        super().__init__(booking_id)
         self.acc, self.amt, self.curr = '', '', ''
         if not code[0].isspace():
             self.error = 'non-intro line not indented'
@@ -74,7 +93,11 @@ class BookingLine:
             self.error = 'illegal number of tokens'
             return
         if 3 == len(toks):
-            amt_dec = Decimal(toks[1])
+            try:
+                amt_dec = Decimal(toks[1])
+            except DecimalInvalidOperation:
+                self.error = 'improper amount value'
+                return
             exp = amt_dec.as_tuple().exponent
             assert isinstance(exp, int)
             self.amt = (f'{amt_dec:.1f}…' if exp < -2
@@ -89,10 +112,10 @@ class Booking:
     def __init__(self, id_: int, dat_lines: list[DatLine]) -> None:
         self.id_ = id_
         self.dat_lines = dat_lines
-        self.dat_lines[0].booking_line = BookingLine(
-                self.id_, self.dat_lines[0].code, as_intro=True)
+        self.dat_lines[0].booking_line = IntroLine(self.id_,
+                                                   self.dat_lines[0].code)
         for dat_line in self.dat_lines[1:]:
-            dat_line.booking_line = BookingLine(self.id_, dat_line.code)
+            dat_line.booking_line = TransactionLine(self.id_, dat_line.code)
 
 
 class Handler(PlomHttpHandler):