Source code for libwedger
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# Copyright © 2013 Felix Crux <felixc@felixcrux.com> and the Authors.
# Released under the terms of the MIT License (Expat Version).
# See the LICENSE and AUTHORS files for further details.
#
"""Interfaces to read data from ledger."""
__author__ = "Felix Crux and the Authors"
__copyright__ = "2013 " + __author__
__license__ = "MIT License"
__title__ = "libwedger"
__version__ = "0.1.0"
import datetime
import os
import re
import subprocess
_DATE_FMT = "%Y-%m-%d"
[docs]def get_all_account_names():
"""Retrieve the list of account names known to ledger.
:return: Set of account names.
:rtype: {str}
"""
full_account_names = subprocess.check_output(
["ledger", "accounts"], env=os.environ.copy()
).strip().decode().split("\n")
def get_sub_accounts(account_name, accumulator):
parts = account_name.rsplit(":", 1)
if len(parts) == 1:
return accumulator + parts
return get_sub_accounts(parts[0], accumulator + [account_name])
all_accounts = []
for full_account in full_account_names:
if full_account is "":
continue
all_accounts.extend(get_sub_accounts(full_account, []))
return set(all_accounts)
[docs]def get_account(account, start=None, end=None):
"""Get the balance of an account for the given period.
:param str account: The name of the account.
:param datetime.date start: Date from which to start counting the balance.
If omitted, you'll get the overall balance of the account.
:param datetime.date end: Date at which to stop counting the balance.
Defaults to :py:meth:`datetime.date.today()` if omitted.
:return: The account balance (including currency symbol or commodity name).
:rtype: str
"""
if end is None:
end = datetime.date.today()
if start and end:
period = "from {} to {}".format(
start.strftime(_DATE_FMT), end.strftime(_DATE_FMT))
elif start:
period = "from {}".format(start.strftime(_DATE_FMT))
else:
period = "until {}".format(end.strftime(_DATE_FMT))
output = subprocess.check_output(
["ledger", "bal", "--market", "--flat", account, "--period", period],
env=os.environ.copy()
).strip().decode()
output_lines = output.split("\n")
if len(output_lines) > 1:
return output_lines[-1].strip()
else:
return output.split(" ")[0]
[docs]def get_transaction_register():
"""Get the list of transactions."""
new_transaction_re = re.compile(
"^" # Start of the line.
"(?P<date>\d{4}-\d{2}-\d{2})" # Date in YYYY-MM-DD format.
" " # A single space.
"(?P<payee>\S(?:\S| (?! ))*)" # Payee name.
"\s{2,}") # Two or more spaces.
account_in_transaction_re = re.compile(
"\s+" # Leading whitespace.
"(?P<account>\S(?:\S| (?! ))*)" # Account name.
"\s{2,}" # Two or more spaces.
"(?P<balance>\S(?:\S| (?! ))*)" # Balance.
"\s{2,}" # Two or more spaces.
"\S(?:\S| (?! ))*" # Total within transaction (ignored).
"$") # End of the line.
# 1000 columns wide is an arbitrary limit we expect never to hit.
# It prevents truncation of long payee names/account names.
output = subprocess.check_output(
["ledger", "reg", "--columns", "1000", "--date-format", _DATE_FMT],
env=os.environ.copy()
).strip().decode()
output_lines = output.split("\n")
transactions = []
cur_transaction = None
for line in output_lines:
new_tx = new_transaction_re.search(line)
if new_tx:
if cur_transaction:
transactions.append(cur_transaction)
tx_date = datetime.datetime.strptime(
new_tx.group("date"),
_DATE_FMT)
tx_date = datetime.date(tx_date.year, tx_date.month, tx_date.day)
cur_transaction = {
"date": tx_date,
"payee": new_tx.group("payee"),
"accounts": []
}
account = account_in_transaction_re.search(line)
if account:
cur_transaction["accounts"].append(
{"account": account.group("account"),
"amount": account.group("balance")})
if cur_transaction:
transactions.append(cur_transaction)
return transactions