From 7a8813092219e4790450806e1bd355313cf52fa8 Mon Sep 17 00:00:00 2001 From: Simon Michael Date: Thu, 10 Oct 2024 09:53:29 -1000 Subject: [PATCH] ;examples: csv: add an example python converter script --- examples/csv/csv-hledger-1.py | 87 +++++++++++++++++++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 examples/csv/csv-hledger-1.py diff --git a/examples/csv/csv-hledger-1.py b/examples/csv/csv-hledger-1.py new file mode 100644 index 00000000000..14c30dc3494 --- /dev/null +++ b/examples/csv/csv-hledger-1.py @@ -0,0 +1,87 @@ +#!/usr/bin/env python3 +# An example of using Python to convert a certain CSV to hledger journal entries. +# This won't work as-is (unless you have this particular kind of CSV); +# use it for inspiration. hledger's own CSV rules are not used at all here. + + +__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()