保存三个修改后的beancount文件,可以识别科目中文字符
This commit is contained in:
parent
c0541dd553
commit
a5b4605cf6
|
|
@ -0,0 +1,236 @@
|
||||||
|
"""Functions that operate on account strings.
|
||||||
|
|
||||||
|
These account objects are rather simple and dumb; they do not contain the list
|
||||||
|
of their associated postings. This is achieved by building a realization; see
|
||||||
|
realization.py for details.
|
||||||
|
"""
|
||||||
|
__copyright__ = "Copyright (C) 2013-2016 Martin Blais"
|
||||||
|
__license__ = "GNU GPLv2"
|
||||||
|
|
||||||
|
import re
|
||||||
|
import os
|
||||||
|
from os import path
|
||||||
|
|
||||||
|
from beancount.utils import regexp_utils
|
||||||
|
|
||||||
|
|
||||||
|
# Component separator for account names.
|
||||||
|
# pylint: disable=invalid-name
|
||||||
|
sep = ':'
|
||||||
|
|
||||||
|
|
||||||
|
# # Regular expression string that matches valid account name components.
|
||||||
|
# # Categories are:
|
||||||
|
# # Lu: Uppercase letters.
|
||||||
|
# # L: All letters.
|
||||||
|
# # Nd: Decimal numbers.
|
||||||
|
# ACC_COMP_TYPE_RE = regexp_utils.re_replace_unicode(r"[\p{Lu}][\p{L}\p{Nd}\-]*")
|
||||||
|
# ACC_COMP_NAME_RE = regexp_utils.re_replace_unicode(r"[\p{Lu}\p{Nd}][\p{L}\p{Nd}\-]*")
|
||||||
|
|
||||||
|
# Regular expression string that matches valid account name components.
|
||||||
|
# Categories are:
|
||||||
|
# Lu: Uppercase letters.
|
||||||
|
# Lu1: Uppercase letters And CJK letters
|
||||||
|
# L: All letters.
|
||||||
|
# Nd: Decimal numbers.
|
||||||
|
ACC_COMP_TYPE_RE = regexp_utils.re_replace_unicode(r"[\p{Lu}][\p{L}\p{Nd}\-]*")
|
||||||
|
ACC_COMP_NAME_RE = regexp_utils.re_replace_unicode(r"[\p{Lu1}\p{Nd}][\p{L}\p{Nd}\-]*")
|
||||||
|
|
||||||
|
# Regular expression string that matches a valid account. {5672c7270e1e}
|
||||||
|
ACCOUNT_RE = "(?:{})(?:{}{})+".format(ACC_COMP_TYPE_RE, sep, ACC_COMP_NAME_RE)
|
||||||
|
|
||||||
|
|
||||||
|
# A dummy object which stands for the account type. Values in custom directives
|
||||||
|
# use this to disambiguate between string objects and account names.
|
||||||
|
TYPE = '<AccountDummy>'
|
||||||
|
|
||||||
|
|
||||||
|
def is_valid(string):
|
||||||
|
"""Return true if the given string is a valid account name.
|
||||||
|
This does not check for the root account types, just the general syntax.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
string: A string, to be checked for account name pattern.
|
||||||
|
Returns:
|
||||||
|
A boolean, true if the string has the form of an account's name.
|
||||||
|
"""
|
||||||
|
return (isinstance(string, str) and
|
||||||
|
bool(re.match('{}$'.format(ACCOUNT_RE), string)))
|
||||||
|
|
||||||
|
|
||||||
|
def join(*components):
|
||||||
|
"""Join the names with the account separator.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
*components: Strings, the components of an account name.
|
||||||
|
Returns:
|
||||||
|
A string, joined in a single account name.
|
||||||
|
"""
|
||||||
|
return sep.join(components)
|
||||||
|
|
||||||
|
|
||||||
|
def split(account_name):
|
||||||
|
"""Split an account's name into its components.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
account_name: A string, an account name.
|
||||||
|
Returns:
|
||||||
|
A list of strings, the components of the account name (without the separators).
|
||||||
|
"""
|
||||||
|
return account_name.split(sep)
|
||||||
|
|
||||||
|
|
||||||
|
def parent(account_name):
|
||||||
|
"""Return the name of the parent account of the given account.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
account_name: A string, the name of the account whose parent to return.
|
||||||
|
Returns:
|
||||||
|
A string, the name of the parent account of this account.
|
||||||
|
"""
|
||||||
|
assert isinstance(account_name, str), account_name
|
||||||
|
if not account_name:
|
||||||
|
return None
|
||||||
|
components = account_name.split(sep)
|
||||||
|
components.pop(-1)
|
||||||
|
return sep.join(components)
|
||||||
|
|
||||||
|
|
||||||
|
def leaf(account_name):
|
||||||
|
"""Get the name of the leaf of this account.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
account_name: A string, the name of the account whose leaf name to return.
|
||||||
|
Returns:
|
||||||
|
A string, the name of the leaf of the account.
|
||||||
|
"""
|
||||||
|
assert isinstance(account_name, str)
|
||||||
|
return account_name.split(sep)[-1] if account_name else None
|
||||||
|
|
||||||
|
|
||||||
|
def sans_root(account_name):
|
||||||
|
"""Get the name of the account without the root.
|
||||||
|
|
||||||
|
For example, an input of 'Assets:BofA:Checking' will produce 'BofA:Checking'.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
account_name: A string, the name of the account whose leaf name to return.
|
||||||
|
Returns:
|
||||||
|
A string, the name of the non-root portion of this account name.
|
||||||
|
"""
|
||||||
|
assert isinstance(account_name, str)
|
||||||
|
components = account_name.split(sep)[1:]
|
||||||
|
return join(*components) if account_name else None
|
||||||
|
|
||||||
|
|
||||||
|
def root(num_components, account_name):
|
||||||
|
"""Return the first few components of an account's name.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
num_components: An integer, the number of components to return.
|
||||||
|
account_name: A string, an account name.
|
||||||
|
Returns:
|
||||||
|
A string, the account root up to 'num_components' components.
|
||||||
|
"""
|
||||||
|
return join(*(split(account_name)[:num_components]))
|
||||||
|
|
||||||
|
|
||||||
|
def has_component(account_name, component):
|
||||||
|
"""Return true if one of the account contains a given component.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
account_name: A string, an account name.
|
||||||
|
component: A string, a component of an account name. For instance,
|
||||||
|
``Food`` in ``Expenses:Food:Restaurant``. All components are considered.
|
||||||
|
Returns:
|
||||||
|
Boolean: true if the component is in the account. Note that a component
|
||||||
|
name must be whole, that is ``NY`` is not in ``Expenses:Taxes:StateNY``.
|
||||||
|
"""
|
||||||
|
return bool(re.search('(^|:){}(:|$)'.format(component), account_name))
|
||||||
|
|
||||||
|
|
||||||
|
def commonprefix(accounts):
|
||||||
|
"""Return the common prefix of a list of account names.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
accounts: A sequence of account name strings.
|
||||||
|
Returns:
|
||||||
|
A string, the common parent account. If none, returns an empty string.
|
||||||
|
"""
|
||||||
|
accounts_lists = [account_.split(sep)
|
||||||
|
for account_ in accounts]
|
||||||
|
# Note: the os.path.commonprefix() function just happens to work here.
|
||||||
|
# Inspect its code, and even the special case of no common prefix
|
||||||
|
# works well with str.join() below.
|
||||||
|
common_list = path.commonprefix(accounts_lists)
|
||||||
|
return sep.join(common_list)
|
||||||
|
|
||||||
|
|
||||||
|
def walk(root_directory):
|
||||||
|
"""A version of os.walk() which yields directories that are valid account names.
|
||||||
|
|
||||||
|
This only yields directories that are accounts... it skips the other ones.
|
||||||
|
For convenience, it also yields you the account's name.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
root_directory: A string, the name of the root of the hierarchy to be walked.
|
||||||
|
Yields:
|
||||||
|
Tuples of (root, account-name, dirs, files), similar to os.walk().
|
||||||
|
"""
|
||||||
|
for root, dirs, files in os.walk(root_directory):
|
||||||
|
dirs.sort()
|
||||||
|
files.sort()
|
||||||
|
relroot = root[len(root_directory)+1:]
|
||||||
|
account_name = relroot.replace(os.sep, sep)
|
||||||
|
if is_valid(account_name):
|
||||||
|
yield (root, account_name, dirs, files)
|
||||||
|
|
||||||
|
|
||||||
|
def parent_matcher(account_name):
|
||||||
|
"""Build a predicate that returns whether an account is under the given one.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
account_name: The name of the parent account we want to check for.
|
||||||
|
Returns:
|
||||||
|
A callable, which, when called, will return true if the given account is a
|
||||||
|
child of ``account_name``.
|
||||||
|
"""
|
||||||
|
return re.compile(r'{}($|{})'.format(re.escape(account_name), sep)).match
|
||||||
|
|
||||||
|
|
||||||
|
def parents(account_name):
|
||||||
|
"""A generator of the names of the parents of this account, including this account.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
account_name: The name of the account we want to start iterating from.
|
||||||
|
Returns:
|
||||||
|
A generator of account name strings.
|
||||||
|
"""
|
||||||
|
while account_name:
|
||||||
|
yield account_name
|
||||||
|
account_name = parent(account_name)
|
||||||
|
|
||||||
|
|
||||||
|
class AccountTransformer:
|
||||||
|
"""Account name transformer.
|
||||||
|
|
||||||
|
This is used to support Win... huh, filesystems and platforms which do not
|
||||||
|
support colon characters.
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
rsep: A character string, the new separator to use in link names.
|
||||||
|
"""
|
||||||
|
def __init__(self, rsep=None):
|
||||||
|
self.rsep = rsep
|
||||||
|
|
||||||
|
def render(self, account_name):
|
||||||
|
"Convert the account name to a transformed account name."
|
||||||
|
return (account_name
|
||||||
|
if self.rsep is None
|
||||||
|
else account_name.replace(sep, self.rsep))
|
||||||
|
|
||||||
|
def parse(self, transformed_name):
|
||||||
|
"Convert the transform account name to an account name."
|
||||||
|
return (transformed_name
|
||||||
|
if self.rsep is None
|
||||||
|
else transformed_name.replace(self.rsep, sep))
|
||||||
|
|
@ -0,0 +1,175 @@
|
||||||
|
"""Definition for global account types.
|
||||||
|
|
||||||
|
This is where we keep the global account types value and definition.
|
||||||
|
|
||||||
|
Note that it's unfortunate that we're using globals and side-effect here, but
|
||||||
|
this is the best solution in the short-term, the account types are used
|
||||||
|
in too many places to pass around that state everywhere. Maybe we change
|
||||||
|
this later on.
|
||||||
|
"""
|
||||||
|
__copyright__ = "Copyright (C) 2014-2017 Martin Blais"
|
||||||
|
__license__ = "GNU GPLv2"
|
||||||
|
|
||||||
|
import re
|
||||||
|
from collections import namedtuple
|
||||||
|
|
||||||
|
from beancount.core import account
|
||||||
|
|
||||||
|
|
||||||
|
# A tuple that contains the names of the root accounts.
|
||||||
|
# Attributes:
|
||||||
|
# assets: a str, the name of the prefix for the Asset subaccounts.
|
||||||
|
# liabilities: a str, the name of the prefix for the Liabilities subaccounts.
|
||||||
|
# equity: a str, the name of the prefix for the Equity subaccounts.
|
||||||
|
# income: a str, the name of the prefix for the Income subaccounts.
|
||||||
|
# expenses: a str, the name of the prefix for the Expenses subaccounts.
|
||||||
|
AccountTypes = namedtuple('AccountTypes', "assets liabilities equity income expenses")
|
||||||
|
|
||||||
|
# Default values for root accounts.
|
||||||
|
DEFAULT_ACCOUNT_TYPES = AccountTypes("Assets",
|
||||||
|
"Liabilities",
|
||||||
|
"Equity",
|
||||||
|
"Income",
|
||||||
|
"Expenses")
|
||||||
|
|
||||||
|
|
||||||
|
def get_account_sort_key(account_types, account_name):
|
||||||
|
"""Return a tuple that can be used to order/sort account names.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
account_types: An instance of AccountTypes, a tuple of account type names.
|
||||||
|
Returns:
|
||||||
|
A function object to use as the optional 'key' argument to the sort
|
||||||
|
function. It accepts a single argument, the account name to sort and
|
||||||
|
produces a sortable key.
|
||||||
|
"""
|
||||||
|
return (account_types.index(get_account_type(account_name)), account_name)
|
||||||
|
|
||||||
|
|
||||||
|
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]
|
||||||
|
|
||||||
|
|
||||||
|
def is_account_type(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_type: A string, the prefix type of the account.
|
||||||
|
account_name: A string, the name of the account whose type is to return.
|
||||||
|
Returns:
|
||||||
|
A boolean, true if the account is of the given type.
|
||||||
|
"""
|
||||||
|
return bool(re.match('^{}{}'.format(account_type, account.sep), account_name))
|
||||||
|
|
||||||
|
|
||||||
|
def is_root_account(account_name, account_types=None):
|
||||||
|
"""Return true if the account name is a root account.
|
||||||
|
This function does not verify whether the account root is a valid
|
||||||
|
one, just that it is a root account or not.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
account_name: A string, the name of the account to check for.
|
||||||
|
account_types: An optional instance of the current account_types;
|
||||||
|
if provided, we check against these values. If not provided, we
|
||||||
|
merely check that name pattern is that of an account component with
|
||||||
|
no separator.
|
||||||
|
Returns:
|
||||||
|
A boolean, true if the account is root account.
|
||||||
|
"""
|
||||||
|
assert isinstance(account_name, str), "Account is not a string: {}".format(account_name)
|
||||||
|
if account_types is not None:
|
||||||
|
assert isinstance(account_types, AccountTypes), (
|
||||||
|
"Account types has invalid type: {}".format(account_types))
|
||||||
|
return account_name in account_types
|
||||||
|
else:
|
||||||
|
# return (account_name and
|
||||||
|
# bool(re.match(r'([A-Z][A-Za-z0-9\-]+)$', account_name)))
|
||||||
|
return (account_name and
|
||||||
|
bool(re.match(r'([A-Z][A-Za-z一-鿼㐀-䶿0-9\-]+)$', account_name)))
|
||||||
|
|
||||||
|
|
||||||
|
def is_balance_sheet_account(account_name, account_types):
|
||||||
|
"""Return true if the given account is a balance sheet account.
|
||||||
|
Assets, liabilities and equity accounts are balance sheet accounts.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
account_name: A string, an account name.
|
||||||
|
account_types: An instance of AccountTypes.
|
||||||
|
Returns:
|
||||||
|
A boolean, true if the account is a balance sheet account.
|
||||||
|
"""
|
||||||
|
assert isinstance(account_name, str), "Account is not a string: {}".format(account_name)
|
||||||
|
assert isinstance(account_types, AccountTypes), (
|
||||||
|
"Account types has invalid type: {}".format(account_types))
|
||||||
|
account_type = get_account_type(account_name)
|
||||||
|
return account_type in (account_types.assets,
|
||||||
|
account_types.liabilities,
|
||||||
|
account_types.equity)
|
||||||
|
|
||||||
|
|
||||||
|
def is_income_statement_account(account_name, account_types):
|
||||||
|
"""Return true if the given account is an income statement account.
|
||||||
|
Income and expense accounts are income statement accounts.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
account_name: A string, an account name.
|
||||||
|
account_types: An instance of AccountTypes.
|
||||||
|
Returns:
|
||||||
|
A boolean, true if the account is an income statement account.
|
||||||
|
"""
|
||||||
|
assert isinstance(account_name, str), "Account is not a string: {}".format(account_name)
|
||||||
|
assert isinstance(account_types, AccountTypes), (
|
||||||
|
"Account types has invalid type: {}".format(account_types))
|
||||||
|
account_type = get_account_type(account_name)
|
||||||
|
return account_type in (account_types.income,
|
||||||
|
account_types.expenses)
|
||||||
|
|
||||||
|
|
||||||
|
def is_equity_account(account_name, account_types):
|
||||||
|
"""Return true if the given account is an equity account.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
account_name: A string, an account name.
|
||||||
|
account_types: An instance of AccountTypes.
|
||||||
|
Returns:
|
||||||
|
A boolean, true if the account is an equity account.
|
||||||
|
"""
|
||||||
|
assert isinstance(account_name, str), "Account is not a string: {}".format(account_name)
|
||||||
|
assert isinstance(account_types, AccountTypes), (
|
||||||
|
"Account types has invalid type: {}".format(account_types))
|
||||||
|
account_type = get_account_type(account_name)
|
||||||
|
return account_type == account_types.equity
|
||||||
|
|
||||||
|
|
||||||
|
def get_account_sign(account_name, account_types=None):
|
||||||
|
"""Return the sign of the normal balance of a particular account.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
account_name: A string, the name of the account whose sign is to return.
|
||||||
|
account_types: An optional instance of the current account_types.
|
||||||
|
Returns:
|
||||||
|
+1 or -1, depending on the account's type.
|
||||||
|
"""
|
||||||
|
if account_types is None:
|
||||||
|
account_types = DEFAULT_ACCOUNT_TYPES
|
||||||
|
assert isinstance(account_name, str), "Account is not a string: {}".format(account_name)
|
||||||
|
account_type = get_account_type(account_name)
|
||||||
|
return (+1
|
||||||
|
if account_type in (account_types.assets,
|
||||||
|
account_types.expenses)
|
||||||
|
else -1)
|
||||||
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue