Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(cmd): added command to generate basic cmd autocompletion scripts #1240

Merged
merged 6 commits into from
Apr 6, 2020
Merged
Show file tree
Hide file tree
Changes from 5 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
1 change: 1 addition & 0 deletions cmd/add.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ the name of the peer that originally added the dataset. You must have
}

cmd.Flags().StringVar(&o.LinkDir, "link", "", "path to directory to link dataset to")
cmd.MarkFlagFilename("link")
cmd.Flags().BoolVar(&o.LogsOnly, "logs-only", false, "only fetch logs, skipping HEAD data")

return cmd
Expand Down
280 changes: 280 additions & 0 deletions cmd/completion.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,280 @@
package cmd

import (
"bytes"
"fmt"
"io"

"github.com/qri-io/ioes"
"github.com/spf13/cobra"
)

// NewAutocompleteCommand creates a new `qri complete` cobra command that prints autocomplete scripts
func NewAutocompleteCommand(_ Factory, ioStreams ioes.IOStreams) *cobra.Command {
o := &AutocompleteOptions{IOStreams: ioStreams}
cmd := &cobra.Command{
Use: "completion [bash|zsh]",
Short: "Generates shell completion scripts",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

keeping with our CLI Help Style, should be generate shell auto-completion scripts

Long: `To load completion run

source <(qri completion [bash|zsh])
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add a $ for anything the user should type, and consider moving these into the Examples field.


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

# ~/.bashrc or ~/.zshrc
source <(qri completion [bash|zsh])

Alternatively you can pipe the output to a local script and
reference that as the source for faster loading
`,
RunE: func(cmd *cobra.Command, args []string) error {
return o.Run(cmd, args)
},
ValidArgs: []string{"bash", "zsh"},
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's add the Annotations field to make this show up in qri --help:

Annotations: map[string]string{
  "group": "other",
}

}

return cmd
}

// AutocompleteOptions encapsulates completion options
type AutocompleteOptions struct {
ioes.IOStreams
}

// Run executes the completion command
func (o *AutocompleteOptions) Run(cmd *cobra.Command, args []string) (err error) {
if len(args) == 0 {
return fmt.Errorf("shell not specified")
}
if len(args) > 1 {
return fmt.Errorf("too many arguments, expected only the shell type")
}
if args[0] == "bash" {
cmd.Parent().GenBashCompletion(o.Out)
}
if args[0] == "zsh" {
zshBody := bytes.Buffer{}
cmd.Parent().GenBashCompletion(&zshBody)
io.WriteString(o.Out, zshHead)
o.Out.Write(zshBody.Bytes())
io.WriteString(o.Out, zshTail)
}
return nil
}

const (
bashCompletionFunc = `
__qri_parse_list()
{
local qri_output out
if qri_output=$(qri list --format=simple --no-prompt --no-color 2>/dev/null); then
out=($(echo "${qri_output}"))
COMPREPLY=( $( compgen -W "${out[*]}" -- "$cur" ) )
fi
}

__qri_parse_search()
{
echo 'test'
local qri_output out
if qri_output=$(qri search $1 --format=simple --no-prompt --no-color 2>/dev/null); then
out=($(echo "${qri_output}"))
COMPREPLY=( $( compgen -W "${out[*]}" -- "$cur" ) )
fi
}

__qri_get_datasets()
{
__qri_parse_list
if [[ $? -eq 0 ]]; then
return 0
fi
}

__qri_get_search()
{
__qri_parse_search
if [[ $? -eq 0 ]]; then
return 0
fi
}

__qri_custom_func() {
case ${last_command} in
qri_checkout | qri_export | qri_get | qri_log | qri_logbook | qri_publish | qri_remove | qri_rename | qri_render | qri_save | qri_stats | qri_use | qri_validate | qri_whatchanged)
__qri_get_datasets
return
;;
qri_add | qri_fetch | qri_search)
__qri_get_search
return
;;
*)
;;
esac
}
`

zshHead = `# reference kubectl completion zsh

__qri_bash_source() {
alias shopt=':'
alias _expand=_bash_expand
alias _complete=_bash_comp
emulate -L sh
setopt kshglob noshglob braceexpand

source "$@"
}

__qri_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" = "__qri_compopt" ]; then
echo builtin
return 0
fi
fi
type "$@"
}

__qri_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
}

__qri_compopt() {
true # don't do anything. Not supported by bashcompinit in zsh
}

__qri_declare() {
if [ "$1" == "-F" ]; then
whence -w "$@"
else
builtin declare "$@"
fi
}

__qri_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
}

__qri_get_comp_words_by_ref() {
cur="${COMP_WORDS[COMP_CWORD]}"
prev="${COMP_WORDS[${COMP_CWORD}-1]}"
words=("${COMP_WORDS[@]}")
cword=("${COMP_CWORD[@]}")
}

__qri_filedir() {
# todo(arqu): This is a horrible hack to just return to normal
# path completion, ZSH has issues with correctly handling _filedir in this case.
# Extension filtering is not supported right now
return 1
local RET OLD_IFS w qw

__qri_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="," __qri_debug "RET=${RET[@]} len=${#RET[@]}"

for w in ${RET[@]}; do
if [[ ! "${w}" = "${cur}"* ]]; then
continue
fi
if eval "[[ \"\${w}\" = *.$1 || -d \"\${w}\" ]]"; then
qw="$(__qri_quote "${w}")"
if [ -d "${w}" ]; then
COMPREPLY+=("${qw}/")
else
COMPREPLY+=("${qw}")
fi
fi
done
}

__qri_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

__qri_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}/__qri_filedir/g" \
-e "s/${LWORD}_get_comp_words_by_ref${RWORD}/__qri_get_comp_words_by_ref/g" \
-e "s/${LWORD}__ltrim_colon_completions${RWORD}/__qri_ltrim_colon_completions/g" \
-e "s/${LWORD}compgen${RWORD}/__qri_compgen/g" \
-e "s/${LWORD}compopt${RWORD}/__qri_compopt/g" \
-e "s/${LWORD}declare${RWORD}/builtin declare/g" \
-e "s/\\\$(type${RWORD}/\$(__qri_type/g" \
<<'BASH_COMPLETION_EOF'
`

zshTail = `
BASH_COMPLETION_EOF
}

__qri_bash_source <(__qri_convert_bash_to_zsh)
`
)
1 change: 1 addition & 0 deletions cmd/export.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ To export to a specific directory, use the --output flag.`,
}

cmd.Flags().StringVarP(&o.Output, "output", "o", "", "path to write to, default is current directory")
cmd.MarkFlagFilename("output")
cmd.Flags().StringVarP(&o.Format, "format", "f", "", "format for the exported dataset, such as native, json, xlsx. default: json")
cmd.Flags().BoolVarP(&o.Zipped, "zip", "z", false, "export as a zip file")

Expand Down
2 changes: 2 additions & 0 deletions cmd/qri.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ https://github.com/qri-io/qri/issues`,
setNoPrompt(noPrompt)
}
},
BashCompletionFunction: bashCompletionFunc,
}

qriPath, ipfsPath := pf()
Expand All @@ -51,6 +52,7 @@ https://github.com/qri-io/qri/issues`,

cmd.AddCommand(
NewAddCommand(opt, ioStreams),
NewAutocompleteCommand(opt, ioStreams),
NewCheckoutCommand(opt, ioStreams),
NewConfigCommand(opt, ioStreams),
NewConnectCommand(opt, ioStreams),
Expand Down
2 changes: 2 additions & 0 deletions cmd/render.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,10 @@ provided, Qri will render the dataset with a default template.`,
}

cmd.Flags().StringVarP(&o.Template, "template", "t", "", "path to template file")
cmd.MarkFlagFilename("template")
cmd.Flags().BoolVarP(&o.UseViz, "viz", "v", false, "whether to use the viz component")
cmd.Flags().StringVarP(&o.Output, "output", "o", "", "path to write output file")
cmd.MarkFlagFilename("output")

return cmd
}
Expand Down
2 changes: 2 additions & 0 deletions cmd/save.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,11 @@ commit message and title to the save.`,
}

cmd.Flags().StringSliceVarP(&o.FilePaths, "file", "f", nil, "dataset or component file (yaml or json)")
cmd.MarkFlagFilename("file", "yaml", "yml", "json")
cmd.Flags().StringVarP(&o.Title, "title", "t", "", "title of commit message for save")
cmd.Flags().StringVarP(&o.Message, "message", "m", "", "commit message for save")
cmd.Flags().StringVarP(&o.BodyPath, "body", "", "", "path to file or url of data to add as dataset contents")
cmd.MarkFlagFilename("body")
cmd.Flags().StringVarP(&o.Recall, "recall", "", "", "restore revisions from dataset history")
// cmd.Flags().BoolVarP(&o.ShowValidation, "show-validation", "s", false, "display a list of validation errors upon adding")
cmd.Flags().StringSliceVar(&o.Secrets, "secrets", nil, "transform secrets as comma separated key,value,key,value,... sequence")
Expand Down
3 changes: 3 additions & 0 deletions cmd/validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,11 @@ if these flags are provided.`,
// TODO: restore
// cmd.Flags().StringVarP(&o.URL, "url", "u", "", "url to file to initialize from")
cmd.Flags().StringVarP(&o.BodyFilepath, "body", "b", "", "body file to validate")
cmd.MarkFlagFilename("body")
cmd.Flags().StringVarP(&o.SchemaFilepath, "schema", "", "", "json schema file to use for validation")
cmd.MarkFlagFilename("schema", "json")
cmd.Flags().StringVarP(&o.StructureFilepath, "structure", "", "", "json structure file to use for validation")
cmd.MarkFlagFilename("structure", "json")

return cmd
}
Expand Down