1
0
PiggyBank/piggybank/cli/main.py

186 lines
7.1 KiB
Python
Raw Normal View History

"""CLI: main"""
from re import search, split
from sys import argv, stderr
from typing import List
from piggybank import VERSION
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 piggy bank;
\tof CURRENCY -- set a partcular currency for a piggy bank;
\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 * s
print(f"{'Sums in currency':^27}{''.join(f'{c/100:^12.2f}' for c in s)}")
print_separator("", "", "", "")
s = sum(s)
print(f"{'Total in currency':^27}{s/100:^{12*cur.count+cur.count-1}.2f}")
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_currencies() -> None:
"""Load currencies defined in a configuration file."""
if not "currency" in Configuration().items:
return
for iso, cur in Configuration()["currency"].items():
CURRENCIES[iso.upper()] = Currency.from_string(cur)
def complement_list_of_coins(coins: List[int], currency: str,
_reversed: bool = False) -> List[int]:
"""Complements list of coins up to the count of currency's coins."""
offset_array = [0] * (len(CURRENCIES[currency]) - len(coins))
return offset_array + coins if not _reversed else coins + offset_array
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"]:
load_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_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)