diff --git a/LICENSE b/LICENSE
index 87579ac..b59cc73 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,7 @@
The MIT License (MIT)
Copyright (c) 2013 - 2018 Peter Melnichenko
+ 2019 Paul Ouellette
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
diff --git a/docsrc/completions.rst b/docsrc/completions.rst
new file mode 100644
index 0000000..c6c10e7
--- /dev/null
+++ b/docsrc/completions.rst
@@ -0,0 +1,78 @@
+Shell completions
+=================
+
+Argparse can generate shell completion scripts for
+`Bash `_, `Zsh `_, and
+`Fish `_.
+The completion scripts support completing options, commands, and argument
+choices.
+
+The Parser methods ``:get_bash_complete()``, ``:get_zsh_complete()``, and
+``:get_fish_complete()`` return completion scripts as a string.
+
+Adding a completion option or command
+-------------------------------------
+
+A ``--completion`` option can be added to a parser using the
+``:add_complete([value])`` method. The optional ``value`` argument is a string
+or table used to configure the option (by calling the option with ``value``).
+
+.. code-block:: lua
+ :linenos:
+
+ local parser = argparse()
+ :add_complete()
+
+.. code-block:: none
+
+ $ lua script.lua -h
+
+.. code-block:: none
+
+ Usage: script.lua [-h] [--completion {bash,zsh,fish}]
+
+ Options:
+ -h, --help Show this help message and exit.
+ --completion {bash,zsh,fish}
+ Output a shell completion script for the specified shell.
+
+A similar ``completion`` command can be added to a parser using the
+``:add_complete_command([value])`` method.
+
+Using completions
+-----------------
+
+Bash
+^^^^
+
+Save the generated completion script at
+``/usr/share/bash-completion/completions/script.lua`` or
+``~/.local/share/bash-completion/completions/script.lua``.
+
+Alternatively, add the following line to the ``~/.bashrc``:
+
+.. code-block:: bash
+
+ source <(script.lua --completion bash)
+
+Zsh
+^^^
+
+Save the completion script in the ``/usr/share/zsh/site-functions/`` directory
+or any directory in the ``$fpath``. The file name should be an underscore
+followed by the program name. A new directory can be added to to the ``$fpath``
+by adding e.g. ``fpath=(~/.zfunc $fpath)`` in the ``~/.zshrc`` before
+``compinit``.
+
+Fish
+^^^^
+
+Save the completion script at
+``/usr/share/fish/vendor_completions.d/script.lua.fish`` or
+``~/.config/fish/completions/script.lua.fish``.
+
+Alternatively, add the following line to the file ``~/.config/fish/config.fish``:
+
+.. code-block:: fish
+
+ script.lua --completion fish | source
diff --git a/docsrc/index.rst b/docsrc/index.rst
index 0e8c6c0..e4d0de1 100644
--- a/docsrc/index.rst
+++ b/docsrc/index.rst
@@ -13,6 +13,7 @@ Contents:
defaults
callbacks
messages
+ completions
misc
This is a tutorial for `argparse `_, a feature-rich command line parser for Lua.
diff --git a/spec/completion_spec.lua b/spec/completion_spec.lua
new file mode 100644
index 0000000..6812cd2
--- /dev/null
+++ b/spec/completion_spec.lua
@@ -0,0 +1,222 @@
+local script = "./spec/comptest"
+local script_cmd = "lua"
+
+if package.loaded["luacov.runner"] then
+ script_cmd = script_cmd .. " -lluacov"
+end
+
+script_cmd = script_cmd .. " " .. script
+
+local function get_output(args)
+ local handler = io.popen(script_cmd .. " " .. args .. " 2>&1", "r")
+ local output = handler:read("*a")
+ handler:close()
+ return output
+end
+
+describe("tests related to generation of shell completion scripts", function()
+ it("generates correct bash completion script", function()
+ assert.equal([=[
+_comptest() {
+ local IFS=$' \t\n'
+ local cur prev cmd opts arg
+ cur="${COMP_WORDS[COMP_CWORD]}"
+ prev="${COMP_WORDS[COMP_CWORD-1]}"
+ cmd="comptest"
+ opts="-h --help --completion -v --verbose -f --files"
+
+ case "$prev" in
+ --completion)
+ COMPREPLY=($(compgen -W "bash zsh fish" -- "$cur"))
+ return 0
+ ;;
+ -f|--files)
+ COMPREPLY=($(compgen -f "$cur"))
+ return 0
+ ;;
+ esac
+
+ for arg in ${COMP_WORDS[@]:1}; do
+ case "$arg" in
+ completion)
+ cmd="completion"
+ break
+ ;;
+ install|i)
+ cmd="install"
+ break
+ ;;
+ admin)
+ cmd="admin"
+ break
+ ;;
+ esac
+ done
+
+ case "$cmd" in
+ comptest)
+ COMPREPLY=($(compgen -W "help completion install i admin" -- "$cur"))
+ ;;
+ completion)
+ opts="$opts -h --help"
+ ;;
+ install)
+ case "$prev" in
+ --deps-mode)
+ COMPREPLY=($(compgen -W "all one order none" -- "$cur"))
+ return 0
+ ;;
+ esac
+
+ opts="$opts -h --help --deps-mode --no-doc"
+ ;;
+ admin)
+ opts="$opts -h --help"
+ ;;
+ esac
+
+ if [[ "$cur" = -* ]]; then
+ COMPREPLY=($(compgen -W "$opts" -- "$cur"))
+ fi
+}
+
+complete -F _comptest -o bashdefault -o default comptest
+]=], get_output("completion bash"))
+ end)
+
+ it("generates correct zsh completion script", function()
+ assert.equal([=[
+#compdef comptest
+
+_comptest() {
+ local context state state_descr line
+ typeset -A opt_args
+
+ _arguments -s -S \
+ {-h,--help}"[Show this help message and exit]" \
+ "--completion[Output a shell completion script for the specified shell]: :(bash zsh fish)" \
+ "*"{-v,--verbose}"[Set the verbosity level]" \
+ {-f,--files}"[A description with illegal \"' characters]:*: :_files" \
+ ": :_comptest_cmds" \
+ "*:: :->args" \
+ && return 0
+
+ case $words[1] in
+ help)
+ _arguments -s -S \
+ {-h,--help}"[Show this help message and exit]" \
+ ": :(help completion install i admin)" \
+ && return 0
+ ;;
+
+ completion)
+ _arguments -s -S \
+ {-h,--help}"[Show this help message and exit]" \
+ ": :(bash zsh fish)" \
+ && return 0
+ ;;
+
+ install|i)
+ _arguments -s -S \
+ {-h,--help}"[Show this help message and exit]" \
+ "--deps-mode: :(all one order none)" \
+ "--no-doc[Install without documentation]" \
+ && return 0
+ ;;
+
+ admin)
+ _arguments -s -S \
+ {-h,--help}"[Show this help message and exit]" \
+ ": :_comptest_admin_cmds" \
+ "*:: :->args" \
+ && return 0
+
+ case $words[1] in
+ help)
+ _arguments -s -S \
+ {-h,--help}"[Show this help message and exit]" \
+ ": :(help add remove)" \
+ && return 0
+ ;;
+
+ add)
+ _arguments -s -S \
+ {-h,--help}"[Show this help message and exit]" \
+ ": :_files" \
+ && return 0
+ ;;
+
+ remove)
+ _arguments -s -S \
+ {-h,--help}"[Show this help message and exit]" \
+ ": :_files" \
+ && return 0
+ ;;
+
+ esac
+ ;;
+
+ esac
+
+ return 1
+}
+
+_comptest_cmds() {
+ local -a commands=(
+ "help:Show help for commands"
+ "completion:Output a shell completion script"
+ {install,i}":Install a rock"
+ "admin:Rock server administration interface"
+ )
+ _describe "command" commands
+}
+
+_comptest_admin_cmds() {
+ local -a commands=(
+ "help:Show help for commands"
+ "add:Add a rock to a server"
+ "remove:Remove a rock from a server"
+ )
+ _describe "command" commands
+}
+
+_comptest
+]=], get_output("completion zsh"))
+ end)
+
+ it("generates correct fish completion script", function()
+ assert.equal([=[
+
+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'
+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_seen_subcommand_from 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_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_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_seen_subcommand_from 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'
+]=], get_output("completion fish"))
+ end)
+end)
diff --git a/spec/comptest b/spec/comptest
new file mode 100755
index 0000000..948bf33
--- /dev/null
+++ b/spec/comptest
@@ -0,0 +1,39 @@
+#!/usr/bin/env lua
+
+local argparse = require "argparse"
+
+local parser = argparse()
+ :add_help_command()
+ :add_complete_command()
+ :add_complete()
+
+parser:flag "-v --verbose"
+ :description "Set the verbosity level."
+ :count "*"
+
+parser:option "-f --files"
+ :description "A description with illegal \"' characters."
+ :args "+"
+
+local install = parser:command "install i"
+ :description "Install a rock."
+
+install:option "--deps-mode"
+ :choices {"all", "one", "order", "none"}
+
+install:flag "--no-doc"
+ :description "Install without documentation."
+
+local admin = parser:command "admin"
+ :description "Rock server administration interface."
+ :add_help_command()
+
+local admin_add = admin:command "add"
+ :description "Add a rock to a server."
+admin_add:argument "rock"
+
+local admin_remove = admin:command "remove"
+ :description "Remove a rock from a server."
+admin_remove:argument "rock"
+
+parser:parse()
diff --git a/src/argparse.lua b/src/argparse.lua
index 21e0d0b..ddb9e2d 100644
--- a/src/argparse.lua
+++ b/src/argparse.lua
@@ -1,6 +1,7 @@
-- The MIT License (MIT)
-- Copyright (c) 2013 - 2018 Peter Melnichenko
+-- 2019 Paul Ouellette
-- Permission is hereby granted, free of charge, to any person obtaining a copy of
-- this software and associated documentation files (the "Software"), to deal in
@@ -1080,9 +1081,420 @@ function Parser:add_help_command(value)
help "help"
end
+ help._is_help_command = true
return self
end
+function Parser:_is_shell_safe()
+ if self._basename then
+ if self._basename:find("[^%w_%-%+%.]") then
+ return false
+ end
+ else
+ for _, alias in ipairs(self._aliases) do
+ if alias:find("[^%w_%-%+%.]") then
+ return false
+ end
+ end
+ end
+ for _, option in ipairs(self._options) do
+ for _, alias in ipairs(option._aliases) do
+ if alias:find("[^%w_%-%+%.]") then
+ return false
+ end
+ end
+ if option._choices then
+ for _, choice in ipairs(option._choices) do
+ if choice:find("[%s'\"]") then
+ return false
+ end
+ end
+ end
+ end
+ for _, argument in ipairs(self._arguments) do
+ if argument._choices then
+ for _, choice in ipairs(argument._choices) do
+ if choice:find("[%s'\"]") then
+ return false
+ end
+ end
+ end
+ end
+ for _, command in ipairs(self._commands) do
+ if not command:_is_shell_safe() then
+ return false
+ end
+ end
+ return true
+end
+
+function Parser:add_complete(value)
+ if value then
+ assert(type(value) == "string" or type(value) == "table",
+ ("bad argument #1 to 'add_complete' (string or table expected, got %s)"):format(type(value)))
+ end
+
+ local complete = self:option()
+ :description "Output a shell completion script for the specified shell."
+ :args(1)
+ :choices {"bash", "zsh", "fish"}
+ :action(function(_, _, shell)
+ io.write(self["get_" .. shell .. "_complete"](self))
+ os.exit(0)
+ end)
+
+ if value then
+ complete = complete(value)
+ end
+
+ if not complete._name then
+ complete "--completion"
+ end
+
+ return self
+end
+
+function Parser:add_complete_command(value)
+ if value then
+ assert(type(value) == "string" or type(value) == "table",
+ ("bad argument #1 to 'add_complete_command' (string or table expected, got %s)"):format(type(value)))
+ end
+
+ local complete = self:command()
+ :description "Output a shell completion script."
+ complete:argument "shell"
+ :description "The shell to output a completion script for."
+ :choices {"bash", "zsh", "fish"}
+ :action(function(_, _, shell)
+ io.write(self["get_" .. shell .. "_complete"](self))
+ os.exit(0)
+ end)
+
+ if value then
+ complete = complete(value)
+ end
+
+ if not complete._name then
+ complete "completion"
+ end
+
+ return self
+end
+
+local function base_name(pathname)
+ return pathname:gsub("[/\\]*$", ""):match(".*[/\\]([^/\\]*)") or pathname
+end
+
+local function get_short_description(element)
+ local short = element:_get_description():match("^(.-)%.%s")
+ return short or element:_get_description():match("^(.-)%.?$")
+end
+
+function Parser:_get_options()
+ local options = {}
+ for _, option in ipairs(self._options) do
+ for _, alias in ipairs(option._aliases) do
+ table.insert(options, alias)
+ end
+ end
+ return table.concat(options, " ")
+end
+
+function Parser:_get_commands()
+ local commands = {}
+ for _, command in ipairs(self._commands) do
+ for _, alias in ipairs(command._aliases) do
+ table.insert(commands, alias)
+ end
+ end
+ return table.concat(commands, " ")
+end
+
+function Parser:_bash_option_args(buf, indent)
+ local opts = {}
+ for _, option in ipairs(self._options) do
+ if option._choices or option._minargs > 0 then
+ local compreply
+ if option._choices then
+ compreply = 'COMPREPLY=($(compgen -W "' .. table.concat(option._choices, " ") .. '" -- "$cur"))'
+ else
+ compreply = 'COMPREPLY=($(compgen -f "$cur"))'
+ end
+ table.insert(opts, (" "):rep(indent + 4) .. table.concat(option._aliases, "|") .. ")")
+ table.insert(opts, (" "):rep(indent + 8) .. compreply)
+ table.insert(opts, (" "):rep(indent + 8) .. "return 0")
+ table.insert(opts, (" "):rep(indent + 8) .. ";;")
+ end
+ end
+
+ if #opts > 0 then
+ table.insert(buf, (" "):rep(indent) .. 'case "$prev" in')
+ table.insert(buf, table.concat(opts, "\n"))
+ table.insert(buf, (" "):rep(indent) .. "esac\n")
+ 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
+ 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")
+ end
+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
+ 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"))
+ end
+ table.insert(buf, (" "):rep(4) .. "esac\n")
+end
+
+function Parser:get_bash_complete()
+ self._basename = base_name(self._name)
+ assert(self:_is_shell_safe())
+ local buf = {([[
+_%s() {
+ local IFS=$' \t\n'
+ local cur prev cmd opts arg
+ cur="${COMP_WORDS[COMP_CWORD]}"
+ prev="${COMP_WORDS[COMP_CWORD-1]}"
+ cmd="%s"
+ opts="%s"
+]]):format(self._basename, self._basename, self:_get_options())}
+
+ self:_bash_option_args(buf, 4)
+ self:_bash_get_cmd(buf)
+ if #self._commands > 0 then
+ self:_bash_cmd_completions(buf)
+ end
+
+ table.insert(buf, ([=[
+ if [[ "$cur" = -* ]]; then
+ COMPREPLY=($(compgen -W "$opts" -- "$cur"))
+ fi
+}
+
+complete -F _%s -o bashdefault -o default %s
+]=]):format(self._basename, self._basename))
+
+ return table.concat(buf, "\n")
+end
+
+function Parser:_zsh_arguments(buf, cmd_name, indent)
+ table.insert(buf, (" "):rep(indent) .. "_arguments -s -S \\")
+
+ for _, option in ipairs(self._options) do
+ local line = {}
+ if #option._aliases > 1 then
+ if option._maxcount > 1 then
+ table.insert(line, '"*"')
+ end
+ table.insert(line, "{" .. table.concat(option._aliases, ",") .. '}"')
+ else
+ table.insert(line, '"')
+ if option._maxcount > 1 then
+ table.insert(line, "*")
+ end
+ table.insert(line, option._name)
+ end
+ if option._description then
+ local description = get_short_description(option):gsub('["%]:]', "\\%0")
+ table.insert(line, "[" .. description .. "]")
+ end
+ if option._maxargs == math.huge then
+ table.insert(line, ":*")
+ end
+ if option._choices then
+ table.insert(line, ": :(" .. table.concat(option._choices, " ") .. ")")
+ elseif option._maxargs > 0 then
+ table.insert(line, ": :_files")
+ end
+ table.insert(line, '"')
+
+ table.insert(buf, (" "):rep(indent + 2) .. table.concat(line) .. " \\")
+ end
+
+ if self._is_help_command then
+ table.insert(buf, (" "):rep(indent + 2) .. '": :(' .. self._parent:_get_commands() .. ')" \\')
+ else
+ for _, argument in ipairs(self._arguments) do
+ local spec
+ if argument._choices then
+ spec = ": :(" .. table.concat(argument._choices, " ") .. ")"
+ else
+ spec = ": :_files"
+ end
+ if argument._maxargs == math.huge then
+ table.insert(buf, (" "):rep(indent + 2) .. '"*' .. spec .. '" \\')
+ break
+ end
+ for _ = 1, argument._maxargs do
+ table.insert(buf, (" "):rep(indent + 2) .. '"' .. spec .. '" \\')
+ end
+ end
+
+ if #self._commands > 0 then
+ table.insert(buf, (" "):rep(indent + 2) .. '": :_' .. cmd_name .. '_cmds" \\')
+ table.insert(buf, (" "):rep(indent + 2) .. '"*:: :->args" \\')
+ end
+ end
+
+ table.insert(buf, (" "):rep(indent + 2) .. "&& return 0")
+end
+
+function Parser:_zsh_cmds(buf, cmd_name)
+ table.insert(buf, "\n_" .. cmd_name .. "_cmds() {")
+ table.insert(buf, " local -a commands=(")
+
+ for _, command in ipairs(self._commands) do
+ local line = {}
+ if #command._aliases > 1 then
+ table.insert(line, "{" .. table.concat(command._aliases, ",") .. '}"')
+ else
+ table.insert(line, '"' .. command._name)
+ end
+ if command._description then
+ table.insert(line, ":" .. get_short_description(command):gsub('["]', "\\%0"))
+ end
+ table.insert(buf, " " .. table.concat(line) .. '"')
+ end
+
+ table.insert(buf, ' )\n _describe "command" commands\n}')
+end
+
+function Parser:_zsh_complete_help(buf, cmds_buf, cmd_name, indent)
+ if #self._commands == 0 then
+ return
+ end
+
+ self:_zsh_cmds(cmds_buf, cmd_name)
+ table.insert(buf, "\n" .. (" "):rep(indent) .. "case $words[1] in")
+
+ for _, command in ipairs(self._commands) do
+ local name = cmd_name .. "_" .. command._name
+ table.insert(buf, (" "):rep(indent + 2) .. table.concat(command._aliases, "|") .. ")")
+ command:_zsh_arguments(buf, name, indent + 4)
+ command:_zsh_complete_help(buf, cmds_buf, name, indent + 4)
+ table.insert(buf, (" "):rep(indent + 4) .. ";;\n")
+ end
+
+ table.insert(buf, (" "):rep(indent) .. "esac")
+end
+
+function Parser:get_zsh_complete()
+ self._basename = base_name(self._name)
+ assert(self:_is_shell_safe())
+ local buf = {("#compdef %s\n"):format(self._basename)}
+ local cmds_buf = {}
+ table.insert(buf, "_" .. self._basename .. "() {")
+ if #self._commands > 0 then
+ table.insert(buf, " local context state state_descr line")
+ table.insert(buf, " typeset -A opt_args\n")
+ end
+ self:_zsh_arguments(buf, self._basename, 2)
+ self:_zsh_complete_help(buf, cmds_buf, self._basename, 2)
+ table.insert(buf, "\n return 1")
+ table.insert(buf, "}")
+
+ local result = table.concat(buf, "\n")
+ if #cmds_buf > 0 then
+ result = result .. "\n" .. table.concat(cmds_buf, "\n")
+ end
+ return result .. "\n\n_" .. self._basename .. "\n"
+end
+
+local function fish_escape(string)
+ return string:gsub("[\\']", "\\%0")
+end
+
+function Parser:_fish_complete_help(buf, prefix)
+ 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)
+ end
+
+ 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())
+ table.insert(buf, line)
+ end
+
+ for _, option in ipairs(self._options) do
+ local parts = {prefix}
+
+ if self._parent then
+ table.insert(parts, "-n '__fish_seen_subcommand_from " .. table.concat(self._aliases, " ") .. "'")
+ end
+
+ for _, alias in ipairs(option._aliases) do
+ if alias:match("^%-.$") then
+ table.insert(parts, "-s " .. alias:sub(2))
+ elseif alias:match("^%-%-.+") then
+ table.insert(parts, "-l " .. alias:sub(3))
+ end
+ end
+
+ if option._choices then
+ table.insert(parts, "-xa '" .. table.concat(option._choices, " ") .. "'")
+ elseif option._minargs > 0 then
+ table.insert(parts, "-r")
+ end
+
+ if option._description then
+ table.insert(parts, "-d '" .. fish_escape(get_short_description(option)) .. "'")
+ end
+
+ table.insert(buf, table.concat(parts, " "))
+ end
+
+ for _, command in ipairs(self._commands) do
+ command:_fish_complete_help(buf, prefix)
+ end
+end
+
+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)
+ return table.concat(buf, "\n") .. "\n"
+end
+
local function get_tip(context, wrong_name)
local context_pool = {}
local possible_name