Skip to content

Commit

Permalink
[#2355] Bugfix: The built-in help subcommand should return the exit…
Browse files Browse the repository at this point in the history
… code of the subcommand's `exitCodeOnUsageHelp` value for the subcommand whose help was requested

Closes #2355
  • Loading branch information
remkop committed Jan 5, 2025
1 parent 9e5e58e commit ec52e70
Show file tree
Hide file tree
Showing 3 changed files with 49 additions and 27 deletions.
4 changes: 4 additions & 0 deletions RELEASE-NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,15 @@ Artifacts in this release are signed by Remko Popma (6601 E5C0 8DCC BB96).

## <a name="4.7.7-new"></a> New and Noteworthy

The built-in `picocli.CommandLine.HelpCommand` subcommand now implements `Callable<Integer>` and returns the exit code of the subcommand's `exitCodeOnUsageHelp` value for the subcommand whose help was requested.

From this release, if a command implements both `Callable` and `Runnable`, then the default execution strategy will invoke the `call` method instead of the `run` method.



## <a name="4.7.7-fixes"></a> Fixed issues

* [#2355] Bugfix: The built-in `help` subcommand should return the exit code of the subcommand's `exitCodeOnUsageHelp` value for the subcommand whose help was requested. Thanks to [marco-brandizi](https://github.com/marco-brandizi) for raising this.
* [#2335] Bugfix: Module info missing in all jars except the main picocli jar file. Thanks to [Oliver B. Fischer](https://github.com/obfischer) for raising this.
* [#2331] Bugfix: AutoComplete with jline3 was showing hidden commands. Thanks to [clebertsuconic](https://github.com/clebertsuconic) for raising this.
* [#2291] Bugfix: NullPointerException when using PropertiesDefaultProvider. Thanks to [JessHolle](https://github.com/JessHolle) for raising this.
Expand Down
59 changes: 33 additions & 26 deletions src/main/java/picocli/CommandLine.java
Original file line number Diff line number Diff line change
Expand Up @@ -2013,8 +2013,8 @@ static Integer executeHelpRequest(List<CommandLine> parsedCommands) {
t.debug("helpCommand '%s' does not implement IHelpCommandInitializable2 or IHelpCommandInitializable...", fullName);
}
t.debug("Executing helpCommand '%s'...", fullName);
executeUserObject(parsed, new ArrayList<Object>());
return parsed.getCommandSpec().exitCodeOnUsageHelp();
List<Object> execResult = executeUserObject(parsed, new ArrayList<Object>());
return AbstractParseResultHandler.resolveExecutionResultAsExitCode(ExitCode.OK, execResult);
}
}
t.debug("Help was not requested. Continuing to process ParseResult...");
Expand All @@ -2024,34 +2024,34 @@ private static List<Object> executeUserObject(CommandLine parsed, List<Object> e
Tracer tracer = CommandLine.tracer();

Object command = parsed.getCommand();
if (command instanceof Runnable) {
if (command instanceof Callable) {
try {
tracer.debug("Invoking Runnable::run on user object %s@%s...", command.getClass().getName(), Integer.toHexString(command.hashCode()));
((Runnable) command).run();
parsed.setExecutionResult(null); // 4.0
executionResultList.add(null); // for compatibility with picocli 2.x
tracer.debug("Invoking Callable::call on user object %s@%s...", command.getClass().getName(), Integer.toHexString(command.hashCode()));
@SuppressWarnings("unchecked") Callable<Object> callable = (Callable<Object>) command;
Object executionResult = callable.call();
parsed.setExecutionResult(executionResult);
executionResultList.add(executionResult);
return executionResultList;
} catch (ParameterException ex) {
throw ex;
} catch (ExecutionException ex) {
throw ex;
} catch (Exception ex) {
throw new ExecutionException(parsed, "Error while running command (" + command + "): " + ex, ex);
throw new ExecutionException(parsed, "Error while calling command (" + command + "): " + ex, ex);
}
} else if (command instanceof Callable) {
} else if (command instanceof Runnable) {
try {
tracer.debug("Invoking Callable::call on user object %s@%s...", command.getClass().getName(), Integer.toHexString(command.hashCode()));
@SuppressWarnings("unchecked") Callable<Object> callable = (Callable<Object>) command;
Object executionResult = callable.call();
parsed.setExecutionResult(executionResult);
executionResultList.add(executionResult);
tracer.debug("Invoking Runnable::run on user object %s@%s...", command.getClass().getName(), Integer.toHexString(command.hashCode()));
((Runnable) command).run();
parsed.setExecutionResult(null); // 4.0
executionResultList.add(null); // for compatibility with picocli 2.x
return executionResultList;
} catch (ParameterException ex) {
throw ex;
} catch (ExecutionException ex) {
throw ex;
} catch (Exception ex) {
throw new ExecutionException(parsed, "Error while calling command (" + command + "): " + ex, ex);
throw new ExecutionException(parsed, "Error while running command (" + command + "): " + ex, ex);
}
} else if (command instanceof Method) {
try {
Expand Down Expand Up @@ -2298,19 +2298,23 @@ private int resolveExitCode(int exitCodeOnSuccess, R executionResult, List<IExit
t.debug("resolveExitCode: exit code generators resulted in exit code=%d", result);
if (executionResult instanceof List) {
List<?> resultList = (List<?>) executionResult;
for (Object obj : resultList) {
if (obj instanceof Integer) {
int exitCode = (Integer) obj;
if ((exitCode > 0 && exitCode > result) || (exitCode < result && result <= 0)) {
result = exitCode;
}
}
}
result = resolveExecutionResultAsExitCode(result, (List<?>) executionResult);
}
t.debug("resolveExitCode: execution results resulted in exit code=%d", result);
t.debug("resolveExitCode: returning exit code=%d", result == 0 ? exitCodeOnSuccess : result);
return result == 0 ? exitCodeOnSuccess : result;
}
static Integer resolveExecutionResultAsExitCode(int result, List<?> executionResultList) {
for (Object obj : executionResultList) {
if (obj instanceof Integer) {
int exitCode = (Integer) obj;
if ((exitCode > 0 && exitCode > result) || (exitCode < result && result <= 0)) {
result = exitCode;
}
}
}
return result;
}

/** Processes the specified {@code ParseResult} and returns the result as a list of objects.
* Implementations are responsible for catching any exceptions thrown in the {@code handle} method, and
Expand Down Expand Up @@ -15346,7 +15350,7 @@ static class AutoHelpMixin {
synopsisHeading = "%nUsage: ", helpCommand = true,
description = {"%nWhen no COMMAND is given, the usage help for the main command is displayed.",
"If a COMMAND is specified, the help for that command is shown.%n"})
public static final class HelpCommand implements IHelpCommandInitializable, IHelpCommandInitializable2, Runnable {
public static final class HelpCommand implements IHelpCommandInitializable, IHelpCommandInitializable2, Runnable, Callable<Integer> {

@Option(names = {"-h", "--help"}, usageHelp = true, descriptionKey = "helpCommand.help",
description = "Show usage help for the help command and exit.")
Expand All @@ -15365,9 +15369,10 @@ public static final class HelpCommand implements IHelpCommandInitializable, IHel
private Help.ColorScheme colorScheme;

/** Invokes {@link #usage(PrintStream, Help.ColorScheme) usage} for the specified command, or for the parent command. */
public void run() {
public void run() { call(); }
public Integer call() {
CommandLine parent = self == null ? null : self.getParent();
if (parent == null) { return; }
if (parent == null) { return ExitCode.OK; }
Help.ColorScheme colors = colorScheme != null ? colorScheme : Help.defaultColorScheme(ansi);
if (commands != null) {
Map<String, CommandLine> parentSubcommands = parent.getCommandSpec().subcommands();
Expand All @@ -15382,6 +15387,7 @@ public void run() {
} else {
subcommand.usage(out, colors); // for compatibility with pre-4.0 clients
}
return subcommand.getCommandSpec().exitCodeOnUsageHelp();
} else {
throw new ParameterException(parent, "Unknown subcommand '" + commands + "'.", null, commands);
}
Expand All @@ -15391,6 +15397,7 @@ public void run() {
} else {
parent.usage(out, colors); // for compatibility with pre-4.0 clients
}
return parent.getCommandSpec().exitCodeOnUsageHelp();
}
}
/** {@inheritDoc} */
Expand Down
13 changes: 12 additions & 1 deletion src/test/java/picocli/HelpSubCommandTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -529,4 +529,15 @@ public void testIssue1870StringIndexOutOfBounds() {
" txt).%n");
assertEquals(expected, actual);
}
}

@Command(name = "issue2355", exitCodeOnUsageHelp = 123)
static class Issue2355 implements Runnable {
public void run() {}
}

@Test
public void testIssue2355ExitCode() {
@Command(name = "top", subcommands = {Issue2355.class, HelpCommand.class}) class Top { }
int actual = new CommandLine(new Top()).execute("help", "issue2355");
assertEquals(123, actual);
}}

0 comments on commit ec52e70

Please sign in to comment.