Initial commit adding everything that consists the project.
This commit is contained in:
commit
da5f71ab2c
BIN
.gitignore
vendored
Normal file
BIN
.gitignore
vendored
Normal file
Binary file not shown.
13
COPYING
Normal file
13
COPYING
Normal file
@ -0,0 +1,13 @@
|
||||
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
||||
Version 2, December 2004
|
||||
|
||||
Copyright (C) 2004 Sam Hocevar <sam@hocevar.net>
|
||||
|
||||
Everyone is permitted to copy and distribute verbatim or modified
|
||||
copies of this license document, and changing it is allowed as long
|
||||
as the name is changed.
|
||||
|
||||
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. You just DO WHAT THE FUCK YOU WANT TO.
|
89
README.md
Normal file
89
README.md
Normal file
@ -0,0 +1,89 @@
|
||||
Keep track of your piggy bank.
|
||||
|
||||
|
||||
Every time you put coins in or take them out of your piggy bank, write here down
|
||||
how much did you put or take. So you won't have to spend time to count them when
|
||||
you decide to take them all out.
|
||||
|
||||
Yes, only coins, no banknots. Well, you actually can easily add support for
|
||||
banknots, but table will become indeed wide.
|
||||
|
||||
# Usage
|
||||
|
||||
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
|
||||
there is no such file;
|
||||
- `piggybank-take` — takes coins from an existsing piggy bank file;
|
||||
- `piggybank-show` — shows summarised information on a piggy bank and,
|
||||
optionally, print a list of transactions.
|
||||
|
||||
## Common flags
|
||||
|
||||
```bash
|
||||
piggybank-* [(-h | --help) | (-v | --version) | --list-currencies]
|
||||
```
|
||||
|
||||
Those three flags are very self-explanatory. The first one will print the help.
|
||||
The second — version and license. And the last one will print all
|
||||
supported currencies. They are not combinable.
|
||||
|
||||
## How to put coins in
|
||||
|
||||
```bash
|
||||
piggybank-put <file> [-r | --reverse] <coins> [(-c | --currency) <currency>]
|
||||
```
|
||||
|
||||
Here's how to put coins in a piggybank file. It will also create a file if it
|
||||
doesn't exist.
|
||||
|
||||
<file> is a name of a piggy bank file wich may not have a .pb extension at the
|
||||
end, it will be added automatically.
|
||||
|
||||
<coins> is a set of coins' counts separated with space character. For example:
|
||||
0 0 0 5 0 4 6 7. By default trailing zeroes will be added from the
|
||||
left. So if you wrote something like 6 0 5 12 then it will be complemented to
|
||||
0 0 0 0 6 0 5 12 if currency has 8 coins.
|
||||
|
||||
--reverse flag reverses the order of coins. By default they come from least
|
||||
significant face value to the most significant one and as stated above being
|
||||
complemented from left so a set shorter than number of coins in a currency,
|
||||
e.g. 5 8 4 will be interpreted as 0 0 0 0 0 5 8 4. And this flag will make it
|
||||
to be interpreted as 5 8 4 0 0 0 0 0. It is convenient if you want to add only
|
||||
coins of low face value (e.g. cents or kopeks).
|
||||
|
||||
--currency parameter specifies the currency of a new piggy bank. It will not
|
||||
change currency of an existing piggy bank.
|
||||
|
||||
### Examples:
|
||||
|
||||
piggybank-put example.pb 7 4 3 0 5 -c euro
|
||||
piggybank-put example -r 0 4 6
|
||||
|
||||
## How to take coins out
|
||||
|
||||
```bash
|
||||
piggybank-take <file> [-r | --reverse] <coins>
|
||||
```
|
||||
|
||||
All the parameters are explained above. `--currency` flag is not applicable
|
||||
here. It just takes coins out rather putting them in.
|
||||
|
||||
### Examples:
|
||||
|
||||
```bash
|
||||
piggybank-take example.pb -r 0 0 4
|
||||
```
|
||||
|
||||
## How to see what do you have
|
||||
|
||||
piggybank-show <file> [-t | --transactions]
|
||||
|
||||
Without -t flag only summarised information will be printed. --transactions flag
|
||||
tells the program to print out all the transactions stored in a piggy bank.
|
||||
|
||||
### Examples:
|
||||
|
||||
```bash
|
||||
piggybank-show example.pb -t
|
||||
```
|
4
build_and_install.cmd
Normal file
4
build_and_install.cmd
Normal file
@ -0,0 +1,4 @@
|
||||
@echo off
|
||||
python setup.py sdist bdist_wheel
|
||||
python -m pip install --user --upgrade dist/piggybank-1.0.0-py3-none-any.whl
|
||||
pause
|
20
piggybank/__init__.py
Normal file
20
piggybank/__init__.py
Normal file
@ -0,0 +1,20 @@
|
||||
__date__ = "25 December 2019"
|
||||
__version__ = "1.0.0"
|
||||
__author__ = "Alexander \"Arav\""
|
||||
__email__ = "me@aravs.ru"
|
||||
__copyright__ = f"Copyright (c) 2019 {__author__} <{__email__}>"
|
||||
__license__ = \
|
||||
"""This program is free software. It comes without any warranty, to
|
||||
the extent permitted by applicable law. You can redistribute it
|
||||
and/or modify it under the terms of the Do What The Fuck You Want
|
||||
To Public License, Version 2, as published by Sam Hocevar. See
|
||||
http://www.wtfpl.net/ for more details."""
|
||||
|
||||
|
||||
PIGGYBANK_FILE_EXTENSION = ".pb"
|
||||
|
||||
|
||||
def print_program_version() -> None:
|
||||
"""Print information about program. Includes name and version; copyright
|
||||
notice and license."""
|
||||
print(f"Coinbox ver. {__version__}\n\n{__copyright__}\n\n{__license__}")
|
2
piggybank/cli/__init__.py
Normal file
2
piggybank/cli/__init__.py
Normal file
@ -0,0 +1,2 @@
|
||||
EPILOGUE = """This program is to assist you to keep track of how much coins
|
||||
you have across your piggy banks."""
|
61
piggybank/cli/put.py
Normal file
61
piggybank/cli/put.py
Normal file
@ -0,0 +1,61 @@
|
||||
"""CLI: Put a set of coins into a piggy bank."""
|
||||
|
||||
from argparse import ArgumentParser
|
||||
from os.path import exists
|
||||
from sys import exit, stderr
|
||||
|
||||
from piggybank import print_program_version
|
||||
from piggybank.piggybank import PiggyBank
|
||||
from piggybank.cli import EPILOGUE
|
||||
from piggybank.currencies import CURRENCIES, DEFAULT_CURRENCY, \
|
||||
BaseCurrencyError, print_supported_currencies
|
||||
from piggybank.util import add_common_arguments_to_parser, \
|
||||
complement_array_of_coins
|
||||
|
||||
__all__ = ["main"]
|
||||
|
||||
|
||||
def main() -> None:
|
||||
"""An entry point for a put command."""
|
||||
parser = ArgumentParser(prog="piggybank-put",
|
||||
description="Add a set of coins to a piggy bank.",
|
||||
epilog=EPILOGUE)
|
||||
parser.add_argument("file", type=str,
|
||||
help="a piggy bank file name. Missing .pb extension"
|
||||
"will be added")
|
||||
parser.add_argument("coins", type=int, nargs="+", metavar="COIN",
|
||||
help="a set of coins to add to a piggy bank. A new file"
|
||||
"will be created if it doesn't exist")
|
||||
|
||||
parser.add_argument("-c", "--currency", type=str, default=DEFAULT_CURRENCY,
|
||||
help="set currency of a piggy bank. Not applicable to"
|
||||
"an existing one")
|
||||
|
||||
add_common_arguments_to_parser(parser)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.version:
|
||||
print_program_version()
|
||||
exit()
|
||||
|
||||
if args.list_currencies:
|
||||
print_supported_currencies()
|
||||
exit()
|
||||
|
||||
try:
|
||||
try:
|
||||
piggybank = PiggyBank.from_file(args.file)
|
||||
except FileNotFoundError:
|
||||
piggybank = PiggyBank(args.currency)
|
||||
coins = complement_array_of_coins(args.coins, piggybank.currency,
|
||||
args.reverse)
|
||||
piggybank.transact(coins)
|
||||
piggybank.save(args.file)
|
||||
except BaseCurrencyError:
|
||||
print(f"{type(err).__name__}:", err, file=stderr)
|
||||
except ValueError as err:
|
||||
print(f"{type(err).__name__}:", err, file=stderr)
|
||||
except Exception as err:
|
||||
print(f"Something went exceptionally wrong. Error:",
|
||||
f"{type(err).__name__}:", err, file=stderr)
|
116
piggybank/cli/show.py
Normal file
116
piggybank/cli/show.py
Normal file
@ -0,0 +1,116 @@
|
||||
"""CLI: Take a set of coins from a piggy bank."""
|
||||
|
||||
from argparse import ArgumentParser
|
||||
from os.path import exists
|
||||
from sys import exit, stderr
|
||||
|
||||
from piggybank import print_program_version, PIGGYBANK_FILE_EXTENSION
|
||||
from piggybank.piggybank import PiggyBank
|
||||
from piggybank.cli import EPILOGUE
|
||||
from piggybank.currencies import CURRENCIES, DEFAULT_CURRENCY, \
|
||||
BaseCurrencyError, print_supported_currencies
|
||||
from piggybank.util import add_common_arguments_to_parser
|
||||
|
||||
__all__ = ["main"]
|
||||
|
||||
DEFAULT_COIN_CENTERING: int = 10
|
||||
|
||||
|
||||
def print_summary(piggybank: PiggyBank,
|
||||
centering: int = DEFAULT_COIN_CENTERING) -> None:
|
||||
"""Print summarised info on a piggy bank."""
|
||||
def print_separator(left="┣", lmiddle="╋", rmiddle="╋", right="┫"):
|
||||
line = rmiddle.join('━' * centering
|
||||
for _ in
|
||||
range(CURRENCIES[piggybank.currency]['count']))
|
||||
print(f"{left}{'━'*27}{lmiddle}{line}{right}")
|
||||
|
||||
cc, cs, ct = piggybank.count, piggybank.sum, piggybank.total
|
||||
|
||||
cline = "┃".join([f'{l:^{centering}}'
|
||||
for l in CURRENCIES[piggybank.currency]["names"]])
|
||||
cline_len = len(cline)
|
||||
|
||||
print_separator(left="┏", lmiddle="┳", rmiddle="━", right="┓")
|
||||
print(f"┃{'currency':^27}┃"
|
||||
f"{CURRENCIES[piggybank.currency]['name']:^{cline_len}}┃")
|
||||
print_separator(rmiddle="┳")
|
||||
print(f"┃{'face values':^27}┃{cline}┃")
|
||||
print_separator()
|
||||
print(f"┃{'amount':^27}┃{'┃'.join([f'{c:^{centering}}' for c in cc])}┃")
|
||||
print_separator()
|
||||
print(f"┃{'sum':^27}┃"
|
||||
f"{'┃'.join(['{:^{}.2f}'.format(c / 100, centering) for c in cs])}┃")
|
||||
print_separator(rmiddle="┻")
|
||||
print(f"┃{'total':^27}┃{'{:^{}.2f}'.format(ct / 100, cline_len)}┃")
|
||||
print_separator(left="┗", lmiddle="┻", rmiddle="━", right="┛")
|
||||
|
||||
|
||||
def print_transactions(piggybank, centering=DEFAULT_COIN_CENTERING):
|
||||
"""Print a list of all transactions stored in a piggy bank."""
|
||||
def print_separator(left="┏", middle="┳", right="┓"):
|
||||
line = middle.join('━' * centering
|
||||
for _ in
|
||||
range(CURRENCIES[piggybank.currency]['count']))
|
||||
print(f"{left}━━━━━━━━━━━━━━━━━━━━━{middle}━━━━━{middle}{line}{right}")
|
||||
|
||||
cline = "┃".join([f'{l:^{centering}}'
|
||||
for l in CURRENCIES[piggybank.currency]["names"]])
|
||||
# cline_len = len(cline)
|
||||
|
||||
print_separator()
|
||||
print(f"┃{'Timestamp':^21}┃ I/O ┃{cline}┃")
|
||||
print_separator("┣", "╋", "┫")
|
||||
for tr in piggybank.transactions:
|
||||
coin_line = "┃".join([f'{c:^{centering}}' for c in tr.coins])
|
||||
print(f"┃ {tr.timestamp} ┃{tr.direction:^5}┃{coin_line}┃")
|
||||
print_separator("┗", "┻", "┛")
|
||||
|
||||
|
||||
def main():
|
||||
"""An entry point for a show command."""
|
||||
parser = ArgumentParser(prog="piggybank-show",
|
||||
description="Show information on a piggy bank.",
|
||||
epilog=EPILOGUE)
|
||||
|
||||
parser.add_argument("file", type=str,
|
||||
help="a piggy bank file name. Missing .pb extension"
|
||||
"will be added")
|
||||
|
||||
parser.add_argument("-t", "--transactions", action="store_true",
|
||||
help="print a list of transactions as well")
|
||||
|
||||
parser.add_argument("-m", "--merge", action="append",
|
||||
type=str, metavar="FILE",
|
||||
help="merge multiple files to show how much do you"
|
||||
"have across them. They all should have same currency")
|
||||
|
||||
add_common_arguments_to_parser(parser, include_reverse_flag=False)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.version:
|
||||
print_program_version()
|
||||
exit()
|
||||
|
||||
if args.list_currencies:
|
||||
print_supported_currencies()
|
||||
exit()
|
||||
|
||||
try:
|
||||
piggybank = PiggyBank.from_file(args.file)
|
||||
if args.merge:
|
||||
for _file in args.merge:
|
||||
merge_piggybank = PiggyBank.from_file(_file)
|
||||
piggybank += merge_piggybank
|
||||
print(_file)
|
||||
print_summary(piggybank)
|
||||
if args.transactions:
|
||||
print_transactions(piggybank)
|
||||
except BaseCurrencyError as err:
|
||||
print(f"{type(err).__name__}:", err, file=stderr)
|
||||
except FileNotFoundError as err:
|
||||
print(f"{type(err).__name__}:", f"{err} doesn't exist.", file=stderr)
|
||||
except Exception as err:
|
||||
print(f"Something went exceptionally wrong. Error:",
|
||||
f"{type(err).__name__}:", err, file=stderr)
|
56
piggybank/cli/take.py
Normal file
56
piggybank/cli/take.py
Normal file
@ -0,0 +1,56 @@
|
||||
"""CLI: Take a set of coins from a coin box."""
|
||||
|
||||
from argparse import ArgumentParser
|
||||
from sys import exit, stderr
|
||||
|
||||
from piggybank import print_program_version
|
||||
from piggybank.piggybank import PiggyBank
|
||||
from piggybank.cli import EPILOGUE
|
||||
from piggybank.currencies import CURRENCIES, DEFAULT_CURRENCY, \
|
||||
BaseCurrencyError, print_supported_currencies
|
||||
from piggybank.transaction import TYPE_OUTCOME
|
||||
from piggybank.util import add_common_arguments_to_parser, \
|
||||
complement_array_of_coins
|
||||
|
||||
__all__ = ["main"]
|
||||
|
||||
|
||||
def main():
|
||||
"""An entry point for a take command."""
|
||||
parser = ArgumentParser(prog="piggybank-take",
|
||||
description="Take a set of coins from a coin box.",
|
||||
epilog=EPILOGUE)
|
||||
parser.add_argument("file", type=str,
|
||||
help="a coin box file name. Missing .cb extension will"
|
||||
"be added")
|
||||
parser.add_argument("coins", type=int, nargs="+", metavar="COIN",
|
||||
help="add a set of coins to a coin box. A new file"
|
||||
"will be created if it doesn't exist")
|
||||
|
||||
add_common_arguments_to_parser(parser)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.version:
|
||||
print_program_version()
|
||||
exit()
|
||||
|
||||
if args.list_currencies:
|
||||
print_supported_currencies()
|
||||
exit()
|
||||
|
||||
try:
|
||||
piggybank = PiggyBank.from_file(args.file)
|
||||
coins = complement_array_of_coins(args.coins, piggybank.currency,
|
||||
args.reverse)
|
||||
piggybank.transact(coins, TYPE_OUTCOME)
|
||||
piggybank.save(args.file)
|
||||
except BaseCurrencyError as err:
|
||||
print(f"{type(err).__name__}:", err, file=stderr)
|
||||
except ValueError as err:
|
||||
print(f"{type(err).__name__}:", err, file=stderr)
|
||||
except FileNotFoundError as err:
|
||||
print(f"{type(err).__name__}:", f"{err} doesn't exist.", file=stderr)
|
||||
except Exception as err:
|
||||
print(f"Something went exceptionally wrong. Error:",
|
||||
f"{type(err).__name__}:", err, file=stderr)
|
116
piggybank/currencies.py
Normal file
116
piggybank/currencies.py
Normal file
@ -0,0 +1,116 @@
|
||||
"""Here is a dictionary of supported currencies defined. Which could be easily
|
||||
extended with new ones.
|
||||
|
||||
Each dictionary entry has an ISO code of a currency as its key. Or it can
|
||||
slightly differ from an ISO to represent a modified version of a currency. An
|
||||
example is SRUB entry for shortened version of RUB where coins of 1 and 5 kopek
|
||||
value were removed. And value is an another dictionary consists of following
|
||||
fields:
|
||||
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 are used to store a fraction part. You can simply
|
||||
divide this number by 100 to get a regular floating-point number.
|
||||
"""
|
||||
|
||||
from typing import Dict, List
|
||||
|
||||
__all__ = ["CURRENCIES", "DEFAULT_CURRENCY", "BaseCurrencyError",
|
||||
"CurrencyIsNotSupportedError", "CurrenciesCoinCountMismatchError",
|
||||
"CurrencyMismatchError", "print_supported_currencies"]
|
||||
|
||||
|
||||
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
|
||||
|
||||
|
||||
DEFAULT_CURRENCY: str = "SRUB"
|
||||
|
||||
CURRENCIES: Dict[Dict[str, str, int, List[str], List[int]]] = {
|
||||
"RUB": {
|
||||
"name": "Ruble",
|
||||
"description": "Russian Federation",
|
||||
"count": 8,
|
||||
"names": ["1к.", "5к.", "10к.", "50к.", "1₽", "2₽", "5₽", "10₽"],
|
||||
"multipliers": [1, 5, 10, 50, 1_00, 2_00, 5_00, 10_00]
|
||||
},
|
||||
"SRUB": {
|
||||
"name": "Ruble (shortened)",
|
||||
"description": "Russian Federation. Excluding coins of 1 and 5 kopek",
|
||||
"count": 6,
|
||||
"names": ["10к.", "50к.", "1₽", "2₽", "5₽", "10₽"],
|
||||
"multipliers": [10, 50, 1_00, 2_00, 5_00, 10_00]
|
||||
},
|
||||
"BYN": {
|
||||
"name": "Belarusian ruble",
|
||||
"description": "Belarus",
|
||||
"count": 8,
|
||||
"names": ["1к.", "2к.", "5к.", "10к.", "20к.", "50к.", "1р.", "2р."],
|
||||
"multipliers": [1, 2, 5, 10, 20, 50, 1_00, 2_00]
|
||||
},
|
||||
"UAH": {
|
||||
"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]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def print_supported_currencies() -> None:
|
||||
"""Print a list of supported currencies."""
|
||||
print("Supported currencies are:")
|
||||
for cur in CURRENCIES:
|
||||
print(f" {cur:^4} ┃ {CURRENCIES[cur]['name']:^31}"
|
||||
f"┃ {CURRENCIES[cur]['description']}")
|
||||
print("Default currency is", DEFAULT_CURRENCY)
|
128
piggybank/piggybank.py
Normal file
128
piggybank/piggybank.py
Normal file
@ -0,0 +1,128 @@
|
||||
"""Implementation of the piggy bank itself."""
|
||||
|
||||
from __future__ import annotations
|
||||
from os.path import exists
|
||||
from typing import List
|
||||
|
||||
from piggybank import PIGGYBANK_FILE_EXTENSION
|
||||
from piggybank.currencies import CURRENCIES, DEFAULT_CURRENCY, \
|
||||
CurrencyIsNotSupportedError, CurrenciesCoinCountMismatchError, \
|
||||
CurrencyMismatchError
|
||||
from piggybank.transaction import Transaction, TYPE_INCOME
|
||||
|
||||
__all__ = ["PiggyBank"]
|
||||
|
||||
|
||||
class PiggyBank:
|
||||
"""This class stores transactions and do file I/O on piggy bank
|
||||
.pb files."""
|
||||
def __init__(self, currency: str = DEFAULT_CURRENCY) -> None:
|
||||
if currency.upper() in CURRENCIES:
|
||||
self.currency = currency.upper()
|
||||
else:
|
||||
raise CurrencyIsNotSupportedError
|
||||
|
||||
self.transactions = []
|
||||
|
||||
def transact(self, coins: List[int], direction: str = TYPE_INCOME) -> None:
|
||||
"""Make a transaction."""
|
||||
if len(coins) != CURRENCIES[self.currency]["count"]:
|
||||
raise ValueError("Length of passed coins list doesn't match the "
|
||||
f"currency's coins count ({len(coins)} "
|
||||
f"!= {CURRENCIES[self.currency]['count']})")
|
||||
|
||||
self.transactions.append(Transaction(coins, direction))
|
||||
|
||||
for coin_count in self.count:
|
||||
if coin_count < 0:
|
||||
del self.transactions[-1]
|
||||
raise ValueError(
|
||||
"You can't take out more than you have.")
|
||||
|
||||
@property
|
||||
def count(self) -> List[int]:
|
||||
"""Returns a list of counts for each face value."""
|
||||
count = [0] * CURRENCIES[self.currency]["count"]
|
||||
for tr in self.transactions:
|
||||
count = [x + y if tr.direction == TYPE_INCOME
|
||||
else x - y for x, y in zip(count, tr.coins)]
|
||||
return count
|
||||
|
||||
@property
|
||||
def sum(self) -> List[int]:
|
||||
"""Returns a list of sums for each face value multiplied by its
|
||||
currency's multipilers."""
|
||||
return [x * y for x, y
|
||||
in zip(self.count, CURRENCIES[self.currency]["multipliers"])]
|
||||
|
||||
@property
|
||||
def total(self) -> int:
|
||||
"""Returns a total amount of money stored in a PiggyBank."""
|
||||
return sum(self.sum)
|
||||
|
||||
@staticmethod
|
||||
def from_file(filename: str) -> PiggyBank:
|
||||
"""Returns a PiggyBank object loaded from a file with name
|
||||
`filename`."""
|
||||
piggybank = PiggyBank()
|
||||
piggybank.load(filename)
|
||||
return piggybank
|
||||
|
||||
def save(self, filename: str) -> None:
|
||||
"""Writes a PiggyBank object to a file with name `filename`."""
|
||||
if not filename.endswith(PIGGYBANK_FILE_EXTENSION):
|
||||
filename += PIGGYBANK_FILE_EXTENSION
|
||||
with open(filename, 'w') as _file:
|
||||
_file.write(f"{self.currency}\n")
|
||||
for transaction in self.transactions:
|
||||
_file.write(f"{str(transaction)}\n")
|
||||
|
||||
def load(self, filename: str) -> None:
|
||||
"""Loads a PiggyBank from a file with name `filename`."""
|
||||
if not filename.endswith(PIGGYBANK_FILE_EXTENSION):
|
||||
filename += PIGGYBANK_FILE_EXTENSION
|
||||
if not exists(filename):
|
||||
raise FileNotFoundError(filename)
|
||||
with open(filename, 'r') as _file:
|
||||
currency = _file.readline()[:-1]
|
||||
if currency not in CURRENCIES:
|
||||
raise CurrencyIsNotSupportedError
|
||||
else:
|
||||
self.currency = currency
|
||||
for transaction in _file:
|
||||
self.transactions.append(Transaction.from_string(transaction))
|
||||
|
||||
@property
|
||||
def currency(self) -> str:
|
||||
"""Returns a currency of a PiggyBank."""
|
||||
return self.currency
|
||||
|
||||
@currency.setter
|
||||
def currency(self, currency: str) -> None:
|
||||
"""Sets a currency of a PiggyBank with check for support. And if count
|
||||
of coins doesn't match the old currency it won't set a new one."""
|
||||
currency = currency.upper()
|
||||
if currency not in CURRENCIES:
|
||||
raise CurrencyIsNotSupportedError
|
||||
if CURRENCIES[currency]["count"] != CURRENCIES[self.currency]["count"]:
|
||||
raise CurrenciesCoinCountMismatchError
|
||||
self.currency = currency
|
||||
|
||||
@property
|
||||
def transactions(self) -> List[Transaction]:
|
||||
"""Returns a list of transactions."""
|
||||
return self.transactions
|
||||
|
||||
def __eq__(self, piggybank: PiggyBank) -> str:
|
||||
"""It compares only 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
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"PiggyBank(currency={self.currency!r})"
|
49
piggybank/transaction.py
Normal file
49
piggybank/transaction.py
Normal file
@ -0,0 +1,49 @@
|
||||
"""Implementation of Transaction class."""
|
||||
|
||||
from __future__ import annotations
|
||||
from time import strftime, strptime, gmtime
|
||||
from typing import Optional
|
||||
|
||||
__all__ = ["Transaction", "TYPE_INCOME", "TYPE_OUTCOME"]
|
||||
|
||||
TYPE_INCOME = "in"
|
||||
TYPE_OUTCOME = "out"
|
||||
TIME_FORMAT = "%Y-%m-%dT%H:%M:%S"
|
||||
|
||||
|
||||
class Transaction:
|
||||
"""Represents a single transaction.
|
||||
Consists of array of coins' list, direction and timestamp."""
|
||||
def __init__(self, coins, direction: str = TYPE_INCOME,
|
||||
timestamp: Optional[str] = None) -> None:
|
||||
self.coins = coins
|
||||
if direction in [TYPE_INCOME, TYPE_OUTCOME]:
|
||||
self.direction = direction
|
||||
else:
|
||||
raise ValueError(f"Direction may only be"
|
||||
f"'{TYPE_INCOME}' or '{TYPE_OUTCOME}'")
|
||||
if timestamp is None:
|
||||
self.timestamp = strftime(TIME_FORMAT, gmtime())
|
||||
else:
|
||||
try:
|
||||
strptime(timestamp, TIME_FORMAT)
|
||||
except ValueError:
|
||||
raise ValueError(f"Timestamp {timestamp} has wrong format. "
|
||||
f"The right one is {TIME_FORMAT}")
|
||||
self.timestamp = timestamp
|
||||
|
||||
@staticmethod
|
||||
def from_string(transaction: str) -> Transaction:
|
||||
"""Makes a Transaction object from its string output."""
|
||||
timestamp, direction, coins = transaction.split()
|
||||
|
||||
coins = [int(coin) for coin in coins.split(",")]
|
||||
return Transaction(coins, direction, timestamp)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"{self.timestamp} {self.direction} " \
|
||||
f"{','.join(str(c) for c in self.coins)}"
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"Transaction(coins={self.coins!r}," \
|
||||
f"direction={self.direction!r}, timestamp={self.timestamp!r})"
|
34
piggybank/util.py
Normal file
34
piggybank/util.py
Normal file
@ -0,0 +1,34 @@
|
||||
"""Utility functions."""
|
||||
|
||||
from argparse import ArgumentParser
|
||||
from typing import List
|
||||
|
||||
from piggybank import __version__, __copyright__, __license__
|
||||
from piggybank.currencies import CURRENCIES, DEFAULT_CURRENCY
|
||||
|
||||
|
||||
__all__ = ["add_common_arguments_to_parser", "complement_array_of_coins"]
|
||||
|
||||
|
||||
def add_common_arguments_to_parser(parser: ArgumentParser,
|
||||
include_reverse_flag: bool = True) -> None:
|
||||
"""Extends ArgumentParser with a common set of arguments that are shared
|
||||
amongst all parsers in CLI module."""
|
||||
parser.add_argument("-v", "--version", action="store_true",
|
||||
help="show program's version and license and exit")
|
||||
parser.add_argument("--list-currencies", action="store_true",
|
||||
help="list all supported currencies and exit")
|
||||
|
||||
if include_reverse_flag:
|
||||
parser.add_argument("-r", "--reverse", action="store_true",
|
||||
help="reverse a set of coins so incomplete set"
|
||||
"fills with zeros from right. E.g. '8 9' will be"
|
||||
"interpreted as '8 9 0 0 0 0' instead of"
|
||||
"'0 0 0 0 8 9'")
|
||||
|
||||
|
||||
def complement_array_of_coins(coins: List[int], currency: str,
|
||||
_reversed: bool = False) -> List[int]:
|
||||
"""Complements array of coins up to the count of currency's coins."""
|
||||
offset_array = [0] * (CURRENCIES[currency]["count"] - len(coins))
|
||||
return offset_array + coins if not _reversed else coins + offset_array
|
41
setup.cfg
Normal file
41
setup.cfg
Normal file
@ -0,0 +1,41 @@
|
||||
[metadata]
|
||||
name = piggybank
|
||||
version = attr: piggybank.__version__
|
||||
description =
|
||||
Keep track of your piggy bank by writting down quantity of coins you put,
|
||||
not just sums. So you know how much coins you have in there.
|
||||
long_description = file: README.md
|
||||
long_description_content_type = text/markdown
|
||||
author = attr: piggybank.__author__
|
||||
author_email = attr: piggybank.__email__
|
||||
url = https://aravs.ru
|
||||
keywords =
|
||||
coins
|
||||
piggy bank
|
||||
coin box
|
||||
savings
|
||||
license = WTFPL 2.0
|
||||
license_file = COPYING
|
||||
classifiers =
|
||||
Development Status :: 5 - Production/Stable
|
||||
Environment :: Console
|
||||
License :: Other/Proprietary License
|
||||
Natural Language :: English
|
||||
Programming Language :: Python :: 3.6
|
||||
Programming Language :: Python :: 3.7
|
||||
Topic :: Office/Business :: Financial :: Accounting
|
||||
|
||||
[options]
|
||||
zip_safe = True
|
||||
python_requires = >=3.6
|
||||
include_package_data = True
|
||||
packages = find:
|
||||
|
||||
[options.package_data]
|
||||
* = COPYING, README.md
|
||||
|
||||
[options.entry_points]
|
||||
console_scripts =
|
||||
piggybank-put = piggybank.cli.put:main
|
||||
piggybank-take = piggybank.cli.take:main
|
||||
piggybank-show = piggybank.cli.show:main
|
Loading…
Reference in New Issue
Block a user