保存三个修改后的beancount文件,可以识别科目中文字符

This commit is contained in:
liangzai450 2024-03-18 01:05:43 +08:00
parent c0541dd553
commit a5b4605cf6
3 changed files with 1753 additions and 0 deletions

View File

@ -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))

View File

@ -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