Wire admin add-account endpoint into the UI #46
2 changed files with 29 additions and 4 deletions
fix(accounts): match Beancount's DATE grammar in duplicate detection (libra-#48)
_open_directive_exists hardcoded '^YYYY-MM-DD open ' (dash-only, 2-digit,
single-space), but Beancount's DATE token (parser/lexer.l) is
(17|18|19|20)[0-9]{2}[-/][0-9]+[-/][0-9]+ and inter-token whitespace is any
[ \t\r] run. So a validly-formatted existing Open written as '2024/3/5 open X'
or '2020-01-01 open X' escaped detection → duplicate Open appended →
bean-check rejects the file. Anchor on Beancount's actual date pattern and
[ \t]+ separators. Adds parametrized coverage for slash/single-digit/multi-
space/tab variants.
Found in a coherence pass over the Beancount source.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
commit
3adb3d356a
|
|
@ -60,11 +60,21 @@ def _escape_beancount_string(value: str) -> str:
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# Beancount's DATE token (parser/lexer.l): (17|18|19|20)[0-9]{2}[-/][0-9]+[-/][0-9]+
|
||||||
|
# — '-' OR '/' separators, 1+ digit month/day. Inter-token whitespace is any
|
||||||
|
# run of [ \t\r] (ignored by the lexer). The duplicate-detection regex must
|
||||||
|
# mirror this, or a validly-formatted existing Open (e.g. '2024/3/5 open X' or
|
||||||
|
# '2020-01-01 open X') escapes detection and a duplicate Open is appended,
|
||||||
|
# which bean-check then rejects — breaking every later write.
|
||||||
|
_OPEN_DATE = r"(?:17|18|19|20)\d\d[-/]\d+[-/]\d+"
|
||||||
|
|
||||||
|
|
||||||
def _open_directive_exists(source: str, account_name: str) -> bool:
|
def _open_directive_exists(source: str, account_name: str) -> bool:
|
||||||
"""Return True if `source` already contains an Open directive for exactly
|
"""Return True if `source` already contains an Open directive for exactly
|
||||||
`account_name`.
|
`account_name`.
|
||||||
|
|
||||||
Anchored to a real `YYYY-MM-DD open <account>` directive line (re.MULTILINE)
|
Anchored to a real `<date> open <account>` directive line (re.MULTILINE),
|
||||||
|
with `<date>` and the inter-token whitespace matching Beancount's grammar,
|
||||||
so the account name can't match text inside another account's description
|
so the account name can't match text inside another account's description
|
||||||
metadata or a comment (false positive → spurious 409). The trailing
|
metadata or a comment (false positive → spurious 409). The trailing
|
||||||
negative-lookahead `(?![\\w:-])` requires the next char not to be an
|
negative-lookahead `(?![\\w:-])` requires the next char not to be an
|
||||||
|
|
@ -72,12 +82,11 @@ def _open_directive_exists(source: str, account_name: str) -> bool:
|
||||||
- a prefix (Expenses:Gas) does not match a longer sibling
|
- a prefix (Expenses:Gas) does not match a longer sibling
|
||||||
(Expenses:GasStation / Expenses:Gas:Vehicle), and
|
(Expenses:GasStation / Expenses:Gas:Vehicle), and
|
||||||
- a real directive with an inline comment and no space
|
- a real directive with an inline comment and no space
|
||||||
(`open Expenses:Gas;legacy`) is still detected (`;` ends the name),
|
(`open Expenses:Gas;legacy`) is still detected (`;` ends the name).
|
||||||
which the previous `(?:\\s|$)` boundary missed → duplicate write.
|
|
||||||
"""
|
"""
|
||||||
return bool(
|
return bool(
|
||||||
re.search(
|
re.search(
|
||||||
rf"^\d{{4}}-\d{{2}}-\d{{2}} open {re.escape(account_name)}(?![\w:-])",
|
rf"^{_OPEN_DATE}[ \t]+open[ \t]+{re.escape(account_name)}(?![\w:-])",
|
||||||
source,
|
source,
|
||||||
re.MULTILINE,
|
re.MULTILINE,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -84,6 +84,22 @@ def test_open_directive_exists_does_not_match_deeper_child():
|
||||||
assert fc._open_directive_exists(src, "Expenses:Vehicle:Gas") is False
|
assert fc._open_directive_exists(src, "Expenses:Vehicle:Gas") is False
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"line",
|
||||||
|
[
|
||||||
|
"2024/3/5 open Expenses:Vehicle:Gas", # slash date, single-digit M/D
|
||||||
|
"2020-1-1 open Expenses:Vehicle:Gas", # dash date, single-digit M/D
|
||||||
|
"2020-01-01 open Expenses:Vehicle:Gas", # multiple spaces
|
||||||
|
"2020-01-01\topen\tExpenses:Vehicle:Gas", # tab separators
|
||||||
|
"1970-01-01 open Expenses:Vehicle:Gas EUR", # currency-constrained
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_open_directive_exists_matches_beancount_date_and_whitespace_variants(line):
|
||||||
|
# All of these are valid Beancount Open directives per lexer.l's DATE token
|
||||||
|
# and ignored inter-token whitespace; each must be detected as existing.
|
||||||
|
assert fc._open_directive_exists(line + "\n", "Expenses:Vehicle:Gas") is True
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# beancount_format.sanitize_link
|
# beancount_format.sanitize_link
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue