
Etapes guidées et pistes d'amélioration simple
Suivez nos étapes guidées pour créer un gestionnaire de tâches en Rust et explorez des pistes d'amélioration simples pour enrichir votre application CLI. Devenez opérationnel pas à pas.
Construire votre gestionnaire de tâches : une feuille de route structurée
Pour aborder sereinement la création de notre mini outil CLI de gestion de tâches, nous allons, comme pour le projet précédent, suivre une série d'étapes guidées de développement. Cette approche décompose le projet en morceaux digestes, vous permettant de vous concentrer sur une fonctionnalité à la fois et de voir des progrès concrets rapidement. Chaque étape s'appuiera sur les précédentes, construisant itérativement l'application.
L'objectif principal est de vous rendre autonome dans la création d'une application CLI simple en Rust. En plus des étapes de base, nous esquisserons également quelques pistes d'amélioration simples. Ces suggestions vous offriront des défis supplémentaires si vous souhaitez aller plus loin et personnaliser votre outil, renforçant ainsi votre apprentissage.
Ce guide est conçu pour être pratique et pédagogique. Nous mettrons l'accent sur la compréhension des concepts Rust utilisés à chaque phase, vous encourageant à expérimenter et à adapter le code proposé.
Etape 1 : initialisation du projet et structure de la boucle principale
Commençons par créer un nouveau projet Cargo et mettre en place la structure de base de notre application CLI. Dans votre terminal :
cargo new todo_cli
cd todo_cliOuvrez src/main.rs. Nous allons y définir une boucle principale qui lira les commandes de l'utilisateur jusqu'à ce qu'il décide de quitter.
use std::io::{self, Write}; // Write pour flush stdout
fn main() {
println!("Bienvenue dans votre gestionnaire de tâches Rust!");
// Nous aurons besoin d'un endroit pour stocker les tâches plus tard
// let mut tasks: Vec = Vec::new(); // Exemple simple pour l'instant
loop {
print!("> "); // Affiche l'invite
io::stdout().flush().unwrap(); // S'assure que l'invite s'affiche avant read_line
let mut input = String::new();
if io::stdin().read_line(&mut input).is_err() {
println!("Erreur lors de la lecture de l'entrée.");
continue; // Recommence la boucle
}
let command = input.trim().to_lowercase(); // Nettoie et met en minuscule pour simplifier
if command == "quitter" || command == "exit" {
println!("Au revoir !");
break; // Sort de la boucle
} else if command.is_empty() {
// Ne rien faire si l'entrée est vide
} else {
// Ici, nous traiterons les autres commandes (ajouter, lister, etc.)
println!("Commande non reconnue : {}", command);
}
}
} Cette structure de base nous donne un programme qui attend des commandes et sait comment s'arrêter. L'utilisation de io::stdout().flush().unwrap() est importante après print! (qui ne saute pas de ligne) pour s'assurer que l'invite > s'affiche immédiatement.
Etape 2 : définition de la structure `Task` et stockage des tâches
Définissons maintenant notre structure Task et initialisons le vecteur qui les contiendra. Modifiez le début de src/main.rs :
use std::io::{self, Write};
#[derive(Debug)] // Pour pouvoir afficher une Task avec {:?}
struct Task {
id: usize, // Un identifiant unique pour chaque tâche
description: String,
completed: bool, // Pour marquer une tâche comme faite
}
fn main() {
println!("Bienvenue dans votre gestionnaire de tâches Rust!");
let mut tasks: Vec = Vec::new();
let mut next_id: usize = 1; // Pour générer des IDs uniques
// ... (la boucle `loop` vient ici)
// Dans la boucle, avant le `if command == "quitter"`:
// if command.starts_with("ajouter ") {
// // Logique d'ajout
// } else if command == "lister" {
// // Logique de listage
// }
} Nous avons ajouté une structure Task avec un id, une description, et un booléen completed (initialement false). Nous avons aussi un compteur next_id pour attribuer des IDs croissants. L'attribut #[derive(Debug)] permet d'afficher facilement une instance de Task à des fins de débogage avec println!("{:?}", ma_tache);.
Etape 3 : implémentation de l'ajout de tâches
Intégrons la logique pour ajouter une tâche. Dans la boucle loop, à l'endroit où nous traitons les commandes :
// ... (dans la boucle `loop` de main())
// ...
let command = input.trim(); // Gardons la casse originale pour la description
let command_lower = command.to_lowercase();
if command_lower == "quitter" || command_lower == "exit" {
// ... (quitter)
} else if command_lower.starts_with("ajouter ") {
let description = command.strip_prefix("ajouter ").unwrap_or("").trim();
// ou, pour gérer la casse de "Ajouter " aussi :
// let prefix_len = "ajouter ".len();
// let description = command.get(prefix_len..).unwrap_or("").trim();
if description.is_empty() {
println!("La description de la tâche ne peut pas être vide.");
} else {
let new_task = Task {
id: next_id,
description: description.to_string(),
completed: false,
};
tasks.push(new_task);
println!("Tâche #{} ajoutée : {}", next_id, description);
next_id += 1;
}
} else if command_lower == "lister" {
// ... (logique de listage viendra ici)
} else if command_lower.is_empty() {
// Ne rien faire
} else {
println!("Commande non reconnue : {}", command);
}
// ... (fin de la boucle `loop`)Ici, nous vérifions si la commande commence par "ajouter " (avec un espace). Si oui, nous extrayons le reste de la chaîne comme description. La méthode strip_prefix est pratique pour cela. Nous créons ensuite une nouvelle Task et l'ajoutons au vecteur tasks, en incrémentant next_id.
Etape 4 : implémentation du listage des tâches
Ajoutons maintenant la fonctionnalité pour lister les tâches. Toujours dans la boucle loop :
// ... (dans la section `else if` de la boucle `loop`)
// ...
} else if command_lower == "lister" {
if tasks.is_empty() {
println!("Aucune tâche à afficher.");
} else {
println!("Vos tâches :");
for task in &tasks { // Itère sur des références aux tâches
let status = if task.completed { "[x]" } else { "[ ]" };
println!(" {}. {} {}", task.id, status, task.description);
}
}
// ... (reste des conditions else if)
}Si la commande est "lister", nous vérifions si le vecteur tasks est vide. Sinon, nous itérons dessus et affichons chaque tâche avec son ID, un indicateur de statut ([ ] pour non complétée, [x] pour complétée - que nous implémenterons plus tard), et sa description.
Pistes d'amélioration simples (à explorer par vous-même)
Votre gestionnaire de tâches de base est maintenant fonctionnel ! Voici quelques idées pour l'améliorer :
- Marquer une tâche comme complétée : Ajoutez une commande comme `completer
` (ou `done `). Vous devrez : - Parser l'ID de la tâche.
- Trouver la tâche correspondante dans le vecteur
tasks(par exemple, avectasks.iter_mut().find(|t| t.id == parsed_id)). - Mettre son champ
completedàtrue.
- Supprimer une tâche : Ajoutez une commande `supprimer
`. Vous devrez : - Parser l'ID.
- Trouver l'index de la tâche dans le vecteur (par exemple, avec
tasks.iter().position(|t| t.id == parsed_id)). - Utiliser
tasks.remove(index)si la tâche est trouvée.
- Persistance des données : C'est une étape plus complexe, mais très formatrice. Explorez comment sauvegarder les tâches dans un fichier (par exemple, au format JSON avec la crate
serdeetserde_json) lorsque le programme quitte, et les recharger au démarrage. - Meilleure gestion des erreurs : Pour les commandes comme `completer` ou `supprimer`, gérez les cas où l'ID n'est pas un nombre valide, ou si aucune tâche avec cet ID n'existe.
- Refactorisation en fonctions : Pour garder
main()lisible, déplacez la logique d'ajout, de listage, de complétion, etc., dans des fonctions séparées qui prennent&mut Vecet d'autres arguments nécessaires. Par exemple :fn add_new_task(tasks: &mut Vec., next_id: &mut usize, description: &str) { ... }
Ces étapes guidées et pistes d'amélioration vous fournissent un excellent cadre pour pratiquer et approfondir vos compétences en Rust. N'ayez pas peur d'expérimenter, de faire des erreurs et d'apprendre de celles-ci. C'est ainsi que l'on progresse réellement en programmation. Bon codage !