1
0
PiggyBank/piggybank/cli/main.py

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)