Skip to content

feat: replace picocli with aesh for CLI parsing#2453

Open
stalep wants to merge 1 commit intojbangdev:mainfrom
stalep:feature/aesh-migration
Open

feat: replace picocli with aesh for CLI parsing#2453
stalep wants to merge 1 commit intojbangdev:mainfrom
stalep:feature/aesh-migration

Conversation

@stalep
Copy link
Copy Markdown

@stalep stalep commented Apr 28, 2026

Migrate jbang's CLI framework from picocli to aesh, leveraging aesh's compile-time annotation processor to eliminate runtime reflection and improve startup performance.

Key changes:

  • Replace all picocli annotations (@command, @option, @parameters) with aesh equivalents (@CommandDefinition, @GroupCommandDefinition, @option, @argument, @arguments, @OptionList, @OptionGroup, @mixin)
  • Convert Callable commands to Command with CommandResult return type
  • Port custom type converters, parameter consumers, and preprocessors to aesh Converter and OptionParser interfaces
  • Implement external plugin command discovery in help output
  • Add default value provider using aesh's post-parse processing
  • Wire mutual exclusion validation via aesh's exclusiveWith
  • Restore version checking, dynamic completion, option aliases, negatable options, and grouped help sections
  • Regenerate native-image configuration for aesh
  • Update tests for aesh CLI parsing

Performance (vs picocli, median):

  • Native image: 6ms vs 33ms (5.5x faster) for single commands
  • JDK 25: 109ms vs 228ms (2.1x faster)
  • JDK 11: 149ms vs 269ms (1.8x faster)

BaseCommand cmd = ((BaseCommand) usrobj);
cmd.realOut = System.out; // Reset the output stream to the original, just for testing
try {
BaseCommand cmd = JBang.parseCommand(args);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is this code actually doing ? first parse command and then an exception occurs and it then runs the actual command?

...remind me why ? :)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

parseCommand() gives us direct access to the command instance so we can call doCall() directly and capture the return value — this is what most unit tests need (inspect fields, check exit codes, etc.).

But parseCommand() uses a simplified parsing path that doesn't handle all edge cases (e.g., help requests, parse errors with usage output). When it fails with a CommandLineParserException, we fall back to JBang.execute() which runs through the full aesh runtime and handles those cases properly with correct exit codes and error messages.

In short: fast path for normal commands, fallback to full runtime for error/help scenarios.

Migrate jbang's CLI framework from picocli to aesh, leveraging aesh's
compile-time annotation processor to eliminate runtime reflection and
improve startup performance.

Key changes:
- Replace all picocli annotations (@command, @option, @parameters) with
  aesh equivalents (@CommandDefinition, @GroupCommandDefinition, @option,
  @argument, @arguments, @OptionList, @OptionGroup, @mixin)
- Convert Callable<Integer> commands to Command<CommandInvocation> with
  CommandResult return type
- Port custom type converters, parameter consumers, and preprocessors to
  aesh Converter and OptionParser interfaces
- Implement external plugin command discovery in help output
- Add default value provider using aesh's post-parse processing
- Wire mutual exclusion validation via aesh's exclusiveWith
- Restore version checking, dynamic completion, option aliases, negatable
  options, and grouped help sections
- Regenerate native-image configuration for aesh
- Update tests for aesh CLI parsing

Performance (vs picocli, median):
- Native image: 6ms vs 33ms (5.5x faster) for single commands
- JDK 25: 109ms vs 228ms (2.1x faster)
- JDK 11: 149ms vs 269ms (1.8x faster)

Picks up fixes for inherited option propagation (jbangdev#421) and
@OptionGroup keys without values (jbangdev#422).
@stalep stalep force-pushed the feature/aesh-migration branch from b6f51c7 to ad9ecbe Compare April 29, 2026 11:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants