Apache Commons CLI


Développer une application en ligne de commande avec Apache Commons CLI.


Ou comment gérer les paramètres passés au programme...

Problématique

Développer une application en ligne de commande en Java ne pose pas de problème en soit. En effet, il suffit de développer une méthode main et de traiter les arguments reçus dans la variable args.

 public static void main(String[] args) {
   String premiereOption = args[0];
   ...
 }

Dans une application de ce type, les arguments servent à paramétrer le comportement du programme et à injecter des valeurs à utiliser dans le traitements. Le problème, c'est que cette façon de procéder ne permet pas facilement de gérer des passages d'arguments nommés, optionnels ou obligatoire, à la Posix ou à la GNU. Il est bien possible de passer des propriétés, par l'option -D, mais là encore, le coté verbeux peut rebuter plus d'un utilisateur.

On notera que passer des arguments au programme n'est pas la seule façon de procéder. On peut aussi utiliser des fichiers properties, exploiter les possibilités de l'API Preference de Java ou utiliser des variables d'environnement.

Formats d'arguments

Options et données

Tout d'abord, il faut bien distinguer dans le pratiques le passage d'option et le passage de données en argument. Une donnée serait apr exemple le nom d'un fichier, alors que l'option serait par exemple "-f" pour indiquer qu'il faut prendre en compte un fichier. On voit ici que l'option n'est qu'un flag transmis à l'application.

Généralement, la données est passée de façon brute alors que l'option est préfixées, par un tiret ou un double tiret, sous Linux, ou par un slash sous Windows. Les données et options peuvent être associées dans une liste d'arguments, en les juxtaposant ou en les séparant par un signe prédéfini, comme "égal", par exemple.

Dans ce premier exemple, la donnée foo.tar.gz concerne l'option f uniquement parce qu'elle lui succède dans la liste des arguments :

tar -f foo.tar.gz

Dans ce deuxième exemple, le fait que la donnée foo.tar.gz concerne l'option file est indiqué par le signe "=". On notera que le choix de ce signe dépend uniquement d'une convention et qu'il pourrait être replcé par ":", ">", ou tout autre signe.

 tar --file=foo.tar.gz

Propriétés Java

Les propriétés Java sont préfixées par les deux caractère "-D" et utilisent le séparateur "=" entre l'option et la valeur. L'option est souvent constituée d'un ensemble de mots séparés par des points ("."). passées au format -Dsewatech.doitfast=true. Ce type d'argument est géré automatique lorsqu'il est passé à la JVM, mais pas quand il est passé au programme.

Avec ce premier format, l'argument peut être lu dans les system properties.

 java -Dsewatech.doit=true fr.sewatech.example.CLI
 public static void main(String[] args) {
   System.out.println(System.getProperty("sewatech.doit"));
 }

Par contre, avec ce format-ci, l'argument est passé dans le tableau args et doit être traité manuellement.

 java fr.sewatech.example.CLI -Dsewatech.doit=true 
 public static void main(String[] args) {
   System.out.println(args[0]);
 }


Options Posix

Le format d'option Posix est court, sur un caractère, avec un tiret ('-') devant chaque option ou devant un ensemble d'option. Par exemple, pour la commande tar, chaque lettre derrière le tiret correspond à une option :

 tar -zxvlf foo.tar.gz

Options GNU

Le format d'option GNU est plus long, mais plus lisible pour un humain. Chaque option est précédée par un double tiret et est écrite sous forme d'un mot ou d'une suite de motes séparés par des tirets simples.

 tar --gzip --verbose --extract --check-links --file=foo.tar.gz

Apache Commons CLI

L'utilitaire Commons CLI a été développer pour faciliter le travail du développeur. Il se charge de vérifier la liste des arguments, nous aide à présenter un message d'erreur en cas de problème, puis nous assiste dans la lecture des options et des valeurs associées.

Créer les options

Avant de créer les options, il faut instancier leur conteneur, de type Options.

 Options options = new Options();

Puis chaque option peut être créé directement en appelant la méthode addOption, dont il existe trois variantes.

 // Ajoute une option -a, sans argument, avec une description
 options.addOption("a", false, "Option a");
 
 // Ajoute une option -b, ou --blabla, avec un argument, avec une description
 options.addOption("b", "blabla", true, "Option a");

La troisième variante prend un objet Option en paramètre. Celui-ci doit être instancié via l'OptionBuilder. une partie de sa configuration peut se faire avec le builder, le reste de la configuration se fait ultérieurement sur l'option. Cette façon de procéder est plus verbeuse, mais apporte plus de souplesse.

 // Création de l'option -o, ou --out-file, avec un argument
 Option outFileOption = OptionBuilder.hasArg()
                                     .withLongOpt("out-file")
                                     .withDescription("URL du fichier cible")
                                     .create('o');
 // L'option est obligatoire
 outFileOption.setRequired(true);
 // L'option est ajoutée à la liste
 options.addOption(outFileOption);

Analyser les options et arguments

L'analyse des options et arguments transmis se fait par un CommandLineParser. Apache CLI en propose trois implémentations qui se différencient essentiellement par leur traitement des options longues.

Hiérarchie des parsers de Apache Common CLI : BasicParser, GnuParser et PosixParser héritent de la classe abstraite Parser, qui implémente l'interface CommandLineParser

Le BasicParser est donc rarement intéressant, sauf à vouloir imposer une rigueur teintée de pauvreté à l'utilisateur. Le PosixParser est généralement le plus souple d'utilisation. Selon la documentation, le GnuParser est adapté au traitement des options de -Dxxx=yyy, mais je n'ai pas (encore ?) trouvé celà convaincant.

La méthode parse renvoie un objet de type CommandLine qui nous permettra de lire toutes les options et tous les arguments.

 CommandLineParser parser = new PosixParser();
 CommandLine cmd = parser.parse(options, args);

Lecture des options et arguments

Le plus facile reste à faire : lire les options et arguments... Pour cela, on utilise l'objet de type CommandLine produit par le parser, et on lui demande les options, avec le méthode hasOption ou getOptions, puis les valeurs avec la méthode getOptionValue de CommandLine ou getValue de Option.

 // Option obligatoire, je ne teste donc pas sa présence
 outFile = cmd.getOptionValue('o');
 
 // Option facultative, je teste sa présence
 if (cmd.hasOption('a')) {
   aValue = cmd.getOptionValue('a');
 }
 // Option facultative, je passe une valeur par défaut
 bValue = cmd.getOptionValue('b', "Valeur par défaut pour b") ;

Erreurs de validation

En cas d'erreur lors de l'analyse des arguments, une ParseException se produit et fournit un message d'erreur qui indique quelles options posent problème.

 System.err.println("Parsing failed : " + e.getMessage());

La ParseException a cependant le défaut de ne proposer qu'un message en anglais, sans donner de détail sur les options incriminées. Pour avoir plus de détails, il faut accéder aux sous-classes.

Hiérarchie d'exceptions de Apache Common CLI : MissingOptionException, MissingArgumentException, UnrecognizedOptionException et AlreadySelectedException héritent de ParseException

Afficher l'aide

La classe HelpFormatter permet d'afficher un texte d'aide qui liste les options et arguments attendus, ainsi que le texte d'accompagnement.

 HelpFormatter formatter = new HelpFormatter();
 formatter.printHelp( "CLI", options );

Cette aide est affichée, de façon classique, sur les options --help ou --usage, et, éventuellement, quand l'utilisateur fait une erreur de saisie.

Conclusion

Cet exemple a été réalisé avec Apache Commons CLI 1.2. Il montre une façon simple d'exploiter cet utilitaire, qui peut encore être amélioré par l'utilisation d'une configuration externe (fichier XML ou properties).