Skip to content
This repository has been archived by the owner on Jun 13, 2021. It is now read-only.

Adding zsh completion #376

Merged
merged 1 commit into from
Sep 14, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 26 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,7 @@ Options:
-v, --version Print version information

Commands:
completion Generates bash completion scripts
completion Generates completion scripts for the specified shell (bash or zsh)
deploy Deploy or update an application
fork Create a fork of an existing application to be modified
helm Generate a Helm chart
Expand All @@ -265,3 +265,28 @@ Commands:

Run 'docker-app COMMAND --help' for more information on a command.
```

## Shell completion

### Bash

Load the docker-app completion code for bash into the current shell:
```sh
$ source <(docker-app completion bash)
```
Set the docker-app completion code for bash to autoload on startup in your ~/.bashrc, ~/.profile or ~/.bash_profile:
```sh
source <(docker-app completion bash)
```
**Note**: `bash-completion` is needed.

### Zsh

Load the docker-app completion code for zsh into the current shell
```sh
$ source <(docker-app completion zsh)
```
Set the docker-app completion code for zsh to autoload on startup in your ~/.zshrc
```sh
source <(docker-app completion zsh)
```
182 changes: 171 additions & 11 deletions cmd/docker-app/completion.go
Original file line number Diff line number Diff line change
@@ -1,27 +1,187 @@
package main

import (
"bytes"
"fmt"
"io"

"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/spf13/cobra"
)

func completionCmd(dockerCli command.Cli, rootCmd *cobra.Command) *cobra.Command {
return &cobra.Command{
Use: "completion",
Short: "Generates bash completion scripts",
Long: `To load completion run

. <(docker-app completion)
Use: "completion SHELL",
Short: "Generates completion scripts for the specified shell (bash or zsh)",
Long: `# Load the docker-app completion code for bash into the current shell
. <(docker-app completion bash)
# Set the docker-app completion code for bash to autoload on startup in your ~/.bashrc,
# ~/.profile or ~/.bash_profile
. <(docker-app completion bash)
# Note: bash-completion is needed.

To configure your bash shell to load completions for each session add to your bashrc

# ~/.bashrc or ~/.profile
. <(docker-app completion)
# Load the docker-app completion code for zsh into the current shell
source <(docker-app completion zsh)
# Set the docker-app completion code for zsh to autoload on startup in your ~/.zshrc
source <(docker-app completion zsh)
`,
Args: cli.NoArgs,
Args: cli.RequiresMaxArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
return rootCmd.GenBashCompletion(dockerCli.Out())
switch {
case len(args) == 0:
return rootCmd.GenBashCompletion(dockerCli.Out())
case args[0] == "bash":
return rootCmd.GenBashCompletion(dockerCli.Out())
case args[0] == "zsh":
return runCompletionZsh(dockerCli.Out(), rootCmd)
default:
return fmt.Errorf("%q is not a supported shell", args[0])
}
},
}
}

const (
// Largely inspired by https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/completion.go
zshHead = `#compdef dockerapp
__dockerapp_bash_source() {
alias shopt=':'
alias _expand=_bash_expand
alias _complete=_bash_comp
emulate -L sh
setopt kshglob noshglob braceexpand
source "$@"
}
__dockerapp_type() {
# -t is not supported by zsh
if [ "$1" == "-t" ]; then
shift
# fake Bash 4 to disable "complete -o nospace". Instead
# "compopt +-o nospace" is used in the code to toggle trailing
# spaces. We don't support that, but leave trailing spaces on
# all the time
if [ "$1" = "__dockerapp_compopt" ]; then
echo builtin
return 0
fi
fi
type "$@"
}
__dockerapp_compgen() {
local completions w
completions=( $(compgen "$@") ) || return $?
# filter by given word as prefix
while [[ "$1" = -* && "$1" != -- ]]; do
shift
shift
done
if [[ "$1" == -- ]]; then
shift
fi
for w in "${completions[@]}"; do
if [[ "${w}" = "$1"* ]]; then
echo "${w}"
fi
done
}
__dockerapp_compopt() {
true # don't do anything. Not supported by bashcompinit in zsh
}
__dockerapp_ltrim_colon_completions()
{
if [[ "$1" == *:* && "$COMP_WORDBREAKS" == *:* ]]; then
# Remove colon-word prefix from COMPREPLY items
local colon_word=${1%${1##*:}}
local i=${#COMPREPLY[*]}
while [[ $((--i)) -ge 0 ]]; do
COMPREPLY[$i]=${COMPREPLY[$i]#"$colon_word"}
done
fi
}
__dockerapp_get_comp_words_by_ref() {
cur="${COMP_WORDS[COMP_CWORD]}"
prev="${COMP_WORDS[${COMP_CWORD}-1]}"
words=("${COMP_WORDS[@]}")
cword=("${COMP_CWORD[@]}")
}
__dockerapp_filedir() {
local RET OLD_IFS w qw
__debug "_filedir $@ cur=$cur"
if [[ "$1" = \~* ]]; then
# somehow does not work. Maybe, zsh does not call this at all
eval echo "$1"
return 0
fi
OLD_IFS="$IFS"
IFS=$'\n'
if [ "$1" = "-d" ]; then
shift
RET=( $(compgen -d) )
else
RET=( $(compgen -f) )
fi
IFS="$OLD_IFS"
IFS="," __debug "RET=${RET[@]} len=${#RET[@]}"
for w in ${RET[@]}; do
if [[ ! "${w}" = "${cur}"* ]]; then
continue
fi
if eval "[[ \"\${w}\" = *.$1 || -d \"\${w}\" ]]"; then
qw="$(__dockerapp_quote "${w}")"
if [ -d "${w}" ]; then
COMPREPLY+=("${qw}/")
else
COMPREPLY+=("${qw}")
fi
fi
done
}
__dockerapp_quote() {
if [[ $1 == \'* || $1 == \"* ]]; then
# Leave out first character
printf %q "${1:1}"
else
printf %q "$1"
fi
}
autoload -U +X bashcompinit && bashcompinit
# use word boundary patterns for BSD or GNU sed
LWORD='[[:<:]]'
RWORD='[[:>:]]'
if sed --help 2>&1 | grep -q GNU; then
LWORD='\<'
RWORD='\>'
fi
__dockerapp_convert_bash_to_zsh() {
sed \
-e 's/declare -F/whence -w/' \
-e 's/_get_comp_words_by_ref "\$@"/_get_comp_words_by_ref "\$*"/' \
-e 's/local \([a-zA-Z0-9_]*\)=/local \1; \1=/' \
-e 's/flags+=("\(--.*\)=")/flags+=("\1"); two_word_flags+=("\1")/' \
-e 's/must_have_one_flag+=("\(--.*\)=")/must_have_one_flag+=("\1")/' \
-e "s/${LWORD}_filedir${RWORD}/__dockerapp_filedir/g" \
-e "s/${LWORD}_get_comp_words_by_ref${RWORD}/__dockerapp_get_comp_words_by_ref/g" \
-e "s/${LWORD}__ltrim_colon_completions${RWORD}/__dockerapp_ltrim_colon_completions/g" \
-e "s/${LWORD}compgen${RWORD}/__dockerapp_compgen/g" \
-e "s/${LWORD}compopt${RWORD}/__dockerapp_compopt/g" \
-e "s/${LWORD}declare${RWORD}/builtin declare/g" \
-e "s/\\\$(type${RWORD}/\$(__dockerapp_type/g" \
<<'BASH_COMPLETION_EOF'
`
zshTail = `
BASH_COMPLETION_EOF
}
__dockerapp_bash_source <(__dockerapp_convert_bash_to_zsh)
_complete dockerapp 2>/dev/null
`
)

func runCompletionZsh(out io.Writer, rootCmd *cobra.Command) error {
fmt.Fprint(out, zshHead)
buf := new(bytes.Buffer)
rootCmd.GenBashCompletion(buf)
fmt.Fprint(out, buf.String())
fmt.Fprint(out, zshTail)
return nil
}