initial
This commit is contained in:
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
/data/
|
||||
/config/
|
||||
/MOP_v9-9-9.zip
|
||||
.idea/
|
||||
9
requirements.txt
Normal file
9
requirements.txt
Normal file
@@ -0,0 +1,9 @@
|
||||
altgraph==0.17.2
|
||||
future==0.18.2
|
||||
invoke==1.6.0
|
||||
pefile==2021.9.3
|
||||
pyinstaller==4.7
|
||||
pyinstaller-hooks-contrib==2021.4
|
||||
pywin32-ctypes==0.2.0
|
||||
ruamel.yaml==0.17.20
|
||||
ruamel.yaml.clib==0.2.6
|
||||
62
src/data_exporter.py
Normal file
62
src/data_exporter.py
Normal file
@@ -0,0 +1,62 @@
|
||||
"""
|
||||
"""
|
||||
|
||||
# --------------------------------------- #
|
||||
# imports #
|
||||
# --------------------------------------- #
|
||||
from csv import DictWriter, QUOTE_NONNUMERIC
|
||||
|
||||
|
||||
# --------------------------------------- #
|
||||
# definitions #
|
||||
# --------------------------------------- #
|
||||
|
||||
|
||||
# --------------------------------------- #
|
||||
# global vars #
|
||||
# --------------------------------------- #
|
||||
|
||||
|
||||
# --------------------------------------- #
|
||||
# functions #
|
||||
# --------------------------------------- #
|
||||
def _get_all_columns_from_orders(order_data):
|
||||
columns = []
|
||||
|
||||
for buyer, orders in order_data.items():
|
||||
for order_data, order in orders.items():
|
||||
for order_key, order_value in order.items():
|
||||
if order_key not in columns:
|
||||
columns.append(order_key)
|
||||
|
||||
return columns
|
||||
|
||||
|
||||
def _fill_order_with_missing_columns(order, required_columns):
|
||||
for required_column in required_columns:
|
||||
if required_column not in order:
|
||||
order[required_column] = ""
|
||||
|
||||
return order
|
||||
|
||||
|
||||
def export_order_to_csv(order_data, export_file_path):
|
||||
seen_columns = _get_all_columns_from_orders(order_data)
|
||||
|
||||
with open(export_file_path, 'w', newline='\n') as csvfile:
|
||||
writer = DictWriter(csvfile, fieldnames=seen_columns, delimiter=";")
|
||||
writer.writeheader()
|
||||
|
||||
for buyer, orders in order_data.items():
|
||||
for order_data, order in orders.items():
|
||||
writer.writerow(_fill_order_with_missing_columns(order, seen_columns))
|
||||
|
||||
|
||||
# --------------------------------------- #
|
||||
# classes #
|
||||
# --------------------------------------- #
|
||||
|
||||
|
||||
# --------------------------------------- #
|
||||
# main #
|
||||
# --------------------------------------- #
|
||||
64
src/mail_parser.py
Normal file
64
src/mail_parser.py
Normal file
@@ -0,0 +1,64 @@
|
||||
"""
|
||||
"""
|
||||
|
||||
# --------------------------------------- #
|
||||
# imports #
|
||||
# --------------------------------------- #
|
||||
import re
|
||||
|
||||
|
||||
# --------------------------------------- #
|
||||
# definitions #
|
||||
# --------------------------------------- #
|
||||
START_LINE = "-------------------------------------\n"
|
||||
ADDITIONAL_SECTION = "Sonstiges (nach Verfügbarkeit)"
|
||||
PHONE_NUMBER_SECTION = "Telefonnummer"
|
||||
|
||||
|
||||
# --------------------------------------- #
|
||||
# global vars #
|
||||
# --------------------------------------- #
|
||||
|
||||
|
||||
# --------------------------------------- #
|
||||
# functions #
|
||||
# --------------------------------------- #
|
||||
def parse_mail_and_return_order(order_lines):
|
||||
order = {}
|
||||
start_found = False
|
||||
|
||||
for line in order_lines:
|
||||
if start_found:
|
||||
if line != "\n":
|
||||
split_line = line.split(":")
|
||||
|
||||
if len(split_line) == 2:
|
||||
key = split_line[0]
|
||||
value = split_line[1].replace("\n", "")
|
||||
elif "Datum/Uhrzeit" in split_line[0]:
|
||||
key = "Datum/Uhrzeit"
|
||||
value = re.search(r'(?<=Uhrzeit: ).*', line)[0]
|
||||
|
||||
if key != ADDITIONAL_SECTION:
|
||||
if key == PHONE_NUMBER_SECTION:
|
||||
order[key] = '"{}"'.format(value.strip())
|
||||
else:
|
||||
order[key] = value.strip()
|
||||
else:
|
||||
if value != "":
|
||||
for option in re.sub(r'\([^)]*\)', '', value).split(","):
|
||||
order["Sonstiges {}".format(option)] = "ja"
|
||||
else:
|
||||
if line == START_LINE:
|
||||
start_found = True
|
||||
|
||||
return order
|
||||
|
||||
# --------------------------------------- #
|
||||
# classes #
|
||||
# --------------------------------------- #
|
||||
|
||||
|
||||
# --------------------------------------- #
|
||||
# main #
|
||||
# --------------------------------------- #
|
||||
77
src/start_app.py
Normal file
77
src/start_app.py
Normal file
@@ -0,0 +1,77 @@
|
||||
"""
|
||||
"""
|
||||
|
||||
# --------------------------------------- #
|
||||
# imports #
|
||||
# --------------------------------------- #
|
||||
import os
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from ruamel.yaml import YAML
|
||||
|
||||
from data_exporter import export_order_to_csv
|
||||
from mail_parser import parse_mail_and_return_order
|
||||
|
||||
# --------------------------------------- #
|
||||
# definitions #
|
||||
# --------------------------------------- #
|
||||
ORDER_FILE = "../config/existing_orders.yml"
|
||||
DATA_IMPORT_DIRECTORY = "../data/"
|
||||
EXPORT_FILE = "../data/Bestellungen.csv"
|
||||
|
||||
# --------------------------------------- #
|
||||
# global vars #
|
||||
# --------------------------------------- #
|
||||
|
||||
|
||||
# --------------------------------------- #
|
||||
# functions #
|
||||
# --------------------------------------- #
|
||||
def _check_if_directory_exists_for_correct_run():
|
||||
if not os.path.exists("../data/"):
|
||||
os.mkdir("../data/")
|
||||
|
||||
if not os.path.exists("../config/"):
|
||||
os.mkdir("../config/")
|
||||
|
||||
# --------------------------------------- #
|
||||
# classes #
|
||||
# --------------------------------------- #
|
||||
|
||||
|
||||
# --------------------------------------- #
|
||||
# main #
|
||||
# --------------------------------------- #
|
||||
if __name__ == "__main__":
|
||||
_check_if_directory_exists_for_correct_run()
|
||||
|
||||
if os.path.exists(ORDER_FILE):
|
||||
order_path = Path(ORDER_FILE)
|
||||
order_data = YAML(typ='safe')
|
||||
order_data.default_flow_style = False
|
||||
existing_orders = order_data.load(order_path)
|
||||
else:
|
||||
existing_orders = {}
|
||||
|
||||
for filename in os.listdir(DATA_IMPORT_DIRECTORY):
|
||||
f = os.path.join(DATA_IMPORT_DIRECTORY, filename)
|
||||
if filename != EXPORT_FILE and filename.endswith(".txt"):
|
||||
if os.path.isfile(f):
|
||||
with open(f, "r", encoding="utf-8") as order_file:
|
||||
parsed_order = parse_mail_and_return_order(order_file.readlines())
|
||||
|
||||
if parsed_order["Name"] in existing_orders:
|
||||
if parsed_order["Datum/Uhrzeit"] in existing_orders[parsed_order["Name"]]:
|
||||
print("Order for {} from {} already added!".format(parsed_order["Name"], parsed_order["Datum/Uhrzeit"]))
|
||||
else:
|
||||
existing_orders[parsed_order["Name"]][parsed_order["Datum/Uhrzeit"]] = parsed_order
|
||||
else:
|
||||
existing_orders[parsed_order["Name"]] = {parsed_order["Datum/Uhrzeit"]: parsed_order}
|
||||
|
||||
export_order_to_csv(existing_orders, EXPORT_FILE)
|
||||
|
||||
with open(ORDER_FILE, "w", encoding="utf-8") as order_file:
|
||||
order_data = YAML(typ='safe')
|
||||
order_data.default_flow_style = False
|
||||
order_data.dump(existing_orders, order_file)
|
||||
40
start_app.spec
Normal file
40
start_app.spec
Normal file
@@ -0,0 +1,40 @@
|
||||
# -*- mode: python ; coding: utf-8 -*-
|
||||
|
||||
|
||||
block_cipher = None
|
||||
|
||||
|
||||
a = Analysis(['src\\start_app.py'],
|
||||
pathex=[],
|
||||
binaries=[],
|
||||
datas=[],
|
||||
hiddenimports=[],
|
||||
hookspath=[],
|
||||
hooksconfig={},
|
||||
runtime_hooks=[],
|
||||
excludes=[],
|
||||
win_no_prefer_redirects=False,
|
||||
win_private_assemblies=False,
|
||||
cipher=block_cipher,
|
||||
noarchive=False)
|
||||
pyz = PYZ(a.pure, a.zipped_data,
|
||||
cipher=block_cipher)
|
||||
|
||||
exe = EXE(pyz,
|
||||
a.scripts,
|
||||
a.binaries,
|
||||
a.zipfiles,
|
||||
a.datas,
|
||||
[],
|
||||
name='MOP',
|
||||
debug=False,
|
||||
bootloader_ignore_signals=False,
|
||||
strip=False,
|
||||
upx=True,
|
||||
upx_exclude=[],
|
||||
runtime_tmpdir=None,
|
||||
console=True,
|
||||
disable_windowed_traceback=False,
|
||||
target_arch=None,
|
||||
codesign_identity=None,
|
||||
entitlements_file=None )
|
||||
73
tasks.py
Normal file
73
tasks.py
Normal file
@@ -0,0 +1,73 @@
|
||||
"""
|
||||
"""
|
||||
|
||||
# --------------------------------------- #
|
||||
# imports #
|
||||
# --------------------------------------- #
|
||||
from invoke import task
|
||||
|
||||
import os
|
||||
import shutil
|
||||
|
||||
# --------------------------------------- #
|
||||
# definitions #
|
||||
# --------------------------------------- #
|
||||
VIRTUALENV_NAME = "py39_MailOrderParser"
|
||||
EXECUTABLE_NAME = "MOP"
|
||||
|
||||
# --------------------------------------- #
|
||||
# global vars #
|
||||
# --------------------------------------- #
|
||||
|
||||
|
||||
# --------------------------------------- #
|
||||
# functions #
|
||||
# --------------------------------------- #
|
||||
def remove_temporary_folders():
|
||||
print("-> remove unused folders")
|
||||
for folder in ["dist", "build", "temp"]:
|
||||
if os.path.exists(folder):
|
||||
print("remove: {}".format(folder))
|
||||
shutil.rmtree(folder)
|
||||
print("finished!")
|
||||
|
||||
|
||||
# --------------------------------------- #
|
||||
# classes #
|
||||
# --------------------------------------- #
|
||||
@task
|
||||
def update_requirements(cmd):
|
||||
with cmd.prefix("workon {}".format(VIRTUALENV_NAME)):
|
||||
cmd.run("pip freeze > requirements.txt")
|
||||
|
||||
|
||||
@task
|
||||
def create_exe(c, version="v9-9-9"):
|
||||
with c.prefix("workon {}".format(VIRTUALENV_NAME)):
|
||||
|
||||
print("---------- START CREATING EXE ----------")
|
||||
remove_temporary_folders()
|
||||
|
||||
print("-> start creating .exe")
|
||||
c.run("pyinstaller start_app.spec")
|
||||
print("finished!")
|
||||
|
||||
print("-> start creating temporary folders and copy files")
|
||||
for folder in ["temp", "temp/apps"]:
|
||||
os.mkdir(folder)
|
||||
|
||||
shutil.copyfile("dist/{}.exe".format(EXECUTABLE_NAME), "temp/apps/{}.exe".format(EXECUTABLE_NAME))
|
||||
|
||||
print("finished!")
|
||||
print("-> start creating .zip")
|
||||
|
||||
zip_name = EXECUTABLE_NAME + "_" + version
|
||||
|
||||
shutil.make_archive(zip_name, "zip", "temp")
|
||||
print("finished!")
|
||||
remove_temporary_folders()
|
||||
print("---------- FINISHED CREATING EXE ----------")
|
||||
|
||||
# --------------------------------------- #
|
||||
# main #
|
||||
# --------------------------------------- #
|
||||
56
test/data/test_mail.txt
Normal file
56
test/data/test_mail.txt
Normal file
@@ -0,0 +1,56 @@
|
||||
|
||||
|
||||
|
||||
|
||||
Von meinem/meiner Galaxy gesendet
|
||||
|
||||
|
||||
|
||||
-------- Ursprüngliche Nachricht --------
|
||||
Von: Stefan Erne <stefan_erne@gmx.net>
|
||||
Datum: 05.01.22 11:24 (GMT+01:00)
|
||||
An: Dressel Bernhard <Bernhard.Dressel@getzner.com>
|
||||
Betreff: Fwd: Nachricht über https://haus-gruenerwald-net.jimdofree.com/bio-bauernhof/
|
||||
|
||||
|
||||
Von meinem iPhone gesendet
|
||||
|
||||
Anfang der weitergeleiteten Nachricht:
|
||||
Von: haus-gruenerwald@gmx.net
|
||||
Datum: 5. Jänner 2022 um 11:22:21 MEZ
|
||||
An: stefan_erne@gmx.net
|
||||
Betreff: WG: Nachricht über https://haus-gruenerwald-net.jimdofree.com/bio-bauernhof/
|
||||
|
||||
|
||||
-----Ursprüngliche Nachricht-----
|
||||
Von: Jimdo <no-reply@jimdo.de>
|
||||
Gesendet: Mittwoch, 5. Januar 2022 11:18
|
||||
An: haus-gruenerwald@gmx.net
|
||||
Betreff: Nachricht über https://haus-gruenerwald-net.jimdofree.com/bio-bauernhof/
|
||||
|
||||
Hallo, du hast eine Nachricht über deine Jimdo-Seite https://haus-gruenerwald-net.jimdofree.com/bio-bauernhof/ erhalten:
|
||||
|
||||
-------------------------------------
|
||||
|
||||
Rasse: BIO-Angus
|
||||
|
||||
Paketgröße: 5 kg
|
||||
|
||||
Vakuumiert: ja
|
||||
|
||||
Option: Suppenfleisch
|
||||
|
||||
Verpackungsgröße pro Beutel: 500 g
|
||||
|
||||
Sonstiges (nach Verfügbarkeit): Backen (EUR 20,-/kg), Knochen (gratis)
|
||||
|
||||
Name: Bernhard Dresseo
|
||||
|
||||
Telefonnummer: +436644073617
|
||||
|
||||
eMail-Adresse: bernhard.dressel@gmail.com
|
||||
|
||||
Nachricht:
|
||||
|
||||
Nutzer hat die Datenschutzerklärung akzeptiert. Datum/Uhrzeit: 2022-01-05 11:17:44 CET
|
||||
|
||||
41
test/test_mail_parser.py
Normal file
41
test/test_mail_parser.py
Normal file
@@ -0,0 +1,41 @@
|
||||
"""
|
||||
"""
|
||||
|
||||
# --------------------------------------- #
|
||||
# imports #
|
||||
# --------------------------------------- #
|
||||
from unittest import TestCase
|
||||
|
||||
from src.mail_parser import parse_mail_and_return_order
|
||||
|
||||
# --------------------------------------- #
|
||||
# definitions #
|
||||
# --------------------------------------- #
|
||||
|
||||
|
||||
# --------------------------------------- #
|
||||
# global vars #
|
||||
# --------------------------------------- #
|
||||
|
||||
|
||||
# --------------------------------------- #
|
||||
# functions #
|
||||
# --------------------------------------- #
|
||||
|
||||
|
||||
# --------------------------------------- #
|
||||
# classes #
|
||||
# --------------------------------------- #
|
||||
|
||||
|
||||
# --------------------------------------- #
|
||||
# main #
|
||||
# --------------------------------------- #
|
||||
|
||||
|
||||
class TestMailParser(TestCase):
|
||||
def test_parse_mail_and_return_order(self):
|
||||
with open("data/test_mail.txt", "r", encoding="utf-8") as test_mail:
|
||||
test_order_data = parse_mail_and_return_order(test_mail.readlines())
|
||||
|
||||
self.assertEqual("Bernhard Dresseo", test_order_data["Name"])
|
||||
Reference in New Issue
Block a user