Skip to content

Commit

Permalink
Write to transfer file (#4)
Browse files Browse the repository at this point in the history
* Write transfer record to another file if transfer_to_file is present

* Delete model.py to keep everything in a single file

* Fix import

* Add test for another format
  • Loading branch information
riclage authored Mar 25, 2019
1 parent a2aa2be commit ed3b346
Show file tree
Hide file tree
Showing 5 changed files with 72 additions and 23 deletions.
49 changes: 40 additions & 9 deletions icsv2ledger.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import csv
import io
import glob
import mmap
import sys
import os
import hashlib
Expand All @@ -17,11 +18,11 @@
import readline
import configparser
from argparse import HelpFormatter
from dataclasses import dataclass
from datetime import datetime
from operator import attrgetter
from locale import atof

from model import MappingInfo
from typing import AnyStr, Pattern, Optional


class FileType(object):
Expand Down Expand Up @@ -468,6 +469,19 @@ def parse_args_and_config_file():
return args


@dataclass(frozen=True)
class MappingInfo:
"""
This represents one entry in the mapping file.
"""
pattern: Pattern[AnyStr]
payee: str
account: str
tags: [str]
transfer_to: Optional[str]
transfer_to_file: Optional[str]


class Entry:
"""
This represents one entry in the CSV file.
Expand Down Expand Up @@ -863,13 +877,15 @@ def get_payee_and_account(entry):
account = options.default_expense
tags = []
transfer_to = None
transfer_to_file = None
found = False
# Try to match entry desc with mappings patterns
for m in mappings:
pattern = m.pattern
if isinstance(pattern, str):
if entry.desc == pattern:
payee, account, tags, transfer_to = m.payee, m.account, m.tags, m.transfer_to
payee, account, tags = m.payee, m.account, m.tags
transfer_to, transfer_to_file = m.transfer_to, m.transfer_to_file
found = True # do not break here, later mapping must win
else:
# If the pattern isn't a string it's a regex
Expand All @@ -880,7 +896,8 @@ def get_payee_and_account(entry):
# perform regexp substitution if captures were used
if match.groups():
payee = m.pattern.sub(m.payee, entry.desc)
account, tags, transfer_to = m.account, m.tags, m.transfer_to
account, tags = m.account, m.tags
transfer_to, transfer_to_file = m.transfer_to, m.transfer_to_file
found = True

modified = False
Expand Down Expand Up @@ -911,17 +928,17 @@ def get_payee_and_account(entry):
yn_response = prompt_for_value('Append to mapping file?', possible_yesno, 'Y')
if yn_response:
value = yn_response
if value.upper().strip() not in ('N','NO'):
if value.upper().strip() not in ('N', 'NO'):
# Add new or changed mapping to mappings and append to file
mappings.append((entry.desc, payee, account, tags))
mappings.append(MappingInfo(entry.desc, payee, account, tags, None, None))
append_mapping_file(options.mapping_file,
entry.desc, payee, account, tags)

# Add new possible_values to possible values lists
possible_payees.add(payee)
possible_accounts.add(account)

return (payee, account, tags, transfer_to)
return (payee, account, tags, transfer_to, transfer_to_file)

def process_input_output(in_file, out_file):
""" Read CSV lines either from filename or stdin.
Expand Down Expand Up @@ -983,7 +1000,7 @@ def process_csv_lines(csv_lines):
if value.upper().strip() not in ('N', 'NO'):
continue
while True:
payee, account, tags, transfer_to = get_payee_and_account(entry)
payee, account, tags, transfer_to, transfer_to_file = get_payee_and_account(entry)
value = 'C'
if options.entry_review:
# need to display ledger formatted entry here
Expand Down Expand Up @@ -1012,7 +1029,21 @@ def process_csv_lines(csv_lines):

if transfer_to is not None:
transaction_index += 1
yield entry.transfer_entry(transaction_index, payee, account, transfer_to, tags)
transfer_entry = entry.transfer_entry(transaction_index, payee, account, transfer_to, tags)
if transfer_to_file is None:
yield transfer_entry
else:
with open(transfer_to_file, "rb") as f:
if f.read(1):
with mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ) as s:
has_entry = s.find(bytes(entry.md5sum, 'utf-8')) != -1
else:
has_entry = False

if not has_entry or not options.skip_dupes:
with open(transfer_to_file, "a") as f:
f.write(transfer_entry)
f.write("\n")

try:
process_input_output(options.infile, options.outfile)
Expand Down
13 changes: 0 additions & 13 deletions model.py

This file was deleted.

1 change: 1 addition & 0 deletions tests/stubs/simple_2.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
10 Dec 2018 ; To John Doe ; 20.75 ; ; ; ; 48.35; transfers; Bob + coffee+groceries :)
3 changes: 2 additions & 1 deletion tests/stubs/simple_mapping.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
CREDIT CARD 15/12/2018 MY RESTAURANT,My Restaurant,Expenses:Dining
TRANSFER RECEIVED MR UNKNOWN,Unknown Transfer,Income:Unknown
TRANSFER RECEIVED MR UNKNOWN,Unknown Transfer,Income:Unknown
To John Doe,Unknown Transfer,Expenses:Unknown
29 changes: 29 additions & 0 deletions tests/test_conversion.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,35 @@ def test_simple_parsing(self):
Income:Unknown
Assets:Bank:Current 250.73
"""
)

def test_simple_parsing_another_format(self):
infile = open('stubs/simple_2.csv')
out = StringIO()

args = parse_args_and_config_file()
args.quiet = True
args.infile = infile
args.outfile = out
args.csv_date_format = "%d %b %Y"
args.ledger_date_format= "%Y/%m/%d"
args.skip_lines = 0
args.debit = 3
args.credit = 4
args.delimiter = ';'
args.mapping_file = 'stubs/simple_mapping.txt'
main(args)

infile.close()

self.assertEqual(
out.getvalue(), """2018/12/10 * Unknown Transfer
; MD5Sum: 5c3d6f20c79b6ba0760c43c3b9c9be47
; CSV: 10 Dec 2018 ; To John Doe ; 20.75 ; ; ; ; 48.35; transfers; Bob + coffee+groceries :)
Expenses:Unknown 20.75
Assets:Bank:Current
"""
)

Expand Down

0 comments on commit ed3b346

Please sign in to comment.