Example #1
0
        def wrapper(self):
            entries, errors, options_map = load_string(fun.__doc__,
                                                       dedent=True)

            if expect_errors is not None:
                if expect_errors is False and errors:
                    oss = io.StringIO()
                    printer.print_errors(errors, file=oss)
                    self.fail("Unexpected errors found:\n{}".format(
                        oss.getvalue()))
                elif expect_errors is True and not errors:
                    self.fail("Expected errors, none found:")

            return fun(self, entries, errors, options_map)
Example #2
0
def _log_errors(errors, log_errors):
    """Log errors, if 'log_errors' is set.

    Args:
      log_errors: A file object or function to write errors to,
        or None, if it should remain quiet.
    """
    if log_errors and errors:
        if hasattr(log_errors, 'write'):
            printer.print_errors(errors, file=log_errors)
        else:
            error_io = io.StringIO()
            printer.print_errors(errors, file=error_io)
            log_errors(error_io.getvalue())
Example #3
0
    def test_format_and_print_error(self):
        entry = data.Open(META, date(2014, 1, 15), 'Assets:Bank:Checking', [],
                          None)
        error = interpolate.BalanceError(META, "Example balance error", entry)
        error_str = printer.format_error(error)
        self.assertTrue(isinstance(error_str, str))

        oss = io.StringIO()
        printer.print_error(error, oss)
        self.assertTrue(isinstance(oss.getvalue(), str))

        oss = io.StringIO()
        printer.print_errors([error], oss)
        self.assertTrue(isinstance(oss.getvalue(), str))
Example #4
0
    def wrapper(*posargs, **kwargs):
        filename = app.args.filename

        if loader.needs_refresh(app.options):
            logging.info('Reloading...')

            # Save the source for later, to render.
            with open(filename, encoding='utf8') as f:
                app.source = f.read()

            # Parse the beancount file.
            entries, errors, options_map = loader.load_file(filename)

            # Print out the list of errors.
            if errors:
                # pylint: disable=unsupported-assignment-operation
                request.params['render_overlay'] = True
                print(
                    ',----------------------------------------------------------------'
                )
                printer.print_errors(errors, file=sys.stdout)
                print(
                    '`----------------------------------------------------------------'
                )

            # Save globals in the global app.
            app.entries = entries
            app.errors = errors
            app.options = options_map
            app.account_types = options.get_account_types(options_map)

            # Pre-compute the price database.
            app.price_map = prices.build_price_map(entries)

            # Pre-compute the list of active years.
            app.active_years = list(getters.get_active_years(entries))

            # Reset the view cache.
            app.views.clear()

        else:
            # For now, the overlay is a link to the errors page. Always render
            # it on the right when there are errors.
            if app.errors:
                # pylint: disable=unsupported-assignment-operation
                request.params['render_overlay'] = True

        return callback(*posargs, **kwargs)
Example #5
0
def main():
    # We don't do much other the validate the file
    # The File needs to load the plugin coolbean.matcher
    parser = argparse.ArgumentParser()
    add_arguments(parser)
    args = parser.parse_args()

    logging.basicConfig(stream=sys.stderr, level=logging.DEBUG)

    from beancount import loader

    # Load the bean_file
    entries, errors, options_map = loader.load_file(args.existing)

    if errors:
        print_errors(errors)
Example #6
0
    def wrapper(*posargs, **kwargs):
        filename = app.args.filename
        mtime = path.getmtime(filename)
        if mtime > app.last_mtime:
            app.last_mtime = mtime

            logging.info('Reloading...')

            # Save the source for later, to render.
            with open(filename, encoding='utf8') as f:
                app.source = f.read()

            # Parse the beancount file.
            entries, errors, options_map = loader.load_file(filename)

            # Print out the list of errors.
            if errors:
                request.params['render_overlay'] = True
                print(
                    ',----------------------------------------------------------------'
                )
                printer.print_errors(errors, file=sys.stdout)
                print(
                    '`----------------------------------------------------------------'
                )

            # Save globals in the global app.
            app.entries = entries
            app.errors = errors
            app.options = options_map
            app.account_types = options.get_account_types(options_map)

            # Pre-compute the price database.
            app.price_map = prices.build_price_map(app.entries)

            # Reset the view cache.
            app.views.clear()

        else:
            # For now, the overlay is a link to the errors page. Always render
            # it on the right when there are errors.
            if app.errors:
                request.params['render_overlay'] = True

        return callback(*posargs, **kwargs)
Example #7
0
        def wrapper(self):
            entries, errors, options_map = load_string(fun.__doc__,
                                                       dedent=True)

            if expect_errors is not None:
                if expect_errors is False and errors:
                    oss = io.StringIO()
                    printer.print_errors(errors, file=oss)
                    self.fail("Unexpected errors found:\n{}".format(
                        oss.getvalue()))
                elif expect_errors is True and not errors:
                    self.fail("Expected errors, none found:")

            # Note: Even if we expected no errors, we call this function with an
            # empty 'errors' list. This is so that the interface does not change
            # based on the arguments to the decorator, which would be somewhat
            # ugly and which would require explanation.
            return fun(self, entries, errors, options_map)
Example #8
0
        def wrapper(self):
            assert fun.__doc__ is not None, (
                "You need to insert a docstring on {}".format(fun.__name__))
            entries, errors, options_map = parse_string(fun.__doc__,
                                                        report_filename=filename,
                                                        report_firstline=lineno,
                                                        dedent=True)

            if not allow_incomplete and any(is_entry_incomplete(entry)
                                            for entry in entries):
                self.fail("parse_doc() may not use interpolation.")

            if expect_errors is not None:
                if expect_errors is False and errors:
                    oss = io.StringIO()
                    printer.print_errors(errors, file=oss)
                    self.fail("Unexpected errors found:\n{}".format(oss.getvalue()))
                elif expect_errors is True and not errors:
                    self.fail("Expected errors, none found:")

            return fun(self, entries, errors, options_map)
Example #9
0
def read_string_or_entries(entries_or_str, allow_incomplete=False):
    """Read a string of entries or just entries.

    Args:
      entries_or_str: Either a list of directives, or a string containing directives.
      allow_incomplete: A boolean, true if we allow incomplete inputs and perform
        light-weight booking.
    Returns:
      A list of directives.
    """
    if isinstance(entries_or_str, str):
        entries, errors, options_map = parser.parse_string(
            textwrap.dedent(entries_or_str))

        if allow_incomplete:
            # Do a simplistic local conversion in order to call the comparison.
            entries = [_local_booking(entry) for entry in entries]
        else:
            # Don't accept incomplete entries either.
            if any(parser.is_entry_incomplete(entry) for entry in entries):
                raise TestError(
                    "Entries in assertions may not use interpolation.")

            entries, booking_errors = booking.book(entries, options_map)
            errors = errors + booking_errors

        # Don't tolerate errors.
        if errors:
            oss = io.StringIO()
            printer.print_errors(errors, file=oss)
            raise TestError("Unexpected errors in expected: {}".format(
                oss.getvalue()))

    else:
        assert isinstance(entries_or_str,
                          list), "Expecting list: {}".format(entries_or_str)
        entries = entries_or_str

    return entries
def test_data(testCase):
    dataDir = os.path.join(
        os.path.dirname(__file__), "data", "generate_base_ccy_prices"
    )
    inputPath = os.path.join(dataDir, testCase + "_input.beancount")
    expectedPath = os.path.join(dataDir, testCase + "_expected.beancount")

    entries, errors, _ = loader.load_file(inputPath)
    if errors:
        printer.print_errors(errors)
        assert False

    actualStrIo = StringIO()
    printer.print_entries(entries, file=actualStrIo)
    actual = actualStrIo.getvalue()

    if os.path.isfile(expectedPath):
        with open(expectedPath, 'r') as expectedFile:
            expected = expectedFile.read()
            assert actual == expected
    else:
        with open(expectedPath, 'w') as expectedFile:
            expectedFile.write(actual)
Example #11
0
def render_mini_balances(entries,
                         options_map,
                         conversion=None,
                         price_map=None):
    """Render a treeified list of the balances for the given transactions.

    Args:
      entries: A list of selected transactions to render.
      options_map: The parsed options.
      conversion: Conversion method string, None, 'value' or 'cost'.
      price_map: A price map from the original entries. If this isn't provided,
        the inventories are rendered directly. If it is, their contents are
        converted to market value.
    """
    # Render linked entries (in date order) as errors (for Emacs).
    errors = [RenderError(entry.meta, '', entry) for entry in entries]
    printer.print_errors(errors)

    # Print out balances.
    real_root = realization.realize(entries)
    dformat = options_map['dcontext'].build(alignment=Align.DOT, reserved=2)

    # TODO(blais): I always want to be able to convert at cost. We need
    # arguments capability.
    #
    # TODO(blais): Ideally this conversion inserts a new transactions to
    # 'Unrealized' to account for the difference between cost and market value.
    # Insert one and update the realization. Add an update() method to the
    # realization, given a transaction.
    acctypes = options.get_account_types(options_map)
    if conversion == 'value':
        assert price_map is not None

        # Warning: Mutate the inventories in-place, converting them to market
        # value.
        balance_diff = inventory.Inventory()
        for real_account in realization.iter_children(real_root):
            balance_cost = real_account.balance.reduce(convert.get_cost)
            balance_value = real_account.balance.reduce(
                convert.get_value, price_map)
            real_account.balance = balance_value
            balance_diff.add_inventory(balance_cost)
            balance_diff.add_inventory(-balance_value)
        if not balance_diff.is_empty():
            account_unrealized = account.join(
                acctypes.income, options_map["account_unrealized_gains"])
            unrealized = realization.get_or_create(real_root,
                                                   account_unrealized)
            unrealized.balance.add_inventory(balance_diff)

    elif conversion == 'cost':
        for real_account in realization.iter_children(real_root):
            real_account.balance = real_account.balance.reduce(
                convert.get_cost)

    realization.dump_balances(real_root, dformat, file=sys.stdout)

    # Print out net income change.
    net_income = inventory.Inventory()
    for real_node in realization.iter_children(real_root):
        if account_types.is_income_statement_account(real_node.account,
                                                     acctypes):
            net_income.add_inventory(real_node.balance)

    print()
    print('Net Income: {}'.format(-net_income))
Example #12
0
def _load(sources, log_timings, log_errors, extra_validations, encoding):
    """Parse Beancount input, run its transformations and validate it.

    (This is an internal method.)
    This routine does all that is necessary to obtain a list of entries ready
    for realization and working with them. This is the principal call for of the
    scripts that load a ledger. It returns a list of entries transformed and
    ready for reporting, a list of errors, and parser's options dict.

    Args:
      sources: A list of (filename-or-string, is-filename) where the first
        element is a string, with either a filename or a string to be parsed directly,
        and the second arugment is a boolean that is true if the first is a filename.
        You may provide a list of such arguments to be parsed. Filenames must be absolute
        paths.
      log_timings: A file object or function to write timings to,
        or None, if it should remain quiet.
      log_errors: A file object or function to write errors to,
        or None, if it should remain quiet.
      extra_validations: A list of extra validation functions to run after loading
        this list of entries.
      encoding: A string or None, the encoding to decode the input filename with.
    Returns:
      See load() or load_string().
    """
    assert isinstance(sources, list) and all(
        isinstance(el, tuple) for el in sources)

    if hasattr(log_timings, 'write'):
        log_timings = log_timings.write

    # Parse all the files recursively.
    entries, parse_errors, options_map = _parse_recursive(
        sources, log_timings, encoding)

    # Run interpolation on incomplete entries.
    entries, balance_errors = booking.book(entries, options_map)
    parse_errors.extend(balance_errors)

    # Transform the entries.
    entries, errors = run_transformations(entries, parse_errors, options_map,
                                          log_timings)

    # Validate the list of entries.
    with misc_utils.log_time('beancount.ops.validate', log_timings, indent=1):
        valid_errors = validation.validate(entries, options_map, log_timings,
                                           extra_validations)
        errors.extend(valid_errors)

        # Note: We could go hardcode here and further verify that the entries
        # haven't been modified by user-provided validation routines, by
        # comparing hashes before and after. Not needed for now.

    if log_errors and errors:
        if hasattr(log_errors, 'write'):
            printer.print_errors(errors, file=log_errors)
        else:
            error_io = io.StringIO()
            printer.print_errors(errors, file=error_io)
            log_errors(error_io.getvalue())

    return entries, errors, options_map
Example #13
0
 def reload(self):
     self.entries, errors, self.options = loader.load_file(
         path.join(self.basedir, self.fileName))
     if errors:
         printer.print_errors(errors)
         raise BapiException('Failed to load file ' + self.fileName + '.')
def main():
    argparser = argparse.ArgumentParser()
    ameritrade.add_args(argparser)

    argparser.add_argument('-i',
                           '--ignore-errors',
                           dest='raise_error',
                           action='store_false',
                           default=True,
                           help="Raise an error on unhandled messages")
    argparser.add_argument(
        '-J',
        '--debug-file',
        '--json',
        action='store',
        help="Debug filename where to strore al the raw JSON")
    argparser.add_argument(
        '-j',
        '--debug-transaction',
        action='store',
        type=int,
        help="Process a single transaction and print debugging data about it.")

    argparser.add_argument('-e',
                           '--end-date',
                           action='store',
                           help="Period of end date minus one year.")

    argparser.add_argument('-B',
                           '--no-booking',
                           dest='booking',
                           action='store_false',
                           default=True,
                           help="Do booking to resolve lots.")

    argparser.add_argument('-l',
                           '--ledger',
                           action='store',
                           help=("Beancount ledger to remove already imported "
                                 "transactions (optional)."))

    argparser.add_argument(
        '-g',
        '--group-by-underlying',
        action='store_true',
        help=("Group the transaction output by corresponding "
              "underlying. This is great for options."))

    args = argparser.parse_args()

    # Open a connection and figure out the main account.
    api = ameritrade.open(ameritrade.config_from_args(args))
    accountId = utils.GetMainAccount(api)
    positions = utils.GetPositions(api, accountId)

    # Fetch transactions.
    # Note that the following arguments are also honored:
    #   endDate=datetime.date.today().isoformat())
    #   startDate='2014-01-01',
    #   endDate='2015-01-01')
    if args.end_date:
        end_date = parser.parse(args.end_date).date()
        start_date = end_date - datetime.timedelta(days=364)
        start = start_date.isoformat()
        end = end_date.isoformat()
    else:
        start = end = None
    txns = api.GetTransactions(accountId=accountId,
                               startDate=start,
                               endDate=end)
    if isinstance(txns, dict):
        pprint(txns, sys.stderr)
        return
    txns.reverse()

    # Optionally write out the raw original content downloaded to a file.
    if args.debug_file:
        with open(args.debug, 'w') as ofile:
            ofile.write(pformat(txns))

    # Process each of the transactions.
    entries = []
    balances = collections.defaultdict(inventory.Inventory)
    commodities = {}
    for txn in txns:
        if args.debug_transaction and txn[
                'transactionId'] != args.debug_transaction:
            continue
        else:
            pprint.pprint(txn)

        # print('{:30} {}'.format(txn['type'], txn['description'])); continue
        dispatch_entries = RunDispatch(txn, balances, commodities,
                                       args.raise_error)
        if dispatch_entries:
            entries.extend(dispatch_entries)

            # Update a balance account of just the units.
            #
            # This is only here so that the options removal can figure out which
            # side is the reduction side and what sign to use on the position
            # change. Ideally the API would provide a side indication and we
            # wouldn't have to maintain any state at alll. {492fa5292636}
            for entry in data.filter_txns(dispatch_entries):
                for posting in entry.postings:
                    balance = balances[posting.account]
                    if posting.units is not None:
                        balance.add_amount(posting.units)

    # Add a final balance entry.
    balance_entry = CreateBalance(api, accountId)
    if balance_entry:
        entries.append(balance_entry)

    if args.booking:
        # Book the entries.
        entries, balance_errors = booking.book(entries,
                                               OPTIONS_DEFAULTS.copy())
        if balance_errors:
            printer.print_errors(balance_errors)

        # Remove dates on reductions when they have no prices. This is an
        # artifact of not being able to pass in prior balance state to the
        # booking code, which we will fix in v3.
        entries = RemoveDateReductions(entries)

        # Match up the trades we can in this subset of the history and pair them up
        # with a common random id.
        entries, balances = MatchTrades(entries)

        # Add zero prices for expired options for which we still have non-zero
        # positions.
        entries.extend(GetExpiredOptionsPrices(positions, balances))

    # If a Beancount ledger has been specified, open it, read it in, and remove
    # all the transactions up to the latest one with the transaction id (as a
    # link) that's present in the ledger.
    if args.ledger:
        ledger_entries, _, __ = loader.load_file(args.ledger)

        # Find the date of the last transaction in the ledger with a TD
        # transaction id, and the full set of links to remove.
        links = set()
        last_date = None
        for entry in data.filter_txns(ledger_entries):
            for link in (entry.links or {}):
                if re.match(r"td-\d{9,}", link):
                    links.add(link)
                    last_date = entry.date

        # Remove all the transactions already present in the ledger.
        #
        # Also remove all the non-transactions with a date before that of the
        # last linked one that was found. This allows us to remove Price and
        # Commodity directives. (In v3, when links are available on every
        # directive, we can just use the links.)
        entries = [
            entry for entry in entries
            if ((isinstance(entry, data.Transaction) and not entry.links
                 & links) or (not isinstance(entry, data.Transaction)
                              and entry.date >= last_date))
        ]

    if args.group_by_underlying:
        # Group the transactions by their underlying, with org-mode separators.
        groups = GroupByUnderlying(entries)
        for (has_option, currency), group_entries in sorted(groups.items()):
            header = currency or "General"
            if has_option:
                header = "Options: {}".format(header)
            print("** {}".format(header))
            print()
            printer.print_entries(data.sorted(group_entries), file=sys.stdout)
            print()
            print()
    else:
        # Render all the entries chronologically (the default).
        sentries = SortCommodityFirst(entries)
        printer.print_entries(sentries, file=sys.stdout)
Example #15
0
def do_linked(filename, args):
    """Print out a list of transactions linked to the one at the given line.

    Args:
      filename: A string, which consists in the filename.
      args: A tuple of the rest of arguments. We're expecting the first argument
        to be an integer as a string.
    """
    from beancount.parser import options
    from beancount.parser import printer
    from beancount.core import account_types
    from beancount.core import inventory
    from beancount.core import data
    from beancount.core import realization
    from beancount import loader

    # Parse the arguments, get the line number.
    if len(args) != 1:
        raise SystemExit("Missing line number argument.")
    lineno = int(args[0])

    # Load the input file.
    entries, errors, options_map = loader.load_file(filename)

    # Find the closest entry.
    closest_entry = data.find_closest(entries, options_map['filename'], lineno)

    # Find its links.
    if closest_entry is None:
        raise SystemExit("No entry could be found before {}:{}".format(
            filename, lineno))
    links = (closest_entry.links if isinstance(closest_entry, data.Transaction)
             else data.EMPTY_SET)
    if not links:
        linked_entries = [closest_entry]
    else:
        # Find all linked entries.
        #
        # Note that there is an option here: You can either just look at the links
        # on the closest entry, or you can include the links of the linked
        # transactions as well. Whichever one you want depends on how you use your
        # links. Best would be to query the user (in Emacs) when there are many
        # links present.
        follow_links = True
        if not follow_links:
            linked_entries = [
                entry for entry in entries
                if (isinstance(entry, data.Transaction) and entry.links
                    and entry.links & links)
            ]
        else:
            links = set(links)
            linked_entries = []
            while True:
                num_linked = len(linked_entries)
                linked_entries = [
                    entry for entry in entries
                    if (isinstance(entry, data.Transaction) and entry.links
                        and entry.links & links)
                ]
                if len(linked_entries) == num_linked:
                    break
                for entry in linked_entries:
                    if entry.links:
                        links.update(entry.links)

    # Render linked entries (in date order) as errors (for Emacs).
    errors = [RenderError(entry.meta, '', entry) for entry in linked_entries]
    printer.print_errors(errors)

    # Print out balances.
    real_root = realization.realize(linked_entries)
    dformat = options_map['dcontext'].build(
        alignment=display_context.Align.DOT, reserved=2)
    realization.dump_balances(real_root, dformat, file=sys.stdout)

    # Print out net income change.
    acctypes = options.get_account_types(options_map)
    net_income = inventory.Inventory()
    for real_node in realization.iter_children(real_root):
        if account_types.is_income_statement_account(real_node.account,
                                                     acctypes):
            net_income.add_inventory(real_node.balance)

    print()
    print('Net Income: {}'.format(-net_income))
Example #16
0
def do_roundtrip(filename, unused_args):
    """Round-trip test on arbitrary Ledger.

    Read a Ledger's transactions, print them out, re-read them again and compare
    them. Both sets of parsed entries should be equal. Both printed files are
    output to disk, so you can also run diff on them yourself afterwards.

    Args:
      filename: A string, the Beancount input filename.
    """
    from beancount.parser import printer
    from beancount.core import compare
    from beancount import loader

    round1_filename = round2_filename = None
    try:
        logging.basicConfig(level=logging.INFO,
                            format='%(levelname)-8s: %(message)s')
        logging.info("Read the entries")
        entries, errors, options_map = loader.load_file(filename)
        printer.print_errors(errors, file=sys.stderr)

        logging.info("Print them out to a file")
        basename, extension = path.splitext(filename)
        round1_filename = ''.join([basename, '.roundtrip1', extension])
        with open(round1_filename, 'w') as outfile:
            printer.print_entries(entries, file=outfile)

        logging.info("Read the entries from that file")

        # Note that we don't want to run any of the auto-generation here, but
        # parsing now returns incomplete objects and we assume idempotence on a
        # file that was output from the printer after having been processed, so
        # it shouldn't add anything new. That is, a processed file printed and
        # resolve when parsed again should contain the same entries, i.e.
        # nothing new should be generated.
        entries_roundtrip, errors, options_map = loader.load_file(
            round1_filename)

        # Print out the list of errors from parsing the results.
        if errors:
            print(
                ',----------------------------------------------------------------------'
            )
            printer.print_errors(errors, file=sys.stdout)
            print(
                '`----------------------------------------------------------------------'
            )

        logging.info("Print what you read to yet another file")
        round2_filename = ''.join([basename, '.roundtrip2', extension])
        with open(round2_filename, 'w') as outfile:
            printer.print_entries(entries_roundtrip, file=outfile)

        logging.info("Compare the original entries with the re-read ones")
        same, missing1, missing2 = compare.compare_entries(
            entries, entries_roundtrip)
        if same:
            logging.info('Entries are the same. Congratulations.')
        else:
            logging.error('Entries differ!')
            print()
            print('\n\nMissing from original:')
            for entry in entries:
                print(entry)
                print(compare.hash_entry(entry))
                print(printer.format_entry(entry))
                print()

            print('\n\nMissing from round-trip:')
            for entry in missing2:
                print(entry)
                print(compare.hash_entry(entry))
                print(printer.format_entry(entry))
                print()
    finally:
        for rfilename in (round1_filename, round2_filename):
            if path.exists(rfilename):
                os.remove(rfilename)
Example #17
0
def load():
    entries, errors, options = loader.load_string(
        textwrap.dedent("""
      2022-01-01 open Assets:Checking         USD
      2022-01-01 open Assets:Federal:401k     IRAUSD
      2022-01-01 open Assets:Gold             GLD
      2022-01-01 open Assets:Vacation         VACHR
      2022-01-01 open Assets:Vanguard:RGAGX   RGAGX
      2022-01-01 open Expenses:Commissions    USD
      2022-01-01 open Expenses:Food           USD
      2022-01-01 open Expenses:Home:Rent      USD
      2022-01-01 open Expenses:Taxes:401k     IRAUSD
      2022-01-01 open Expenses:Taxes:Federal  USD
      2022-01-01 open Expenses:Tests          USD
      2022-01-01 open Expenses:Vacation       VACHR
      2022-01-01 open Income:ACME             USD
      2022-01-01 open Income:Gains            USD
      2022-01-01 open Income:Vacation         VACHR

      2022-01-01 * "ACME" "Salary"
        Assets:Checking           10.00 USD
        Income:ACME              -11.00 USD
        Expenses:Taxes:Federal     1.00 USD
        Assets:Federal:401k       -2.00 IRAUSD
        Expenses:Taxes:401k        2.00 IRAUSD
        Assets:Vacation               5 VACHR
        Income:Vacation              -5 VACHR

      2022-01-01 * "Rent"
        Assets:Checking           42.00 USD
        Expenses:Home:Rent        42.00 USD

      2022-01-02 * "Holidays"
        Assets:Vacation              -1 VACHR
        Expenses:Vacation

      2022-01-03 * "Test 01"
        Assets:Checking            1.00 USD
        Expenses:Tests

      2022-01-04 * "My Fovorite Plase" "Eating out alone"
        Assets:Checking            4.00 USD
        Expenses:Food

      2022-01-05 * "Invest"
        Assets:Checking         -359.94 USD
        Assets:Vanguard:RGAGX     2.086 RGAGX {172.55 USD}

      2013-10-23 * "Buy Gold"
        Assets:Checking        -1278.67 USD
        Assets:Gold                   9 GLD {141.08 USD}
        Expenses:Commissions          8.95 USD

      2022-01-07 * "Sell Gold"
        Assets:Gold                 -16 GLD {147.01 USD} @ 135.50 USD
        Assets:Checking         2159.05 USD
        Expenses:Commissions       8.95 USD
        Income:Gains             184.16 USD

      2022-01-08 * "Sell Gold"
        Assets:Gold                 -16 GLD {147.01 USD} @ 135.50 USD
        Assets:Checking         2159.05 USD
        Expenses:Commissions       8.95 USD
        Income:Gains             184.16 USD

      2022-02-01 * "ACME" "Salary"
        Assets:Checking           10.00 USD
        Income:ACME              -11.00 USD
        Expenses:Taxes:Federal     1.00 USD
        Assets:Federal:401k       -2.00 IRAUSD
        Expenses:Taxes:401k        2.00 IRAUSD
        Assets:Vacation               5 VACHR
        Income:Vacation              -5 VACHR

      2022-02-01 * "Rent"
        Assets:Checking           43.00 USD
        Expenses:Home:Rent        43.00 USD

      2022-02-02 * "Test 02"
        Assets:Checking            2.00 USD
        Expenses:Tests

      2030-01-01 query "taxes" "
        SELECT
          date, description, position, balance
        WHERE
          account ~ 'Taxes'
        ORDER BY date DESC
        LIMIT 20"

      2015-01-01 query "home" "
        SELECT
          last(date) as latest,
          account,
          sum(position) as total
        WHERE
          account ~ ':Home:'
        GROUP BY account"

    """))
    if errors:
        printer.print_errors(errors)
    return entries, errors, options
Example #18
0
 def render_text(self, entries, errors, options_map, file):
     printer.print_errors(errors, file=file)
Example #19
0
def do_linked(filename, args):
    """Print out a list of transactions linked to the one at the given line.

    Args:
      filename: A string, which consists in the filename.
      args: A tuple of the rest of arguments. We're expecting the first argument
        to be a string which contains either a lineno integer or a filename:lineno
        combination (which can be used if the location is not in the top-level file).
    """
    from beancount.parser import options
    from beancount.parser import printer
    from beancount.core import account_types
    from beancount.core import inventory
    from beancount.core import data
    from beancount.core import realization
    from beancount import loader

    # Parse the arguments, get the line number.
    if len(args) != 1:
        raise SystemExit("Missing line number or link argument.")
    location_spec = args[0]

    # Load the input file.
    entries, errors, options_map = loader.load_file(filename)

    # Accept an explicit link name as the location. Must include the '^'
    # character.
    if re.match(r"\^(.*)$", location_spec):
        search_filename = options_map['filename']
        links = {location_spec[1:]}
        linked_entries = find_linked_entries(entries, links, False)

    else:
        # Parse the argument as a line number or a "<filename>:<lineno>" spec to
        # pull context from.
        match = re.match(r"(.+):(\d+)$", location_spec)
        if match:
            search_filename = path.abspath(match.group(1))
            lineno = int(match.group(2))
        elif re.match(r"(\d+)$", location_spec):
            # Parse the argument as just a line number to pull context from on
            # the main filename.
            search_filename = options_map['filename']
            lineno = int(location_spec)
        else:
            raise SystemExit(
                "Invalid line number or link format for location.")

        # Find the closest entry.
        closest_entry = data.find_closest(entries, search_filename, lineno)

        # Find its links.
        if closest_entry is None:
            raise SystemExit("No entry could be found before {}:{}".format(
                search_filename, lineno))
        links = (closest_entry.links if isinstance(
            closest_entry, data.Transaction) else data.EMPTY_SET)

        # Get the linked entries, or just the closest one, if no links.
        linked_entries = (find_linked_entries(entries, links, True)
                          if links else [closest_entry])

    # Render linked entries (in date order) as errors (for Emacs).
    errors = [RenderError(entry.meta, '', entry) for entry in linked_entries]
    printer.print_errors(errors)

    # Print out balances.
    real_root = realization.realize(linked_entries)
    dformat = options_map['dcontext'].build(
        alignment=display_context.Align.DOT, reserved=2)
    realization.dump_balances(real_root, dformat, file=sys.stdout)

    # Print out net income change.
    acctypes = options.get_account_types(options_map)
    net_income = inventory.Inventory()
    for real_node in realization.iter_children(real_root):
        if account_types.is_income_statement_account(real_node.account,
                                                     acctypes):
            net_income.add_inventory(real_node.balance)

    print()
    print('Net Income: {}'.format(-net_income))
Example #20
0
 def setUp(self):
     self.entries, errors, __ = loader.load_string(INPUT)
     printer.print_errors(errors)
     self.assertFalse(errors)
Example #21
0
 def do_errors(self, _line):
     "Print the errors that occurred during Beancount input file parsing."
     if self.errors:
         printer.print_errors(self.errors)
     else:
         print('(no errors)', file=self.outfile)