| @@ -0,0 +1,138 @@ | |||||
| # Created by https://www.toptal.com/developers/gitignore/api/python | |||||
| # Edit at https://www.toptal.com/developers/gitignore?templates=python | |||||
| ### Python ### | |||||
| # Byte-compiled / optimized / DLL files | |||||
| __pycache__/ | |||||
| *.py[cod] | |||||
| *$py.class | |||||
| # C extensions | |||||
| *.so | |||||
| # Distribution / packaging | |||||
| .Python | |||||
| build/ | |||||
| develop-eggs/ | |||||
| dist/ | |||||
| downloads/ | |||||
| eggs/ | |||||
| .eggs/ | |||||
| lib/ | |||||
| lib64/ | |||||
| parts/ | |||||
| sdist/ | |||||
| var/ | |||||
| wheels/ | |||||
| pip-wheel-metadata/ | |||||
| share/python-wheels/ | |||||
| *.egg-info/ | |||||
| .installed.cfg | |||||
| *.egg | |||||
| MANIFEST | |||||
| # 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/ | |||||
| .nox/ | |||||
| .coverage | |||||
| .coverage.* | |||||
| .cache | |||||
| nosetests.xml | |||||
| coverage.xml | |||||
| *.cover | |||||
| *.py,cover | |||||
| .hypothesis/ | |||||
| .pytest_cache/ | |||||
| # Translations | |||||
| *.mo | |||||
| *.pot | |||||
| # Django stuff: | |||||
| *.log | |||||
| local_settings.py | |||||
| db.sqlite3 | |||||
| db.sqlite3-journal | |||||
| # Flask stuff: | |||||
| instance/ | |||||
| .webassets-cache | |||||
| # Scrapy stuff: | |||||
| .scrapy | |||||
| # Sphinx documentation | |||||
| docs/_build/ | |||||
| # PyBuilder | |||||
| target/ | |||||
| # Jupyter Notebook | |||||
| .ipynb_checkpoints | |||||
| # IPython | |||||
| profile_default/ | |||||
| ipython_config.py | |||||
| # pyenv | |||||
| .python-version | |||||
| # pipenv | |||||
| # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. | |||||
| # However, in case of collaboration, if having platform-specific dependencies or dependencies | |||||
| # having no cross-platform support, pipenv may install dependencies that don't work, or not | |||||
| # install all needed dependencies. | |||||
| #Pipfile.lock | |||||
| # PEP 582; used by e.g. github.com/David-OConnor/pyflow | |||||
| __pypackages__/ | |||||
| # Celery stuff | |||||
| celerybeat-schedule | |||||
| celerybeat.pid | |||||
| # SageMath parsed files | |||||
| *.sage.py | |||||
| # Environments | |||||
| .env | |||||
| .venv | |||||
| env/ | |||||
| venv/ | |||||
| ENV/ | |||||
| env.bak/ | |||||
| venv.bak/ | |||||
| # Spyder project settings | |||||
| .spyderproject | |||||
| .spyproject | |||||
| # Rope project settings | |||||
| .ropeproject | |||||
| # mkdocs documentation | |||||
| /site | |||||
| # mypy | |||||
| .mypy_cache/ | |||||
| .dmypy.json | |||||
| dmypy.json | |||||
| # Pyre type checker | |||||
| .pyre/ | |||||
| # pytype static type analyzer | |||||
| .pytype/ | |||||
| # End of https://www.toptal.com/developers/gitignore/api/python | |||||
| @@ -0,0 +1,16 @@ | |||||
| # A small script that converts CSV output from Sparebanken Sør to a format that YNAB can import | |||||
| Usage: | |||||
| ```bash | |||||
| python ynab.py <path-to-csv-file> | |||||
| ``` | |||||
| When eksporting from Sparebanken Sør, the file will usually be called `transaksjoner.csv` and will be placed in the Downloads folder, | |||||
| so the command will usually be: | |||||
| ```bash | |||||
| python ynab.py Downloads/transaksjoner.csv | |||||
| ``` | |||||
| A new CSV file will be created in the same folder, with `ynab-` prepended to the filename. | |||||
| @@ -0,0 +1,64 @@ | |||||
| #!/bin/python3 | |||||
| import sys | |||||
| import csv | |||||
| import re | |||||
| from pathlib import Path | |||||
| def usage(): | |||||
| print('Usage: ynab.py <filename>') | |||||
| def is_final(row): | |||||
| return row[2] not in ['VISA', 'Varekjøp'] | |||||
| def convert(reader, writer): | |||||
| header = ['Date', 'Payee', 'Memo', 'Outflow', 'Inflow'] | |||||
| writer.writerow(header) | |||||
| # Ignore header | |||||
| reader.__next__() | |||||
| for row in reader: | |||||
| if not is_final(row): | |||||
| continue | |||||
| money_out = 0 | |||||
| money_in = 0 | |||||
| money = float(row[3].replace(',', '.')) | |||||
| if money >= 0: | |||||
| money_in = money | |||||
| else: | |||||
| money_out = -money | |||||
| memo = re.match(r'="[ ]?(.+)"', row[2]).groups()[0] | |||||
| new_row = [*row[:2], memo, money_out, money_in] | |||||
| writer.writerow(new_row) | |||||
| def main(): | |||||
| if len(sys.argv) != 2: | |||||
| usage() | |||||
| exit(1) | |||||
| filepath = Path(sys.argv[1]) | |||||
| new_basename = f'ynab-{filepath.name}' | |||||
| new_filepath = filepath.parent / new_basename | |||||
| with filepath.open(mode='r') as old_file: | |||||
| reader = csv.reader(old_file, delimiter=';') | |||||
| with new_filepath.open(mode='w') as new_file: | |||||
| writer = csv.writer(new_file) | |||||
| convert(reader, writer) | |||||
| if __name__ == '__main__': | |||||
| main() | |||||