-
-
Notifications
You must be signed in to change notification settings - Fork 3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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
- Loading branch information
Showing
6 changed files
with
203 additions
and
982 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,138 @@ | ||
package commands | ||
|
||
import ( | ||
"io" | ||
"sort" | ||
"text/template" | ||
|
||
cmds "github.com/ipfs/go-ipfs-cmds" | ||
) | ||
|
||
type completionCommand struct { | ||
Name string | ||
Subcommands []*completionCommand | ||
ShortFlags []string | ||
ShortOptions []string | ||
LongFlags []string | ||
LongOptions []string | ||
} | ||
|
||
func commandToCompletions(name string, cmd *cmds.Command) *completionCommand { | ||
parsed := &completionCommand{ | ||
Name: name, | ||
} | ||
for name, subCmd := range cmd.Subcommands { | ||
parsed.Subcommands = append(parsed.Subcommands, commandToCompletions(name, subCmd)) | ||
} | ||
sort.Slice(parsed.Subcommands, func(i, j int) bool { | ||
return parsed.Subcommands[i].Name < parsed.Subcommands[j].Name | ||
}) | ||
|
||
for _, opt := range cmd.Options { | ||
if opt.Type() == cmds.Bool { | ||
parsed.LongFlags = append(parsed.LongFlags, opt.Name()) | ||
for _, name := range opt.Names() { | ||
if len(name) == 1 { | ||
parsed.ShortFlags = append(parsed.ShortFlags, name) | ||
break | ||
} | ||
} | ||
} else { | ||
parsed.LongOptions = append(parsed.LongOptions, opt.Name()) | ||
for _, name := range opt.Names() { | ||
if len(name) == 1 { | ||
parsed.ShortOptions = append(parsed.ShortOptions, name) | ||
break | ||
} | ||
} | ||
} | ||
} | ||
sort.Slice(parsed.LongFlags, func(i, j int) bool { | ||
return parsed.LongFlags[i] < parsed.LongFlags[j] | ||
}) | ||
sort.Slice(parsed.ShortFlags, func(i, j int) bool { | ||
return parsed.ShortFlags[i] < parsed.ShortFlags[j] | ||
}) | ||
sort.Slice(parsed.LongOptions, func(i, j int) bool { | ||
return parsed.LongOptions[i] < parsed.LongOptions[j] | ||
}) | ||
sort.Slice(parsed.ShortOptions, func(i, j int) bool { | ||
return parsed.ShortOptions[i] < parsed.ShortOptions[j] | ||
}) | ||
return parsed | ||
} | ||
|
||
var bashCompletionTemplate = template.Must(template.New("root").Parse(`#!/bin/bash | ||
_ipfs_compgen() { | ||
local oldifs="$IFS" | ||
IFS=$'\n' | ||
while read -r line; do | ||
COMPREPLY+=("$line") | ||
done < <(compgen "$@") | ||
IFS="$oldifs" | ||
} | ||
_ipfs() { | ||
COMPREPLY=() | ||
local index=1 | ||
local argidx=0 | ||
local word="${COMP_WORDS[COMP_CWORD]}" | ||
{{ template "command" . }} | ||
} | ||
complete -o nosort -o nospace -o default -F _ipfs ipfs | ||
`)) | ||
|
||
var bashCompletionCommandTemplate = template.Must(bashCompletionTemplate.New("command").Parse(` | ||
while [[ ${index} -lt ${COMP_CWORD} ]]; do | ||
case "${COMP_WORDS[index]}" in | ||
-*) | ||
let index++ | ||
continue | ||
;; | ||
{{ range .Subcommands }} | ||
"{{ .Name }}") | ||
let index++ | ||
{{ template "command" . }} | ||
return 0 | ||
;; | ||
{{ end }} | ||
esac | ||
break | ||
done | ||
if [[ "${word}" == -* ]]; then | ||
{{ if .ShortFlags -}} | ||
_ipfs_compgen -W $'{{ range .ShortFlags }}-{{.}} \n{{end}}' -- "${word}" | ||
{{ end -}} | ||
{{- if .ShortOptions -}} | ||
_ipfs_compgen -S = -W $'{{ range .ShortOptions }}-{{.}}\n{{end}}' -- "${word}" | ||
{{ end -}} | ||
{{- if .LongFlags -}} | ||
_ipfs_compgen -W $'{{ range .LongFlags }}--{{.}} \n{{end}}' -- "${word}" | ||
{{ end -}} | ||
{{- if .LongOptions -}} | ||
_ipfs_compgen -S = -W $'{{ range .LongOptions }}--{{.}}\n{{end}}' -- "${word}" | ||
{{ end -}} | ||
return 0 | ||
fi | ||
while [[ ${index} -lt ${COMP_CWORD} ]]; do | ||
if [[ "${COMP_WORDS[index]}" != -* ]]; then | ||
let argidx++ | ||
fi | ||
let index++ | ||
done | ||
{{- if .Subcommands }} | ||
if [[ "${argidx}" -eq 0 ]]; then | ||
_ipfs_compgen -W $'{{ range .Subcommands }}{{.Name}} \n{{end}}' -- "${word}" | ||
fi | ||
{{ end -}} | ||
`)) | ||
|
||
// WritebashCompletions generates a bash completion script for the given command tree. | ||
func writeBashCompletions(cmd *cmds.Command, out io.Writer) error { | ||
cmds := commandToCompletions("ipfs", cmd) | ||
return bashCompletionTemplate.ExecuteTemplate(out, "root", cmds) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,29 +1,15 @@ | ||
Command Completion | ||
================== | ||
# Command Completion | ||
|
||
Shell command completion is provided by the script at | ||
[/misc/completion/ipfs-completion.bash](../misc/completion/ipfs-completion.bash). | ||
Shell command completions can be generated by running one of the `ipfs commands completions` | ||
sub-commands. | ||
|
||
The simplest way to see it working is write the completions | ||
to a file and then source it: | ||
|
||
Installation | ||
------------ | ||
The simplest way to see it working is to run | ||
`source misc/completion/ipfs-completion.bash` straight from your shell. This | ||
is only temporary and to fully enable it, you'll have to follow one of the steps | ||
below. | ||
|
||
### Bash on Linux | ||
For bash, completion can be enabled in a couple of ways. One is to copy the | ||
completion script to the directory `~/.ipfs/` and then in the file | ||
`~/.bash_completion` add | ||
```bash | ||
source ~/.ipfs/ipfs-completion.bash | ||
> ipfs commands completion bash > ipfs-completion.bash | ||
> source ./ipfs-completion.bash | ||
``` | ||
It will automatically be loaded the next time bash is loaded. | ||
To enable ipfs command completion globally on your system you may also | ||
copy the completion script to `/etc/bash_completion.d/`. | ||
|
||
|
||
Additional References | ||
--------------------- | ||
* https://www.debian-administration.org/article/316/An_introduction_to_bash_completion_part_1 | ||
To install the completions permanently, they can be moved to | ||
`/etc/bash_completion.d` or sourced from your `~/.bashrc` file. |
Oops, something went wrong.