From: Christian Heller Date: Wed, 11 Feb 2026 00:47:26 +0000 (+0100) Subject: Add auto-balancing per #balancer comment instructions. X-Git-Url: https://plomlompom.com/repos/booking/%7B%7Bdb.prefix%7D%7D/%7B%7Bprefix%7D%7D/condition?a=commitdiff_plain;h=HEAD;p=ledgplom Add auto-balancing per #balancer comment instructions. --- diff --git a/src/ledgplom/ledger.py b/src/ledgplom/ledger.py index 7cc1abb..cae1ac1 100644 --- a/src/ledgplom/ledger.py +++ b/src/ledgplom/ledger.py @@ -10,7 +10,8 @@ from typing import Any, Callable, Iterator, Optional, Self, Union SPACE = ' ' _INDENT_CHARS = {SPACE, '\t'} SEP_COMMENTS = ';' -_PREFIX_DEF = '#def ' +_PREFIX_DEF = '#desc ' +_PREFIX_BAL = '#balancer ' class _Wealth(): @@ -63,11 +64,12 @@ class _Wealth(): class _Account: 'Combine name, position in tree of owner, and wealth of self + children.' + desc = '' + balancer = '' def __init__(self, parent: Optional[Self], basename: str) -> None: self._wealth_diffs: dict[int, _Wealth] = {} self.basename = basename - self.desc = '' self.children: list[Self] = [] self.parent = parent if self.parent: @@ -161,16 +163,18 @@ class _DatLine: return self._raw[:] @property - def comment_instructions(self) -> dict[str, str]: + def comment_instructions(self) -> dict[str, dict[str, str]]: 'Parse .comment into _Account modification instructions.' - instructions = {} - if self.comment.startswith(_PREFIX_DEF): - parts = [part.strip() for part - in self.comment[len(_PREFIX_DEF):].split(SEP_COMMENTS)] - first_part_parts = parts[0].split(maxsplit=1) - account_name = first_part_parts[0] - desc = first_part_parts[1] if len(first_part_parts) > 1 else '' - instructions[account_name] = desc + instructions: dict[str, dict[str, str]] = {} + for prefix in (_PREFIX_DEF, _PREFIX_BAL): + attr_name = prefix[1:].rstrip() + instructions[attr_name] = {} + if self.comment.startswith(prefix): + parts = tuple(part.strip() for part + in self.comment[len(prefix):].split(maxsplit=1)) + if not parts: + continue + instructions[attr_name][parts[0]] = ''.join(parts[1:]) return instructions @@ -649,10 +653,11 @@ class Ledger: for block in self.blocks: collect_more_than_names = id_ < 0 or block.id_ <= id_ for line in block.lines: - for acc_name, desc in line.comment_instructions.items(): - ensure_accounts(acc_name) - if collect_more_than_names: - accounts[acc_name].desc = desc + for attr_name, defs in line.comment_instructions.items(): + for acc_name, definition in defs.items(): + ensure_accounts(acc_name) + if collect_more_than_names and definition: + setattr(accounts[acc_name], attr_name, definition) if block.booking: for acc_name, wealth in block.booking.diffs_targeted.items(): ensure_accounts(acc_name) @@ -729,12 +734,14 @@ class Ledger: def view_ctx_edit(self, id_: int, raw=True, lines=False) -> dict[str, Any]: 'All context data relevant for rendering an edit view.' block = self.blocks[id_] + accounts = self._calc_accounts(id_) if lines: return {'raw_gap_lines': [dl.raw for dl in block.gap_lines], + 'account_balancers': {k: v.balancer + for k, v in accounts.items()}, 'booking_lines': ([line.as_dict for line in block.booking.booking_lines] if block.booking else tuple())} - accounts = self._calc_accounts(id_) roots: list[dict[str, Any]] = [] for full_path in sorted(block.booking.diffs_targeted.keys() if block.booking else []): diff --git a/src/templates/edit_structured.js b/src/templates/edit_structured.js index 6fecc48..f607ea4 100644 --- a/src/templates/edit_structured.js +++ b/src/templates/edit_structured.js @@ -21,11 +21,11 @@ eslint ], "max-lines": [ "error", - {"max": 376, "skipBlankLines": true, "skipComments": true} + {"max": 394, "skipBlankLines": true, "skipComments": true} ], "max-lines-per-function": [ "error", - 250 + 259 ], "max-params": [ "error", @@ -33,7 +33,7 @@ eslint ], "max-statements": [ "error", - 42 + 43 ], "multiline-comment-style": [ "error", @@ -56,7 +56,7 @@ import { } from "/taint.js"; const - DEFAULT_INPUT_LEN_INDENT = 2, + BALANCERS_DEFAULT = {"": "?"}, IDX_LAST = -1, IDX_PAST_INTRO_LINE = 1, IDX_START = 0, @@ -68,26 +68,33 @@ const LEN_CURRENCY = 3, LEN_DATE = 10, LEN_EMPTY = 0, + LEN_INDENT_INPUT_BALANCED = 4, + LEN_INDENT_INPUT_DEFAULT = 2, LEN_INTRO_LINE = 4, LEN_LEN_INDENT = 1, LEN_LINE_STEP = 1, LEN_STEP_INPUT_LEN_INDENT = 1, LEN_TARGET = 37, MINIMUM_INPUT_LEN_INDENT = 1, + accountBalancers = Object.assign( + BALANCERS_DEFAULT, + {{ account_balancers|tojson|safe }} + ), bookingLines = {{ booking_lines|tojson|safe }}, rawGapLines = {{ raw_gap_lines|tojson|safe }}; const newBookingLine = ( account = "", amount = "None", - currency = "" + currency = "", + lenIndent = LEN_INDENT_INPUT_DEFAULT ) => ({ account, amount, "comment": "", currency, "errors": [], - "len_indent": DEFAULT_INPUT_LEN_INDENT + "len_indent": lenIndent }); const taintAndUpdateForm = () => { @@ -95,6 +102,29 @@ const taintAndUpdateForm = () => { updateForm(); // eslint-disable-line no-use-before-define }; +const balance = (bookingLine) => { + let invertedAmount = "None"; + if (bookingLine.amount !== "None") { + invertedAmount = `-${bookingLine.amount}`; + const doubleMinus = "--"; + if (invertedAmount.startsWith(doubleMinus)) { + invertedAmount = invertedAmount.slice(doubleMinus.length); + } + } + let balancer = ""; + const accNameSteps = bookingLine.account.split(":"); + while (balancer === "") { + balancer = accountBalancers[accNameSteps.join(":")]; + accNameSteps.pop(); + } + return newBookingLine( + balancer, + invertedAmount, + bookingLine.currency, + LEN_INDENT_INPUT_BALANCED + ); +}; + const updateForm = () => { const table = document.getElementById("booking_lines"), @@ -111,8 +141,8 @@ const updateForm = () => { const addButton = ( parentTd, label, - disabled, - onclick + onclick, + disabled = false ) => { /* add button to td to run onclick (after updating bookingLines from from inputs, and followed by calling taint and updateForm) */ @@ -221,7 +251,6 @@ const updateForm = () => { ].forEach((kwargs) => addButton( tdBtnsUpdown, kwargs.label, - !kwargs.enabled, () => { const otherLine = bookingLines[kwargs.earlierIdx]; bookingLines.splice( @@ -233,7 +262,8 @@ const updateForm = () => { LEN_EMPTY, otherLine ); - } + }, + !kwargs.enabled )); } @@ -309,7 +339,6 @@ const updateForm = () => { addButton( tdAddDel, "add new", - false, () => bookingLines.splice( idx + LEN_LINE_STEP, LEN_EMPTY, @@ -317,10 +346,18 @@ const updateForm = () => { ) ); if (idx > IDX_START) { + addButton( + tdAddDel, + "balance", + () => bookingLines.splice( + idx + LEN_LINE_STEP, + LEN_EMPTY, + balance(bookingLine) + ); + ); addButton( tdAddDel, "delete", - idx <= IDX_START, () => bookingLines.splice( idx, LEN_LINE_STEP @@ -367,23 +404,9 @@ const replace = () => { }; const mirror = () => { - bookingLines.slice(IDX_PAST_INTRO_LINE).forEach((bookingLine) => { - let invertedAmount = "None"; - if (bookingLine.amount !== "None") { - invertedAmount = `-${bookingLine.amount}`; - const doubleMinus = "--"; - if (invertedAmount.startsWith(doubleMinus)) { - invertedAmount = invertedAmount.slice(doubleMinus.length); - } - } - bookingLines.push( - newBookingLine( - "?", - invertedAmount, - bookingLine.currency - ) - ); - }); + bookingLines.slice(IDX_PAST_INTRO_LINE).forEach( + (line) => bookingLines.push(balance(line)) + ); taintAndUpdateForm(); }; diff --git a/src/tests/full.dat b/src/tests/full.dat index 91c537d..12f75ef 100644 --- a/src/tests/full.dat +++ b/src/tests/full.dat @@ -1,4 +1,4 @@ -; #def bar:x bla bla bla +; #desc bar:x bla bla bla 2001-01-01 test ; foo ; in-body comment 1 @@ -22,7 +22,7 @@ bar:x:y -10 € bar:z -1 USD -; #def bar:x bla foo bla +; #desc bar:x bla foo bla 2001-01-03 test foo:x 10 € diff --git a/src/tests/full.edit_raw.0 b/src/tests/full.edit_raw.0 index af3835c..e63bf9d 100644 --- a/src/tests/full.edit_raw.0 +++ b/src/tests/full.edit_raw.0 @@ -82,7 +82,7 @@ td.direct_target {
- diff --git a/src/tests/full.edit_structured.4 b/src/tests/full.edit_structured.4 index 6ad51b0..1cedd9b 100644 --- a/src/tests/full.edit_structured.4 +++ b/src/tests/full.edit_structured.4 @@ -110,7 +110,7 @@ to
Gap:
diff --git a/src/tests/full.ledger_raw b/src/tests/full.ledger_raw index 49e154e..3765eb8 100644 --- a/src/tests/full.ledger_raw +++ b/src/tests/full.ledger_raw @@ -70,7 +70,7 @@ Detected redundant empty lines in gaps, e] - ; #def bar:x bla bla bla  + ; #desc bar:x bla bla bla    2001-01-01 test ; foo  ; in-body comment 1  @@ -154,7 +154,7 @@ Detected redundant empty lines in gaps, ; #def bar:x bla bla bla  + ; #desc bar:x bla bla bla    @@ -242,7 +242,7 @@ Detected redundant empty lines in gaps,   - ; #def bar:x bla foo bla  + ; #desc bar:x bla foo bla