# 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')
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:
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())
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 = ''
@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
'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
return copy
-class DatBlock:
+class _DatBlock:
'Unit of lines with optional .booking, and possibly empty .gap.'
def __init__(
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
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
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:
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
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):
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
@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
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)
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
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
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_
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)
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:
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')}