Skip to content

Commit

Permalink
fix export of ledger commodities to ledger (#215)
Browse files Browse the repository at this point in the history
- add is_currency() to Commodity
- do not add the "check commodity" in ledger export if commodity is not a currency
- properly escape commodity names
  • Loading branch information
sebastiendementen committed Jun 17, 2024
1 parent 9a3d010 commit c0b84a1
Show file tree
Hide file tree
Showing 6 changed files with 30 additions and 36 deletions.
Binary file modified gnucash_books/complex_sample.gnucash
Binary file not shown.
2 changes: 1 addition & 1 deletion piecash/core/factories.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ def create_stock_accounts(
:class:`piecash.core.account.Account`: a tuple with the account under the broker_account where the stock is held
and the list of income accounts.
"""
if cdty.namespace == "CURRENCY":
if cdty.is_currency():
raise GnucashException(
"{} is a currency ! You can't create stock_accounts for currencies".format(
cdty
Expand Down
2 changes: 1 addition & 1 deletion piecash/core/transaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -314,7 +314,7 @@ def validate(self):
if old["STATE_CHANGES"][-1] == "deleted":
return

if self.currency.namespace != "CURRENCY":
if not self.currency.is_currency():
raise GncValidationError(
"You are assigning a non currency commodity to a transaction"
)
Expand Down
57 changes: 24 additions & 33 deletions piecash/ledger.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
from __future__ import unicode_literals

import json
import re
from functools import singledispatch
from locale import getdefaultlocale

from .core import Transaction, Account, Commodity, Price, Book
from .core import Account, Book, Commodity, Price, Transaction

"""original script from https://github.com/MatzeB/pygnucash/blob/master/gnucash2ledger.py by Matthias Braun [email protected]
adapted for:
Expand All @@ -27,34 +28,34 @@ def ledger(obj, **kwargs):


CURRENCY_RE = re.compile("^[A-Z]{3}$")
NUMBER_RE = re.compile("[0-9., ]")
NUMBER_RE = re.compile(r"(^|\s+)[-+]?([0-9]*\.[0-9]+|[0-9]+)($|\s+)") # regexp to identify a float with blank spaces
NUMERIC_SPACE = re.compile(r"[0-9\s]")
CHARS_ONLY = re.compile(r"[A-Za-z]+")


def quote_commodity(mnemonic):
if not CHARS_ONLY.fullmatch(mnemonic):
return json.dumps(mnemonic)
else:
return mnemonic


def format_commodity(mnemonic, locale):
if CURRENCY_RE.match(mnemonic):
# format the commodity
if CURRENCY_RE.match(mnemonic) or True:
# format the currency via BABEL if available and then remove the number/amount
s = format_currency(0, 0, mnemonic, locale)

# remove the non currency part and real white spaces
return NUMBER_RE.sub("", s)
else:
if NUMBER_RE.search(mnemonic):
return '"{}"'.format(mnemonic)
else:
return mnemonic

return NUMBER_RE.sub("", s)
# just quote the commodity
return quote_commodity(mnemonic)


def format_currency(
amount, decimals, currency, locale=False, decimal_quantization=True
):
def format_currency(amount, decimals, currency, locale=False, decimal_quantization=True):
currency = quote_commodity(currency)
if locale is True:
locale = getdefaultlocale()[0]
if BABEL_AVAILABLE is False:
raise ValueError(
f"You must install babel ('pip install babel') to export to ledger in your locale '{locale}'"
)
raise ValueError(f"You must install babel ('pip install babel') to export to ledger in your locale '{locale}'")
else:
return babel.numbers.format_currency(
amount,
Expand Down Expand Up @@ -115,11 +116,7 @@ def _(tr, locale=False, **kwargs):
)
)
else:
s.append(
format_currency(
split.value, tr.currency.precision, tr.currency.mnemonic, locale
)
)
s.append(format_currency(split.value, tr.currency.precision, tr.currency.mnemonic, locale))

if split.memo:
s.append(" ; {:20}".format(split.memo))
Expand Down Expand Up @@ -157,9 +154,8 @@ def _(acc, short_account_names=False, **kwargs):
if acc.description != "":
res += "\tnote {}\n".format(acc.description)

res += '\tcheck commodity == "{}"\n'.format(
acc.commodity.mnemonic
) # .replace('"', '\\"'))
if acc.commodity.is_currency():
res += '\tcheck commodity == "{}"\n'.format(acc.commodity.mnemonic) # .replace('"', '\\"'))
return res


Expand Down Expand Up @@ -192,18 +188,13 @@ def _(book, **kwargs):
if kwargs.get("short_account_names"): # check that no ambiguity in account names
accounts = [acc.name for acc in book.accounts]
if len(accounts) != len(set(accounts)):
raise ValueError(
"You have duplicate short names in your book. "
"You cannot use the 'short_account_names' option."
)
raise ValueError("You have duplicate short names in your book. " "You cannot use the 'short_account_names' option.")
for acc in book.accounts:
res.append(ledger(acc, **kwargs))
res.append("\n")

# Prices
for price in sorted(
book.prices, key=lambda x: (x.commodity_guid, x.currency_guid, x.date)
):
for price in sorted(book.prices, key=lambda x: (x.commodity_guid, x.currency_guid, x.date)):
res.append(ledger(price, **kwargs))
res.append("\n")

Expand Down
3 changes: 3 additions & 0 deletions piecash/scripts/export.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,3 +66,6 @@ def export(book, entities, output, inactive):
)

output.write(res)

if __name__ == '__main__':
cli()
2 changes: 1 addition & 1 deletion piecash/scripts/qif_export.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ def sort_split(sp):
continue # skip template transactions

splits = sorted(tr.splits, key=sort_split)
if all(sp.account.commodity.namespace == "CURRENCY" for sp in splits):
if all(sp.account.commodity.is_currency() for sp in splits):

sp1, sp2 = splits[:2]
item = _qif.Transaction(
Expand Down

0 comments on commit c0b84a1

Please sign in to comment.