예제 #1
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.
    links = closest_entry.links
    if closest_entry is None:
        raise SystemExit("No entry could be found before {}:{}".format(
            filename, lineno))
    if not links:
        return

    # Find all linked entries.
    linked_entries = [
        entry for entry in entries
        if (isinstance(entry, data.Transaction) and entry.links and entry.links
            & 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)
    realization.dump_balances(real_root, 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))
예제 #2
0
def render_file_context(entries, options_map, filename, lineno):
    """Render the context before and after a particular transaction is applied.

    Args:
      entries: A list of directives.
      options_map: A dict of options, as produced by the parser.
      filename: A string, the name of the file from which the transaction was parsed.
      lineno: An integer, the line number in the file the transaction was parsed from.
    Returns:
      A multiline string of text, which consists of the context before the
      transaction is applied, the transaction itself, and the context after it
      is applied. You can just print that, it is in form that is intended to be
      consumed by the user.
    """
    # Find the closest entry.
    closest_entry = data.find_closest(entries, filename, lineno)
    if closest_entry is None:
        raise SystemExit("No entry could be found before {}:{}".format(
            filename, lineno))

    # Run just the parser stage (no booking nor interpolation, which would
    # remove the postings) on the input file to produced the corresponding
    # unbooked transaction, so that we can get the list of accounts.
    if path.exists(filename):
        parsed_entries, _, __ = parser.parse_file(filename)

        # Note: We cannot bisect as we cannot rely on sorting behavior from the parser.
        lineno = closest_entry.meta['lineno']
        closest_parsed_entries = [
            parsed_entry for parsed_entry in parsed_entries
            if parsed_entry.meta['lineno'] == lineno
        ]
        if len(closest_parsed_entries) != 1:
            # This is an internal error, this should never occur.
            raise RuntimeError(
                "Parsed entry corresponding to real entry not found in original filename."
            )
        closest_parsed_entry = next(iter(closest_parsed_entries))
    else:
        closest_parsed_entry = None

    return render_entry_context(entries, options_map, closest_entry,
                                closest_parsed_entry)
def render_file_context(entries, options_map, filename, lineno):
    """Render the context before and after a particular transaction is applied.

    Args:
      entries: A list of directives.
      options_map: A dict of options, as produced by the parser.
      filename: A string, the name of the file from which the transaction was parsed.
      lineno: An integer, the line number in the file the transaction was parsed from.
    Returns:
      A multiline string of text, which consists of the context before the
      transaction is applied, the transaction itself, and the context after it
      is applied. You can just print that, it is in form that is intended to be
      consumed by the user.
    """
    # Find the closest entry.
    closest_entry = data.find_closest(entries, filename, lineno)
    if closest_entry is None:
        raise SystemExit("No entry could be found before {}:{}".format(
            filename, lineno))

    return render_entry_context(entries, options_map, closest_entry)
예제 #4
0
    def test_find_closest(self):
        entry1 = data.Transaction(
            data.new_metadata("/tmp/apples.beancount", 200),
            datetime.date(2014, 9, 14), '*', None, "", None, None, [])

        # Insert a decoy from another file (should fail).
        entry2 = data.Transaction(
            data.new_metadata("/tmp/bananas.beancount", 100),
            datetime.date(2014, 9, 20), '*', None, "", None, None, [])

        entry3 = data.Transaction(
            data.new_metadata("/tmp/apples.beancount", 105),
            datetime.date(2014, 10, 1), '*', None, "", None, None, [])

        entries = [entry1, entry2, entry3]

        # Try an exact match.
        self.assertTrue(
            data.find_closest(entries, "/tmp/apples.beancount", 105) is entry3)

        # Try a bit after.
        self.assertTrue(
            data.find_closest(entries, "/tmp/apples.beancount", 107) is entry3)

        # Try way closer to the next one.
        self.assertTrue(
            data.find_closest(entries, "/tmp/apples.beancount", 199) is entry3)

        # Get the next one.
        self.assertTrue(
            data.find_closest(entries, "/tmp/apples.beancount", 201) is entry1)

        # Get from the other file.
        self.assertTrue(
            data.find_closest(entries, "/tmp/bananas.beancount", 300) is entry2
        )

        # Get none.
        self.assertTrue(
            data.find_closest(entries, "/tmp/apples.beancount", 99) is None)
예제 #5
0
파일: doctor.py 프로젝트: scanta2/beancount
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))
예제 #6
0
def linked(filename, location_spec):
    """List related transactions.

    List all transaction in ledger FILENAME linked to LINK or tagged
    with TAG, or linked to the one at LOCATION, or linked to any
    transaction in REGION.

    The LINK and TAG arguments must include the leading ^ or #
    charaters. The LOCATION argument is either a line number or a
    filename:lineno tuple to indicate a location in a ledger file
    included from the main input file. The REGION argument is either a
    stard:end line numbers tuple or a filename:start:end triplet to
    indicate a region in a ledger file included from the main input
    file.

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

    # Link name.
    if re.match(r"\^(.*)$", location_spec):
        search_filename = options_map['filename']
        links = {location_spec[1:]}
        linked_entries = find_linked_entries(entries, links, False)

    # Tag name.
    elif re.match(r"#(.*)$", location_spec):
        search_filename = options_map['filename']
        tag = location_spec[1:]
        linked_entries = find_tagged_entries(entries, tag)

    else:
        # Parse the argument as a line number or a
        # "<filename>:<lineno>:<lineno>" spec to pull context from, with
        # optional filename and optional last line number.
        #
        # If a filename is not provided, the ledger's top-level filename is used
        # (this is the common case). An explicit filename is used to get context
        # in included files.
        #
        # If a single line number is provided the closest transaction is
        # selected. If an internal of line numbers is provided, the list of all
        # transactions whose first line is inside the interval are selected.
        match = re.match(r"(\d+)(?::(\d+))?$", location_spec)
        if match:
            included_filename = None
            first_line, last_line = match.groups()
        else:
            match = re.match(r"(.+?):(\d+)(?::(\d+))?$", location_spec)
            if match:
                included_filename, first_line, last_line = match.groups()
            else:
                raise SystemExit("Invalid line number or link format for location.")

        search_filename = (os.path.abspath(included_filename)
                           if included_filename else
                           options_map['filename'])
        lineno = int(first_line)
        if last_line is None:
            # Find the closest entry.
            closest_entry = data.find_closest(entries, search_filename, lineno)
            selected_entries = [closest_entry]

            # 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)
        else:
            # Find all the entries in the interval, following all links.
            last_lineno = int(last_line)
            links = set()
            selected_entries = []
            for entry in data.filter_txns(entries):
                if (entry.meta['filename'] == search_filename and
                    lineno <= entry.meta['lineno'] <= last_lineno):
                    links.update(entry.links)
                    selected_entries.append(entry)

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

    render_mini_balances(linked_entries, options_map, None)
예제 #7
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))
예제 #8
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(:lineno)? combination (which can be used if the
        location is not in the top-level file).
    """
    # 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)

    elif re.match(r"#(.*)$", location_spec):
        search_filename = options_map['filename']
        tag = location_spec[1:]
        linked_entries = find_tagged_entries(entries, tag)

    else:
        # Parse the argument as a line number or a
        # "<filename>:<lineno>:<lineno>" spec to pull context from, with
        # optional filename and optional last line number.
        #
        # If a filename is not provided, the ledger's top-level filename is used
        # (this is the common case). An explicit filename is used to get context
        # in included files.
        #
        # If a single line number is provided the closest transaction is
        # selected. If an internal of line numbers is provided, the list of all
        # transactions whose first line is inside the interval are selected.
        match = re.match(r"(\d+)(?::(\d+))?$", location_spec)
        if match:
            included_filename = None
            first_line, last_line = match.groups()
        else:
            match = re.match(r"(.+?):(\d+)(?::(\d+))?$", location_spec)
            if match:
                included_filename, first_line, last_line = match.groups()
            else:
                raise SystemExit(
                    "Invalid line number or link format for location.")

        search_filename = (path.abspath(included_filename)
                           if included_filename else options_map['filename'])
        lineno = int(first_line)
        if last_line is None:
            # 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)
        else:
            # Find all the entries in the interval, following all links.
            last_lineno = int(last_line)
            links = set()
            for entry in data.filter_txns(entries):
                if (entry.meta['filename'] == search_filename
                        and lineno <= entry.meta['lineno'] <= last_lineno):
                    links.update(entry.links)

        # 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_mini_balances(linked_entries, options_map, None)