picocli

repository·main·Indexed 26 days ago

https://github.com/remkop/picocli

A library for creating rich command line applications in Java using annotations to define commands, options, and parameters. It supports native binary compilation via GraalVM, code generation via annotation processing, and integration with Groovy scripts. Additionally, it provides shell integration components for JLine 2 and JLine 3, including TAB auto-completion via PicocliJLineCompleter and advanced completion with descriptions via PicocliCommands.

Tokens
66.2K
Snippets
172
Records
250
Agent score
90%

What's inside picocli

  1. Overview of picocli

    main

    picocli is a modern Java library and framework for creating rich command-line applications. It provides both an annotations API and a programmatic API, supporting features like ANSI colors, TAB autocompletion, and nested subcommands. It is designed to be lightweight and can be included in source form to avoid external dependencies.

    Key capabilities include:

    • Strong Typing: Converts command-line input into strongly typed data for named options and positional parameters.
    • GraalVM Support: Applications can be ahead-of-time compiled to native images for extremely fast startup and low memory usage. An annotation processor is available to automatically Graal-enable your JAR.
    • Advanced CLI Features: Supports git-like subcommands, POSIX-style grouped short options, custom type converters, password options, argument groups (mutually exclusive or dependent), and map options.
    • Integration: Easily integrates with Dependency Injection containers like Spring Boot (via picocli-spring-boot-starter), Micronaut, Quarkus, and Guice.
  2. Ecosystem Integrations and Adoption

    main

    picocli is widely adopted across various major Java-based frameworks and tools. Key integrations include:

    • Groovy: All Groovy command line tools are picocli-based; it powers the CliBuilder DSL.
    • Micronaut: The Micronaut CLI is rewritten with picocli, and it supports running microservices standalone.
    • Quarkus: Offers Command mode with picocli.
    • JUnit 5: Since version 5.3, ConsoleLauncher uses picocli to support @-files (argument files).
    • jbang: Uses picocli internally and provides a CLI template for generating picocli-enabled scripts.
    • GraalVM: Used in the CookieTemple cli-java template for building GraalVM native CLI executables.
    • Other notable users: Debian (libpicocli-java), Karate, Ballerina, CheckStyle, Apache Hadoop Ozone, Apache Hive, and Pinterest ktlint.
  3. Mix options and positional parameters on the command line

    main

    In picocli 2.0+, positional parameters can be mixed with options. Any command line argument that is not an option or a subcommand is interpreted as a positional parameter.

    Note: To support this, multi-value options (array, list, and map fields) are not greedy by default.

    class MixDemo implements Runnable {
      @Option(names = "-o")
      List<String> options;
    
      @Parameters
      List<String> positional;
    
      public void run() {
        System.out.println("positional: " + positional);
        System.out.println("options   : " + options);
      }
    
      public static void main(String[] args) {
        CommandLine.run(new MixDemo(), System.err, args);
      }
    }

    Example execution:

    $ java MixDemo param0 -o AAA param1 param2 -o BBB param3
    # Output:
    # positional: [param0, param1, param2, param3]
    # options   : [AAA, BBB]
  4. Run Picocli Legacy Tests in Java 5

    main

    This project is a standalone testing project used to build the main picocli project and run its tests specifically in a Java 5 environment. It is not a module of the main picocli build and does not publish artifacts.

    To run these tests, you must have Java 5 installed. If you have multiple Java versions, you must set the JAVA_HOME environment variable to point to your Java 5 installation directory before executing the Gradle wrapper.

    : (on Windows)
    : start command prompt if we are running in Powershell
    cmd
    cd picocli-tests-java567
    
    : set JAVA_HOME to your Java 5 directory
    set JAVA_HOME=C:\apps\jdk1.5.0_22
    
    : build the project
    gradlew clean build --no-daemon
  5. Create a command-line application with picocli

    main

    To create a CLI application, annotate a class with @Command and implement Runnable or Callable. Use @Option for command-line flags and @Parameters for positional arguments.

    You can execute the command in a single line using new CommandLine(new YourCommand()).execute(args). This method handles parsing, error reporting, and requests for usage or version help automatically. It returns an exit code that can be passed to System.exit() to signal success or failure to the caller.

    import picocli.CommandLine;
    import picocli.CommandLine.Command;
    import picocli.CommandLine.Option;
    import picocli.CommandLine.Parameters;
    import java.io.File;
    
    @Command(name = "example", mixinStandardHelpOptions = true, version = "Picocli example 4.0")
    public class Example implements Runnable {
    
        @Option(names = { "-v", "--verbose" },
          description = "Verbose mode. Helpful for troubleshooting. Multiple -v options increase the verbosity.")
        private boolean[] verbose = new boolean[0];
    
        @Parameters(arity = "1..*", paramLabel = "FILE", description = "File(s) to process.")
        private File[] inputFiles;
    
        public void run() {
            if (verbose.length > 0) {
                System.out.println(inputFiles.length + " files to process...");
            }
            if (verbose.length > 1) {
                for (File f : inputFiles) {
                    System.out.println(f.getAbsolutePath());
                }
            }
        }
    
        public static void main(String[] args) {
            int exitCode = new CommandLine(new Example()).execute(args);
            System.exit(exitCode);
        }
    }
  6. Handle custom error messages in converters

    main

    By default, exceptions thrown in converters result in a generic error message. To provide a specific, user-friendly error message, throw picocli.CommandLine.TypeConversionException instead of a standard exception.

    class InetSocketAddressConverter implements ITypeConverter<InetSocketAddress> {
        @Override
        public InetSocketAddress convert(String value) {
            int pos = value.lastIndexOf(':');
            if (pos < 0) {
                throw new TypeConversionException(
                    "Invalid format: must be 'host:port' but was '" + value + "'");
            }
            // ... implementation
        }
    }
  7. Automatic vs Manual Help Printing

    main

    When executing commands, certain CommandLine methods automatically print usage help if requested (e.g., via --help). Other methods require you to manually check the parse result and invoke CommandLine::usage or CommandLine::printVersionHelp.

    Methods that automatically print help:

    • CommandLine::execute
    • CommandLine::call
    • CommandLine::run
    • CommandLine::invoke
    • CommandLine::parseWithHandler (with built-in Run... handlers)
    • CommandLine::parseWithHandlers (with built-in Run... handlers)

    Methods that DO NOT automatically print help:

    • CommandLine::parse
    • CommandLine::parseArgs
    • CommandLine::populateCommand
  8. Force ANSI color output

    main

    Picocli automatically detects if the platform supports ANSI escape codes. You can override this behavior:

    1. System Property: Set picocli.ansi to true to force ANSI on, or false to force it off.
    2. Programmatically: Pass Ansi.ON or Ansi.OFF when invoking CommandLine.usage.
    import picocli.CommandLine.Help.Ansi;
    
    App app = CommandLine.usage(new App(), System.out, Ansi.OFF, args);
  9. Build an interactive shell with JLine 2 and picocli

    main

    You can combine JLine 2's ConsoleReader for handling console input and tokenization with picocli's command parsing to build powerful interactive shells.

    Key steps in the integration pattern:

    1. Initialize a ConsoleReader.
    2. Create a CommandLine instance using your top-level command.
    3. Add a PicocliJLineCompleter to the reader for TAB completion.
    4. Run a loop that reads lines from the reader, tokenizes them (e.g., using WhitespaceArgumentDelimiter), and executes them via CommandLine.execute().
    import java.io.IOException;
    import java.io.PrintWriter;
    import java.util.concurrent.Callable;
    import java.util.concurrent.TimeUnit;
    
    import jline.console.ConsoleReader;
    import jline.console.completer.ArgumentCompleter.ArgumentList;
    import jline.console.completer.ArgumentCompleter.WhitespaceArgumentDelimiter;
    import picocli.CommandLine;
    import picocli.CommandLine.Command;
    import picocli.CommandLine.IFactory;
    import picocli.CommandLine.Model.CommandSpec;
    import picocli.CommandLine.Option;
    import picocli.CommandLine.ParentCommand;
    import picocli.CommandLine.Spec;
    import picocli.shell.jline2.PicocliJLineCompleter;
    
    /**
     * Example that demonstrates how to build an interactive shell with JLine and picocli.
     * @since 3.7
     */
    public class Example {
    
        /**
         * Top-level command that just prints help.
         */
        @Command(name = "", description = "Example interactive shell with completion",
                footer = {"", "Press Ctrl-C to exit."},
                subcommands = {MyCommand.class, ClearScreen.class, ReadInteractive.class})
        static class CliCommands implements Runnable {
            final ConsoleReader reader;
            final PrintWriter out;
            
            @Spec
            private CommandSpec spec;
    
            CliCommands(ConsoleReader reader) {
                this.reader = reader;
                out = new PrintWriter(reader.getOutput());
            }
    
            public void run() {
                out.println(spec.commandLine().getUsageMessage());
            }
        }
    
        /**
         * A command with some options to demonstrate completion.
         */
        @Command(name = "cmd", mixinStandardHelpOptions = true, version = "1.0",
                description = "Command with some options to demonstrate TAB-completion" +
                        " (note that enum values also get completed)")
        static class MyCommand implements Runnable {
            @Option(names = {"-v", "--verbose"})
            private boolean[] verbosity = {};
    
            @Option(names = {"-d", "--duration"})
            private int amount;
    
            @Option(names = {"-u", "--timeUnit"})
            private TimeUnit unit;
    
            @ParentCommand CliCommands parent;
    
            public void run() {
                if (verbosity.length > 0) {
                    parent.out.printf("Hi there. You asked for %d %s.%n", amount, unit);
                } else {
                    parent.out.println("hi!");
                }
            }
        }
    
        /**
         * Command that clears the screen.
         */
        @Command(name = "cls", aliases = "clear", mixinStandardHelpOptions = true,
                description = "Clears the screen", version = "1.0")
        static class ClearScreen implements Callable<Void> {
    
            @ParentCommand CliCommands parent;
    
            public Void call() throws IOException {
                parent.reader.clearScreen();
                return null;
            }
        }
        
        /**
         * Command that optionally reads a password interactively.
         */
        @Command(name = "pwd", mixinStandardHelpOptions = true,
                description = "Interactively reads a password", version = "1.0")
        static class ReadInteractive implements Callable<Void> {
            
            @Option(names = {"-p"}, parameterConsumer = InteractiveParameterConsumer.class)
            private String password;
    
            @ParentCommand CliCommands parent;
    
            public Void call() throws Exception {
                if(password == null) {
                    parent.out.println("No password prompted");
                } else {
                    parent.out.println("Password is '" + password + "'");
                }
                return null;
            }
        }
        
        public static void main(String[] args) {
    
            // JLine 2 does not detect some terminal as not ANSI compatible (e.g  Eclipse Console)
            // See : https://github.com/jline/jline2/issues/185
            // This is an optional workaround which allow to use picocli heuristic instead :
            if (!Help.Ansi.AUTO.enabled() && //
                    Configuration.getString(TerminalFactory.JLINE_TERMINAL, TerminalFactory.AUTO).toLowerCase()
                            .equals(TerminalFactory.AUTO)) {
                TerminalFactory.configure(Type.NONE);
            }
    
            try {
                ConsoleReader reader = new ConsoleReader();
                IFactory factory = new CustomFactory(new InteractiveParameterConsumer(reader));
                
                // set up the completion
                CliCommands commands = new CliCommands(reader);
                CommandLine cmd = new CommandLine(commands, factory);
                reader.addCompleter(new PicocliJLineCompleter(cmd.getCommandSpec()));
    
                // start the shell and process input until the user quits with Ctrl-D
                String line;
                while ((line = reader.readLine("prompt> ")) != null) {
                    ArgumentList list = new WhitespaceArgumentDelimiter()
                        .delimit(line, line.length());
                    new CommandLine(commands, factory)
                        .execute(list.getArguments());
                }
            } catch (Throwable t) {
                t.printStackTrace();
            }
        }
    }