Commit 815ba9f1 authored by olb's avatar olb
Browse files

lint code

parent 73d9c965
......@@ -9,78 +9,77 @@ import os
import shutil
import sys
assert sys.version_info >= (3, 5)
try:
from jinja2 import Environment, FileSystemLoader
except ImportError:
raise Exception('Need python-jinja2')
from jinja2 import Environment, FileSystemLoader
from hreports.hledger import Hledger, AccountingYears
from hreports import settings
from hreports.hledger import AccountingYears, Hledger
assert sys.version_info >= (3, 5)
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger('hreport')
LOGGER = logging.getLogger('hreport')
tpl_env = Environment(
TPL_ENV = Environment(
loader=FileSystemLoader(settings.get('TEMPLATES_DIRS')),
extensions=['jinja2.ext.with_'],
)
def incomes_statement(accounting_years, report, context={}):
def incomes_statement(accounting_years, report, context=None):
expenses = accounting_years.extract(match='|'.join(
report['expenses']['accounts']
))
incomes = accounting_years.extract(match='|'.join(
report['incomes']['accounts']
))
context = context or dict()
context.update({
'incomes': incomes,
'expenses': expenses,
'conf': report,
})
template = tpl_env.get_template('income-statement.html')
template = TPL_ENV.get_template('income-statement.html')
return template.render(**context)
def balance_sheet(accounting_years, report, context={}):
def balance_sheet(accounting_years, report, context=None):
liabilities = accounting_years.extract(match='|'.join(
report['liabilities']['accounts']
))
assets = accounting_years.extract(match='|'.join(
report['assets']['accounts']
))
context = context or dict()
context.update({
'liabilities': liabilities,
'assets': assets,
'conf': report,
})
template = tpl_env.get_template('balance-sheet.html')
template = TPL_ENV.get_template('balance-sheet.html')
return template.render(**context)
def cash(accounting_years, report, context={}):
def cash(accounting_years, report, context=None):
accounting_year = accounting_years.get_year(accounting_years.last_year())
context = context or dict()
context.update({
'accounting_year': accounting_year,
'conf': report,
})
template = tpl_env.get_template('cash.html')
template = TPL_ENV.get_template('cash.html')
return template.render(**context)
def chart_of_accounts(accounting_years, report, context={}):
def chart_of_accounts(accounting_years, report, context=None):
accounting_year = accounting_years.get_year(accounting_years.last_year())
chart_of_accounts = accounting_year.chart_of_accounts
context = context or dict()
context.update({
'chart_of_accounts': chart_of_accounts,
'chart_of_accounts': accounting_year.chart_of_accounts,
'conf': report,
})
template = tpl_env.get_template('chart-of-accounts.html')
template = TPL_ENV.get_template('chart-of-accounts.html')
return template.render(**context)
......@@ -97,7 +96,7 @@ def load(options):
accounting_years = AccountingYears()
accounting_years_closed = AccountingYears()
for year in settings.get('ACCOUNTING_YEARS'):
logger.info('loading year {year} with hledger'.format(year=year))
LOGGER.info('loading year %s with hledger', year)
hledger = Hledger(
settings.get('ACCOUNTING_YEARS_OPTIONS')[year]['incomes_statement'],
settings.get('ACCOUNTING_YEARS_OPTIONS')[year]['chart_of_accounts'],
......@@ -106,7 +105,7 @@ def load(options):
accounting_years.add_year(
hledger.accounting_year(year)
)
logger.info('loading closed year {year} with hledger'.format(year=year))
LOGGER.info('loading closed year %s with hledger', year)
hledger = Hledger(
settings.get('ACCOUNTING_YEARS_OPTIONS')[year]['balance_sheet'],
settings.get('ACCOUNTING_YEARS_OPTIONS')[year]['chart_of_accounts'],
......@@ -119,7 +118,7 @@ def load(options):
return (accounting_years, accounting_years_closed)
def doreport(accounting_years, accounting_years_closed, report, context={}):
def doreport(accounting_years, accounting_years_closed, report, context=None):
accounting_years = accounting_years.clone_until(report['year'])
accounting_years_closed = accounting_years_closed.clone_until(report['year'])
......@@ -128,6 +127,7 @@ def doreport(accounting_years, accounting_years_closed, report, context={}):
css_path = os.path.join(settings.get('OUTPUT_DIR'), 'style.css')
css_url = os.path.relpath(css_path, directory)
context = context or dict()
context.update({
'css_url': css_url,
})
......@@ -146,8 +146,7 @@ def doreport(accounting_years, accounting_years_closed, report, context={}):
elif not os.path.isdir(directory):
raise Exception('%s must be a directory' % directory)
logger.info('write report {type} to file {file}'.format(file=report['output'],
type=report['type']))
LOGGER.info('write report %s to file %s', report['output'], report['type'])
ofile = open(report['output'], 'w+')
print(output, file=ofile)
ofile.close()
......@@ -159,8 +158,8 @@ def doreports(options):
doreport(accounting_years, accounting_years_closed, report)
def doindex(options, context={}):
template = tpl_env.get_template('index.html')
def doindex(options, context=None):
template = TPL_ENV.get_template('index.html')
output = os.path.join(
settings.get('OUTPUT_DIR'),
'index.html')
......@@ -174,12 +173,13 @@ def doindex(options, context={}):
reports_by_year[report['year']] = []
reports_by_year[report['year']].append(report)
context = context or dict()
context.update({
'css_url': 'style.css',
'accounting_years': settings.get('ACCOUNTING_YEARS'),
'reports': reports_by_year,
})
logger.info('write index to file {file}'.format(file=output))
LOGGER.info('write index to file %s', output)
print(template.render(**context), file=ofile)
......@@ -187,7 +187,7 @@ def doassets(options):
source = os.path.join(settings.get('STATIC_DIR'), 'style.css')
output = os.path.join(settings.get('OUTPUT_DIR'), 'style.css')
logger.info('copy {source} to {file}'.format(source=source, file=output))
LOGGER.info('copy %s to %s', source, output)
shutil.copyfile(source, output)
......
import locale
import logging
import re
""" hledger lib
"""
import datetime
import os
import decimal
from subprocess import Popen, PIPE
#locale.setlocale(locale.LC_ALL, 'fr_FR.utf-8')
import logging
import os
import re
from subprocess import PIPE, Popen
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger('hreport')
LOGGER = logging.getLogger('hreport')
decimal.getcontext().prec = 200
class Hledger(object):
class Hledger:
"""
Command hledger and handle output
"""
......@@ -75,7 +74,7 @@ class Hledger(object):
for line in lines:
matches = re.search(
'^\s+(\w+(?:[\s:-]\w+)*)\s+\d+\s+;\s+(\d+\w*)(:?\s+(.*))?\s*$',
r'^\s+(\w+(?:[\s:-]\w+)*)\s+\d+\s+;\s+(\d+\w*)(:?\s+(.*))?\s*$',
line,
re.UNICODE)
......@@ -93,7 +92,10 @@ class Hledger(object):
lines.close()
return coa
def atod(self, amount):
@staticmethod
def atod(amount):
""" Convert string to decimal
"""
amount = amount.replace(',', '.')
amount = decimal.Decimal(amount)
return amount
......@@ -105,27 +107,25 @@ class Hledger(object):
new_transaction_re = re.compile(
(
"^"
"(?P<year>\d{4})/(?P<month>\d{2})/(?P<day>\d{2})"
" "
r"^"
r"(?P<year>\d{4})/(?P<month>\d{2})/(?P<day>\d{2})"
r" "
), re.UNICODE)
account_re = re.compile(
(
"^\s+"
"(?P<account>\w[^;]*[.\w-])"
"\s+"
"(?P<amount>-?\d+([\.,]\d+)?)"
"(?:\s+€)?"
"\s*(?:\s;|$)"
r"^\s+"
r"(?P<account>\w[^;]*[.\w-])"
r"\s+"
r"(?P<amount>-?\d+([\.,]\d+)?)"
r"(?:\s+€)?"
r"\s*(?:\s;|$)"
),
re.UNICODE)
out = str(self.hl_print(), 'utf-8')
lines = out.split('\n')
for line in lines:
for line in out.split('\n'):
match = new_transaction_re.match(line)
if match:
tyear = int(match.group('year'))
......@@ -142,7 +142,7 @@ class Hledger(object):
else:
account = Account(uname=uname)
logger.debug('{period} // {amount:>10} // {account}'.format(
LOGGER.debug('{period} // {amount:>10} // {account}'.format(
period=str(period),
account=str(account),
amount=str(amount),
......@@ -171,7 +171,7 @@ class Hledger(object):
return accounting_year
class ChartOfAccounts(object):
class ChartOfAccounts:
"""
Chart of accounts
"""
......@@ -186,14 +186,17 @@ class ChartOfAccounts(object):
accounts = property(_accounts)
def fusion(self, chart_of_accounts):
self.per_number.update( chart_of_accounts.per_number)
self.per_uname.update( chart_of_accounts.per_uname)
self.per_number.update(chart_of_accounts.per_number)
self.per_uname.update(chart_of_accounts.per_uname)
def get_or_create(self, uname):
return self.per_uname.get(uname, Account(uname))
class Account(object):
class Account:
"""
Account
"""
def __init__(self, uname, number=None, name=None, description=None, parent=None):
self.number = number
self.uname = uname
......@@ -210,31 +213,31 @@ class Account(object):
def __eq__(self, other):
if self.number and other.number:
return (self.number, self.uname) == (other.number, other.uname)
else:
return self.uname == other.uname
return self.uname == other.uname
def __ne__(self, other):
if self.number and other.number:
return (self.number, self.uname) != (other.number, other.uname)
else:
return self.uname != other.uname
return self.uname != other.uname
def __lt__(self, other):
if self.number and other.number:
return (self.number, self.uname) < (other.number, other.uname)
else:
return self.uname < other.uname
return self.uname < other.uname
def __str__(self):
if self.name:
return "{number} − {name}".format(
number=self.number or "",
name=self.name)
else:
return str(self.uname)
return str(self.uname)
class Period(object):
class Period:
PERIODS = dict()
......@@ -272,9 +275,6 @@ class Period(object):
def __hash__(self):
return hash(self.name)
def __str__(self):
return str(self.name)
def __str__(self):
return self.name
......@@ -282,7 +282,7 @@ class Period(object):
return str(self.date.strftime('%b'))
class PeriodTable(object):
class PeriodTable:
def __init__(self):
self.balances = {}
......@@ -316,13 +316,13 @@ class PeriodTable(object):
if self._filled and not force:
return
for dic in [ self.balances, self.credits, self.debits, ]:
for dic in [self.balances, self.credits, self.debits]:
for account in self.accounts():
if not account in dic:
if account not in dic:
dic[account] = {}
for period in self.periods():
if not period in dic[account]:
if period not in dic[account]:
dic[account][period] = 0
self._filled = True
......@@ -378,7 +378,7 @@ class PeriodTable(object):
"""
Add a amount to a dictionary (credits, debits or balances)
"""
if not account in dic:
if account not in dic:
dic[account] = {}
for period in amounts:
......@@ -408,8 +408,6 @@ class PeriodTable(object):
"""
return self._add_amounts(self.debits, account, amounts)
def add_balance(self, account, period, amount):
"""
Add balance for a period
......@@ -442,18 +440,18 @@ class PeriodTable(object):
def _amount(self, dic, account=None, period=None, accounts=None, periods=None, rounded=None):
if account:
accounts = [ account ]
accounts = [account]
elif not accounts:
accounts = self.accounts()
if period:
periods = [ period ]
periods = [period]
elif not periods:
periods = self.periods()
ret= sum( dic[account][period] if account in dic else 0 for account in accounts for period in periods)
ret = sum(dic[account][period] if account in dic else 0 for account in accounts for period in periods)
if rounded != None:
if rounded is not None:
if rounded == 0:
ret = int(ret)
return ret
......@@ -475,7 +473,7 @@ class PeriodTable(object):
Return the balance at the end of the given period and for the given accounts
"""
if account:
accounts = [ account ]
accounts = [account]
elif not accounts:
accounts = self.accounts()
......@@ -484,13 +482,12 @@ class PeriodTable(object):
return 0
period = self.periods()[-1]
ret = sum( self.balances[account][period] if account in self.balances else 0 for account in accounts)
ret = sum(self.balances[account][period] if account in self.balances else 0 for account in accounts)
if begin:
ret -= self.credit(accounts=accounts, period=period) + self.debit(accounts=accounts, period=period)
if rounded != None:
if rounded is not None:
if rounded == 0:
ret = int(ret)
......@@ -502,18 +499,18 @@ class PeriodTable(object):
"""
root = None
for account in self.accounts():
if root == None:
if root is None:
root = account.uname
continue
prefix = ""
for i in range(0, min( len(root), len(account.uname))):
for i in range(0, min(len(root), len(account.uname))):
if account.uname.startswith(root[:i+1]):
prefix = root[:i+1]
else:
break
index = prefix.rfind(':')
index = prefix.rfind(':')
if prefix == root:
continue
elif account.uname == prefix:
......@@ -523,13 +520,12 @@ class PeriodTable(object):
else:
root = ""
return self.chart_of_accounts.get_or_create(root)
def print_debug(self):
for period in self.periods():
for account in self.accounts():
logger.info('{period} // {amount:>10} // {account}'.format(
LOGGER.info('{period} // {amount:>10} // {account}'.format(
period=period,
account=str(account),
amount=self.balance(account=account, period=period),
......@@ -549,6 +545,7 @@ class PeriodTable(object):
for account in to_remove:
self._accounts.remove(account)
class AccountingYear(PeriodTable):
def __init__(self, year, chart_of_accounts):
......@@ -570,9 +567,9 @@ class AccountingYear(PeriodTable):
for aggregation in aggregations or []:
if re.match('^(' + aggregation + ')(?:(?::([^;]+)))*$', account.uname, re.UNICODE):
destination = self.chart_of_accounts.get_or_create(aggregation)
table.add_balances( destination, self.balances[account])
table.add_credits( destination, self.credits[account])
table.add_debits( destination, self.debits[account])
table.add_balances(destination, self.balances[account])
table.add_credits(destination, self.credits[account])
table.add_debits(destination, self.debits[account])
none = False
if none:
......@@ -581,9 +578,9 @@ class AccountingYear(PeriodTable):
if depth and len(parts) > depth:
destination = self.chart_of_accounts.get_or_create(':'.join(parts[:depth]))
table.add_balances( destination, self.balances[account])
table.add_credits( destination, self.credits[account])
table.add_debits( destination, self.debits[account])
table.add_balances(destination, self.balances[account])
table.add_credits(destination, self.credits[account])
table.add_debits(destination, self.debits[account])
if auto_remove_zero:
table.auto_remove_zero()
......@@ -614,24 +611,24 @@ class AccountingYears(PeriodTable):
def get_year(self, year):
year = "%s" % year
if not year in self.years:
if year not in self.years:
return None
return self.years[year]
def clone_until(self, year):
def clone_until(self, until_year):
ret = AccountingYears()
for y in self.years.keys():
if y <= year:
ret.add_year(self.get_year(y))
for year in self.years:
if year <= until_year:
ret.add_year(self.get_year(year))
return ret
def balance(self, account=None, period=None, accounts=None, begin=False, rounded=None):
def balance(self, account=None, period=None, begin=False):
if period == None:
if period is None:
period = self.periods()[-1]
return self.years[period].balance(account, begin)
return self.years[period].balance(account, begin, rounded=rounded)
def extract(self, match=None, aggregations=None, depth=None, auto_remove_zero=True):
......@@ -646,12 +643,12 @@ class AccountingYears(PeriodTable):
return ret
def fill_table(self):
def fill_table(self, force=False):
if not self.chart_of_accounts:
self.chart_of_accounts = ChartOfAccounts()
for year in self.years:
self.years[year].fill_table()
self.years[year].fill_table(force=force)
self.chart_of_accounts.fusion(self.years[year].chart_of_accounts)
def last_year(self):
......
""" hreports settings
"""
import collections
import os
CONF_FILES = [
__CONF_FILES = [
"hreports.conf",
os.path.join(os.path.expanduser("~"), "hreports.conf"),
]
__settings = None
__SETTINGS = None
def get(opt):
return __settings.get(opt)
return __SETTINGS.get(opt)
for conf_file in CONF_FILES:
for conf_file in __CONF_FILES:
if os.path.isfile(conf_file):
__settings = {}
exec(open(conf_file).read(), __settings)
__SETTINGS = {}
exec(open(conf_file).read(), __SETTINGS)
break
if __settings is None:
if __SETTINGS is None:
raise Exception(
"Need a configuration file. One of %s" % ','.join(CONF_FILES))
"Need a configuration file. One of %s" % ','.join(__CONF_FILES))
if 'OUTPUT_DIR' not in __settings:
if 'OUTPUT_DIR' not in __SETTINGS:
raise Exception('need OUTPUT_DIR settings')
if 'TEMPLATES_DIRS' not in __settings:
__settings['TEMPLATES_DIRS'] = os.path.join(
if 'TEMPLATES_DIRS' not in __SETTINGS:
__SETTINGS['TEMPLATES_DIRS'] = os.path.join(
os.path.dirname(__file__), '..', 'templates')
if 'STATIC_DIR' not in __settings:
__settings['STATIC_DIR'] = os.path.join(
if 'STATIC_DIR' not in __SETTINGS:
__SETTINGS['STATIC_DIR'] = os.path.join(
os.path.dirname(__file__), '..', 'static')
__inverted = __settings.get('INVERTED', False)
if 'INVERTED' not in __SETTINGS:
__SETTINGS['INVERTED'] = False
if 'ACCOUNTING_YEARS_DEFAULT' not in __settings:
__settings['ACCOUNTING_YEARS_DEFAULT'] = {
if 'ACCOUNTING_YEARS_DEFAULT' not in __SETTINGS:
__SETTINGS['ACCOUNTING_YEARS_DEFAULT'] = {
'incomes_statement': os.path.join('accounting_years', '{year}', 'incomes_statement.journal'),
'balance_sheet': os.path.join('accounting_years', '{year}', 'balance_sheet.journal'),
'chart_of_accounts': os.path.join('accounting_years', '{year}', 'chart_of_accounts.journal'),
}
__accounting_years_default = __settings['ACCOUNTING_YEARS_DEFAULT']
__ACCOUNTING_YEARS_DEFAULT = __SETTINGS['ACCOUNTING_YEARS_DEFAULT']