Skip to content

Commit

Permalink
Generate balance assignments for padding entries
Browse files Browse the repository at this point in the history
While ledger has no concept of "padding", it has balance assignments
and we can convert the automatically generated padding entries (P)
to balance assignments.

Fixes #33
  • Loading branch information
tbm committed Apr 22, 2022
1 parent 50005bc commit d773a0e
Show file tree
Hide file tree
Showing 5 changed files with 110 additions and 23 deletions.
16 changes: 16 additions & 0 deletions beancount2ledger/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -243,3 +243,19 @@ def map_currency(match):
string = re.sub(rf'(?<=\d\s)(")?({amount.CURRENCY_RE})\1?', map_currency, string)

return string


def gen_bal_assignment(entry, amt, indent):
"""
Generate a balance assignment
"""

string = f"{entry.date:%Y-%m-%d}"
src_acct = entry.postings[0].account
string += f" Setting account {src_acct} to {amt}\n"
amt = f"= {amt}"
len_amt = max(0, 25 - len(indent))
string += f"{indent}{src_acct:50}{amt:>{len_amt}}\n"
dest_acct = entry.postings[1].account
string += f"{indent}{dest_acct}"
return string
17 changes: 14 additions & 3 deletions beancount2ledger/hledger.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
__license__ = "GPL-2.0-or-later"

import datetime
import re

from beancount.core.amount import Amount
from beancount.core import position
Expand All @@ -17,7 +18,7 @@

from .common import ROUNDING_ACCOUNT
from .common import ledger_flag, ledger_str, quote_currency, user_meta
from .common import get_lineno, filter_rounding_postings
from .common import gen_bal_assignment, get_lineno, filter_rounding_postings
from .ledger import LedgerPrinter


Expand All @@ -34,6 +35,18 @@ def format_meta(self, key, val):
return f"{key}: {val}"

def Transaction(self, entry):
indent = " " * self.config["indent"]

if entry.flag == "P":
match = re.match(
r"\(Padding inserted for Balance of (.+) for difference",
entry.narration,
)
if match:
string = gen_bal_assignment(entry, match.group(1), indent)
self.io.write(string)
return

# Insert a posting to absorb the residual if necessary. This is
# sometimes needed because Ledger bases its balancing precision on the
# *last* number of digits used on that currency. This is believed to be
Expand Down Expand Up @@ -70,8 +83,6 @@ def Transaction(self, entry):
self.io.write(" " + payee)
self.io.write("\n")

indent = " " * self.config["indent"]

if entry.tags:
self.io.write(indent + "; {}:\n".format(":, ".join(sorted(entry.tags))))
if entry.links:
Expand Down
23 changes: 16 additions & 7 deletions beancount2ledger/ledger.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

import datetime
import io
import re

from beancount.core.amount import Amount
from beancount.core.inventory import Inventory
Expand All @@ -24,6 +25,7 @@
from .common import ledger_flag, ledger_str, quote_currency, postings_by_type, user_meta
from .common import (
set_default,
gen_bal_assignment,
get_lineno,
is_automatic_posting,
filter_rounding_postings,
Expand Down Expand Up @@ -80,6 +82,18 @@ def format_meta(self, key, val):
def Transaction(self, entry):
"""Transactions"""

indent = " " * self.config["indent"]

if entry.flag == "P":
match = re.match(
r"\(Padding inserted for Balance of (.+) for difference",
entry.narration,
)
if match:
string = gen_bal_assignment(entry, match.group(1), indent)
self.io.write(string)
return

# Insert a posting to absorb the residual if necessary. This is
# sometimes needed because Ledger bases its balancing precision on the
# *last* number of digits used on that currency. This is believed to be
Expand Down Expand Up @@ -116,8 +130,6 @@ def Transaction(self, entry):
self.io.write(" " + payee)
self.io.write("\n")

indent = " " * self.config["indent"]

if entry.tags:
self.io.write(indent + "; :{}:\n".format(":".join(sorted(entry.tags))))
if entry.links:
Expand Down Expand Up @@ -255,11 +267,8 @@ def Pad(self, entry):

# Note: We don't need to output these because when we're loading the
# Beancount file explicit padding entries will be generated
# automatically, thus balancing the accounts. Ledger does not support
# automatically padding, so we can just output this as a comment.
self.io.write(
";; Pad: {e.date:%Y-%m-%d} {e.account} {e.source_account}\n".format(e=entry)
)
# automatically. We special-case these automatically padding
# entries to generate a ledger balance assignment.

def Commodity(self, entry):
"Commodity declarations" ""
Expand Down
32 changes: 32 additions & 0 deletions tests/hledger_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -654,6 +654,38 @@ def test_preserve_posting_order(self, entries, _, ___):
result,
)

@loader.load_doc()
def test_pad(self, entries, _, ___):
"""
2022-01-01 open Assets:US:Chase:Checking
2022-01-01 open Equity:Opening-Balances
2022-01-01 * "Set opening balance"
Assets:US:Chase:Checking 400.00 USD
Equity:Opening-Balances -400.00 USD
2022-03-01 pad Assets:US:Chase:Checking Equity:Opening-Balances
2022-04-07 balance Assets:US:Chase:Checking 1000.00 USD
"""
result = beancount2ledger.convert(entries)
self.assertLines(
r"""
account Assets:US:Chase:Checking
account Equity:Opening-Balances
2022-01-01 * Set opening balance
Assets:US:Chase:Checking 400.00 USD
Equity:Opening-Balances -400.00 USD
2022-03-01 Setting account Assets:US:Chase:Checking to 1000.00 USD
Assets:US:Chase:Checking = 1000.00 USD
Equity:Opening-Balances
""",
result,
)

def test_example(self):
"""
Test converted example with hledger
Expand Down
45 changes: 32 additions & 13 deletions tests/ledger_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -340,19 +340,6 @@ def test_metadata_posting(self, entries, _, ___):
result,
)

@loader.load_doc()
def test_metadata_none(self, entries, _, ___):
"""
2000-01-01 open Assets:Test1
2000-01-01 open Assets:Test2
2019-01-21 pad Assets:Test1 Assets:Test2
2019-01-22 balance Assets:Test1 10.00 GBP
"""
# padding (P) entries don't set entry.meta or posting.meta, so
# convert to make sure we don't crash.
_ = beancount2ledger.convert(entries)

@loader.load_doc()
def test_cost_info(self, entries, _, ___):
"""
Expand Down Expand Up @@ -985,6 +972,38 @@ def test_preserve_posting_order(self, entries, _, ___):
result,
)

@loader.load_doc()
def test_pad(self, entries, _, ___):
"""
2022-01-01 open Assets:US:Chase:Checking
2022-01-01 open Equity:Opening-Balances
2022-01-01 * "Set opening balance"
Assets:US:Chase:Checking 400.00 USD
Equity:Opening-Balances -400.00 USD
2022-03-01 pad Assets:US:Chase:Checking Equity:Opening-Balances
2022-04-07 balance Assets:US:Chase:Checking 1000.00 USD
"""
result = beancount2ledger.convert(entries)
self.assertLines(
r"""
account Assets:US:Chase:Checking
account Equity:Opening-Balances
2022-01-01 * Set opening balance
Assets:US:Chase:Checking 400.00 USD
Equity:Opening-Balances -400.00 USD
2022-03-01 Setting account Assets:US:Chase:Checking to 1000.00 USD
Assets:US:Chase:Checking = 1000.00 USD
Equity:Opening-Balances
""",
result,
)

def test_example(self):
with tempfile.NamedTemporaryFile(
"w", suffix=".beancount", encoding="utf-8"
Expand Down

0 comments on commit d773a0e

Please sign in to comment.