"""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-h|--help)|(?P-v|--version)" \ r"|(?P-L|--list-currencies)" \ r"|(?=--set-default-currency (?P\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-r|--reversed)? ?(?P[\d, ]+)?)" \ r" ?(?Pin|from|show) (?P\S+)" \ r" ?(?=(?=of (?P\S+)" \ r"|(?=with (?Pt|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)