Application en ligne de commande
Introduction
Réaliser un programme s’exécutant en ligne de commande est extrêmement simple : il suffit de mettre le code dans un fichier et d’exécuter celui-ci avec node :
$ node index.js
Bonjour tout le monde !
Mais la réalisation d’un véritable projet d’application en ligne de commande implique d’autres difficultés comme l’exécution directe (sans préciser node) ou même la prise en compte de paramètres.
Dans ce chapitre, nous verrons dans un premier temps comment gérer les paramètres d’un programme en ligne de commande, puis comment tester celui-ci. L’exécution directe sera ensuite abordée, et l’installation d’un programme terminera la partie théorique. Enfin, nous terminerons avec l’analyse d’un exemple complet de programme en ligne de commande, via la réalisation d’un serveur HTTP trivial basé sur la bibliothèque express, que nous nommerons mon-server et qui sera implémenté dans un fichier nommé cli.js. Vous serez donc en mesure de créer entièrement vos propres applications en ligne de commandes à la fin de cette partie !
Gestion des paramètres
Dans Node, l’ensemble des paramètres de la ligne de commande est accessible via la propriété argv de l’objet process qui est un tableau de chaînes de caractères. Comme on peut le voir à l’exécution du code suivant :
console.log(process.argv);
L’exécution de ce code donne le résultat suivant :
$ node cli.js --foo bar --baz
[ 'node', '/home/me/dev/args.js', '--foo', 'bar', '--baz' ]
On constate qu’en plus de contenir les différents paramètres, ce tableau contient également l’exécutable node en première position suivi du chemin absolu du script en cours d’exécution.
La première étape est de ne sélectionner que les paramètres qui sont réellement intéressants :
console.log(process.argv.slice(2));
Ce qui donne à l’exécution le résultat suivant :
$ node cli.js --foo bar --baz
[ 'node', '/home/me/dev/args.js', '--foo', 'bar', '--baz' ]
Notez qu’il n’est plus nécessaire de faire cette sélection soi-même quand on utilise le module exec-promise (voir la section Testabilité).
Une fois que les paramètres intéressants sont sélectionnés dans le tableau, il est nécessaire...
Testabilité
Dans l’idéal, un programme en ligne de commande devrait pouvoir se tester de la même façon qu’une bibliothèque.
Pour cela, cli.js est implémenté comme un vrai module et expose une fonction qui prend en paramètres les paramètres de la ligne de commande et retourne le résultat de l’exécution du programme.
Dans le cas où le programme est asynchrone, il suffit de retourner une promesse vers le futur résultat.
Il y a alors deux cas de sortie de succès : le message d’aide si --help est spécifié, sinon undefined (rien) une fois que le serveur se termine lorsque la méthode server.close() est appelée.
function main(args) {
// Traitement des paramètres.
var opts = ...;
if (opts.help) {
return 'Usage: mon-serveur [--port=<port>] <dossier>...';
}
// Initialisation d'express et du serveur web.
var server = ...;
// Pour l'exemple, le serveur se fermera automatiquement
// au bout d'une heure.
setTimeout(function () {
server.close();
}, 1 * 60 * 60 * 1000);
// Retourne une promesse qui sera résolue quand
// le serveur se fermera....
Exécution directe
Jusqu’ici, le module était exécutable via la commande node :
$ node cli.js --help
Usage: mon-serveur [--port=<port>] <dossier>...
Mais ce que l’on souhaite, c’est qu’il soit exécutable directement :
$ ./cli.js --help
Usage: mon-serveur [--port=<port>] <dossier>...
Pour cela, les systèmes d’exploitation basés sur Unix (BSD, GNU/Linux, Mac Os...) permettent de spécifier le chemin de l’interprète (ici node) à utiliser pour le script dans l’en-tête de ce dernier grâce à une syntaxe spéciale appelée shebang. Celle-ci consiste à écrire sur la toute première ligne du script, un caractère dièse (#) suivi d’un point d’exclamation (!) suivi du chemin absolu de l’interprète et optionnellement d’un paramètre :
#!/usr/bin/node
Si l’exemple est fonctionnel, il n’est pas optimal car il est nécessaire que node soit installé précisément dans le dossier /usr/bin, ce qui peut ne pas être le cas. C’est pourquoi on utilise la commande env qui permet de chercher l’interprète dans l’ensemble des chemins possibles (décrits par la variable d’environnement PATH) :
#!/usr/bin/env node
Sous Windows, npm embarque une couche de compatibilité...
Installation du programme
Dernière étape de la création du programme, celle de l’installation (globale) via npm.
Tout d’abord, il faut créer une description minimale du paquet dans le fichier package.json :
{
"private": true,
"name": "mon-serveur",
"version": "0.0.0",
"bin": {
"mon-serveur": "cli.js"
}
}
Outre les entrées private (private indique que le paquet ne peut pas être publié sur le registre public) name et version (qui sont obligatoires), on peut voir l’entrée bin. Celle-ci contient un dictionnaire qui associe le nom de commande à installer (ici mon-serveur) au module correspondant dans le répertoire du projet (cli.js).
On peut maintenant installer le programme et l’exécuter :
$ npm install --global .
$ mon-serveur --help
Usage: mon-serveur [--port=<port>] <dossier>...
La première ligne ordonne à npm d’installer globalement le paquet contenu dans le répertoire courant (.) et la seconde lance le programme.
Exemple complet
Voici le code source complet du module principal cli.js :
#!/bin/env node
'use strict';
var express = require('express');
var minimist = require('minimist');
var serveStatic = require('serve-static');
function main(args) {
var opts = minimist(args, {
boolean: ['help'],
string: ['port'],
default: {
port: '80'
}
});
if (opts.help) {
return 'Usage: mon-serveur [--port=<port>] <dossier>...';
}
var app = express();
opts._.forEach(function (directory) {
app.use(serveStatic(directory));
});
var server = app.listen(opts.port);
// Pour l'exemple, le serveur se fermera automatiquement
// au bout d'une heure.
setTimeout(function () {
server.close();
}...