La sous-commande « bsc create » étant enfin terminée (les derniers éléments sont expliqués dans l’article précédent), on va enfin pouvoir s’attaquer à la plus grosse partie de ce projet en Rust : la sous-commande d’ajout d’un module, incluant la gestion des dépendances du projet C généré plus tôt.

Je vais donc commencer par vous expliquer comment est-ce que je compte faire ça, et on regardera ensuite le code en détails, comme d’habitude.

Ready ? Alors on est reparti, toujours dans la joie et la bonne humeur !

 

 

 

En quoi va consister cette sous-commande ?

 

Une des fonctionnalités principales d’un package manager (sans quoi il n’aurait d’ailleurs que peu d’intérêt), réside dans la possibilité d’ajouter des modules au projet. C’est à dire d’ajouter des morceaux de code déjà écrits, sous forme de petites API, afin de les réutiliser, et d’accroître ainsi la vitesse de développement d’un projet.

Par exemple dans le cas de Rust, avec Cargo, j’ai utilisé pour le moment deux crates (deux modules Rust) : clap, et git2; permettant respectivement de gérer la création de CLI (command line interface) et d’effectuer des opérations liées à git (dans mon cas, pour le moment, j’utilise le crate git2 uniquement dans le but d’initialiser le dépôt git automatiquement, lors de la création du squelette du projet).

Sans ces crates, j’aurais dû développer ces fonctionnalités from scratch, ce qui aurait pris beaucoup plus de temps.

Le but de la sous-commmande add est donc de pouvoir importer le code des modules dans le projet courant, et d’ajouter ces modules à la liste des dépendances, dans notre fichier dependencies.bsc.

Concrètement, comment est-ce que cela va se passer ?

Et bien on tapera une commande de ce type :

bsc add --git https://github.com/Elkantor/bsc_test

Ce qui permettra de récupérer le code du module (dans ce cas-ci, situé dans le dépôt github à l’adresse https://github.com/Elkantor/bsc_test), et l’ajouter au projet.

Pour conclure sur ces explications très basiques de cette sous-commande, je pars du principe que bsc est un package manager qui compile à chaque fois le code des modules. Pour le moment, il n’est donc pas question d’ajouter directement des librairies compilées au projet (.lib ou .dll sur windows, .so sur linux…).

 

Ajout d’une nouvelle sous-commande via le crate clap

 

Vous vous souvenez de ce que j’avais fait pour obtenir la sous-commande create, dans les premiers articles de cette série ?

Si oui, ça tombe bien, car on va faire un peu la même chose pour ajouter la nouvelle sous-commande : add (avec quelques particularités cela dit).

Pour résumer, on a besoin d’une nouvelle sous-commande avec trois options :

  • une pour récupérer un module depuis un dépôt git distant (––git)
  • une pour télécharger le module zippé directement depuis un url distant, puis l’extraire du fichier .zip téléchargé (––zip)
  • enfin, une autre permettant de simplement copier/coller le contenu du dossier disponible sur le disque dur local (ou réseau), via le chemin passé en argument (––local)

On verra par la suite pour ajouter d’éventuelles options supplémentaires.

Pour ajouter cette nouvelle sous-commande, je commence donc par simplement l’ajouter dans le fichier main.rs, dans la fonction main, grâce au crate clap :

fn main() {
    let matches = clap::App::new("bsc")
        .version("0.1.0")
        .author("Victor Gallet <victor.gallet@developing-stuff.com>")
        .about("clone of bscxx (a package manager for C++), for C language, made in Rust")
        // La sous-commande "create", créée plus tôt dans cette série
        .subcommand(clap::SubCommand::with_name("create")
            .about("create a new project / module")
            .arg(clap::Arg::with_name("PROJECT_NAME")
                .help("the name of the project / module")
                .required(true)
                .takes_value(true)
                .index(1)
            )
        )  
        // La nouvelle sous-commande fraîchement ajouté : "add"
        // avec ses arguments
        .subcommand(clap::SubCommand::with_name("add")
            .about("add a module to the project")
            .arg(clap::Arg::with_name("git")
                .help("Clones git repository")
                .short("g")
                .long("git")
                .multiple(true)
                .requires("MODULE_URL")
            )
            .arg(clap::Arg::with_name("local")
                .help("For a module in local")
                .short("l")
                .long("local")
                .multiple(true)
                .requires("MODULE_URL")
            )
            .arg(clap::Arg::with_name("zip")
                .help("For an online module")
                .short("zip")
                .long("zip")
                .multiple(true)
                .requires("MODULE_URL")
            )
            .arg(clap::Arg::with_name("MODULE_URL")
                .help("Url of the module")
                .required(true)
                .takes_value(true)
            )
        )
        .get_matches();

    // On traite les sous-commandes
    match matches.subcommand() {
        ("create", Some(create_matches)) => create_project("./", create_matches.value_of("PROJECT_NAME").unwrap()),
        ("add", Some(add_matches)) => add_dependency("./", add_matches.value_of("MODULE_URL").unwrap(), add_matches.is_present("git"), add_matches.is_present("local"), add_matches.is_present("zip")),
        ("", None) => println!("No subcommand was used"), // If no subcommand was used it'll match the tuple ("", None)
        _ => unreachable!(), // If all subcommands are defined above, anything else is unreachable!()
    }
}

 

Okay, ça fait peut-être beaucoup de code en plus… Mais promis, c’est tout bête, je vous explique !

Comme précédemment, avec create, on ajoute une nouvelle sous-commande add à notre programme, en lui spécifiant une phrase qui est affichée (about) lorsqu’on utilise l’help (-h ou –help) :

.subcommand(clap::SubCommand::with_name("add")
    .about("add a module to the project")

 

De plus, cette sous-commande doit inclure obligatoirement un chemin url du module, quelque soit l’option choisie (parmi local, zip, ou git) :

// On ajoute un argument à cette sous-commmande
// nommé "MODULE_URL", qui est obligatoirement renseigné
// S'il n'est pas renseigné, alors un message signalant que
// la syntaxe n'est pas correcte est affiché, en plus de l'help
.arg(clap::Arg::with_name("MODULE_URL")
    .help("Url of the module")
    .required(true)
    .takes_value(true)
)

 

Enfin, on ajoute les options précédemment citées (zip, local et git) :

// Option --git, ou -g
.arg(clap::Arg::with_name("git")
    .help("Clones git repository")
    .short("g")
    .long("git")
    .multiple(true)
    .requires("MODULE_URL")
)
// Option --local, ou -l
.arg(clap::Arg::with_name("local")
    .help("For a module in local")
    .short("l")
    .long("local")
    .multiple(true)
    .requires("MODULE_URL")
)
// Option --zip, ou -z
.arg(clap::Arg::with_name("zip")
    .help("For an online module")
    .short("z")
    .long("zip")
    .multiple(true)
    .requires("MODULE_URL")
)

 

Chacune de ces options peut-être écrite plusieurs fois dans la sous-commande, sans que cela perturbe le fonctionnement du programme, grâce au multiple(true). Dans ce cas, c’est comme si une seule option était renseignée (les multiples sont ignorés).

Enfin, quelque soit l’option sélectionnée, elle requière l’argument MODULE_URL, sans quoi, cela affichera un message signalant qu’il y a une erreur de syntaxe, et l’help.

Et pour conclure, puisqu’on a ajouté une nouvelle sous-commande, il faut dorénavant la traiter, grâce au match :

match matches.subcommand() {
    ("create", Some(create_matches)) => create_project("./", create_matches.value_of("PROJECT_NAME").unwrap()),
    // On traite la nouvelle sous-commande 
    // en appelant la méthode add_dependency qui est définie plus loin dans le fichier main.rs.
    // Cette dernière prend en paramètre le chemin courant, l'url du module (MODULE_URL),
    // ainsi que des booléens pour savoir quelle option parmi git, local, ou zip, a été séléctionnée
    ("add", Some(add_matches)) => add_dependency("./", add_matches.value_of("MODULE_URL").unwrap(), add_matches.is_present("git"), add_matches.is_present("local"), add_matches.is_present("zip")),
    ("", None) => println!("No subcommand was used"),
    _ => unreachable!(), 
}

 

Okay, voyons voir ce que ça donne. Comme d’habitude, on peut compiler et lancer le programme directement en affichant l’aide de la sous-commande, grâce à cargo :

cargo run -- add --help

 

Et voici le résultat :

Finished dev [unoptimized + debuginfo] target(s) in 6.01s
     Running `target\debug\bsc.exe add --help`
bsc.exe-add
add a module to the project

USAGE:
    bsc.exe add [FLAGS] <MODULE_URL>

FLAGS:
    -g, --git        Clones git repository
    -h, --help       Prints help information
    -l, --local      For a module in local
    -V, --version    Prints version information
    -z, --zip        For an online module

ARGS:
    <MODULE_URL>    Url of the module

 

Impeccable ! Tout est en ordre, on a bien les flags gitlocal et zip, ainsi que l’url du module, comme convenu.

Pour en finir avec le code présent dans le fichier main.rs, regardons maintenant la fonction add_dependency, appelée dans le match des sous-commandes précédent :

fn add_dependency(path: &str, module_url: &str, git_repository: bool, local_repository: bool, zip_repository: bool){
    if git_repository {
        add::add_dependency(&path, &module_url, add::ModuleType::Git);
    } else if local_repository {
        add::add_dependency(&path, &module_url, add::ModuleType::Local);
    } else if zip_repository {
        add::add_dependency(&path, &module_url, add::ModuleType::Zip);
    } else {
        println!("Error: the module is neither a local, a git, or a zip url.");
    }
}

 

En fonction des booléens donnés en paramètres (git_repositorylocal_repositoryzip_repository) correspondant aux flags de la sous-commande, j’appelle la fonction d’entrée de la fonctionnalité d’ajout d’un module, présente dans le fichier add.rs, en lui passant le chemin courant (« ./ »), l’url du module et enfin, un enum, dont voici la définition (présente dans add.rs):

pub enum ModuleType {
    Local,
    Git,
    Zip
}

 

Bien entendu, pour pouvoir utiliser la fonction add::add_dependency et cet enum add::ModuleType, j’ai ajouté l’instruction pré-processeur mod add (en référence au fichier add.rs, situé dans le même dossier que main.rs), comme vu précédemment dans cette série.

// Tout en haut de main.rs
mod add;

 

Il nous reste donc plus qu’à voir dans les articles suivant tout ce qui se passe dans la fonction d’entrée de cette fonctionnalité d’ajout d’un module : add_dependency, située dans le fichier add.rs.

C’est là que se déroule finalement ce qui est vraiment intéressant !

Comme à l’accoutumée, j’espère vraiment que vous avez trouvé cet article intéressant; et je reste bien entendu à l’écoute de remarques, d’avis concernant le fond, tout comme la forme de cet article. N’hésitez pas à me partager vos opinions via les commentaires ci-dessous.

 

Victor Gallet

Victor Gallet

Étudiant programmeur jeu vidéo. J'aime par dessus tout apprendre, et je suis un éternel curieux de tout. Mon principal but dans la vie est d’être une meilleure personne, et de partager mes (faibles) connaissances avec les autres.

Leave a Reply

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.