commit da5f71ab2c2617c61ce75863f18de6aae05a1653 Author: Alexander "Arav" Andreev Date: Wed Dec 25 23:08:20 2019 +0400 Initial commit adding everything that consists the project. diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bc8fe37 Binary files /dev/null and b/.gitignore differ diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..8cffccc --- /dev/null +++ b/COPYING @@ -0,0 +1,13 @@ + DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE + Version 2, December 2004 + + Copyright (C) 2004 Sam Hocevar + + 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. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..2344d04 --- /dev/null +++ b/README.md @@ -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 [-r | --reverse] [(-c | --currency) ] +``` + +Here's how to put coins in a piggybank file. It will also create a file if it +doesn't exist. + + is a name of a piggy bank file wich may not have a .pb extension at the +end, it will be added automatically. + + 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 [-r | --reverse] +``` + +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 [-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 +``` \ No newline at end of file diff --git a/build_and_install.cmd b/build_and_install.cmd new file mode 100644 index 0000000..7d60ba1 --- /dev/null +++ b/build_and_install.cmd @@ -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 \ No newline at end of file diff --git a/piggybank/__init__.py b/piggybank/__init__.py new file mode 100644 index 0000000..6b92923 --- /dev/null +++ b/piggybank/__init__.py @@ -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__}") diff --git a/piggybank/cli/__init__.py b/piggybank/cli/__init__.py new file mode 100644 index 0000000..f903e10 --- /dev/null +++ b/piggybank/cli/__init__.py @@ -0,0 +1,2 @@ +EPILOGUE = """This program is to assist you to keep track of how much coins +you have across your piggy banks.""" \ No newline at end of file diff --git a/piggybank/cli/put.py b/piggybank/cli/put.py new file mode 100644 index 0000000..78f974b --- /dev/null +++ b/piggybank/cli/put.py @@ -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) diff --git a/piggybank/cli/show.py b/piggybank/cli/show.py new file mode 100644 index 0000000..bc1406d --- /dev/null +++ b/piggybank/cli/show.py @@ -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) diff --git a/piggybank/cli/take.py b/piggybank/cli/take.py new file mode 100644 index 0000000..b2d7571 --- /dev/null +++ b/piggybank/cli/take.py @@ -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) diff --git a/piggybank/currencies.py b/piggybank/currencies.py new file mode 100644 index 0000000..3b76c01 --- /dev/null +++ b/piggybank/currencies.py @@ -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) diff --git a/piggybank/piggybank.py b/piggybank/piggybank.py new file mode 100644 index 0000000..902beff --- /dev/null +++ b/piggybank/piggybank.py @@ -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})" diff --git a/piggybank/transaction.py b/piggybank/transaction.py new file mode 100644 index 0000000..25d008c --- /dev/null +++ b/piggybank/transaction.py @@ -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})" diff --git a/piggybank/util.py b/piggybank/util.py new file mode 100644 index 0000000..6268a5e --- /dev/null +++ b/piggybank/util.py @@ -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 diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..2c868a3 --- /dev/null +++ b/setup.cfg @@ -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 diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..fc1f76c --- /dev/null +++ b/setup.py @@ -0,0 +1,3 @@ +from setuptools import setup + +setup() \ No newline at end of file