sort_entries: New tool.

This commit is contained in:
Brett Smith 2020-10-06 10:38:39 -04:00
parent 7baa78fe34
commit 639a41b782
2 changed files with 102 additions and 1 deletions

View file

@ -0,0 +1,100 @@
#!/usr/bin/env python3
"""sort_entries.py - Consistently sort and format Beancount entries
This is useful to use as a preprocessing step before comparing entries with
tools like ``diff``.
"""
# Copyright © 2020 Brett Smith
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
import argparse
import logging
import os
import sys
from pathlib import Path
from beancount.core import display_context as bc_dcontext
from beancount import loader as bc_loader
from beancount.parser import printer as bc_printer
from typing import (
Hashable,
Optional,
Sequence,
TextIO,
)
from ..beancount_types import (
Directive,
Entries,
Errors,
)
from .. import cliutil
PROGNAME = 'bean-sort'
logger = logging.getLogger('conservancy_beancount.tools.sort_entries')
def parse_arguments(arglist: Optional[Sequence[str]]=None) -> argparse.Namespace:
parser = argparse.ArgumentParser(prog=PROGNAME)
cliutil.add_version_argument(parser)
cliutil.add_loglevel_argument(parser)
parser.add_argument(
'--quiet', '-q',
action='store_true',
help="""Suppress Beancount errors
""")
parser.add_argument(
'paths',
metavar='PATH',
type=Path,
nargs=argparse.ONE_OR_MORE,
help="""Beancount path(s) to read entries from
""")
return parser.parse_args(arglist)
def entry_sorter(entry: Directive) -> Hashable:
type_name = type(entry).__name__
if type_name == 'Transaction':
return (entry.date, type_name, entry.narration, entry.payee or '') # type:ignore[attr-defined]
else:
return (entry.date, type_name)
def main(arglist: Optional[Sequence[str]]=None,
stdout: TextIO=sys.stdout,
stderr: TextIO=sys.stderr,
) -> int:
args = parse_arguments(arglist)
cliutil.set_loglevel(logger, args.loglevel)
entries: Entries = []
errors: Errors = []
for path in args.paths:
new_entries, new_errors, _ = bc_loader.load_file(path)
entries.extend(new_entries)
errors.extend(new_errors)
entries.sort(key=entry_sorter)
if not args.quiet:
for error in errors:
bc_printer.print_error(error, file=stderr)
dcontext = bc_dcontext.DisplayContext()
dcontext.set_commas(True)
bc_printer.print_entries(entries, dcontext, file=stdout)
return os.EX_DATAERR if errors and not args.quiet else os.EX_OK
entry_point = cliutil.make_entry_point(__name__, PROGNAME)
if __name__ == '__main__':
exit(entry_point())

View file

@ -5,7 +5,7 @@ from setuptools import setup
setup(
name='conservancy_beancount',
description="Plugin, library, and reports for reading Conservancy's books",
version='1.11.1',
version='1.12.0',
author='Software Freedom Conservancy',
author_email='info@sfconservancy.org',
license='GNU AGPLv3+',
@ -40,6 +40,7 @@ setup(
'accrual-report = conservancy_beancount.reports.accrual:entry_point',
'assemble-audit-reports = conservancy_beancount.tools.audit_report:entry_point',
'balance-sheet-report = conservancy_beancount.reports.balance_sheet:entry_point',
'bean-sort = conservancy_beancount.tools.sort_entries:entry_point',
'extract-odf-links = conservancy_beancount.tools.extract_odf_links:entry_point',
'fund-report = conservancy_beancount.reports.fund:entry_point',
'ledger-report = conservancy_beancount.reports.ledger:entry_point',