Packages
fnord
0.6.0
0.9.40
0.9.39
0.9.38
0.9.37
0.9.36
0.9.35
0.9.34
0.9.33
0.9.32
0.9.31
0.9.30
0.9.29
0.9.28
0.9.27
0.9.26
0.9.25
0.9.24
0.9.23
0.9.22
0.9.21
0.9.20
0.9.19
0.9.18
0.9.17
0.9.16
0.9.15
0.9.14
0.9.13
0.9.12
0.9.11
0.9.10
0.9.9
0.9.8
0.9.7
0.9.6
0.9.5
0.9.4
0.9.3
0.9.2
0.9.1
0.9.0
0.8.99
0.8.98
0.8.97
0.8.96
0.8.95
0.8.94
0.8.93
0.8.92
0.8.91
0.8.90
0.8.89
0.8.88
0.8.87
0.8.86
0.8.85
0.8.84
0.8.83
0.8.82
0.8.81
0.8.80
0.8.79
0.8.78
0.8.77
0.8.76
0.8.75
0.8.74
0.8.73
0.8.72
0.8.71
0.8.70
0.8.69
0.8.68
0.8.67
0.8.66
0.8.65
0.8.64
0.8.63
0.8.62
0.8.61
0.8.60
0.8.59
0.8.58
0.8.57
0.8.56
0.8.55
0.8.54
0.8.53
0.8.52
0.8.51
0.8.50
0.8.49
0.8.48
0.8.47
0.8.46
0.8.45
0.8.44
0.8.43
0.8.42
0.8.41
0.8.40
0.8.39
0.8.38
0.8.37
0.8.36
0.8.35
0.8.34
0.8.33
0.8.32
0.8.31
0.8.30
0.8.29
0.8.27
0.8.26
0.8.25
0.8.24
0.8.23
0.8.22
0.8.21
0.8.20
0.8.19
0.8.18
0.8.17
0.8.16
0.8.15
0.8.14
0.8.13
0.8.12
0.8.11
0.8.1
0.8.0
0.7.24
0.7.23
0.7.22
0.7.21
0.7.20
0.7.19
0.7.18
0.7.17
0.7.16
0.7.15
0.7.14
0.7.13
0.7.12
0.7.11
0.7.10
0.7.9
0.7.8
0.7.7
0.7.6
0.7.5
0.7.3
0.7.2
0.7.1
0.7.0
0.6.9
0.6.8
0.6.7
0.6.6
0.6.5
0.6.4
0.6.3
0.6.1
0.6.0
0.5.9
0.5.8
0.5.7
0.5.6
0.5.5
0.5.4
0.5.3
0.5.2
0.5.1
0.5.0
0.4.44
0.4.43
0.4.42
0.4.41
0.4.40
0.4.39
0.4.38
0.4.37
0.4.36
0.4.35
0.4.34
0.4.33
0.4.32
0.4.30
0.4.29
0.4.28
0.4.27
0.4.26
0.4.25
0.4.24
0.4.23
0.4.22
0.4.21
0.4.20
0.4.19
0.4.18
0.4.17
0.4.16
0.4.15
0.4.14
0.4.13
0.4.12
0.4.11
0.4.10
0.4.9
0.4.8
0.4.7
0.4.6
0.4.5
0.4.4
0.4.3
0.4.2
0.4.1
0.4.0
0.3.0
0.2.0
0.1.0
AI code archaeology
Current section
Files
Jump to
Current section
Files
lib/shell/completion/zsh.ex
defmodule Shell.Completion.Zsh do
@moduledoc """
Zsh completion script generator. This module should not be used directly.
Instead, use the `Shell.Completion` module to generate completion scripts.
"""
alias Shell.Completion, as: API
@spec generate_zsh_script(API.command_spec()) :: String.t()
def generate_zsh_script(spec) when is_map(spec) do
case validate_command_spec(spec) do
:ok ->
tables =
build_tables(
spec,
spec.name,
%{subcommands: %{}, options: %{}, option_args: %{}, argument: %{}}
)
generate_zsh_script_from_tables(spec.name, tables)
error ->
error
end
end
@spec validate_command_spec(API.command_spec()) :: :ok | {:error, String.t()}
def validate_command_spec(spec) when is_map(spec) do
if Map.has_key?(spec, :name), do: :ok, else: {:error, "Command spec must have a :name key"}
end
@spec build_tables(map(), String.t(), map()) :: map()
defp build_tables(spec, current_path, tables) do
tables =
update_in(tables, [:subcommands, current_path], fn existing ->
existing || []
end)
tables =
update_in(tables, [:options, current_path], fn existing ->
existing || []
end)
tables = put_in(tables, [:argument, current_path], nil)
tables =
Enum.reduce(Map.get(spec, :options, []), tables, fn opt, acc ->
acc =
update_in(acc, [:options, current_path], fn list -> (list || []) ++ [opt[:name]] end)
if Map.has_key?(opt, :from) do
takes_argument = Map.get(opt, :takes_argument, true)
if takes_argument do
put_in(
acc,
[:option_args, "#{current_path}:#{opt[:name]}"],
completion_source_to_zsh(opt[:from])
)
else
acc
end
else
acc
end
end)
args = Map.get(spec, :arguments, [])
tables =
case args do
[arg | _] ->
put_in(tables, [:argument, current_path], completion_source_to_zsh(arg[:from]))
_ ->
tables
end
tables =
Enum.reduce(Map.get(spec, :subcommands, []), tables, fn sub, acc ->
acc =
update_in(acc, [:subcommands, current_path], fn list -> (list || []) ++ [sub[:name]] end)
new_path = current_path <> ":" <> sub[:name]
acc = put_in(acc, [:subcommands, new_path], [])
acc = put_in(acc, [:options, new_path], [])
acc = put_in(acc, [:argument, new_path], nil)
build_tables(sub, new_path, acc)
end)
tables
end
@spec completion_source_to_zsh(API.completion_source()) :: String.t()
defp completion_source_to_zsh({:choices, choices}) when is_list(choices) do
"choices:" <> Enum.join(choices, " ")
end
defp completion_source_to_zsh({:command, cmd}) when is_binary(cmd) do
"cmd:" <> cmd
end
defp completion_source_to_zsh(:files), do: "files"
defp completion_source_to_zsh(:directories), do: "directories"
defp completion_source_to_zsh(fun) when is_function(fun, 1) do
raise "Custom function completions are not supported in zsh"
end
@spec generate_zsh_script_from_tables(String.t(), map()) :: String.t()
defp generate_zsh_script_from_tables(command_name, tables) do
header =
"#compdef #{command_name}\n" <>
"# Zsh completion script for #{command_name}\n" <>
"typeset -A _sc_subcommands\n" <>
"typeset -A _sc_options\n" <>
"typeset -A _sc_option_args\n" <>
"typeset -A _sc_argument\n\n"
subcommands_lines =
tables.subcommands
|> Enum.map(fn {path, subs} ->
~s(_sc_subcommands["#{path}"]="#{Enum.join(subs, " ")}")
end)
|> Enum.join("\n")
options_lines =
tables.options
|> Enum.map(fn {path, opts} ->
~s(_sc_options["#{path}"]="#{Enum.join(opts, " ")}")
end)
|> Enum.join("\n")
option_args_lines =
tables.option_args
|> Enum.map(fn {key, spec} ->
~s(_sc_option_args["#{key}"]="#{spec}")
end)
|> Enum.join("\n")
argument_lines =
tables.argument
|> Enum.filter(fn {_path, arg} -> not is_nil(arg) end)
|> Enum.map(fn {path, arg} ->
~s(_sc_argument["#{path}"]="#{arg}")
end)
|> Enum.join("\n")
function_body = """
_#{command_name}_completion() {
local cur prev path
cur=${words[$CURRENT]}
prev=${words[$CURRENT-1]}
path=${words[1]}
local i token
for (( i=2; i<=$CURRENT; i++ )); do
token=${words[i]}
if [[ "$token" == -* ]]; then
continue
fi
local subs=${_sc_subcommands[$path]}
if [[ -n "$subs" ]]; then
for sub in $subs; do
if [[ "$token" == "$sub" ]]; then
path="$path:$token"
break
fi
done
fi
done
if [[ "$prev" == -* ]]; then
local key="${path}:${prev}"
if [[ -n "${_sc_option_args[$key]}" ]]; then
local spec=${_sc_option_args[$key]}
if [[ "$spec" == choices:* ]]; then
local choices=${spec#choices:}
compadd -- $=choices
return 0
elif [[ "$spec" == "files" ]]; then
_files
return 0
elif [[ "$spec" == "directories" ]]; then
_directories
return 0
elif [[ "$spec" == cmd:* ]]; then
local cmd=${spec#cmd:}
local choices
choices=$(eval $cmd)
compadd -- $=choices
return 0
fi
fi
fi
if [[ "$cur" == -* ]]; then
local opts=${_sc_options[$path]}
compadd -- $=opts
return 0
fi
local subs=${_sc_subcommands[$path]}
if [[ -n "$subs" ]]; then
compadd -- $=subs
return 0
fi
local arg=${_sc_argument[$path]}
if [[ -n "$arg" ]]; then
if [[ "$arg" == choices:* ]]; then
local choices=${arg#choices:}
compadd -- $=choices
return 0
elif [[ "$arg" == "files" ]]; then
_files
return 0
elif [[ "$arg" == "directories" ]]; then
_directories
return 0
elif [[ "$arg" == cmd:* ]]; then
local cmd=${arg#cmd:}
local choices
choices=$(eval $cmd)
compadd -- $=choices
return 0
fi
fi
return 0
}
compdef _#{command_name}_completion #{command_name}
"""
[
header,
subcommands_lines,
options_lines,
option_args_lines,
argument_lines,
"\n",
function_body
]
|> Enum.filter(&(&1 != ""))
|> Enum.join("\n")
end
end