From 846b7e81b4eda809573c197b0a9668c71097f2fc Mon Sep 17 00:00:00 2001 From: Christian Heller Date: Wed, 14 Jan 2026 00:36:21 +0100 Subject: [PATCH] Move view context generations to allow refactoring of testing. --- src/ledgplom/http.py | 80 +++----------------- src/ledgplom/ledger.py | 147 ++++++++++++++++++++++++++----------- src/ledgplom/testing.py | 13 +--- src/templates/balance.tmpl | 4 +- src/tests/full.balance | 2 +- 5 files changed, 121 insertions(+), 125 deletions(-) diff --git a/src/ledgplom/http.py b/src/ledgplom/http.py index 3306a8d..ce4bfdd 100644 --- a/src/ledgplom/http.py +++ b/src/ledgplom/http.py @@ -5,7 +5,7 @@ from typing import Any # plomlib from plomlib.web import PlomHttpHandler, PlomHttpServer, PlomQueryMap # ourselves -from ledgplom.ledger import Account, DatBlock, DEFAULT_INDENT, Ledger +from ledgplom.ledger import DEFAULT_INDENT, Ledger PATH_TEMPLATES = Path('templates') @@ -108,8 +108,6 @@ class _Handler(PlomHttpHandler): self.redirect( Path('/', _PAGENAME_EDIT_STRUCTURED, self.path_toks[2])) return - ### from time import time_ns - ### start = time_ns() ctx = {'unsaved_changes': self.server.ledger.tainted, 'path': self.path} if self.pagename == PAGENAME_BALANCE: @@ -120,78 +118,24 @@ class _Handler(PlomHttpHandler): self.get_ledger(ctx, self.pagename == PAGENAME_LEDGER_RAW) else: self.get_ledger(ctx, False) - ### end = time_ns() - ### duration = (end - start) / (10 ** 9) - ### print("DEBUG GET", self.pagename, f'{duration:8.5f}') def get_balance(self, ctx) -> None: 'Display tree of calculated Accounts over blocks up_incl+1.' - id_ = int(self.params.first('up_incl') - or str(len(self.server.ledger.blocks) - 1)) - roots = [ac for ac in self.server.ledger.calc_accounts().values() - if not ac.parent] - ctx['roots'] = sorted(roots, key=lambda r: r.basename) - ctx['valid'] = self.server.ledger.blocks_valid_up_incl(id_) - ctx['block'] = self.server.ledger.blocks[id_] - ctx['path_up_incl'] = f'{self.path_toks[1]}?up_incl=' - self._send_rendered(PAGENAME_BALANCE, ctx) + id_str = self.params.first('up_incl') + self._send_rendered( + PAGENAME_BALANCE, + ctx | self.server.ledger.view_ctx_balance(int(id_str) if id_str + else -1)) def get_edit(self, ctx, raw: bool) -> None: 'Display edit form for individual Booking.' - def make_balance_roots(b: DatBlock) -> list[dict[str, Any]]: - if not b.booking: - return [] - observed_tree: list[dict[str, Any]] = [] - for full_path in sorted(b.booking.diffs_targeted.keys()): - node_children: list[dict[str, Any]] = observed_tree - for path, _ in Account.path_to_steps(full_path): - already_listed = False - for n in [n for n in node_children if path == n['name']]: - node_children = n['children'] - already_listed = True - break - if already_listed: - continue - diff = b.booking.diffs_inheriting[path].moneys - if (targeted := full_path == path) or diff: - pre, post = accounts[path].get_wealth_at_excl_incl(id_) - displayed_currs = set(diff.keys()) - for wealth in pre, post: - wealth.ensure_currencies(displayed_currs) - wealth.purge_currencies_except(displayed_currs) - node: dict[str, Any] = { - 'name': path, - 'direct_target': targeted, - 'wealth_before': pre.moneys, - 'wealth_diff': diff, - 'wealth_after': post.moneys, - 'children': []} - node_children += [node] - node_children = node['children'] - return observed_tree - - accounts = self.server.ledger.calc_accounts() - id_ = int(self.path_toks[2]) - block = self.server.ledger.blocks[id_] - ctx['block'] = block - ctx['valid'] = self.server.ledger.blocks_valid_up_incl(id_) - ctx['roots'] = make_balance_roots(block) - if raw: - self._send_rendered(_PAGENAME_EDIT_RAW, ctx) - else: - ctx['raw_gap_lines'] = [dl.raw for dl in block.gap.lines] - ctx['all_accounts'] = sorted(accounts.keys()) - ctx['booking_lines'] = [] - if block.booking: - ctx['booking_lines'] += [block.booking.intro_line.as_dict] - ctx['booking_lines'] += [tf_line.as_dict for tf_line - in block.booking.transfer_lines] - self._send_rendered(_PAGENAME_EDIT_STRUCTURED, ctx) + self._send_rendered( + _PAGENAME_EDIT_RAW if raw else _PAGENAME_EDIT_STRUCTURED, + ctx | self.server.ledger.view_ctx_edit(int(self.path_toks[2]), + raw)) def get_ledger(self, ctx: dict[str, Any], raw: bool) -> None: 'Display ledger of all Bookings.' - ctx['blocks'] = self.server.ledger.blocks - ctx['has_redundant_empty_lines'] =\ - self.server.ledger.has_redundant_empty_lines self._send_rendered( - PAGENAME_LEDGER_RAW if raw else PAGENAME_LEDGER_STRUCTURED, ctx) + PAGENAME_LEDGER_RAW if raw else PAGENAME_LEDGER_STRUCTURED, + ctx | self.server.ledger.view_ctx_ledger()) diff --git a/src/ledgplom/ledger.py b/src/ledgplom/ledger.py index d7b13da..a286640 100644 --- a/src/ledgplom/ledger.py +++ b/src/ledgplom/ledger.py @@ -67,10 +67,10 @@ class _Wealth(): return sink -class Account: +class _Account: 'Combine name, position in tree of owner, and wealth of self + children.' - def __init__(self, parent: Optional['Account'], basename: str) -> None: + def __init__(self, parent: Optional[Self], basename: str) -> None: self._wealth_diffs: dict[int, _Wealth] = {} self.basename = basename self.desc = '' @@ -165,7 +165,7 @@ class _DatLine: @property def comment_instructions(self) -> dict[str, str]: - 'Parse .comment into Account modification instructions.' + 'Parse .comment into _Account modification instructions.' instructions = {} if self.comment.startswith(_PREFIX_DEF): parts = [part.strip() for part @@ -405,7 +405,7 @@ class _Booking(_LinesBlock[_BookingLine]): 'All accounts affected by .diffs_targeted with calculated totals.' acc_diffs: dict[str, _Wealth] = {} for full_path, moneys in self.diffs_targeted.items(): - for path, _ in Account.path_to_steps(full_path): + for path, _ in _Account.path_to_steps(full_path): if path not in acc_diffs: acc_diffs[path] = _Wealth() acc_diffs[path] += moneys @@ -443,7 +443,7 @@ class _Booking(_LinesBlock[_BookingLine]): return copy -class DatBlock: +class _DatBlock: 'Unit of lines with optional .booking, and possibly empty .gap.' def __init__( @@ -460,7 +460,7 @@ class DatBlock: def id_(self) -> int: 'Return index in chain.' count = -1 - block_iterated: Optional[DatBlock] = self + block_iterated: Optional[_DatBlock] = self while block_iterated: block_iterated = block_iterated.prev count += 1 @@ -496,21 +496,21 @@ class DatBlock: setattr(self, f'_{this}', new_this) @property - def next(self) -> Optional['DatBlock']: + def next(self) -> Optional['_DatBlock']: 'Successor in chain.' return self._next @next.setter - def next(self, new_next: Optional['DatBlock']) -> None: + def next(self, new_next: Optional['_DatBlock']) -> None: self._set_neighbor(new_next, 'next', 'prev') @property - def prev(self) -> Optional['DatBlock']: + def prev(self) -> Optional['_DatBlock']: 'Predecessor in chain.' return self._prev @prev.setter - def prev(self, new_prev: Optional['DatBlock']): + def prev(self, new_prev: Optional['_DatBlock']): self._set_neighbor(new_prev, 'prev', 'next') @property @@ -565,9 +565,9 @@ class DatBlock: self.next.prev = new_block new_block.fix_position() - def copy_to_current_date(self) -> 'DatBlock': + def copy_to_current_date(self) -> '_DatBlock': 'Make copy of same lines but now as date, position accordingly.' - copy = DatBlock( + copy = _DatBlock( self.booking.copy_to_current_date() if self.booking else None, self.gap.copy()) if self.next: @@ -578,8 +578,8 @@ class DatBlock: class Ledger: - 'Collection of DatBlocks, _Bookings and Accounts derived from them.' - _blocks_start: Optional[DatBlock] + 'Collection of _DatBlocks, _Bookings and _Accounts derived from them.' + _blocks_start: Optional[_DatBlock] def __init__(self, path_dat: Path) -> None: self._path_dat = path_dat @@ -593,7 +593,7 @@ class Ledger: if (not dat_lines) or dat_lines[-1].code: # ensure final gap line so dat_lines += [_DatLine()] # last booking gets finished booking_lines: list[_BookingLine] = [] - i_block = DatBlock(None, _Gap()) + i_block = _DatBlock(None, _Gap()) self._blocks_start = i_block for dat_line in dat_lines: if bool(dat_line.code): @@ -603,14 +603,14 @@ class Ledger: booking_lines += [_TransferLine.from_dat(dat_line)] else: # enter new gap -> ready to start next block if booking_lines: - i_block.next = DatBlock(_Booking(booking_lines)) + i_block.next = _DatBlock(_Booking(booking_lines)) i_block = i_block.next booking_lines = [] i_block.gap.add([_GapLine.from_dat(dat_line)]) self.last_save_hash = self._hash_dat_lines() @property - def blocks(self) -> list[DatBlock]: + def _blocks(self) -> list[_DatBlock]: 'Return blocks chain as list.' blocks = [] block = self._blocks_start @@ -621,21 +621,21 @@ class Ledger: @property def _dat_lines(self) -> list[_DatLine]: - 'From .blocks build list of current _DatLines.' + 'From ._blocks build list of current _DatLines.' lines = [] - for block in self.blocks: + for block in self._blocks: lines += block.lines return lines - def calc_accounts(self) -> dict[str, Account]: - 'Build mapping of account names to Accounts.' - accounts: dict[str, Account] = {} + def _calc_accounts(self) -> dict[str, _Account]: + 'Build mapping of account names to _Accounts.' + accounts: dict[str, _Account] = {} def ensure_accounts(full_path: str) -> None: parent_path = '' - for path, step_name in Account.path_to_steps(full_path): + for path, step_name in _Account.path_to_steps(full_path): if path not in accounts: - accounts[path] = Account( + accounts[path] = _Account( accounts[parent_path] if parent_path else None, step_name) parent_path = path @@ -644,7 +644,7 @@ class Ledger: for acc_name, desc in dat_line.comment_instructions.items(): ensure_accounts(acc_name) accounts[acc_name].desc = desc - for block in [b for b in self.blocks if b.booking]: + for block in [b for b in self._blocks if b.booking]: assert block.booking is not None for acc_name, wealth in block.booking.diffs_targeted.items(): ensure_accounts(acc_name) @@ -660,9 +660,9 @@ class Ledger: def _hash_dat_lines(self) -> int: return hash(tuple(dl.raw for dl in self._dat_lines)) - def blocks_valid_up_incl(self, block_id: int) -> bool: + def _blocks_valid_up_incl(self, block_id: int) -> bool: 'Whether nothing questionable about blocks until block_id.' - for block in self.blocks[:block_id]: + for block in self._blocks[:block_id]: if block.booking: if block.booking.sink_error: return False @@ -673,13 +673,14 @@ class Ledger: return True @property - def has_redundant_empty_lines(self) -> bool: + def _has_redundant_empty_lines(self) -> bool: 'If any gaps have redunant empty lines.' - return bool([b for b in self.blocks if b.gap.redundant_empty_lines]) + return bool([b for b in self._blocks if b.gap.redundant_empty_lines]) def remove_redundant_empty_lines(self) -> None: - 'From all .blocks remove redundant empty lines.' - for gap in [b.gap for b in self.blocks if b.gap.redundant_empty_lines]: + 'From all ._blocks remove redundant empty lines.' + for gap in [b.gap for b in self._blocks + if b.gap.redundant_empty_lines]: gap.remove_redundant_empty_lines() @property @@ -688,8 +689,8 @@ class Ledger: return self._hash_dat_lines() != self.last_save_hash def move_block(self, idx_from: int, up: bool) -> int: - 'Move DatBlock of idx_from step up or downwards.' - block = self.blocks[idx_from] + 'Move _DatBlock of idx_from step up or downwards.' + block = self._blocks[idx_from] block.move(up) return block.id_ @@ -711,7 +712,7 @@ class Ledger: lines_gap_pre_booking += [_GapLine.from_dat(dat_line)] else: lines_gap_post_booking += [_GapLine.from_dat(dat_line)] - old_block = self.blocks[old_id] + old_block = self._blocks[old_id] if not lines_booking: if old_block.prev: old_block.prev.gap.add(lines_gap_pre_booking) @@ -720,11 +721,11 @@ class Ledger: old_block.booking = None old_block.gap.add(lines_gap_pre_booking) return max(0, old_id - 1) - new_block = DatBlock(_Booking(lines_booking), - _Gap(lines_gap_post_booking)) - self.blocks[old_id].replace_with(new_block) + new_block = _DatBlock(_Booking(lines_booking), + _Gap(lines_gap_post_booking)) + self._blocks[old_id].replace_with(new_block) if not new_block.prev: - self._blocks_start = DatBlock(None, _Gap()) + self._blocks_start = _DatBlock(None, _Gap()) self._blocks_start.next = new_block assert new_block.prev is not None if lines_gap_pre_booking: @@ -732,14 +733,74 @@ class Ledger: return new_block.id_ def add_empty_block(self) -> int: - 'Add new DatBlock of empty _Booking to end of ledger.' - new_block = DatBlock( + 'Add new _DatBlock of empty _Booking to end of ledger.' + new_block = _DatBlock( _Booking([_IntroLine(dt_date.today().isoformat(), '?')])) - self.blocks[-1].next = new_block + self._blocks[-1].next = new_block new_block.fix_position() return new_block.id_ def copy_block(self, id_: int) -> int: - 'Add copy DatBlock of id_ but with current date.' - copy = self.blocks[id_].copy_to_current_date() + 'Add copy _DatBlock of id_ but with current date.' + copy = self._blocks[id_].copy_to_current_date() return copy.id_ + + def view_ctx_balance(self, id_: int = -1) -> dict[str, Any]: + 'All context data relevant for rendering a balance view.' + if id_ < 0: + id_ = len(self._blocks) - 1 + return {'valid': self._blocks_valid_up_incl(id_), + 'block': self._blocks[id_], + 'roots': sorted([ac for ac in self._calc_accounts().values() + if not ac.parent], + key=lambda root: root.basename)} + + def view_ctx_edit(self, id_: int, raw: bool) -> dict[str, Any]: + 'All context data relevant for rendering an edit view.' + accounts = self._calc_accounts() + block = self._blocks[id_] + roots: list[dict[str, Any]] = [] + for full_path in sorted(block.booking.diffs_targeted.keys() + if block.booking else []): + assert block.booking is not None + node_children: list[dict[str, Any]] = roots + for path, _ in _Account.path_to_steps(full_path): + already_listed = False + for n in [n for n in node_children if path == n['name']]: + node_children = n['children'] + already_listed = True + break + if already_listed: + continue + diff = block.booking.diffs_inheriting[path].moneys + if (targeted := full_path == path) or diff: + pre, post = accounts[path].get_wealth_at_excl_incl(id_) + displayed_currs = set(diff.keys()) + for wealth in pre, post: + wealth.ensure_currencies(displayed_currs) + wealth.purge_currencies_except(displayed_currs) + node: dict[str, Any] = { + 'name': path, + 'direct_target': targeted, + 'wealth_before': pre.moneys, + 'wealth_diff': diff, + 'wealth_after': post.moneys, + 'children': []} + node_children += [node] + node_children = node['children'] + ctx = {'block': block, + 'valid': self._blocks_valid_up_incl(id_), + 'roots': roots} + if not raw: + ctx['raw_gap_lines'] = [dl.raw for dl in block.gap.lines] + ctx['all_accounts'] = sorted(accounts.keys()) + ctx['booking_lines'] = ( + [block.booking.intro_line.as_dict] + + [tf_line.as_dict for tf_line in block.booking.transfer_lines] + ) if block.booking else [] + return ctx + + def view_ctx_ledger(self) -> dict[str, Any]: + 'All context data relevant for rendering a ledger view.' + return {key: getattr(self, f'_{key}') + for key in ('blocks', 'has_redundant_empty_lines')} diff --git a/src/ledgplom/testing.py b/src/ledgplom/testing.py index 8e7a53a..3c7d576 100644 --- a/src/ledgplom/testing.py +++ b/src/ledgplom/testing.py @@ -42,18 +42,9 @@ def run_tests() -> None: for line in f.readlines()] ctx: dict[str, Any] = {} if key == PAGENAME_BALANCE: - id_ = len(ledger.blocks) - 1 - ctx['roots'] = sorted([ac for - ac in ledger.calc_accounts().values() - if not ac.parent], - key=lambda r: r.basename) - ctx['valid'] = ledger.blocks_valid_up_incl(id_) - ctx['block'] = ledger.blocks[id_] - ctx['path_up_incl'] = f'{PAGENAME_BALANCE}?up_incl=' + ctx |= ledger.view_ctx_balance(-1) else: - ctx['blocks'] = ledger.blocks - ctx['has_redundant_empty_lines'] =\ - ledger.has_redundant_empty_lines + ctx |= ledger.view_ctx_ledger() lines_rendered = template.render(**ctx).split('\n') msg_prefix = f'test for {test_path}:' for idx0, line in enumerate(lines_rendered): diff --git a/src/templates/balance.tmpl b/src/templates/balance.tmpl index 1812ce9..4b71c7a 100644 --- a/src/templates/balance.tmpl +++ b/src/templates/balance.tmpl @@ -90,8 +90,8 @@ span.indent { {% block content %}

- {{ macros.conditional_block_nav(path_up_incl, 'prev', block) -}}{##} - {{ macros.conditional_block_nav(path_up_incl, 'next', block) }}{##} + {{ macros.conditional_block_nav('/balance?up_incl=', 'prev', block) -}}{##} + {{ macros.conditional_block_nav('/balance?up_incl=', 'next', block) }}{##} | balance after {# -#} {# -#} diff --git a/src/tests/full.balance b/src/tests/full.balance index c348be1..92a0264 100644 --- a/src/tests/full.balance +++ b/src/tests/full.balance @@ -82,7 +82,7 @@ span.indent {

- prev + prev next | balance after booking 4 (2001-01-01: test) -- 2.30.2