diff --git a/CODE.rst b/CODE.rst index 10d90337653ab0088402d4f58efe65c8cded0a48..75327523374bf9a99b05900b4e0185ee155b3bee 100644 --- a/CODE.rst +++ b/CODE.rst @@ -51,7 +51,13 @@ Hooks make arbitrary transformations to entry data dicts. Every entry data dict Initializes the hook with the user's configuration. ``run(entry_data)`` - This method makes the hook's transformations to the entry data dict, if any. If this method sets ``entry_data['_hook_cancel']`` to a truthy value, that entry will not be output. + This method can make arbitrary transformations to the entry data, or filter it so it isn't output. + + If this method returns ``None``, processing the entry data continues normally. Most hooks should do this, and just transform entry data in place. + + If this method returns ``False``, processing the entry data stops immediately. The entry will not appear in the program output. + + If this method returns any other value, the program replaces the entry data with the return value, and continues processing. Templates ~~~~~~~~~ @@ -86,9 +92,14 @@ At a high level, import2ledger handles each input file this way:: input_file.seek(0) for entry_data in importer_class(input_file): for hook in all_hooks: - hook.run(entry_data) - if entry_data: - template.render(entry_data) + hook_return = hook.run(entry_data) + if hook_return is False: + break + elif hook_return is not None: + entry_data = hook_return + else: + if entry_data: + template.render(entry_data) Note in particular that multiple importers can handle the same input file. This helps support inputs like Patreon's earnings CSV, where completely different transactions are generated from the same source. diff --git a/import2ledger/__main__.py b/import2ledger/__main__.py index 068460118add55a3249c7ad1b695a7c9504c93ad..6d3cd0e826145b5167960feaf273172b57179357 100644 --- a/import2ledger/__main__.py +++ b/import2ledger/__main__.py @@ -49,13 +49,15 @@ class FileImporter: default_date = self.config.get_default_date() in_file.seek(0) for entry_data in importer(in_file): - entry_data['_hook_cancel'] = False for hook in self.hooks: - hook.run(entry_data) - if entry_data['_hook_cancel']: + hook_retval = hook.run(entry_data) + if hook_retval is None: + pass + elif hook_retval is False: break + else: + entry_data = hook_retval else: - del entry_data['_hook_cancel'] render_vars = collections.ChainMap(entry_data, source_vars) print(template.render(render_vars), file=out_file, end='') diff --git a/import2ledger/hooks/filter_by_date.py b/import2ledger/hooks/filter_by_date.py index 4c0883033c68080c8cbcb6bded31493c3c7ca685..78e35c50f44fd977a2a437d088c575913bde2143 100644 --- a/import2ledger/hooks/filter_by_date.py +++ b/import2ledger/hooks/filter_by_date.py @@ -9,4 +9,4 @@ class FilterByDateHook: pass else: if not self.config.date_in_want_range(date): - entry_data['_hook_cancel'] = True + return False diff --git a/tests/test_hooks.py b/tests/test_hooks.py index e30632a6e2d41f72d7bd8e11f82f8f46ace08617..63be848477998b6e495d5a476a0a1e5959a9eca2 100644 --- a/tests/test_hooks.py +++ b/tests/test_hooks.py @@ -57,8 +57,7 @@ class DateRangeConfig: def test_filter_by_date(entry_date, start_date, end_date, allowed): entry_data = {'date': entry_date} hook = filter_by_date.FilterByDateHook(DateRangeConfig(start_date, end_date)) - hook.run(entry_data) - assert entry_data.get('_hook_cancel', False) == (not allowed) + assert hook.run(entry_data) is (None if allowed else False) class DefaultDateConfig: ONE_DAY = datetime.timedelta(days=1)