diff --git a/spec/completion_spec.lua b/spec/completion_spec.lua index 0f5bdd9..1964376 100644 --- a/spec/completion_spec.lua +++ b/spec/completion_spec.lua @@ -19,10 +19,10 @@ describe("tests related to generation of shell completion scripts", function() assert.equal([=[ _comptest() { local IFS=$' \t\n' - local cur prev cmd opts arg + local args cur prev cmd opts arg + args=("${COMP_WORDS[@]}") cur="${COMP_WORDS[COMP_CWORD]}" prev="${COMP_WORDS[COMP_CWORD-1]}" - cmd="comptest" opts="-h --help --completion -v --verbose -f --files" case "$prev" in @@ -36,31 +36,60 @@ _comptest() { ;; esac - for arg in ${COMP_WORDS[@]:1}; do + args=("${args[@]:1}") + for arg in "${args[@]}"; do case "$arg" in + help) + cmd="help" + opts="$opts -h --help" + break + ;; completion) cmd="completion" + opts="$opts -h --help" break ;; install|i) cmd="install" + opts="$opts -h --help --deps-mode --no-doc" break ;; admin) cmd="admin" + opts="$opts -h --help" + args=("${args[@]:1}") + for arg in "${args[@]}"; do + case "$arg" in + help) + cmd="$cmd help" + opts="$opts -h --help" + break + ;; + add) + cmd="$cmd add" + opts="$opts -h --help" + break + ;; + remove) + cmd="$cmd remove" + opts="$opts -h --help" + break + ;; + esac + done break ;; esac done case "$cmd" in - comptest) + '') COMPREPLY=($(compgen -W "help completion install i admin" -- "$cur")) ;; - completion) - opts="$opts -h --help" + 'help') + COMPREPLY=($(compgen -W "help completion install i admin" -- "$cur")) ;; - install) + 'install') case "$prev" in --deps-mode) COMPREPLY=($(compgen -W "all one order none" -- "$cur")) @@ -68,10 +97,12 @@ _comptest() { ;; esac - opts="$opts -h --help --deps-mode --no-doc" ;; - admin) - opts="$opts -h --help" + 'admin') + COMPREPLY=($(compgen -W "help add remove" -- "$cur")) + ;; + 'admin help') + COMPREPLY=($(compgen -W "help add remove" -- "$cur")) ;; esac @@ -217,37 +248,84 @@ _comptest it("generates correct fish completion script", function() assert.equal([=[ +function __fish_comptest_print_command + set -l cmdline (commandline -poc) + set -l cmd + set -e cmdline[1] + for arg in $cmdline + switch $arg + case help + set cmd $cmd help + break + case completion + set cmd $cmd completion + break + case install i + set cmd $cmd install + break + case admin + set cmd $cmd admin + set -e cmdline[1] + for arg in $cmdline + switch $arg + case help + set cmd $cmd help + break + case add + set cmd $cmd add + break + case remove + set cmd $cmd remove + break + end + end + break + end + end + echo "$cmd" +end -complete -c comptest -n '__fish_use_subcommand' -xa 'help' -d 'Show help for commands' -complete -c comptest -n '__fish_use_subcommand' -xa 'completion' -d 'Output a shell completion script' -complete -c comptest -n '__fish_use_subcommand' -xa 'install' -d 'Install a rock' -complete -c comptest -n '__fish_use_subcommand' -xa 'i' -d 'Install a rock' -complete -c comptest -n '__fish_use_subcommand' -xa 'admin' -d 'Rock server administration interface' +function __fish_comptest_using_command + test (__fish_comptest_print_command) = "$argv" + and return 0 + or return 1 +end + +function __fish_comptest_seen_command + string match -q "$argv*" (__fish_comptest_print_command) + and return 0 + or return 1 +end + +complete -c comptest -n '__fish_comptest_using_command' -xa 'help' -d 'Show help for commands' +complete -c comptest -n '__fish_comptest_using_command' -xa 'completion' -d 'Output a shell completion script' +complete -c comptest -n '__fish_comptest_using_command' -xa 'install i' -d 'Install a rock' +complete -c comptest -n '__fish_comptest_using_command' -xa 'admin' -d 'Rock server administration interface' complete -c comptest -s h -l help -d 'Show this help message and exit' complete -c comptest -l completion -xa 'bash zsh fish' -d 'Output a shell completion script for the specified shell' complete -c comptest -s v -l verbose -d 'Set the verbosity level' complete -c comptest -s f -l files -r -d 'A description with illegal "\' characters' -complete -c comptest -n '__fish_seen_subcommand_from help' -xa 'help completion install i admin' -complete -c comptest -n '__fish_seen_subcommand_from help' -s h -l help -d 'Show this help message and exit' +complete -c comptest -n '__fish_comptest_using_command help' -xa 'help completion install i admin' +complete -c comptest -n '__fish_comptest_seen_command help' -s h -l help -d 'Show this help message and exit' -complete -c comptest -n '__fish_seen_subcommand_from completion' -s h -l help -d 'Show this help message and exit' +complete -c comptest -n '__fish_comptest_seen_command completion' -s h -l help -d 'Show this help message and exit' -complete -c comptest -n '__fish_seen_subcommand_from install i' -s h -l help -d 'Show this help message and exit' -complete -c comptest -n '__fish_seen_subcommand_from install i' -l deps-mode -xa 'all one order none' -complete -c comptest -n '__fish_seen_subcommand_from install i' -l no-doc -d 'Install without documentation' +complete -c comptest -n '__fish_comptest_seen_command install' -s h -l help -d 'Show this help message and exit' +complete -c comptest -n '__fish_comptest_seen_command install' -l deps-mode -xa 'all one order none' +complete -c comptest -n '__fish_comptest_seen_command install' -l no-doc -d 'Install without documentation' -complete -c comptest -n '__fish_use_subcommand' -xa 'help' -d 'Show help for commands' -complete -c comptest -n '__fish_use_subcommand' -xa 'add' -d 'Add a rock to a server' -complete -c comptest -n '__fish_use_subcommand' -xa 'remove' -d 'Remove a rock from a server' -complete -c comptest -n '__fish_seen_subcommand_from admin' -s h -l help -d 'Show this help message and exit' +complete -c comptest -n '__fish_comptest_using_command admin' -xa 'help' -d 'Show help for commands' +complete -c comptest -n '__fish_comptest_using_command admin' -xa 'add' -d 'Add a rock to a server' +complete -c comptest -n '__fish_comptest_using_command admin' -xa 'remove' -d 'Remove a rock from a server' +complete -c comptest -n '__fish_comptest_seen_command admin' -s h -l help -d 'Show this help message and exit' -complete -c comptest -n '__fish_seen_subcommand_from help' -xa 'help add remove' -complete -c comptest -n '__fish_seen_subcommand_from help' -s h -l help -d 'Show this help message and exit' +complete -c comptest -n '__fish_comptest_using_command admin help' -xa 'help add remove' +complete -c comptest -n '__fish_comptest_seen_command admin help' -s h -l help -d 'Show this help message and exit' -complete -c comptest -n '__fish_seen_subcommand_from add' -s h -l help -d 'Show this help message and exit' +complete -c comptest -n '__fish_comptest_seen_command admin add' -s h -l help -d 'Show this help message and exit' -complete -c comptest -n '__fish_seen_subcommand_from remove' -s h -l help -d 'Show this help message and exit' +complete -c comptest -n '__fish_comptest_seen_command admin remove' -s h -l help -d 'Show this help message and exit' ]=], get_output("completion fish")) end) end) diff --git a/src/argparse.lua b/src/argparse.lua index a91da95..2f95680 100644 --- a/src/argparse.lua +++ b/src/argparse.lua @@ -584,12 +584,17 @@ function Option:_is_vararg() return self._maxargs ~= self._minargs end -function Parser:_get_fullname() +function Parser:_get_fullname(exclude_root) local parent = self._parent + if exclude_root and not parent then + return "" + end local buf = {self._name} while parent do - table.insert(buf, 1, parent._name) + if not exclude_root or parent._parent then + table.insert(buf, 1, parent._name) + end parent = parent._parent end @@ -1234,45 +1239,51 @@ function Parser:_bash_option_args(buf, indent) end end -function Parser:_bash_get_cmd(buf) - local cmds = {} - for _, command in ipairs(self._commands) do - if not command._is_help_command then - table.insert(cmds, (" "):rep(12) .. table.concat(command._aliases, "|") .. ")") - table.insert(cmds, (" "):rep(16) .. 'cmd="' .. command._name .. '"') - table.insert(cmds, (" "):rep(16) .. "break") - table.insert(cmds, (" "):rep(16) .. ";;") - end +function Parser:_bash_get_cmd(buf, indent) + if #self._commands == 0 then + return end - if #cmds > 0 then - table.insert(buf, (" "):rep(4) .. "for arg in ${COMP_WORDS[@]:1}; do") - table.insert(buf, (" "):rep(8) .. 'case "$arg" in') - table.insert(buf, table.concat(cmds, "\n")) - table.insert(buf, (" "):rep(8) .. "esac") - table.insert(buf, (" "):rep(4) .. "done\n") + table.insert(buf, (" "):rep(indent) .. 'args=("${args[@]:1}")') + table.insert(buf, (" "):rep(indent) .. 'for arg in "${args[@]}"; do') + table.insert(buf, (" "):rep(indent + 4) .. 'case "$arg" in') + + for _, command in ipairs(self._commands) do + table.insert(buf, (" "):rep(indent + 8) .. table.concat(command._aliases, "|") .. ")") + if self._parent then + table.insert(buf, (" "):rep(indent + 12) .. 'cmd="$cmd ' .. command._name .. '"') + else + table.insert(buf, (" "):rep(indent + 12) .. 'cmd="' .. command._name .. '"') + end + table.insert(buf, (" "):rep(indent + 12) .. 'opts="$opts ' .. command:_get_options() .. '"') + command:_bash_get_cmd(buf, indent + 12) + table.insert(buf, (" "):rep(indent + 12) .. "break") + table.insert(buf, (" "):rep(indent + 12) .. ";;") end + + table.insert(buf, (" "):rep(indent + 4) .. "esac") + table.insert(buf, (" "):rep(indent) .. "done") end function Parser:_bash_cmd_completions(buf) - local subcmds = {} - for _, command in ipairs(self._commands) do - if #command._options > 0 and not command._is_help_command then - table.insert(subcmds, (" "):rep(8) .. command._name .. ")") - command:_bash_option_args(subcmds, 12) - table.insert(subcmds, (" "):rep(12) .. 'opts="$opts ' .. command:_get_options() .. '"') - table.insert(subcmds, (" "):rep(12) .. ";;") - end + local cmd_buf = {} + if self._parent then + self:_bash_option_args(cmd_buf, 12) + end + if #self._commands > 0 then + table.insert(cmd_buf, (" "):rep(12) .. 'COMPREPLY=($(compgen -W "' .. self:_get_commands() .. '" -- "$cur"))') + elseif self._is_help_command then + table.insert(cmd_buf, (" "):rep(12) .. 'COMPREPLY=($(compgen -W "' .. self._parent:_get_commands() .. '" -- "$cur"))') + end + if #cmd_buf > 0 then + table.insert(buf, (" "):rep(8) .. "'" .. self:_get_fullname(true) .. "')") + table.insert(buf, table.concat(cmd_buf, "\n")) + table.insert(buf, (" "):rep(12) .. ";;") end - table.insert(buf, (" "):rep(4) .. 'case "$cmd" in') - table.insert(buf, (" "):rep(8) .. self._basename .. ")") - table.insert(buf, (" "):rep(12) .. 'COMPREPLY=($(compgen -W "' .. self:_get_commands() .. '" -- "$cur"))') - table.insert(buf, (" "):rep(12) .. ";;") - if #subcmds > 0 then - table.insert(buf, table.concat(subcmds, "\n")) + for _, command in ipairs(self._commands) do + command:_bash_cmd_completions(buf) end - table.insert(buf, (" "):rep(4) .. "esac\n") end function Parser:get_bash_complete() @@ -1281,17 +1292,20 @@ function Parser:get_bash_complete() local buf = {([[ _%s() { local IFS=$' \t\n' - local cur prev cmd opts arg + local args cur prev cmd opts arg + args=("${COMP_WORDS[@]}") cur="${COMP_WORDS[COMP_CWORD]}" prev="${COMP_WORDS[COMP_CWORD-1]}" - cmd="%s" opts="%s" -]]):format(self._basename, self._basename, self:_get_options())} +]]):format(self._basename, self:_get_options())} self:_bash_option_args(buf, 4) - self:_bash_get_cmd(buf) + self:_bash_get_cmd(buf, 4) if #self._commands > 0 then + table.insert(buf, "") + table.insert(buf, (" "):rep(4) .. 'case "$cmd" in') self:_bash_cmd_completions(buf) + table.insert(buf, (" "):rep(4) .. "esac\n") end table.insert(buf, ([=[ @@ -1441,23 +1455,48 @@ local function fish_escape(string) return string:gsub("[\\']", "\\%0") end -function Parser:_fish_complete_help(buf, prefix) +function Parser:_fish_get_cmd(buf, indent) + if #self._commands == 0 then + return + end + + table.insert(buf, (" "):rep(indent) .. "set -e cmdline[1]") + table.insert(buf, (" "):rep(indent) .. "for arg in $cmdline") + table.insert(buf, (" "):rep(indent + 4) .. "switch $arg") + + for _, command in ipairs(self._commands) do + table.insert(buf, (" "):rep(indent + 8) .. "case " .. table.concat(command._aliases, " ")) + table.insert(buf, (" "):rep(indent + 12) .. "set cmd $cmd " .. command._name) + command:_fish_get_cmd(buf, indent + 12) + table.insert(buf, (" "):rep(indent + 12) .. "break") + end + + table.insert(buf, (" "):rep(indent + 4) .. "end") + table.insert(buf, (" "):rep(indent) .. "end") +end + +function Parser:_fish_complete_help(buf, basename) + local prefix = "complete -c " .. basename table.insert(buf, "") for _, command in ipairs(self._commands) do - for _, alias in ipairs(command._aliases) do - local line = ("%s -n '__fish_use_subcommand' -xa '%s'"):format(prefix, alias) - if command._description then - line = ("%s -d '%s'"):format(line, fish_escape(get_short_description(command))) - end - table.insert(buf, line) + local aliases = table.concat(command._aliases, " ") + local line + if self._parent then + line = ("%s -n '__fish_%s_using_command %s' -xa '%s'") + :format(prefix, basename, self:_get_fullname(true), aliases) + else + line = ("%s -n '__fish_%s_using_command' -xa '%s'"):format(prefix, basename, aliases) end - + if command._description then + line = ("%s -d '%s'"):format(line, fish_escape(get_short_description(command))) + end + table.insert(buf, line) end if self._is_help_command then - local line = ("%s -n '__fish_seen_subcommand_from %s' -xa '%s'") - :format(prefix, table.concat(self._aliases, " "), self._parent:_get_commands()) + local line = ("%s -n '__fish_%s_using_command %s' -xa '%s'") + :format(prefix, basename, self:_get_fullname(true), self._parent:_get_commands()) table.insert(buf, line) end @@ -1465,7 +1504,7 @@ function Parser:_fish_complete_help(buf, prefix) local parts = {prefix} if self._parent then - table.insert(parts, "-n '__fish_seen_subcommand_from " .. table.concat(self._aliases, " ") .. "'") + table.insert(parts, "-n '__fish_" .. basename .. "_seen_command " .. self:_get_fullname(true) .. "'") end for _, alias in ipairs(option._aliases) do @@ -1490,7 +1529,7 @@ function Parser:_fish_complete_help(buf, prefix) end for _, command in ipairs(self._commands) do - command:_fish_complete_help(buf, prefix) + command:_fish_complete_help(buf, basename) end end @@ -1498,8 +1537,31 @@ function Parser:get_fish_complete() self._basename = base_name(self._name) assert(self:_is_shell_safe()) local buf = {} - local prefix = "complete -c " .. self._basename - self:_fish_complete_help(buf, prefix) + + if #self._commands > 0 then + table.insert(buf, ([[ +function __fish_%s_print_command + set -l cmdline (commandline -poc) + set -l cmd]]):format(self._basename)) + self:_fish_get_cmd(buf, 4) + table.insert(buf, ([[ + echo "$cmd" +end + +function __fish_%s_using_command + test (__fish_%s_print_command) = "$argv" + and return 0 + or return 1 +end + +function __fish_%s_seen_command + string match -q "$argv*" (__fish_%s_print_command) + and return 0 + or return 1 +end]]):format(self._basename, self._basename, self._basename, self._basename)) + end + + self:_fish_complete_help(buf, self._basename) return table.concat(buf, "\n") .. "\n" end