"""CLI: main""" from re import search, split from sys import argv, stderr from typing import List from piggybank import VERSION, CURRENCIES from piggybank.configuration import Configuration from piggybank.currency import Currency, BaseCurrencyError 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-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-h|--help)|(?P-v|--version)" \ r"|(?P-L|--list-currencies)" 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 } 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 * 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() 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) if __name__ == "__main__": main()