1
0

Hurray! A huge refactoring!

This commit is contained in:
Alexander Andreev 2020-07-07 02:28:33 +04:00
parent 5bd3a99dfe
commit 2fd57bf193
5 changed files with 44 additions and 176 deletions

View File

@ -1,9 +1,14 @@
from typing import Dict
from piggybank.currency import Currency
__all__ = ["__date__", "__version__", "__author__", "__email__", __all__ = ["__date__", "__version__", "__author__", "__email__",
"__copyright__", "__license__", "PIGGYBANK_FILE_EXTENSION", "__copyright__", "__license__", "PIGGYBANK_FILE_EXTENSION",
"VERSION"] "VERSION"]
__date__ = "30 June 2020" __date__ = "6 Jule 2020"
__version__ = "1.0.0" __version__ = "1.0.0"
__author__ = "Alexander \"Arav\" Andreev" __author__ = "Alexander \"Arav\" Andreev"
__email__ = "me@arav.top" __email__ = "me@arav.top"
@ -17,3 +22,5 @@ see <https://opensource.org/licenses/MIT>"""
PIGGYBANK_FILE_EXTENSION = ".pb" PIGGYBANK_FILE_EXTENSION = ".pb"
VERSION = f"PiggyBank ver. {__version__} ({__date__})\n\n{__copyright__}\n" \ VERSION = f"PiggyBank ver. {__version__} ({__date__})\n\n{__copyright__}\n" \
f"\n{__license__}" f"\n{__license__}"
CURRENCIES: Dict[str, Currency] = dict()

View File

@ -4,9 +4,9 @@ from re import search, split
from sys import argv, stderr from sys import argv, stderr
from typing import List from typing import List
from piggybank import VERSION from piggybank import VERSION, CURRENCIES
from piggybank.configuration import Configuration from piggybank.configuration import Configuration
from piggybank.currencies import Currency, BaseCurrencyError, CURRENCIES from piggybank.currency import Currency, BaseCurrencyError
from piggybank.piggybank import PiggyBank from piggybank.piggybank import PiggyBank
from piggybank.transaction import sum_transactions, TYPE_INCOME, TYPE_OUTCOME from piggybank.transaction import sum_transactions, TYPE_INCOME, TYPE_OUTCOME
@ -18,13 +18,12 @@ USAGE = \
| show FILE [with t(ransactions)]) | show FILE [with t(ransactions)])
Options: Options:
\t-h,--help -- print this help; \t-h,--help -- print this help;
\t-v,--version -- print program version; \t-v,--version -- print program version;
\t-L,--list-currencies -- list supported currencies and a default one; \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-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 (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)
\t with this flag. If currency has 6 coins)
There are three actions available: put, take and show. There are three actions available: put, take and show.
@ -42,16 +41,14 @@ Arguments:
def parse_common_arguments(args: str) -> dict: def parse_common_arguments(args: str) -> dict:
r = r"(?P<help>-h|--help)|(?P<version>-v|--version)" \ r = r"(?P<help>-h|--help)|(?P<version>-v|--version)" \
r"|(?P<list_currencies>-L|--list-currencies)" \ r"|(?P<list_currencies>-L|--list-currencies)"
r"|(?=--set-default-currency (?P<default_currency>\w+))"
argd = search(r, args) argd = search(r, args)
if not argd is None: if not argd is None:
argd = argd.groupdict() argd = argd.groupdict()
return { return {
"help": not argd["help"] is None, "help": not argd["help"] is None,
"version": not argd["version"] is None, "version": not argd["version"] is None,
"list-currencies": not argd["list_currencies"] is None, "list-currencies": not argd["list_currencies"] is None }
"default-currency": argd["default_currency"] }
return None return None
def parse_arguments(args: str) -> dict: def parse_arguments(args: str) -> dict:
@ -142,11 +139,6 @@ def main():
load_currencies() load_currencies()
print_supported_currencies() print_supported_currencies()
exit() exit()
elif not cargs["default-currency"] is None:
conf = Configuration()
conf["default-currency"] = cargs["default-currency"]
conf.save()
exit()
args = parse_arguments(' '.join(argv)) args = parse_arguments(' '.join(argv))
if not args: if not args:

View File

@ -26,41 +26,38 @@ def get_configuration_path() -> str:
return getenv("APPDATA") return getenv("APPDATA")
DEFAULT_CONFIGURATION = { DEFAULT_CONFIGURATION_FILE_CONTENT = \
"default-currency": "SRUB", """# A default currency's ISO code.
"currency": { default-currency = SRUB
"RUB": "RUB;Russian ruble;Russian Federation;8;" \
"1к,5к,10к,50к,1₽,2₽,5₽,10₽;1,5,10,50,100,200,500,1000", # A set of predefined currencies.
"SRUB": "SRUB;Russian ruble (short);No 1 and 5 kopek;6;" \ # Format is following:
"10к,50к,1₽,2₽,5₽,10₽;10,50,100,200,500,1000", # <ISO>;<CURRENCY NAME>;<COINS COUNT>;{}<FRACTION SYMBOL>,{}<CURRENCY SYMBOL>;<FACE VALUES>
"BYN": "BYN;Belarusian ruble;Belarus;8;" \ # "{}" is used as a placeholder for where to put digits.
"1к,2к,5к,10к,20к,50к,1р,2р;1,2,5,10,20,50,100,200", currency.RUB = RUB;Russian ruble;8;{}коп.,{};1,5,10,50,100,200,500,1000
"UAH": "UAH;Ukrainian hryvnia;Ukraine;10;" \ currency.SRUB = SRUB;Russian ruble (no 1 and 5 kopek);6;{}коп.,{};10,50,100,200,500,1000
"1к,2к,5к,10к,25к,50к,₴1,₴2,₴5,₴10;1,2,5,10,25,50,100,200,500,1000", currency.BYN = BYN;Belarusian ruble;8;{}коп.,{}р.;1,2,5,10,20,50,100,200
"USD": "USD;US Dollar;United States of America;6;" \ currency.UAH = UAH;Ukrainian hryvnia;10;{}коп.,{};1,2,5,10,25,50,100,200,500,1000
"1¢,5¢,10¢,25¢,50¢,$1;1,5,10,25,50,100", currency.USD = USD;US Dollar;6;{}¢,${};1,5,10,25,50,100
"EUR": "EUR;Euro;European Union;8;" \ currency.EUR = EUR;Euro;8;{}c,{};1,2,5,10,20,50,100,200
"1c,2c,5c,10c,20c,50c,€1,€2;1,2,5,10,20,50,100,200", currency.GBP = GBP;Pound sterling;9;{}p,£{};1,2,5,10,20,25,50,100,200
"GBP": "GBP;Pound sterling;United Kingdom;9;" \ """
"1p,2p,5p,10p,20p,25p,50p,£1,£2;1,2,5,10,20,25,50,100,200" } }
DEFAULT_CONFIGURATION_FILE = join(get_configuration_path(), "piggybank.conf") 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) -> 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: else:
self._configuration = default_configuration raise FileNotFoundError()
self.save()
def load(self) -> None: def load(self) -> None:
for line in open(self._configuration_file, 'r'): for line in open(self._configuration_file, 'r'):
if line[0] == "#" or line[0] == "\n": if line[0] in ['#', '\n']:
continue continue
key, value = line[:-1].split(" = ") key, value = line[:-1].split(" = ")
if key.find(".") != -1: if key.find(".") != -1:
@ -71,14 +68,11 @@ class Configuration:
else: else:
self._configuration[key] = value self._configuration[key] = value
def save(self) -> None: @staticmethod
with open(self._configuration_file, 'w') as cf: def write_default_to_file(self, path: str = DEFAULT_CONFIGURATION_FILE) -> None:
for key, value in self._configuration.items(): """Writes default configuration to a file. Overrides existing."""
if type(value) is dict: with open(path, 'w') as cf:
for subkey, subvalue in value.items(): cf.write(DEFAULT_CONFIGURATION_FILE_CONTENT)
cf.write(f"{key}.{subkey} = {subvalue}\n")
else:
cf.write(f"{key} = {value}\n")
@property @property
def items(self) -> dict: def items(self) -> dict:

View File

@ -1,125 +0,0 @@
"""Dictionary of supported currencies as instances of a Currency class.
Face values are stored as decimals*.
* Decimal is used to avoid problems of rounding float numbers. So first two
digits to the right are used to store a fraction part. You can simply divide
this number by 100 to get a floating-point number.
"""
from __future__ import annotations
from operator import mul
from typing import Dict, List, TypedDict
__all__ = [ "Currency", "CURRENCIES", "BaseCurrencyError",
"CurrencyIsNotSupportedError", "CurrenciesCoinCountMismatchError",
"CurrencyMismatchError"]
CURRENCIES: Dict[str, Currency] = dict()
class BaseCurrencyError(Exception):
"""Base class for all currency exeptions."""
def __init__(self, message=None, *args, **kwargs) -> None:
if message is None:
message = self.__doc__
super().__init__(message, *args, **kwargs)
class CurrencyIsNotSupportedError(BaseCurrencyError):
"""Currency is not supported."""
pass
class CurrencyMismatchError(BaseCurrencyError):
"""Currencies doesn't match, but they must do so."""
def __init__(self, extra=None):
if not extra is None:
super().__init__(f"{self.__doc__} {extra}")
else:
super().__init__(self.__doc__)
class CurrenciesCoinCountMismatchError(BaseCurrencyError):
"""Count of coins of a new currency and an old one should be equal."""
pass
class Currency:
"""Currency's representation and methods to work with it.
Arguments:
iso_code -- ISO code of a currency;
name -- full name of a currency;
description -- description of a currency;
coins_count -- number of face values of coins;
coin_names -- list of names of face values (e.g. 1c, 5c);
face_values -- list of values for each face value in decimal
(e.g. $476.40 is 47640);
"""
def __init__(self, iso_code: str, name: str, description: str,
coins_count: int, coin_names: List[str],
face_values: List[int]):
self._iso = iso_code.upper()
self._name = name
self._description = description
self._coins_count = coins_count
self._coin_names = coin_names
self._face_values = face_values
@property
def iso(self) -> str:
"""ISO code of a currency."""
return self._iso
@property
def name(self) -> str:
"""Full name of a currency."""
return self._name
@property
def description(self) -> str:
"""Description of a currency (e.g. country)."""
return self._description
@property
def count(self):
"""Count of coins of a currency."""
return self._coins_count
@property
def coin_names(self) -> List[str]:
"""Names of coins (usually face values, e.g. 25¢).
From least to most."""
return self._coin_names
@property
def face_values(self) -> List[int]:
"""Face values of coins. From least to most."""
return self._face_values
def multiply(self, lst: List[int]) -> List[float]:
"""Multiplies a list by face values. Length of list must match
currency's coins count."""
if len(lst) != len(self):
raise CurrenciesCoinCountMismatchError
return list(map(mul, lst, self.face_values))
@staticmethod
def from_string(currency: str) -> Currency:
"""Creates a Currency object from its string representation."""
iso, name, desc, ccnt, fvn, fv = currency.split(";")
return Currency(iso, name, desc, int(ccnt),
fvn.split(","), list(map(int, fv.split(","))))
def __mul__(self, lst: List[int]) -> List[float]:
return self.multiply(lst)
def __len__(self) -> int:
return self.count
def __str__(self):
return f"{self.iso};{self.name};{self.description};" \
f"{self.count};{','.join(self.coin_names)};" \
f"{','.join(list(map(str, self.face_values)))}"

View File

@ -5,7 +5,7 @@ from operator import add, sub, mul
from time import strftime, strptime, gmtime from time import strftime, strptime, gmtime
from typing import Optional, List, Union from typing import Optional, List, Union
from piggybank.currencies import CURRENCIES from piggybank import CURRENCIES
__all__ = ["Transaction", "sum_transactions", __all__ = ["Transaction", "sum_transactions",
"TYPE_INCOME", "TYPE_OUTCOME", "TIME_FORMAT"] "TYPE_INCOME", "TYPE_OUTCOME", "TIME_FORMAT"]