1
0
Fork 0

Initial commit adding everything that consists the project.

This commit is contained in:
Alexander "Arav" Andreev 2019-12-25 23:08:20 +04:00 committed by Alexander Arav Andreev
commit da5f71ab2c
15 changed files with 732 additions and 0 deletions

BIN
.gitignore vendored Normal file

Binary file not shown.

13
COPYING Normal file
View 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
View 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` &mdash; puts coins in a piggy bank and create a new one if
there is no such file;
- `piggybank-take` &mdash; takes coins from an existsing piggy bank file;
- `piggybank-show` &mdash; 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 &mdash; 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
View 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
View 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__}")

View 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
View 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
View 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
View 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
View 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": ["", "", "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
View 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
View 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
View 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
View 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

3
setup.py Normal file
View File

@ -0,0 +1,3 @@
from setuptools import setup
setup()