-
Notifications
You must be signed in to change notification settings - Fork 9
/
Copy pathgnucash2ledger.py
executable file
·119 lines (102 loc) · 3.9 KB
/
gnucash2ledger.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
#!/usr/bin/env python3
"""
Converts gnucash3 sqlite files to ledger format.
"""
from __future__ import annotations
import sys
import gnucash
from gnucash import Account, Commodity
def format_commodity(commodity: Commodity) -> str:
mnemonic = commodity.mnemonic
try:
if mnemonic.encode("ascii").isalpha():
return mnemonic
except Exception:
pass
return f'"{mnemonic}"' # TODO: escape " char in mnemonic
def full_acc_name(acc: Account) -> str:
result = ""
parent = acc.parent
assert parent is not None
parent_parent = parent.parent
if parent_parent is not None:
result = full_acc_name(parent) + ":"
result += acc.name
return result
def no_nl(string: str) -> str:
return string.replace("\n", " ")
def _main() -> None:
out = sys.stdout
if len(sys.argv) == 1:
sys.stderr.write(f"Invocation: {sys.argv[0]} gnucash_filename\n")
sys.exit(1)
data = gnucash.read_file(sys.argv[1])
commodities = data.commodities.values()
for commodity in commodities:
if not commodity.mnemonic:
continue
out.write(f"commodity {format_commodity(commodity)}\n")
if commodity.fullname:
out.write(f"\tnote {no_nl(commodity.fullname)}\n")
out.write("\n")
accounts = list(data.accounts.values())
accounts.sort(key=lambda acc: acc.guid)
for acc in accounts:
# ignore "dummy" accounts
if acc.type is None or acc.type == "ROOT":
continue
if str(acc.commodity) == "template":
continue
out.write(f"account {full_acc_name(acc)}\n")
if acc.description != "":
out.write(f"\tnote {no_nl(acc.description)}\n")
formated_commodity = format_commodity(acc.commodity)
formated_commodity = formated_commodity.replace('"', '\\"')
out.write(f'\tcheck commodity == "{formated_commodity}"\n')
out.write("\n")
# Prices
prices = list(data.prices.values())
prices.sort(key=lambda price: price.date)
for price in prices:
date = price.date.strftime("%Y/%m/%d %H:%M:%S")
price_commodity = format_commodity(price.commodity)
price_currency = format_commodity(price.currency)
out.write(f"P {date} {price_commodity} {price.value} {price_currency}\n")
out.write("\n")
transactions = list(data.transactions.values())
transactions.sort(key=lambda transaction: transaction.post_date)
for trans in transactions:
date = trans.post_date.strftime("%Y/%m/%d")
code = f"({no_nl(trans.num.replace(')', ''))}) " if trans.num else ""
description = no_nl(trans.description)
out.write(f"{date} * {code}{description}\n")
for split in trans.splits:
# Ensure 2 spaces after account name
out.write(f"\t{full_acc_name(split.account):<40s} ")
trans_currency = format_commodity(trans.currency)
if split.account.commodity != trans.currency:
commodity_precision = split.account.commodity.precision
split_acc_commodity = format_commodity(split.account.commodity)
quantity = split.quantity
value = abs(split.value)
out.write(
"%10.*f %s @@ %.2f %s" # noqa: UP031
% (
commodity_precision,
quantity,
split_acc_commodity,
value,
trans_currency,
)
)
else:
commodity_precision = trans.currency.precision
out.write(
"%10.*f %s" % (commodity_precision, split.value, trans_currency) # noqa: UP031
)
if split.memo:
out.write(f" ; {no_nl(split.memo)}")
out.write("\n")
out.write("\n")
if __name__ == "__main__":
_main()