Command Line Interface en Java : tours d’horrizon

Pourquoi utiliser la ligne de commande CLI ?

  • Plus rapide pour faire un prototype lorsque l’inconnu technique n’ai pas l’interface graphique.
  • Plus rapide pour debuger
  • Plus puissant que les UI qui délibérement n’implémenter pas toutes les possibilités par souci d’ergonomie
  •  Quelques fois c’est même obligatoire sur linux ou autre unix serveur. Lorsque la ligne de command est la seul option. C’est le cas sur la pluspart des serveurs.

Développer pour la ligne de commande n’ai pas toujours pertiant et il existe évidemment des avantages en faveur de l’interface graphique.

Executer un jar en ligne de commande

Une fois compiler en jar executable il suffit de l’executer depuis votre ligne de commande.

java -jar mon-exec.jar

Afin d’envoyer des parametres:

java -jar mon-exec.jar unParamEnString

Dans cette exemple la string “unParamEnString” est envoyée dans la variable “args” du main de la class choisi:

package com.doduck.prototype.cli;
public class Main {
	public static void main(String[] args) {
		System.out.println("first argument => "+ args[0]);
	}
}

Affichant dans la console:

first argument => unParamEnString

Il ne reste plus qu’a interpréter les strings envoyés dans la variable “String[] args”. Pour cela il existe déjà de nombreuses solutions:

Les API pour CLI en Java

Télécharger le code source de ces test sur github
Prenez le temps de lire comment rendre un jar exécutable

Apache commons CLI

Une réference puisqu’il s’agit d’un projet Apache. Pourtant l’API est très limitée mais cette option est souvent suffisante.

	public static void main(String[] args) {
		Options options = new Options();
		options.addOption("myCmd", "myCommand", false, "will run myCommand()." );
		options.addOption("helloW", "helloWord", true, "display hello word the number of time specify." );

		try{
			CommandLine line = new BasicParser().parse( options, args );

			if( line.hasOption( "myCommand" ) ) {
				myCommand();
			}

			if(line.hasOption("helloWord")){
				String repeat = line.getOptionValue("helloWord");
				Integer repeatInt = new Integer(repeat);
				for(int i =0; i<repeatInt; i++){
					System.out.println( "Hello word !");
				}
			}

		}catch( ParseException exp ) {
		    System.out.println( "Unexpected exception:" + exp.getMessage() );
		}
	}

	public static void myCommand(){
		System.out.println("myCommand() just get call");
	}


Pour

  • Projet apache. Une référence.
  • Simple
  • POSIX,GNU,Java like options
  • Group d’option




Contre

  • Trop simple, pauvre en option
  • Arguments en string uniquement


jopt-simple

Jopt-simple comme son nom l’indique cherche à être le plus simple possible.
Permetant de définir la CLI en une seul ligne:

public static void main(String[] args) {
	OptionParser p = new OptionParser("fc:q::");
	p.accepts("ftp" );
	p.accepts("user").requiredIf("ftp").withRequiredArg();
	p.accepts("pass").requiredIf("ftp").withRequiredArg();

	OptionSet opt = p.parse(args);
	if(opt.has("f")){
		System.out.println("-f exist");
		System.out.println("-f with : "+ opt.valueOf("f"));
	}
	if(opt.has( "c" )){
		System.out.println("-c exist with:"+opt.valueOf("c"));
	}

	if(opt.has("ftp")){
		// we know username and password
		// existe if -ftp is set
		String user = (String) opt.valueOf("user");
		String pwd = (String) opt.valueOf("pass");
		System.out.println("user:"+user+" pass:"+pwd);
	}
}

La string “fc:q::” défini le parseur de CLI.
-f est optionel
-c est obligatoire
-q est une liste de parametres obligatoire

-ftp est optionel
-user est obligatoire si -ftp est renseigner.
-pass est obligatoire si -ftp est renseigner.

Lors qu’il s’agit de plus complexe jopt-simple reste simple à comprendre:

OptionParser p = new OptionParser();
OptionSpec f = p.accepts("file").withRequiredArg().ofType(File.class).defaultsTo(tempFile);


Pour

  • Simple, intuitif
  • Puissant
  • Bonne documentation
  • Alternative aux groups




Contre


naturalcli

Idée interesente. Rendre la ligne de commande lisible par un humain.

Voici un exemple pour crée la commande du genre:

send file /home/doduck/myFile.txt to ftp://ftpsrv/dir notify theBoss with "Please, see myFile.txt on the ftp"

Voici le code java:

	public static void main(String[] args) throws ExecutionException, InvalidSyntaxException {

		ICommandExecutor sendFile = new ICommandExecutor ()
        {
           public void execute(ParseResult pr) throws ExecutionException
           {
        	   String file = (String) pr.getParameterValue(0);
        	   String server = (String) pr.getParameterValue(1);
        	   String userName = (String) pr.getParameterValue(2);
        	   String msg = (String) pr.getParameterValue(3);

        	   // TODO send the file to the server...

        	   System.out.println("the file '"+file+"' was sent sucessfully");
        	   System.out.println("to '"+server+"' and the user '"+userName+"'");
        	   System.out.println("get notify with the message '"+msg+"'");
           }
        };

		Command cmd = new Command("send file  to  notify  with ",
			 "Send a file to a server with notification message",
			 sendFile);

	    Set<command></command> cs = new HashSet<command></command>();
<command></command>	    cs.add(cmd);
<command></command>	    new NaturalCLI(cs).execute(args);
<command></command>    }
<command></command>


Pour

  • facile a lire
  • commande facile a retenir




Contre

  • different type difficile
  • API difficile
  • pas de repo maven


JCommando

API très differente. Elle permet de définir des dependances entre les options.
Si le param A est resigner alors le param B est obligatoire.
Facilement on peux crée multiple commandes et dépendances d’arguments.

Dans un premier temps on “design” notre ligne de commande en XML:

<!--?xml version="1.0" encoding="UTF-8"?-->

      send file by ftp
      debug level
[...]

Puis grâce à une commande Ant on génère notre classe responsable du parsing de ligne de commande.
Pour exécuter ce projet consultés notre prototype sur github

La class générée aurai pu être crée à la main:

public abstract class Cli extends JCommandParser
{
   /**
     * JCommando generated constructor.
     */
   public Cli()
   {
      Option sendFtp = new Option();
      sendFtp.setId("sendFtp");
      sendFtp.setShortMnemonic("sendFtp");
      sendFtp.setLongMnemonic("sendFtp");
      sendFtp.setDescription("send file by ftp");
      addOption(sendFtp);

      Option isFtp = new Option();
      isFtp.setId("isFtp");
      isFtp.setShortMnemonic("isFtp");
      isFtp.setLongMnemonic("isFtp");
      isFtp.setDescription("isFtp");
      addOption(isFtp);
   [...]

Ensuite, il faut implementer cette class généré car elle est abstraite.
Le stockage des params doit ce faire à la main.

public class MyCLI extends Cli{

	private boolean isSendByFTP;
	private long debugLevel = 0;
	private boolean isSFtp;
	private String host;
	private String login;
	private String password;
	[...]

	@Override
	public void setSendFtp() {
		this.isSendByFTP = true;
	}
	@Override
	public void setIsFtp() {
		this.isSFtp = false;
	}
	@Override
	public void setIsSftp() {
		this.isSFtp = true;
	}
	@Override
	public void setHost(String host) {
		this.host = host;
	}

	@Override
	public void setLogin(String login) {
		this.login = login;
	}

	@Override
	public void setPwd(String pwd) {
		this.password = pwd;
	}

	@Override
	public void doExecute() {
		if(isSendByFTP){
			String protocol = this.isSFtp ? "SFTP" : "FTP";

			System.out.println("send a file by FTP");
			System.out.println("over the protocole "+protocol);
			System.out.println("host:"+this.host);
			System.out.println("login:"+this.login);
			System.out.println("password:"+this.password);

		}else if(isSendEmail){
			[...]
		}
	}
[...]
}

Enfin on lance la commande depuis le main:

public class Main {
    public static void main(String[] args) {
	MyCLI cli = new MyCLI();
	cli.parse(args);
    }
}


Pour

  • Facile de crée des dépendance de param (AND, OR, XOR, NOT)
  • Pour des CLI complexe en commandes / lage panel de commandes


Contre

  • Pauvre en retour lors d’erreur de commande. L’unique message utilisateur est “Incompatible options specified.”
  • Pauvre en typage
  • Ant est vieillissant
  • Pas de repo Maven
  • Plus Complexe


Jcommander

Solution très interesante à l’aide d’annotation. Idéal pour garder son code propre.

public class UploaderCommand {
	@Parameter(names = "-debug", description = "Debug mode")
	private boolean debug = false;

	@Parameter(names = "-file", description = "file to send", required = true)
	private String file = null;

	@Parameter(names = "-srv", description = "server to send the file to", required = true)
	private String srv = null;

	//[...Getter Setter...]
}
	public static void main(String[] args) {
		UploaderCommand uploader = new UploaderCommand();
		new JCommander(uploader, args);

		System.out.println("sending file: "+uploader.getFile());
		System.out.println("to srv: "+uploader.getSrv());
		System.out.println("debug enable: "+uploader.isDebug());
	}


Pour

  • code propre
  • annotation
  • facile




Contre

  • Pas de groups


clajr

De bonnes idées pour faire un prototype.
Il suffi de bien définir les noms de fonctions. Ensuite on les appel depuis l’exterieure grâce à la reflexion.
L’utilisatation de Clajr est limité pour la ligne de commande. Pourtant en réutilisant le code source on peux imaginer des utilisations bien plus intéresante !

	public static void main(String[] args) throws Throwable{
		CLAJR.parse(args, new Action());
    }
public class Action {

	public void _i__info(){
		System.out.println("Here is some information");
	}
	public void _p__print(String toPrint){
		System.out.println("let's print =>"+toPrint);
	}

}

La classe Action peut à present être appeller grâce à la commande

java -jar mon-exec.jar -i -p "hello world"

Ce qui affichera dans la console:

Here is some information
let's print =>hello world


Pour

  • Original
  • Une seul class java
  • D’autres utilité en perspective




Contre

  • Moins puissant que la compétition


jewelcli

jewelcli est très resemblant à jcommander.
Grâce aux annotation le code reste propre et les possibilités sont nombreuse.

Jewelcli est une très bonne librarie. La documentation est très claire, les possibilités nombreuse. Je manque simplement de temps pour la tester plus en détail.

Jargs

Simple et élégant.

JSAP

“Yet an other CLI library”.
Traditionelle.

args4j

Utilisation des annotations pour parcer la ligne de commande.
Similaire à Jewelcli et JCommander avec les generics.

parse-cmd

Parse-cmd ce démarque par la possibilité de définir des arguments matchant des regex.

Et il y en à encore bien d’autres


Exemple réél

Nous avons developper une application Dropbox en Java qui utilise la Ligne de Commande.
C’est un petit soft qui permet de passer de gros fichier entre client-serveur sans FTP.

Conclusion

Il existe d’innombrables libraries pour résoudre ce problème.
Recommander une librarie plutôt qu’une autre n’a pas lieu d’être. Tous dépend de vos besoin.
Prenez le temps de tester, prototyper, survoler la documentation afin de trouver chaussure a votre pied.

Laisser un commentaire