diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 24e73f171..d7feba7ca 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -24,9 +24,7 @@ jobs: with: python-version: ${{ matrix.python }} allow-prereleases: true - - run: python -m pip install --upgrade pip - if: runner.os == 'macOS' - - run: python -m pip install setuptools pytest regex click python-dateutil - - run: make build + - run: python -m pip install meson-python ninja pytest + - run: python -m pip install --no-build-isolation -e . - run: pytest beancount if: runner.os != 'Windows' diff --git a/beancount/core/data.py b/beancount/core/data.py index 08ca73741..d041fb81e 100644 --- a/beancount/core/data.py +++ b/beancount/core/data.py @@ -59,6 +59,8 @@ class Booking(enum.Enum): # Highest-in first-out in the case of ambiguity. HIFO = "HIFO" + # Extension point for Magicbeans. + MAGICBEANS = 'MAGICBEANS' # All possible types of entries. These are the main data structures in use # within the program. They are all treated as immutable. diff --git a/beancount/parser/booking_method.py b/beancount/parser/booking_method.py index 39ae5cafc..cc118fd6c 100644 --- a/beancount/parser/booking_method.py +++ b/beancount/parser/booking_method.py @@ -6,6 +6,7 @@ __license__ = "GNU GPLv2" import collections +from datetime import timedelta from decimal import Decimal from beancount.core.number import ZERO @@ -139,20 +140,25 @@ def booking_method_STRICT_WITH_SIZE(entry, posting, matches): def booking_method_FIFO(entry, posting, matches): """FIFO booking method implementation.""" - return _booking_method_xifo(entry, posting, matches, "date", False) + return _booking_method_xifo(entry, posting, matches, + lambda m: m.cost and getattr(m.cost, "date"), + reverse_order=False) def booking_method_LIFO(entry, posting, matches): """LIFO booking method implementation.""" - return _booking_method_xifo(entry, posting, matches, "date", True) + return _booking_method_xifo(entry, posting, matches, + lambda m: m.cost and getattr(m.cost, "date"), + reverse_order=True) def booking_method_HIFO(entry, posting, matches): """HIFO booking method implementation.""" - return _booking_method_xifo(entry, posting, matches, "number", True) + return _booking_method_xifo(entry, posting, matches, + lambda m: m.cost and getattr(m.cost, "number"), + reverse_order=True) - -def _booking_method_xifo(entry, posting, matches, sortattr, reverse_order): +def _booking_method_xifo(entry, posting, matches, key, reverse_order): """FIFO and LIFO booking method implementations.""" booked_reductions = [] booked_matches = [] @@ -162,9 +168,7 @@ def _booking_method_xifo(entry, posting, matches, sortattr, reverse_order): # Each up the positions. sign = -1 if posting.units.number < ZERO else 1 remaining = abs(posting.units.number) - for match in sorted( - matches, key=lambda p: p.cost and getattr(p.cost, sortattr), reverse=reverse_order - ): + for match in sorted(matches, key=key, reverse=reverse_order): if remaining <= ZERO: break @@ -295,6 +299,10 @@ def booking_method_AVERAGE(entry, posting, matches): ) _insufficient = abs(posting.units.number) > abs(units.number) +def booking_method_magicbeans_stub(entry, posting, matches): + """Magicbeans booking method stub.""" + raise NotImplementedError("Magicbeans booking method should be redefined by Magicbeans package") + _BOOKING_METHODS = { Booking.STRICT: booking_method_STRICT, @@ -304,4 +312,5 @@ def booking_method_AVERAGE(entry, posting, matches): Booking.HIFO: booking_method_HIFO, Booking.NONE: booking_method_NONE, Booking.AVERAGE: booking_method_AVERAGE, + Booking.MAGICBEANS: booking_method_magicbeans_stub, # Should be redefined by Magicbeans package } diff --git a/meson.build b/meson.build index b7d108a24..80f960951 100644 --- a/meson.build +++ b/meson.build @@ -111,6 +111,90 @@ py.install_sources( preserve_path: true, ) +# tests +py.install_sources(''' + beancount/core/account_test.py + beancount/core/account_types_test.py + beancount/core/amount_test.py + beancount/core/compare_test.py + beancount/core/convert_test.py + beancount/core/data_test.py + beancount/core/display_context_test.py + beancount/core/distribution_test.py + beancount/core/flags_test.py + beancount/core/getters_test.py + beancount/core/interpolate_test.py + beancount/core/inventory_test.py + beancount/core/number_test.py + beancount/core/position_test.py + beancount/core/prices_test.py + beancount/core/realization_test.py + beancount/loader_test.py + beancount/ops/balance_test.py + beancount/ops/basicops_test.py + beancount/ops/compress_test.py + beancount/ops/documents_test.py + beancount/ops/find_prices_test.py + beancount/ops/lifetimes_test.py + beancount/ops/pad_test.py + beancount/ops/summarize_test.py + beancount/ops/validation_test.py + beancount/parser/booking_full_test.py + beancount/parser/booking_method_test.py + beancount/parser/booking_test.py + beancount/parser/cmptest_test.py + beancount/parser/context_test.py + beancount/parser/grammar_test.py + beancount/parser/hashsrc_test.py + beancount/parser/lexer_test.py + beancount/parser/options_test.py + beancount/parser/parser_test.py + beancount/parser/printer_test.py + beancount/parser/version_test.py + beancount/plugins/auto_accounts_test.py + beancount/plugins/auto_test.py + beancount/plugins/check_average_cost_test.py + beancount/plugins/check_closing_test.py + beancount/plugins/check_commodity_test.py + beancount/plugins/check_drained_test.py + beancount/plugins/close_tree_test.py + beancount/plugins/coherent_cost_test.py + beancount/plugins/commodity_attr_test.py + beancount/plugins/currency_accounts_test.py + beancount/plugins/implicit_prices_test.py + beancount/plugins/leafonly_test.py + beancount/plugins/noduplicates_test.py + beancount/plugins/nounused_test.py + beancount/plugins/onecommodity_test.py + beancount/plugins/pedantic_test.py + beancount/plugins/sellgains_test.py + beancount/plugins/unique_prices_test.py + beancount/projects/export_test.py + beancount/scripts/check_examples_test.py + beancount/scripts/check_test.py + beancount/scripts/deps_test.py + beancount/scripts/directories_test.py + beancount/scripts/doctor_test.py + beancount/scripts/example_test.py + beancount/scripts/format_test.py + beancount/tools/treeify_test.py + beancount/utils/bisect_key_test.py + beancount/utils/date_utils_test.py + beancount/utils/defdict_test.py + beancount/utils/encryption_test.py + beancount/utils/file_utils_test.py + beancount/utils/import_utils_test.py + beancount/utils/invariants_test.py + beancount/utils/memo_test.py + beancount/utils/misc_utils_test.py + beancount/utils/pager_test.py + beancount/utils/snoop_test.py + beancount/utils/table_test.py + beancount/utils/test_utils_test.py + '''.split(), + preserve_path: true, +) + parser_source_hash = run_command( [py, '-c', 'from hashsrc import hash_parser_source_files; print(hash_parser_source_files())'], env: {'PYTHONPATH': '@0@/beancount/parser/'.format(meson.current_source_dir())},