clamp 1.5.0 → 1.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGES.md +4 -0
- data/lib/clamp/completion/bash_generator.rb +103 -35
- data/lib/clamp/completion/fish_generator.rb +148 -17
- data/lib/clamp/completion/zsh_generator.rb +22 -25
- data/lib/clamp/completion.rb +24 -0
- data/lib/clamp/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ceef639e7f7761b30abc7be04ab5384ee2dc238ba472a12c808e2cdffc14e540
|
|
4
|
+
data.tar.gz: 7f97fc3d84a9fad89123fad6ed35dffeb37efc9e82815a44a6ca76ad134905ec
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 886c0eb9476e36c0c46e07812a412108f8575799f2a1b5588d0e6ed6feac5d7d8fe25ea27a1b8c4e244e691a62a9c502f0ecdb848eb56022cf48004f353d11ed
|
|
7
|
+
data.tar.gz: 18cc7167ae022ea13afd50b2367e47f5a9832e67d3754c683077125d4eaa99e75efba69b736fd327173ce9b2662f16be43bdbf53eea33f17b409703579f11b03
|
data/CHANGES.md
CHANGED
|
@@ -19,22 +19,25 @@ module Clamp
|
|
|
19
19
|
"",
|
|
20
20
|
takes_value_function,
|
|
21
21
|
"",
|
|
22
|
-
|
|
22
|
+
param_count_function,
|
|
23
23
|
"",
|
|
24
|
-
|
|
24
|
+
canonical_function,
|
|
25
|
+
"",
|
|
26
|
+
main_function,
|
|
27
|
+
"",
|
|
28
|
+
"complete -F #{completion_function} #{@executable_name}"
|
|
25
29
|
].push("").join("\n")
|
|
26
30
|
end
|
|
27
31
|
|
|
28
32
|
private
|
|
29
33
|
|
|
30
|
-
def
|
|
31
|
-
@executable_name
|
|
34
|
+
def completion_function
|
|
35
|
+
"_clamp_complete_#{Completion.encode_name(@executable_name)}"
|
|
32
36
|
end
|
|
33
37
|
|
|
34
|
-
def
|
|
35
|
-
fn = function_name
|
|
38
|
+
def main_function
|
|
36
39
|
[
|
|
37
|
-
"
|
|
40
|
+
"#{completion_function}() {",
|
|
38
41
|
" local cur prev",
|
|
39
42
|
" if type _init_completion &>/dev/null; then",
|
|
40
43
|
" _init_completion",
|
|
@@ -43,13 +46,21 @@ module Clamp
|
|
|
43
46
|
' prev="${COMP_WORDS[COMP_CWORD-1]}"',
|
|
44
47
|
" fi",
|
|
45
48
|
"",
|
|
46
|
-
" local subcmd",
|
|
47
|
-
"
|
|
48
|
-
|
|
49
|
+
" local subcmd_info subcmd params_remaining",
|
|
50
|
+
" subcmd_info=$(#{completion_function}_find_subcmd)",
|
|
51
|
+
' subcmd="${subcmd_info%% *}"',
|
|
52
|
+
' params_remaining="${subcmd_info##* }"',
|
|
53
|
+
" if #{completion_function}_takes_value \"$prev\" \"$subcmd\"; then",
|
|
49
54
|
" return",
|
|
50
55
|
" fi",
|
|
51
56
|
"",
|
|
52
|
-
|
|
57
|
+
' if [[ "$cur" == -* ]]; then',
|
|
58
|
+
options_case("$subcmd"),
|
|
59
|
+
" fi",
|
|
60
|
+
"",
|
|
61
|
+
' if [ "$params_remaining" -eq 0 ]; then',
|
|
62
|
+
subcommands_case("$subcmd"),
|
|
63
|
+
" fi",
|
|
53
64
|
"}",
|
|
54
65
|
"",
|
|
55
66
|
find_subcmd_function
|
|
@@ -57,54 +68,111 @@ module Clamp
|
|
|
57
68
|
end
|
|
58
69
|
|
|
59
70
|
def find_subcmd_function
|
|
60
|
-
fn = function_name
|
|
61
|
-
subcmds = Completion.collect_subcommand_names(@command_class).join("|")
|
|
62
71
|
[
|
|
63
|
-
"
|
|
64
|
-
" local i=1 word subcmd",
|
|
72
|
+
"#{completion_function}_find_subcmd() {",
|
|
73
|
+
" local i=1 word subcmd skip",
|
|
74
|
+
" skip=$(#{completion_function}_param_count \"\")",
|
|
65
75
|
' while [ "$i" -lt "$COMP_CWORD" ]; do',
|
|
66
76
|
' word="${COMP_WORDS[$i]}"',
|
|
67
77
|
' case "$word" in',
|
|
68
78
|
" -*)",
|
|
69
|
-
" if
|
|
79
|
+
" if #{completion_function}_takes_value \"$word\" \"$subcmd\"; then",
|
|
70
80
|
" ((i++))",
|
|
71
81
|
" fi",
|
|
72
82
|
" ;;",
|
|
73
83
|
" *)",
|
|
74
|
-
|
|
75
|
-
" #{subcmds})",
|
|
76
|
-
' if [ -z "$subcmd" ]; then',
|
|
77
|
-
' subcmd="$word"',
|
|
78
|
-
" else",
|
|
79
|
-
' subcmd="${subcmd}::${word}"',
|
|
80
|
-
" fi",
|
|
81
|
-
" ;;",
|
|
82
|
-
" esac",
|
|
84
|
+
find_subcmd_match_word,
|
|
83
85
|
" ;;",
|
|
84
86
|
" esac",
|
|
85
87
|
" ((i++))",
|
|
86
88
|
" done",
|
|
87
|
-
' echo "$subcmd"',
|
|
89
|
+
' echo "$subcmd $skip"',
|
|
88
90
|
"}"
|
|
89
91
|
].join("\n")
|
|
90
92
|
end
|
|
91
93
|
|
|
92
|
-
def
|
|
94
|
+
def find_subcmd_match_word
|
|
95
|
+
subcmds = Completion.collect_subcommand_names(@command_class).join("|")
|
|
96
|
+
[
|
|
97
|
+
' if [ "$skip" -gt 0 ]; then',
|
|
98
|
+
" ((skip--))",
|
|
99
|
+
" else",
|
|
100
|
+
' case "$word" in',
|
|
101
|
+
" #{subcmds})",
|
|
102
|
+
" local canonical=$(#{completion_function}_canonical \"$word\")",
|
|
103
|
+
' if [ -z "$subcmd" ]; then',
|
|
104
|
+
' subcmd="$canonical"',
|
|
105
|
+
" else",
|
|
106
|
+
' subcmd="${subcmd}::${canonical}"',
|
|
107
|
+
" fi",
|
|
108
|
+
" skip=$(#{completion_function}_param_count \"$subcmd\")",
|
|
109
|
+
" ;;",
|
|
110
|
+
" esac",
|
|
111
|
+
" fi"
|
|
112
|
+
].join("\n")
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def options_case(var)
|
|
116
|
+
build_case(var, " ", "COMPREPLY=") do |cmd, _has_children|
|
|
117
|
+
Completion.visible_options(cmd).flat_map { |o| Completion.expanded_switches(o) }
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def subcommands_case(var)
|
|
122
|
+
build_case(var, " ", "COMPREPLY+=") do |cmd, has_children|
|
|
123
|
+
cmd.recognised_subcommands.flat_map(&:names) if has_children
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def build_case(var, indent, assign)
|
|
93
128
|
entries = {}
|
|
94
129
|
Completion.walk_command_tree(@command_class) do |cmd, path, has_children|
|
|
130
|
+
words = yield(cmd, has_children)
|
|
131
|
+
next if words.nil? || words.empty?
|
|
132
|
+
|
|
95
133
|
path_str = path.map { |sub| sub.names.first }.join("::")
|
|
96
|
-
words = Completion.visible_options(cmd).flat_map { |o| Completion.expanded_switches(o) }
|
|
97
|
-
cmd.recognised_subcommands.each { |sub| words.concat(sub.names) } if has_children
|
|
98
134
|
entries[path_str] = words.join(" ")
|
|
99
135
|
end
|
|
100
|
-
lines = ["
|
|
136
|
+
lines = ["#{indent}case \"#{var}\" in"]
|
|
101
137
|
entries.each do |path, words|
|
|
102
138
|
pattern = path.empty? ? '""' : "\"#{path}\""
|
|
103
|
-
lines << "
|
|
104
|
-
lines << "
|
|
105
|
-
lines << "
|
|
139
|
+
lines << "#{indent} #{pattern})"
|
|
140
|
+
lines << "#{indent} #{assign}($(compgen -W \"#{words}\" -- \"$cur\"))"
|
|
141
|
+
lines << "#{indent} ;;"
|
|
142
|
+
end
|
|
143
|
+
lines << "#{indent}esac"
|
|
144
|
+
lines.join("\n")
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def param_count_function
|
|
148
|
+
entries = {}
|
|
149
|
+
Completion.walk_command_tree(@command_class) do |cmd, path, has_children|
|
|
150
|
+
next unless has_children
|
|
151
|
+
|
|
152
|
+
entries[path.map { |s| s.names.first }.join("::")] = Completion.required_parameter_count(cmd)
|
|
153
|
+
end
|
|
154
|
+
build_lookup_function("param_count", entries, "echo", default: "echo 0")
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def canonical_function
|
|
158
|
+
aliases = {}
|
|
159
|
+
Completion.walk_command_tree(@command_class) do |cmd, _path, has_children|
|
|
160
|
+
next unless has_children
|
|
161
|
+
|
|
162
|
+
cmd.recognised_subcommands.each do |sub|
|
|
163
|
+
sub.names.drop(1).each { |name| aliases[name] = sub.names.first }
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
build_lookup_function("canonical", aliases, "echo", default: 'echo "$1"')
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def build_lookup_function(suffix, entries, verb, default:)
|
|
170
|
+
lines = ["#{completion_function}_#{suffix}() {", ' case "$1" in']
|
|
171
|
+
entries.each do |key, value|
|
|
172
|
+
pattern = key.empty? ? '""' : key
|
|
173
|
+
lines << " #{pattern}) #{verb} #{value.is_a?(String) ? "\"#{value}\"" : value} ;;"
|
|
106
174
|
end
|
|
107
|
-
lines
|
|
175
|
+
lines.push(" *) #{default} ;;", " esac", "}")
|
|
108
176
|
lines.join("\n")
|
|
109
177
|
end
|
|
110
178
|
|
|
@@ -116,7 +184,7 @@ module Clamp
|
|
|
116
184
|
.flat_map { |o| Completion.expanded_switches(o) }
|
|
117
185
|
end
|
|
118
186
|
lines = [
|
|
119
|
-
"
|
|
187
|
+
"#{completion_function}_takes_value() {",
|
|
120
188
|
' local option="$1"',
|
|
121
189
|
' local subcmd="$2"',
|
|
122
190
|
' case "$subcmd" in'
|
|
@@ -16,40 +16,119 @@ module Clamp
|
|
|
16
16
|
lines << "# Fish completions for #{@executable_name}"
|
|
17
17
|
lines << "# Generated by Clamp"
|
|
18
18
|
lines << ""
|
|
19
|
+
helpers = [subcmd_args_function]
|
|
20
|
+
|
|
21
|
+
# Track visible option switches at each depth, so we can identify
|
|
22
|
+
# which options are new at each level. Depth-first walk order means
|
|
23
|
+
# the entry at depth N-1 is always the current node's parent.
|
|
24
|
+
switches_at_depth = []
|
|
25
|
+
|
|
19
26
|
Completion.walk_command_tree(@command_class) do |cmd, path, has_children|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
Completion.visible_options(cmd).each do |option|
|
|
23
|
-
lines << option_completion(option, condition)
|
|
24
|
-
end
|
|
27
|
+
generate_option_completions(lines, cmd, path, switches_at_depth)
|
|
28
|
+
|
|
25
29
|
next unless has_children
|
|
26
30
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
end
|
|
31
|
+
# Subcommand names need an exclusive condition (only at this level).
|
|
32
|
+
child_names = cmd.recognised_subcommands.flat_map(&:names)
|
|
33
|
+
exclusive_cond = condition_for(path, child_names)
|
|
34
|
+
subcmd_cond = subcommand_condition(cmd, path, exclusive_cond, helpers)
|
|
35
|
+
generate_subcommand_completions(lines, cmd, subcmd_cond)
|
|
33
36
|
lines << ""
|
|
34
37
|
end
|
|
35
|
-
"#{lines.join("\n")}\n"
|
|
38
|
+
"#{helpers.join("\n\n")}\n\n#{lines.join("\n")}\n"
|
|
36
39
|
end
|
|
37
40
|
|
|
38
41
|
private
|
|
39
42
|
|
|
43
|
+
def subcommand_condition(cmd, path, condition, helpers)
|
|
44
|
+
param_count = Completion.required_parameter_count(cmd)
|
|
45
|
+
return condition unless param_count.positive?
|
|
46
|
+
|
|
47
|
+
helper = ParamsSatisfiedHelper.new(@executable_name, path, param_count)
|
|
48
|
+
helpers << helper.to_s
|
|
49
|
+
"#{condition}; and #{helper.function_name}"
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def generate_subcommand_completions(lines, cmd, condition)
|
|
53
|
+
cmd.recognised_subcommands.each do |sub|
|
|
54
|
+
sub.names.each do |name|
|
|
55
|
+
lines << "complete -c #{@executable_name} -f -n '#{condition}' -a #{name} " \
|
|
56
|
+
"-d '#{escape(sub.description)}'"
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Emit only options new at this level (not inherited from parent).
|
|
62
|
+
# Use an inclusive condition so they also apply to child subcommands.
|
|
63
|
+
def generate_option_completions(lines, cmd, path, switches_at_depth)
|
|
64
|
+
depth = path.length
|
|
65
|
+
visible = Completion.visible_options(cmd)
|
|
66
|
+
parent_switches = depth.positive? ? switches_at_depth[depth - 1] : nil
|
|
67
|
+
new_options = if parent_switches
|
|
68
|
+
visible.reject { |o| parent_switches.include?(o.switches) }
|
|
69
|
+
else
|
|
70
|
+
visible
|
|
71
|
+
end
|
|
72
|
+
option_cond = depth.positive? ? inclusive_condition_for(path) : nil
|
|
73
|
+
new_options.each do |option|
|
|
74
|
+
lines << option_completion(option, option_cond)
|
|
75
|
+
end
|
|
76
|
+
switches_at_depth[depth] = Set.new(visible.map(&:switches))
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Condition that matches this level AND all child subcommands.
|
|
80
|
+
def inclusive_condition_for(path)
|
|
81
|
+
path.map { |sub| "#{completion_function}_seen_subcommand_from #{sub.names.join(' ')}" }.join("; and ")
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Condition that matches this level only (excludes child subcommands).
|
|
40
85
|
def condition_for(path, child_names)
|
|
41
86
|
if path.empty?
|
|
42
|
-
"
|
|
87
|
+
"not #{completion_function}_subcmd_args >/dev/null"
|
|
43
88
|
else
|
|
44
|
-
parts = path.map { |sub| "
|
|
45
|
-
parts << "not
|
|
89
|
+
parts = path.map { |sub| "#{completion_function}_seen_subcommand_from #{sub.names.join(' ')}" }
|
|
90
|
+
parts << "not #{completion_function}_seen_subcommand_from #{child_names.join(' ')}" if child_names.any?
|
|
46
91
|
parts.join("; and ")
|
|
47
92
|
end
|
|
48
93
|
end
|
|
49
94
|
|
|
50
|
-
def
|
|
95
|
+
def completion_function
|
|
96
|
+
"_clamp_complete_#{@executable_name}"
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def subcmd_args_function
|
|
100
|
+
optspecs = argparse_optspecs(@command_class)
|
|
101
|
+
lines = [
|
|
102
|
+
"function #{completion_function}_subcmd_args",
|
|
103
|
+
" set -l tokens (commandline -opc)",
|
|
104
|
+
" set -e tokens[1]",
|
|
105
|
+
" argparse -si #{optspecs.map { |s| "'#{s}'" }.join(' ')} -- $tokens 2>/dev/null",
|
|
106
|
+
" or return 1",
|
|
107
|
+
" test (count $argv) -gt 0",
|
|
108
|
+
" and printf '%s\\n' $argv",
|
|
109
|
+
"end",
|
|
110
|
+
"",
|
|
111
|
+
"function #{completion_function}_seen_subcommand_from",
|
|
112
|
+
" for p in (#{completion_function}_subcmd_args)",
|
|
113
|
+
" if contains -- $p $argv",
|
|
114
|
+
" return 0",
|
|
115
|
+
" end",
|
|
116
|
+
" end",
|
|
117
|
+
" return 1",
|
|
118
|
+
"end"
|
|
119
|
+
]
|
|
120
|
+
lines.join("\n")
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def argparse_optspecs(command_class)
|
|
124
|
+
command_class.recognised_options.flat_map do |option|
|
|
125
|
+
Completion.argparse_specs_for(option)
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def option_completion(option, condition = nil)
|
|
51
130
|
parts = ["complete -c #{@executable_name} -f"]
|
|
52
|
-
parts << "-n '#{condition}'"
|
|
131
|
+
parts << "-n '#{condition}'" if condition
|
|
53
132
|
|
|
54
133
|
Completion.expanded_switches(option).each do |switch|
|
|
55
134
|
parts << if switch.start_with?("--")
|
|
@@ -71,5 +150,57 @@ module Clamp
|
|
|
71
150
|
|
|
72
151
|
end
|
|
73
152
|
|
|
153
|
+
# Generates a fish function that checks whether required parameters
|
|
154
|
+
# have been provided before offering subcommand completions.
|
|
155
|
+
class ParamsSatisfiedHelper
|
|
156
|
+
|
|
157
|
+
def initialize(executable_name, path, required_count)
|
|
158
|
+
@executable_name = executable_name
|
|
159
|
+
@path = path
|
|
160
|
+
@required_count = required_count
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def function_name
|
|
164
|
+
parts = [@executable_name]
|
|
165
|
+
@path.each { |sub| parts << sub.names.first }
|
|
166
|
+
"_clamp_complete_#{parts.join('_')}_params_satisfied"
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def to_s
|
|
170
|
+
lines = ["function #{function_name}", " set -l tokens (commandline -opc)", " set -l positional 0"]
|
|
171
|
+
if @path.empty?
|
|
172
|
+
count_positional_root(lines)
|
|
173
|
+
else
|
|
174
|
+
count_positional_nested(lines)
|
|
175
|
+
end
|
|
176
|
+
lines.push(" test $positional -ge #{@required_count}", "end")
|
|
177
|
+
lines.join("\n")
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
private
|
|
181
|
+
|
|
182
|
+
def count_positional_root(lines)
|
|
183
|
+
lines.push(" for token in $tokens[2..]",
|
|
184
|
+
" if not string match -q -- '-*' $token",
|
|
185
|
+
" set positional (math $positional + 1)",
|
|
186
|
+
" end", " end")
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
def count_positional_nested(lines)
|
|
190
|
+
lines.push(" set -l depth 0", " for token in $tokens[2..]", " switch $depth")
|
|
191
|
+
@path.each_with_index do |sub, i|
|
|
192
|
+
all_names = sub.names.join(" ")
|
|
193
|
+
lines.push(" case #{i}",
|
|
194
|
+
" if contains -- $token #{all_names}",
|
|
195
|
+
" set depth #{i + 1}", " end")
|
|
196
|
+
end
|
|
197
|
+
lines.push(" case #{@path.length}",
|
|
198
|
+
" if not string match -q -- '-*' $token",
|
|
199
|
+
" set positional (math $positional + 1)",
|
|
200
|
+
" end", " end", " end")
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
end
|
|
204
|
+
|
|
74
205
|
end
|
|
75
206
|
end
|
|
@@ -14,27 +14,27 @@ module Clamp
|
|
|
14
14
|
|
|
15
15
|
def generate
|
|
16
16
|
lines = ["#compdef #{@executable_name}", ""]
|
|
17
|
-
generate_functions(lines, @command_class, [
|
|
18
|
-
lines <<
|
|
17
|
+
generate_functions(lines, @command_class, [completion_function], Set.new)
|
|
18
|
+
lines << completion_function
|
|
19
19
|
lines.push("").join("\n")
|
|
20
20
|
end
|
|
21
21
|
|
|
22
22
|
private
|
|
23
23
|
|
|
24
|
-
def
|
|
25
|
-
@executable_name
|
|
24
|
+
def completion_function
|
|
25
|
+
"_clamp_complete_#{Completion.encode_name(@executable_name)}"
|
|
26
26
|
end
|
|
27
27
|
|
|
28
28
|
def generate_functions(lines, command_class, path, visited)
|
|
29
29
|
has_children = command_class.has_subcommands? && !visited.include?(command_class)
|
|
30
30
|
visited |= [command_class]
|
|
31
|
-
func_name =
|
|
31
|
+
func_name = path.join("_")
|
|
32
32
|
|
|
33
33
|
if has_children
|
|
34
34
|
generate_subcommand_node(lines, command_class, path, func_name, visited)
|
|
35
35
|
else
|
|
36
36
|
lines << "#{func_name}() {"
|
|
37
|
-
specs = Completion.visible_options(command_class).
|
|
37
|
+
specs = Completion.visible_options(command_class).flat_map { |o| option_specs(o) }
|
|
38
38
|
generate_arguments_call(lines, specs) if specs.any?
|
|
39
39
|
lines << "}"
|
|
40
40
|
lines << ""
|
|
@@ -46,7 +46,7 @@ module Clamp
|
|
|
46
46
|
lines << " local context state state_descr line"
|
|
47
47
|
lines << " typeset -A opt_args"
|
|
48
48
|
lines << ""
|
|
49
|
-
specs = Completion.visible_options(command_class).
|
|
49
|
+
specs = Completion.visible_options(command_class).flat_map { |o| option_specs(o) }
|
|
50
50
|
specs << "'1:command:->commands'"
|
|
51
51
|
specs << "'*::args:->args'"
|
|
52
52
|
lines << " _arguments -C \\"
|
|
@@ -56,7 +56,7 @@ module Clamp
|
|
|
56
56
|
lines << "}"
|
|
57
57
|
lines << ""
|
|
58
58
|
command_class.recognised_subcommands.each do |sub|
|
|
59
|
-
generate_functions(lines, sub.subcommand_class, path + [
|
|
59
|
+
generate_functions(lines, sub.subcommand_class, path + [Completion.encode_name(sub.names.first)], visited)
|
|
60
60
|
end
|
|
61
61
|
end
|
|
62
62
|
|
|
@@ -88,7 +88,7 @@ module Clamp
|
|
|
88
88
|
lines << " args)"
|
|
89
89
|
lines << " case $line[1] in"
|
|
90
90
|
command_class.recognised_subcommands.each do |sub|
|
|
91
|
-
sub_fn =
|
|
91
|
+
sub_fn = (path + [Completion.encode_name(sub.names.first)]).join("_")
|
|
92
92
|
pattern = sub.names.join("|")
|
|
93
93
|
lines << " #{pattern}) #{sub_fn} ;;"
|
|
94
94
|
end
|
|
@@ -97,23 +97,20 @@ module Clamp
|
|
|
97
97
|
lines << " esac"
|
|
98
98
|
end
|
|
99
99
|
|
|
100
|
-
def
|
|
100
|
+
def option_specs(option)
|
|
101
101
|
expanded = Completion.expanded_switches(option)
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
exclusion = expanded.length > 1 ? "(#{expanded.join(' ')})" : ""
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
def sanitize(name)
|
|
116
|
-
name.gsub(/[^a-zA-Z0-9_]/, "_")
|
|
102
|
+
suffix = "[#{escape(option.description)}]"
|
|
103
|
+
suffix += ":#{option.type.to_s.downcase}:" unless option.flag?
|
|
104
|
+
exclusion = expanded.length > 1 ? "'(#{expanded.join(' ')})'" : ""
|
|
105
|
+
short = expanded.find { |s| s.match?(/^-[^-]/) }
|
|
106
|
+
longs = expanded.grep(/^--/)
|
|
107
|
+
|
|
108
|
+
if short && longs.length == 1
|
|
109
|
+
# Braces outside quotes for zsh brace expansion
|
|
110
|
+
["#{exclusion}{#{short},#{longs.first}}'#{suffix}'"]
|
|
111
|
+
else
|
|
112
|
+
expanded.map { |sw| "#{exclusion}'#{sw}#{suffix}'" }
|
|
113
|
+
end
|
|
117
114
|
end
|
|
118
115
|
|
|
119
116
|
def escape(str)
|
data/lib/clamp/completion.rb
CHANGED
|
@@ -33,6 +33,12 @@ module Clamp
|
|
|
33
33
|
|
|
34
34
|
module_function
|
|
35
35
|
|
|
36
|
+
# Encode a name for use as a shell function identifier.
|
|
37
|
+
# Special characters are replaced with _XX hex codes.
|
|
38
|
+
def encode_name(name)
|
|
39
|
+
name.gsub(/[^a-zA-Z0-9_]/) { |c| format("_%02x", c.ord) }
|
|
40
|
+
end
|
|
41
|
+
|
|
36
42
|
def generate(command_class, shell, executable_name)
|
|
37
43
|
generator_class = GENERATORS.fetch(shell) do
|
|
38
44
|
raise ArgumentError, "unsupported shell: #{shell.inspect}"
|
|
@@ -71,6 +77,24 @@ module Clamp
|
|
|
71
77
|
end
|
|
72
78
|
end
|
|
73
79
|
|
|
80
|
+
# Count required, non-multivalued parameters for a command.
|
|
81
|
+
def required_parameter_count(command_class)
|
|
82
|
+
command_class.parameters.count { |p| p.required? && !p.multivalued? }
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Return fish argparse optspecs for an option.
|
|
86
|
+
def argparse_specs_for(option)
|
|
87
|
+
switches = expanded_switches(option)
|
|
88
|
+
suffix = option.flag? ? "" : "="
|
|
89
|
+
short = switches.find { |s| s.match?(/^-[^-]$/) }
|
|
90
|
+
longs = switches.select { |s| s.start_with?("--") }
|
|
91
|
+
if short && longs.length == 1
|
|
92
|
+
["#{short.delete_prefix('-')}/#{longs.first.delete_prefix('--')}#{suffix}"]
|
|
93
|
+
else
|
|
94
|
+
longs.map { |l| "#{l.delete_prefix('--')}#{suffix}" }
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
74
98
|
# Collect all subcommand names across the command tree.
|
|
75
99
|
def collect_subcommand_names(command_class)
|
|
76
100
|
names = []
|
data/lib/clamp/version.rb
CHANGED