home · contact · privacy
Refactor .as_dict properties. master
authorChristian Heller <c.heller@plomlompom.de>
Tue, 4 Feb 2025 13:29:37 +0000 (14:29 +0100)
committerChristian Heller <c.heller@plomlompom.de>
Tue, 4 Feb 2025 13:29:37 +0000 (14:29 +0100)
ledger.py

index 30f93dd3221c3cba1be1caed4e0e155c8421c408..614fef5f98cbc1492c0ec7586ddce64bba7c9209 100755 (executable)
--- a/ledger.py
+++ b/ledger.py
@@ -1,6 +1,5 @@
 #!/usr/bin/env python3
 """Viewer for ledger .dat files."""
 #!/usr/bin/env python3
 """Viewer for ledger .dat files."""
-from abc import ABC, abstractmethod
 from datetime import date as dt_date
 from decimal import Decimal, InvalidOperation as DecimalInvalidOperation
 from os import environ
 from datetime import date as dt_date
 from decimal import Decimal, InvalidOperation as DecimalInvalidOperation
 from os import environ
@@ -26,6 +25,24 @@ LEDGER_STRUCT = f'{PREFIX_LEDGER}{TOK_STRUCT}'
 LEDGER_RAW = f'{PREFIX_LEDGER}{TOK_RAW}'
 
 
 LEDGER_RAW = f'{PREFIX_LEDGER}{TOK_RAW}'
 
 
+class Dictable:
+    """Line abstraction featuring .as_dict property."""
+    dictables: set[str] = set()
+
+    @property
+    def as_dict(self) -> dict[str, Any]:
+        """Return as JSON-ready dict attributes listed in .dictables."""
+        d = {}
+        for name in self.dictables:
+            value = getattr(self, name)
+            if hasattr(value, 'as_dict'):
+                value = value.as_dict
+            elif not isinstance(value, (str, int)):
+                value = str(value)
+            d[name] = value
+        return d
+
+
 class Wealth:
     """Collects amounts mapped to currencies."""
 
 class Wealth:
     """Collects amounts mapped to currencies."""
 
@@ -98,17 +115,9 @@ class Account:
         return sorted(list(names))
 
 
         return sorted(list(names))
 
 
-class Line(ABC):
-    """Line abstraction featuring .as_dict property."""
-
-    @property
-    @abstractmethod
-    def as_dict(self) -> dict:
-        """Return as JSON-ready dict."""
-
-
-class DatLine(Line):
+class DatLine(Dictable):
     """Line of .dat file parsed into comments and machine-readable data."""
     """Line of .dat file parsed into comments and machine-readable data."""
+    dictables = {'booking_line', 'code', 'comment', 'error', 'is_intro'}
 
     def __init__(self, line: str) -> None:
         self.raw = line[:]
 
     def __init__(self, line: str) -> None:
         self.raw = line[:]
@@ -117,13 +126,6 @@ class DatLine(Line):
         self.code = halves[0]
         self.booking_line: Optional[BookingLine] = None
 
         self.code = halves[0]
         self.booking_line: Optional[BookingLine] = None
 
-    @property
-    def as_dict(self) -> dict:
-        assert self.booking_line is not None
-        return {'comment': self.comment, 'code': self.code,
-                'is_intro': self.is_intro, 'error': self.error,
-                'booking_line': self.booking_line.as_dict}
-
     @property
     def is_intro(self) -> bool:
         """Return if intro line of a Booking."""
     @property
     def is_intro(self) -> bool:
         """Return if intro line of a Booking."""
@@ -163,7 +165,7 @@ class DatLine(Line):
         return self.raw.replace(' ', '&nbsp;')
 
 
         return self.raw.replace(' ', '&nbsp;')
 
 
-class BookingLine(Line):
+class BookingLine(Dictable):
     """Parsed code part of a DatLine belonging to a Booking."""
 
     def __init__(self, booking: 'Booking') -> None:
     """Parsed code part of a DatLine belonging to a Booking."""
 
     def __init__(self, booking: 'Booking') -> None:
@@ -174,6 +176,7 @@ class BookingLine(Line):
 
 class IntroLine(BookingLine):
     """First line of a Booking, expected to carry date etc."""
 
 class IntroLine(BookingLine):
     """First line of a Booking, expected to carry date etc."""
+    dictables = {'date', 'target'}
 
     def __init__(self, booking: 'Booking', code: str) -> None:
         super().__init__(booking)
 
     def __init__(self, booking: 'Booking', code: str) -> None:
         super().__init__(booking)
@@ -189,13 +192,10 @@ class IntroLine(BookingLine):
         except ValueError:
             self.errors += [f'not properly formatted legal date: {self.date}']
 
         except ValueError:
             self.errors += [f'not properly formatted legal date: {self.date}']
 
-    @property
-    def as_dict(self) -> dict:
-        return {'date': self.date, 'target': self.target}
-
 
 class TransferLine(BookingLine):
     """Non-first Booking line, expected to carry value movement."""
 
 class TransferLine(BookingLine):
     """Non-first Booking line, expected to carry value movement."""
+    dictables = {'amount', 'account', 'currency'}
 
     def __init__(self, booking: 'Booking', code: str, idx: int) -> None:
         super().__init__(booking)
 
     def __init__(self, booking: 'Booking', code: str, idx: int) -> None:
         super().__init__(booking)
@@ -224,11 +224,6 @@ class TransferLine(BookingLine):
             return f'{self.amount:.1f}…' if exp < -2 else f'{self.amount:.2f}'
         return ''
 
             return f'{self.amount:.1f}…' if exp < -2 else f'{self.amount:.2f}'
         return ''
 
-    @property
-    def as_dict(self) -> dict:
-        return {'account': self.account, 'currency': self.currency,
-                'amount': str(self.amount)}
-
 
 class Booking:
     """Represents lines of individual booking."""
 
 class Booking:
     """Represents lines of individual booking."""