236 lines
10 KiB
Markdown
236 lines
10 KiB
Markdown
# Export CalDAV vers CSV
|
|
|
|
Le script [`crealead-caldav.py`](../crealead-caldav.py) récupère des événements
|
|
d'agendas accessibles en CalDAV, notamment Nextcloud, et produit un fichier CSV
|
|
par calendrier configuré. Ces fichiers peuvent ensuite être utilisés par
|
|
`csv-to-timemanager.py`.
|
|
|
|
## Pré-requis
|
|
|
|
- Python 3, avec `pip` et le module `venv`. L'environnement Docker du dépôt
|
|
utilise Python 3.9.2.
|
|
- Les dépendances de [`requirements.txt`](../requirements.txt), notamment
|
|
`caldav`, `icalendar` et `pytz`.
|
|
- Un serveur CalDAV accessible depuis la machine d'exécution et un compte
|
|
autorisé à lire les calendriers à exporter.
|
|
- Des droits de lecture sur `config.json` et d'écriture sur les fichiers CSV
|
|
ainsi que sur le dossier `logs/`, situé à côté du script.
|
|
- Bash pour utiliser le lanceur `exec-export-caldav.sh`.
|
|
|
|
Depuis la racine du dépôt, préparer l'environnement :
|
|
|
|
```bash
|
|
python3 -m venv env
|
|
source env/bin/activate
|
|
python -m pip install -r requirements.txt
|
|
```
|
|
|
|
L'environnement Docker fourni constitue une autre façon d'exécuter le script
|
|
et les tests. Sa construction est décrite dans
|
|
[`support/docker-dev/README.md`](../support/docker-dev/README.md).
|
|
|
|
## Usage
|
|
|
|
### Configuration minimale
|
|
|
|
Créer un fichier `config.json` à la racine du dépôt. Exemple pour exporter les
|
|
événements d'octobre 2026 :
|
|
|
|
```json
|
|
{
|
|
"caldav_url": "https://cloud.example.com/remote.php/dav/",
|
|
"username": "utilisateur",
|
|
"password": "mot-de-passe-application",
|
|
"year_start": 2026,
|
|
"month_start": 10,
|
|
"day_start": 1,
|
|
"year_end": 2026,
|
|
"month_end": 11,
|
|
"day_end": 1,
|
|
"calendars": [
|
|
{
|
|
"calendar_name": "Temps de travail",
|
|
"csv_name": "csv/utilisateur.csv"
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
| Champ | Rôle |
|
|
| --- | --- |
|
|
| `caldav_url` | URL du serveur CalDAV. |
|
|
| `username`, `password` | Identifiants du compte qui peut lire les calendriers. |
|
|
| `year_start`, `month_start`, `day_start` | Date de début de la recherche, à minuit. Les valeurs sont des nombres entiers. |
|
|
| `year_end`, `month_end`, `day_end` | Date de fin de la recherche, à minuit. Pour couvrir un mois, utiliser le premier jour du mois suivant. |
|
|
| `calendars` | Liste des calendriers à exporter. |
|
|
| `calendars[].calendar_name` | Texte contenu dans le nom du calendrier distant, avec respect de la casse. Il doit correspondre à un seul calendrier. |
|
|
| `calendars[].csv_name` | Chemin du fichier CSV à écrire. Un chemin relatif est résolu depuis le répertoire de lancement. |
|
|
|
|
Ajouter une entrée dans `calendars` pour chaque calendrier à exporter.
|
|
Les champs `db_*`, `user_name` et `api_password`, utilisés par d'autres scripts
|
|
du dépôt, ne sont pas nécessaires à cet export.
|
|
|
|
`config.json` est ignoré par Git. Remplacer les valeurs d'exemple par les
|
|
informations du serveur et du compte utilisés.
|
|
|
|
### Lancement
|
|
|
|
Lancer les commandes depuis la racine du dépôt : le script lit
|
|
`./config.json`, et le lanceur recherche `env/bin/activate` dans le répertoire
|
|
courant. Créer au préalable les dossiers de destination des CSV ; le script
|
|
ne les crée pas.
|
|
|
|
```bash
|
|
mkdir -p csv
|
|
bash ./exec-export-caldav.sh
|
|
```
|
|
|
|
Le lanceur active l'environnement `env/`, puis exécute le script. Il est aussi
|
|
possible de lancer directement Python :
|
|
|
|
```bash
|
|
env/bin/python crealead-caldav.py
|
|
```
|
|
|
|
Le script ne propose pas d'arguments en ligne de commande. La période et les
|
|
calendriers sont définis dans `config.json`.
|
|
|
|
### Fichiers produits et traitement des événements
|
|
|
|
Chaque CSV existant est remplacé lors de l'export. Les lignes sont triées par
|
|
date et heure de début. Le séparateur est `;` et les colonnes sont :
|
|
|
|
```text
|
|
Titre;Date debut;Heure debut;Date fin;Heure fin;Categories;Emplacement;Description
|
|
```
|
|
|
|
Les événements récurrents sont demandés au serveur avec `expand=True`.
|
|
Le lecteur iCalendar prend en charge les paramètres des propriétés, comme
|
|
`TZID` et `VALUE=DATE`, ainsi que les textes répartis sur plusieurs lignes.
|
|
Les heures sont converties vers `Europe/Paris`. Une heure sans fuseau est
|
|
interprétée dans ce fuseau ; une date seule est exportée à minuit.
|
|
Si `DTEND` est absent, `DURATION` peut fournir la date de fin.
|
|
|
|
Les événements sans propriété `SUMMARY` sont ignorés. Ceux dont les dates
|
|
de début ou de fin sont absentes ou invalides sont également ignorés. Chaque
|
|
exclusion laisse un avertissement détaillé dans le journal, y compris en
|
|
l'absence de `SUMMARY`. Un document iCalendar illisible ou une ressource sans
|
|
composant `VEVENT` est signalé puis ignoré. Les autres événements restent
|
|
exportés. Si un calendrier configuré est introuvable ou correspond à plusieurs
|
|
calendriers distants, le script affiche un message et s'arrête avant l'écriture
|
|
des CSV.
|
|
|
|
En cas d'erreur d'écriture d'un CSV, le script s'arrête avec une exception.
|
|
Il laisse un avertissement pour chaque événement du calendrier en cours,
|
|
dont le fichier peut être incomplet, et des calendriers restant à écrire.
|
|
Les fichiers déjà écrits avec succès sont conservés.
|
|
|
|
### Journalisation
|
|
|
|
Les sorties standard et d'erreur sont affichées dans le terminal et copiées
|
|
dans `logs/crealead-caldav.log`, à côté du script. Le dossier est créé
|
|
automatiquement. Le journal utilise UTF-8, conserve les exécutions précédentes
|
|
et enregistre les traces d'exception en cas de plantage pendant le traitement.
|
|
Il n'y a pas de rotation automatique du journal. `logs/` est ignoré par Git.
|
|
|
|
Chaque nouvelle ligne du journal est préfixée par l'heure d'écriture en UTC,
|
|
au format ISO 8601 `YYYY-MM-DDTHH:MM:SSZ`, puis par `[info]` :
|
|
|
|
```text
|
|
[2026-10-15T12:34:56Z]: [info] - csv/utilisateur.csv ...
|
|
```
|
|
|
|
Cela s'applique aussi aux messages écrits en plusieurs fragments et à chaque
|
|
ligne d'une trace d'exception. L'horodatage est ajouté dans le fichier de
|
|
journal, sans modifier les messages ordinaires du terminal.
|
|
|
|
Tout événement non exporté est signalé avec `[warning][event]`, à la place de
|
|
`[info]`. Le message indique le calendrier distant (ou son nom configuré),
|
|
le fichier CSV, le titre, le début, la fin, l'UID et la raison de l'exclusion :
|
|
|
|
```text
|
|
[2026-10-15T12:34:56Z]: [warning][event] - Calendrier='Temps de travail' ; CSV='csv/utilisateur.csv' ; titre='Réunion' ; début='2026-10-15T08:00:00Z' ; fin='indisponible' ; UID='reunion-1' ; événement non exporté : dates de début/fin absentes ou invalides (date absente ou invalide)
|
|
```
|
|
|
|
L'heure entre crochets est celle du message ; les champs `début` et `fin`
|
|
décrivent le moment de l'événement. Les dates et heures décodées sont affichées
|
|
en UTC, les dates de journée entière restent des dates seules. En cas de
|
|
document illisible ou de date invalide, le script récupère les champs bruts
|
|
disponibles. Une métadonnée absente ou irrécupérable vaut `indisponible`.
|
|
Les retours à la ligne dans les métadonnées et le motif sont échappés pour
|
|
garder un avertissement sur une seule ligne. Ces avertissements sont aussi
|
|
visibles dans le terminal.
|
|
|
|
## Tests
|
|
|
|
Les tests se trouvent dans
|
|
[`tests/test_crealead_caldav.py`](../tests/test_crealead_caldav.py) et utilisent
|
|
`unittest`, fourni avec Python. Aucune dépendance de test supplémentaire
|
|
n'est nécessaire.
|
|
|
|
### Cas couverts
|
|
|
|
Les 13 tests de `ExportEventsTests` vérifient :
|
|
|
|
- La lecture de `DTSTART` et `DTEND` avec `TZID`, leur conversion vers Paris
|
|
et le tri chronologique des lignes.
|
|
- La conversion des dates UTC en été et en hiver, ainsi que celle d'un autre
|
|
fuseau horaire.
|
|
- Les événements sur une journée entière (`VALUE=DATE`), les heures sans
|
|
fuseau et le calcul de la fin à partir de `DURATION`.
|
|
- L'exclusion des événements sans propriété `SUMMARY`, des dates manquantes
|
|
ou invalides et des documents iCalendar illisibles, sans bloquer les
|
|
événements valides, ainsi que la présence d'un avertissement contextualisé
|
|
dans le journal pour chaque événement écarté.
|
|
- La récupération des métadonnées de chaque événement dans un document
|
|
illisible, la conservation d'une date brute invalide et le signalement
|
|
d'une ressource sans composant `VEVENT`.
|
|
- La conservation des deux-points dans les textes, des paramètres de texte,
|
|
des catégories et des lignes repliées, ainsi que le nettoyage de la
|
|
description HTML encodée et le contenu du CSV écrit.
|
|
|
|
Les 9 tests de `LogOutputTests` vérifient :
|
|
|
|
- La création de `logs/` à côté du script, la copie des deux sorties et
|
|
l'écriture immédiate des messages, y compris les accents.
|
|
- L'ajout au journal existant et le choix du nom du journal d'après celui du
|
|
script, y compris lorsqu'une exécution se termine sans retour à la ligne.
|
|
- L'horodatage en UTC, le préfixe `[info]` de chaque ligne, les écritures en
|
|
plusieurs fragments, les messages sur plusieurs lignes et les lignes vides.
|
|
- Le préfixe `[warning][event]` sans `[info]`, les métadonnées de l'événement,
|
|
l'échappement des retours à la ligne et la séparation d'un avertissement
|
|
après un message ordinaire inachevé.
|
|
- La sauvegarde des traces d'exception et la restauration des sorties après
|
|
une erreur.
|
|
- La conservation du code de sortie d'un `SystemExit`, sans ajouter de trace
|
|
d'exception, et la sauvegarde de son éventuel message.
|
|
|
|
Le test de `ExportFailureTests` provoque une erreur d'écriture CSV. Il vérifie
|
|
qu'un avertissement est enregistré pour les événements du calendrier en
|
|
échec et des calendriers restant à écrire, et que le premier CSV déjà écrit
|
|
est conservé.
|
|
|
|
Ces tests utilisent le vrai lecteur `icalendar`, des événements synthétiques
|
|
et des dossiers temporaires pour les CSV et les journaux. Le module CalDAV
|
|
et les réponses du serveur sont simulés : aucun accès réseau, compte,
|
|
`config.json` ou calendrier réel n'est nécessaire. Ils vérifient que la
|
|
recherche demande `expand=True`, mais ne valident pas l'expansion des
|
|
récurrences par un serveur réel ni l'import dans TimeManager.
|
|
|
|
### Exécution
|
|
|
|
Depuis la racine du dépôt, avec les dépendances installées dans `env/` :
|
|
|
|
```bash
|
|
env/bin/python -B -m unittest discover -s tests -v
|
|
```
|
|
|
|
L'option `-B` évite de créer des fichiers de cache Python. La suite contient
|
|
actuellement 23 tests ; une exécution réussie se termine par `OK`.
|
|
|
|
Avec l'image Docker du dépôt déjà construite :
|
|
|
|
```bash
|
|
docker run --rm -v "$(pwd)":/app -w /app cal-to-time-manager:dev -B -m unittest discover -s tests -v
|
|
```
|