保存三个修改后的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