Initial commit
This commit is contained in:
commit
da872e8aab
4 changed files with 267 additions and 0 deletions
111
.gitignore
vendored
Normal file
111
.gitignore
vendored
Normal file
|
@ -0,0 +1,111 @@
|
||||||
|
# Byte-compiled / optimized / DLL files
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*$py.class
|
||||||
|
|
||||||
|
# C extensions
|
||||||
|
*.so
|
||||||
|
|
||||||
|
# Distribution / packaging
|
||||||
|
.Python
|
||||||
|
env/
|
||||||
|
build/
|
||||||
|
develop-eggs/
|
||||||
|
dist/
|
||||||
|
downloads/
|
||||||
|
eggs/
|
||||||
|
.eggs/
|
||||||
|
lib/
|
||||||
|
lib64/
|
||||||
|
parts/
|
||||||
|
sdist/
|
||||||
|
var/
|
||||||
|
wheels/
|
||||||
|
*.egg-info/
|
||||||
|
.installed.cfg
|
||||||
|
*.egg
|
||||||
|
|
||||||
|
# PyInstaller
|
||||||
|
# Usually these files are written by a python script from a template
|
||||||
|
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||||
|
*.manifest
|
||||||
|
*.spec
|
||||||
|
|
||||||
|
# Installer logs
|
||||||
|
pip-log.txt
|
||||||
|
pip-delete-this-directory.txt
|
||||||
|
|
||||||
|
# Unit test / coverage reports
|
||||||
|
htmlcov/
|
||||||
|
.tox/
|
||||||
|
.coverage
|
||||||
|
.coverage.*
|
||||||
|
.cache
|
||||||
|
nosetests.xml
|
||||||
|
coverage.xml
|
||||||
|
*.cover
|
||||||
|
.hypothesis/
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
*.mo
|
||||||
|
*.pot
|
||||||
|
|
||||||
|
# Django stuff:
|
||||||
|
*.log
|
||||||
|
local_settings.py
|
||||||
|
|
||||||
|
# Flask stuff:
|
||||||
|
instance/
|
||||||
|
.webassets-cache
|
||||||
|
|
||||||
|
# Scrapy stuff:
|
||||||
|
.scrapy
|
||||||
|
|
||||||
|
# Sphinx documentation
|
||||||
|
docs/_build/
|
||||||
|
|
||||||
|
# PyBuilder
|
||||||
|
target/
|
||||||
|
|
||||||
|
# Jupyter Notebook
|
||||||
|
.ipynb_checkpoints
|
||||||
|
|
||||||
|
# pyenv
|
||||||
|
.python-version
|
||||||
|
|
||||||
|
# celery beat schedule file
|
||||||
|
celerybeat-schedule
|
||||||
|
|
||||||
|
# SageMath parsed files
|
||||||
|
*.sage.py
|
||||||
|
|
||||||
|
# dotenv
|
||||||
|
.env
|
||||||
|
|
||||||
|
# virtualenv
|
||||||
|
.venv
|
||||||
|
venv/
|
||||||
|
ENV/
|
||||||
|
|
||||||
|
# Spyder project settings
|
||||||
|
.spyderproject
|
||||||
|
.spyproject
|
||||||
|
|
||||||
|
# Rope project settings
|
||||||
|
.ropeproject
|
||||||
|
|
||||||
|
# mkdocs documentation
|
||||||
|
/site
|
||||||
|
|
||||||
|
# mypy
|
||||||
|
.mypy_cache/
|
||||||
|
|
||||||
|
# backup files
|
||||||
|
*~
|
||||||
|
|
||||||
|
bin/
|
||||||
|
include/
|
||||||
|
pip-selfcheck.json
|
||||||
|
|
||||||
|
# visual studio code
|
||||||
|
.vscode/
|
12
Pipfile
Normal file
12
Pipfile
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
[[source]]
|
||||||
|
url = "https://pypi.org/simple"
|
||||||
|
verify_ssl = true
|
||||||
|
name = "pypi"
|
||||||
|
|
||||||
|
[packages]
|
||||||
|
requests = "*"
|
||||||
|
|
||||||
|
[dev-packages]
|
||||||
|
|
||||||
|
[requires]
|
||||||
|
python_version = "3.7"
|
57
Pipfile.lock
generated
Normal file
57
Pipfile.lock
generated
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
{
|
||||||
|
"_meta": {
|
||||||
|
"hash": {
|
||||||
|
"sha256": "bb57e0d7853b45999e47c163c46b95bc2fde31c527d8d7b5b5539dc979444a6d"
|
||||||
|
},
|
||||||
|
"pipfile-spec": 6,
|
||||||
|
"requires": {
|
||||||
|
"python_version": "3.7"
|
||||||
|
},
|
||||||
|
"sources": [
|
||||||
|
{
|
||||||
|
"name": "pypi",
|
||||||
|
"url": "https://pypi.org/simple",
|
||||||
|
"verify_ssl": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"default": {
|
||||||
|
"certifi": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:046832c04d4e752f37383b628bc601a7ea7211496b4638f6514d0e5b9acc4939",
|
||||||
|
"sha256:945e3ba63a0b9f577b1395204e13c3a231f9bc0223888be653286534e5873695"
|
||||||
|
],
|
||||||
|
"version": "==2019.6.16"
|
||||||
|
},
|
||||||
|
"chardet": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae",
|
||||||
|
"sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"
|
||||||
|
],
|
||||||
|
"version": "==3.0.4"
|
||||||
|
},
|
||||||
|
"idna": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407",
|
||||||
|
"sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c"
|
||||||
|
],
|
||||||
|
"version": "==2.8"
|
||||||
|
},
|
||||||
|
"requests": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:11e007a8a2aa0323f5a921e9e6a2d7e4e67d9877e85773fba9ba6419025cbeb4",
|
||||||
|
"sha256:9cf5292fcd0f598c671cfc1e0d7d1a7f13bb8085e9a590f48c010551dc6c4b31"
|
||||||
|
],
|
||||||
|
"index": "pypi",
|
||||||
|
"version": "==2.22.0"
|
||||||
|
},
|
||||||
|
"urllib3": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:b246607a25ac80bedac05c6f282e3cdaf3afb65420fd024ac94435cabe6e18d1",
|
||||||
|
"sha256:dbe59173209418ae49d485b87d1681aefa36252ee85884c31346debd19463232"
|
||||||
|
],
|
||||||
|
"version": "==1.25.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"develop": {}
|
||||||
|
}
|
87
tab
Executable file
87
tab
Executable file
|
@ -0,0 +1,87 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import itertools
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
import requests
|
||||||
|
|
||||||
|
|
||||||
|
ME = os.getenv("TAB_ME")
|
||||||
|
TOKEN = os.getenv("TAB_TOKEN")
|
||||||
|
|
||||||
|
TAP_ACCOUNT = os.getenv("LEDGER_TAP_ACCOUNT", "Expenses:Zeuskelder:Drinks and snacks")
|
||||||
|
FOOD_ACCOUNT = os.getenv("LEDGER_FOOD_ACCOUNT", "Expenses:Zeuskelder:Food")
|
||||||
|
|
||||||
|
|
||||||
|
def formatted_price(cents):
|
||||||
|
return f"€ {cents / 100}"
|
||||||
|
|
||||||
|
|
||||||
|
def balance_assertion(balance):
|
||||||
|
return \
|
||||||
|
f"= {formatted_price(balance)}" \
|
||||||
|
if balance is not None else ""
|
||||||
|
|
||||||
|
|
||||||
|
def formatted_transaction(transaction, me, balance_after=None):
|
||||||
|
issuer = transaction["issuer"]
|
||||||
|
debtor = transaction["debtor"]
|
||||||
|
date = transaction["time"][:10]
|
||||||
|
amount = formatted_price(transaction["amount"])
|
||||||
|
|
||||||
|
message = (
|
||||||
|
transaction["message"].replace("1 ", "").replace(" and ", " en ")
|
||||||
|
if issuer == "Tap" else
|
||||||
|
transaction["message"]
|
||||||
|
).strip()
|
||||||
|
|
||||||
|
header = f"{date} {message}"
|
||||||
|
|
||||||
|
assertion = balance_assertion(balance_after)
|
||||||
|
|
||||||
|
if debtor == me:
|
||||||
|
# If the issuer wasn't Tap, we assume the money we gave was for food – manual change to the
|
||||||
|
# output required if it wasn't
|
||||||
|
to_account = TAP_ACCOUNT if issuer == "Tap" else FOOD_ACCOUNT
|
||||||
|
|
||||||
|
to_line = f"{to_account} {amount}"
|
||||||
|
from_line = f"tab {assertion}".rstrip()
|
||||||
|
|
||||||
|
else:
|
||||||
|
to_line = f"tab {amount} {assertion}"
|
||||||
|
from_line = f"; {debtor}"
|
||||||
|
|
||||||
|
|
||||||
|
return "\n".join([
|
||||||
|
header,
|
||||||
|
"\t" + to_line,
|
||||||
|
"\t" + from_line
|
||||||
|
])
|
||||||
|
|
||||||
|
|
||||||
|
def formatted_transactions(transactions, final_balance, me):
|
||||||
|
all_but_last = itertools.islice(transactions, len(transactions) - 1)
|
||||||
|
return [
|
||||||
|
*(formatted_transaction(t, me) for t in all_but_last),
|
||||||
|
formatted_transaction(transactions[-1], me, final_balance)
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def main(me, token, file=sys.stdout):
|
||||||
|
def get(path):
|
||||||
|
return requests.get(
|
||||||
|
f"https://tab.zeus.gent{path}",
|
||||||
|
headers={"Accept": "application/json", "Authorization": f"Token token={token}"}
|
||||||
|
).json()
|
||||||
|
|
||||||
|
balance = get(f"/users/{me}")["balance"]
|
||||||
|
transactions = get(f"/users/{me}/transactions")
|
||||||
|
|
||||||
|
print(
|
||||||
|
"\n\n".join(formatted_transactions(transactions, balance, me)),
|
||||||
|
file=file
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main(ME, TOKEN)
|
Loading…
Reference in a new issue