From da872e8aabf9fbb724a9554b8dcd7c255b9024aa Mon Sep 17 00:00:00 2001 From: Midgard Date: Fri, 21 Jun 2019 12:58:16 +0200 Subject: [PATCH] Initial commit --- .gitignore | 111 +++++++++++++++++++++++++++++++++++++++++++++++++++ Pipfile | 12 ++++++ Pipfile.lock | 57 ++++++++++++++++++++++++++ tab | 87 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 267 insertions(+) create mode 100644 .gitignore create mode 100644 Pipfile create mode 100644 Pipfile.lock create mode 100755 tab diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2e60305 --- /dev/null +++ b/.gitignore @@ -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/ \ No newline at end of file diff --git a/Pipfile b/Pipfile new file mode 100644 index 0000000..1171f78 --- /dev/null +++ b/Pipfile @@ -0,0 +1,12 @@ +[[source]] +url = "https://pypi.org/simple" +verify_ssl = true +name = "pypi" + +[packages] +requests = "*" + +[dev-packages] + +[requires] +python_version = "3.7" diff --git a/Pipfile.lock b/Pipfile.lock new file mode 100644 index 0000000..b7f0a91 --- /dev/null +++ b/Pipfile.lock @@ -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": {} +} diff --git a/tab b/tab new file mode 100755 index 0000000..535aca5 --- /dev/null +++ b/tab @@ -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)