コード例 #1
0
ファイル: web.py プロジェクト: pmarciniak/beancount
 def render_account(self, account_name):
     """See base class."""
     if self.view_links:
         if self.leaf_only:
             # Calculate the number of components to figure out the indent to
             # render at.
             components = account.split(account_name)
             indent = '{:.1f}'.format(
                 len(components) * self.EMS_PER_COMPONENT)
             anchor = '<a href="{}" class="account">{}</a>'.format(
                 self.build_url(
                     'journal',
                     account_name=self.account_xform.render(account_name)),
                 account.leaf(account_name))
             return '<span "account" style="padding-left: {}em">{}</span>'.format(
                 indent, anchor)
         else:
             anchor = '<a href="{}" class="account">{}</a>'.format(
                 self.build_url(
                     'journal',
                     account_name=self.account_xform.render(account_name)),
                 account_name)
             return '<span "account">{}</span>'.format(anchor)
     else:
         if self.leaf_only:
             # Calculate the number of components to figure out the indent to
             # render at.
             components = account.split(account_name)
             indent = '{:.1f}'.format(
                 len(components) * self.EMS_PER_COMPONENT)
             account_name = account.leaf(account_name)
             return '<span "account" style="padding-left: {}em">{}</span>'.format(
                 indent, account_name)
         else:
             return '<span "account">{}</span>'.format(account_name)
コード例 #2
0
    def sort_key(self, entry):
        logger.debug(f"Sorting {entry} on {self.sort_method}")
        account_sort_key = ()
        amount_sort_key = 0
        text_sort_key = ""
        method = self.sort_method

        account_sort_key = tuple(account.split(entry.meta['_account']))
        # Not sure in what cases this is needed, but just incase
        if isinstance(entry, data.TxnPosting):
            entry = entry.txn

        if isinstance(entry, data.Transaction) and entry.postings:
            # Sort on the first account
            for posting in entry.postings:
                amount_sort_key = -abs(
                    posting.price.number) if posting.price else 0
                break

            text_sort_key = entry.narration

        if isinstance(entry, data.Note):
            account_sort_key = tuple(account.split(entry.account))
            text_sort_key = entry.comment

        if entry.meta.get('filename') == self.merge_file:
            # If we're dealing with a transaction originating from our existing file, respect the position
            line_number = entry.meta.get('lineno', 0)
        else:
            # Let's put it at the end of the day?
            line_number = 99999

        if method == 'date':
            return (
                entry.meta.get('global-sort', 100),
                entry.date,
                entry.meta.get('sort', 100),
                #  entry.meta.get('lineno', 0) if , # Not sure how highly to prioritize existing position.
                data.SORT_ORDER.get(type(entry), 0),
                account_sort_key,
                amount_sort_key,
                text_sort_key,
            )
        elif method == 'account':
            sort_key = (
                entry.meta.get('global-sort', 100),
                account_sort_key,
                entry.date,
                entry.meta.get('sort', 100),
                #           entry.meta.get('lineno', 0) if , # Not sure how highly to prioritize existing position.
                data.SORT_ORDER.get(type(entry), 0),
                amount_sort_key,
                text_sort_key,
            )
            logger.debug(f"{sort_key}")
            return sort_key
        else:
            raise ValueError(
                f"Unknown sort method {method}. Try date or account")
コード例 #3
0
ファイル: account_test.py プロジェクト: simonla/beancount-1
    def test_account_split(self):
        account_name = account.split("Expenses:Toys:Computer")
        self.assertEqual(["Expenses", "Toys", "Computer"], account_name)

        account_name = account.split("Expenses")
        self.assertEqual(["Expenses"], account_name)

        account_name = account.split("")
        self.assertEqual([""], account_name)
コード例 #4
0
def get_account_type(account_name):
    """Return the type of this account's name.

    Warning: No check is made on the validity of the account type. This merely
    returns the root account of the corresponding account name.

    Args:
      account_name: A string, the name of the account whose type is to return.
    Returns:
      A string, the type of the account in 'account_name'.

    """
    assert isinstance(account_name, str), "Account is not a string: {}".format(account_name)
    return account.split(account_name)[0]
コード例 #5
0
def get_account_components(entries):
    """Gather all the account components available in the given directives.

    Args:
      entries: A list of directive instances.
    Returns:
      A set of strings, the unique account components, including the root
      account names.
    """
    accounts = get_accounts(entries)
    components = set()
    for account_name in accounts:
        components.update(account.split(account_name))
    return sorted(components)
コード例 #6
0
ファイル: getters.py プロジェクト: simonla/beancount-1
def get_dict_accounts(account_names):
    """Return a nested dict of all the unique leaf names.
    account names are labelled with LABEL=True

    Args:
      account_names: An iterable of account names (strings)
    Returns:
      A nested OrderedDict of account leafs
    """
    leveldict = OrderedDict()
    for account_name in account_names:
        nested_dict = leveldict
        for component in account.split(account_name):
            nested_dict = nested_dict.setdefault(component, OrderedDict())
        nested_dict[get_dict_accounts.ACCOUNT_LABEL] = True
    return leveldict
コード例 #7
0
ファイル: organizer.py プロジェクト: runarp/coolbeans
 def guess_account(self, entry: data.Directive) -> str:
     # Tag each entry with an "Account" Based on attribute, or best guess on Postings
     if isinstance(entry, data.Transaction) and entry.postings:
         logger.debug(
             f"Guessing account for {entry} in {entry.meta['filename']}")
         # Sort on the first account
         for posting in entry.postings:
             account_parts = account.split(posting.account)
             if account_parts[0] in ("Assets", "Liabilities"):
                 return posting.account
         else:
             return entry.postings[0]
     elif hasattr(entry, 'account'):
         return entry.account
     else:
         return 'other'
コード例 #8
0
ファイル: wishfarm.py プロジェクト: scauligi/refried
def wishfarm(entries, options_map):
    errors = []
    wish_sprouts = {}
    for entry in entries:
        if isinstance(entry, Open):
            if 'wish_slot' in entry.meta:
                slot = entry.meta['wish_slot']
                if 'name' in entry.meta:
                    errors.append(
                        WishfarmError(
                            entry.meta,
                            'name should be handled by wishfarm plugin',
                            [entry]))
                    continue
                tag = entry.meta.get('tag')
                if tag is None:
                    tag = acctops.split(entry.account)[-1]
                if slot in wish_sprouts:
                    errors.append(
                        WishfarmError(entry.meta,
                                      f'Duplicate wish_slot: "{slot}"',
                                      [wish_sprouts[slot], entry]))
                    continue
                wish_sprouts[slot] = (entry, tag)
                entry.meta['name'] = f'({tag}) ...'
    for entry in entries:
        if isinstance(entry, Custom) and entry.type == "wish-list":
            if 'wish_slot' in entry.meta:
                slot = entry.meta['wish_slot']
                if slot not in wish_sprouts:
                    errors.append(
                        WishfarmError(entry.meta,
                                      f'Unavailable wish_slot: "{slot}"',
                                      entry))
                    continue
                sprout, slot_name = wish_sprouts.pop(slot)
                wish_name = entry.values[0].value
                sprout.meta['wish_name'] = wish_name
                sprout.meta['wish_filename'] = entry.meta['filename']
                sprout.meta['wish_lineno'] = entry.meta['lineno']
                sprout.meta.update({
                    k: v
                    for k, v in entry.meta.items() if k not in sprout.meta
                })
                sprout.meta['name'] = f'({slot_name}) {wish_name}'
    return entries, errors
コード例 #9
0
def aname(open_close, a, prefix=''):
    """Retrieve a name for an account/group.
    If the account/group has an associated `name:` metadata
    then return that, else return the final component of the account string.

    Args:
      open_close: The dictionary returned from `refried.get_account_entries()`.
      a: The account/group string.
      prefix: A string to use for tree-like nesting/indentation.
    Returns:
      The associated name for the account.
    """
    components = acctops.split(a)
    start = prefix * (len(components) - 1)
    if a not in open_close:
        rest = components[-1]
    else:
        rest = open_close[a][0].meta.get('name', components[-1])
    return start + rest
コード例 #10
0
def get_leveln_parent_accounts(account_names, level, nrepeats=0):
    """Return a list of all the unique leaf names are level N in an account hierarchy.

    Args:
      account_names: A list of account names (strings)
      level: The level to cross-cut. 0 is for root accounts.
      nrepeats: A minimum number of times a leaf is required to be present in the
        the list of unique account names in order to be returned by this function.
    Returns:
      A list of leaf node names.
    """
    leveldict = defaultdict(int)
    for account_name in set(account_names):
        components = account.split(account_name)
        if level < len(components):
            leveldict[components[level]] += 1
    levels = {level_
              for level_, count in leveldict.items()
              if count > nrepeats}
    return sorted(levels)
コード例 #11
0
ファイル: realization.py プロジェクト: emowen4/beancount
def get(real_account, account_name, default=None):
    """Fetch the subaccount name from the real_account node.

    Args:
      real_account: An instance of RealAccount, the parent node to look for
        children of.
      account_name: A string, the name of a possibly indirect child leaf
        found down the tree of 'real_account' nodes.
      default: The default value that should be returned if the child
        subaccount is not found.
    Returns:
      A RealAccount instance for the child, or the default value, if the child
      is not found.
    """
    if not isinstance(account_name, str):
        raise ValueError
    components = account.split(account_name)
    for component in components:
        real_child = real_account.get(component, default)
        if real_child is default:
            return default
        real_account = real_child
    return real_account
コード例 #12
0
ファイル: realization.py プロジェクト: emowen4/beancount
def get_or_create(real_account, account_name):
    """Fetch the subaccount name from the real_account node.

    Args:
      real_account: An instance of RealAccount, the parent node to look for
        children of, or create under.
      account_name: A string, the name of the direct or indirect child leaf
        to get or create.
    Returns:
      A RealAccount instance for the child, or the default value, if the child
      is not found.
    """
    if not isinstance(account_name, str):
        raise ValueError
    components = account.split(account_name)
    path = []
    for component in components:
        path.append(component)
        real_child = real_account.get(component, None)
        if real_child is None:
            real_child = RealAccount(account.join(*path))
            real_account[component] = real_child
        real_account = real_child
    return real_account
コード例 #13
0
    def safe_add_entry(self, entry):
        """Check for possible duplicate in the existing self.entries"""

        if self.year:
            if entry.date.year != self.year:
                return

        # Loaded Transactions could have a match-key, to help de-duplicate
        match_key = entry.meta.get('match-key', None)
        if not match_key:
            # Roll our own match-key for some things
            match_key = printer.format_entry(entry)

        if isinstance(entry, data.Transaction) and entry.postings:
            # Sort on the first account
            for posting in entry.postings:
                account_parts = account.split(posting.account)
                if account_parts[0] in ("Assets", "Liabilities"):
                    entry.meta['_account'] = posting.account
                    break
            else:
                # Use last account?
                entry.meta['_account'] = entry.postings[0]
        elif hasattr(entry, 'account'):
            entry.meta['_account'] = entry.account
        else:
            entry.meta['_account'] = 'other'

        found_match = False
        existing_entry = None
        remove_list = []

        # TODO do a yaml.load(match_key) to support list
        count = 0
        while match_key and not found_match:
            existing_entry = None
            if match_key in self.duplicates:
                existing_entry = self.duplicates.get(match_key)
            if existing_entry:
                # Don't do anything since it's duplicate
                found_match = True
            else:
                # Make note of this match-key
                self.duplicates[match_key] = entry
            count += 1
            # We support multiple match keys in the format 'match-key-1' .. 'match-key-N'
            match_key = entry.meta.get(f'match-key-{count}', None)

        if found_match:
            # We only "preserve" * entries.  Others might be overwritten.
            if not hasattr(existing_entry, 'flag'):
                # No need to check flags
                return
            # Make sure the existing_entry isn't "booked" with a '*'
            if existing_entry.flag == entry.flag or existing_entry.flag == '*':
                return
            else:
                # We need to replace the existing entry!
                remove_list.append(existing_entry)

        for item in remove_list:
            if item in self.entries:
                self.entries.remove(item)

        self.entries.append(entry)
コード例 #14
0
def categorize_accounts(account: Account, accounts: Set[Account],
                        atypes: tuple) -> Dict[Account, Cat]:
    """Categorize the type of accounts for a particular stock. Our purpose is to
    make the types of postings generic, so they can be categorized and handled
    generically later on.

    The patterns used in this file depend on the particular choices of account
    names in my chart of accounts, and for others to use this, this needs to be
    specialized somewhat.
    """
    accpath = accountlib.join(*accountlib.split(account)[1:-1])
    currency = accountlib.leaf(account)

    accounts = set(accounts)
    catmap = {}

    def move(acc, category):
        if acc in accounts:
            accounts.remove(acc)
            catmap[acc] = category

    # The account itself.
    move(account, Cat.ASSET)

    # The adjacent cash account.
    move(accountlib.join(atypes.assets, accpath, "Cash"), Cat.CASH)

    # The adjacent P/L and interest account.
    move(accountlib.join(atypes.income, accpath, "PnL"), Cat.PNL)
    move(accountlib.join(atypes.income, accountlib.parent(accpath), "PnL"),
         Cat.PNL)
    move(accountlib.join(atypes.income, accpath, "Interest"), Cat.INTEREST)

    # The associated dividend account.
    move(accountlib.join(atypes.income, accpath, currency, "Dividend"),
         Cat.DIVIDEND)

    # Rounding error account.
    move("Equity:RoundingError", Cat.ROUNDING)
    move("Expenses:Losses", Cat.ROUNDING)
    move("Income:US:MSSB:RoundingVariance", Cat.ROUNDING)

    for acc in list(accounts):

        # Employer match and corresponding tracking accounts in IRAUSD.
        if re.match(
                "({}|{}):.*:Match401k".format(atypes.assets, atypes.expenses),
                acc):
            move(acc, Cat.TRACKING)
        elif re.search(r"\b(Vested|Unvested)", acc):
            move(acc, Cat.TRACKING)

        # Dividends for other stocks.
        elif re.search(r":Dividends?$", acc):
            move(acc, Cat.OTHERDIVIDEND)

        # Direct contribution from employer.
        elif re.match("{}:.*:Match401k$".format(atypes.income), acc):
            move(acc, Cat.CASH)
        elif re.search(":GSURefund$", acc):
            move(acc, Cat.CASH)

        # Expenses accounts.
        elif acc.startswith(atypes.expenses):
            move(acc, Cat.EXPENSES)
        elif acc.endswith(":Commissions"):  # Income..Commissions
            move(acc, Cat.EXPENSES)

        # Currency accounts.
        elif acc.startswith("Equity:CurrencyAccounts"):
            move(acc, Cat.CONVERSIONS)

        # Other cash or checking accounts.
        elif acc.endswith(":Cash"):
            move(acc, Cat.CASH)
        elif acc.endswith(":Checking"):
            move(acc, Cat.CASH)
        elif acc.endswith("Receivable"):
            move(acc, Cat.CASH)

        # Other stock.
        elif re.match("{}:[A-Z_.]+$".format(accountlib.parent(acc)), acc):
            move(acc, Cat.OTHERASSET)

        else:
            print("ERROR: Unknown account: {}".format(acc))
            move(acc, Cat.UNKNOWN)

    return catmap
コード例 #15
0
def _reverse_parents(a):
    chain = acctops.split(a)
    for i in range(len(chain)):
        yield acctops.join(*chain[:i + 1])