#!/bin/python3 import sys import csv import re from pathlib import Path whitelist_cards = ["7756"] in_header = ['Utført dato', 'Bokført dato', 'Rentedato', 'Beskrivelse', 'Type', 'Undertype', 'Fra konto', 'Avsender', 'Til konto', 'Mottakernavn', 'Beløp inn', 'Beløp ut', 'Valuta', 'Status', 'Melding'] out_header = ['Date', 'Payee', 'Memo', 'Outflow', 'Inflow'] def usage(): print('Usage: ynab.py ') def is_reserved(row): return row['Status'] == "Reservert" def convert_memo(original): original = original.replace(" Kurs: 1.0000", "") words = original.split(" ") for i in range(20): if words[0] == "": # It's empty del words[0] elif m := re.match(r'\*(\d{4})', words[0]): # It's the last four digits of a card if m.groups()[0] in whitelist_cards: # It's an expected card, ignore it del words[0] else: # It's an unexpected card, move it to the end words.append(words.pop(0)) elif m := re.match(r'\d{2}\.\d{2}', words[0]): # It's the date. Move it to the end words.append(words.pop(0)) elif (m1 := re.match(r'^[A-Z]{3}$', words[0])) and (m2 := re.match(r'[\d]+\.[\d]+', words[1])): # It's the original currency if words[0] == "NOK": # It's Norwegian kroner, ignoring del words[0] del words[0] else: # It's some other currency, move it to the end words.append(words.pop(0)) words.append(words.pop(0)) else: break else: raise Exception(f"Infinite loop while parsing \"{original}\"") return " ".join(words) def convert_row(row): if is_reserved(row): return None if row['Valuta'] != 'NOK': raise ValueError(f"Unknown currency {row['Valuta']}") return [ row['Bokført dato'], row['Beskrivelse'], convert_memo(row['Melding']), -float(row['Beløp ut'] or '0'), float(row['Beløp inn'] or '0'), ] def convert(reader, writer): writer.writerow(out_header) # Ignore header reader.__next__() for raw_row in reader: # Stop when we hit an empty row for field in raw_row: if field: break else: break # Create dictionary with proper field names row = {x[0]: x[1] for x in zip(in_header, raw_row)} try: if (result := convert_row(row)) is not None: writer.writerow(result) except Exception as e: print(f'Error in row {row}') raise e def main(): if len(sys.argv) != 2: usage() exit(1) filepath = Path(sys.argv[1]) new_basename = f'ynab-{filepath.name}' new_filepath = filepath.parent / new_basename with filepath.open(mode='r', encoding='latin1') as old_file: reader = csv.reader(old_file, delimiter=';') with new_filepath.open(mode='w') as new_file: writer = csv.writer(new_file) convert(reader, writer) if __name__ == '__main__': main()