Added possibility to add missing currencies through config.
This commit is contained in:
parent
7f3acc8ce4
commit
ab6647ceb3
35
README.md
35
README.md
@ -24,16 +24,16 @@ took by a simple text format.
|
|||||||
This is a CLI program that is broken down to three separate programs:
|
This is a CLI program that is broken down to three separate programs:
|
||||||
|
|
||||||
- `piggybank-put` — puts coins in a piggy bank and create a new one if
|
- `piggybank-put` — puts coins in a piggy bank and create a new one if
|
||||||
there is no such file;
|
there is no such file;
|
||||||
- `piggybank-take` — takes coins from an existsing piggy bank file;
|
- `piggybank-take` — takes coins from an existsing piggy bank file;
|
||||||
- `piggybank-show` — shows summarised information on a piggy bank and,
|
- `piggybank-show` — shows summarised information on a piggy bank and,
|
||||||
optionally, print a list of transactions.
|
optionally, print a list of transactions.
|
||||||
|
|
||||||
## Common flags
|
## Common flags
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
piggybank-* [(-h | --help) | (-v | --version) | (-L | --list-currencies)
|
piggybank-* [(-h | --help) | (-v | --version) | (-L | --list-currencies)
|
||||||
| --set-default-currency]
|
| --set-default-currency]
|
||||||
```
|
```
|
||||||
|
|
||||||
These flags are used with every sub-program.
|
These flags are used with every sub-program.
|
||||||
@ -92,7 +92,7 @@ All the parameters are explained above.
|
|||||||
piggybank-take -r 0 0 4 from example.pb
|
piggybank-take -r 0 0 4 from example.pb
|
||||||
```
|
```
|
||||||
|
|
||||||
## How to see what do you have
|
## How to see what you have in there
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
piggybank-show FILE [ with (t | transactions)]
|
piggybank-show FILE [ with (t | transactions)]
|
||||||
@ -107,4 +107,29 @@ file.
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
piggybank-show example.pb with t
|
piggybank-show example.pb with t
|
||||||
```
|
```
|
||||||
|
|
||||||
|
# Configuration
|
||||||
|
|
||||||
|
Configuration file `piggybank.conf` is stored in directory specified by
|
||||||
|
`$XDG_CONFIG_HOME` environment variable or, if it isn't set, in `$HOME/.config`
|
||||||
|
on Linux and in `%APPDATA%` on Windows.
|
||||||
|
|
||||||
|
By default it has following content:
|
||||||
|
|
||||||
|
default-currency = SRUB
|
||||||
|
|
||||||
|
Aside from `default-currency` there is also a `currency.*` parameter. `*` is a
|
||||||
|
currency's ISO code, e.g. `currency.RUB`, `currency.USD`, `currency.EUR` etc.
|
||||||
|
This parameter allows you to add a currency needed for you, but not presented in
|
||||||
|
program.
|
||||||
|
|
||||||
|
Currency is defined following way:
|
||||||
|
|
||||||
|
currency.ISO = ISO;NAME;DESCRIPTION;COINS_COUNT;COINS_NAME;FACE_VALUES
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
Yes, long and clunky way... May be I'll come up with something better.
|
||||||
|
@ -3,7 +3,7 @@ __all__ = ["__date__", "__version__", "__author__", "__author_email__",
|
|||||||
"print_program_version"]
|
"print_program_version"]
|
||||||
|
|
||||||
|
|
||||||
__date__ = "5 June 2020"
|
__date__ = "6 June 2020"
|
||||||
__version__ = "1.0.0"
|
__version__ = "1.0.0"
|
||||||
__author__ = "Alexander \"Arav\" Andreev"
|
__author__ = "Alexander \"Arav\" Andreev"
|
||||||
__author_email__ = "me@arav.top"
|
__author_email__ = "me@arav.top"
|
||||||
|
@ -1,4 +1,14 @@
|
|||||||
"""An implementation of a simple key=value configuration."""
|
"""An implementation of a slightly advanced key=value configuration file.
|
||||||
|
Commentary lines are started with # symbol.
|
||||||
|
Keys and values are separated by ' = ' (spaces are necessary).
|
||||||
|
Keys with . symbol are splited and right part is a nested dictionary.
|
||||||
|
Example:
|
||||||
|
# A commentary line
|
||||||
|
default_something = 10
|
||||||
|
currency.RUB = ...
|
||||||
|
Above example is translated as a following dictionary:
|
||||||
|
{ "default_something": 10, "currency": { "RUB": "..." } }
|
||||||
|
"""
|
||||||
|
|
||||||
from os import getenv
|
from os import getenv
|
||||||
from os.path import exists, join
|
from os.path import exists, join
|
||||||
@ -8,7 +18,8 @@ from typing import Union
|
|||||||
__all__ = ["Configuration", "get_configuration_path"]
|
__all__ = ["Configuration", "get_configuration_path"]
|
||||||
|
|
||||||
|
|
||||||
def get_configuration_path():
|
def get_configuration_path() -> str:
|
||||||
|
"""Returns a path to where configuration is stored."""
|
||||||
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"
|
||||||
elif system() == "Windows":
|
elif system() == "Windows":
|
||||||
@ -16,8 +27,8 @@ def get_configuration_path():
|
|||||||
|
|
||||||
|
|
||||||
DEFAULT_CONFIGURATION = {
|
DEFAULT_CONFIGURATION = {
|
||||||
"default-currency": "SRUB"
|
"default-currency": "SRUB" }
|
||||||
}
|
|
||||||
DEFAULT_CONFIGURATION_FILE = join(get_configuration_path(), "piggybank.conf")
|
DEFAULT_CONFIGURATION_FILE = join(get_configuration_path(), "piggybank.conf")
|
||||||
|
|
||||||
|
|
||||||
@ -34,8 +45,14 @@ 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] == "#":
|
||||||
|
continue
|
||||||
key, value = line[:-1].split(" = ")
|
key, value = line[:-1].split(" = ")
|
||||||
self._configuration[key] = value
|
if key.find(".") != -1:
|
||||||
|
k0, k1 = key.split(".")
|
||||||
|
self._configuration[k0] = {k1 : value}
|
||||||
|
else:
|
||||||
|
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:
|
||||||
|
@ -1,28 +1,19 @@
|
|||||||
"""Here is a dictionary of supported currencies defined. Which could be easily
|
"""Dictionary of supported currencies as instances of a Currency class.
|
||||||
extended with new ones.
|
|
||||||
|
|
||||||
Each entry is a dictionary that has an ISO code of a currency as its key. Or it
|
Face values are stored as decimals*.
|
||||||
can be slightly differ from an ISO code to represent a modified version of a
|
|
||||||
currency.
|
|
||||||
|
|
||||||
Each entry consists of following fields:
|
* Decimal is used to avoid problems of rounding float numbers. So first two
|
||||||
name -- a full name of a currency;
|
|
||||||
description -- usually a country where this currency is used is being
|
|
||||||
mentioned. Plus additional information;
|
|
||||||
count -- a number of coins in a currency;
|
|
||||||
names -- an array of names for each coins' face values;
|
|
||||||
multipliers -- an array of multipliers for each face value in a decimal*
|
|
||||||
format.
|
|
||||||
|
|
||||||
* - 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
|
digits to the right are used to store a fraction part. You can simply divide
|
||||||
this number by 100 to get a regular floating-point number.
|
this number by 100 to get a floating-point number.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
from operator import mul
|
||||||
from typing import Dict, List, TypedDict
|
from typing import Dict, List, TypedDict
|
||||||
|
|
||||||
__all__ = ["CURRENCIES", "BaseCurrencyError", "CurrencyIsNotSupportedError",
|
__all__ = [ "Currency", "CURRENCIES", "BaseCurrencyError",
|
||||||
"CurrenciesCoinCountMismatchError", "CurrencyMismatchError"]
|
"CurrencyIsNotSupportedError", "CurrenciesCoinCountMismatchError",
|
||||||
|
"CurrencyMismatchError"]
|
||||||
|
|
||||||
|
|
||||||
class BaseCurrencyError(Exception):
|
class BaseCurrencyError(Exception):
|
||||||
@ -52,63 +43,106 @@ class CurrenciesCoinCountMismatchError(BaseCurrencyError):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class Currency(TypedDict):
|
class Currency:
|
||||||
name: str
|
"""Currency's representation and methods to work with it.
|
||||||
description: str
|
|
||||||
count: int
|
Arguments:
|
||||||
names: List[str]
|
iso_code -- ISO code of a currency;
|
||||||
multipliers: List[int]
|
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)))}"
|
||||||
|
|
||||||
|
|
||||||
CURRENCIES: Dict[str, Currency] = {
|
CURRENCIES: Dict[str, Currency] = {
|
||||||
"RUB": {
|
"RUB": Currency("RUB", "Russian ruble", "Russian Federation", 8,
|
||||||
"name": "Ruble",
|
["1к.", "5к.", "10к.", "50к.", "1₽", "2₽", "5₽", "10₽"],
|
||||||
"description": "Russian Federation",
|
[1, 5, 10, 50, 1_00, 2_00, 5_00, 10_00]),
|
||||||
"count": 8,
|
"SRUB": Currency("SRUB", "Russian ruble (shortened)",
|
||||||
"names": ["1к.", "5к.", "10к.", "50к.", "1₽", "2₽", "5₽", "10₽"],
|
"Russian Federation. Excluding coins of 1 and 5 kopek.",
|
||||||
"multipliers": [1, 5, 10, 50, 1_00, 2_00, 5_00, 10_00]
|
8, ["10к.", "50к.", "1₽", "2₽", "5₽", "10₽"],
|
||||||
},
|
[10, 50, 1_00, 2_00, 5_00, 10_00]),
|
||||||
"SRUB": {
|
"BYN": Currency("BYN", "Belarusian ruble", "Belarus", 8,
|
||||||
"name": "Ruble (shortened)",
|
["1к.", "2к.", "5к.", "10к.", "20к.", "50к.", "1р.", "2р."],
|
||||||
"description": "Russian Federation. Excluding coins of 1 and 5 kopek",
|
[1, 2, 5, 10, 20, 50, 1_00, 2_00]),
|
||||||
"count": 6,
|
"UAH": Currency("UAH", "Ukrainian hryvnia", "Ukraine", 10,
|
||||||
"names": ["10к.", "50к.", "1₽", "2₽", "5₽", "10₽"],
|
["1к.", "2к.", "5к.", "10к.", "25к.", "50к.", "₴1", "₴2", "₴5", "₴10"],
|
||||||
"multipliers": [10, 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,
|
||||||
"BYN": {
|
["1¢", "5¢", "10¢", "25¢", "50¢", "$1"],
|
||||||
"name": "Belarusian ruble",
|
[1, 5, 10, 25, 50, 1_00]),
|
||||||
"description": "Belarus",
|
"EUR": Currency("EUR", "Euro", "European Union", 8,
|
||||||
"count": 8,
|
["1c", "2c", "5c", "10c", "20c", "50c", "€1", "€2"],
|
||||||
"names": ["1к.", "2к.", "5к.", "10к.", "20к.", "50к.", "1р.", "2р."],
|
[1, 2, 5, 10, 20, 50, 1_00, 2_00]),
|
||||||
"multipliers": [1, 2, 5, 10, 20, 50, 1_00, 2_00]
|
"GBP": Currency("GBP", "Pound sterling", "United Kingdom", 9,
|
||||||
},
|
["1p", "2p", "5p", "10p", "20p", "25p", "50p", "£1", "£2"],
|
||||||
"UAH": {
|
[1, 2, 5, 10, 20, 25, 50, 1_00, 2_00])
|
||||||
"name": "Ukrainian hryvnia",
|
|
||||||
"description": "Ukraine",
|
|
||||||
"count": 10,
|
|
||||||
"names": ["1к.", "2к.", "5к.", "10к.", "25к.", "50к.", "₴1", "₴2",
|
|
||||||
"₴5", "₴10"],
|
|
||||||
"multipliers": [1, 2, 5, 10, 25, 50, 1_00, 2_00, 5_00, 10_00]
|
|
||||||
},
|
|
||||||
"USD": {
|
|
||||||
"name": "Dollar",
|
|
||||||
"description": "United States of America",
|
|
||||||
"count": 6,
|
|
||||||
"names": ["1¢", "5¢", "10¢", "25¢", "50¢", "$1"],
|
|
||||||
"multipliers": [1, 5, 10, 25, 50, 1_00]
|
|
||||||
},
|
|
||||||
"EUR": {
|
|
||||||
"name": "Euro",
|
|
||||||
"description": "European Union",
|
|
||||||
"count": 8,
|
|
||||||
"names": ["1c", "2c", "5c", "10c", "20c", "50c", "€1", "€2"],
|
|
||||||
"multipliers": [1, 2, 5, 10, 20, 50, 1_00, 2_00]
|
|
||||||
},
|
|
||||||
"GBP": {
|
|
||||||
"name": "Pound sterling",
|
|
||||||
"description": "United Kingdom",
|
|
||||||
"count": 9,
|
|
||||||
"names": ["1p", "2p", "5p", "10p", "20p", "25p", "50p", "£1", "£2"],
|
|
||||||
"multipliers": [1, 2, 5, 10, 20, 25, 50, 1_00, 2_00]
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user