diff --git a/.gitignore b/.gitignore index b6e4761..f347387 100644 --- a/.gitignore +++ b/.gitignore @@ -127,3 +127,5 @@ dmypy.json # Pyre type checker .pyre/ + +.vscode/ \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..cf927b5 --- /dev/null +++ b/README.md @@ -0,0 +1,70 @@ +# ICS to CSV + +Ce script permet de convertir un fichier ICS (format d'export du calendrier Google Agenda) en fichier CSV (fichier texte délimité). + +## Utilisation +`python ics_to_csv.py` + +Cela ouvrira une interface graphique (générée par [Gooey](https://github.com/chriskiehl/Gooey)). +![interface](./doc/interface.png) + +### Arguments obligatoires +- Fichier d'entrée : un fichier CSV généré par Google Agenda. +### Arguments optionnels +- Fichier de sortie : le chemin du fichier à sauvegarder. Par défaut : le même nom que le fichier d'entrée, mais avec l'extension CSV. +- Avec lieu uniquement : permet de ne sauvegarder que les enregistrements pour lesquels un lieu a été renseigné. +- Date de début : permet de fitrer les évènements postérieurs à cette date (format `AAAA-MM-JJ`). +- Date de fin : permet de filtrer les évènements antérieurs à cette date (format `AAAA-MM-JJ`) + + +## Utilisation en ligne de commande +Vous pouvez commenter la ligne de décorateur `@Gooey(...)` qui se trouve jsute au dessus de la fonction `main()` et le programme fonctionnera en ligne de commande : +``` +usage: ics_to_csv.py [-h] [--output-file Fichier de sortie] [--location-only] [--from-date Date de début] + [--to-date Date de fin] + Fichier d'entrée + +Script qui transforme un fichier ICS en fichier CSV. + +positional arguments: + Fichier d'entrée chemin du fichier ICS à convertir + +optional arguments: + -h, --help show this help message and exit + --output-file Fichier de sortie, -o Fichier de sortie + chemin du fichier CSV à créer + --location-only, -l si on ne souhaite que les évènements avec un lieu renseigné + --from-date Date de début, -f Date de début + date de début de la période à convertir + --to-date Date de fin, -t Date de fin + date de fin de la période à convertir +``` + + +## Installation +Nécessite Python 3.9+. +- Dans une ligne de commande, allez dans le répertoire de votre choix. +Exemple : +``` +cd c:\Python +``` +- Clonez le repo : +``` +git clone https://github.com/PhunkyBob/ics_to_csv.git +``` +- Allez dans le répertoire qui vient d'être créé : +``` +cd ics_to_csv +``` +- (optionnel, mais conseillé) Créez un environnement virtuel et activez le : +``` +python -m venv venv +.\env\Script\activate +``` +- Installez les librairies nécessaires : +``` +pip install -r requirements.txt +``` + +### Exécutable +Vous pouvez essayer d'utiliser directement [la version binaire](https://github.com/PhunkyBob/ics_to_csv/releases) (expérimental). \ No newline at end of file diff --git a/doc/interface.png b/doc/interface.png new file mode 100644 index 0000000..d387f93 Binary files /dev/null and b/doc/interface.png differ diff --git a/ics_to_csv.ico b/ics_to_csv.ico new file mode 100644 index 0000000..ea190d3 Binary files /dev/null and b/ics_to_csv.ico differ diff --git a/ics_to_csv.py b/ics_to_csv.py new file mode 100644 index 0000000..3533e4a --- /dev/null +++ b/ics_to_csv.py @@ -0,0 +1,139 @@ +# -*- coding: utf-8 -*- +""" +Ce script permet de convertir un fichier ICS en un fichier CSV, tout en filtrant les évènements +- selon une date de début et une date de fin +- selon la présence d'un lieu +""" +import re +import csv +from datetime import datetime +import argparse +import os +from gooey import Gooey, GooeyParser +import codecs +import sys + +VERSION = "1.0" + +def valid_date(s): + """ Fonction de validation de date : soit vide, soit au format YYYY-MM-DD. """ + if not s: + return None + try: + return datetime.strptime(s, "%Y-%m-%d") + except ValueError: + msg = "ce n'est pas une date valide: {0!r}".format(s) + raise argparse.ArgumentTypeError(msg) + +@Gooey(program_name=f"ICS to CSV {VERSION}", default_size=(700, 600), language='french', image_dir='images') +def main(): + # Patch parce que Gooey ne sait pas gérer les entrées / sorties utf-8. + if sys.stdout.encoding != 'UTF-8': + sys.stdout = codecs.getwriter('utf-8')(sys.stdout.buffer, 'strict') + if sys.stderr.encoding != 'UTF-8': + sys.stderr = codecs.getwriter('utf-8')(sys.stderr.buffer, 'strict') + + # Définition des arguments de la ligne de commande par Gooey. + parser = GooeyParser( + description="""Script qui transforme un fichier ICS en fichier CSV.""" + ) + parser.add_argument( + "from_file", + type=str, + metavar="Fichier d'entrée", + help="chemin du fichier ICS à convertir", + widget='FileChooser', + ) + parser.add_argument( + "--output-file", + "-o", + default="", + # nargs='?', + type=str, + metavar="Fichier de sortie", + help="chemin du fichier CSV à créer", + widget='FileSaver', + ) + parser.add_argument( + "--location-only", + "-l", + default=False, + action='store_true', + metavar="Avec lieu uniquement", + help="si on ne souhaite que les évènements avec un lieu renseigné", + ) + parser.add_argument( + "--from-date", + "-f", + default=None, + type=valid_date, + metavar="Date de début", + help="date de début de la période à convertir", + widget='DateChooser', + ) + parser.add_argument( + "--to-date", + "-t", + default=None, + type=valid_date, + metavar="Date de fin", + help="date de fin de la période à convertir", + widget='DateChooser', + ) + + args = parser.parse_args() + from_file = args.from_file + to_file = args.output_file + from_date = args.from_date + to_date = args.to_date + location_only = args.location_only + + if not to_file: + to_file = from_file.replace(".ics", ".csv") + + # Vérification de l'existance du fichier ICS. + if not os.path.isfile(from_file): + print(f"ERREUR : Impossible de trouver le fichier \"{from_file}\".") + exit(1) + + with open(from_file, 'r', encoding='utf-8') as f: + lines = f.readlines() + + # On récupère les évènements. + res = re.findall(r"BEGIN:VEVENT(.+?)END:VEVENT", "".join(lines), re.DOTALL) + to_csv = [] + for elem in res: + elem = elem.replace("\n ", "") + current_item = {item.split(':')[0].split(";")[0]: item.split(':')[-1] for item in elem.split("\n") if item} + if 'DTSTART' in current_item: + # On transforme la date en datetime. + if re.match(r"^\d{8}$", current_item['DTSTART']): + start_date = datetime.strptime(current_item['DTSTART'], "%Y%m%d") + if re.match(r"^\d{8}T\d{6}Z$", current_item['DTSTART']): + start_date = datetime.strptime(current_item['DTSTART'], "%Y%m%dT%H%M%SZ") + # On stocke en plus la date au format "YYYY-MM-DD HH:mm:SS". + current_item['DTSTART_2'] = start_date + # On vérifier que la date est dans la période souhaitée et qu'il y a un lieu de défini. + if (not from_date or start_date >= from_date) and (not to_date or start_date <= to_date): + if not location_only or ('LOCATION' in current_item and current_item['LOCATION'].strip()): + to_csv.append(current_item) + + if not to_csv: + print("Aucun évènement à convertir.") + exit(0) + + # On récupère toutes les clés. + keys = to_csv[0].keys() + for line in to_csv: + keys |= line.keys() + + # On écrit le fichier CSV. + with open(to_file, 'w', encoding='utf-8', newline='') as output_file: + dict_writer = csv.DictWriter(output_file, keys) + dict_writer.writeheader() + dict_writer.writerows(to_csv) + print(f"Fichier \"{to_file}\" créé avec succès ({len(to_csv)} éléments).") + +if __name__ == '__main__': + main() + \ No newline at end of file diff --git a/images/config_icon.png b/images/config_icon.png new file mode 100644 index 0000000..503355d Binary files /dev/null and b/images/config_icon.png differ diff --git a/images/program_icon.png b/images/program_icon.png new file mode 100644 index 0000000..503355d Binary files /dev/null and b/images/program_icon.png differ diff --git a/images/running_icon.png b/images/running_icon.png new file mode 100644 index 0000000..503355d Binary files /dev/null and b/images/running_icon.png differ diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..ce19b66 --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +Gooey==1.0.8.1