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:
parent
d2f8772e08
commit
76f2707aac
6 changed files with 100 additions and 28 deletions
|
@ -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,10 +33,11 @@ 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:
|
||||
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:
|
||||
|
@ -55,9 +46,6 @@ class FileImporter:
|
|||
break
|
||||
else:
|
||||
entry_data = hook_retval
|
||||
else:
|
||||
render_vars = collections.ChainMap(entry_data, source_vars)
|
||||
print(template.render(render_vars), file=out_file, end='')
|
||||
|
||||
def import_path(self, in_path):
|
||||
if in_path is None:
|
||||
|
|
|
@ -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():
|
||||
|
|
23
import2ledger/hooks/ledger_entry.py
Normal file
23
import2ledger/hooks/ledger_entry.py
Normal 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='')
|
|
@ -40,3 +40,8 @@ template =
|
|||
; :NonItem:
|
||||
Income:Sales -{item_sales}
|
||||
; :Item:
|
||||
|
||||
[Empty]
|
||||
template =
|
||||
|
||||
[Nonexistent]
|
||||
|
|
|
@ -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'),
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
Loading…
Reference in a new issue