reports: All reports support rewrite rules.

I realized that if ledger-report supported rewrite rules, then it would
include all the information necessary to reproduce the numbers on the
statement of functional expenses.

With that, it was easy enough to add support to the rest of the reports for
consistency's sake.
This commit is contained in:
Brett Smith 2020-08-31 14:19:00 -04:00
parent 8c81eceb2c
commit 35804db617
7 changed files with 51 additions and 20 deletions

View file

@ -119,6 +119,7 @@ class ExitCode(enum.IntEnum):
NoConfig = NoConfiguration NoConfig = NoConfiguration
NoDataFiltered = os.EX_DATAERR NoDataFiltered = os.EX_DATAERR
NoDataLoaded = os.EX_NOINPUT NoDataLoaded = os.EX_NOINPUT
RewriteRulesError = os.EX_DATAERR
# Our own exit codes, working down from that range # Our own exit codes, working down from that range
BeancountErrors = 63 BeancountErrors = 63
@ -253,6 +254,17 @@ def add_loglevel_argument(parser: argparse.ArgumentParser,
f" Default {default.name.lower()}.", f" Default {default.name.lower()}.",
) )
def add_rewrite_rules_argument(parser: argparse.ArgumentParser) -> argparse.Action:
return parser.add_argument(
'--rewrite-rules', '--rewrites', '-r',
action='append',
default=[],
metavar='PATH',
type=Path,
help="""Use rewrite rules from the given YAML file. You can specify
this option multiple times to load multiple sets of rewrite rules in order.
""")
def add_version_argument(parser: argparse.ArgumentParser) -> argparse.Action: def add_version_argument(parser: argparse.ArgumentParser) -> argparse.Action:
progname = parser.prog or sys.argv[0] progname = parser.prog or sys.argv[0]
return parser.add_argument( return parser.add_argument(

View file

@ -115,6 +115,7 @@ import rt
from beancount.parser import printer as bc_printer from beancount.parser import printer as bc_printer
from . import core from . import core
from . import rewrite
from .. import books from .. import books
from .. import cliutil from .. import cliutil
from .. import config as configmod from .. import config as configmod
@ -647,6 +648,7 @@ def filter_search(postings: Iterable[data.Posting],
def parse_arguments(arglist: Optional[Sequence[str]]=None) -> argparse.Namespace: def parse_arguments(arglist: Optional[Sequence[str]]=None) -> argparse.Namespace:
parser = argparse.ArgumentParser(prog=PROGNAME) parser = argparse.ArgumentParser(prog=PROGNAME)
cliutil.add_version_argument(parser) cliutil.add_version_argument(parser)
cliutil.add_rewrite_rules_argument(parser)
parser.add_argument( parser.add_argument(
'--report-type', '-t', '--report-type', '-t',
metavar='NAME', metavar='NAME',
@ -721,9 +723,16 @@ def main(arglist: Optional[Sequence[str]]=None,
for error in load_errors: for error in load_errors:
bc_printer.print_error(error, file=stderr) bc_printer.print_error(error, file=stderr)
postings = list(filter_search( postings_src = data.Posting.from_entries(entries)
data.Posting.from_entries(entries), args.search_terms, for rewrite_path in args.rewrite_rules:
)) try:
ruleset = rewrite.RewriteRuleset.from_yaml(rewrite_path)
except ValueError as error:
logger.critical("failed loading rewrite rules from %s: %s",
rewrite_path, error.args[0])
return cliutil.ExitCode.RewriteRulesError
postings_src = ruleset.rewrite(postings_src)
postings = list(filter_search(postings_src, args.search_terms))
if not postings: if not postings:
logger.warning("no matching entries found to report") logger.warning("no matching entries found to report")
returncode = returncode or cliutil.ExitCode.NoDataFiltered returncode = returncode or cliutil.ExitCode.NoDataFiltered

View file

@ -20,7 +20,6 @@ import datetime
import enum import enum
import logging import logging
import operator import operator
import os
import sys import sys
from decimal import Decimal from decimal import Decimal
@ -633,15 +632,7 @@ The default is one year ago.
help="""Date to stop reporting entries, exclusive, in YYYY-MM-DD format. help="""Date to stop reporting entries, exclusive, in YYYY-MM-DD format.
The default is a year after the start date. The default is a year after the start date.
""") """)
parser.add_argument( cliutil.add_rewrite_rules_argument(parser)
'--rewrite-rules', '--rewrites', '-r',
action='append',
default=[],
metavar='PATH',
type=Path,
help="""Use rewrite rules from the given YAML file. You can specify
this option multiple times to load multiple sets of rewrite rules in order.
""")
parser.add_argument( parser.add_argument(
'--fund-metadata-key', '-m', '--fund-metadata-key', '-m',
metavar='KEY', metavar='KEY',
@ -705,7 +696,7 @@ def main(arglist: Optional[Sequence[str]]=None,
except ValueError as error: except ValueError as error:
logger.critical("failed loading rewrite rules from %s: %s", logger.critical("failed loading rewrite rules from %s: %s",
rewrite_path, error.args[0]) rewrite_path, error.args[0])
return os.EX_DATAERR return cliutil.ExitCode.RewriteRulesError
postings = ruleset.rewrite(postings) postings = ruleset.rewrite(postings)
balances = Balances( balances = Balances(

View file

@ -74,6 +74,7 @@ import odf.table # type:ignore[import]
from beancount.parser import printer as bc_printer from beancount.parser import printer as bc_printer
from . import core from . import core
from . import rewrite
from .. import books from .. import books
from .. import cliutil from .. import cliutil
from .. import config as configmod from .. import config as configmod
@ -308,6 +309,7 @@ The default is one year ago.
help="""Date to stop reporting entries, exclusive, in YYYY-MM-DD format. help="""Date to stop reporting entries, exclusive, in YYYY-MM-DD format.
The default is a year after the start date. The default is a year after the start date.
""") """)
cliutil.add_rewrite_rules_argument(parser)
parser.add_argument( parser.add_argument(
'--report-type', '-t', '--report-type', '-t',
metavar='TYPE', metavar='TYPE',
@ -378,11 +380,19 @@ def main(arglist: Optional[Sequence[str]]=None,
for error in load_errors: for error in load_errors:
bc_printer.print_error(error, file=stderr) bc_printer.print_error(error, file=stderr)
postings = ( postings = iter(
post post
for post in data.Posting.from_entries(entries) for post in data.Posting.from_entries(entries)
if post.meta.date < args.stop_date if post.meta.date < args.stop_date
) )
for rewrite_path in args.rewrite_rules:
try:
ruleset = rewrite.RewriteRuleset.from_yaml(rewrite_path)
except ValueError as error:
logger.critical("failed loading rewrite rules from %s: %s",
rewrite_path, error.args[0])
return cliutil.ExitCode.RewriteRulesError
postings = ruleset.rewrite(postings)
for search_term in args.search_terms: for search_term in args.search_terms:
postings = search_term.filter_postings(postings) postings = search_term.filter_postings(postings)
fund_postings = { fund_postings = {

View file

@ -78,6 +78,7 @@ from beancount.core import data as bc_data
from beancount.parser import printer as bc_printer from beancount.parser import printer as bc_printer
from . import core from . import core
from . import rewrite
from .. import books from .. import books
from .. import cliutil from .. import cliutil
from .. import config as configmod from .. import config as configmod
@ -701,6 +702,7 @@ multiple times. You can specify a part of the account hierarchy, or an account
classification from metadata. If not specified, the default set adapts to your classification from metadata. If not specified, the default set adapts to your
search criteria. search criteria.
""") """)
cliutil.add_rewrite_rules_argument(parser)
parser.add_argument( parser.add_argument(
'--show-totals', '-S', '--show-totals', '-S',
metavar='ACCOUNT', metavar='ACCOUNT',
@ -797,6 +799,14 @@ def main(arglist: Optional[Sequence[str]]=None,
data.Account.load_from_books(entries, options) data.Account.load_from_books(entries, options)
postings = data.Posting.from_entries(entries) postings = data.Posting.from_entries(entries)
for rewrite_path in args.rewrite_rules:
try:
ruleset = rewrite.RewriteRuleset.from_yaml(rewrite_path)
except ValueError as error:
logger.critical("failed loading rewrite rules from %s: %s",
rewrite_path, error.args[0])
return cliutil.ExitCode.RewriteRulesError
postings = ruleset.rewrite(postings)
for search_term in args.search_terms: for search_term in args.search_terms:
postings = search_term.filter_postings(postings) postings = search_term.filter_postings(postings)

View file

@ -196,6 +196,8 @@ def main(arglist: Optional[Sequence[str]]=None,
raise ValueError(f"unknown year {year!r}") raise ValueError(f"unknown year {year!r}")
out_path = args.output_directory / out_name out_path = args.output_directory / out_name
output_reports.append(out_path) output_reports.append(out_path)
for path in args.rewrite_rules:
yield f'--rewrite-rules={path}'
yield f'--output-file={out_path}' yield f'--output-file={out_path}'
yield from arglist yield from arglist
reports: List[Tuple[ReportFunc, ArgList]] = [ reports: List[Tuple[ReportFunc, ArgList]] = [
@ -208,10 +210,7 @@ def main(arglist: Optional[Sequence[str]]=None,
(ledger.main, list(common_args('Disbursements', next_year, '--disbursements'))), (ledger.main, list(common_args('Disbursements', next_year, '--disbursements'))),
(ledger.main, list(common_args('Receipts', next_year, '--receipts'))), (ledger.main, list(common_args('Receipts', next_year, '--receipts'))),
(accrual.main, list(common_args('AgingReport.ods'))), (accrual.main, list(common_args('AgingReport.ods'))),
(balance_sheet.main, list(common_args( (balance_sheet.main, list(common_args('Summary', args.audit_year))),
'Summary', args.audit_year,
*(f'--rewrite-rules={path}' for path in args.rewrite_rules),
))),
(fund.main, list(common_args('FundReport', args.audit_year))), (fund.main, list(common_args('FundReport', args.audit_year))),
(fund.main, list(common_args('FundReport', next_year))), (fund.main, list(common_args('FundReport', next_year))),
] ]

View file

@ -5,7 +5,7 @@ from setuptools import setup
setup( setup(
name='conservancy_beancount', name='conservancy_beancount',
description="Plugin, library, and reports for reading Conservancy's books", description="Plugin, library, and reports for reading Conservancy's books",
version='1.9.0', version='1.9.1',
author='Software Freedom Conservancy', author='Software Freedom Conservancy',
author_email='info@sfconservancy.org', author_email='info@sfconservancy.org',
license='GNU AGPLv3+', license='GNU AGPLv3+',