hooks.ledger_entry: New hook to output Ledger entries.

This is roughly the smallest diff necessary to move output to a hook.
There's a lot of code reorganization that should still happen to bring it
better in line with this new structure.
This commit is contained in:
Brett Smith 2017-12-31 12:26:49 -05:00
parent d2f8772e08
commit 76f2707aac
6 changed files with 100 additions and 28 deletions

View file

@ -22,17 +22,7 @@ class FileImporter:
for importer in self.importers: for importer in self.importers:
in_file.seek(0) in_file.seek(0)
if importer.can_import(in_file): if importer.can_import(in_file):
try: importers.append(importer)
template = self.config.get_template(importer.TEMPLATE_KEY)
except errors.UserInputConfigurationError as error:
if error.strerror.startswith('template not defined '):
have_template = False
else:
raise
else:
have_template = not template.is_empty()
if have_template:
importers.append((importer, template))
if not importers: if not importers:
raise errors.UserInputFileError("no importers available", in_file.name) raise errors.UserInputFileError("no importers available", in_file.name)
source_vars = { source_vars = {
@ -43,21 +33,19 @@ class FileImporter:
'source_path': in_path.as_posix(), 'source_path': in_path.as_posix(),
'source_stem': in_path.stem, 'source_stem': in_path.stem,
} }
with self.config.open_output_file() as out_file: for importer in importers:
for importer, template in importers: in_file.seek(0)
in_file.seek(0) source_vars['template'] = importer.TEMPLATE_KEY
for entry_data in importer(in_file): for entry_data in importer(in_file):
for hook in self.hooks: entry_data = collections.ChainMap(entry_data, source_vars)
hook_retval = hook.run(entry_data) for hook in self.hooks:
if hook_retval is None: hook_retval = hook.run(entry_data)
pass if hook_retval is None:
elif hook_retval is False: pass
break elif hook_retval is False:
else: break
entry_data = hook_retval
else: else:
render_vars = collections.ChainMap(entry_data, source_vars) entry_data = hook_retval
print(template.render(render_vars), file=out_file, end='')
def import_path(self, in_path): def import_path(self, in_path):
if in_path is None: if in_path is None:

View file

@ -19,6 +19,8 @@ HOOK_KINDS = enum.Enum('HOOK_KINDS', [
# DATA_FILTER hooks make a decision about whether or not to proceed with # DATA_FILTER hooks make a decision about whether or not to proceed with
# processing the entry. # processing the entry.
'DATA_FILTER', 'DATA_FILTER',
# OUTPUT hooks run last, sending the data somewhere else.
'OUTPUT',
]) ])
def load_all(): def load_all():

View file

@ -0,0 +1,23 @@
from . import HOOK_KINDS
from .. import errors
class LedgerEntryHook:
KIND = HOOK_KINDS.OUTPUT
def __init__(self, config):
self.config = config
def run(self, entry_data):
try:
template = self.config.get_template(entry_data['template'])
except errors.UserInputConfigurationError as error:
if error.strerror.startswith('template not defined '):
have_template = False
else:
raise
else:
have_template = not template.is_empty()
if have_template:
with self.config.open_output_file() as out_file:
print(template.render(entry_data), file=out_file, end='')

View file

@ -40,3 +40,8 @@ template =
; :NonItem: ; :NonItem:
Income:Sales -{item_sales} Income:Sales -{item_sales}
; :Item: ; :Item:
[Empty]
template =
[Nonexistent]

View file

@ -5,13 +5,19 @@ import itertools
import pytest import pytest
from import2ledger import hooks from import2ledger import hooks
from import2ledger.hooks import add_entity, default_date, filter_by_date from import2ledger.hooks import add_entity, default_date, filter_by_date, ledger_entry
def test_load_all(): def test_load_all():
all_hooks = list(hooks.load_all()) all_hooks = list(hooks.load_all())
positions = {hook: index for index, hook in enumerate(all_hooks)} positions = {hook: index for index, hook in enumerate(all_hooks)}
assert positions[default_date.DefaultDateHook] < positions[add_entity.AddEntityHook] expected_order = [
assert positions[add_entity.AddEntityHook] < positions[filter_by_date.FilterByDateHook] default_date.DefaultDateHook,
add_entity.AddEntityHook,
filter_by_date.FilterByDateHook,
ledger_entry.LedgerEntryHook,
]
actual_order = list(sorted(expected_order, key=positions.__getitem__))
assert actual_order == expected_order
@pytest.mark.parametrize('in_key,payee,out_key,expected', [ @pytest.mark.parametrize('in_key,payee,out_key,expected', [
('payee', 'Alex Smith', 'entity', 'Smith-Alex'), ('payee', 'Alex Smith', 'entity', 'Smith-Alex'),

View file

@ -1,11 +1,14 @@
import collections import collections
import configparser import configparser
import contextlib
import datetime import datetime
import decimal import decimal
import io
import pathlib import pathlib
import pytest import pytest
from import2ledger import errors, template from import2ledger import errors, template
from import2ledger.hooks import ledger_entry
from . import DATA_DIR, normalize_whitespace from . import DATA_DIR, normalize_whitespace
@ -199,3 +202,48 @@ def test_line1_not_custom_payee():
def test_bad_amount_expression(amount_expr): def test_bad_amount_expression(amount_expr):
with pytest.raises(errors.UserInputError): with pytest.raises(errors.UserInputError):
template.Template(" Income " + amount_expr) template.Template(" Income " + amount_expr)
class Config:
def __init__(self):
self.stdout = io.StringIO()
@contextlib.contextmanager
def open_output_file(self):
yield self.stdout
def get_template(self, key):
try:
return template_from(key)
except KeyError:
raise errors.UserInputConfigurationError(
"template not defined in test config", key)
def run_hook(entry_data):
hook_config = Config()
hook = ledger_entry.LedgerEntryHook(hook_config)
assert hook.run(entry_data) is None
stdout = hook_config.stdout.getvalue()
return normalize_whitespace(stdout).splitlines()
def hook_vars(template_key, payee, amount):
return template_vars(payee, amount, other_vars={'template': template_key})
def test_hook_renders_template():
entry_data = hook_vars('Simplest', 'BB', '0.99')
lines = run_hook(entry_data)
assert lines == [
"",
"2015/03/14 BB",
" Accrued:Accounts Receivable 0.99 USD",
" Income:Donations -0.99 USD",
]
def test_hook_handles_empty_template():
entry_data = hook_vars('Empty', 'CC', 1)
assert not run_hook(entry_data)
def test_hook_handles_template_undefined():
entry_data = hook_vars('Nonexistent', 'DD', 1)
assert not run_hook(entry_data)