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>
This commit is contained in:
parent
39440b75a7
commit
3adb3d356a
2 changed files with 29 additions and 4 deletions
|
|
@ -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:
|
||||
"""Return True if `source` already contains an Open directive for exactly
|
||||
`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
|
||||
metadata or a comment (false positive → spurious 409). The trailing
|
||||
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
|
||||
(Expenses:GasStation / Expenses:Gas:Vehicle), and
|
||||
- a real directive with an inline comment and no space
|
||||
(`open Expenses:Gas;legacy`) is still detected (`;` ends the name),
|
||||
which the previous `(?:\\s|$)` boundary missed → duplicate write.
|
||||
(`open Expenses:Gas;legacy`) is still detected (`;` ends the name).
|
||||
"""
|
||||
return bool(
|
||||
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,
|
||||
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
|
||||
|
||||
|
||||
@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
|
||||
# ---------------------------------------------------------------------------
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue