179 lines
6.7 KiB
Python
179 lines
6.7 KiB
Python
|
"""CLI: main"""
|
||
|
|
||
|
from re import search, split
|
||
|
from sys import argv, stderr
|
||
|
|
||
|
from piggybank import VERSION
|
||
|
from piggybank.cli import complement_list_of_coins
|
||
|
from piggybank.configuration import Configuration
|
||
|
from piggybank.currencies import Currency, BaseCurrencyError, CURRENCIES
|
||
|
from piggybank.piggybank import PiggyBank
|
||
|
from piggybank.transaction import sum_transactions, TYPE_INCOME, TYPE_OUTCOME
|
||
|
|
||
|
__all__ = ["main"]
|
||
|
|
||
|
|
||
|
USAGE = \
|
||
|
"""Usage: piggybank [OPTIONS] (COINS in FILE [of CURRENCY] | COINS from FILE
|
||
|
| show FILE [with t(ransactions)])
|
||
|
|
||
|
Options:
|
||
|
\t-h,--help -- print this help;
|
||
|
\t-v,--version -- print program version;
|
||
|
\t-L,--list-currencies -- list supported currencies and a default one;
|
||
|
\t--set-default-currency -- set currency that'll be used as a default;
|
||
|
\t-r,--reversed -- change COINS' complementation from left to right.
|
||
|
\t (e.g. 4 5 -> 0 0 0 0 4 5 by default; 4 5 0 0 0 0
|
||
|
\t with this flag. If currency has 6 coins)
|
||
|
|
||
|
There are three actions available: put, take and show.
|
||
|
|
||
|
Put: piggybank [-r|--reversed] COINS in FILE [of CURRENCY]
|
||
|
Take: piggybank [-r|--reversed] COINS from FILE
|
||
|
Show: piggybank show FILE [with t(ransactions)]
|
||
|
|
||
|
Arguments:
|
||
|
\tCOINS -- a set of comma or whitespace separated coin counts;
|
||
|
\tFILE -- a filename of a piggybank;
|
||
|
\tof CURRENCY -- set a partcular currency for a piggybank;
|
||
|
\twith t(ransactions) -- print a table of transactions as well.
|
||
|
"""
|
||
|
|
||
|
|
||
|
def parse_common_arguments(args: str) -> dict:
|
||
|
r = r"(?P<help>-h|--help)|(?P<version>-v|--version)" \
|
||
|
r"|(?P<list_currencies>-L|--list-currencies)" \
|
||
|
r"|(?=--set-default-currency (?P<default_currency>\w+))"
|
||
|
argd = search(r, args)
|
||
|
if not argd is None:
|
||
|
argd = argd.groupdict()
|
||
|
return {
|
||
|
"help": not argd["help"] is None,
|
||
|
"version": not argd["version"] is None,
|
||
|
"list-currencies": not argd["list_currencies"] is None,
|
||
|
"default-currency": argd["default_currency"] }
|
||
|
return None
|
||
|
|
||
|
def parse_arguments(args: str) -> dict:
|
||
|
r = r"((?P<reversed>-r|--reversed)? ?(?P<coins>[\d, ]+)?)" \
|
||
|
r" ?(?P<action>in|from|show) (?P<file>\S+)" \
|
||
|
r" ?(?=(?=of (?P<currency>\S+)" \
|
||
|
r"|(?=with (?P<transactions>t|transactions))))?"
|
||
|
argd = search(r, args)
|
||
|
if not argd is None:
|
||
|
argd = argd.groupdict()
|
||
|
return {
|
||
|
"reversed": not argd["reversed"] is None,
|
||
|
"coins": list(map(int, split(r"\D", argd["coins"].strip()))) \
|
||
|
if not argd["coins"] is None else None,
|
||
|
"action": argd["action"],
|
||
|
"file": argd["file"],
|
||
|
"currency": argd["currency"],
|
||
|
"show-transactions": argd["transactions"] }
|
||
|
return None
|
||
|
|
||
|
|
||
|
def print_transactions(pb: PiggyBank) -> None:
|
||
|
cur = CURRENCIES[pb.currency]
|
||
|
def print_separator(l: str, m: str, r: str):
|
||
|
print(f"{l}{'━'*21}{m}{'━'*5}{m}{m.join(['━'*12]*cur.count)}{r}")
|
||
|
|
||
|
print_separator("┏", "┳", "┓")
|
||
|
print(f"┃{'Timestamp':^21}┃ I/O ┃" \
|
||
|
f"{'┃'.join(f'{n:^12}' for n in cur.coin_names)}┃")
|
||
|
print_separator("┣", "╋", "┫")
|
||
|
for t in pb.transactions:
|
||
|
print(f"┃ {t.timestamp.replace('T', ' ')} "
|
||
|
f"┃{t.direction:^5}"
|
||
|
f"┃{'┃'.join(f'{c:^12}' for c in t.coins)}┃")
|
||
|
print_separator("┗", "┻", "┛")
|
||
|
|
||
|
def print_summary(pb: PiggyBank) -> None:
|
||
|
cur = CURRENCIES[pb.currency]
|
||
|
def print_separator(l: str, lm: str, rm: str, r:str):
|
||
|
print(f"{l}{'━'*27}{lm}{rm.join(['━'*12]*cur.count)}{r}")
|
||
|
|
||
|
print_separator("┏", "┳", "┳", "┓")
|
||
|
print(f"┃{cur.name:^27}┃{'┃'.join(f'{n:^12}' for n in cur.coin_names)}┃")
|
||
|
print_separator("┣", "╋", "╋", "┫")
|
||
|
s = sum_transactions(pb.transactions)
|
||
|
print(f"┃{'Counts':^27}┃{'┃'.join(f'{c:^12}' for c in s)}┃")
|
||
|
print_separator("┣", "╋", "╋", "┫")
|
||
|
s = cur.multiply(s)
|
||
|
print(f"┃{'Sums':^27}┃{'┃'.join(f'{c/100:^12.2f}' for c in s)}┃")
|
||
|
print_separator("┣", "╋", "┻", "┫")
|
||
|
s = sum(s)
|
||
|
print(f"┃{'Total':^27}┃{s:^{12*cur.count+cur.count-1}}┃")
|
||
|
print_separator("┗", "┻", "━", "┛")
|
||
|
|
||
|
|
||
|
def print_supported_currencies() -> None:
|
||
|
"""Print a list of supported currencies."""
|
||
|
print("Supported currencies are:")
|
||
|
for cur in CURRENCIES.keys():
|
||
|
print(f" {cur:^4} ┃ {CURRENCIES[cur].name:^31}"
|
||
|
f" ┃ {CURRENCIES[cur].description}")
|
||
|
print("Default currency is", Configuration()["default-currency"])
|
||
|
|
||
|
|
||
|
def load_user_defined_currencies() -> None:
|
||
|
if not "currency" in Configuration().items:
|
||
|
return
|
||
|
for iso, cur in Configuration()["currency"].items():
|
||
|
CURRENCIES[iso] = Currency.from_string(cur)
|
||
|
|
||
|
|
||
|
def main():
|
||
|
cargs = parse_common_arguments(' '.join(argv))
|
||
|
if not cargs is None:
|
||
|
if cargs["help"]:
|
||
|
print(USAGE)
|
||
|
exit()
|
||
|
elif cargs["version"]:
|
||
|
print(VERSION)
|
||
|
exit()
|
||
|
elif cargs["list-currencies"]:
|
||
|
print_supported_currencies()
|
||
|
exit()
|
||
|
elif not cargs["default-currency"] is None:
|
||
|
conf = Configuration()
|
||
|
conf["default-currency"] = cargs["default-currency"]
|
||
|
conf.save()
|
||
|
exit()
|
||
|
|
||
|
args = parse_arguments(' '.join(argv))
|
||
|
if not args:
|
||
|
print(USAGE)
|
||
|
exit()
|
||
|
|
||
|
try:
|
||
|
load_user_defined_currencies()
|
||
|
try:
|
||
|
pb = PiggyBank.from_file(args["file"])
|
||
|
except FileNotFoundError:
|
||
|
if args["action"] == "in":
|
||
|
currency = Configuration()["default-currency"] \
|
||
|
if args["currency"] is None else args["currency"]
|
||
|
pb = PiggyBank(currency)
|
||
|
else:
|
||
|
raise FileNotFoundError(f"{args['file']} is missing.")
|
||
|
if args["action"] in ["in", "from"]:
|
||
|
coins = complement_list_of_coins(args["coins"], pb.currency,
|
||
|
args["reversed"])
|
||
|
pb.transact(coins, TYPE_INCOME if args["action"] == "in" \
|
||
|
else TYPE_OUTCOME)
|
||
|
pb.save(args["file"])
|
||
|
elif args["action"] == "show":
|
||
|
print_summary(pb)
|
||
|
if args["show-transactions"]:
|
||
|
print_transactions(pb)
|
||
|
except BaseCurrencyError as err:
|
||
|
print(f"{type(err).__name__}:", err, file=stderr)
|
||
|
except FileNotFoundError as err:
|
||
|
print(f"{type(err).__name__}:", err, file=stderr)
|
||
|
except ValueError as err:
|
||
|
print(f"{type(err).__name__}:", err, file=stderr)
|
||
|
except Exception as err:
|
||
|
print(f"Something went exceptionally wrong. Error:",
|
||
|
f"{type(err).__name__}:", err, file=stderr)
|