| # PIP-343: Use picocli instead of jcommander |
| |
| # Motivation |
| |
| We use the [jcommander](https://github.com/cbeust/jcommander) to build the CLI tool, which is a good library, and is |
| stable, but it misses modern CLI features likes autocompletion, flag/command suggestion, native image, etc. |
| |
| These features are very important because there are many commands in the CLI, but the jcommander doesn't give friendly |
| hints when we use incorrect flags/commands, which makes the user experience not very friendly. |
| |
| In modern times, the [picocli](https://github.com/remkop/picocli) supports these features, which is a popular library. |
| |
| The following is some comparison between jcommander and picocli: |
| |
| - Error prompt: |
| ``` |
| bin/pulsar-admin clusters update cluster-a -b |
| |
| # jcommander |
| Need to provide just 1 parameter |
| |
| # picocli |
| Unknown option: '-b' |
| ``` |
| |
| - Command suggestion: |
| ``` |
| bin/pulsar-admin cluste |
| |
| # jcommander |
| Expected a command, got cluste |
| |
| # picocli |
| Unmatched argument at index 0: 'cluste' |
| Did you mean: pulsar-admin clusters? |
| ``` |
| |
| # Goals |
| |
| ## In Scope |
| |
| Use the picocli instead of the jcommander in our CLI tool: |
| |
| - bin/pulsar |
| - bin/pulsar-admin |
| - bin/pulsar-client |
| - bin/pulsar-shell |
| - bin/pulsar-perf |
| |
| I'm sure this will greatly improve the user experience, and in the future we can also consider using native images to |
| reduce runtime, and improve the CLI document based on picocli. |
| |
| ## Out Scope |
| |
| This PR simply replaces jcommander and does not introduce any enhancements. |
| |
| In the CLI, [autocomplete](https://picocli.info/autocomplete.html) is an important feature, and after this PIP is |
| complete I will make a new PIP to support this feature. |
| |
| # Detailed Design |
| |
| ## Design & Implementation Details |
| |
| The jcommander and picocli have similar APIs, this will make the migration task very simple. |
| |
| This is [utility argument syntax](https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap12.html): |
| |
| ``` |
| utility_name[-a][-b][-c option_argument] |
| [-d|-e][-f[option_argument]][operand...] |
| ``` |
| |
| 1. Use `@Command` instead of `@Parameters` to define the class as a command: |
| |
| ```java |
| @Command(name = "my-command", description = "Operations on persistent topics") |
| public class MyCommand { |
| |
| } |
| ``` |
| |
| 2. Use `@Option` instead of `@Parameter` to defined the option of command: |
| |
| ```java |
| @Option(names = {"-r", "--role"}) |
| private String role; |
| ``` |
| |
| 3. Use `@Parameters` to get the operand of command: |
| |
| ```java |
| @Parameters(description = "persistent://tenant/namespace/topic", arity = "1") |
| private String topicName; |
| ``` |
| |
| 4. Migrate jcommander converter to picocli converter: |
| |
| ```java |
| public class TimeUnitToMillisConverter implements ITypeConverter<Long> { |
| @Override |
| public Long convert(String value) throws Exception { |
| return TimeUnit.SECONDS.toMillis(RelativeTimeUtil.parseRelativeTimeInSeconds(value)); |
| } |
| } |
| ``` |
| |
| 5. Add the picocli entrypoint: |
| |
| ```java |
| @Command |
| public class MyCommand implements Callable<Integer> { |
| // Picocli entrypoint. |
| @Override |
| public Integer call() throws Exception { |
| // TODO |
| // run(); |
| return 0; |
| } |
| } |
| ``` |
| |
| The above is a common migration approach, and then we need to consider pulsar-shell and custom command separately. |
| |
| - pulsar-shell |
| |
| This is an interactive shell based on jline3 and jcommander, which includes pulsar-admin and pulsar-client commands. |
| The jcommander does not provide autocompletion because we have implemented it ourselves. In picocli, they |
| have [picocli-shell-jline3](https://github.com/remkop/picocli/blob/main/picocli-shell-jline3) to help us quickly build |
| the interactive shell. |
| |
| - custom command: |
| |
| This is an extension of pulsar-admin, and the plugin's implementation does not depend on jcommander. Since the bridge |
| is used, we only need to change the generator code based on picocli. |
| |
| # Backward & Forward Compatibility |
| |
| Fully compatible. |
| |
| # Links |
| |
| * Mailing List discussion thread: https://lists.apache.org/thread/ydg1q064cd11pxwz693frtk4by74q32f |
| * Mailing List voting thread: https://lists.apache.org/thread/1bpsr6tkgm00bb66dt2s74r15o4b37s3 |