From 4e7e7336bb4394cf3e4f5a443c28b7e400b6c2ee Mon Sep 17 00:00:00 2001
From: Loic Rouchon
Date: Tue, 16 Mar 2021 19:37:23 +0100
Subject: [PATCH 1/5] #1346 Add pathCompletionTypes attribute to CommandLine
---
src/main/java/picocli/CommandLine.java | 42 ++++++++++++++++++++++++++
1 file changed, 42 insertions(+)
diff --git a/src/main/java/picocli/CommandLine.java b/src/main/java/picocli/CommandLine.java
index 93a691d11..291271174 100644
--- a/src/main/java/picocli/CommandLine.java
+++ b/src/main/java/picocli/CommandLine.java
@@ -150,6 +150,7 @@ public class CommandLine {
private final Tracer tracer = new Tracer();
private CommandSpec commandSpec;
private final Interpreter interpreter;
+ private final Set pathCompletionTypes = new HashSet();
private final IFactory factory;
private Object executionResult;
@@ -229,6 +230,12 @@ private CommandLine(Object command, IFactory factory, boolean userCalled) {
if (userCalled) { this.applyModelTransformations(); }
commandSpec.validate();
if (commandSpec.unmatchedArgsBindings().size() > 0) { setUnmatchedArgumentsAllowed(true); }
+ registerDefaultPathCompletionTypes();
+ }
+
+ private void registerDefaultPathCompletionTypes() {
+ pathCompletionTypes.add("java.io.File");
+ pathCompletionTypes.add("java.nio.file.Path");
}
/** Apply transformers to command spec recursively. */
@@ -253,6 +260,8 @@ private CommandLine copy() {
result.interpreter.converterRegistry.clear();
result.interpreter.converterRegistry.putAll(interpreter.converterRegistry);
+ result.pathCompletionTypes.clear();
+ result.pathCompletionTypes.addAll(pathCompletionTypes);
return result;
}
@@ -3277,6 +3286,39 @@ public CommandLine registerConverter(Class cls, ITypeConverter convert
return this;
}
+ /**
+ *
Adds the type {@code type} to the list of supported type for path completion.
+ *
Built-in supported types being:
+ *
+ *
{@link java.io.File}
+ *
{@link java.nio.file.Path}
+ *
+ *
+ * type {@code type}.
+ * @param type the type to check if path completion is supported for
+ * @return this CommandLine object, to allow method chaining
+ * @see #supportsPathCompletion(Class)
+ */
+ public CommandLine registerForPathCompletion(Class cls) {
+ pathCompletionTypes.add(cls.getName());
+ for (CommandLine command : getCommandSpec().commands.values()) {
+ command.registerForPathCompletion(cls);
+ }
+ return this;
+ }
+
+ /**
+ * Returns {@code true} if the CommandLine supports path completion for {@link Option} and {@link Parameters} of
+ * type {@code type}.
+ * @param type the type to check if path completion is supported for
+ * @return {@code true} if the CommandLine supports path completion for {@link Option} and {@link Parameters} of
+ * type {@code type}.
+ * @see #registerForPathCompletion(Class)
+ */
+ public boolean supportsPathCompletion(Class> type) {
+ return pathCompletionTypes.contains(type.getName());
+ }
+
/** Returns the String that separates option names from option values when parsing command line options.
* @return the String the parser uses to separate option names from option values
* @see ParserSpec#separator() */
From 4388af4798b57c64032c44ba77621247f802de88 Mon Sep 17 00:00:00 2001
From: Loic Rouchon
Date: Tue, 16 Mar 2021 19:39:22 +0100
Subject: [PATCH 2/5] #1346 Add custom types for AutoComplete path completion
---
src/main/java/picocli/AutoComplete.java | 29 +++++++++++++++++--------
1 file changed, 20 insertions(+), 9 deletions(-)
diff --git a/src/main/java/picocli/AutoComplete.java b/src/main/java/picocli/AutoComplete.java
index 5c6b224dd..f0ec9bd60 100644
--- a/src/main/java/picocli/AutoComplete.java
+++ b/src/main/java/picocli/AutoComplete.java
@@ -149,6 +149,10 @@ private static class App implements Callable {
"as the completion script.")
boolean writeCommandScript;
+ @Option(names = {"-p", "--pathTypes"}, split=",", description = "Comma-separated list of fully "
+ + "qualified custom types for which to delegate to built-in path name completion.")
+ List pathCompletionTypes = new ArrayList();
+
@Option(names = {"-f", "--force"}, description = "Overwrite existing script files.")
boolean overwriteIfExists;
@@ -162,6 +166,11 @@ public Integer call() throws Exception {
Class> cls = Class.forName(commandLineFQCN);
Object instance = factory.create(cls);
CommandLine commandLine = new CommandLine(instance, factory);
+ for (String className : pathCompletionTypes) {
+ // TODO implement error handling if the class is not on the classpath
+ Class> pathCompletionClass = Class.forName(className);
+ commandLine.registerForPathCompletion(pathCompletionClass);
+ }
if (commandName == null) {
commandName = commandLine.getCommandName(); //new CommandLine.Help(commandLine.commandDescriptor).commandName;
@@ -288,7 +297,7 @@ private static class CommandDescriptor {
final String commandName;
final CommandLine commandLine;
final CommandLine parent;
-
+
CommandDescriptor(String functionName, String commandName, CommandLine commandLine, CommandLine parent) {
this.functionName = functionName;
this.commandName = commandName;
@@ -651,7 +660,7 @@ private static String generateFunctionForCommand(String functionName, String com
// sql.Types?
// Now generate the "case" switches for the options whose arguments we can generate completions for
- buff.append(generateOptionsSwitch(argOptionFields));
+ buff.append(generateOptionsSwitch(commandLine, argOptionFields));
// Generate completion lists for positional params with a known set of valid values (including java enums)
for (PositionalParamSpec f : commandSpec.positionalParameters()) {
@@ -660,7 +669,7 @@ private static String generateFunctionForCommand(String functionName, String com
}
}
- String paramsCases = generatePositionalParamsCases(commandSpec.positionalParameters(), "", "${curr_word}");
+ String paramsCases = generatePositionalParamsCases(commandLine, commandSpec.positionalParameters(), "", "${curr_word}");
String posParamsFooter = "";
if (paramsCases.length() > 0) {
String POSITIONAL_PARAMS_FOOTER = "" +
@@ -696,7 +705,8 @@ private static List extract(Iterable generator) {
return result;
}
- private static String generatePositionalParamsCases(List posParams, String indent, String currWord) {
+ private static String generatePositionalParamsCases(
+ CommandLine commandLine, List posParams, String indent, String currWord) {
StringBuilder buff = new StringBuilder(1024);
for (PositionalParamSpec param : posParams) {
if (param.hidden()) { continue; } // #887 skip hidden params
@@ -711,7 +721,7 @@ private static String generatePositionalParamsCases(List po
if (param.completionCandidates() != null) {
buff.append(format("%s %s (( currIndex >= %d && currIndex <= %d )); then\n", indent, ifOrElif, min, max));
buff.append(format("%s positionals=$( compgen -W \"$%s_pos_param_args\" -- \"%s\" )\n", indent, paramName, currWord));
- } else if (type.equals(File.class) || "java.nio.file.Path".equals(type.getName())) {
+ } else if (commandLine.supportsPathCompletion(type)) {
buff.append(format("%s %s (( currIndex >= %d && currIndex <= %d )); then\n", indent, ifOrElif, min, max));
buff.append(format("%s compopt -o filenames\n", indent));
buff.append(format("%s positionals=$( compgen -f -- \"%s\" ) # files\n", indent, currWord));
@@ -727,8 +737,8 @@ private static String generatePositionalParamsCases(List po
return buff.toString();
}
- private static String generateOptionsSwitch(List argOptions) {
- String optionsCases = generateOptionsCases(argOptions, "", "${curr_word}");
+ private static String generateOptionsSwitch(CommandLine commandLine, List argOptions) {
+ String optionsCases = generateOptionsCases(commandLine, argOptions, "", "${curr_word}");
if (optionsCases.length() == 0) {
return "";
@@ -742,7 +752,8 @@ private static String generateOptionsSwitch(List argOptions) {
+ " esac\n";
}
- private static String generateOptionsCases(List argOptionFields, String indent, String currWord) {
+ private static String generateOptionsCases(
+ CommandLine commandLine, List argOptionFields, String indent, String currWord) {
StringBuilder buff = new StringBuilder(1024);
for (OptionSpec option : argOptionFields) {
if (option.hidden()) { continue; } // #887 skip hidden options
@@ -755,7 +766,7 @@ private static String generateOptionsCases(List argOptionFields, Str
buff.append(format("%s COMPREPLY=( $( compgen -W \"${%s_option_args}\" -- \"%s\" ) )\n", indent, bashify(option.paramLabel()), currWord));
buff.append(format("%s return $?\n", indent));
buff.append(format("%s ;;\n", indent));
- } else if (type.equals(File.class) || "java.nio.file.Path".equals(type.getName())) {
+ } else if (commandLine.supportsPathCompletion(type)) {
buff.append(format("%s %s)\n", indent, concat("|", option.names()))); // " -f|--file)\n"
buff.append(format("%s compopt -o filenames\n", indent));
buff.append(format("%s COMPREPLY=( $( compgen -f -- \"%s\" ) ) # files\n", indent, currWord));
From 2b818646c4353ca649e4972abc0bc44ff7dc7ef6 Mon Sep 17 00:00:00 2001
From: Loic Rouchon
Date: Tue, 16 Mar 2021 19:42:26 +0100
Subject: [PATCH 3/5] #1346 Add test for AutoComplete custom types path
completion
---
src/main/java/picocli/AutoComplete.java | 2 +-
src/test/java/picocli/AutoCompleteTest.java | 119 +++++++++++++++++++-
2 files changed, 118 insertions(+), 3 deletions(-)
diff --git a/src/main/java/picocli/AutoComplete.java b/src/main/java/picocli/AutoComplete.java
index f0ec9bd60..eca8639e8 100644
--- a/src/main/java/picocli/AutoComplete.java
+++ b/src/main/java/picocli/AutoComplete.java
@@ -149,7 +149,7 @@ private static class App implements Callable {
"as the completion script.")
boolean writeCommandScript;
- @Option(names = {"-p", "--pathTypes"}, split=",", description = "Comma-separated list of fully "
+ @Option(names = {"-p", "--pathCompletionTypes"}, split=",", description = "Comma-separated list of fully "
+ "qualified custom types for which to delegate to built-in path name completion.")
List pathCompletionTypes = new ArrayList();
diff --git a/src/test/java/picocli/AutoCompleteTest.java b/src/test/java/picocli/AutoCompleteTest.java
index de5a57b89..21cebb387 100644
--- a/src/test/java/picocli/AutoCompleteTest.java
+++ b/src/test/java/picocli/AutoCompleteTest.java
@@ -15,6 +15,7 @@
*/
package picocli;
+import java.util.regex.Pattern;
import org.hamcrest.CoreMatchers;
import org.junit.Rule;
import org.junit.Test;
@@ -247,7 +248,8 @@ private static String toString(Object obj) {
private static final String AUTO_COMPLETE_APP_USAGE = String.format("" +
"Usage: picocli.AutoComplete [-fhVw] [-c=] [-n=]%n" +
- " [-o=] [@...]%n" +
+ " [-o=] [-p=%n" +
+ " [,...]]... [@...]%n" +
" %n" +
"Generates a bash completion script for the specified command class.%n" +
" [@...] One or more argument files containing options.%n" +
@@ -272,6 +274,10 @@ private static String toString(Object obj) {
" the current directory.%n" +
" -w, --writeCommandScript Write a '' sample command script to%n" +
" the same directory as the completion script.%n" +
+ " -p, --pathCompletionTypes=[,...]%n" +
+ " Comma-separated list of fully qualified custom%n" +
+ " types for which to delegate to built-in path%n" +
+ " name completion.%n" +
" -f, --force Overwrite existing script files.%n" +
" -h, --help Show this help message and exit.%n" +
" -V, --version Print version information and exit.%n" +
@@ -761,7 +767,7 @@ private String expectedCompletionScriptForAutoCompleteApp() {
"\n" +
" local commands=\"\"\n" +
" local flag_opts=\"-w --writeCommandScript -f --force -h --help -V --version\"\n" +
- " local arg_opts=\"-c --factory -n --name -o --completionScript\"\n" +
+ " local arg_opts=\"-c --factory -n --name -o --completionScript -p --pathCompletionTypes\"\n" +
"\n" +
" compopt +o default\n" +
"\n" +
@@ -777,6 +783,9 @@ private String expectedCompletionScriptForAutoCompleteApp() {
" COMPREPLY=( $( compgen -f -- \"${curr_word}\" ) ) # files\n" +
" return $?\n" +
" ;;\n" +
+ " -p|--pathCompletionTypes)\n" +
+ " return\n" +
+ " ;;\n" +
" esac\n" +
"\n" +
" if [[ \"${curr_word}\" == -* ]]; then\n" +
@@ -1822,5 +1831,111 @@ public void run() {
static class NestedLevel1 implements Runnable {
public void run() {
}
+
+ }
+
+ @Test
+ public void testPathCompletionOnCustomTypes() throws IOException {
+ final String commandName = "bestCommandEver";
+ final File completionScript = new File(commandName + "_completion");
+ if (completionScript.exists()) {assertTrue(completionScript.delete());}
+ completionScript.deleteOnExit();
+
+ AutoComplete.main(String.format("--name=%s", commandName),
+ String.format("--pathCompletionTypes=%s,%s",
+ PathCompletionCommand.CustomPath1.class.getName(),
+ PathCompletionCommand.CustomPath2.class.getName()),
+ "picocli.AutoCompleteTest$PathCompletionCommand");
+
+ byte[] completion = readBytes(completionScript);
+ assertTrue(completionScript.delete());
+
+ String expected = expectedCommandCompletion(commandName,
+ "function _picocli_bestCommandEver() {\n" +
+ " # Get completion data\n" +
+ " local curr_word=${COMP_WORDS[COMP_CWORD]}\n" +
+ " local prev_word=${COMP_WORDS[COMP_CWORD-1]}\n" +
+ "\n" +
+ " local commands=\"\"\n" +
+ " local flag_opts=\"\"\n" +
+ " local arg_opts=\"--file --custom-path-1 --custom-path-2 --custom-type\"\n" +
+ "\n" +
+ " compopt +o default\n" +
+ "\n" +
+ " case ${prev_word} in\n" +
+ " --file)\n" +
+ " compopt -o filenames\n" +
+ " COMPREPLY=( $( compgen -f -- \"${curr_word}\" ) ) # files\n" +
+ " return $?\n" +
+ " ;;\n" +
+ " --custom-path-1)\n" +
+ " compopt -o filenames\n" +
+ " COMPREPLY=( $( compgen -f -- \"${curr_word}\" ) ) # files\n" +
+ " return $?\n" +
+ " ;;\n" +
+ " --custom-path-2)\n" +
+ " compopt -o filenames\n" +
+ " COMPREPLY=( $( compgen -f -- \"${curr_word}\" ) ) # files\n" +
+ " return $?\n" +
+ " ;;\n" +
+ " --custom-type)\n" +
+ " return\n" +
+ " ;;\n" +
+ " esac\n" +
+ "\n" +
+ " if [[ \"${curr_word}\" == -* ]]; then\n" +
+ " COMPREPLY=( $(compgen -W \"${flag_opts} ${arg_opts}\" -- \"${curr_word}\") )\n" +
+ " else\n" +
+ " local positionals=\"\"\n" +
+ " COMPREPLY=( $(compgen -W \"${commands} ${positionals}\" -- \"${curr_word}\") )\n" +
+ " fi\n" +
+ "}\n");
+
+ assertEquals(expected, new String(completion, "UTF8"));
+ }
+
+ private String expectedCommandCompletion(String commandName, String autoCompleteFunctionContent) {
+ String expected = expectedCompletionScriptForAutoCompleteApp()
+ .replaceAll("picocli\\.AutoComplete", commandName);
+ expected = Pattern.compile("function _picocli_" + commandName + "\\(\\) \\{\n"
+ + "(.+)\n"
+ + "}\n"
+ + "\n"
+ + "# Define a completion specification \\(a compspec\\) for the", Pattern.DOTALL)
+ .matcher(expected)
+ .replaceFirst(autoCompleteFunctionContent.replace("$", "\\$") +
+ "\n# Define a completion specification (a compspec) for the");
+ return expected;
+ }
+
+ @Command(name = "PathCompletion")
+ static class PathCompletionCommand implements Runnable {
+
+ @Option(names = "--file")
+ private File file;
+
+ @Option(names = "--custom-path-1")
+ private CustomPath1 customPath1;
+
+ @Option(names = "--custom-path-2")
+ private List customPath2;
+
+ @Option(names = "--custom-type")
+ private CustomType customType;
+
+ public void run() {
+ }
+
+ static class CustomPath1 {
+ String value;
+ }
+
+ static class CustomPath2 {
+ String value;
+ }
+
+ static class CustomType {
+ String value;
+ }
}
}
From a600a16508dc6bc8d74b7a83ed6c90ac690c6fb2 Mon Sep 17 00:00:00 2001
From: Loic Rouchon
Date: Thu, 18 Mar 2021 19:18:53 +0100
Subject: [PATCH 4/5] Revert "#1346 Add pathCompletionTypes attribute to
CommandLine"
This reverts commit b9fdd697522afd032feb60c94dfef9b9f065752c.
---
src/main/java/picocli/CommandLine.java | 42 --------------------------
1 file changed, 42 deletions(-)
diff --git a/src/main/java/picocli/CommandLine.java b/src/main/java/picocli/CommandLine.java
index 291271174..93a691d11 100644
--- a/src/main/java/picocli/CommandLine.java
+++ b/src/main/java/picocli/CommandLine.java
@@ -150,7 +150,6 @@ public class CommandLine {
private final Tracer tracer = new Tracer();
private CommandSpec commandSpec;
private final Interpreter interpreter;
- private final Set pathCompletionTypes = new HashSet();
private final IFactory factory;
private Object executionResult;
@@ -230,12 +229,6 @@ private CommandLine(Object command, IFactory factory, boolean userCalled) {
if (userCalled) { this.applyModelTransformations(); }
commandSpec.validate();
if (commandSpec.unmatchedArgsBindings().size() > 0) { setUnmatchedArgumentsAllowed(true); }
- registerDefaultPathCompletionTypes();
- }
-
- private void registerDefaultPathCompletionTypes() {
- pathCompletionTypes.add("java.io.File");
- pathCompletionTypes.add("java.nio.file.Path");
}
/** Apply transformers to command spec recursively. */
@@ -260,8 +253,6 @@ private CommandLine copy() {
result.interpreter.converterRegistry.clear();
result.interpreter.converterRegistry.putAll(interpreter.converterRegistry);
- result.pathCompletionTypes.clear();
- result.pathCompletionTypes.addAll(pathCompletionTypes);
return result;
}
@@ -3286,39 +3277,6 @@ public CommandLine registerConverter(Class cls, ITypeConverter convert
return this;
}
- /**
- *
Adds the type {@code type} to the list of supported type for path completion.
- *
Built-in supported types being:
- *
- *
{@link java.io.File}
- *
{@link java.nio.file.Path}
- *
- *
- * type {@code type}.
- * @param type the type to check if path completion is supported for
- * @return this CommandLine object, to allow method chaining
- * @see #supportsPathCompletion(Class)
- */
- public CommandLine registerForPathCompletion(Class cls) {
- pathCompletionTypes.add(cls.getName());
- for (CommandLine command : getCommandSpec().commands.values()) {
- command.registerForPathCompletion(cls);
- }
- return this;
- }
-
- /**
- * Returns {@code true} if the CommandLine supports path completion for {@link Option} and {@link Parameters} of
- * type {@code type}.
- * @param type the type to check if path completion is supported for
- * @return {@code true} if the CommandLine supports path completion for {@link Option} and {@link Parameters} of
- * type {@code type}.
- * @see #registerForPathCompletion(Class)
- */
- public boolean supportsPathCompletion(Class> type) {
- return pathCompletionTypes.contains(type.getName());
- }
-
/** Returns the String that separates option names from option values when parsing command line options.
* @return the String the parser uses to separate option names from option values
* @see ParserSpec#separator() */
From 0cb42e790f850e6df8253c64c80bd70b1a9bb622 Mon Sep 17 00:00:00 2001
From: Loic Rouchon
Date: Thu, 18 Mar 2021 20:34:22 +0100
Subject: [PATCH 5/5] Create the TypeCompletionRegistry to hold type to
completion kind mapping
---
src/main/java/picocli/AutoComplete.java | 182 ++++++++++++++++----
src/test/java/picocli/AutoCompleteTest.java | 61 ++++++-
2 files changed, 206 insertions(+), 37 deletions(-)
diff --git a/src/main/java/picocli/AutoComplete.java b/src/main/java/picocli/AutoComplete.java
index eca8639e8..3d4bd7fe1 100644
--- a/src/main/java/picocli/AutoComplete.java
+++ b/src/main/java/picocli/AutoComplete.java
@@ -21,15 +21,10 @@
import java.io.PrintWriter;
import java.io.Writer;
import java.net.InetAddress;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.LinkedHashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
+import java.util.*;
import java.util.concurrent.Callable;
+import picocli.AutoComplete.TypeCompletionRegistry.CompletionKind;
import picocli.CommandLine.*;
import picocli.CommandLine.Model.PositionalParamSpec;
import picocli.CommandLine.Model.ArgSpec;
@@ -149,10 +144,14 @@ private static class App implements Callable {
"as the completion script.")
boolean writeCommandScript;
- @Option(names = {"-p", "--pathCompletionTypes"}, split=",", description = "Comma-separated list of fully "
+ @Option(names = {"--pathCompletionTypes"}, split=",", description = "Comma-separated list of fully "
+ "qualified custom types for which to delegate to built-in path name completion.")
List pathCompletionTypes = new ArrayList();
+ @Option(names = {"--hostCompletionTypes"}, split=",", description = "Comma-separated list of fully "
+ + "qualified custom types for which to delegate to built-in host name completion.")
+ List hostCompletionTypes = new ArrayList();
+
@Option(names = {"-f", "--force"}, description = "Overwrite existing script files.")
boolean overwriteIfExists;
@@ -166,12 +165,7 @@ public Integer call() throws Exception {
Class> cls = Class.forName(commandLineFQCN);
Object instance = factory.create(cls);
CommandLine commandLine = new CommandLine(instance, factory);
- for (String className : pathCompletionTypes) {
- // TODO implement error handling if the class is not on the classpath
- Class> pathCompletionClass = Class.forName(className);
- commandLine.registerForPathCompletion(pathCompletionClass);
- }
-
+ TypeCompletionRegistry registry = typeCompletionRegistry(pathCompletionTypes, hostCompletionTypes);
if (commandName == null) {
commandName = commandLine.getCommandName(); //new CommandLine.Help(commandLine.commandDescriptor).commandName;
if (CommandLine.Help.DEFAULT_COMMAND_NAME.equals(commandName)) {
@@ -192,10 +186,27 @@ public Integer call() throws Exception {
return EXIT_CODE_COMPLETION_SCRIPT_EXISTS;
}
- AutoComplete.bash(commandName, autoCompleteScript, commandScript, commandLine);
+ AutoComplete.bash(commandName, autoCompleteScript, commandScript, commandLine, registry);
return EXIT_CODE_SUCCESS;
}
+ private static TypeCompletionRegistry typeCompletionRegistry(List pathCompletionTypes, List hostCompletionTypes)
+ throws ClassNotFoundException {
+ TypeCompletionRegistry registry = new TypeCompletionRegistry();
+ addToRegistry(registry, pathCompletionTypes, CompletionKind.FILE);
+ addToRegistry(registry, hostCompletionTypes, CompletionKind.HOST);
+ return registry;
+ }
+
+ private static void addToRegistry(TypeCompletionRegistry registry, List types,
+ CompletionKind kind) throws ClassNotFoundException {
+ for (String type : types) {
+ // TODO implement error handling if the class is not on the classpath
+ Class> cls = Class.forName(type);
+ registry.registerType(cls, kind);
+ }
+ }
+
private boolean checkExists(final File file) {
if (file.exists()) {
PrintWriter err = spec.commandLine().getErr();
@@ -207,6 +218,90 @@ private boolean checkExists(final File file) {
}
}
+ /**
+ * Meta-information about FQCN to {@link CompletionKind} mappings.
+ */
+ public static class TypeCompletionRegistry {
+
+ /**
+ * The different kinds of supported auto completion mechanisms.
+ */
+ public enum CompletionKind {
+ /**
+ * Auto completion resolved against paths on the file system.
+ */
+ FILE,
+ /**
+ * Auto completion resolved against known hosts.
+ */
+ HOST,
+ /**
+ * No auto-completion.
+ */
+ NONE
+ }
+
+ private final Map registry = new HashMap();
+
+ public TypeCompletionRegistry() {
+ registerDefaultPathCompletionTypes();
+ registerDefaultHostCompletionTypes();
+ }
+
+ private void registerDefaultPathCompletionTypes() {
+ registry.put(File.class.getName(), CompletionKind.FILE);
+ registry.put("java.nio.file.Path", CompletionKind.FILE);
+ }
+
+ private void registerDefaultHostCompletionTypes() {
+ registry.put(InetAddress.class.getName(), CompletionKind.HOST);
+ }
+
+ /**
+ *
Register the type {@code type} to the given {@link CompletionKind}.
+ *
Built-in supported types to {@link CompletionKind} mappings are:
+ *
+ *
{@link CompletionKind#FILE}:
+ *
+ *
{@link java.io.File}
+ *
{@link java.nio.file.Path}
+ *
+ *
+ *
{@link CompletionKind#HOST}:
+ *
+ *
{@link java.net.InetAddress}
+ *
+ *
+ *
+ *
+ *
+ * @param type the type to register
+ * @param kind the kind of completion to apply for this type
+ * @return this {@link TypeCompletionRegistry} object, to allow method chaining
+ * @see #forType(Class)
+ */
+ public TypeCompletionRegistry registerType(Class type, CompletionKind kind) {
+ registry.put(type.getName(), kind);
+ return this;
+ }
+
+ /**
+ * Returns the {@link CompletionKind} for the requested {@code type} or {@link CompletionKind#NONE} if no
+ * mapping exists.
+ * @param type the type to retrieve the {@link CompletionKind} for.
+ * @return the {@link CompletionKind} for the requested {@code type} or {@link CompletionKind#NONE} if no
+ * mapping exists.
+ * @see #registerType(Class, CompletionKind)
+ */
+ public CompletionKind forType(Class> type) {
+ CompletionKind kind = registry.get(type.getName());
+ if (kind == null) {
+ return CompletionKind.NONE;
+ }
+ return kind;
+ }
+ }
+
/**
* Command that generates a Bash/ZSH completion script for its top-level command.
*
@@ -442,7 +537,23 @@ private static class CommandDescriptor {
* @throws IOException if a problem occurred writing to the specified files
*/
public static void bash(String scriptName, File out, File command, CommandLine commandLine) throws IOException {
- String autoCompleteScript = bash(scriptName, commandLine);
+ bash(scriptName, out, command, commandLine, new TypeCompletionRegistry());
+ }
+
+ /**
+ * Generates source code for an autocompletion bash script for the specified picocli-based application,
+ * and writes this script to the specified {@code out} file, and optionally writes an invocation script
+ * to the specified {@code command} file.
+ * @param scriptName the name of the command to generate a bash autocompletion script for
+ * @param commandLine the {@code CommandLine} instance for the command line application
+ * @param out the file to write the autocompletion bash script source code to
+ * @param command the file to write a helper script to that invokes the command, or {@code null} if no helper script file should be written
+ * @param registry the custom types to completions kind registry
+ * @throws IOException if a problem occurred writing to the specified files
+ */
+ public static void bash(String scriptName, File out, File command, CommandLine commandLine,
+ TypeCompletionRegistry registry) throws IOException {
+ String autoCompleteScript = bash(scriptName, commandLine, registry);
Writer completionWriter = null;
Writer scriptWriter = null;
try {
@@ -471,6 +582,17 @@ public static void bash(String scriptName, File out, File command, CommandLine c
* @return source code for an autocompletion bash script
*/
public static String bash(String scriptName, CommandLine commandLine) {
+ return bash(scriptName, commandLine, new TypeCompletionRegistry());
+ }
+
+ /**
+ * Generates and returns the source code for an autocompletion bash script for the specified picocli-based application.
+ * @param scriptName the name of the command to generate a bash autocompletion script for
+ * @param commandLine the {@code CommandLine} instance for the command line application
+ * @param registry the custom types to completions kind registry
+ * @return source code for an autocompletion bash script
+ */
+ public static String bash(String scriptName, CommandLine commandLine, TypeCompletionRegistry registry) {
if (scriptName == null) { throw new NullPointerException("scriptName"); }
if (commandLine == null) { throw new NullPointerException("commandLine"); }
StringBuilder result = new StringBuilder();
@@ -481,7 +603,8 @@ public static String bash(String scriptName, CommandLine commandLine) {
for (CommandDescriptor descriptor : hierarchy) {
if (descriptor.commandLine.getCommandSpec().usageMessage().hidden()) { continue; } // #887 skip hidden subcommands
- result.append(generateFunctionForCommand(descriptor.functionName, descriptor.commandName, descriptor.commandLine));
+ result.append(generateFunctionForCommand(descriptor.functionName, descriptor.commandName,
+ descriptor.commandLine, registry));
}
result.append(format(SCRIPT_FOOTER, scriptName));
return result.toString();
@@ -592,7 +715,8 @@ private static String concat(String infix, List values, T la
return sb.append(normalize.apply(lastValue)).toString();
}
- private static String generateFunctionForCommand(String functionName, String commandName, CommandLine commandLine) {
+ private static String generateFunctionForCommand(String functionName, String commandName, CommandLine commandLine,
+ TypeCompletionRegistry registry) {
String FUNCTION_HEADER = "" +
"\n" +
"# Generates completions for the options and subcommands of the `%s` %scommand.\n" +
@@ -660,7 +784,7 @@ private static String generateFunctionForCommand(String functionName, String com
// sql.Types?
// Now generate the "case" switches for the options whose arguments we can generate completions for
- buff.append(generateOptionsSwitch(commandLine, argOptionFields));
+ buff.append(generateOptionsSwitch(registry, argOptionFields));
// Generate completion lists for positional params with a known set of valid values (including java enums)
for (PositionalParamSpec f : commandSpec.positionalParameters()) {
@@ -669,7 +793,7 @@ private static String generateFunctionForCommand(String functionName, String com
}
}
- String paramsCases = generatePositionalParamsCases(commandLine, commandSpec.positionalParameters(), "", "${curr_word}");
+ String paramsCases = generatePositionalParamsCases(registry, commandSpec.positionalParameters(), "", "${curr_word}");
String posParamsFooter = "";
if (paramsCases.length() > 0) {
String POSITIONAL_PARAMS_FOOTER = "" +
@@ -706,7 +830,7 @@ private static List extract(Iterable generator) {
}
private static String generatePositionalParamsCases(
- CommandLine commandLine, List posParams, String indent, String currWord) {
+ TypeCompletionRegistry registry, List posParams, String indent, String currWord) {
StringBuilder buff = new StringBuilder(1024);
for (PositionalParamSpec param : posParams) {
if (param.hidden()) { continue; } // #887 skip hidden params
@@ -721,11 +845,11 @@ private static String generatePositionalParamsCases(
if (param.completionCandidates() != null) {
buff.append(format("%s %s (( currIndex >= %d && currIndex <= %d )); then\n", indent, ifOrElif, min, max));
buff.append(format("%s positionals=$( compgen -W \"$%s_pos_param_args\" -- \"%s\" )\n", indent, paramName, currWord));
- } else if (commandLine.supportsPathCompletion(type)) {
+ } else if (registry.forType(type) == CompletionKind.FILE) {
buff.append(format("%s %s (( currIndex >= %d && currIndex <= %d )); then\n", indent, ifOrElif, min, max));
buff.append(format("%s compopt -o filenames\n", indent));
buff.append(format("%s positionals=$( compgen -f -- \"%s\" ) # files\n", indent, currWord));
- } else if (type.equals(InetAddress.class)) {
+ } else if (registry.forType(type) == CompletionKind.HOST) {
buff.append(format("%s %s (( currIndex >= %d && currIndex <= %d )); then\n", indent, ifOrElif, min, max));
buff.append(format("%s compopt -o filenames\n", indent));
buff.append(format("%s positionals=$( compgen -A hostname -- \"%s\" )\n", indent, currWord));
@@ -737,8 +861,8 @@ private static String generatePositionalParamsCases(
return buff.toString();
}
- private static String generateOptionsSwitch(CommandLine commandLine, List argOptions) {
- String optionsCases = generateOptionsCases(commandLine, argOptions, "", "${curr_word}");
+ private static String generateOptionsSwitch(TypeCompletionRegistry registry, List argOptions) {
+ String optionsCases = generateOptionsCases(registry, argOptions, "", "${curr_word}");
if (optionsCases.length() == 0) {
return "";
@@ -753,7 +877,7 @@ private static String generateOptionsSwitch(CommandLine commandLine, List