Skip to content

Commit

Permalink
Merge pull request #1467 from fortify-ps/master
Browse files Browse the repository at this point in the history
  • Loading branch information
remkop committed Nov 24, 2021
2 parents df24ead + fda862a commit ff96cc9
Show file tree
Hide file tree
Showing 4 changed files with 815 additions and 60 deletions.
47 changes: 26 additions & 21 deletions src/main/java/picocli/AutoComplete.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
*/
package picocli;

import static java.lang.String.format;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
Expand All @@ -24,19 +26,25 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Callable;

import picocli.CommandLine.*;
import picocli.CommandLine.Model.PositionalParamSpec;
import picocli.CommandLine.Command;
import picocli.CommandLine.HelpCommand;
import picocli.CommandLine.IExecutionExceptionHandler;
import picocli.CommandLine.IFactory;
import picocli.CommandLine.Model.ArgSpec;
import picocli.CommandLine.Model.CommandSpec;
import picocli.CommandLine.Model.OptionSpec;

import static java.lang.String.*;
import picocli.CommandLine.Model.PositionalParamSpec;
import picocli.CommandLine.Option;
import picocli.CommandLine.Parameters;
import picocli.CommandLine.ParseResult;
import picocli.CommandLine.Spec;

/**
* Stand-alone tool that generates bash auto-complete scripts for picocli-based command line applications.
Expand Down Expand Up @@ -285,15 +293,15 @@ private static <K, T extends K> List<T> filter(List<T> list, Predicate<K> filter
}
private static class CommandDescriptor {
final String functionName;
final String parentWithoutTopLevelCommand;
final String commandName;
final CommandLine commandLine;
final CommandLine parent;

CommandDescriptor(String functionName, String commandName, CommandLine commandLine, CommandLine parent) {
CommandDescriptor(String functionName, String parentWithoutTopLevelCommand, String commandName, CommandLine commandLine) {
this.functionName = functionName;
this.parentWithoutTopLevelCommand = parentWithoutTopLevelCommand;
this.commandName = commandName;
this.commandLine = commandLine;
this.parent = parent;
}
}

Expand Down Expand Up @@ -480,30 +488,30 @@ public static String bash(String scriptName, CommandLine commandLine) {

private static List<CommandDescriptor> createHierarchy(String scriptName, CommandLine commandLine) {
List<CommandDescriptor> result = new ArrayList<CommandDescriptor>();
result.add(new CommandDescriptor("_picocli_" + scriptName, scriptName, commandLine, null));
createSubHierarchy(scriptName, commandLine, result);
result.add(new CommandDescriptor("_picocli_" + scriptName, "", scriptName, commandLine));
createSubHierarchy(scriptName, "", commandLine, result);
return result;
}

private static void createSubHierarchy(String scriptName, CommandLine commandLine, List<CommandDescriptor> out) {
private static void createSubHierarchy(String scriptName, String parentWithoutTopLevelCommand, CommandLine commandLine, List<CommandDescriptor> out) {
// breadth-first: generate command lists and function calls for predecessors + each subcommand
for (Map.Entry<String, CommandLine> entry : commandLine.getSubcommands().entrySet()) {
CommandSpec spec = entry.getValue().getCommandSpec();
if (spec.usageMessage().hidden()) { continue; } // #887 skip hidden subcommands
String commandName = entry.getKey(); // may be an alias
String full = spec.qualifiedName("_");
String withoutTopLevelCommand = full.substring(full.indexOf('_') + 1);
String withoutCommand = withoutTopLevelCommand.substring(0, withoutTopLevelCommand.lastIndexOf('_') + 1);
String functionName = "_picocli_" + scriptName + "_" + bashify(withoutCommand + commandName);
String functionNameWithoutPrefix = bashify(concat("_", parentWithoutTopLevelCommand.replace(' ', '_'), commandName));
String functionName = concat("_", "_picocli", scriptName, functionNameWithoutPrefix);

// remember the function name and associated subcommand so we can easily generate a function later
out.add(new CommandDescriptor(functionName, commandName, entry.getValue(), commandLine));
out.add(new CommandDescriptor(functionName, parentWithoutTopLevelCommand, commandName, entry.getValue()));
}

// then recursively do the same for all nested subcommands
for (Map.Entry<String, CommandLine> entry : commandLine.getSubcommands().entrySet()) {
if (entry.getValue().getCommandSpec().usageMessage().hidden()) { continue; } // #887 skip hidden subcommands
createSubHierarchy(scriptName, entry.getValue(), out);
String commandName = entry.getKey();
String newParent = concat(" ", parentWithoutTopLevelCommand, commandName);
createSubHierarchy(scriptName, newParent, entry.getValue(), out);
}
}

Expand Down Expand Up @@ -558,10 +566,7 @@ private static void generateFunctionCallsToArrContains(StringBuilder buff,

for (CommandDescriptor descriptor : hierarchy.subList(1, hierarchy.size())) { // skip top-level command
int count = functionCalls.size();
CommandSpec spec = descriptor.commandLine.getCommandSpec();
String full = spec.qualifiedName(" ");
String withoutTopLevelCommand = full.substring(spec.root().name().length() + 1,
full.length() - spec.name().length()) + descriptor.commandName;
String withoutTopLevelCommand = concat(" ", descriptor.parentWithoutTopLevelCommand, descriptor.commandName);

functionCalls.add(format(" if CompWordsContainsArray \"${cmds%2$d[@]}\"; then %1$s; return $?; fi\n", descriptor.functionName, count));
buff.append( format(" local cmds%2$d=(%1$s)\n", withoutTopLevelCommand, count));
Expand Down Expand Up @@ -899,7 +904,7 @@ private static boolean isPicocliModelObject(Object obj) {
}

private static void filterAndTrimMatchingPrefix(String prefix, List<CharSequence> candidates) {
List<CharSequence> replace = new ArrayList<CharSequence>();
Set<CharSequence> replace = new HashSet<CharSequence>();
for (CharSequence seq : candidates) {
if (seq.toString().startsWith(prefix)) {
replace.add(seq.subSequence(prefix.length(), seq.length()));
Expand Down
30 changes: 15 additions & 15 deletions src/test/java/picocli/AutoCompleteTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -120,30 +120,30 @@ public static void main(String[] args) {
static class Candidates extends ArrayList<String> {
Candidates() {super(Arrays.asList("aaa", "bbb", "ccc"));}
}
@Command(description = "First level subcommand 1")
@Command(description = "First level subcommand 1", aliases = {"sub1-alias"})
public static class Sub1 {
@Option(names = "--num", description = "a number") double number;
@Option(names = "--str", description = "a String") String str;
@Option(names = "--candidates", completionCandidates = Candidates.class, description = "with candidates") String[] str2;
}
@Command(description = "First level subcommand 2")
@Command(description = "First level subcommand 2", aliases = {"sub2-alias"})
public static class Sub2 {
@Option(names = "--num2", description = "another number") int number2;
@Option(names = {"--directory", "-d"}, description = "a directory") File[] directory;
@Parameters(arity = "0..1") Possibilities possibilities;
}
@Command(description = "Second level sub-subcommand 1")
@Command(description = "Second level sub-subcommand 1", aliases = {"sub2child1-alias"})
public static class Sub2Child1 {
@Option(names = {"-h", "--host"}, description = "a host") List<InetAddress> host;
}
@Command(description = "Second level sub-subcommand 2")
@Command(description = "Second level sub-subcommand 2", aliases = {"sub2child2-alias"})
public static class Sub2Child2 {
@Option(names = {"-u", "--timeUnit"}) private TimeUnit timeUnit;
@Option(names = {"-t", "--timeout"}) private long timeout;
@Parameters(completionCandidates = Candidates.class, description = "with candidates") String str2;
}

@Command(description = "Second level sub-subcommand 3")
@Command(description = "Second level sub-subcommand 3", aliases = {"sub2child3-alias"})
public static class Sub2Child3 {
@Parameters(index = "1..2") File[] files;
@Parameters(index = "3..*") List<InetAddress> other;
Expand Down Expand Up @@ -1082,13 +1082,13 @@ public void testComplete() {
spec.parser().collectErrors(true);
int cur = 500;

test(spec, a(), 0, 0, cur, l("--help", "--version", "-V", "-h", "sub1", "sub2"));
test(spec, a("-"), 0, 0, cur, l("--help", "--version", "-V", "-h", "sub1", "sub2"));
test(spec, a(), 0, 0, cur, l("--help", "--version", "-V", "-h", "sub1", "sub1-alias", "sub2", "sub2-alias"));
test(spec, a("-"), 0, 0, cur, l("--help", "--version", "-V", "-h", "sub1", "sub1-alias", "sub2", "sub2-alias"));
test(spec, a("-"), 0, 1, cur, l("-help", "-version", "V", "h"));
test(spec, a("-h"), 0, 1, cur, l("-help", "-version", "V", "h"));
test(spec, a("-h"), 0, 2, cur, l(""));
test(spec, a("s"), 0, 1, cur, l("ub1", "ub2"));
test(spec, a("sub1"), 0, 0, cur, l("--help", "--version", "-V", "-h", "sub1", "sub2"));
test(spec, a("s"), 0, 1, cur, l("ub1", "ub1-alias", "ub2", "ub2-alias"));
test(spec, a("sub1"), 0, 0, cur, l("--help", "--version", "-V", "-h", "sub1", "sub1-alias", "sub2", "sub2-alias"));
test(spec, a("sub1"), 1, 0, cur, l("--candidates", "--num", "--str"));
test(spec, a("sub1", "-"), 1, 0, cur, l("--candidates", "--num", "--str"));
test(spec, a("sub1", "-"), 1, 1, cur, l("-candidates", "-num", "-str"));
Expand All @@ -1109,22 +1109,22 @@ public void testComplete() {
test(spec, a("sub1", "--candidates", "a", "--"), 3, 2, cur, l("candidates", "num", "str"));
test(spec, a("sub1", "--num"), 2, 0, cur, l());
test(spec, a("sub1", "--str"), 2, 0, cur, l());
test(spec, a("sub2"), 1, 0, cur, l("--directory", "--num2", "-d", "Aaa", "Bbb", "Ccc", "subsub1", "subsub2"));
test(spec, a("sub2"), 1, 0, cur, l("--directory", "--num2", "-d", "Aaa", "Bbb", "Ccc", "sub2child1-alias", "sub2child2-alias", "subsub1", "subsub2"));
test(spec, a("sub2", "-"), 1, 1, cur, l("-directory", "-num2", "d"));
test(spec, a("sub2", "-d"), 2, 0, cur, l());
test(spec, a("sub2", "-d", "/"), 3, 0, cur, l("--directory", "--num2", "-d", "Aaa", "Bbb", "Ccc", "subsub1", "subsub2"));
test(spec, a("sub2", "-d", "/"), 3, 0, cur, l("--directory", "--num2", "-d", "Aaa", "Bbb", "Ccc", "sub2child1-alias", "sub2child2-alias", "subsub1", "subsub2"));
test(spec, a("sub2", "-d", "/", "-"), 3, 1, cur, l("-directory", "-num2", "d"));
test(spec, a("sub2", "-d", "/", "--"), 3, 2, cur, l("directory", "num2"));
test(spec, a("sub2", "-d", "/", "--n"), 3, 3, cur, l("um2"));
test(spec, a("sub2", "-d", "/", "--num2"), 3, 6, cur, l(""));
test(spec, a("sub2", "-d", "/", "--num2"), 4, 0, cur, l());
test(spec, a("sub2", "-d", "/", "--num2", "0"), 4, 1, cur, l());
test(spec, a("sub2", "-d", "/", "--num2", "0"), 5, 0, cur, l("--directory", "--num2", "-d", "Aaa", "Bbb", "Ccc", "subsub1", "subsub2"));
test(spec, a("sub2", "-d", "/", "--num2", "0", "s"), 5, 1, cur, l("ubsub1", "ubsub2"));
test(spec, a("sub2", "-d", "/", "--num2", "0"), 5, 0, cur, l("--directory", "--num2", "-d", "Aaa", "Bbb", "Ccc", "sub2child1-alias", "sub2child2-alias", "subsub1", "subsub2"));
test(spec, a("sub2", "-d", "/", "--num2", "0", "s"), 5, 1, cur, l("ub2child1-alias", "ub2child2-alias", "ubsub1", "ubsub2"));
test(spec, a("sub2", "A"), 1, 1, cur, l("aa"));
test(spec, a("sub2", "Aaa"), 1, 3, cur, l(""));
test(spec, a("sub2", "Aaa"), 2, 0, cur, l("--directory", "--num2", "-d", "Aaa", "Bbb", "Ccc", "subsub1", "subsub2"));
test(spec, a("sub2", "Aaa", "s"), 2, 1, cur, l("ubsub1", "ubsub2"));
test(spec, a("sub2", "Aaa"), 2, 0, cur, l("--directory", "--num2", "-d", "Aaa", "Bbb", "Ccc", "sub2child1-alias", "sub2child2-alias", "subsub1", "subsub2"));
test(spec, a("sub2", "Aaa", "s"), 2, 1, cur, l("ub2child1-alias", "ub2child2-alias", "ubsub1", "ubsub2"));
test(spec, a("sub2", "Aaa", "subsub1"), 3, 0, cur, l("--host", "-h"));
test(spec, a("sub2", "subsub1"), 2, 0, cur, l("--host", "-h"));
test(spec, a("sub2", "subsub2"), 2, 0, cur, l("--timeUnit", "--timeout", "-t", "-u", "aaa", "bbb", "ccc"));
Expand Down
Loading

0 comments on commit ff96cc9

Please sign in to comment.