Merge remote-tracking branch 'luarocks/refs/pull/2/head'

This commit is contained in:
daurnimator
2019-07-26 12:27:43 +10:00
6 changed files with 753 additions and 0 deletions

View File

@@ -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