Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added HelpPreprocessor to greedily consume HelpCommand.commands #2126

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 21 additions & 1 deletion src/main/java/picocli/CommandLine.java
Original file line number Diff line number Diff line change
Expand Up @@ -15334,9 +15334,29 @@ static class AutoHelpMixin {
@Command(name = "help", header = "Display help information about the specified command.",
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"})
"If a COMMAND is specified, the help for that command is shown.%n"},
preprocessor = HelpCommand.HelpPreprocessor.class)
public static final class HelpCommand implements IHelpCommandInitializable, IHelpCommandInitializable2, Runnable {

/** Custom {@link IParameterPreprocessor} to greedily consume the {@link #commands} parameter
* when it matches a command, before repeatable subcommand processing consumes it instead.
*/
private static class HelpPreprocessor implements IParameterPreprocessor {
@Override
public boolean preprocess(Stack<String> args, CommandSpec commandSpec, ArgSpec argSpec, Map<String, Object> info) {
if (!args.isEmpty()) {
final String arg = args.peek();
if (commandSpec.parent().subcommands().get(arg) != null) {
final HelpCommand help = (HelpCommand) commandSpec.userObject();
help.commands = arg;
args.pop();
return true;
}
}
return false;
}
}

@Option(names = {"-h", "--help"}, usageHelp = true, descriptionKey = "helpCommand.help",
description = "Show usage help for the help command and exit.")
private boolean helpRequested;
Expand Down
79 changes: 79 additions & 0 deletions src/test/java/picocli/Issue2123RepeatableVsHelpTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package picocli;

import org.junit.Test;
import picocli.CommandLine.Command;
import picocli.CommandLine.HelpCommand;

import java.io.PrintWriter;
import java.io.StringWriter;

import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.object.HasToString.hasToString;
import static picocli.CommandLine.ExitCode.OK;

public class Issue2123RepeatableVsHelpTest {

@Command(name = "tool", mixinStandardHelpOptions = true, subcommandsRepeatable = true)
static class Tool {
}

@Command(name = "sub", mixinStandardHelpOptions = true)
static class Subcommand implements Runnable {
@Override
public void run() {
}
}

private final StringWriter out = new StringWriter();

private final CommandLine commandLine = new CommandLine(new Tool())
.addSubcommand(new Subcommand())
.addSubcommand(new HelpCommand())
.setOut(new PrintWriter(out))
.setErr(new PrintWriter(out));

@Test
public void testToolHelpShowsToolUsage() {
final int result = commandLine.execute("help");

assertThat(out, hasToString(containsString("Usage: tool [-hV]")));
assertThat(result, equalTo(OK));
}

@Test
public void testToolHelpSubShowsToolSubUsage() {
final int result = commandLine.execute("help", "sub");

assertThat(out, hasToString(containsString("Usage: tool sub")));
assertThat(result, equalTo(OK));
}

@Test
public void testToolDashHSubShowsToolSubUsage() {
final int result = commandLine.execute("-h", "sub");

assertThat(out, hasToString(containsString("Usage: tool [-hV]")));
assertThat(result, equalTo(OK));
}

@Test
public void testToolSubHelpShowsToolSubUsage() {
final int result = commandLine.execute("sub", "help");

// With subcommandsRepeatable = false, "help" wasn't allowed so returns USAGE showing usage for "sub"
// With subcommandsRepeatable = true, "help" is processed alone so returns OK showing usage for "tool"
// It would be helpful to note that "sub" is the latest command in use so return OK showing usage for "sub"
assertThat(out, hasToString(containsString("Usage: tool [-hV]")));
assertThat(result, equalTo(OK));
}

@Test
public void testToolSubDashHShowsToolSubUsage() {
final int result = commandLine.execute("sub", "-h");

assertThat(out, hasToString(containsString("Usage: tool sub")));
assertThat(result, equalTo(OK));
}
}
Loading