hledger/examples/csv/foocsv2hledger.py
2025-11-23 10:16:47 -08:00

92 lines
3.1 KiB
Python

#!/usr/bin/env python3
# Here's an example of converting a particular CSV to hledger journal
# entries with your own custom script, without using hledger at all.
# This is worth considering if the conversion is hard to do with hledger's CSV rules.
# (You give up some of the domain knowledge built in to rules,
# but gain the full power of a programming language.)
#
# This script won't work as-is (without the original foo.csv); use it for inspiration.
__version__ = "1.0"
__author__ = "Simon Michael"
VERSION = "%prog " + __version__
USAGE = """%prog [options] [CSVFILE [JOURNALFILE]]
Reads a [certain kind of] CSV, writes a hledger journal.
Journal entries will be in the same order as the CSV records.
Requirements: python 3.
"""
# from pprint import pprint as pp
import csv
import datetime
import optparse
import re
import subprocess
import sys
import tempfile
# import warnings
def parse_options():
parser = optparse.OptionParser(usage=USAGE, version=VERSION)
opts, args = parser.parse_args()
if len(args) > 2:
parser.print_help()
sys.exit()
return opts, args
def single_space(ms):
if ms is not None: ms = re.sub(r' +',' ',ms)
return ms
# Join two strings with the give separator, but omit the separator
# if either string is empty.
def intercalate2(sep,astr,bstr):
if astr and bstr:
return astr + sep + bstr
else:
return astr + bstr
def main():
opts, args = parse_options()
out = open(args[1],'w') if len(args) > 1 else sys.stdout
with open(args[0],'r') if len(args) > 0 else sys.stdin as csvfile:
# Process CSV records, and generate a hledger journal entry for each transaction.
for r in csv.reader(csvfile):
# skip headings (record containing no numbers)
if all(map(lambda v: not any(c.isdigit() for c in v), r)): continue
# hledger doesn't like records with only one field
if len(r) < 2: continue
# skip non-transaction records
if re.match(r'^(Starting Balance|Net Change|Total|)$',r[0]): continue
# write a hledger journal entry
property_,date_,payee_payer_,type_,reference_,debit_,credit_,balance_,description_,gl_account_ = r
date = datetime.datetime.strptime(date_,"%m/%d/%Y").date().isoformat()
code = f" ({reference_})" if reference_ else ""
description = intercalate2(' | ', payee_payer_, intercalate2(' ', description_, type_))
amount1 = f"${debit_}" if debit_ else ""
amount2 = f"${credit_}" if credit_ else ""
balance1 = balance_
account1 = f"Properties:{property_}"
if gl_account_[0].isdigit():
account2 = f"{gl_account_[0]}xxx:{gl_account_}"
else:
account2 = f"{gl_account_}"
account1, account2 = single_space(account1), single_space(account2)
out.write(f"""\
{date}{code} {description}
{account1} {amount1} ; = {balance1}
{account2} {amount2}
""")
if __name__ == "__main__": main()