2020-07-08 02:10:35 +04:00
|
|
|
"""CLI"""
|
2020-06-07 04:47:15 +04:00
|
|
|
|
|
|
|
from re import search, split
|
|
|
|
from sys import argv, stderr
|
2020-07-07 02:30:21 +04:00
|
|
|
from os.path import exists
|
2020-06-07 20:41:17 +04:00
|
|
|
from typing import List
|
2020-06-07 04:47:15 +04:00
|
|
|
|
2020-07-07 02:28:33 +04:00
|
|
|
from piggybank import VERSION, CURRENCIES
|
2020-07-07 02:30:21 +04:00
|
|
|
from piggybank.configuration import Configuration, DEFAULT_CONFIGURATION_FILE
|
2020-07-07 02:28:33 +04:00
|
|
|
from piggybank.currency import Currency, BaseCurrencyError
|
2020-06-07 04:47:15 +04:00
|
|
|
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:
|
2020-07-07 02:28:33 +04:00
|
|
|
\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
|
2020-07-08 02:10:35 +04:00
|
|
|
\t with this flag).
|
2020-06-07 04:47:15 +04:00
|
|
|
|
|
|
|
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;
|
2020-06-07 20:41:17 +04:00
|
|
|
\tFILE -- a filename of a piggy bank;
|
|
|
|
\tof CURRENCY -- set a partcular currency for a piggy bank;
|
2020-06-07 04:47:15 +04:00
|
|
|
\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)" \
|
2020-07-07 02:28:33 +04:00
|
|
|
r"|(?P<list_currencies>-L|--list-currencies)"
|
2020-06-07 04:47:15 +04:00
|
|
|
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,
|
2020-07-07 02:28:33 +04:00
|
|
|
"list-currencies": not argd["list_currencies"] is None }
|
2020-06-07 04:47:15 +04:00
|
|
|
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:
|
2020-07-07 02:30:21 +04:00
|
|
|
currency = CURRENCIES[pb.currency]
|
|
|
|
names = (currency.currency_symbol.format(n//100) if n >= 100 \
|
|
|
|
else currency.fraction_symbol.format(n) \
|
|
|
|
for n in currency.face_values)
|
2020-06-07 04:47:15 +04:00
|
|
|
def print_separator(l: str, m: str, r: str):
|
2020-07-07 02:30:21 +04:00
|
|
|
print(f"{l}{'━'*21}{m}{'━'*5}{m}{m.join(['━'*12]*len(currency))}{r}")
|
2020-06-07 04:47:15 +04:00
|
|
|
|
|
|
|
print_separator("┏", "┳", "┓")
|
|
|
|
print(f"┃{'Timestamp':^21}┃ I/O ┃" \
|
2020-07-07 02:30:21 +04:00
|
|
|
f"{'┃'.join(f'{n:^12}' for n in names)}┃")
|
2020-06-07 04:47:15 +04:00
|
|
|
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:
|
2020-07-07 02:30:21 +04:00
|
|
|
currency = CURRENCIES[pb.currency]
|
|
|
|
names = (currency.currency_symbol.format(n//100) if n >= 100 \
|
|
|
|
else currency.fraction_symbol.format(n) \
|
|
|
|
for n in currency.face_values)
|
2020-06-07 04:47:15 +04:00
|
|
|
def print_separator(l: str, lm: str, rm: str, r:str):
|
2020-07-07 02:30:21 +04:00
|
|
|
print(f"{l}{'━'*27}{lm}{rm.join(['━'*12]*len(currency))}{r}")
|
2020-06-07 04:47:15 +04:00
|
|
|
|
|
|
|
print_separator("┏", "┳", "┳", "┓")
|
2020-07-07 02:30:21 +04:00
|
|
|
print(f"┃{currency.name:^27}┃{'┃'.join(f'{n:^12}' for n in names)}┃")
|
2020-06-07 04:47:15 +04:00
|
|
|
print_separator("┣", "╋", "╋", "┫")
|
|
|
|
s = sum_transactions(pb.transactions)
|
|
|
|
print(f"┃{'Counts':^27}┃{'┃'.join(f'{c:^12}' for c in s)}┃")
|
|
|
|
print_separator("┣", "╋", "╋", "┫")
|
2020-07-07 02:30:21 +04:00
|
|
|
s = currency * s
|
2020-07-08 02:17:11 +04:00
|
|
|
print(f"┃{'Sums':^27}┃{'┃'.join(f'{c/100:^12.2f}' for c in s)}┃")
|
2020-06-07 04:47:15 +04:00
|
|
|
print_separator("┣", "╋", "┻", "┫")
|
|
|
|
s = sum(s)
|
2020-07-08 02:17:11 +04:00
|
|
|
print(f"┃{'Total':^27}┃{s/100:^{12*len(currency)+len(currency)-1}.2f}┃")
|
2020-06-07 04:47:15 +04:00
|
|
|
print_separator("┗", "┻", "━", "┛")
|
|
|
|
|
|
|
|
|
|
|
|
def print_supported_currencies() -> None:
|
|
|
|
"""Print a list of supported currencies."""
|
|
|
|
print("Supported currencies are:")
|
|
|
|
for cur in CURRENCIES.keys():
|
2020-07-08 02:10:35 +04:00
|
|
|
print(f" {cur:^4} ┃ {CURRENCIES[cur].name:^31}")
|
2020-06-07 04:47:15 +04:00
|
|
|
print("Default currency is", Configuration()["default-currency"])
|
|
|
|
|
2020-06-07 20:41:17 +04:00
|
|
|
def load_currencies() -> None:
|
|
|
|
"""Load currencies defined in a configuration file."""
|
2020-06-07 04:47:15 +04:00
|
|
|
if not "currency" in Configuration().items:
|
|
|
|
return
|
|
|
|
for iso, cur in Configuration()["currency"].items():
|
2020-06-07 20:41:17 +04:00
|
|
|
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."""
|
2020-07-08 02:10:35 +04:00
|
|
|
complementary = [0] * (len(CURRENCIES[currency]) - len(coins))
|
|
|
|
return complementary + coins if not _reversed else coins + complementary
|
2020-06-07 04:47:15 +04:00
|
|
|
|
|
|
|
|
|
|
|
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"]:
|
2020-06-07 20:41:17 +04:00
|
|
|
load_currencies()
|
2020-06-07 04:47:15 +04:00
|
|
|
print_supported_currencies()
|
|
|
|
exit()
|
|
|
|
|
|
|
|
args = parse_arguments(' '.join(argv))
|
|
|
|
if not args:
|
|
|
|
print(USAGE)
|
|
|
|
exit()
|
|
|
|
|
|
|
|
try:
|
2020-07-07 02:30:21 +04:00
|
|
|
if not exists(DEFAULT_CONFIGURATION_FILE):
|
|
|
|
Configuration.write_default_to_file(DEFAULT_CONFIGURATION_FILE)
|
2020-06-07 20:41:17 +04:00
|
|
|
load_currencies()
|
2020-07-07 02:30:21 +04:00
|
|
|
|
2020-06-07 04:47:15 +04:00
|
|
|
try:
|
|
|
|
pb = PiggyBank.from_file(args["file"])
|
|
|
|
except FileNotFoundError:
|
2020-07-07 02:30:21 +04:00
|
|
|
if args["action"] == 'in':
|
2020-06-07 04:47:15 +04:00
|
|
|
currency = Configuration()["default-currency"] \
|
2020-07-07 02:30:21 +04:00
|
|
|
if not args["currency"] else args["currency"]
|
2020-06-07 04:47:15 +04:00
|
|
|
pb = PiggyBank(currency)
|
|
|
|
else:
|
|
|
|
raise FileNotFoundError(f"{args['file']} is missing.")
|
2020-07-07 02:30:21 +04:00
|
|
|
|
2020-06-07 04:47:15 +04:00
|
|
|
if args["action"] in ["in", "from"]:
|
|
|
|
coins = complement_list_of_coins(args["coins"], pb.currency,
|
2020-07-07 02:30:21 +04:00
|
|
|
args["reversed"])
|
2020-06-07 04:47:15 +04:00
|
|
|
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)
|
2020-06-30 23:46:15 +04:00
|
|
|
|
|
|
|
if __name__ == "__main__":
|
2020-07-07 02:30:21 +04:00
|
|
|
main()
|