From: Christian Heller <c.heller@plomlompom.de>
Date: Mon, 20 Jan 2025 21:47:01 +0000 (+0100)
Subject: Extend BookingLine validations, differentiate class into two cases.
X-Git-Url: https://plomlompom.com/repos/%7B%7B%20web_path%20%7D%7D/booking/%7B%7Bprefix%7D%7D/%7B%7Bdb.prefix%7D%7D/bar%20baz.html?a=commitdiff_plain;h=3dfdc1f581aae00dadbdfa3262589a1760e73e1b;p=ledgplom

Extend BookingLine validations, differentiate class into two cases.
---

diff --git a/ledger.py b/ledger.py
index 8c2d48b..6085397 100755
--- 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):