home · contact · privacy
Add per-line error messages. master
authorChristian Heller <c.heller@plomlompom.de>
Mon, 20 Jan 2025 20:47:11 +0000 (21:47 +0100)
committerChristian Heller <c.heller@plomlompom.de>
Mon, 20 Jan 2025 20:47:11 +0000 (21:47 +0100)
ledger.py
templates/_base.tmpl
templates/_macros.tmpl

index 24e83119da101ef3c5af6bb8f3ec0e858e9d553c..8c2d48bc6b77d3356aad27af9895c5e585d514b5 100755 (executable)
--- a/ledger.py
+++ b/ledger.py
@@ -25,15 +25,20 @@ class DatLine:
         self.booking_line: Optional[BookingLine] = None
 
     @property
         self.booking_line: Optional[BookingLine] = None
 
     @property
-    def type(self) -> str:
-        """Provide categorization of .code part."""
-        return self.booking_line.type if self.booking_line else 'no_data'
+    def is_intro(self) -> bool:
+        """Return if intro line of a Booking."""
+        return self.booking_line.is_intro if self.booking_line else False
 
     @property
     def booking_id(self) -> int:
         """If .booking_line, its .booking_id, else -1."""
         return self.booking_line.booking_id if self.booking_line else -1
 
 
     @property
     def booking_id(self) -> int:
         """If .booking_line, its .booking_id, else -1."""
         return self.booking_line.booking_id if self.booking_line else -1
 
+    @property
+    def error(self) -> str:
+        """Return error if registered on attempt to parse into BookingLine."""
+        return self.booking_line.error if self.booking_line else ''
+
     @property
     def is_empty(self) -> bool:
         """Return if both .code and .comment are empty."""
     @property
     def is_empty(self) -> bool:
         """Return if both .code and .comment are empty."""
@@ -50,25 +55,31 @@ class DatLine:
 class BookingLine:
     """Parsed code part of a DatLine belonging to a Booking."""
 
 class BookingLine:
     """Parsed code part of a DatLine belonging to a Booking."""
 
-    def __init__(self, booking_id: int, code: str) -> None:
+    def __init__(self, booking_id: int, code: str, as_intro: bool = False
+                 ) -> None:
+        self.error = ''
         self.booking_id = booking_id
         self.booking_id = booking_id
-        self.type = 'invalid'
+        self.is_intro = as_intro
+        if self.is_intro:
+            if code[0].isspace():
+                self.error = 'intro line indented'
+            return
         self.acc, self.amt, self.curr = '', '', ''
         self.acc, self.amt, self.curr = '', '', ''
-        if code[0].isspace():
-            toks = code.lstrip().split()
-            self.acc = toks[0]
-            if 1 == len(toks):
-                self.type = 'value'
-            elif 3 == len(toks):
-                amt_dec = Decimal(toks[1])
-                exp = amt_dec.as_tuple().exponent
-                assert isinstance(exp, int)
-                self.amt = (f'{amt_dec:.1f}…' if exp < -2
-                            else f'{amt_dec:.2f}')
-                self.curr = toks[2]
-                self.type = 'value'
-        else:
-            self.type = 'intro'
+        if not code[0].isspace():
+            self.error = 'non-intro line not indented'
+            return
+        toks = code.lstrip().split()
+        self.acc = toks[0]
+        if len(toks) not in {1, 3}:
+            self.error = 'illegal number of tokens'
+            return
+        if 3 == len(toks):
+            amt_dec = Decimal(toks[1])
+            exp = amt_dec.as_tuple().exponent
+            assert isinstance(exp, int)
+            self.amt = (f'{amt_dec:.1f}…' if exp < -2
+                        else f'{amt_dec:.2f}')
+            self.curr = toks[2]
 
 
 class Booking:
 
 
 class Booking:
@@ -78,7 +89,9 @@ class Booking:
     def __init__(self, id_: int, dat_lines: list[DatLine]) -> None:
         self.id_ = id_
         self.dat_lines = dat_lines
     def __init__(self, id_: int, dat_lines: list[DatLine]) -> None:
         self.id_ = id_
         self.dat_lines = dat_lines
-        for dat_line in self.dat_lines:
+        self.dat_lines[0].booking_line = BookingLine(
+                self.id_, self.dat_lines[0].code, as_intro=True)
+        for dat_line in self.dat_lines[1:]:
             dat_line.booking_line = BookingLine(self.id_, dat_line.code)
 
 
             dat_line.booking_line = BookingLine(self.id_, dat_line.code)
 
 
index 2953be26a0a0820ab90b333f71c571017e3abb28..2206d5199770e544ce900fbd955d3f9df609bf05 100644 (file)
@@ -6,7 +6,7 @@
 <style>
 body { background-color: white; font-family: sans-serif; }
 tr:nth-child(odd) { background-color: #dcdcdc; }
 <style>
 body { background-color: white; font-family: sans-serif; }
 tr:nth-child(odd) { background-color: #dcdcdc; }
-tr.invalid > td { background-color: red; }
+td.invalid { background-color: red; }
 {% block css %}{% endblock %}
 </style>
 </head>
 {% block css %}{% endblock %}
 </style>
 </head>
index 4df4fc609fbef257d0d4406e5eb75c052c3a0941..7ba713f6356468bb88546ac3f89fb3548a086923 100644 (file)
@@ -7,36 +7,39 @@ td.curr { text-align: center; }
 {% macro table_dat_lines(dat_lines, single, raw) %}
 <table>
 {% for dat_line in dat_lines %}
 {% macro table_dat_lines(dat_lines, single, raw) %}
 <table>
 {% for dat_line in dat_lines %}
-  {% if (not (raw or single)) and dat_line.type == "intro" and loop.index > 1 %}
+  {% if (not (raw or single)) and dat_line.is_intro and loop.index > 1 %}
     <tr><td colspan=5>&nbsp;</td></tr>
   {% endif %}
     <tr><td colspan=5>&nbsp;</td></tr>
   {% endif %}
-  <tr class="{{dat_line.type}}">
+  <tr>
   {% if not single %}
   {% if not single %}
-    {% if dat_line.type == "intro" %}
+    {% if dat_line.is_intro %}
       <td id="{{dat_line.booking_id}}"><a href="#{{dat_line.booking_id}}">#</a></td>
     {% else %}
       <td></td>
     {% endif %}
   {% endif %}
   {% if raw %}
       <td id="{{dat_line.booking_id}}"><a href="#{{dat_line.booking_id}}">#</a></td>
     {% else %}
       <td></td>
     {% endif %}
   {% endif %}
   {% if raw %}
-    {% if dat_line.type == "intro" %}
+    {% if dat_line.is_intro %}
       <td><a href="/booking/{{dat_line.booking_id}}"/>{{dat_line.raw_nbsp}}</a></td>
     {% else %}
       <td>{{dat_line.raw_nbsp}}</td>
     {% endif %}
   {% else %}
       <td><a href="/booking/{{dat_line.booking_id}}"/>{{dat_line.raw_nbsp}}</a></td>
     {% else %}
       <td>{{dat_line.raw_nbsp}}</td>
     {% endif %}
   {% else %}
-    {% if dat_line.type == "intro" %}
-      <td class="code" colspan=3><a href="/booking/{{dat_line.booking_id}}">{{dat_line.code}}</a></td>
-    {% elif dat_line.type == "value" %}
+    {% if dat_line.is_intro %}
+      <td class="code{% if dat_line.error %} invalid{% endif %}" colspan=3><a href="/booking/{{dat_line.booking_id}}">{{dat_line.code}}</a></td>
+    {% elif not dat_line.error %}
       <td class="amt">{{dat_line.booking_line.amt}}</td>
       <td class="curr">{{dat_line.booking_line.curr|truncate(4,true,"…")}}</td>
       <td>{{dat_line.booking_line.acc}}</td>
     {% else %}
       <td class="amt">{{dat_line.booking_line.amt}}</td>
       <td class="curr">{{dat_line.booking_line.curr|truncate(4,true,"…")}}</td>
       <td>{{dat_line.booking_line.acc}}</td>
     {% else %}
-      <td colspan=3>{{dat_line.code}}</td>
+      <td class="invalid" colspan=3>{{dat_line.code}}</td>
     {% endif %}
     <td>{{dat_line.comment}}</td>
   {% endif %}
   </tr>
     {% endif %}
     <td>{{dat_line.comment}}</td>
   {% endif %}
   </tr>
+  {% if dat_line.error and not raw %}
+    <tr><td class="invalid" colspan={% if single %}4{% else %}5{% endif %}>{{dat_line.error}}</td></tr>
+  {% endif %}
 {% endfor %}
 </table>
 {% endmacro %}
 {% endfor %}
 </table>
 {% endmacro %}