@cryptotaxi247 / kubo / commits / 0060d5f7c

feat: programmatic shell completions

These are missing some of the features of the current hand-rolled completions, but: 1. Are less buggy. 2. Cover _all_ commands. 3. Don't need to be manually maintained (which we never do anyways). fixes #4551 fixes #8033

Steven Allen committed Apr 1, 2021 at 00:47 UTC 0060d5f7c8a14931b75af8eda20bd327c08c675b
7 files changed +213 -982
README.md
+1 -1
@@ -220,7 +220,7 @@ dependencies as well.
220 We strongly recommend you use the [latest version of OSX FUSE](http://osxfuse.github.io/).
221 (See https://github.com/ipfs/go-ipfs/issues/177)
222 - Read [docs/fuse.md](docs/fuse.md) for more details on setting up FUSE (so that you can mount the filesystem).
223 -- Shell command completion is available in `misc/completion/ipfs-completion.bash`. Read [docs/command-completion.md](docs/command-completion.md) to learn how to install it.
223 +- Shell command completions can be generated with one of the `ipfs commands completion` subcommands. Read [docs/command-completion.md](docs/command-completion.md) to learn more.
224 - See the [misc folder](https://github.com/ipfs/go-ipfs/tree/master/misc) for how to connect IPFS to systemd or whatever init system your distro uses.
225
226 ### Updating go-ipfs
core/commands/commands.go
+42
@@ -6,6 +6,7 @@
6 package commands
7
8 import (
9 + "bytes"
10 "fmt"
11 "io"
12 "os"
@@ -63,6 +64,9 @@ func CommandsCmd(root *cmds.Command) *cmds.Command {
64 Tagline: "List all available commands.",
65 ShortDescription: `Lists all available commands (and subcommands) and exits.`,
66 },
67 + Subcommands: map[string]*cmds.Command{
68 + "completion": CompletionCmd(root),
69 + },
70 Options: []cmds.Option{
71 cmds.BoolOption(flagsOptionName, "f", "Show command flags"),
72 },
@@ -131,6 +135,44 @@ func cmdPathStrings(cmd *Command, showOptions bool) []string {
135 return cmds
136 }
137
138 +func CompletionCmd(root *cmds.Command) *cmds.Command {
139 + return &cmds.Command{
140 + Helptext: cmds.HelpText{
141 + Tagline: "Generate shell completions.",
142 + },
143 + NoRemote: true,
144 + Subcommands: map[string]*cmds.Command{
145 + "bash": {
146 + Helptext: cmds.HelpText{
147 + Tagline: "Generate bash shell completions.",
148 + ShortDescription: "Generates command completions for the bash shell.",
149 + LongDescription: `
150 +Generates command completions for the bash shell.
151 +
152 +The simplest way to see it working is write the completions
153 +to a file and then source it:
154 +
155 + > ipfs commands completion bash > ipfs-completion.bash
156 + > source ./ipfs-completion.bash
157 +
158 +To install the completions permanently, they can be moved to
159 +/etc/bash_completion.d or sourced from your ~/.bashrc file.
160 +`,
161 + },
162 + NoRemote: true,
163 + Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
164 + var buf bytes.Buffer
165 + if err := writeBashCompletions(root, &buf); err != nil {
166 + return err
167 + }
168 + res.SetLength(uint64(buf.Len()))
169 + return res.Emit(&buf)
170 + },
171 + },
172 + },
173 + }
174 +}
175 +
176 type nonFatalError string
177
178 // streamResult is a helper function to stream results that possibly
core/commands/commands_test.go
+4
@@ -22,6 +22,8 @@ func TestROCommands(t *testing.T) {
22 "/block/stat",
23 "/cat",
24 "/commands",
25 + "/commands/completion",
26 + "/commands/completion/bash",
27 "/dag",
28 "/dag/get",
29 "/dag/resolve",
@@ -89,6 +91,8 @@ func TestCommands(t *testing.T) {
91 "/bootstrap/rm/all",
92 "/cat",
93 "/commands",
94 + "/commands/completion",
95 + "/commands/completion/bash",
96 "/config",
97 "/config/edit",
98 "/config/replace",
core/commands/completion.go new
+142
@@ -0,0 +1,142 @@
1 +package commands
2 +
3 +import (
4 + "io"
5 + "sort"
6 + "text/template"
7 +
8 + cmds "github.com/ipfs/go-ipfs-cmds"
9 +)
10 +
11 +type completionCommand struct {
12 + Name string
13 + Subcommands []*completionCommand
14 + ShortFlags []string
15 + ShortOptions []string
16 + LongFlags []string
17 + LongOptions []string
18 +}
19 +
20 +func commandToCompletions(name string, cmd *cmds.Command) *completionCommand {
21 + parsed := &completionCommand{
22 + Name: name,
23 + }
24 + for name, subCmd := range cmd.Subcommands {
25 + parsed.Subcommands = append(parsed.Subcommands, commandToCompletions(name, subCmd))
26 + }
27 + sort.Slice(parsed.Subcommands, func(i, j int) bool {
28 + return parsed.Subcommands[i].Name < parsed.Subcommands[j].Name
29 + })
30 +
31 + for _, opt := range cmd.Options {
32 + if opt.Type() == cmds.Bool {
33 + parsed.LongFlags = append(parsed.LongFlags, opt.Name())
34 + for _, name := range opt.Names() {
35 + if len(name) == 1 {
36 + parsed.ShortFlags = append(parsed.ShortFlags, name)
37 + break
38 + }
39 + }
40 + } else {
41 + parsed.LongOptions = append(parsed.LongOptions, opt.Name())
42 + for _, name := range opt.Names() {
43 + if len(name) == 1 {
44 + parsed.ShortOptions = append(parsed.ShortOptions, name)
45 + break
46 + }
47 + }
48 + }
49 + }
50 + sort.Slice(parsed.LongFlags, func(i, j int) bool {
51 + return parsed.LongFlags[i] < parsed.LongFlags[j]
52 + })
53 + sort.Slice(parsed.ShortFlags, func(i, j int) bool {
54 + return parsed.ShortFlags[i] < parsed.ShortFlags[j]
55 + })
56 + sort.Slice(parsed.LongOptions, func(i, j int) bool {
57 + return parsed.LongOptions[i] < parsed.LongOptions[j]
58 + })
59 + sort.Slice(parsed.ShortOptions, func(i, j int) bool {
60 + return parsed.ShortOptions[i] < parsed.ShortOptions[j]
61 + })
62 + return parsed
63 +}
64 +
65 +var bashCompletionTemplate *template.Template
66 +
67 +func init() {
68 + commandTemplate := template.Must(template.New("command").Parse(`
69 +while [[ ${index} -lt ${COMP_CWORD} ]]; do
70 + case "${COMP_WORDS[index]}" in
71 + -*)
72 + let index++
73 + continue
74 + ;;
75 + {{ range .Subcommands }}
76 + "{{ .Name }}")
77 + let index++
78 + {{ template "command" . }}
79 + return 0
80 + ;;
81 + {{ end }}
82 + esac
83 + break
84 +done
85 +
86 +if [[ "${word}" == -* ]]; then
87 +{{ if .ShortFlags -}}
88 + _ipfs_compgen -W $'{{ range .ShortFlags }}-{{.}} \n{{end}}' -- "${word}"
89 +{{ end -}}
90 +{{- if .ShortOptions -}}
91 + _ipfs_compgen -S = -W $'{{ range .ShortOptions }}-{{.}}\n{{end}}' -- "${word}"
92 +{{ end -}}
93 +{{- if .LongFlags -}}
94 + _ipfs_compgen -W $'{{ range .LongFlags }}--{{.}} \n{{end}}' -- "${word}"
95 +{{ end -}}
96 +{{- if .LongOptions -}}
97 + _ipfs_compgen -S = -W $'{{ range .LongOptions }}--{{.}}\n{{end}}' -- "${word}"
98 +{{ end -}}
99 + return 0
100 +fi
101 +
102 +while [[ ${index} -lt ${COMP_CWORD} ]]; do
103 + if [[ "${COMP_WORDS[index]}" != -* ]]; then
104 + let argidx++
105 + fi
106 + let index++
107 +done
108 +
109 +{{- if .Subcommands }}
110 +if [[ "${argidx}" -eq 0 ]]; then
111 + _ipfs_compgen -W $'{{ range .Subcommands }}{{.Name}} \n{{end}}' -- "${word}"
112 +fi
113 +{{ end -}}
114 +`))
115 +
116 + bashCompletionTemplate = template.Must(commandTemplate.New("root").Parse(`#!/bin/bash
117 +
118 +_ipfs_compgen() {
119 + local oldifs="$IFS"
120 + IFS=$'\n'
121 + while read -r line; do
122 + COMPREPLY+=("$line")
123 + done < <(compgen "$@")
124 + IFS="$oldifs"
125 +}
126 +
127 +_ipfs() {
128 + COMPREPLY=()
129 + local index=1
130 + local argidx=0
131 + local word="${COMP_WORDS[COMP_CWORD]}"
132 + {{ template "command" . }}
133 +}
134 +complete -o nosort -o nospace -o default -F _ipfs ipfs
135 +`))
136 +}
137 +
138 +// writeBashCompletions generates a bash completion script for the given command tree.
139 +func writeBashCompletions(cmd *cmds.Command, out io.Writer) error {
140 + cmds := commandToCompletions("ipfs", cmd)
141 + return bashCompletionTemplate.Execute(out, cmds)
142 +}
docs/command-completion.md
+9 -23
@@ -1,29 +1,15 @@
1 -Command Completion
2 -==================
1 +# Command Completion
2
4 -Shell command completion is provided by the script at
5 -[/misc/completion/ipfs-completion.bash](../misc/completion/ipfs-completion.bash).
3 +Shell command completions can be generated by running one of the `ipfs commands completions`
4 +sub-commands.
5
6 +The simplest way to see it working is write the completions
7 +to a file and then source it:
8
8 -Installation
9 -------------
10 -The simplest way to see it working is to run
11 -`source misc/completion/ipfs-completion.bash` straight from your shell. This
12 -is only temporary and to fully enable it, you'll have to follow one of the steps
13 -below.
14 -
15 -### Bash on Linux
16 -For bash, completion can be enabled in a couple of ways. One is to copy the
17 -completion script to the directory `~/.ipfs/` and then in the file
18 -`~/.bash_completion` add
9 ```bash
20 -source ~/.ipfs/ipfs-completion.bash
10 +> ipfs commands completion bash > ipfs-completion.bash
11 +> source ./ipfs-completion.bash
12 ```
22 -It will automatically be loaded the next time bash is loaded.
23 -To enable ipfs command completion globally on your system you may also
24 -copy the completion script to `/etc/bash_completion.d/`.
25 -
13
27 -Additional References
28 ----------------------
29 -* https://www.debian-administration.org/article/316/An_introduction_to_bash_completion_part_1
14 +To install the completions permanently, they can be moved to
15 +`/etc/bash_completion.d` or sourced from your `~/.bashrc` file.
misc/completion/ipfs-completion.bash deleted
-958
@@ -1,958 +0,0 @@
1 -_do_comp()
2 -{
3 - if [[ $(type compopt) == *"builtin" ]]; then
4 - compopt $@
5 - else
6 - complete $@
7 - fi
8 -}
9 -
10 -_ipfs_comp()
11 -{
12 - COMPREPLY=( $(compgen -W "$1" -- ${word}) )
13 - if [[ ${#COMPREPLY[@]} == 1 && ${COMPREPLY[0]} == "--"*"=" ]] ; then
14 - # If there's only one option, with =, then discard space
15 - _do_comp -o nospace
16 - fi
17 -}
18 -
19 -_ipfs_help_only()
20 -{
21 - _ipfs_comp "--help"
22 -}
23 -
24 -_ipfs_add()
25 -{
26 - if [[ "${prev}" == "--chunker" ]] ; then
27 - _ipfs_comp "placeholder1 placeholder2 placeholder3" # TODO: a) Give real options, b) Solve autocomplete bug for "="
28 - elif [ "${prev}" == "--pin" ] ; then
29 - _ipfs_comp "true false"
30 - elif [[ ${word} == -* ]] ; then
31 - _ipfs_comp "--recursive --dereference-args --stdin-name= --hidden --ignore= --ignore-rules-path= --quiet --quieter --silent --progress --trickle --only-hash --wrap-with-directory --chunker= --pin= --raw-leaves --nocopy --fscache --cid-version= --hash= --inline --inline-limit= --help "
32 - else
33 - _ipfs_filesystem_complete
34 - fi
35 -}
36 -
37 -_ipfs_bitswap()
38 -{
39 - ipfs_comp "ledger stat wantlist --help"
40 -}
41 -
42 -_ipfs_bitswap_ledger()
43 -{
44 - _ipfs_help_only
45 -}
46 -
47 -_ipfs_bitswap_stat()
48 -{
49 - _ipfs_help_only
50 -}
51 -
52 -_ipfs_bitswap_wantlist()
53 -{
54 - ipfs_comp "--peer= --help"
55 -}
56 -
57 -_ipfs_block()
58 -{
59 - _ipfs_comp "get put rm stat --help"
60 -}
61 -
62 -_ipfs_block_get()
63 -{
64 - _ipfs_hash_complete
65 -}
66 -
67 -_ipfs_block_put()
68 -{
69 - if [ "${prev}" == "--format" ] ; then
70 - _ipfs_comp "v0 placeholder2 placeholder3" # TODO: a) Give real options, b) Solve autocomplete bug for "="
71 - elif [[ ${word} == -* ]] ; then
72 - _ipfs_comp "--format= --help"
73 - else
74 - _ipfs_filesystem_complete
75 - fi
76 -}
77 -
78 -_ipfs_block_rm()
79 -{
80 - if [[ ${word} == -* ]] ; then
81 - _ipfs_comp "--force --quiet --help"
82 - else
83 - _ipfs_hash_complete
84 - fi
85 -}
86 -
87 -_ipfs_block_stat()
88 -{
89 - _ipfs_hash_complete
90 -}
91 -
92 -_ipfs_bootstrap()
93 -{
94 - _ipfs_comp "add list rm --help"
95 -}
96 -
97 -_ipfs_bootstrap_add()
98 -{
99 - _ipfs_comp "default --help"
100 -}
101 -
102 -_ipfs_bootstrap_list()
103 -{
104 - _ipfs_help_only
105 -}
106 -
107 -_ipfs_bootstrap_rm()
108 -{
109 - _ipfs_comp "all --help"
110 -}
111 -
112 -_ipfs_cat()
113 -{
114 - if [[ ${prev} == */* ]] ; then
115 - COMPREPLY=() # Only one argument allowed
116 - elif [[ ${word} == */* ]] ; then
117 - _ipfs_hash_complete
118 - else
119 - _ipfs_pinned_complete
120 - fi
121 -}
122 -
123 -_ipfs_commands()
124 -{
125 - _ipfs_comp "--flags --help"
126 -}
127 -
128 -_ipfs_config()
129 -{
130 - if [[ ${word} == -* ]] ; then
131 - _ipfs_comp "--bool --json"
132 - elif [[ ${prev} == *.* ]] ; then
133 - COMPREPLY=() # Only one subheader of the config can be shown or edited.
134 - else
135 - _ipfs_comp "show edit replace"
136 - fi
137 -}
138 -
139 -_ipfs_config_edit()
140 -{
141 - _ipfs_help_only
142 -}
143 -
144 -_ipfs_config_replace()
145 -{
146 - if [[ ${word} == -* ]] ; then
147 - _ipfs_comp "--help"
148 - else
149 - _ipfs_filesystem_complete
150 - fi
151 -}
152 -
153 -_ipfs_config_show()
154 -{
155 - _ipfs_help_only
156 -}
157 -
158 -_ipfs_daemon()
159 -{
160 - if [[ ${prev} == "--routing" ]] ; then
161 - _ipfs_comp "dht dhtclient none" # TODO: Solve autocomplete bug for "="
162 - elif [[ ${prev} == "--mount-ipfs" ]] || [[ ${prev} == "--mount-ipns" ]] || [[ ${prev} == "=" ]]; then
163 - _ipfs_filesystem_complete
164 - elif [[ ${word} == -* ]] ; then
165 - _ipfs_comp "--init --routing= --mount --writable --mount-ipfs= \
166 - --mount-ipns= --unrestricted-api --disable-transport-encryption \
167 - -- enable-gc --manage-fdlimit --offline --migrate --help"
168 - fi
169 -}
170 -
171 -_ipfs_dag()
172 -{
173 - _ipfs_comp "get put --help"
174 -}
175 -
176 -_ipfs_dag_get()
177 -{
178 - _ipfs_help_only
179 -}
180 -
181 -_ipfs_dag_put()
182 -{
183 - if [[ ${prev} == "--format" ]] ; then
184 - _ipfs_comp "cbor placeholder1" # TODO: a) Which format more than cbor is valid? b) Solve autocomplete bug for "="
185 - elif [[ ${prev} == "--input-enc" ]] ; then
186 - _ipfs_comp "json placeholder1" # TODO: a) Which format more than json is valid? b) Solve autocomplete bug for "="
187 - elif [[ ${word} == -* ]] ; then
188 - _ipfs_comp "--format= --input-enc= --help"
189 - else
190 - _ipfs_filesystem_complete
191 - fi
192 -}
193 -
194 -_ipfs_dht()
195 -{
196 - _ipfs_comp "findpeer findprovs get provide put query --help"
197 -}
198 -
199 -_ipfs_dht_findpeer()
200 -{
201 - _ipfs_comp "--verbose --help"
202 -}
203 -
204 -_ipfs_dht_findprovs()
205 -{
206 - _ipfs_comp "--verbose --help"
207 -}
208 -
209 -_ipfs_dht_get()
210 -{
211 - _ipfs_comp "--verbose --help"
212 -}
213 -
214 -_ipfs_dht_provide()
215 -{
216 - _ipfs_comp "--recursive --verbose --help"
217 -}
218 -
219 -_ipfs_dht_put()
220 -{
221 - _ipfs_comp "--verbose --help"
222 -}
223 -
224 -_ipfs_dht_query()
225 -{
226 - _ipfs_comp "--verbose --help"
227 -}
228 -
229 -_ipfs_diag()
230 -{
231 - _ipfs_comp "sys cmds net --help"
232 -}
233 -
234 -_ipfs_diag_cmds()
235 -{
236 - if [[ ${prev} == "clear" ]] ; then
237 - return 0
238 - elif [[ ${prev} =~ ^-?[0-9]+$ ]] ; then
239 - _ipfs_comp "ns us µs ms s m h" # TODO: Trigger without space, eg. "ipfs diag set-time 10ns" not "... set-time 10 ns"
240 - elif [[ ${prev} == "set-time" ]] ; then
241 - _ipfs_help_only
242 - elif [[ ${word} == -* ]] ; then
243 - _ipfs_comp "--verbose --help"
244 - else
245 - _ipfs_comp "clear set-time"
246 - fi
247 -}
248 -
249 -_ipfs_diag_sys()
250 -{
251 - _ipfs_help_only
252 -}
253 -
254 -_ipfs_diag_net()
255 -{
256 - if [[ ${prev} == "--vis" ]] ; then
257 - _ipfs_comp "d3 dot text" # TODO: Solve autocomplete bug for "="
258 - elif [[ ${word} == -* ]] ; then
259 - _ipfs_comp "--timeout= --vis= --help"
260 - fi
261 -}
262 -
263 -_ipfs_dns()
264 -{
265 - if [[ ${word} == -* ]] ; then
266 - _ipfs_comp "--recursive --help"
267 - fi
268 -}
269 -
270 -_ipfs_files()
271 -{
272 - _ipfs_comp "mv rm flush read write cp ls mkdir stat"
273 -}
274 -
275 -_ipfs_files_mv()
276 -{
277 - if [[ ${word} == -* ]] ; then
278 - _ipfs_comp "--recursive --flush"
279 - elif [[ ${word} == /* ]] ; then
280 - _ipfs_files_complete
281 - else
282 - COMPREPLY=( / )
283 - [[ $COMPREPLY = */ ]] && _do_comp -o nospace
284 - fi
285 -}
286 -
287 -_ipfs_files_rm()
288 -{
289 - if [[ ${word} == -* ]] ; then
290 - _ipfs_comp "--recursive --flush"
291 - elif [[ ${word} == /* ]] ; then
292 - _ipfs_files_complete
293 - else
294 - COMPREPLY=( / )
295 - [[ $COMPREPLY = */ ]] && _do_comp -o nospace
296 - fi
297 -}
298 -_ipfs_files_flush()
299 -{
300 - if [[ ${word} == /* ]] ; then
301 - _ipfs_files_complete
302 - else
303 - COMPREPLY=( / )
304 - [[ $COMPREPLY = */ ]] && _do_comp -o nospace
305 - fi
306 -}
307 -
308 -_ipfs_files_read()
309 -{
310 - if [[ ${prev} == "--count" ]] || [[ ${prev} == "--offset" ]] ; then
311 - COMPREPLY=() # Numbers, just keep it empty
312 - elif [[ ${word} == -* ]] ; then
313 - _ipfs_comp "--offset --count --help"
314 - elif [[ ${word} == /* ]] ; then
315 - _ipfs_files_complete
316 - else
317 - COMPREPLY=( / )
318 - [[ $COMPREPLY = */ ]] && _do_comp -o nospace
319 - fi
320 -}
321 -
322 -_ipfs_files_write()
323 -{
324 - if [[ ${prev} == "--count" ]] || [[ ${prev} == "--offset" ]] ; then # Dirty check
325 - COMPREPLY=() # Numbers, just keep it empty
326 - elif [[ ${word} == -* ]] ; then
327 - _ipfs_comp "--offset --count --create --truncate --help"
328 - elif [[ ${prev} == /* ]] ; then
329 - _ipfs_filesystem_complete
330 - elif [[ ${word} == /* ]] ; then
331 - _ipfs_files_complete
332 - else
333 - COMPREPLY=( / )
334 - [[ $COMPREPLY = */ ]] && _do_comp -o nospace
335 - fi
336 -}
337 -
338 -_ipfs_files_cp()
339 -{
340 - if [[ ${word} == /* ]] ; then
341 - _ipfs_files_complete
342 - else
343 - COMPREPLY=( / )
344 - [[ $COMPREPLY = */ ]] && _do_comp -o nospace
345 - fi
346 -}
347 -
348 -_ipfs_files_ls()
349 -{
350 - if [[ ${word} == -* ]] ; then
351 - _ipfs_comp "-l --help"
352 - elif [[ ${prev} == /* ]] ; then
353 - COMPREPLY=() # Path exist
354 - elif [[ ${word} == /* ]] ; then
355 - _ipfs_files_complete
356 - else
357 - COMPREPLY=( / )
358 - [[ $COMPREPLY = */ ]] && _do_comp -o nospace
359 - fi
360 -}
361 -
362 -_ipfs_files_mkdir()
363 -{
364 - if [[ ${word} == -* ]] ; then
365 - _ipfs_comp "--parents --help"
366 -
367 - elif [[ ${prev} == /* ]] ; then
368 - COMPREPLY=() # Path exist
369 - elif [[ ${word} == /* ]] ; then
370 - _ipfs_files_complete
371 - else
372 - COMPREPLY=( / )
373 - [[ $COMPREPLY = */ ]] && _do_comp -o nospace
374 - fi
375 -}
376 -
377 -_ipfs_files_stat()
378 -{
379 - if [[ ${prev} == /* ]] ; then
380 - COMPREPLY=() # Path exist
381 - elif [[ ${word} == /* ]] ; then
382 - _ipfs_files_complete
383 - else
384 - COMPREPLY=( / )
385 - [[ $COMPREPLY = */ ]] && _do_comp -o nospace
386 - fi
387 -}
388 -
389 -_ipfs_file()
390 -{
391 - if [[ ${prev} == "ls" ]] ; then
392 - _ipfs_hash_complete
393 - else
394 - _ipfs_comp "ls --help"
395 - fi
396 -}
397 -
398 -_ipfs_file_ls()
399 -{
400 - _ipfs_help_only
401 -}
402 -
403 -_ipfs_get()
404 -{
405 - if [ "${prev}" == "--output" ] ; then
406 - _do_comp -o default # Re-enable default file read
407 - COMPREPLY=()
408 - elif [ "${prev}" == "--compression-level" ] ; then
409 - _ipfs_comp "-1 1 2 3 4 5 6 7 8 9" # TODO: Solve autocomplete bug for "="
410 - elif [[ ${word} == -* ]] ; then
411 - _ipfs_comp "--output= --archive --compress --compression-level= --help"
412 - else
413 - _ipfs_hash_complete
414 - fi
415 -}
416 -
417 -_ipfs_id()
418 -{
419 - if [[ ${word} == -* ]] ; then
420 - _ipfs_comp "--format= --help"
421 - fi
422 -}
423 -
424 -_ipfs_init()
425 -{
426 - _ipfs_comp "--bits --force --empty-repo --help"
427 -}
428 -
429 -_ipfs_log()
430 -{
431 - _ipfs_comp "level ls tail --help"
432 -}
433 -
434 -_ipfs_log_level()
435 -{
436 - # TODO: auto-complete subsystem and level
437 - _ipfs_help_only
438 -}
439 -
440 -_ipfs_log_ls()
441 -{
442 - _ipfs_help_only
443 -}
444 -
445 -_ipfs_log_tail()
446 -{
447 - _ipfs_help_only
448 -}
449 -
450 -_ipfs_ls()
451 -{
452 - if [[ ${word} == -* ]] ; then
453 - _ipfs_comp "--headers --resolve-type=false --help"
454 - else
455 - _ipfs_hash_complete
456 - fi
457 -}
458 -
459 -_ipfs_mount()
460 -{
461 - if [[ ${prev} == "--ipfs-path" ]] || [[ ${prev} == "--ipns-path" ]] || [[ ${prev} == "=" ]] ; then
462 - _ipfs_filesystem_complete
463 - elif [[ ${word} == -* ]] ; then
464 - _ipfs_comp "--ipfs-path= --ipns-path= --help"
465 - fi
466 -}
467 -
468 -_ipfs_name()
469 -{
470 - _ipfs_comp "publish resolve --help"
471 -}
472 -
473 -_ipfs_name_publish()
474 -{
475 - if [[ ${prev} == "--lifetime" ]] || [[ ${prev} == "--ttl" ]] ; then
476 - COMPREPLY=() # Accept only numbers
477 - elif [[ ${prev} =~ ^-?[0-9]+$ ]] ; then
478 - _ipfs_comp "ns us µs ms s m h" # TODO: Trigger without space, eg. "ipfs diag set-time 10ns" not "... set-time 10 ns"
479 - elif [[ ${word} == -* ]] ; then
480 - _ipfs_comp "--resolve --lifetime --ttl --help"
481 - elif [[ ${word} == */ ]]; then
482 - _ipfs_hash_complete
483 - else
484 - _ipfs_pinned_complete
485 - fi
486 -}
487 -
488 -_ipfs_name_resolve()
489 -{
490 - if [[ ${word} == -* ]] ; then
491 - _ipfs_comp "--recursive --nocache --help"
492 - fi
493 -}
494 -
495 -_ipfs_object()
496 -{
497 - _ipfs_comp "data diff get links new patch put stat --help"
498 -}
499 -
500 -_ipfs_object_data()
501 -{
502 - _ipfs_hash_complete
503 -}
504 -
505 -_ipfs_object_diff()
506 -{
507 - if [[ ${word} == -* ]] ; then
508 - _ipfs_comp "--verbose --help"
509 - else
510 - _ipfs_hash_complete
511 - fi
512 -}
513 -
514 -
515 -_ipfs_object_get()
516 -{
517 - if [ "${prev}" == "--encoding" ] ; then
518 - _ipfs_comp "protobuf json xml"
519 - elif [[ ${word} == -* ]] ; then
520 - _ipfs_comp "--encoding --help"
521 - else
522 - _ipfs_hash_complete
523 - fi
524 -}
525 -
526 -_ipfs_object_links()
527 -{
528 - if [[ ${word} == -* ]] ; then
529 - _ipfs_comp "--headers --help"
530 - else
531 - _ipfs_hash_complete
532 - fi
533 -}
534 -
535 -_ipfs_object_new()
536 -{
537 - if [[ ${word} == -* ]] ; then
538 - _ipfs_comp "--help"
539 - else
540 - _ipfs_comp "unixfs-dir"
541 - fi
542 -}
543 -
544 -_ipfs_object_patch()
545 -{
546 - if [[ -n "${COMP_WORDS[3]}" ]] ; then # Root merkledag object exist
547 - case "${COMP_WORDS[4]}" in
548 - append-data)
549 - _ipfs_help_only
550 - ;;
551 - add-link)
552 - if [[ ${word} == -* ]] && [[ ${prev} == "add-link" ]] ; then # Dirty check
553 - _ipfs_comp "--create"
554 - #else
555 - # TODO: Hash path autocomplete. This is tricky, can be hash or a name.
556 - fi
557 - ;;
558 - rm-link)
559 - _ipfs_hash_complete
560 - ;;
561 - set-data)
562 - _ipfs_filesystem_complete
563 - ;;
564 - *)
565 - _ipfs_comp "append-data add-link rm-link set-data"
566 - ;;
567 - esac
568 - else
569 - _ipfs_hash_complete
570 - fi
571 -}
572 -
573 -_ipfs_object_put()
574 -{
575 - if [ "${prev}" == "--inputenc" ] ; then
576 - _ipfs_comp "protobuf json"
577 - elif [ "${prev}" == "--datafieldenc" ] ; then
578 - _ipfs_comp "text base64"
579 - elif [[ ${word} == -* ]] ; then
580 - _ipfs_comp "--inputenc --datafieldenc --help"
581 - else
582 - _ipfs_hash_complete
583 - fi
584 -}
585 -
586 -_ipfs_object_stat()
587 -{
588 - _ipfs_hash_complete
589 -}
590 -
591 -_ipfs_pin()
592 -{
593 - _ipfs_comp "rm ls add --help"
594 -}
595 -
596 -_ipfs_pin_add()
597 -{
598 - if [[ ${word} == -* ]] ; then
599 - _ipfs_comp "--recursive= --help"
600 - elif [[ ${word} == */ ]] && [[ ${word} != "/ipfs/" ]] ; then
601 - _ipfs_hash_complete
602 - fi
603 -}
604 -
605 -_ipfs_pin_ls()
606 -{
607 - if [[ ${prev} == "--type" ]] || [[ ${prev} == "-t" ]] ; then
608 - _ipfs_comp "direct indirect recursive all" # TODO: Solve autocomplete bug for
609 - elif [[ ${word} == -* ]] ; then
610 - _ipfs_comp "--count --quiet --type= --help"
611 - elif [[ ${word} == */ ]] && [[ ${word} != "/ipfs/" ]] ; then
612 - _ipfs_hash_complete
613 - fi
614 -}
615 -
616 -_ipfs_pin_rm()
617 -{
618 - if [[ ${word} == -* ]] ; then
619 - _ipfs_comp "--recursive --help"
620 - elif [[ ${word} == */ ]] && [[ ${word} != "/ipfs/" ]] ; then
621 - COMPREPLY=() # TODO: _ipfs_hash_complete() + List local pinned hashes as default?
622 - fi
623 -}
624 -
625 -_ipfs_ping()
626 -{
627 - _ipfs_comp "--count= --help"
628 -}
629 -
630 -_ipfs_pubsub()
631 -{
632 - _ipfs_comp "ls peers pub sub --help"
633 -}
634 -
635 -_ipfs_pubsub_ls()
636 -{
637 - _ipfs_help_only
638 -}
639 -
640 -_ipfs_pubsub_peers()
641 -{
642 - _ipfs_help_only
643 -}
644 -
645 -_ipfs_pubsub_pub()
646 -{
647 - _ipfs_help_only
648 -}
649 -
650 -_ipfs_pubsub_sub()
651 -{
652 - _ipfs_comp "--discover --help"
653 -}
654 -
655 -_ipfs_refs()
656 -{
657 - if [ "${prev}" == "--format" ] ; then
658 - _ipfs_comp "src dst linkname"
659 - elif [[ ${word} == -* ]] ; then
660 - _ipfs_comp "local --format= --edges --unique --recursive --help"
661 - #else
662 - # TODO: Use "ipfs ref" and combine it with autocomplete, see _ipfs_hash_complete
663 - fi
664 -}
665 -
666 -_ipfs_refs_local()
667 -{
668 - _ipfs_help_only
669 -}
670 -
671 -_ipfs_repo()
672 -{
673 - _ipfs_comp "fsck gc stat verify version --help"
674 -}
675 -
676 -_ipfs_repo_version()
677 -{
678 - _ipfs_comp "--quiet --help"
679 -}
680 -
681 -_ipfs_repo_verify()
682 -{
683 - _ipfs_help_only
684 -}
685 -
686 -_ipfs_repo_gc()
687 -{
688 - _ipfs_comp "--quiet --help"
689 -}
690 -
691 -_ipfs_repo_stat()
692 -{
693 - _ipfs_comp "--human --help"
694 -}
695 -
696 -_ipfs_repo_fsck()
697 -{
698 - _ipfs_help_only
699 -}
700 -
701 -_ipfs_resolve()
702 -{
703 - if [[ ${word} == /ipfs/* ]] ; then
704 - _ipfs_hash_complete
705 - elif [[ ${word} == /ipns/* ]] ; then
706 - COMPREPLY=() # Can't autocomplete ipns
707 - elif [[ ${word} == -* ]] ; then
708 - _ipfs_comp "--recursive --help"
709 - else
710 - opts="/ipns/ /ipfs/"
711 - COMPREPLY=( $(compgen -W "${opts}" -- ${word}) )
712 - [[ $COMPREPLY = */ ]] && _do_comp -o nospace
713 - fi
714 -}
715 -
716 -_ipfs_stats()
717 -{
718 - _ipfs_comp "bitswap bw repo --help"
719 -}
720 -
721 -_ipfs_stats_bitswap()
722 -{
723 - _ipfs_help_only
724 -}
725 -
726 -_ipfs_stats_bw()
727 -{
728 - # TODO: Which protocol is valid?
729 - _ipfs_comp "--peer= --proto= --poll --interval= --help"
730 -}
731 -
732 -_ipfs_stats_repo()
733 -{
734 - _ipfs_comp "--human= --help"
735 -}
736 -
737 -_ipfs_swarm()
738 -{
739 - _ipfs_comp "addrs connect disconnect filters peers --help"
740 -}
741 -
742 -_ipfs_swarm_addrs()
743 -{
744 - _ipfs_comp "local --help"
745 -}
746 -
747 -_ipfs_swarm_addrs_local()
748 -{
749 - _ipfs_comp "--id --help"
750 -}
751 -
752 -_ipfs_swarm_connect()
753 -{
754 - _ipfs_multiaddr_complete
755 -}
756 -
757 -_ipfs_swarm_disconnect()
758 -{
759 - local OLDIFS="$IFS" ; local IFS=$'\n' # Change divider for iterator one line below
760 - opts=$(for x in `ipfs swarm peers`; do echo ${x} ; done)
761 - IFS="$OLDIFS" # Reset divider to space, ' '
762 - COMPREPLY=( $(compgen -W "${opts}" -- ${word}) )
763 - [[ $COMPREPLY = */ ]] && _do_comp -o nospace -o filenames
764 -}
765 -
766 -_ipfs_swarm_filters()
767 -{
768 - if [[ ${prev} == "add" ]] || [[ ${prev} == "rm" ]]; then
769 - _ipfs_multiaddr_complete
770 - else
771 - _ipfs_comp "add rm --help"
772 - fi
773 -}
774 -
775 -_ipfs_swarm_filters_add()
776 -{
777 - _ipfs_help_only
778 -}
779 -
780 -_ipfs_swarm_filters_rm()
781 -{
782 - _ipfs_help_only
783 -}
784 -
785 -_ipfs_swarm_peers()
786 -{
787 - _ipfs_help_only
788 -}
789 -
790 -_ipfs_tar()
791 -{
792 - _ipfs_comp "add cat --help"
793 -}
794 -
795 -_ipfs_tar_add()
796 -{
797 - if [[ ${word} == -* ]] ; then
798 - _ipfs_comp "--help"
799 - else
800 - _ipfs_filesystem_complete
801 - fi
802 -}
803 -
804 -_ipfs_tar_cat()
805 -{
806 - if [[ ${word} == -* ]] ; then
807 - _ipfs_comp "--help"
808 - else
809 - _ipfs_filesystem_complete
810 - fi
811 -}
812 -
813 -_ipfs_update()
814 -{
815 - if [[ ${word} == -* ]] ; then
816 - _ipfs_comp "--version" # TODO: How does "--verbose" option work?
817 - else
818 - _ipfs_comp "versions version install stash revert fetch"
819 - fi
820 -}
821 -
822 -_ipfs_update_install()
823 -{
824 - if [[ ${prev} == v*.*.* ]] ; then
825 - COMPREPLY=()
826 - elif [[ ${word} == -* ]] ; then
827 - _ipfs_comp "--version"
828 - else
829 - local OLDIFS="$IFS" ; local IFS=$'\n' # Change divider for iterator one line below
830 - opts=$(for x in `ipfs update versions`; do echo ${x} ; done)
831 - IFS="$OLDIFS" # Reset divider to space, ' '
832 - COMPREPLY=( $(compgen -W "${opts}" -- ${word}) )
833 - fi
834 -}
835 -
836 -_ipfs_update_stash()
837 -{
838 - if [[ ${word} == -* ]] ; then
839 - _ipfs_comp "--tag --help"
840 - fi
841 -}
842 -_ipfs_update_fetch()
843 -{
844 - if [[ ${prev} == "--output" ]] ; then
845 - _ipfs_filesystem_complete
846 - elif [[ ${word} == -* ]] ; then
847 - _ipfs_comp "--output --help"
848 - fi
849 -}
850 -
851 -_ipfs_version()
852 -{
853 - _ipfs_comp "--number --commit --repo"
854 -}
855 -
856 -_ipfs_hash_complete()
857 -{
858 - local lastDir=${word%/*}/
859 - echo "LastDir: ${lastDir}" >> ~/Downloads/debug-ipfs.txt
860 - local OLDIFS="$IFS" ; local IFS=$'\n' # Change divider for iterator one line below
861 - opts=$(for x in `ipfs file ls ${lastDir}`; do echo ${lastDir}${x}/ ; done) # TODO: Implement "ipfs file ls -F" to get rid of frontslash after files. This take long time to run first time on a new shell.
862 - echo "Options: ${opts}" >> ~/Downloads/debug-ipfs.txt
863 - IFS="$OLDIFS" # Reset divider to space, ' '
864 - echo "Current: ${word}" >> ~/Downloads/debug-ipfs.txt
865 - COMPREPLY=( $(compgen -W "${opts}" -- ${word}) )
866 - echo "Suggestion: ${COMPREPLY}" >> ~/Downloads/debug-ipfs.txt
867 - [[ $COMPREPLY = */ ]] && _do_comp -o nospace -o filenames # Removing whitespace after output & handle output as filenames. (Only printing the latest folder of files.)
868 - return 0
869 -}
870 -
871 -_ipfs_files_complete()
872 -{
873 - local lastDir=${word%/*}/
874 - local OLDIFS="$IFS" ; local IFS=$'\n' # Change divider for iterator one line below
875 - opts=$(for x in `ipfs files ls ${lastDir}`; do echo ${lastDir}${x}/ ; done) # TODO: Implement "ipfs files ls -F" to get rid of frontslash after files. This does currently throw "Error: /cats/foo/ is not a directory"
876 - IFS="$OLDIFS" # Reset divider to space, ' '
877 - COMPREPLY=( $(compgen -W "${opts}" -- ${word}) )
878 - [[ $COMPREPLY = */ ]] && _do_comp -o nospace -o filenames
879 - return 0
880 -}
881 -
882 -_ipfs_multiaddr_complete()
883 -{
884 - local lastDir=${word%/*}/
885 - # Special case
886 - if [[ ${word} == */"ipcidr"* ]] ; then # TODO: Broken, fix it.
887 - opts="1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32" # TODO: IPv6?
888 - COMPREPLY=( $(compgen -W "${opts}" -- ${word}) )
889 - # "Loop"
890 - elif [[ ${word} == /*/ ]] || [[ ${word} == /*/* ]] ; then
891 - if [[ ${word} == /*/*/*/*/*/ ]] ; then
892 - COMPREPLY=()
893 - elif [[ ${word} == /*/*/*/*/ ]] ; then
894 - word=${word##*/}
895 - opts="ipfs/ "
896 - COMPREPLY=( $(compgen -W "${opts}" -- ${word}) )
897 - elif [[ ${word} == /*/*/*/ ]] ; then
898 - word=${word##*/}
899 - opts="4001/ "
900 - COMPREPLY=( $(compgen -W "${opts}" -- ${word}) )
901 - elif [[ ${word} == /*/*/ ]] ; then
902 - word=${word##*/}
903 - opts="udp/ tcp/ ipcidr/"
904 - COMPREPLY=( $(compgen -W "${opts}" -- ${word}) )
905 - elif [[ ${word} == /*/ ]] ; then
906 - COMPREPLY=() # TODO: This need to return something to NOT break the function. Maybe a "/" in the end as well due to -o filename option.
907 - fi
908 - COMPREPLY=${lastDir}${COMPREPLY}
909 - else # start case
910 - opts="/ip4/ /ip6/"
911 - COMPREPLY=( $(compgen -W "${opts}" -- ${word}) )
912 - fi
913 - [[ $COMPREPLY = */ ]] && _do_comp -o nospace -o filenames
914 - return 0
915 -}
916 -
917 -_ipfs_pinned_complete()
918 -{
919 - local OLDIFS="$IFS" ; local IFS=$'\n'
920 - local pinned=$(ipfs pin ls)
921 - COMPREPLY=( $(compgen -W "${pinned}" -- ${word}) )
922 - IFS="$OLDIFS"
923 - if [[ ${#COMPREPLY[*]} -eq 1 ]]; then # Only one completion, remove pretty output
924 - COMPREPLY=( ${COMPREPLY[0]/ *//} ) #Remove ' ' and everything after
925 - [[ $COMPREPLY = */ ]] && _do_comp -o nospace # Removing whitespace after output
926 - fi
927 -}
928 -_ipfs_filesystem_complete()
929 -{
930 - _do_comp -o default # Re-enable default file read
931 - COMPREPLY=()
932 -}
933 -
934 -_ipfs()
935 -{
936 - COMPREPLY=()
937 - _do_comp +o default # Disable default to not deny completion, see: http://stackoverflow.com/a/19062943/1216348
938 -
939 - local word="${COMP_WORDS[COMP_CWORD]}"
940 - local prev="${COMP_WORDS[COMP_CWORD-1]}"
941 -
942 - case "${COMP_CWORD}" in
943 - 1)
944 - local opts="add bitswap block bootstrap cat commands config daemon dag dht \
945 - diag dns file files get id init log ls mount name object pin ping pubsub \
946 - refs repo resolve stats swarm tar update version"
947 - COMPREPLY=( $(compgen -W "${opts}" -- ${word}) );;
948 - 2)
949 - local command="${COMP_WORDS[1]}"
950 - eval "_ipfs_$command" 2> /dev/null ;;
951 - *)
952 - local command="${COMP_WORDS[1]}"
953 - local subcommand="${COMP_WORDS[2]}"
954 - eval "_ipfs_${command}_${subcommand}" 2> /dev/null && return
955 - eval "_ipfs_$command" 2> /dev/null ;;
956 - esac
957 -}
958 -complete -F _ipfs ipfs
test/sharness/t0011-completion.sh new
+15
@@ -0,0 +1,15 @@
1 +#!/usr/bin/env bash
2 +
3 +test_description="Test generated bash completions"
4 +
5 +. lib/test-lib.sh
6 +
7 +test_expect_success "'ipfs commands completion bash' succeeds" '
8 + ipfs commands completion bash > completions.bash
9 +'
10 +
11 +test_expect_success "generated completions defines '_ipfs'" '
12 + bash -c "source completions.bash && type -t _ipfs"
13 +'
14 +
15 +test_done