diff --git a/src/test/java/picocli/AutoCompleteTest.java b/src/test/java/picocli/AutoCompleteTest.java index 7af62e244..220f6b2a2 100644 --- a/src/test/java/picocli/AutoCompleteTest.java +++ b/src/test/java/picocli/AutoCompleteTest.java @@ -16,12 +16,18 @@ package picocli; import java.io.BufferedReader; +import java.io.File; import java.io.IOException; import java.io.InputStreamReader; +import java.net.InetAddress; import java.net.URL; import java.util.concurrent.TimeUnit; import org.junit.Test; + +import picocli.CommandLine.Command; +import picocli.CommandLine.Option; + import static org.junit.Assert.*; /** @@ -31,13 +37,48 @@ // https://apple.stackexchange.com/a/13019 public class AutoCompleteTest { @Test - public void bash() throws Exception { + public void basic() throws Exception { class App { - @CommandLine.Option(names = {"-u", "--timeUnit"}) private TimeUnit timeUnit; - @CommandLine.Option(names = {"-t", "--timeout"}) private long timeout; + @Option(names = {"-u", "--timeUnit"}) private TimeUnit timeUnit; + @Option(names = {"-t", "--timeout"}) private long timeout; + } + String script = AutoComplete.bash("basicExample", new CommandLine(new App())); + String expected = loadTextFromClasspath("/basic.bash"); + assertEquals(expected, script); + } + @Test + public void nestedSubcommands() throws Exception { + class TopLevel { + @Option(names = {"-V", "--version"}, help = true) boolean versionRequested; + @Option(names = {"-h", "--help"}, help = true) boolean helpRequested; + } + @Command(description = "First level subcommand 1") + class Sub1 { + @Option(names = "--num", description = "a number") double number; + @Option(names = "--str", description = "a String") String str; + } + @Command(description = "First level subcommand 2") + class Sub2 { + @Option(names = "--num2", description = "another number") int number2; + @Option(names = {"--directory", "-d"}, description = "a directory") File directory; + } + @Command(description = "Second level sub-subcommand 1") + class Sub2Child1 { + @Option(names = {"-h", "--host"}, description = "a host") InetAddress host; + } + @Command(description = "Second level sub-subcommand 2") + class Sub2Child2 { + @Option(names = {"-u", "--timeUnit"}) private TimeUnit timeUnit; + @Option(names = {"-t", "--timeout"}) private long timeout; } - String script = AutoComplete.bash("script1", new CommandLine(new App())); - String expected = loadTextFromClasspath("/script1.bash"); + CommandLine hierarchy = new CommandLine(new TopLevel()) + .addSubcommand("sub1", new Sub1()) + .addSubcommand("sub2", new CommandLine(new Sub2()) + .addSubcommand("subsub1", new Sub2Child1()) + .addSubcommand("subsub2", new Sub2Child2()) + ); + String script = AutoComplete.bash("hierarchy", hierarchy); + String expected = loadTextFromClasspath("/nestedSubcommands.bash"); assertEquals(expected, script); } diff --git a/src/test/resources/script1.bash b/src/test/resources/basic.bash similarity index 81% rename from src/test/resources/script1.bash rename to src/test/resources/basic.bash index caba5622a..e69f9f485 100644 --- a/src/test/resources/script1.bash +++ b/src/test/resources/basic.bash @@ -1,9 +1,9 @@ #!bash # -# script1 Bash Completion +# basicExample Bash Completion # ======================= # -# Bash completion support for script1, +# Bash completion support for basicExample, # generated by [picocli](http://picocli.info/). # # Installation @@ -15,12 +15,12 @@ # * /usr/local/etc/bash-completion.d # * ~/bash-completion.d # -# 2. Open a new bash console, and type `script1 [TAB][TAB]` +# 2. Open a new bash console, and type `basicExample [TAB][TAB]` # # Documentation # ------------- # The script is called by bash whenever [TAB] or [TAB][TAB] is pressed after -# 'script1 (..)'. By reading entered command line parameters, it determines possible +# 'basicExample (..)'. By reading entered command line parameters, it determines possible # bash completions and writes them to the COMPREPLY variable. Bash then # completes the user input if only one entry is listed in the variable or # shows the options if more than one is listed in COMPREPLY. @@ -55,18 +55,18 @@ function ArrContains() { } # Bash completion entry point function. -# _complete_script1 finds which commands and subcommands have been specified +# _complete_basicExample finds which commands and subcommands have been specified # on the command line and delegates to the appropriate function # to generate possible options and subcommands for the last specified subcommand. -function _complete_script1() { - CMDS0=(script1) +function _complete_basicExample() { + CMDS0=(basicExample) - ArrContains COMP_WORDS CMDS0 && { _picocli_script1; return $?; } + ArrContains COMP_WORDS CMDS0 && { _picocli_basicExample; return $?; } echo "not found" - _picocli_script1; return $?; + _picocli_basicExample; return $?; } -function script1() { +function _picocli_basicExample() { # Get completion data CURR_WORD=${COMP_WORDS[COMP_CWORD]} PREV_WORD=${COMP_WORDS[COMP_CWORD-1]} @@ -93,4 +93,4 @@ function script1() { COMPREPLY=( $(compgen -W "${FLAG_OPTS} ${ARG_OPTS} ${COMMANDS}" -- ${CURR_WORD}) ) } -complete -F _complete_script1 script1 +complete -F _complete_basicExample basicExample diff --git a/src/test/resources/nestedSubcommands.bash b/src/test/resources/nestedSubcommands.bash new file mode 100644 index 000000000..3095f6a25 --- /dev/null +++ b/src/test/resources/nestedSubcommands.bash @@ -0,0 +1,184 @@ +#!bash +# +# hierarchy Bash Completion +# ======================= +# +# Bash completion support for hierarchy, +# generated by [picocli](http://picocli.info/). +# +# Installation +# ------------ +# +# 1. Place this file in a `bash-completion.d` folder: +# +# * /etc/bash-completion.d +# * /usr/local/etc/bash-completion.d +# * ~/bash-completion.d +# +# 2. Open a new bash console, and type `hierarchy [TAB][TAB]` +# +# Documentation +# ------------- +# The script is called by bash whenever [TAB] or [TAB][TAB] is pressed after +# 'hierarchy (..)'. By reading entered command line parameters, it determines possible +# bash completions and writes them to the COMPREPLY variable. Bash then +# completes the user input if only one entry is listed in the variable or +# shows the options if more than one is listed in COMPREPLY. +# +# References +# ---------- +# [1] http://stackoverflow.com/a/12495480/1440785 +# [2] http://tiswww.case.edu/php/chet/bash/FAQ +# [3] https://www.gnu.org/software/bash/manual/html_node/The-Shopt-Builtin.html +# [4] https://stackoverflow.com/questions/17042057/bash-check-element-in-array-for-elements-in-another-array/17042655#17042655 +# + +# Enable programmable completion facilities (see [3]) +shopt -s progcomp + +# ArrContains takes two arguments, both of which are the name of arrays. +# It creates a temporary hash from lArr1 and then checks if all elements of lArr2 +# are in the hashtable. +# +# Returns zero (no error) if all elements of the 2nd array are in the 1st array, +# otherwise returns 1 (error). +# +# Modified from [4] +function ArrContains() { + local lArr1 lArr2 + declare -A tmp + eval lArr1=("\"\${$1[@]}\"") + eval lArr2=("\"\${$2[@]}\"") + for i in "${lArr1[@]}";{ [ -n "$i" ] && ((++tmp['$i']));} + for i in "${lArr2[@]}";{ [ -z "${tmp[$i]}" ] && return 1;} + return 0 +} + +# Bash completion entry point function. +# _complete_hierarchy finds which commands and subcommands have been specified +# on the command line and delegates to the appropriate function +# to generate possible options and subcommands for the last specified subcommand. +function _complete_hierarchy() { + CMDS0=(hierarchy) + CMDS1=(hierarchy sub1) + CMDS2=(hierarchy sub2) + CMDS3=(hierarchy sub2 subsub1) + CMDS4=(hierarchy sub2 subsub2) + + ArrContains COMP_WORDS CMDS4 && { _picocli_hierarchy_sub2_subsub2; return $?; } + ArrContains COMP_WORDS CMDS3 && { _picocli_hierarchy_sub2_subsub1; return $?; } + ArrContains COMP_WORDS CMDS2 && { _picocli_hierarchy_sub2; return $?; } + ArrContains COMP_WORDS CMDS1 && { _picocli_hierarchy_sub1; return $?; } + ArrContains COMP_WORDS CMDS0 && { _picocli_hierarchy; return $?; } + echo "not found" + _picocli_hierarchy; return $?; +} + +function _picocli_hierarchy() { + # Get completion data + CURR_WORD=${COMP_WORDS[COMP_CWORD]} + PREV_WORD=${COMP_WORDS[COMP_CWORD-1]} + + COMMANDS="sub1 sub2" + FLAG_OPTS="-V --version -h --help" + ARG_OPTS="" + + COMPREPLY=( $(compgen -W "${FLAG_OPTS} ${ARG_OPTS} ${COMMANDS}" -- ${CURR_WORD}) ) +} + +function _picocli_hierarchy_sub1() { + # Get completion data + CURR_WORD=${COMP_WORDS[COMP_CWORD]} + PREV_WORD=${COMP_WORDS[COMP_CWORD-1]} + + COMMANDS="" + FLAG_OPTS="" + ARG_OPTS="--num --str" + + COMPREPLY=( $(compgen -W "${FLAG_OPTS} ${ARG_OPTS} ${COMMANDS}" -- ${CURR_WORD}) ) +} + +function _picocli_hierarchy_sub2() { + # Get completion data + CURR_WORD=${COMP_WORDS[COMP_CWORD]} + PREV_WORD=${COMP_WORDS[COMP_CWORD-1]} + + COMMANDS="subsub1 subsub2" + FLAG_OPTS="" + ARG_OPTS="--num2 --directory -d" + + case ${CURR_WORD} in + --directory|-d) + compopt -o filenames + COMPREPLY=( $( compgen -f -- "" ) ) # files + return $? + ;; + *) + case ${PREV_WORD} in + --directory|-d) + compopt -o filenames + COMPREPLY=( $( compgen -f -- $CURR_WORD ) ) # files + return $? + ;; + esac + esac + + COMPREPLY=( $(compgen -W "${FLAG_OPTS} ${ARG_OPTS} ${COMMANDS}" -- ${CURR_WORD}) ) +} + +function _picocli_hierarchy_sub2_subsub1() { + # Get completion data + CURR_WORD=${COMP_WORDS[COMP_CWORD]} + PREV_WORD=${COMP_WORDS[COMP_CWORD-1]} + + COMMANDS="" + FLAG_OPTS="" + ARG_OPTS="-h --host" + + case ${CURR_WORD} in + -h|--host) + compopt -o filenames + COMPREPLY=( $( compgen -A hostname -- "" ) ) + return $? + ;; + *) + case ${PREV_WORD} in + -h|--host) + compopt -o filenames + COMPREPLY=( $( compgen -A hostname -- $CURR_WORD ) ) + return $? + ;; + esac + esac + + COMPREPLY=( $(compgen -W "${FLAG_OPTS} ${ARG_OPTS} ${COMMANDS}" -- ${CURR_WORD}) ) +} + +function _picocli_hierarchy_sub2_subsub2() { + # Get completion data + CURR_WORD=${COMP_WORDS[COMP_CWORD]} + PREV_WORD=${COMP_WORDS[COMP_CWORD-1]} + + COMMANDS="" + FLAG_OPTS="" + ARG_OPTS="-u --timeUnit -t --timeout" + timeUnit_OPTION_ARGS="NANOSECONDS MICROSECONDS MILLISECONDS SECONDS MINUTES HOURS DAYS" # TimeUnit values + + case ${CURR_WORD} in + -u|--timeUnit) + COMPREPLY=( $( compgen -W "${timeUnit_OPTION_ARGS}" -- "" ) ) + return $? + ;; + *) + case ${PREV_WORD} in + -u|--timeUnit) + COMPREPLY=( $( compgen -W "${timeUnit_OPTION_ARGS}" -- $CURR_WORD ) ) + return $? + ;; + esac + esac + + COMPREPLY=( $(compgen -W "${FLAG_OPTS} ${ARG_OPTS} ${COMMANDS}" -- ${CURR_WORD}) ) +} + +complete -F _complete_hierarchy hierarchy