Contactez-nous

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_cli

Ouvrez 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 :
    1. Parser l'ID de la tâche.
    2. Trouver la tâche correspondante dans le vecteur tasks (par exemple, avec tasks.iter_mut().find(|t| t.id == parsed_id)).
    3. Mettre son champ completed à true.
  • Supprimer une tâche : Ajoutez une commande `supprimer `. Vous devrez :
    1. Parser l'ID.
    2. Trouver l'index de la tâche dans le vecteur (par exemple, avec tasks.iter().position(|t| t.id == parsed_id)).
    3. 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 serde et serde_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 Vec et 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 !