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