\pagebreak
# Projet ADSILLH 2019/2020 - Rapport de fin de projet
Ce projet rend compte du travail effectué au cours de l'année 2019/2020
concernant le logiciel Khal.
## L'équipe
Notre équipe comptabilise cinq personnes:
- Axel Danguin
- Maxime Oçafrain
- Yorick Barbanneau
- Florian Lassenay
- Simon Crespeau
### Organisation git
Nous avons commencé par créer une organisation sur Github:
[Khalzone](https://github.com/khalzone), ou chacun de nous a eut les droits d'administration. Nous y avons créé un nouvel embranchement du projet **Khal** que nous avons tous clôné en local.
À partir de cette organisation nous pouvons créer et travailler sur nos propres branches. En règle générale nous créons des branche de `fix` pour répondre aux différentes `issues` que nous nommons sous la forme `fix/<numero_issue>` par exemple `fix/805`
Lorque nous voulons faire des demandes d'intégration (ou *merge requests*),
nous le faisons via **Khalzone**.
#### Documentation
Nous avons également créé un [wiki](https://github.com/khalzone/khal/wiki)
sur notre dépôt. Il permet à chacun de documenter ses avancées et de
centraliser les informations lorsque l'un d'entre nous a besoin de s'y
référer.
### Échanges
Nous avons l'habitude de communiquer par mail et en messagerie instantanée
via un canal Discord. Nous sommes aussi présent sur le canal IRC de pimutils sur le serveur Freenode.
## Khal
**Khal** est un logiciel faisant partie de [pimutils](https://pimutils.org),
une petite organisation regroupant actuellement quatre programmeurs et
mainteneurs de projets voués à la gestion d'information personnelle telle
que les contacts, les agendas, les todolists...
Les programmes faisant partie de **pimutils** n'ont pas les mêmes buts,
mais ont été pensé pour être chacun la pièce d'un écosystème laissant
la maîtrise à l'utilisateur sur ses informations.
### Quelques chiffres
**Khal**, c'est:
- [https://lostpackets.de/khal](https://lostpackets.de/khal)
- [https://github.com/pimutils/khal](https://github.com/pimutils/khal)
- Création du projet en **août 2014**
- Un peu plus de **2000 commits**
- **58 contributeurs**
**geier**, mainteneur principal du projet, est un neurophysicien (donc
pas développeur professionnel) et travaille sur le logiciel sur son temps libre.
**Khal** n'est donc pas un très gros projet. Il est cependant développé et
maintenu avec sérieux, rassemblant une communauté restreinte mais active
et fidèle. **Khal** se voit de surcroît être implémenté dans divers
modules pour d'autres logiciels tels que des gestionnaires de bureau ou
barres de tâches, développés par la communauté.
### Autour du logiciel...

**Khal** est capable de se synchroniser avec des serveurs CalDAV au travers de
[**Vdirsyncer**](https://github.com/pimutils/vdirsyncer).
#### Vdirsyncer
On voit que **vdirsyncer** stocke les informations synchronisées dans des
dossiers locaux, au format `.ics` pour les calendiers, dans lesquels nous
allons pouvoir interagir grâce à **khal**.
**vdirsyncer** n'est donc pas indispensable à l'utilisation de
**khal**. Cependant il offre un moyen extrêmement simple de synchroniser
ses agendas avec un serveur distant.
#### urwid
**urwid** est une bibliothèque permettant des rendus rapides d'interface.
Sa gestion des couleurs est relativement interessante et pour l'instant ikhal -qui en a la capacité- n'exploite pas les rendus avec des codes 256 couleurs voire en code type html 24 bits.
### Pourquoi avoir choisi ce projet ?
Étant un groupe plutôt disparate en terme d'expérience, et ce dans divers
domaines (programmation python, communauté du logiciel libre, travail
en équipe, ...), **Khal** s'est présenté comme le terrain idéal pour
découvrir ensemble et s'aider mutuellement sur ces sujets, tout en évitant
de s'imposer un projet trop conséquent.
Ce choix s'est avéré judicieux car il nous a permis en quelques
semaines de découvrir et travailler sur des sujets tout à fait nouveaux pour
chacun d'entre nous (tests unitaires, git, intégration continue, python, ...).
Le choix du langage `python` a été aussi décisif car il constitue une
part importante de notre travail au sein de la licence.
### Fonctionnalités du logiciel
**Khal** est capable de:
- Lire et écrire des évènements et des Icalendars dans un dossier.
- Ajouter, modifier et supprimer simplement des évènements, en ligne de
commande et TUI.
**Khal** dépend de la version 3.4 ou supérieure de `python`. Il est
accessible sur la majorité des systèmes d'exploitation à l'exception de
Microsoft Windows.
Les évènements sont enregistrés dans une base de données **SQLite** afin
d'optimiser les traitements.
#### Les tests
**Khal** est "livré" avec un ensemble de tests, destinés à vérifier le
bon fonctionnement des différents objets et fonctions du logiciel. Les tests
unitaires sont réaliés avec [`pytest`][l_pytest] tandis que les tests de
compatibilité sont réalisés avec [`tox`][l_tox].
[l_pytest]:https://pytest.org/en/latest/
[l_tox]:https://tox.readthedocs.io/en/latest/
### Exemple
Très simplement, voici comment se déroule un ajout d'évènement dans **khal**
ainsi que l'affichage de ce dernier dans le terminal.
```bash
$ khal new -a adsillh 09/12/2019 14:00 18:00 Rendez-vous Projet
$ khal calendar
lu ma me je ve sa di lundi, 09/12/
déc. 25 26 27 28 29 30 1 14:00-18:00 Rendez-vous Projet
2 3 4 5 6 7 8 18:00-19:30 Sport
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
```
### Architecture du code

Lors de l'exécution du logiciel, **khal** commence par charger la configuration de l'utilisateur.
La génération de l'objet représentant la configuration est gérée via la
bibliothèque python **configobj**.
Ensuite, afin d'exécuter la commande entrée par l'utilisateur, **khal** utilise
ses fonctions principales, ses classes d'objet et la configuration générée à partir du fichier `config`.
Enfin, l'interaction avec l'utilisateur est gérée dans le terminal. Via des
`print` ou de façon plus intéractive via la bibliothèque python **urwid**.
## Nos contributions
Dans le reste du document, nous pourrons utiliser le mot anglais
**issue**. C'est un terme générique qui n'a pas de traduction équivalente
en français mais qui peut regrouper diverses formes de retours d'expériences
faits par des utilisateurs à la communauté autour du logiciel. (Rapports
de bugs, demandes d'ajout de fonctionnalité,...)
#### Légende des contributions
| Code fonctionnel | Passe les tests | Demande d'intégration effectuée | Demande Acceptée! |
|:----------------:|:---------------:|:-------------------------------:|:-----------------:|
|{width=20px}|{width=20px}|{width=20px}|{width=20px}|
#### Liste des contributions étudiées
| Issue | Objet | Avancée |
|-------|-------|---------|
| #805 | Durée d'évènement par défaut | {width=20px}{width=20px}{width=20px}{width=20px}|
| #805b | Durée d'évènement par défaut, différente pour chaque agenda |{width=20px}{width=20px} | [](img/gn_star.svg){width=20px}|
| #809 | Affichage et format de dates dans l'agenda | {width=20px}{width=20px} | [](img/gn_star.svg)|
| #860 | Tests unitaires générant des erreurs alétoires sous ArchLinux | |
| #876 | Erreur "Cannot find timezone" à l'importation d'évènements |{width=20px}{width=20px}{width=20px}{width=20px} |
| #705 | Gestion des couleurs | |
| #866 | Changements_sans_quitter_ikhal | |
[l_805]:https://github.com/pimutils/khal/issues/805
[l_809]:https://github.com/pimutils/khal/issues/809
[l_860]:https://github.com/pimutils/khal/issues/860
[l_876]:https://github.com/pimutils/khal/issues/876
[l_395]:https://github.com/pimutils/khal/issues/395
[l_633]:https://github.com/pimutils/khal/issues/633
[l_705]:https://github.com/pimutils/khal/issues/705
[w_805]:https://github.com/khalzone/khal/wiki/issue805
[w_805b]:https://github.com/khalzone/khal/wiki/issue805b
[w_809]:https://github.com/khalzone/khal/wiki/Issue-809
[w_860]:https://github.com/khalzone/khal/wiki/issue860
[w_876]:https://github.com/khalzone/khal/wiki/issue876
[w_395-633]:https://github.com/Khalzone/khal/wiki/issue-395-&-633
[w_705]:https://github.com/khalzone/khal/wiki/issue-705
### Issue 805: Durée d'un évènement par défaut
- [Issue][l_805]
- [Wiki][w_805]
Nous avons commencé par travailler tous ensemble sur une [demande
d'ajout](https://github.com/pimutils/khal/issues/805) de fonctionnalité:
définir des durées d'évènement par défaut.
#### Situation de départ
:

Il existe deux types d'évènements dans khal (mais c'est aussi le cas pour
de nombreux autres systèmes de gestion d'évènements par agenda):
| Type d'évènement | Type de la variable représentant le début et la fin | Valeur par défaut |
|------------------|-----------------------------------------------------|-------------------|
| Avec horaires | `datetime` | 1 heure |
| Sans horaire (seulement des jours) | `date` | 1 jour |
L'idée est de fournir aux utilisateurs un moyen de régler eux-mêmes
cette valeur, via le fichier de configuration de **khal**, car comme indiqué sur
le schéma, la valeur par défaut est inscrite "en dur" dans le code source.
```python
# khal/controllers.py
# Nouvel évènement en ligne de commande
def new_from_string(collection, calendar_name, conf, info, location=None,
format=None, env=None):
"""construct a new event from a string and add it"""
info = parse_datetime.eventinfofstr(
info, conf['locale'], adjust_reasonably=True, localize=False,
)
```
```python
# khal/parse_datetime.py
def eventinfofstr(info_string, locale, adjust_reasonably=False, localize=False):
"""parses a string of the form START [END | DELTA] [TIMEZONE] [SUMMARY] [::
DESCRIPTION] into a dictionary with keys: dtstart, dtend, timezone, allday,
summary, description
"""
[...]
start, end, allday = guessrangefstr(
' '.join(parts[0:i]), locale,
adjust_reasonably=adjust_reasonably,
)
def guessrangefstr(daterange, locale,
# Les valeurs par défaut prennent ces valeurs là
default_timedelta_date=dt.timedelta(days=1),
default_timedelta_datetime=dt.timedelta(hours=1),
adjust_reasonably=False):
"""parses a range string"""
```
#### Modifications
Il a donc fallu faire les modifications nécessaires afin de permettre l'ajout des
deux options dans le fichier de configuration.
Lors de la création d'un nouvel évènement et dans le cas où la durée n'est pas précisée lors de la commande,
Ce sera leur valeur qui sera prise en compte.

```bash
# ~/.config/khal/config
[default]
default_event_duration = '1h30m'
default_day_event_duration = '3d'
```
```python
# khal/controllers.py
# Nouvel évènement en ligne de commande
def new_from_string(collection, calendar_name, conf, info, location=None,
format=None, env=None):
"""construct a new event from a string and add it"""
info = parse_datetime.eventinfofstr(
info, conf['locale'],
# lignes de configuration passées en paramètre
conf['default']['default_dayevent_duration'],
conf['default']['default_event_duration'],
adjust_reasonably=True, localize=False,
)
```
```python
# khal/parse_datetime.py
def eventinfofstr(info_string, locale,
default_timedelta_date,
default_timedelta_datetime,
adjust_reasonably=False, localize=False):
"""parses a string of the form START [END | DELTA] [TIMEZONE] [SUMMARY] [::
DESCRIPTION] into a dictionary with keys: dtstart, dtend, timezone, allday,
summary, description
"""
[...]
start, end, allday = guessrangefstr(
' '.join(parts[0:i]), locale,
adjust_reasonably=adjust_reasonably,
default_timedelta_date=default_timedelta=date,
default_timedelta_datetime=default_timedelta_datetime
)
# Cette fois-ci les valeurs par défaut sont écrasées si de nouvelles sont écrites dans la configuration
def guessrangefstr(daterange, locale,
default_timedelta_date=dt.timedelta(days=1),
default_timedelta_datetime=dt.timedelta(hours=1),
adjust_reasonably=False):
"""parses a range string"""
```
#### État de la modification
La demande de fusion a été validée par le mainteneur principal du projet, le **9 novembre 2019**.
#### Remarques
Nous avons tous travaillé sur cette première implémentation, ce qui nous
a permis de nous connaître, d'échanger et de découvrir le logiciel.
Les modifications qui suivent sont le résultat de tâches que nous nous
sommes attribuées de façon plus personnelle, tout en restant en lien et
en communication globale.
### Issue 805b: Durée par défaut d'un évènement, pour chaque agenda
- [Issue][l_805]
- [Wiki][w_805b]
Pour l'instant, la durée des évènements par défaut est définissable
pour l'ensemble de la configuration. Mais **khal** étant destiné à gérer
plusieurs agendas, il nous semble pertinent de pouvoir définir une durée
par défaut pouvant être différente pour chaque agenda.
#### Modifications
Il a fallu faire en sorte que les définitions de durée par défaut se
fassent désormais dans la partie qui correspond à chaque agenda et non
plus dans les valeurs par défaut du programme.

```bash
# ~/.config/khal/config
[calendars]
[[perso]]
path = ~/.local/share/calendars/perso/
color = dark green
default_event_duration = '2h'
default_dayevent_duration = '3d'
[[adsillh]]
path = ~/.local/share/calendars/adsillh/
color = brown
default_event_duration = '1h30m'
```
Il faut tout d'abord charger les nouvelles valeurs de la configuration dans l'objet construit par khal correspondant à l'ensemble des différents agendas.
```python
# khal/cli.py
def build_collection(conf, selection):
"""build and return a khalendar.Collection from the configuration"""
[...]
if selection is None or name in selection:
props[name] = {
'name': name,
'path': cal['path'],
'readonly': cal['readonly'],
'color': cal['color'],
'priority': cal['priority'],
'ctype': cal['ctype'],
# Deux lignes ajoutées:
'default_dayevent_duration': cal['default_dayevent_duration'],
'default_event_duration': cal['default_event_duration']
}
```
Puis cette fois, la valeur n'est plus à chercher dans la configuration globale mais dans le calendrier sélectionné lors de l'ajout de l'évènement.
```python
# khal/controllers.py
def new_from_string(collection, calendar_name, conf, info, location=None ):
"""construct a new event from a string and add it"""
info = parse_datetime.eventinfofstr(
info, conf['locale'],
conf['calendars'][calendar_name]['default_dayevent_duration'],
conf['calendars'][calendar_name]['default_event_duration'],
adjust_reasonably=True, localize=False):
```
Et enfin, il faut modifier le fichier `khal.spec` en conséquence
```bash
# *calendars* subsection will be used.
type = option('calendar', 'birthdays', 'discover', default='calendar')
# Setting the default date duration adding a new AllDay event
default_duration_date = timedelta(default='1d')
# Setting the default datime duration adding a new event
default_duration_datetime = timedelta(default='1h')
```
#### Écriture des tests
De nouveaux tests relatifs à cette nouvelle implémentation ont été créés.
Il faut désormais tester si les durées d'un nouvel évènement entré en ligne de commande correspondent bien aux durées par défaut entrées dans la configuration.
```python
# tests/configs/default_durations.conf
[calendars]
[[home]]
path = ~/.calendars/home/
color = dark green
priority = 20
default_dayevent_duration = 4
[[work]]
path = ~/.calendars/work/
readonly = True
default_event_duration = 44m
[[sport]]
path = ~/.calendars/sport/
default_dayevent_duration = 2
default_event_duration = 55m 12s
```
```python
# tests/settings_test.py
def test_default_durations(self):
config = get_config(
PATH + 'default_durations.conf',
_get_color_from_vdir= lambda x: None,
_get_vdir_type=lambda x: 'calendar',
)
comp_config = {
'calendars': {
'home': {'path': os.path.expanduser('~/.calendars/home/'),
'color': 'dark green', 'readonly': False, 'priority': 20,
'type': 'calendar',
'default_dayevent_duration': dt.timedelta(days=4),
'default_event_duration': dt.timedelta(seconds=3600)},
'work': {'path': os.path.expanduser('~/.calendars/work/'),
'readonly': True, 'color': None, 'priority': 10,
'type': 'calendar',
'default_dayevent_duration': dt.timedelta(days=1),
'default_event_duration': dt.timedelta(seconds=2640)},
'sport': {'path': os.path.expanduser('~/.calendars/sport/'),
'readonly': False, 'color': None, 'priority': 10,
'type': 'calendar',
'default_dayevent_duration': dt.timedelta(days=2),
'default_event_duration': dt.timedelta(seconds=3312)}},
'sqlite': {'path': os.path.expanduser('~/.local/share/khal/khal.db')},
'locale': {
'local_timezone': get_localzone(),
'default_timezone': get_localzone(),
'timeformat': '%X',
'dateformat': '%x',
'longdateformat': '%x',
'datetimeformat': '%c',
'longdatetimeformat': '%c',
'firstweekday': 0,
'unicode_symbols': True,
'weeknumbers': False,
},
'default': {
'default_calendar': None,
'print_new': 'False',
'highlight_event_days': False,
'timedelta': dt.timedelta(days=2),
'show_all_days': False
}
}
for key in comp_config:
assert config[key] == comp_config[key]
```
#### État de la modification
Le code est fonctionnel, les tests ont été écrits et la fonctionnalité
les passe tous. Il reste cependant des points cruciaux à améliorer avant de
proposer une demande de fusion:
- Restreindre le choix d'une durée de jour à un nombre entier tout en vérifiant
que les cas particuliers (comme les changements d'heure) sont bien traités.
- Lier ces durées par défaut dans l'ajout d'évènement via **ikhal**.
### Issue 809: Affichage et formats des dates
- [Issue][l_809]
- [Wiki][w_809]
#### Objectif
Ajout d'une fonctionnalité permettant de modifier le format de la date
affichée, notamment pour les chiffres: `1, 2, 3 ...` -> `01, 02, 03 ...`
#### Modifications
Le but est de modifier l'affichage. Dans khal tout ce qui est relatif à l'affichage est géré dans le fichier `calendar_display.py`.
Plus spécifiquement la modification de l'affichage des dates, ou plutôt la création d'une liste de jours dans une variable semaine est gérée grâce à la méthode `str_week`.
Cette fonction va créer une liste de jours day correspondant à une semaine. C'est donc ici qu'est formaté l'affichage des jours.
Par défaut khal ajoute manuellement un espace devant les chiffres, ceci pour des raisons d'affichages plus propres grâce à la méthode python `rjust()`.
La page de documentation de la méthode `rjust()` nous indique qu'elle complète par défaut la chaine de caractères par des espaces.
Cependant nous pouvons y passer un second paramètre pour définir une chaine de caractères spécifiques afin de combler le vide pour la chaine de caractère original.
#### Résolution du problème
Dans le cas présent il est intéressant de noter que la chaine de caractère fait 2 caractères (pour des jours allant de '1' à '31') et pour répondre à la problèmatique de l'issue il faudrait remplacer l'espace, ajouté par défaut, par un '0'. Ainsi les chiffres de '1' à '9' seraient de la forme '01' à '09'.
Cependant seulement rajouter un 0 dans le code en dur n'est pas une solution. Pour faire correspondre notre solution à Khal nous avons rajouté une ligne de configuration dans le fichier settings/khal.spec : ` date_format_view = string(default='0') ` sous la [locale].
On récuperera la valeur en argument puis on la passe en second paramètre de la méthode `rjust()`.
Désormais, les chiffres s'affichent comme voulu.
```bash
lu ma me je ve sa di No events
nov. 28 29 30 31 01 02 03
04 05 06 07 08 09 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
```
#### État de la modification
L'ajout de tests supplémentaires a dû être fait, pour correspondre au nouveau format de calendrier.
Le pull request est en cours de validation. Une retouche sera probablement nécessaire pour passer les tests du logiciel d'intégration continue Travis.
### Issue 860: Erreurs aléatoires lors des tests sous ArchLinux
- [Issue][l_860]
- [Wiki][w_860]
Sur une de nos machines, il est impossible de lancer les tests unitaires car
ils génèrent des erreurs aléatoires d'une fois sur l'autre. L'erreur a
déjà été reportée précédemment dans le projet **khal**.
#### Modification
Nous en sommes à la reproduction du bug. Nous testons des environnements
différents pour lancer les tests, via des conteneurs `docker` et des
`Dockerfile` adaptés. Nous testons à la fois des distributions et des
méthodes de construction du logiciel différentes (gestionnaire de paquet
de la distribution, gestionnaire de bibliothèques python `pip`, installation
depuis les sources).
Nous avons perdu beaucoup de temps à essayer de reproduire ce bug, sans succès pour l'instant.
#### État de la modification
Le bug n'a toujours pas été reproduit à l'heure actuelle.
### Issue 876: "Cannot find timezone" à l'import d'évènement
- [Issue][l_876]
- [Wiki][w_876]
Lors de l'importation d'un fichier `.ics` avec un champ `TZID` ayant comme valeur
`New Zealand Standard Time`, un message de type `warning` apparaît.
#### Reproduction
Nous avons repdroduit le bug en créant un fichier `.ics` à importer avec une
timezone `TZID:New Zealand Standard Time`.
#### Identification
Le problème ne vient pas du code à proprement parlé, mais du format des
fuseaux horaires. Il s'avère qu'il existe deux formats de base de données:
- le premier utilisé par Microsoft Windows
- le second appelé Olson sous le format `<Continent>/<Ville>`
**New Zealand Standard Time** est la version Olson de **Pacific/Auckland**.
Dans la bibliothèque python `icalendar`, nous avons trouvé un objet
`windows_to_olson` permettant de retrouver le fuseau horaire au format Olson
depuis son nom au format "Windows".
#### Modifications
Dans un premier temps, nous avons écrit un test spécifique qui essaye d'importer un événement avec une `timezone` au format Windows et scrute le journal d'erreur :
```python
def test_windows_timezone(caplog):
"""Test if a windows tz format works"""
cal = _get_text("tz_windows_format")
split_ics(cal)
assert "Cannot find timezone `Pacific/Auckland`" not in caplog.text
```
Puis nous avons modifié la fonction `split_ics()` dans le fichier `icalendar.py`. La modification permet à la fonction de parcourir le fichier `ics` à importer à la recherche d'un événement avec une `timezone` au format Windows et re remplacer celle-ci par son équivqlent Olson dans la variable `needed_tzs`
#### État de la modification
Une Demande d'ajout a été faites le 20 janvier 2020 portant le numéro [#920][l_PR920]. Elle a donné lieu a une discussion constructive avec deux mainteneurs et des allez-retours de code. Elle a été intégrée le 20 mars 2020.
[l_PR920]:https://github.com/pimutils/khal/pull/902
### Issue 705 : Changement des couleurs de ikhal à travers le fichier de config
**https://github.com/pimutils/khal/issues/705**
La gestion des couleurs ne donne pas entièrement satisfaction.
Un lot d'issues concerne des retours d'expériences de portions d'interface illisibles.
#### Origine
L'issue 705 a été créée par le mainteneur pour répondre de manière cohérente à deux issues qui concernent le réglage des couleurs dans ikhal.
##### issue #395
De l'issue 396 il ressort le souhait de pouvoir choisir un thème via le fichier de configuration.
https://github.com/pimutils/khal/issues/395
##### issue #633
L'issue 633 signale l'impossiblité de lire certaines partie de l'interface graphique d'ikhal, du fait des configurations graphiques des terminaux.


#### Le souhait du mainteneur
> Colors color theme should be changeable through the color theme.
> This should be easily doable by `literal_evaling` a string and adding it to
the selected color theme (as urwid iterates over the palette list and later
attributes overwrite earlier ones with the same name).
> Proper documentation might be an issue.
##### Traduction / interprétation
_Les thèmes et les couleurs des themes devraient etre modifiables depuis le thème de couleur._
_Ceci devrait etre facilement réalisable réalisant un `literal_eval` sur une variable ajoutée dans les thèmes._
_(urwid itere au-dessus en utilisant la palette de l'environnement graphique et ce sont ces derniers attributs qui écrasent écrasent les valeurs.)_
_Une documentation plus complète pourrait faire l'objet d'une issue.

#### Etude du code
Actuellement les deux thèmes possibles sont appelés par khal.spec.
Le choix d'un thème se fait uniquement en modifiant ce fichier source.
Ces thèmes renseignent trentaine de tuples composés de deux à trois valeurs codées en dur dans le fichier `colors.py`.
La configuration est construite sur la base de dictionnaires dont `obj.ctx`, peuplé lors de l'appel de la méthode `get_config`
Lorsque les fichiers de configuration sont trouvés ils sont parcourus et le fichier `khal.spec` est utilisé pour peupler le dictionnaire `user_config`.
La méthode `get_extra_values` identifie chaque intitulé de section et va chercher les items correspondants.
Dans notre cas ce sont des tuples précisant la couleur du fond, la couleur de la police et éventuellement le formatage de police à appliquer pour chaque champ identifié du thème.
> **Nota**
Il existe trente noms de champs dans un thème. La documentation ne permet pas d'identifier la fonction de tous. Certains restent introuvables ailleurs dans le code et des zones d'ikhal n'ont pas leur couleur impactée par khal. Nous avons entamé un travail de documentation qui n'a pas encore été soumis à PR.
À ce stade nous pouvons tout de même réaliser l'implémentation du choix du thème.
#### Variable theme
La section `[view]theme` existe déja et nous souhaitons l'alimenter via le fichier de configuration.
Pour aller chercher cette information dans le fichier de configuration
Comme toute information renseignée par l'utilisateur celle-ci doit être vérifiée.
C'est pour cette raison que le dictionnaire de vérification `fdict` est enrichi de cette section à vérifier.
#### Contribution
L'ajout d'un paramètre modifiable par l'utilisateur via l'enrichissement du fichier config est déja une fonctionnalité existante pour d'autres paramètres (chemin des calendriers, format de date ...).
Il s'agit donc de suivre la structure déja existante au sein du code pour réaliser le travail.
C'est pour cette raison que l'issue a été étiquetée "débutant".
#### Enrichissement de dictionnaire
Khal étant très structuré, il faut renseigner le dictionnaire qui recense les informations à récupérer.
Dans la méthode de recherche de configuration `find_configuration_file()`, nous signifions que nous désirons ajouter une section `theme` au dictionnaire principal en enrichissant `fdict`.
```python
fdict = {'timezone': is_timezone,
'timedelta': is_timedelta,
'expand_path': expand_path,
'expand_db_path': expand_db_path,
'weeknumbers': weeknumber_option,
'monthdisplay': monthdisplay_option,
'color': is_color,
'theme': is_theme
}
```
#### Vérification
Comme toute entrée utilisateur nous devons la vérifier.
Ikhal regroupe les méthodes de vérification dans le fichier `utils.py`. Nous y ajoutons `is_theme`.
Cette méthode est appelée par `config_checks` qui parcours l'ensemble des éléments à vérifier.
```python
def is_theme(option):
option = option.lower()
if option in ['dark', 'light']:
return 'user'
else:
raise VdtValueError(
"Invalid value '{}' for option 'theme', must be one of "
"'dark' or 'light' ".format(option))
```
Les thèmes valides sont écrits "en dur", tout comme d'autres méthodes de validation du fichier `utils.py`.
La question se posera lors de la résolution de l'issue #633 de ne pas utiliser de valeurs codées en dur mais de se référer aux variables contenues dans le thème dynamique.
##### Import de is_theme
Nous demandons l'import de cette nouvelle méthode lors du chargement de `settings.py`
```python
from .utils import (config_checks, expand_db_path, expand_path,
get_color_from_vdir, get_vdir_type, is_color, is_timedelta,
is_timezone, weeknumber_option, monthdisplay_option, is_theme)
```
##### Recherche et traitement de la configuration utilisateur
`config_checks( ... )` vérifie qu'une valeur est renseignée dans la configuration et la controle. Dans le cas d'une erreur, khal se rabat sur la valeur par défaut indiquée dans dans `khal.spec`
```python
if not config['view']['theme']:
config['view']['theme'] = is_theme(
config['view']['theme'])
```
#### Documentation
`khal.spec` contribue à la génération de la documentation, aussi une légère reformulation des commentaires est réalisée.
La documentation du fichier de configuration est également enrichie d'un exemple.
Une documentation plus complète a été envisagée sous la forme d'ascii art mais celle-ce n'a pas été inclue dans la PR pour plusieurs raisons
- L'issue concerné est plutot la #633
- L'ensemble des variables de couleurs n'a pas encore été balayé
- A l'usage il semble plus pertinent de travailler sur des images
```python
# ikhal screen
# +header-----------------------------------------
# | dayname
# |mon
# |
# | td date header focused/selected
# |th
# |
# |
# |name
# |
# |
# |
# +footer------------------------------------------
# new event screen
# +header--------------------
# |
# |Title: edit
# |< calendars >
# |
# |Location: edit/edit focused
# |Categories: edit/edit focused
# |Description: edit/edit focused
# |[ ] Alldays
# |From: edit/edit focused/edition validated
# |To: edit/edit focused/edition validated
# |[ ] Repeat: < weekly > every:1
# | <list>
# |
# ---------------------------
```
#### Conclusion
Une pull request documentée a été réalisée avec cette proposition.
Le point concernant le parcours de thème pour l'issue #633 est cité en commentaire.
car celle-ci implique la vérification potentielle de soixantes valeurs de couleurs et de typage de police.
\pagebreak
### Issue 866: Effectuer les suppressions sans quitter le mode interactif
#### Origine
**https://github.com/pimutils/khal/issues/866**
Une demande d'ajout dction qui est passée par pas moins de 5 issues entre 2016 et maintenant.
Certains utilisateurs voudraient pouvoir appliquer les suppressions d'evenements sans quitter Ikhal.
#### Principe
Le comportement normal d'Ikhal est de marquer les évenements pour suppression puis d'appliquer les changements à sa fermeture.
Le but est de résoudre cette contrainte.
Geier a proposé dans une des issues d'imiter le fonctionnement de mutt et d'assigner une touche qui se chargerait d'enregistrer les suppressions.
Dans mutt, cela peut être accomplis avec la touche `$` .
#### Étude
Parmis les pistes évoquées par Geier, nous avons suivi celle de mutt,
Pour ce faire nous avons défini un nouveau contrôle lié à la touche `$` dans le fichier `khal.spec`.
Celui-ci a été nommé `cleanup`, en rapport avec la fonction qu'il permet d'appeler.
Fichier settings/khal.spec
```
#cleanup deleted event
cleanup = force_list(default=list('$'))
```
La méthode `keypress` elle, se sert des contrôles définis dans le `khal.spec`, pour effectuer des actions.
Ici, à la pression de la touche `$`, on appelle `cleanup`.
```python
def keypress(self, size, key):
prev_shown = self._eventshown
self._eventshown = False
self.clear_event_view()
if key in self._conf['keybindings']['new']:
self.new(self.focus_date, None)
key = None
elif key in self._conf['keybindings']['cleanup']:
self.pane.cleanup(None)
key = None
[...]
```
C'est la méthode appelée lors de la fermeture d'Ikhal pour supprimer les évenements qui ont un marqueur de suppression.
L'appeler durant l'exécution permet d'inscrire directement les changements sur le disque.
```python
def cleanup(self, data):
"""delete all events marked for deletion"""
for part in self._deleted[ALL]:
account, href, etag = part.split('\n', 2)
self.collection.delete(href, etag, account)
for part, rec_id in self._deleted[INSTANCES]:
account, href, etag = part.split('\n', 2)
event = self.collection.get_event(href, account)
event.delete_instance(rec_id)
self.collection.update(event)
```
#### Etat actuel
Avec les modifications actuelles il est possible d'enregistrer les modifications sur le disque mais il reste à trouver une solution pour rafraîchir l'écran : en effet l'interface montre toujours les évenements marqués pour suppression, ce qui inclu potentiellement de modifier la fonction `cleanup`.
En conclusion, il faudra trouver une solution à ce dernier problème, avant de proposer une réponse.
## Conclusion
### Nouvelles connaissances acquises
S'intéresser à **khal** a été une source importante d'approfondissement de nos connaissances , notamment en ce qui concerne l'univers **python**:
#### Techniques
- Test unitaires avec `py.test`
- Test de versions de dépendances avec `tox`
- Nous avons découvert les joies du debuggage, notamment avec `pdb`, `ipdb` et `pudb`
- Perfectionnement dans l'utilisation de git
- `git reflog`
- `git bisect`
- `git fetch`
Cela a également l'occasion de découvrir l'existence de bibliothèques python:
- `datetime` pour manipuler les objets relatifs au temps
- `py-tz` pour manipuler les timezones
- `click` pour gérer très efficacement les arguments passés en ligne de commande
- `configobj` pour gérer très simplement et efficacement des fichiers de configuration
### Points durs rencontrés
#### Blocages
La confrontation avec des blocages dans le code n'a pas toujours été un exercice facile, surtout lorsqu'il est identifié tardivement dans le l'implémentation d'une issue. Ces étapes de développement ont été des périodes de questionnement sur la pertinence du choix de l'issue à résoudre.
Quelques interrogations sur notre efficacité sur certains sujets demeurent. (changer ou pas de sujet). Toutefois cela a été un apprentissage humain instructif.
#### Retours d'expérience d'utilisateurs variables
Le débroussaillage des issues et des contributions a été un domaine que nous ne connaissions pas.
Cela a été une difficulté qui s'est cummulée aux autres (aisance dans python, dans l'anglais ...) mais qui nous a aussi permis d'identifier l'importance d'une communauté d'utilisateurs contributeurs capables de communication inclusive.
#### Apparition de bugs en amont
Il est arrivé à deux reprises que des bugs apparaissent sur des dépendances utlisées par le logiciel.
Par chance ils ont été résolus par les mainteneurs de ces autres projets. Cela nous aura permis de découvrir l'importance du choix des dépendances.
#### Rythme des échanges
Le rythme des échanges nous a parfois surpris. En cause la taille modeste du projet.
Les échanges avec l'équipe ont pu prendre un certain temps et nous nous sommes parfois sentis bloqués.
### Conclusion
Nous avons eu l'occasion d'appréhender l'énergie et le temps offert par les bénévoles dans les projets open source.
L'apprentissage par la pratique va nous permettre de mieux dimensionner et identifer nos contributions futures.
La découverte de nouveaux outils (test automatique, débugueur ... ) a été formatrice et nous a ouvert de nouveaux horizons.
L'organisation **Khalzone** va être conservée pour nous permettre de contribuer de manière collégiale à ce projet.
Il est également prévu d'y inviter une connaissance qui découvre l'opensource et l'un de nous a été accepté comme mainteneur du projet **Khal**.