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

[#2104] Usage shows alias used rather than command name #2105

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 3 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
91 changes: 72 additions & 19 deletions src/main/java/picocli/CommandLine.java
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,25 @@ private void applyModelTransformations() {
}

private CommandLine copy() {
CommandLine result = new CommandLine(commandSpec.copy(), factory); // create a new sub-hierarchy
return copyWithSpec(commandSpec.copy());
}

/**
* Copies this {@link CommandLine} with the given alias and name switched.
* @param alias the alias to use instead of the name.
* @return a copy of this {@link CommandLine} with the given alias and name switched.
*/
private CommandLine copyWithAliasAsName(String alias) {
return copyWithSpec(commandSpec.copyWithAliasAsName(alias));
}

/**
* Copies this {@link CommandLine} with the given {@link CommandSpec}.
* @param commandSpec the {@link CommandSpec} to use instead.
* @return a copy of this {@link CommandLine} with the given {@link CommandSpec}.
*/
private CommandLine copyWithSpec(CommandSpec commandSpec) {
CommandLine result = new CommandLine(commandSpec, factory); // create a new sub-hierarchy
result.err = err;
result.out = out;
result.colorScheme = colorScheme;
Expand Down Expand Up @@ -6308,6 +6326,34 @@ private CommandSpec(CommandUserObject userObject) {
private CommandSpec copy() {
Object obj = userObject.type == null ? userObject.instance : userObject.type;
CommandSpec result = obj == null ? CommandSpec.create() : CommandSpec.forAnnotatedObject(obj, userObject.factory);
copyReferencesTo(result);
return result;
}

/**
* Copies this {@link CommandSpec} with the given alias and name switched.
* @param alias the alias to use instead of the name.
* @return a copy of this {@link CommandSpec} with the given alias and name switched.
*/
private CommandSpec copyWithAliasAsName(String alias) {
final CommandSpec result = new CommandSpec(userObject);
copyReferencesTo(result);
copyMapContentsTo(result);

result.name = alias;
result.aliases = new LinkedHashSet<>();
result.aliases.add(name);
result.aliases.addAll(aliases);
result.aliases.remove(alias);
return result;
}

/**
* Copies field references into the given {@link CommandSpec}.
*
* @param result the {@link CommandSpec} instance to copy values into.
*/
private void copyReferencesTo(CommandSpec result) {
result.commandLine = commandLine;
result.parent = parent;
result.methodParams = methodParams;
Expand All @@ -6334,25 +6380,28 @@ private CommandSpec copy() {
result.parser(parser);
result.inherited = inherited;
result.scopeType = scopeType;
}

// TODO if this CommandSpec was created/modified via the programmatic API,
// we need to copy all attributes that are modifiable via the programmatic API
// and point them to this CommandSpec instance.
// result.commands.clear(); result.commands.putAll(this.commands);
// result.optionsByNameMap.clear(); result.optionsByNameMap.putAll(this.optionsByNameMap);
// result.negatedOptionsByNameMap.clear(); result.negatedOptionsByNameMap.putAll(this.negatedOptionsByNameMap);
// result.posixOptionsByKeyMap.clear(); result.posixOptionsByKeyMap.putAll(this.posixOptionsByKeyMap);
// result.mixins.clear(); result.mixins.putAll(this.mixins);
// result.mixinAnnotatedElements.clear(); result.mixinAnnotatedElements.putAll(this.mixinAnnotatedElements);
// result.requiredArgs.clear(); result.requiredArgs.addAll(requiredArgs);
// result.args.clear(); result.args.addAll(args);
// result.options.clear(); result.options.addAll(options);
// result.positionalParameters.clear(); result.positionalParameters.addAll(positionalParameters);
// result.unmatchedArgs.clear(); result.unmatchedArgs.addAll(unmatchedArgs);
// result.specElements.clear(); result.specElements.addAll(specElements);
// result.parentCommandElements.clear(); result.parentCommandElements.addAll(parentCommandElements);
// result.groups.clear(); result.groups.addAll(groups);
return result;
/**
* Copies field map contents into the given {@link CommandSpec}.
*
* @param result the {@link CommandSpec} instance to copy map contents into.
*/
private void copyMapContentsTo(CommandSpec result) {
result.commands.clear(); result.commands.putAll(this.commands);
result.optionsByNameMap.clear(); result.optionsByNameMap.putAll(this.optionsByNameMap);
result.negatedOptionsByNameMap.clear(); result.negatedOptionsByNameMap.putAll(this.negatedOptionsByNameMap);
result.posixOptionsByKeyMap.clear(); result.posixOptionsByKeyMap.putAll(this.posixOptionsByKeyMap);
result.mixins.clear(); result.mixins.putAll(this.mixins);
result.mixinAnnotatedElements.clear(); result.mixinAnnotatedElements.putAll(this.mixinAnnotatedElements);
result.requiredArgs.clear(); result.requiredArgs.addAll(requiredArgs);
result.args.clear(); result.args.addAll(args);
result.options.clear(); result.options.addAll(options);
result.positionalParameters.clear(); result.positionalParameters.addAll(positionalParameters);
result.unmatchedArgs.clear(); result.unmatchedArgs.addAll(unmatchedArgs);
result.specElements.clear(); result.specElements.addAll(specElements);
result.parentCommandElements.clear(); result.parentCommandElements.addAll(parentCommandElements);
result.groups.clear(); result.groups.addAll(groups);
}

/** Creates and returns a new {@code CommandSpec} without any associated user object. */
Expand Down Expand Up @@ -13879,6 +13928,10 @@ else if (config().posixClusteredShortOptionsAllowed() && arg.length() > 2 && arg
}

private void processSubcommand(CommandLine subcommand, ParseResult.Builder builder, List<CommandLine> parsedCommands, Stack<String> args, Collection<ArgSpec> required, Set<ArgSpec> initialized, String[] originalArgs, List<Object> nowProcessing, String separator, String arg) {
if (!subcommand.getCommandName().equals(arg) && subcommand.getCommandSpec().aliases.contains(arg)) {
subcommand = subcommand.copyWithAliasAsName(arg);
}

Tracer tracer = CommandLine.tracer();
if (tracer.isDebug()) {
tracer.debug("Found subcommand '%s' (%s)", arg, subcommand.commandSpec.toString());}
Expand Down
18 changes: 18 additions & 0 deletions src/test/java/picocli/HelpSubCommandTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -529,4 +529,22 @@ public void testIssue1870StringIndexOutOfBounds() {
" txt).%n");
assertEquals(expected, actual);
}

@Test
public void testUsageHelpForAliasedSubcommands() {
@Command(name = "sub", aliases = "alias", mixinStandardHelpOptions = true) class Sub {}
@Command(name = "app", subcommands = {Sub.class}) class App {}

StringWriter out = new StringWriter();
CommandLine app = new CommandLine(new App(), new InnerClassFactory(this));
app.setOut(new PrintWriter(out));
ParseResult result = app.parseArgs("alias", "--help");
CommandLine.printHelpIfRequested(result);

final String expected = format("" +
"Usage: app alias [-hV]%n" +
" -h, --help Show this help message and exit.%n" +
" -V, --version Print version information and exit.%n");
assertEquals(expected, out.toString());
}
}