Chapitre 4 • Labelas Enoreth
Labelas Enoreth, called the Lord of the Continuum, was the chaotic good elven deity who governed the orderly passage of time and guarded against those who would alter the path of history.
Cartouche
| Champ | Valeur |
|---|---|
| Auteur·e | Élise |
| Édition | 2024-08-22 |
| Durée | 2 séances (un jour) |
| Taille des équipes | 1 personne |
| Rendu | via git, dépôt $YEAR_rust_c4, droits en lecture à delivery_collector |
Règlement
Prenez connaissance du règlement ici
L’intégralité de la bibliothèque standard est autorisée. Vous êtes encouragés à vous servir des outils qu’elle vous propose s’ils résolvent un de vos problèmes. Votre objectif c’est d’éviter de ré-inventer la roue quand ça n’est pas nécessaire.
Barème
| Critère | Points Accessibles |
|---|---|
| Étape 1 modèle | 5 |
| Étape 2 sérialisation | 15 |
| Étape 3 CLI design | 5 |
| Étape 4 CLI implémentation | 15 |
| Total des critères | 40 |
| Malus par lignes en faute | -0,5 |
| Malus dépôt sale | -5 |
| Total | N/A |
Introduction
Pour ce sujet, on va travailler sur la mise en place d’un programme complet, qui utilise des options, qui charge son état depuis un fichier, et le met à jour à la fin. Et surtout : on va travailler sur un programme qui a une utilité, le suivi des échéances et des tâches.
Nous allons continuer de nous servir des crates que nous avons vu dans le Chapitre 3 • Crates.
Ce sujet contient une série de resources qui peuvent vous aider à l’implémentation de chacun des morceaux de ce programme :
- le modèle (structures)
- l’interface en ligne de commande (CLI)
- design et implémentation
- la sérialisation et dé-sérialisation
Étape 1 : Modèle
Dans notre modèle on va chercher à représenter une tâche (task) et une échéance (deadline). Les tâches peuvent être des dépendances d’une échéance.
Exemple de tâches et d’échéances :
- échéance : “Projet Printf”
- date : 2023-03-05
- tâches :
- “vérifier zéro fichier interdits”
- “norme”
- “
%ppointeurs”- status : abandonné le 2023-03-04
- commentaire : “c’est trop dur”
- “
%xhexadécimal”- status : en cours
- depuis le 2023-03-04
- “
%%afficher un %”- status : fini
- le 2023-03-02
- échéance : “DM de français”
- date : 2023-03-12
- tâches :
- “lire les consignes”
- etc…
Votre tâche : c’est de créer les structures qui permettent de modéliser tout cela. Les tâches doivent avoir une date de dernière mise à jour. Les échéances doivent avoir une date d’échéance. Les tâches et les échéances doivent porter un nom.
Fonctions et détails d’implémentation
L’état d’une tâche, son status, doit être encodé grâce à une énumération. Les tâches et les échéances doivent avoir un champ pour un (ou plusieurs) commentaire·s ou une description.
Fonctions générales de votre modèle
- [ ] Insérer une nouvelle échéance
- [ ] Supprimer toutes les échéances passées
- [ ] Récupérer un itérateur de références sur toutes les échéances qui tombent sous 7 jours
Fonctions sur échéance :
- [ ] Créer une échéance
- [ ] Ajouter une tâche a une échéance
- [ ] Récupérer un itérateur sur les tâches qui ont un certain status
- [ ] Récupérer un itérateur sur les tâches qui n’ont pas un certain status
- [ ] Trait
Display: pretty print une échéance, sa description, sa date, les tâches qu’elle contient lorsqu’on faitprintln!("{}", deadline);
Fonctions sur tâche :
- [ ] Trait
Display: pretty print une tâche, sa description, son état, lorsqu’on faitprintln!("{}", task); - [ ] bump sa date de dernière modification à maintenant
Exemple de renvoi d’un itérateur
Exemple de signature d’une fonction membre qui renvoie un itérateur avec des références sur une donnée contenue dans une structure. Notez l’utilisation des durées de vie.
La date passée en référence est aussi annotée de la durée de vie. C’est nécessaire car l’itérateur renvoyé ne peut pas être valide si la date qui a servit à la création de l’itérateur a été drop. Cette annotation est évitable en faisant une copie de la date plutôt que de passer sa référence.
/// Iterator on journeys that run on provided date.
pub fn get_journeys_for_day<'a>(
&'a self,
day: &'a chrono::NaiveDate,
) -> impl Iterator<Item = &'a Journey> {
self.journeys
.iter()
.filter_map(|journey| self.filter_map_journey_on_date(journey, day))
}
/// Retuns the given journey Some variant if it runs on provided day,
/// checking service patterns and exceptions.
fn filter_map_journey_on_date<'a>(
&'a self,
journey: &'a Journey,
day: &'a chrono::NaiveDate,
) -> Option<&'a Journey> {
let pattern = self.service_patterns.get(&journey.service_id)?;
if pattern.start_date.le(day)
&& pattern.end_date.ge(day)
&& weekday_flags::runs_on_date(day, pattern.weekdays)
{
Some(journey)
} else {
None
}
}
Dates
Pour les dates, vous êtes encouragés d’utiliser le crate chrono et NaiveDate qui représente une date sans information de fuseau horaire.
Ajoutez la ligne suivante à votre Cargo.toml :
chrono = { version = "0.4.38", default-features = false, features = ["alloc", "now", "serde", "std", "clock"] }
Étape 2 : Sérialisation
Tâche A : sérialisation
Écrivez un main dans src/bin/ser.rs qui génère des données d’exemple pour les structures que vous avez modélisé à l’étape précédente et les sérialisent. Les données sérialisées peuvent être simplement affichée sur la sortie standard ou enregistrées dans un fichier, comme vous le souhaitez.
Tâche B : dé-sérialisation
Écrivez un main dans src/bin/de.rs qui dé-sérialise des données en json et affiche quelques unes de ses données clés. Le contenu à dé-sérialiser doit provenir d’une chaîne de caractères statique.
const TO_DESER: &'static str = r#"
{
"example_key": "example_value"
}
"#;
// ou
const TO_DESER: &'static str = include_str!("to_deser.json");
fn main() {
// [le code qui fait la dé-sérialisation]
}
Remplacez le contenu de la chaîne statique par du contenu qui aurait pu être créé à l’étape de sérialisation.
Vérifiez que la dé-sérialisation ne provoque pas d’erreur, et que les données que vous souhaitez sont bien présentes dans la structure dé-sérialisée.
Tâche C : test d’intégration
Mettez de bout en bout ce que vous avez fait en A et en B dans un unique test d’intégration, vérifier à coup d’assertions que les données sont les mêmes du début à la fin.
Étape 3 : Élaborez l’interface en ligne de commande
L’objectif de votre programme c’est de permettre la consultation de tâches et d’échéances, leur création, modification et suppression, pour qu’un utilisateur puisse faire ces tâches, il a besoin d’une interface en ligne de commande.
Voici un exemple de CLI :
./enoreth deadline_add "printf delivery" "2024-10-01"
./enoreth task_add "printf delivery" "unit tests for %d" "we really need tests"
./enoreth task_add "printf delivery" "check coding style" "we really don't want to lose points for a function too long"
./enoreth task_add "printf delivery" "feature: %p" "displays pointers"
./enoreth task_add "printf delivery" "feature: %d" "displays base10 numbers"
./enoreth task_add "printf delivery" "ask for help on VA_ARGS"
./enoreth task_edit "printf delivery" "unit tests for %d" --name "unit tests for %d with padding" --status started
./enoreth # list deadlines for next 14 days
Vous pouvez l’emprunter, vous en inspirer ou faire tout autre chose, pensez juste à chacune des fonctionnalités que vous devez implémenter, et à ce qui serait le plus naturel à l’usage.
Dans tous les cas vous devez avoir une option -f/--file qui permet de choisir le nom du fichier à sérialiser/dé-sérialiser.
Tâche : Écrivez dans un fichier readme.md la description des commandes de votre programme en vous inspirant de l’exemple ci dessus. Décrivez chacune des sous-commandes, leurs options obligatoires et optionnelles, le format des dates, etc. Imaginez que vous documentez votre futur programme pour un utilisateur.
Écrivez aussi pour chaque commande et au global quelles erreurs sont possibles.
Étape 4 : Implémentez le CLI
Fichier à rendre : src/bin/cli.rs
En utilisant clap, parsez les arguments du programme, en respectant le CLI que vous avez élaboré en Étape 3.
Votre programme doit faire la tâche demandée par l’utilisateur, sur le fichier qu’il a précisé dans les arguments.
Vous devez :
- charger le fichier ;
- le dé-sérialiser ;
- faire l’action demandée, puis ;
- sérialiser les données et ré-écrire le fichier.
Si le fichier n’existe pas, il n’est pas dé-sérialisé mais il est crée à partir du 0.
Pour aller plus loin, étape 5 : menus
Une autre façon d’interagir avec un programme c’est à travers des menus navigables aux clavier (et parfois même à la souris) dans le terminal. On appelle ça un TUI – Terminal User Interface.
Étudiez le crate inquire, qui permet de à l’utilisateur de choisir parmi une liste d’options et écrivez un programme qui permet de faire des actions semblable à votre CLI mais à travers une série de menus.