En este artículo les voy a explicar cómo podemos hacer uso de la nueva integración experimental de Quarkus con Picocli.

Con el release 1.5 de Quarkus se incorporó está nueva extensión que crear aplicaciones de línea de comando bastante enriquecidas y con muy poco código.

A mí particularmente me encanta poder escribir aplicaciones de línea de comando, me resulta más simple ejecutar diversas operaciones por ese medio que entrar a la interfaz gráfica equivalente o cuando debo programar procesos desatendidos.

Y ahora que está disponible la extensión en 1.5.0; ha llegado el momento de hacer algunas pruebas. Lo primero es crear un proyecto sin ningún endpoint de REST.

mvn io.quarkus:quarkus-maven-plugin:1.5.1.Final:create \
    -DprojectGroupId=dev.gerardo \
    -DprojectArtifactId=picocli-demo

Y vamos a agregar la dependencia de picocli

  mvn quarkus:add-extension
     -Dextensions="picocli"

Ahora vamos a crear una clase que será nuestro punto de entrada para nuestra aplicación. En nuestro ejemplo vamos a tener dos comandos, por lo cual nuestra clase sería:

@TopCommand
@CommandLine.Command(mixinStandardHelpOptions = true,
    subcommands = {Comando1.class, Comando2.class})
public class DemoApp {

}

Los comando iniciales van a ser muy sencillos.

@CommandLine.Command(name = "comando1",
    description = "Este comando ejecuta XYZ")
class Comando1 implements Runnable {
  @Override
   public void run() {
       System.out.println("Mi primer comando");
   }
}

@CommandLine.Command(name = "comando2",
    description = "Este comando ejecuta ABC")
class Comando1 implements Runnable {
  @Override
   public void run() {
       System.out.println("Mi segundo comando");
   }
}

Generamos nuestro app y lo ejecutamos.

mvn clean package
java -jar target/picocli-demo-1.0-SNAPSHOT-runner.jar

Lo que veriamos es esto:

Missing required subcommand
Usage: <main class> [-hV] [COMMAND]
  -h, --help      Show this help message and exit.
  -V, --version   Print version information and exit.
Commands:
  comando1  Este comando ejecuta XYZ
  comando2  Este comando ejecuta ABC

Noten que se muestran nuestros dos comandos con el texto descriptivo que colocamos, la manera de usar el app y un error pues omitimos un subcomando. El atributo mixinStandardHelpOptions agrega automáticamente las opciones --help y --version a nuestra aplicación.

Si ejecutamos el app usando el parametro comando1 tendremos:

java -jar target/picocli-demo-1.0-SNAPSHOT-runner.jar comando1

INFO  [io.quarkus] (main) Installed features: [cdi, picocli, resteasy]
Mi primer comando
INFO  [io.quarkus] (main) picocli-demo stopped in 0.034s

Pero no es suficiente sólo indicar un comando, pues generalmente debemos indicar argumentos dentro del comando y parámetros de entrada. Tomaremos el primer comando y le agregaremos dos argumentos uno de las cuales es obligatorio y el otra opcional.

@CommandLine.Option(names = {"-n", "--nombre"},
       description = "Nombre del usuario",
       required = true
       )
String nombre;

@CommandLine.Option(names = {"-s", "--sistema"},
     description = "Sistema a configurar",
     defaultValue = "S001"
       )
String sistema;

Es importante mencionar que la primer opción se puede ser indicada de dos maneras: con -n o con –nombre, lo mismo con la segunda; aunque esto es opcional y podemos dejar sólo una de ellas.

Si ejecutamos nuestra aplicación de nuevo con el comando1 tendríamos:

java -jar target/picocli-demo-1.0-SNAPSHOT-runner.jar comando1

Missing required option: '--nombre=<nombre>'
Usage: <main class> comando1 -n=<nombre> [-s=<sistema>]
Este comando ejecuta XYZ
  -n, --nombre=<nombre>     Nombre del usuario
  -s, --sistema=<sistema>   Sistema a configurar

Si pasamos el primer argumento tendremos esto:

java -jar target/picocli-demo-1.0-SNAPSHOT-runner.jar comando1
   -n Gerardo

[io.quarkus] (main) Installed features: [cdi, picocli, resteasy]
Mi primer comando [Gerardo] sistema [S001]

Para este ejercicio, vamos a asumir que recibimos un archivo como parámetro de entrada

@CommandLine.Parameters(index = "0",
   description = "Archivo de configuración",
   paramLabel = "Archivo")
File archivo;

y al ejecutar nuestro aplicación nos muestra esta ayuda, ahora con el nuevo parámetro.

Usage: <main class> comando1 -n=<nombre> [-s=<sistema>] <archivo>
Este comando ejecuta XYZ
      Archivo               Archivo de configuración
  -n, --nombre=<nombre>     Nombre del usuario
  -s, --sistema=<sistema>   Sistema a configurar

¿Qué sucede si queremos leer una opción, como una credencial, de manera interactiva?

Para eso hacemos lo siguiente:

@CommandLine.Option(names = {"-c", "--credencial"},
            arity = "0..1",
            description = "Credencial", interactive = true)
String credencial;

El atributo arity con valor 0..1 lo que nos dice es que si no brindamos la credencial como argumento, esta nos será solicitada de manera interactiva. Esta posibilidad resulta muy conveniente cuando nuestros aplicativos necesitan ser ejecutados de manera interactiva o en modo batch.

Al ejecutarlo el app luego de estos cambios tendríamos esta ayuda.

Usage: <main class> comando1 -c -n=<nombre> [-s=<sistema>] Archivo
Este comando ejecuta XYZ
      Archivo               Archivo de configuración
  -c, --credencial          Credencial
  -n, --nombre=<nombre>     Nombre del usuario
  -s, --sistema=<sistema>   Sistema a configurar

Si enviamos los parámetros que necesitamos, observariamos que de manera interactiva nos solicita el valor de la credencial.

java -jar target/picocli-demo-1.0-SNAPSHOT-runner.jar
    comando1 test.xml -n Gerardo -c
Enter value for --credencial (Credencial):

o bien,

java -jar target/picocli-demo-1.0-SNAPSHOT-runner.jar comando1
    test.xml -n Gerardo -c 123456

Además podemos agregar elementos a nuestro aplicativo, como versión, encabezado, entre otros. De esta manera:

@TopCommand
@CommandLine.Command(mixinStandardHelpOptions = true,
        subcommands = {Comando1.class, Comando2.class},
        version= "version 1.1",
        footerHeading = "2020 - Demo\n",
        headerHeading = "Gerardo.Dev Tool Set\n",
        description = "App que hace varias cosas"
)

Y se observaría así:

Gerardo.Dev Tool Set
Usage: <main class> [-hV] [COMMAND]
App que hace varias cosas
  -h, --help      Show this help message and exit.
  -V, --version   Print version information and exit.
Commands:
  comando1  Este comando ejecuta XYZ
  comando2  Este comando ejecuta ABC
2020 - Demo

Asimismo Picocli permite hacer sugerencias si el comando que brindamos no es claro, a manera de ilustración:

java -jar target/picocli-demo-1.0-SNAPSHOT-runner.jar co
Did you mean: comando2?

Por último, ya que usamos Quarkus podemos generar un aplicativo nativo por medio del comando.

mvn package -Pnative

Hecho esto tenemos un binario nativo el cual no requiere de tener instalado Java para funcionar.

Conclusión


Espero que este artículo les permita conocer como hacer aplicaciones de línea de comando por medio de Picocli y Quarkus.

El código fuente se encuentra disponible en github.