1
0

I moved built-in currencies to a configuration file. Made some clean ups, fixed what I've done last night. I guess, it's READY!

This commit is contained in:
Alexander Andreev 2020-06-07 20:41:17 +04:00
parent 0efb129ad3
commit f2d2e75128
8 changed files with 76 additions and 89 deletions

View File

@ -7,26 +7,12 @@ you decide to take them all out.
Yes, only coins, no banknots. Well, you actually can easily add support for Yes, only coins, no banknots. Well, you actually can easily add support for
banknots, but table will become indeed wide. banknots, but table will become indeed wide.
# Backstory
Many years ago I wrote a little script to store info on how much coins I have
in my piggy bank. It used SQLite 3 database for it and was hardcoded to rubles.
Why had I use whole SQL database? That was the first time I tried SQLite in
Python.
Once I came up with an idea that it would be good to learn on how to make
packages for Python. And with no better ideas I took my piggy bank script to be
rewritten to support any currency. Database was thrown away and its place was
took by a simple text format.
# Usage # Usage
This is a CLI program that is broken down to three separate programs:
piggybank [OPTIONS] (COINS in FILE [of CURRENCY] | COINS from FILE piggybank [OPTIONS] (COINS in FILE [of CURRENCY] | COINS from FILE
| show FILE [with t(ransactions)]) | show FILE [with t(ransactions)])
Options: Common options:
- `-h,--help` — print this help; - `-h,--help` — print this help;
@ -120,6 +106,6 @@ Currency is defined following way:
That is best described with an example. Here it is: That is best described with an example. Here it is:
currency.SRUB = SRUB;Russian ruble (shortened);Russian Federation. Excluding coins of 1 and 5 kopek;6;10к.,50к.,1₽,2₽,5₽,10₽;10,50,100,200,500,1000 currency.EXM = EXM;Example;Example currency;2;1E,2E;100,200
Yes, long and clunky way... May be I'll come up with something better. Yes, long and clunky way... May be I'll come up with something better.

View File

@ -1,4 +1,4 @@
__all__ = ["__date__", "__version__", "__author__", "__author_email__", __all__ = ["__date__", "__version__", "__author__", "__email__",
"__copyright__", "__license__", "PIGGYBANK_FILE_EXTENSION", "__copyright__", "__license__", "PIGGYBANK_FILE_EXTENSION",
"VERSION"] "VERSION"]
@ -6,8 +6,8 @@ __all__ = ["__date__", "__version__", "__author__", "__author_email__",
__date__ = "7 June 2020" __date__ = "7 June 2020"
__version__ = "1.0.0" __version__ = "1.0.0"
__author__ = "Alexander \"Arav\" Andreev" __author__ = "Alexander \"Arav\" Andreev"
__author_email__ = "me@arav.top" __email__ = "me@arav.top"
__copyright__ = f"Copyright (c) 2020 {__author__} <{__author_email__}>" __copyright__ = f"Copyright (c) 2020 {__author__} <{__email__}>"
__license__ = \ __license__ = \
"""This program is free software. It comes without any warranty, to """This program is free software. It comes without any warranty, to
the extent permitted by applicable law. You can redistribute it the extent permitted by applicable law. You can redistribute it

View File

@ -1,12 +0,0 @@
from typing import List
from piggybank.currencies import CURRENCIES
__all__ = ["complement_list_of_coins"]
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."""
offset_array = [0] * (len(CURRENCIES[currency]) - len(coins))
return offset_array + coins if not _reversed else coins + offset_array

View File

@ -2,9 +2,9 @@
from re import search, split from re import search, split
from sys import argv, stderr from sys import argv, stderr
from typing import List
from piggybank import VERSION from piggybank import VERSION
from piggybank.cli import complement_list_of_coins
from piggybank.configuration import Configuration from piggybank.configuration import Configuration
from piggybank.currencies import Currency, BaseCurrencyError, CURRENCIES from piggybank.currencies import Currency, BaseCurrencyError, CURRENCIES
from piggybank.piggybank import PiggyBank from piggybank.piggybank import PiggyBank
@ -34,8 +34,8 @@ Show: piggybank show FILE [with t(ransactions)]
Arguments: Arguments:
\tCOINS -- a set of comma or whitespace separated coin counts; \tCOINS -- a set of comma or whitespace separated coin counts;
\tFILE -- a filename of a piggybank; \tFILE -- a filename of a piggy bank;
\tof CURRENCY -- set a partcular currency for a piggybank; \tof CURRENCY -- set a partcular currency for a piggy bank;
\twith t(ransactions) -- print a table of transactions as well. \twith t(ransactions) -- print a table of transactions as well.
""" """
@ -99,11 +99,11 @@ def print_summary(pb: PiggyBank) -> None:
s = sum_transactions(pb.transactions) s = sum_transactions(pb.transactions)
print(f"{'Counts':^27}{''.join(f'{c:^12}' for c in s)}") print(f"{'Counts':^27}{''.join(f'{c:^12}' for c in s)}")
print_separator("", "", "", "") print_separator("", "", "", "")
s = cur.multiply(s) s = cur * s
print(f"{'Sums':^27}{''.join(f'{c/100:^12.2f}' for c in s)}") print(f"{'Sums in currency':^27}{''.join(f'{c/100:^12.2f}' for c in s)}")
print_separator("", "", "", "") print_separator("", "", "", "")
s = sum(s) s = sum(s)
print(f"{'Total':^27}{s:^{12*cur.count+cur.count-1}}") print(f"{'Total in currency':^27}{s/100:^{12*cur.count+cur.count-1}.2f}")
print_separator("", "", "", "") print_separator("", "", "", "")
@ -115,12 +115,18 @@ def print_supported_currencies() -> None:
f"{CURRENCIES[cur].description}") f"{CURRENCIES[cur].description}")
print("Default currency is", Configuration()["default-currency"]) print("Default currency is", Configuration()["default-currency"])
def load_currencies() -> None:
def load_user_defined_currencies() -> None: """Load currencies defined in a configuration file."""
if not "currency" in Configuration().items: if not "currency" in Configuration().items:
return return
for iso, cur in Configuration()["currency"].items(): for iso, cur in Configuration()["currency"].items():
CURRENCIES[iso] = Currency.from_string(cur) 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."""
offset_array = [0] * (len(CURRENCIES[currency]) - len(coins))
return offset_array + coins if not _reversed else coins + offset_array
def main(): def main():
@ -133,6 +139,7 @@ def main():
print(VERSION) print(VERSION)
exit() exit()
elif cargs["list-currencies"]: elif cargs["list-currencies"]:
load_currencies()
print_supported_currencies() print_supported_currencies()
exit() exit()
elif not cargs["default-currency"] is None: elif not cargs["default-currency"] is None:
@ -147,7 +154,7 @@ def main():
exit() exit()
try: try:
load_user_defined_currencies() load_currencies()
try: try:
pb = PiggyBank.from_file(args["file"]) pb = PiggyBank.from_file(args["file"])
except FileNotFoundError: except FileNotFoundError:

View File

@ -27,7 +27,22 @@ def get_configuration_path() -> str:
DEFAULT_CONFIGURATION = { DEFAULT_CONFIGURATION = {
"default-currency": "SRUB" } "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 = join(get_configuration_path(), "piggybank.conf") DEFAULT_CONFIGURATION_FILE = join(get_configuration_path(), "piggybank.conf")
@ -45,19 +60,25 @@ 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'):
if line[0] == "#": if line[0] == "#" or line[0] == "\n":
continue continue
key, value = line[:-1].split(" = ") key, value = line[:-1].split(" = ")
if key.find(".") != -1: if key.find(".") != -1:
k0, k1 = key.split(".") k0, k1 = key.split(".")
self._configuration[k0] = {k1 : value} if not k0 in self._configuration:
self._configuration[k0] = dict()
self._configuration[k0][k1] = value
else: else:
self._configuration[key] = value self._configuration[key] = value
def save(self) -> None: def save(self) -> None:
with open(self._configuration_file, 'w') as cf: with open(self._configuration_file, 'w') as cf:
for key, value in self._configuration.items(): for key, value in self._configuration.items():
cf.write(f"{key} = {value}\n") 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")
@property @property
def items(self) -> dict: def items(self) -> dict:

View File

@ -16,6 +16,9 @@ __all__ = [ "Currency", "CURRENCIES", "BaseCurrencyError",
"CurrencyMismatchError"] "CurrencyMismatchError"]
CURRENCIES: Dict[str, Currency] = dict()
class BaseCurrencyError(Exception): class BaseCurrencyError(Exception):
"""Base class for all currency exeptions.""" """Base class for all currency exeptions."""
def __init__(self, message=None, *args, **kwargs) -> None: def __init__(self, message=None, *args, **kwargs) -> None:
@ -122,27 +125,27 @@ class Currency:
f"{','.join(list(map(str, self.face_values)))}" f"{','.join(list(map(str, self.face_values)))}"
CURRENCIES: Dict[str, Currency] = { # CURRENCIES: Dict[str, Currency] = {
"RUB": Currency("RUB", "Russian ruble", "Russian Federation", 8, # "RUB": Currency("RUB", "Russian ruble", "Russian Federation", 8,
["1к.", "5к.", "10к.", "50к.", "1₽", "2₽", "5₽", "10₽"], # ["1к.", "5к.", "10к.", "50к.", "1₽", "2₽", "5₽", "10₽"],
[1, 5, 10, 50, 1_00, 2_00, 5_00, 10_00]), # [1, 5, 10, 50, 1_00, 2_00, 5_00, 10_00]),
"SRUB": Currency("SRUB", "Russian ruble (shortened)", # "SRUB": Currency("SRUB", "Russian ruble (shortened)",
"Russian Federation. Excluding coins of 1 and 5 kopek.", # "Russian Federation. Excluding coins of 1 and 5 kopek.",
6, ["10к.", "50к.", "1₽", "2₽", "5₽", "10₽"], # 6, ["10к.", "50к.", "1₽", "2₽", "5₽", "10₽"],
[10, 50, 1_00, 2_00, 5_00, 10_00]), # [10, 50, 1_00, 2_00, 5_00, 10_00]),
"BYN": Currency("BYN", "Belarusian ruble", "Belarus", 8, # "BYN": Currency("BYN", "Belarusian ruble", "Belarus", 8,
["1к.", "2к.", "5к.", "10к.", "20к.", "50к.", "1р.", "2р."], # ["1к.", "2к.", "5к.", "10к.", "20к.", "50к.", "1р.", "2р."],
[1, 2, 5, 10, 20, 50, 1_00, 2_00]), # [1, 2, 5, 10, 20, 50, 1_00, 2_00]),
"UAH": Currency("UAH", "Ukrainian hryvnia", "Ukraine", 10, # "UAH": Currency("UAH", "Ukrainian hryvnia", "Ukraine", 10,
["1к.", "2к.", "5к.", "10к.", "25к.", "50к.", "₴1", "₴2", "₴5", "₴10"], # ["1к.", "2к.", "5к.", "10к.", "25к.", "50к.", "₴1", "₴2", "₴5", "₴10"],
[1, 2, 5, 10, 25, 50, 1_00, 2_00, 5_00, 10_00]), # [1, 2, 5, 10, 25, 50, 1_00, 2_00, 5_00, 10_00]),
"USD": Currency("USD", "US Dollar", "United States of America", 6, # "USD": Currency("USD", "US Dollar", "United States of America", 6,
["", "", "10¢", "25¢", "50¢", "$1"], # ["1¢", "5¢", "10¢", "25¢", "50¢", "$1"],
[1, 5, 10, 25, 50, 1_00]), # [1, 5, 10, 25, 50, 1_00]),
"EUR": Currency("EUR", "Euro", "European Union", 8, # "EUR": Currency("EUR", "Euro", "European Union", 8,
["1c", "2c", "5c", "10c", "20c", "50c", "€1", "€2"], # ["1c", "2c", "5c", "10c", "20c", "50c", "€1", "€2"],
[1, 2, 5, 10, 20, 50, 1_00, 2_00]), # [1, 2, 5, 10, 20, 50, 1_00, 2_00]),
"GBP": Currency("GBP", "Pound sterling", "United Kingdom", 9, # "GBP": Currency("GBP", "Pound sterling", "United Kingdom", 9,
["1p", "2p", "5p", "10p", "20p", "25p", "50p", "£1", "£2"], # ["1p", "2p", "5p", "10p", "20p", "25p", "50p", "£1", "£2"],
[1, 2, 5, 10, 20, 25, 50, 1_00, 2_00]) # [1, 2, 5, 10, 20, 25, 50, 1_00, 2_00])
} # }

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, CurrencyIsNotSupportedError, \ from piggybank.currencies import CURRENCIES, CurrencyIsNotSupportedError
CurrencyMismatchError
from piggybank.transaction import Transaction, sum_transactions, TYPE_INCOME from piggybank.transaction import Transaction, sum_transactions, TYPE_INCOME
__all__ = ["PiggyBank"] __all__ = ["PiggyBank"]
@ -93,10 +92,3 @@ class PiggyBank:
def __eq__(self, piggybank: PiggyBank) -> str: def __eq__(self, piggybank: PiggyBank) -> str:
"""Compares only currency.""" """Compares only currency."""
return self._currency == piggybank.currency return self._currency == piggybank.currency
def __add__(self, piggybank: PiggyBank) -> PiggyBank:
if self != piggybank:
raise CurrencyMismatchError
new = PiggyBank(self._currency)
new._transactions = self._transactions + piggybank._transactions
return new

View File

@ -7,7 +7,7 @@ from typing import Optional, List, Union
from piggybank.currencies import CURRENCIES from piggybank.currencies import CURRENCIES
__all__ = ["Transaction", "sum_transactions", "multiply_transactions", __all__ = ["Transaction", "sum_transactions",
"TYPE_INCOME", "TYPE_OUTCOME", "TIME_FORMAT"] "TYPE_INCOME", "TYPE_OUTCOME", "TIME_FORMAT"]
@ -27,16 +27,6 @@ def sum_transactions(transactions: List[Transaction]) -> List[int]:
coins = list(map(sub, coins, transaction.coins)) coins = list(map(sub, coins, transaction.coins))
return coins return coins
def multiply_transactions(transactions: Union[List[Transaction], Transaction],
currency: str) -> List[int]:
"""Multiplies coins' counts by currency."""
if type(transactions) is Transaction:
return list(map(mul, transactions.coins, \
CURRENCIES[currency]["multipliers"]))
else:
coins = sum_transactions(transactions)
return list(map(mul, coins, CURRENCIES[currency]["multipliers"]))
class Transaction: class Transaction:
"""An object that holds a single transaction. """An object that holds a single transaction.