diff --git a/README.md b/README.md
index 7806043..606cce5 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,38 @@
-# BEPP
+
+
🏦 Bepp 🧮
+
A tiny tool to manage, clean, and merge transaction exports from Banca Etica and PayPal.
+
![PyPI, Status – Shields.io badge](https://img.shields.io/pypi/status/bepp?style=flat)
+
![PyPI, Version – Shields.io badge](https://img.shields.io/pypi/v/bepp?style=flat&logo=pypi)
+
![PyPI, Python Version – Shields.io badge](https://img.shields.io/pypi/pyversions/bepp?style=flat&logo=python)
+
![PyPI, License – Shields.io badge](https://img.shields.io/pypi/l/bepp?style=flat)
+
-A specific and opinionated helper to merge and clean transactions summaries from [Banca Etica](https://bancaetica.it) and [PayPal](https://paypal.com).
+## ℹ️ About
-## Notes
+I wrote this script to learn and practice with Python and [pandas](https://pandas.pydata.org/), while also getting something I really needed out of it.
-- bepp assumes that inside the specified directory all Excel files are Banca Etica’s “estratto conto” files, and all CSV files are PayPal’s transaction summaries.
+## ⏬ Install
+
+1. [Install uv](https://docs.astral.sh/uv/getting-started/installation/ 'Installing uv')
+2. [Start a virtual environment](https://docs.astral.sh/uv/pip/environments/ 'Python environments – uv documentation'): `uv venv`
+2. Install the package: `uv pip install bepp`
+ - append `-U` to install at the user level
+ - append `--system` to install system-wide
+
+## đź“Š Usage
+
+| argument | type | default | description |
+|---|---|---|---|
+| **`input/path/`** | string | **Required** | Input directory, containing the source CSV and Excel files. |
+| `-d`, `--dry_run` | boolean | False | Run the script without changing or printing anything. |
+| `-b`, `--backup` | boolean | False | Save one CSV backup per kind containing all the original transactions, with no modification. |
+| `-c`, `--convert_to_eur` | boolean | False | Convert transactions in other currencies to €. **Note**: This *heavily* slows down the process! |
+| `-m`, `--merge` | boolean | False | Merge the PayPal’s and Banca Etica’s transaction summaries in one unique CSV. |
+| `-o`, `--output_dir` | string | `input/path/`
+
`bepp_export/` | Specify an output directory. |
+| `-p`, `--keep_pp_dupes` | boolean | False | Prevent from removing PayPal transactions in Banca Etica’s logs. |
+
+**Note**: Bepp assumes that inside the specified directory all Excel files are Banca Etica’s “estratto conto” files, and all CSV files are PayPal transaction summaries.
+
+## ♻️ License
+
+Everything inside this repository is licensed under the [GNU Affero General Public License, version 3](https://www.gnu.org/licenses/agpl-3.0.txt).
diff --git a/pyproject.toml b/pyproject.toml
index d996b33..83fd9cb 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,13 +1,50 @@
[project]
name = 'bepp'
-version = '0.0.1'
+version = '0.0.2'
description = 'An opinionated script to gather, clean up and merge transactions from Banca Etica and PayPal.'
+authors = [{ name = 'Tommi', email = 'surfing@tommi.space' }]
readme = 'README.md'
+license = { file = 'LICENSE' }
requires-python = '>=3.13'
dependencies = [
'currencyconverter>=0.17.34',
'pandas>=2.2.3',
'rich-argparse>=1.6.0',
'rich>=13.9.4',
- 'xlrd>=2.0.1'
+ 'xlrd>=2.0.1',
]
+keywords = [
+ 'Banca Etica',
+ 'PayPal',
+ 'Accounting',
+ 'Personal Finance Management',
+ 'Bank transfers',
+ 'Bank',
+]
+classifiers = [
+ 'License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)',
+ 'Development Status :: 4 - Beta',
+ 'Environment :: Console',
+ 'Natural Language :: English',
+ 'Natural Language :: Italian',
+ 'Operating System :: OS Independent',
+ 'Programming Language :: Python :: 3',
+ 'Programming Language :: Python :: 3.13',
+ 'Topic :: Database',
+ 'Topic :: Office/Business :: Financial :: Accounting',
+ 'Topic :: Office/Business :: Financial :: Spreadsheet',
+ 'Topic :: Scientific/Engineering :: Information Analysis',
+ 'Topic :: Utilities',
+]
+
+[project.urls]
+Repository = 'https://codeberg.org/tommi/bepp'
+Issues = 'https://codeberg.org/tommi/bepp/issues'
+Changelog = 'https://codeberg.org/tommi/bepp/commits/branch/main'
+
+[project.scripts]
+bepp = 'bepp:main'
+
+[build-system]
+requires = ['hatchling']
+build-backend = 'hatchling.build'
diff --git a/src/bepp/__init__.py b/src/bepp/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/bepp.py b/src/bepp/bepp.py
similarity index 61%
rename from bepp.py
rename to src/bepp/bepp.py
index c2dc215..2766619 100644
--- a/bepp.py
+++ b/src/bepp/bepp.py
@@ -3,14 +3,17 @@
import glob
import argparse
import pandas as pd
-import xlrd
-from rich.console import Console
+from rich import print
from rich_argparse import RichHelpFormatter
from currency_converter import CurrencyConverter
-description = """
-[bold magenta]Bepp[/bold magenta] helps you deal with CSV and Excel files exported from PayPal and Banca Etica respectively.
-"""
+description = '''
+[url=https://codeberg.org/tommi/bepp][bold]Bepp[/bold][/url] helps you deal with transaction summary files exported from PayPal and Banca Etica.
+'''
+
+epilog = '''
+[bold]Note[/bold]: bepp assumes that inside the specified directory all Excel files are Banca Etica’s [italic]estratto conto[/italic] files, and all CSV files are PayPal transaction summaries.
+'''
def convert_to_eur(date ,amount, currency):
c = CurrencyConverter(fallback_on_missing_rate=True)
@@ -22,40 +25,44 @@ def main():
parser = argparse.ArgumentParser(
prog='Bepp',
description=description,
+ epilog=epilog,
formatter_class=RichHelpFormatter
)
- parser.add_argument('directory', help='Directory containing the source files.')
- parser.add_argument('-m', '--merge', action='store_true', help='Merge the PayPal’s and Banca Etica’s transaction summaries in one unique CSV')
- parser.add_argument('-b', '--backup', action='store_true', help='Save two separate backups containing all the merged original transactions')
- parser.add_argument('-p', '--pp_duplicates', action='store_false', help='Pass this flag to avoid removing PayPal transactions from Banca Etica')
+ parser.add_argument('directory', metavar='INPUT_DIR', help='Directory containing the source files.')
+ parser.add_argument('-b', '--backup', action='store_true', help='Save two separate backups containing all the merged original transactions.')
parser.add_argument('-c', '--convert_to_eur', action='store_true', help='Convert transactions in other currencies to €.\n[bold]NOTE[/bold]: This slows things down very heavily!')
- parser.add_argument('-o', '--output_dir', metavar='OUTPUT', type=str, help='Specify an output directory (the default is the input directory)')
+ parser.add_argument('-d', '--dry_run', action='store_false', help='Run the script without changing or printing anything.')
+ parser.add_argument('-m', '--merge', action='store_true', help='Merge the PayPal’s and Banca Etica’s transaction summaries in one unique CSV.')
+ parser.add_argument('-o', '--output_dir', metavar='OUTPUT_DIR', type=str, help='Specify an output directory (default: “bepp_export” subdirectory in the input dir).')
+ parser.add_argument('-p', '--keep_pp_dupes', action='store_true', help='Prevent from removing PayPal transactions from Banca Etica.')
args = parser.parse_args()
dir = args.directory
- if not dir.endswith('/'):
- dir = dir + '/'
- output_dir = args.output_dir or dir + 'bepp_export/'
- if not output_dir.endswith('/'):
- output_dir = output_dir + '/'
- os.makedirs(output_dir, exist_ok=True)
-
if not os.path.isdir(dir):
- print(f'Error: “{dir}” is not a valid directory.')
+ print(f'[bold red]Error[/bold red]: “{dir}” is not a valid directory.')
sys.exit(1)
- be_pattern = os.path.join(dir, '*.xls')
- pp_pattern = os.path.join(dir, '*.csv')
+ output_dir = args.output_dir or os.path.join(dir, 'bepp_export')
+ if not os.path.isdir(output_dir):
+ print(f'[bold red]Error[/bold red]: “{output_dir}” is not a valid directory.')
+ sys.exit(1)
+
+ if not args.dry_run:
+ os.makedirs(output_dir, exist_ok=True)
+
+ print(f'Reading from {os.path.abspath(dir)}…')
- be_files = glob.glob(be_pattern)
- pp_files = glob.glob(pp_pattern)
+ be_files = glob.glob(os.path.join(dir, '*.xls'))
+ pp_files = glob.glob(os.path.join(dir, '*.CSV')) + glob.glob(os.path.join(dir, '*.csv'))
if not be_files or not pp_files:
print('No Excel files (Banca Etica’s export format) or CSV files (PayPal’s export format) found in the specified directory.')
sys.exit(1)
+ print('Banca Etica logs found:')
be_list = []
for f in be_files:
+ print(f'\t- \'{os.path.basename(f)}\'')
be_file = pd.DataFrame(pd.read_excel(
f,
parse_dates=[1, 2],
@@ -69,7 +76,7 @@ def main():
be = be[['Valuta', 'amount', 'Divisa', 'Descrizione']]
be = be.sort_values(by='Valuta', ascending=False)
- if args.pp_duplicates:
+ if not args.keep_pp_dupes:
be = be[~be['Descrizione'].str.contains(r'(?i)PayPal', regex=True)]
be['Descrizione'] = be['Descrizione'].str.replace(r'(?i)(?:.* FAVORE |(Pagamenti|accredito).* A )(?P.*)(?: IND\.ORD\..* Note: | Valuta.*C\/O )(?P.*)(:? ID\..*| CARTA.*)', r'\g, \g', regex=True)
@@ -85,8 +92,10 @@ def main():
be['amount'] = be.apply(lambda row: convert_to_eur(row['date'], row['amount'], row['currency']), axis=1)
be.drop('currency', axis='columns')
+ print('PayPal logs found:')
pp_list = []
for f in pp_files:
+ print(f'\t- \'{os.path.basename(f)}\'')
pp_file = pd.DataFrame(pd.read_csv(f))
pp_file = pp_file.dropna(axis='columns', how='all')
pp_list.append(pp_file)
@@ -114,19 +123,19 @@ def main():
pp['amount'] = pp.apply(lambda row: convert_to_eur(row['date'], row['amount'], row['currency']), axis=1)
pp.drop('currency', axis='columns')
- if not args.merge:
- be.to_csv(output_dir + 'Banca Etica.csv', index=False, date_format='%Y-%m-%d')
- pp.to_csv(output_dir + 'PayPal.csv', index=False, date_format='%Y-%m-%d')
+ if not args.merge and not args.dry_run:
+ be.to_csv(os.path.join(output_dir, 'Banca Etica.csv'), index=False, date_format='%Y-%m-%d')
+ pp.to_csv(os.path.join(output_dir, 'PayPal.csv'), index=False, date_format='%Y-%m-%d')
all = pd.concat([be, pp], axis=0, ignore_index=True)
all = all.sort_values(by='date', ascending=False)
- if args.merge:
- all.to_csv(output_dir + 'BEPP.csv', index=False, date_format='%Y-%m-%d')
+ if args.merge and not args.dry_run:
+ all.to_csv(os.path.join(output_dir, 'BEPP.csv'), index=False, date_format='%Y-%m-%d')
- if args.backup:
- be_merged.to_csv(output_dir + 'Banca Etica - original.csv', index=False)
- pp_merged.to_csv(output_dir + 'Pay Pal - original.csv', index=False)
+ if args.backup and not args.dry_run:
+ be_merged.to_csv(os.path.join(output_dir, 'Banca Etica - original.csv'), index=False)
+ pp_merged.to_csv(os.path.join(output_dir, 'Pay Pal - original.csv'), index=False)
if __name__ == '__main__':
main()