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__",
"__copyright__", "__license__", "PIGGYBANK_FILE_EXTENSION",
"VERSION"]
__date__ = "30 June 2020"
__date__ = "6 Jule 2020"
__version__ = "1.0.0"
__author__ = "Alexander \"Arav\" Andreev"
__email__ = "me@arav.top"
@ -17,3 +22,5 @@ see <https://opensource.org/licenses/MIT>"""
PIGGYBANK_FILE_EXTENSION = ".pb"
VERSION = f"PiggyBank ver. {__version__} ({__date__})\n\n{__copyright__}\n" \
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 typing import List
from piggybank import VERSION
from piggybank import VERSION, CURRENCIES
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.transaction import sum_transactions, TYPE_INCOME, TYPE_OUTCOME
@ -21,7 +21,6 @@ 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)
@ -42,16 +41,14 @@ Arguments:
def parse_common_arguments(args: str) -> dict:
r = r"(?P<help>-h|--help)|(?P<version>-v|--version)" \
r"|(?P<list_currencies>-L|--list-currencies)" \
r"|(?=--set-default-currency (?P<default_currency>\w+))"
r"|(?P<list_currencies>-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,
"default-currency": argd["default_currency"] }
"list-currencies": not argd["list_currencies"] is None }
return None
def parse_arguments(args: str) -> dict:
@ -142,11 +139,6 @@ def main():
load_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:

View File

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