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:
in_file.seek(0)
if importer.can_import(in_file):
try:
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))
importers.append(importer)
if not importers:
raise errors.UserInputFileError("no importers available", in_file.name)
source_vars = {
@ -43,21 +33,19 @@ class FileImporter:
'source_path': in_path.as_posix(),
'source_stem': in_path.stem,
}
with self.config.open_output_file() as out_file:
for importer, template in importers:
in_file.seek(0)
for entry_data in importer(in_file):
for hook in self.hooks:
hook_retval = hook.run(entry_data)
if hook_retval is None:
pass
elif hook_retval is False:
break
else:
entry_data = hook_retval
for importer in importers:
in_file.seek(0)
source_vars['template'] = importer.TEMPLATE_KEY
for entry_data in importer(in_file):
entry_data = collections.ChainMap(entry_data, source_vars)
for hook in self.hooks:
hook_retval = hook.run(entry_data)
if hook_retval is None:
pass
elif hook_retval is False:
break
else:
render_vars = collections.ChainMap(entry_data, source_vars)
print(template.render(render_vars), file=out_file, end='')
entry_data = hook_retval
def import_path(self, in_path):
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
# processing the entry.
'DATA_FILTER',
# OUTPUT hooks run last, sending the data somewhere else.
'OUTPUT',
])
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:
Income:Sales -{item_sales}
; :Item:
[Empty]
template =
[Nonexistent]

View file

@ -5,13 +5,19 @@ import itertools
import pytest
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():
all_hooks = list(hooks.load_all())
positions = {hook: index for index, hook in enumerate(all_hooks)}
assert positions[default_date.DefaultDateHook] < positions[add_entity.AddEntityHook]
assert positions[add_entity.AddEntityHook] < positions[filter_by_date.FilterByDateHook]
expected_order = [
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', [
('payee', 'Alex Smith', 'entity', 'Smith-Alex'),

View file

@ -1,11 +1,14 @@
import collections
import configparser
import contextlib
import datetime
import decimal
import io
import pathlib
import pytest
from import2ledger import errors, template
from import2ledger.hooks import ledger_entry
from . import DATA_DIR, normalize_whitespace
@ -199,3 +202,48 @@ def test_line1_not_custom_payee():
def test_bad_amount_expression(amount_expr):
with pytest.raises(errors.UserInputError):
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)