1
0

Massive refactoring continues. All the mess I made last night is fixed next night. Program works!

This commit is contained in:
Alexander Andreev 2020-06-05 03:51:29 +04:00
parent df78739a36
commit 1da27a0271
9 changed files with 100 additions and 84 deletions

View File

@ -1,3 +1,8 @@
__all__ = ["__date__", "__version__", "__author__", "__email__",
"__copyright__", "__license__", "PIGGYBANK_FILE_EXTENSION",
"print_program_version"]
__date__ = "4 June 2020" __date__ = "4 June 2020"
__version__ = "1.0.0" __version__ = "1.0.0"
__author__ = "Alexander \"Arav\" Andreev" __author__ = "Alexander \"Arav\" Andreev"

View File

@ -1,12 +1,13 @@
from re import search from re import search
from typing import List, Callable from typing import List, Callable
from sys import argv, exit from sys import argv
from piggybank import print_program_version from piggybank import print_program_version
from piggybank.configuration import Configuration from piggybank.configuration import Configuration
from piggybank.currencies import CURRENCIES from piggybank.currencies import CURRENCIES
__all__ = ["handle_default_arguments", "complement_array_of_coins"] __all__ = ["handle_default_arguments", "complement_list_of_coins",
"print_supported_currencies", "decimal_to_float"]
USAGE_COMMON: str = "Usage: piggybank-* " \ USAGE_COMMON: str = "Usage: piggybank-* " \
@ -26,28 +27,31 @@ def parse_common_arguments(args: str):
if not argd is None: if not argd is None:
argd = argd.groupdict() argd = argd.groupdict()
return { return {
"help": argd["help"] is None, "help": not argd["help"] is None,
"version": argd["transactions"] is None, "version": not argd["version"] is None,
"list-currencies": argd["list_currencies"] is None, "list-currencies": not argd["list_currencies"] is None,
"default-currency": argd["default_currency"] } "default-currency": argd["default_currency"] }
return None
def handle_default_arguments(args: dict, help_func: Callable) -> None: def handle_default_arguments(args: dict, help_func: Callable) -> None:
cargs = parse_common_arguments(' '.join(argv)) cargs = parse_common_arguments(' '.join(argv))
if not cargs is None:
if cargs["help"]: if cargs["help"]:
help_func() help_func()
print(USAGE_COMMON) print(USAGE_COMMON)
exit() exit()
elif cargs["version"]: elif cargs["version"]:
print_program_version() print_program_version()
exit() exit()
elif cargs["list-currencies"]: elif cargs["list-currencies"]:
print_supported_currencies() print_supported_currencies()
exit() exit()
elif not cargs["default-currency"] is None: elif not cargs["default-currency"] is None:
Configuration()["default-currency"] = cargs["default-currency"] conf = Configuration()
exit() conf["default-currency"] = cargs["default-currency"]
conf.save()
exit()
def print_supported_currencies() -> None: def print_supported_currencies() -> None:
@ -59,8 +63,12 @@ def print_supported_currencies() -> None:
print("Default currency is", Configuration()["default-currency"]) print("Default currency is", Configuration()["default-currency"])
def complement_array_of_coins(coins: List[int], currency: str, def complement_list_of_coins(coins: List[int], currency: str,
_reversed: bool = False) -> List[int]: _reversed: bool = False) -> List[int]:
"""Complements array of coins up to the count of currency's coins.""" """Complements list of coins up to the count of currency's coins."""
offset_array = [0] * (CURRENCIES[currency]["count"] - len(coins)) offset_array = [0] * (CURRENCIES[currency]["count"] - len(coins))
return offset_array + coins if not _reversed else coins + offset_array return offset_array + coins if not _reversed else coins + offset_array
def decimal_to_float(lst: List[int]) -> List[float]:
"""Converts decimal style of storing money amounts to float."""
return list(map(lambda x: x / 100, lst))

View File

@ -1,11 +1,10 @@
"""CLI: Put a set of coins into a piggy bank.""" """CLI: Put a set of coins into a piggy bank."""
from re import match from re import split, search
from sys import argv, exit, stderr from sys import argv, stderr
from piggybank import print_program_version
from piggybank.configuration import Configuration from piggybank.configuration import Configuration
from piggybank.cli import complement_array_of_coins, handle_default_arguments from piggybank.cli import complement_list_of_coins, handle_default_arguments
from piggybank.currencies import CURRENCIES, BaseCurrencyError from piggybank.currencies import CURRENCIES, BaseCurrencyError
from piggybank.piggybank import PiggyBank from piggybank.piggybank import PiggyBank
@ -21,34 +20,39 @@ USAGE_PUT = "Usage: piggybank-put [-r|--reversed] COINS in FILE of CURRENCY\n" \
def parse_put_arguments(args): def parse_put_arguments(args):
r = r"^(?P<reversed>-r|--reversed)? (?P<coins>[\d ,]+)" \ r = r"(?P<reversed>-r|--reversed)? ?(?P<coins>[\d ,]+) in (?P<file>\S+)(?= of (?P<currency>\S+))?"
r" in (?P<file>\S+)(?= of (?P<currency>\w+))?" argd = search(r, args)
argd = match(r, args)
if not argd is None: if not argd is None:
argd = argd.groupdict() argd = argd.groupdict()
return { return {
"coins": list(map(str, argd["coins"].split(", "))), "coins": list(map(int, split(r"\D",argd["coins"]))),
"file": argd["file"], "file": argd["file"],
"currency": Configuration()["default-currency"] \ "currency": Configuration()["default-currency"] \
if argd["currency"] is None else argd["currency"], if argd["currency"] is None else argd["currency"],
"reversed": argd["reversed"] is None } "reversed": not argd["reversed"] is None }
return None
def main() -> None: def main() -> None:
handle_default_arguments(' '.join(argv), lambda: print(USAGE_PUT)) handle_default_arguments(' '.join(argv[1:]), lambda: print(USAGE_PUT))
args = parse_put_arguments(' '.join(argv[1:]))
args = parse_put_arguments(' '.join(argv))
if args is None:
print(USAGE_PUT)
exit()
try: try:
try: try:
piggybank = PiggyBank.from_file(args["file"]) piggybank = PiggyBank.from_file(args["file"])
except FileNotFoundError: except FileNotFoundError:
piggybank = PiggyBank(args["currency"]) currency = Configuration()["default-currency"] \
coins = complement_array_of_coins(args["coins"], piggybank.currency, if args["currency"] is None else args["currency"]
args["reversed"]) piggybank = PiggyBank(currency)
coins = complement_list_of_coins(args["coins"], piggybank.currency,
args["reversed"])
piggybank.transact(coins) piggybank.transact(coins)
piggybank.save(args["file"]) piggybank.save(args["file"])
except BaseCurrencyError: except BaseCurrencyError as err:
print(f"{type(err).__name__}:", err, file=stderr) print(f"{type(err).__name__}:", err, file=stderr)
except ValueError as err: except ValueError as err:
print(f"{type(err).__name__}:", err, file=stderr) print(f"{type(err).__name__}:", err, file=stderr)

View File

@ -3,12 +3,10 @@
from re import match from re import match
from sys import argv, exit, stderr from sys import argv, exit, stderr
from piggybank import print_program_version, PIGGYBANK_FILE_EXTENSION
from piggybank.configuration import Configuration
from piggybank.cli import handle_default_arguments from piggybank.cli import handle_default_arguments
from piggybank.currencies import CURRENCIES, \ from piggybank.currencies import CURRENCIES, BaseCurrencyError
BaseCurrencyError
from piggybank.piggybank import PiggyBank from piggybank.piggybank import PiggyBank
from piggybank.transaction import sum_transactions, multiply_transactions
__all__ = ["main"] __all__ = ["main"]
@ -28,7 +26,8 @@ def parse_show_arguments(args):
argd = argd.groupdict() argd = argd.groupdict()
return { return {
"file": argd["file"], "file": argd["file"],
"transactions": argd["transactions"] is None } "transactions": not argd["transactions"] is None }
return None
def print_summary(piggybank: PiggyBank, def print_summary(piggybank: PiggyBank,
@ -43,24 +42,26 @@ def print_summary(piggybank: PiggyBank,
range(CURRENCIES[piggybank.currency]['count'])) range(CURRENCIES[piggybank.currency]['count']))
print(f"{left}{''*27}{lmiddle}{line}{right}") print(f"{left}{''*27}{lmiddle}{line}{right}")
cc, cs, ct = piggybank.count, piggybank.sum, piggybank.total cc = sum_transactions(piggybank.transactions)
cs = multiply_transactions(piggybank.transactions, piggybank.currency)
ct = sum(cc)
cline = "".join([f'{l:^{centering}}' nline = "".join([f'{l:^{centering}}'
for l in CURRENCIES[piggybank.currency]["names"]]) for l in CURRENCIES[piggybank.currency]["names"]])
cline_len = len(cline) nline_len = len(nline)
print_separator(left="", lmiddle="", rmiddle="", right="") print_separator(left="", lmiddle="", rmiddle="", right="")
print(f"{'currency':^27}" print(f"{'currency':^27}"
f"{CURRENCIES[piggybank.currency]['name']:^{cline_len}}") f"{CURRENCIES[piggybank.currency]['name']:^{nline_len}}")
print_separator(rmiddle="") print_separator(rmiddle="")
print(f"{'face values':^27}{cline}") print(f"{'face values':^27}{nline}")
print_separator() print_separator()
print(f"{'amount':^27}{''.join([f'{c:^{centering}}' for c in cc])}") print(f"{'amount':^27}{''.join([f'{c:^{centering}}' for c in cc])}")
print_separator() print_separator()
print(f"{'sum':^27}" print(f"{'sum':^27}"
f"{''.join(['{:^{}.2f}'.format(c / 100, centering) for c in cs])}") f"{''.join(['{:^{}.2f}'.format(c / 100, centering) for c in cs])}")
print_separator(rmiddle="") print_separator(rmiddle="")
print(f"{'total':^27}{'{:^{}.2f}'.format(ct / 100, cline_len)}") print(f"{'total':^27}{'{:^{}.2f}'.format(ct / 100, nline_len)}")
print_separator(left="", lmiddle="", rmiddle="", right="") print_separator(left="", lmiddle="", rmiddle="", right="")
@ -72,11 +73,11 @@ def print_transactions(piggybank, centering=DEFAULT_COIN_CENTERING):
range(CURRENCIES[piggybank.currency]['count'])) range(CURRENCIES[piggybank.currency]['count']))
print(f"{left}━━━━━━━━━━━━━━━━━━━━━{middle}━━━━━{middle}{line}{right}") print(f"{left}━━━━━━━━━━━━━━━━━━━━━{middle}━━━━━{middle}{line}{right}")
cline = "".join([f'{l:^{centering}}' nline = "".join([f'{l:^{centering}}'
for l in CURRENCIES[piggybank.currency]["names"]]) for l in CURRENCIES[piggybank.currency]["names"]])
print_separator() print_separator()
print(f"{'Timestamp':^21}┃ I/O ┃{cline}") print(f"{'Timestamp':^21}┃ I/O ┃{nline}")
print_separator("", "", "") print_separator("", "", "")
for tr in piggybank.transactions: for tr in piggybank.transactions:
coin_line = "".join([f'{c:^{centering}}' for c in tr.coins]) coin_line = "".join([f'{c:^{centering}}' for c in tr.coins])
@ -86,9 +87,12 @@ def print_transactions(piggybank, centering=DEFAULT_COIN_CENTERING):
def main() -> None: def main() -> None:
handle_default_arguments(' '.join(argv), lambda: print(USAGE_SHOW)) handle_default_arguments(' '.join(argv[1:]), lambda: print(USAGE_SHOW))
args = parse_show_arguments(' '.join(argv[1:]))
args = parse_show_arguments(' '.join(argv)) if args is None:
print(USAGE_SHOW)
exit()
try: try:
piggybank = PiggyBank.from_file(args["file"]) piggybank = PiggyBank.from_file(args["file"])

View File

@ -1,11 +1,11 @@
"""CLI: Take a set of coins from a coin box.""" """CLI: Take a set of coins from a coin box."""
from re import match, search from re import match, split
from sys import argv, exit, stderr from sys import argv, exit, stderr
from piggybank import print_program_version from piggybank import print_program_version
from piggybank.configuration import Configuration from piggybank.configuration import Configuration
from piggybank.cli import complement_array_of_coins, handle_default_arguments from piggybank.cli import complement_list_of_coins, handle_default_arguments
from piggybank.currencies import CURRENCIES, BaseCurrencyError from piggybank.currencies import CURRENCIES, BaseCurrencyError
from piggybank.piggybank import PiggyBank from piggybank.piggybank import PiggyBank
from piggybank.transaction import TYPE_OUTCOME from piggybank.transaction import TYPE_OUTCOME
@ -21,28 +21,28 @@ USAGE_TAKE: str = "Usage: piggybank-take [-r|--reversed] COINS from FILE\n\n" \
def parse_take_arguments(args): def parse_take_arguments(args):
r = r"^(?P<reversed>-r|--reversed)?(?P<coins>[\d ,]+) from (?P<file>\S+)" r = r"^(?P<reversed>-r|--reversed)? ?(?P<coins>[\d ,]+) from (?P<file>\S+)"
argd = match(r, args) argd = match(r, args)
if not argd is None: if not argd is None:
argd = argd.groupdict() argd = argd.groupdict()
return { return {
"coins": list(map(str, argd["coins"].split(", "))), "coins": list(map(int, split(r"\D", argd["coins"]))),
"file": argd["file"], "file": argd["file"],
"reversed": argd["reversed"] is None } "reversed": not argd["reversed"] is None }
return None
def main(): def main():
handle_default_arguments(' '.join(argv), lambda: print(USAGE_TAKE)) handle_default_arguments(' '.join(argv[1:]), lambda: print(USAGE_TAKE))
args = parse_take_arguments(' '.join(argv[1:]))
args = parse_take_arguments(' '.join(argv)) if args is None:
if args["coins"] is None or args["file"] is None:
print(USAGE_TAKE) print(USAGE_TAKE)
exit() exit()
try: try:
piggybank = PiggyBank.from_file(args["file"]) piggybank = PiggyBank.from_file(args["file"])
coins = complement_array_of_coins(args["coins"], piggybank.currency, coins = complement_list_of_coins(args["coins"], piggybank.currency,
args["reversed"]) args["reversed"])
piggybank.transact(coins, TYPE_OUTCOME) piggybank.transact(coins, TYPE_OUTCOME)
piggybank.save(args["file"]) piggybank.save(args["file"])

View File

@ -8,12 +8,6 @@ from typing import Union
__all__ = ["Configuration", "get_configuration_path"] __all__ = ["Configuration", "get_configuration_path"]
DEFAULT_CONFIGURATION = {
"default-currency": "SRUB"
}
DEFAULT_CONFIGURATION_FILE = join(get_configuration_path(), "piggybank.conf")
def get_configuration_path(): def get_configuration_path():
if system() == "Linux": if system() == "Linux":
return getenv("XDG_CONFIG_HOME") or f"{getenv('HOME')}/.config" return getenv("XDG_CONFIG_HOME") or f"{getenv('HOME')}/.config"
@ -21,12 +15,17 @@ def get_configuration_path():
return getenv("APPDATA") return getenv("APPDATA")
DEFAULT_CONFIGURATION = {
"default-currency": "SRUB"
}
DEFAULT_CONFIGURATION_FILE = join(get_configuration_path(), "piggybank.conf")
class Configuration: class Configuration:
def __init__(self, configuration_file: str = DEFAULT_CONFIGURATION_FILE, def __init__(self, configuration_file: str = DEFAULT_CONFIGURATION_FILE,
default_configuration: dict = DEFAULT_CONFIGURATION) -> None: default_configuration: dict = DEFAULT_CONFIGURATION) -> None:
self._configuration_file = configuration_file self._configuration_file = configuration_file
self._configuration = dict() self._configuration = dict()
if exists(self._configuration_file): if exists(self._configuration_file):
self.load() self.load()
elif not default_configuration is None: elif not default_configuration is None:
@ -35,7 +34,7 @@ class Configuration:
def load(self) -> None: def load(self) -> None:
for line in open(self._configuration_file, 'r'): for line in open(self._configuration_file, 'r'):
key, value = line.split(" = ") key, value = line[:-1].split(" = ")
self._configuration[key] = value self._configuration[key] = value
def save(self) -> None: def save(self) -> None:

View File

@ -22,7 +22,7 @@ this number by 100 to get a regular floating-point number.
from typing import Dict, List, TypedDict from typing import Dict, List, TypedDict
__all__ = ["CURRENCIES", "BaseCurrencyError", "CurrencyIsNotSupportedError", __all__ = ["CURRENCIES", "BaseCurrencyError", "CurrencyIsNotSupportedError",
"CurrenciesCoinCountMismatchError", "CurrencyMismatchError"] "CurrenciesCoinCountMismatchError", "CurrencyMismatchError"]
class BaseCurrencyError(Exception): class BaseCurrencyError(Exception):

View File

@ -5,8 +5,7 @@ from os.path import exists
from typing import List from typing import List
from piggybank import PIGGYBANK_FILE_EXTENSION from piggybank import PIGGYBANK_FILE_EXTENSION
from piggybank.currencies import CURRENCIES, \ from piggybank.currencies import CURRENCIES, CurrencyIsNotSupportedError, \
CurrencyIsNotSupportedError, CurrenciesCoinCountMismatchError, \
CurrencyMismatchError CurrencyMismatchError
from piggybank.transaction import Transaction, sum_transactions, TYPE_INCOME from piggybank.transaction import Transaction, sum_transactions, TYPE_INCOME
@ -16,6 +15,7 @@ __all__ = ["PiggyBank"]
class PiggyBank: class PiggyBank:
"""This class stores array of transactions and perform some actions on it.""" """This class stores array of transactions and perform some actions on it."""
def __init__(self, currency: str = None) -> None: def __init__(self, currency: str = None) -> None:
self._currency = None
if not currency is None: if not currency is None:
self.currency = currency self.currency = currency
self._transactions = [] self._transactions = []
@ -42,16 +42,11 @@ class PiggyBank:
return self._currency return self._currency
@currency.setter @currency.setter
def currency(self, currency: str = None) -> None: def currency(self, currency: str) -> None:
"""Sets a currency of a PiggyBank with check for support. And if count """Sets a currency of a PiggyBank with check for support."""
of coins doesn't match the old currency it won't set a new one."""
currency = currency.upper() currency = currency.upper()
if not currency in CURRENCIES: if not currency in CURRENCIES.keys():
raise CurrencyIsNotSupportedError raise CurrencyIsNotSupportedError
if not self._currency is None and \
CURRENCIES[currency]["count"] \
!= CURRENCIES[self._currency]["count"]:
raise CurrenciesCoinCountMismatchError
self._currency = currency self._currency = currency
@property @property

View File

@ -10,6 +10,7 @@ from piggybank.currencies import CURRENCIES
__all__ = ["Transaction", "sum_transactions", "multiply_transactions", __all__ = ["Transaction", "sum_transactions", "multiply_transactions",
"TYPE_INCOME", "TYPE_OUTCOME", "TIME_FORMAT"] "TYPE_INCOME", "TYPE_OUTCOME", "TIME_FORMAT"]
TYPE_INCOME = "i" TYPE_INCOME = "i"
TYPE_OUTCOME = "o" TYPE_OUTCOME = "o"
TIME_FORMAT = "%Y-%m-%dT%H:%M:%S" TIME_FORMAT = "%Y-%m-%dT%H:%M:%S"
@ -21,9 +22,9 @@ def sum_transactions(transactions: List[Transaction]) -> List[int]:
coins = [0] * len(transactions[0].coins) coins = [0] * len(transactions[0].coins)
for transaction in transactions: for transaction in transactions:
if transaction.direction == TYPE_INCOME: if transaction.direction == TYPE_INCOME:
coins = list(map(add, transaction.coins, coins)) coins = list(map(add, coins, transaction.coins))
else: else:
coins = list(map(sub, transaction.coins, coins)) coins = list(map(sub, coins, transaction.coins))
return coins return coins
def multiply_transactions(transactions: Union[List[Transaction], Transaction], def multiply_transactions(transactions: Union[List[Transaction], Transaction],
@ -57,7 +58,7 @@ class Transaction:
@coins.setter @coins.setter
def coins(self, coins: List[int]) -> None: def coins(self, coins: List[int]) -> None:
if coins is list: if type(coins) is list:
self._coins = coins self._coins = coins
else: else:
raise TypeError("Coins must be of type 'list'.") raise TypeError("Coins must be of type 'list'.")