Hurray! A huge refactoring!
This commit is contained in:
parent
5bd3a99dfe
commit
2fd57bf193
@ -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()
|
||||||
|
@ -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:
|
||||||
|
@ -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:
|
||||||
|
@ -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)))}"
|
|
@ -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"]
|
||||||
|
Loading…
Reference in New Issue
Block a user