diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 415468d..ae2df11 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -46,6 +46,21 @@ jobs: sudo apt-get install shellcheck ./run-tests.sh --check-shellcheck + check-shell-completions: + runs-on: ubuntu-24.04 + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Go + uses: actions/setup-go@v5 + with: + go-version: '>=1.18.X' + + - name: Check shell completions are up-to-date + run: | + ./run-tests.sh --check-shell-completions + go-tests: runs-on: ubuntu-24.04 steps: diff --git a/.gitignore b/.gitignore index 76d6f11..bb47fa9 100644 --- a/.gitignore +++ b/.gitignore @@ -4,7 +4,7 @@ *.dll *.so *.dylib -reana-client-go +/reana-client-go # Test binary, built with `go test -c` *.test diff --git a/README.md b/README.md index 4d092b4..29a8d2d 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,25 @@ researchers to submit, run, and manage their computational workflows. The detailed information on how to install and use REANA can be found in [docs.reana.io](https://docs.reana.io). +## Shell completion + +The `reana-client-go` supports shell completion for Bash and Zsh. To enable the +auto-completion of commands and options, add the following to your shell +configuration file: + +**Bash** (add to `~/.bashrc`): + +```bash +source <(reana-client-go completion bash) +``` + +**Zsh** (add to `~/.zshrc`): + +```bash +source <(reana-client-go completion zsh) +compdef _reana-client-go reana-client-go +``` + # Useful links - [REANA project home page](http://www.reana.io/) diff --git a/cmd/completion.go b/cmd/completion.go new file mode 100644 index 0000000..fa26fc5 --- /dev/null +++ b/cmd/completion.go @@ -0,0 +1,85 @@ +/* +This file is part of REANA. +Copyright (C) 2025 CERN. + +REANA is free software; you can redistribute it and/or modify it +under the terms of the MIT License; see LICENSE file for more details. +*/ + +package cmd + +import ( + "os" + + "github.com/spf13/cobra" +) + +const completionLongDesc = `Generate shell completion scripts for reana-client-go. + +To load completions: + +Bash: + + $ source <(reana-client-go completion bash) + + # To load completions for each session, execute once: + # Linux: + $ reana-client-go completion bash > /etc/bash_completion.d/reana-client-go + # macOS: + $ reana-client-go completion bash > $(brew --prefix)/etc/bash_completion.d/reana-client-go + +Zsh: + + # If shell completion is not already enabled in your environment, + # you will need to enable it. You can execute the following once: + + $ echo "autoload -U compinit; compinit" >> ~/.zshrc + + # To load completions for each session, add to your .zshrc: + $ source <(reana-client-go completion zsh) + $ compdef _reana-client-go reana-client-go + + # Or install to fpath (requires new shell): + $ reana-client-go completion zsh > "${fpath[1]}/_reana-client-go" + +Fish: + + $ reana-client-go completion fish | source + + # To load completions for each session, execute once: + $ reana-client-go completion fish > ~/.config/fish/completions/reana-client-go.fish + +PowerShell: + + PS> reana-client-go completion powershell | Out-String | Invoke-Expression + + # To load completions for every new session, run: + PS> reana-client-go completion powershell > reana-client-go.ps1 + # and source this file from your PowerShell profile. +` + +func newCompletionCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "completion [bash|zsh|fish|powershell]", + Short: "Generate shell completion scripts.", + Long: completionLongDesc, + DisableFlagsInUseLine: true, + ValidArgs: []string{"bash", "zsh", "fish", "powershell"}, + Args: cobra.MatchAll(cobra.ExactArgs(1), cobra.OnlyValidArgs), + RunE: func(cmd *cobra.Command, args []string) error { + switch args[0] { + case "bash": + return cmd.Root().GenBashCompletion(os.Stdout) + case "zsh": + return cmd.Root().GenZshCompletion(os.Stdout) + case "fish": + return cmd.Root().GenFishCompletion(os.Stdout, true) + case "powershell": + return cmd.Root().GenPowerShellCompletionWithDesc(os.Stdout) + } + return nil + }, + } + + return cmd +} diff --git a/cmd/root.go b/cmd/root.go index 17baa42..cc4fddc 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -31,7 +31,7 @@ type rootOptions struct { func NewRootCmd() *cobra.Command { o := &rootOptions{} cmd := &cobra.Command{ - Use: "reana-client", + Use: "reana-client-go", Short: "REANA client for interacting with REANA server.", Long: "REANA client for interacting with REANA server.", SilenceUsage: true, @@ -64,6 +64,7 @@ func NewRootCmd() *cobra.Command { { Message: "Configuration commands:", Commands: []*cobra.Command{ + newCompletionCmd(), newInfoCmd(), newPingCmd(), newVersionCmd(), diff --git a/cmd/secrets_list_test.go b/cmd/secrets_list_test.go index 48ec6a8..539d588 100644 --- a/cmd/secrets_list_test.go +++ b/cmd/secrets_list_test.go @@ -32,7 +32,7 @@ func TestSecretsList(t *testing.T) { }, "unexpected args": { args: []string{"arg"}, - expected: []string{"unknown command \"arg\" for \"reana-client secrets-list\""}, + expected: []string{"unknown command \"arg\" for \"reana-client-go secrets-list\""}, wantError: true, }, "server error": { diff --git a/etc/bash_completion.d/reana-client-go b/etc/bash_completion.d/reana-client-go new file mode 100644 index 0000000..d9e9443 --- /dev/null +++ b/etc/bash_completion.d/reana-client-go @@ -0,0 +1,1777 @@ +# This file is part of REANA. +# Copyright (C) 2025 CERN. +# +# REANA is free software; you can redistribute it and/or modify it +# under the terms of the MIT License; see LICENSE file for more details. + +# Bash completion for reana-client-go (Cobra CLI) +# +# Installation: +# Option 1: Source this file in your .bashrc: +# . /path/to/reana-client-go/etc/bash_completion.d/reana-client-go +# +# Option 2: Copy to system completion directory: +# cp /path/to/reana-client-go/etc/bash_completion.d/reana-client-go \ +# /etc/bash_completion.d/ +# +# Option 3: Generate dynamically (add to .bashrc): +# source <(reana-client-go completion bash) + +# bash completion for reana-client-go -*- shell-script -*- + +__reana-client-go_debug() +{ + if [[ -n ${BASH_COMP_DEBUG_FILE:-} ]]; then + echo "$*" >> "${BASH_COMP_DEBUG_FILE}" + fi +} + +# Homebrew on Macs have version 1.3 of bash-completion which doesn't include +# _init_completion. This is a very minimal version of that function. +__reana-client-go_init_completion() +{ + COMPREPLY=() + _get_comp_words_by_ref "$@" cur prev words cword +} + +__reana-client-go_index_of_word() +{ + local w word=$1 + shift + index=0 + for w in "$@"; do + [[ $w = "$word" ]] && return + index=$((index+1)) + done + index=-1 +} + +__reana-client-go_contains_word() +{ + local w word=$1; shift + for w in "$@"; do + [[ $w = "$word" ]] && return + done + return 1 +} + +__reana-client-go_handle_go_custom_completion() +{ + __reana-client-go_debug "${FUNCNAME[0]}: cur is ${cur}, words[*] is ${words[*]}, #words[@] is ${#words[@]}" + + local shellCompDirectiveError=1 + local shellCompDirectiveNoSpace=2 + local shellCompDirectiveNoFileComp=4 + local shellCompDirectiveFilterFileExt=8 + local shellCompDirectiveFilterDirs=16 + + local out requestComp lastParam lastChar comp directive args + + # Prepare the command to request completions for the program. + # Calling ${words[0]} instead of directly reana-client-go allows to handle aliases + args=("${words[@]:1}") + # Disable ActiveHelp which is not supported for bash completion v1 + requestComp="REANA_CLIENT_GO_ACTIVE_HELP=0 ${words[0]} __completeNoDesc ${args[*]}" + + lastParam=${words[$((${#words[@]}-1))]} + lastChar=${lastParam:$((${#lastParam}-1)):1} + __reana-client-go_debug "${FUNCNAME[0]}: lastParam ${lastParam}, lastChar ${lastChar}" + + if [ -z "${cur}" ] && [ "${lastChar}" != "=" ]; then + # If the last parameter is complete (there is a space following it) + # We add an extra empty parameter so we can indicate this to the go method. + __reana-client-go_debug "${FUNCNAME[0]}: Adding extra empty parameter" + requestComp="${requestComp} \"\"" + fi + + __reana-client-go_debug "${FUNCNAME[0]}: calling ${requestComp}" + # Use eval to handle any environment variables and such + out=$(eval "${requestComp}" 2>/dev/null) + + # Extract the directive integer at the very end of the output following a colon (:) + directive=${out##*:} + # Remove the directive + out=${out%:*} + if [ "${directive}" = "${out}" ]; then + # There is not directive specified + directive=0 + fi + __reana-client-go_debug "${FUNCNAME[0]}: the completion directive is: ${directive}" + __reana-client-go_debug "${FUNCNAME[0]}: the completions are: ${out}" + + if [ $((directive & shellCompDirectiveError)) -ne 0 ]; then + # Error code. No completion. + __reana-client-go_debug "${FUNCNAME[0]}: received error from custom completion go code" + return + else + if [ $((directive & shellCompDirectiveNoSpace)) -ne 0 ]; then + if [[ $(type -t compopt) = "builtin" ]]; then + __reana-client-go_debug "${FUNCNAME[0]}: activating no space" + compopt -o nospace + fi + fi + if [ $((directive & shellCompDirectiveNoFileComp)) -ne 0 ]; then + if [[ $(type -t compopt) = "builtin" ]]; then + __reana-client-go_debug "${FUNCNAME[0]}: activating no file completion" + compopt +o default + fi + fi + fi + + if [ $((directive & shellCompDirectiveFilterFileExt)) -ne 0 ]; then + # File extension filtering + local fullFilter filter filteringCmd + # Do not use quotes around the $out variable or else newline + # characters will be kept. + for filter in ${out}; do + fullFilter+="$filter|" + done + + filteringCmd="_filedir $fullFilter" + __reana-client-go_debug "File filtering command: $filteringCmd" + $filteringCmd + elif [ $((directive & shellCompDirectiveFilterDirs)) -ne 0 ]; then + # File completion for directories only + local subdir + # Use printf to strip any trailing newline + subdir=$(printf "%s" "${out}") + if [ -n "$subdir" ]; then + __reana-client-go_debug "Listing directories in $subdir" + __reana-client-go_handle_subdirs_in_dir_flag "$subdir" + else + __reana-client-go_debug "Listing directories in ." + _filedir -d + fi + else + while IFS='' read -r comp; do + COMPREPLY+=("$comp") + done < <(compgen -W "${out}" -- "$cur") + fi +} + +__reana-client-go_handle_reply() +{ + __reana-client-go_debug "${FUNCNAME[0]}" + local comp + case $cur in + -*) + if [[ $(type -t compopt) = "builtin" ]]; then + compopt -o nospace + fi + local allflags + if [ ${#must_have_one_flag[@]} -ne 0 ]; then + allflags=("${must_have_one_flag[@]}") + else + allflags=("${flags[*]} ${two_word_flags[*]}") + fi + while IFS='' read -r comp; do + COMPREPLY+=("$comp") + done < <(compgen -W "${allflags[*]}" -- "$cur") + if [[ $(type -t compopt) = "builtin" ]]; then + [[ "${COMPREPLY[0]}" == *= ]] || compopt +o nospace + fi + + # complete after --flag=abc + if [[ $cur == *=* ]]; then + if [[ $(type -t compopt) = "builtin" ]]; then + compopt +o nospace + fi + + local index flag + flag="${cur%=*}" + __reana-client-go_index_of_word "${flag}" "${flags_with_completion[@]}" + COMPREPLY=() + if [[ ${index} -ge 0 ]]; then + PREFIX="" + cur="${cur#*=}" + ${flags_completion[${index}]} + if [ -n "${ZSH_VERSION:-}" ]; then + # zsh completion needs --flag= prefix + eval "COMPREPLY=( \"\${COMPREPLY[@]/#/${flag}=}\" )" + fi + fi + fi + + if [[ -z "${flag_parsing_disabled}" ]]; then + # If flag parsing is enabled, we have completed the flags and can return. + # If flag parsing is disabled, we may not know all (or any) of the flags, so we fallthrough + # to possibly call handle_go_custom_completion. + return 0; + fi + ;; + esac + + # check if we are handling a flag with special work handling + local index + __reana-client-go_index_of_word "${prev}" "${flags_with_completion[@]}" + if [[ ${index} -ge 0 ]]; then + ${flags_completion[${index}]} + return + fi + + # we are parsing a flag and don't have a special handler, no completion + if [[ ${cur} != "${words[cword]}" ]]; then + return + fi + + local completions + completions=("${commands[@]}") + if [[ ${#must_have_one_noun[@]} -ne 0 ]]; then + completions+=("${must_have_one_noun[@]}") + elif [[ -n "${has_completion_function}" ]]; then + # if a go completion function is provided, defer to that function + __reana-client-go_handle_go_custom_completion + fi + if [[ ${#must_have_one_flag[@]} -ne 0 ]]; then + completions+=("${must_have_one_flag[@]}") + fi + while IFS='' read -r comp; do + COMPREPLY+=("$comp") + done < <(compgen -W "${completions[*]}" -- "$cur") + + if [[ ${#COMPREPLY[@]} -eq 0 && ${#noun_aliases[@]} -gt 0 && ${#must_have_one_noun[@]} -ne 0 ]]; then + while IFS='' read -r comp; do + COMPREPLY+=("$comp") + done < <(compgen -W "${noun_aliases[*]}" -- "$cur") + fi + + if [[ ${#COMPREPLY[@]} -eq 0 ]]; then + if declare -F __reana-client-go_custom_func >/dev/null; then + # try command name qualified custom func + __reana-client-go_custom_func + else + # otherwise fall back to unqualified for compatibility + declare -F __custom_func >/dev/null && __custom_func + fi + fi + + # available in bash-completion >= 2, not always present on macOS + if declare -F __ltrim_colon_completions >/dev/null; then + __ltrim_colon_completions "$cur" + fi + + # If there is only 1 completion and it is a flag with an = it will be completed + # but we don't want a space after the = + if [[ "${#COMPREPLY[@]}" -eq "1" ]] && [[ $(type -t compopt) = "builtin" ]] && [[ "${COMPREPLY[0]}" == --*= ]]; then + compopt -o nospace + fi +} + +# The arguments should be in the form "ext1|ext2|extn" +__reana-client-go_handle_filename_extension_flag() +{ + local ext="$1" + _filedir "@(${ext})" +} + +__reana-client-go_handle_subdirs_in_dir_flag() +{ + local dir="$1" + pushd "${dir}" >/dev/null 2>&1 && _filedir -d && popd >/dev/null 2>&1 || return +} + +__reana-client-go_handle_flag() +{ + __reana-client-go_debug "${FUNCNAME[0]}: c is $c words[c] is ${words[c]}" + + # if a command required a flag, and we found it, unset must_have_one_flag() + local flagname=${words[c]} + local flagvalue="" + # if the word contained an = + if [[ ${words[c]} == *"="* ]]; then + flagvalue=${flagname#*=} # take in as flagvalue after the = + flagname=${flagname%=*} # strip everything after the = + flagname="${flagname}=" # but put the = back + fi + __reana-client-go_debug "${FUNCNAME[0]}: looking for ${flagname}" + if __reana-client-go_contains_word "${flagname}" "${must_have_one_flag[@]}"; then + must_have_one_flag=() + fi + + # if you set a flag which only applies to this command, don't show subcommands + if __reana-client-go_contains_word "${flagname}" "${local_nonpersistent_flags[@]}"; then + commands=() + fi + + # keep flag value with flagname as flaghash + # flaghash variable is an associative array which is only supported in bash > 3. + if [[ -z "${BASH_VERSION:-}" || "${BASH_VERSINFO[0]:-}" -gt 3 ]]; then + if [ -n "${flagvalue}" ] ; then + flaghash[${flagname}]=${flagvalue} + elif [ -n "${words[ $((c+1)) ]}" ] ; then + flaghash[${flagname}]=${words[ $((c+1)) ]} + else + flaghash[${flagname}]="true" # pad "true" for bool flag + fi + fi + + # skip the argument to a two word flag + if [[ ${words[c]} != *"="* ]] && __reana-client-go_contains_word "${words[c]}" "${two_word_flags[@]}"; then + __reana-client-go_debug "${FUNCNAME[0]}: found a flag ${words[c]}, skip the next argument" + c=$((c+1)) + # if we are looking for a flags value, don't show commands + if [[ $c -eq $cword ]]; then + commands=() + fi + fi + + c=$((c+1)) + +} + +__reana-client-go_handle_noun() +{ + __reana-client-go_debug "${FUNCNAME[0]}: c is $c words[c] is ${words[c]}" + + if __reana-client-go_contains_word "${words[c]}" "${must_have_one_noun[@]}"; then + must_have_one_noun=() + elif __reana-client-go_contains_word "${words[c]}" "${noun_aliases[@]}"; then + must_have_one_noun=() + fi + + nouns+=("${words[c]}") + c=$((c+1)) +} + +__reana-client-go_handle_command() +{ + __reana-client-go_debug "${FUNCNAME[0]}: c is $c words[c] is ${words[c]}" + + local next_command + if [[ -n ${last_command} ]]; then + next_command="_${last_command}_${words[c]//:/__}" + else + if [[ $c -eq 0 ]]; then + next_command="_reana-client-go_root_command" + else + next_command="_${words[c]//:/__}" + fi + fi + c=$((c+1)) + __reana-client-go_debug "${FUNCNAME[0]}: looking for ${next_command}" + declare -F "$next_command" >/dev/null && $next_command +} + +__reana-client-go_handle_word() +{ + if [[ $c -ge $cword ]]; then + __reana-client-go_handle_reply + return + fi + __reana-client-go_debug "${FUNCNAME[0]}: c is $c words[c] is ${words[c]}" + if [[ "${words[c]}" == -* ]]; then + __reana-client-go_handle_flag + elif __reana-client-go_contains_word "${words[c]}" "${commands[@]}"; then + __reana-client-go_handle_command + elif [[ $c -eq 0 ]]; then + __reana-client-go_handle_command + elif __reana-client-go_contains_word "${words[c]}" "${command_aliases[@]}"; then + # aliashash variable is an associative array which is only supported in bash > 3. + if [[ -z "${BASH_VERSION:-}" || "${BASH_VERSINFO[0]:-}" -gt 3 ]]; then + words[c]=${aliashash[${words[c]}]} + __reana-client-go_handle_command + else + __reana-client-go_handle_noun + fi + else + __reana-client-go_handle_noun + fi + __reana-client-go_handle_word +} + +_reana-client-go_close() +{ + last_command="reana-client-go_close" + + command_aliases=() + + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--access-token=") + two_word_flags+=("--access-token") + two_word_flags+=("-t") + local_nonpersistent_flags+=("--access-token") + local_nonpersistent_flags+=("--access-token=") + local_nonpersistent_flags+=("-t") + flags+=("--workflow=") + two_word_flags+=("--workflow") + two_word_flags+=("-w") + local_nonpersistent_flags+=("--workflow") + local_nonpersistent_flags+=("--workflow=") + local_nonpersistent_flags+=("-w") + flags+=("--loglevel=") + two_word_flags+=("--loglevel") + two_word_flags+=("-l") + flags+=("--profile=") + two_word_flags+=("--profile") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_reana-client-go_completion() +{ + last_command="reana-client-go_completion" + + command_aliases=() + + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--help") + flags+=("-h") + local_nonpersistent_flags+=("--help") + local_nonpersistent_flags+=("-h") + flags+=("--loglevel=") + two_word_flags+=("--loglevel") + two_word_flags+=("-l") + flags+=("--profile=") + two_word_flags+=("--profile") + + must_have_one_flag=() + must_have_one_noun=() + must_have_one_noun+=("bash") + must_have_one_noun+=("fish") + must_have_one_noun+=("powershell") + must_have_one_noun+=("zsh") + noun_aliases=() +} + +_reana-client-go_delete() +{ + last_command="reana-client-go_delete" + + command_aliases=() + + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--access-token=") + two_word_flags+=("--access-token") + two_word_flags+=("-t") + local_nonpersistent_flags+=("--access-token") + local_nonpersistent_flags+=("--access-token=") + local_nonpersistent_flags+=("-t") + flags+=("--include-all-runs") + local_nonpersistent_flags+=("--include-all-runs") + flags+=("--include-workspace") + local_nonpersistent_flags+=("--include-workspace") + flags+=("--workflow=") + two_word_flags+=("--workflow") + two_word_flags+=("-w") + local_nonpersistent_flags+=("--workflow") + local_nonpersistent_flags+=("--workflow=") + local_nonpersistent_flags+=("-w") + flags+=("--loglevel=") + two_word_flags+=("--loglevel") + two_word_flags+=("-l") + flags+=("--profile=") + two_word_flags+=("--profile") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_reana-client-go_diff() +{ + last_command="reana-client-go_diff" + + command_aliases=() + + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--access-token=") + two_word_flags+=("--access-token") + two_word_flags+=("-t") + local_nonpersistent_flags+=("--access-token") + local_nonpersistent_flags+=("--access-token=") + local_nonpersistent_flags+=("-t") + flags+=("--brief") + flags+=("-q") + local_nonpersistent_flags+=("--brief") + local_nonpersistent_flags+=("-q") + flags+=("--unified=") + two_word_flags+=("--unified") + two_word_flags+=("-u") + local_nonpersistent_flags+=("--unified") + local_nonpersistent_flags+=("--unified=") + local_nonpersistent_flags+=("-u") + flags+=("--loglevel=") + two_word_flags+=("--loglevel") + two_word_flags+=("-l") + flags+=("--profile=") + two_word_flags+=("--profile") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_reana-client-go_download() +{ + last_command="reana-client-go_download" + + command_aliases=() + + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--access-token=") + two_word_flags+=("--access-token") + two_word_flags+=("-t") + local_nonpersistent_flags+=("--access-token") + local_nonpersistent_flags+=("--access-token=") + local_nonpersistent_flags+=("-t") + flags+=("--output-directory=") + two_word_flags+=("--output-directory") + two_word_flags+=("-o") + local_nonpersistent_flags+=("--output-directory") + local_nonpersistent_flags+=("--output-directory=") + local_nonpersistent_flags+=("-o") + flags+=("--workflow=") + two_word_flags+=("--workflow") + two_word_flags+=("-w") + local_nonpersistent_flags+=("--workflow") + local_nonpersistent_flags+=("--workflow=") + local_nonpersistent_flags+=("-w") + flags+=("--loglevel=") + two_word_flags+=("--loglevel") + two_word_flags+=("-l") + flags+=("--profile=") + two_word_flags+=("--profile") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_reana-client-go_du() +{ + last_command="reana-client-go_du" + + command_aliases=() + + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--access-token=") + two_word_flags+=("--access-token") + two_word_flags+=("-t") + local_nonpersistent_flags+=("--access-token") + local_nonpersistent_flags+=("--access-token=") + local_nonpersistent_flags+=("-t") + flags+=("--filter=") + two_word_flags+=("--filter") + local_nonpersistent_flags+=("--filter") + local_nonpersistent_flags+=("--filter=") + flags+=("--help") + flags+=("--human-readable") + flags+=("-h") + local_nonpersistent_flags+=("--human-readable") + local_nonpersistent_flags+=("-h") + flags+=("--summarize") + flags+=("-s") + local_nonpersistent_flags+=("--summarize") + local_nonpersistent_flags+=("-s") + flags+=("--workflow=") + two_word_flags+=("--workflow") + two_word_flags+=("-w") + local_nonpersistent_flags+=("--workflow") + local_nonpersistent_flags+=("--workflow=") + local_nonpersistent_flags+=("-w") + flags+=("--loglevel=") + two_word_flags+=("--loglevel") + two_word_flags+=("-l") + flags+=("--profile=") + two_word_flags+=("--profile") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_reana-client-go_help() +{ + last_command="reana-client-go_help" + + command_aliases=() + + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--loglevel=") + two_word_flags+=("--loglevel") + two_word_flags+=("-l") + flags+=("--profile=") + two_word_flags+=("--profile") + + must_have_one_flag=() + must_have_one_noun=() + has_completion_function=1 + noun_aliases=() +} + +_reana-client-go_info() +{ + last_command="reana-client-go_info" + + command_aliases=() + + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--access-token=") + two_word_flags+=("--access-token") + two_word_flags+=("-t") + local_nonpersistent_flags+=("--access-token") + local_nonpersistent_flags+=("--access-token=") + local_nonpersistent_flags+=("-t") + flags+=("--json") + local_nonpersistent_flags+=("--json") + flags+=("--loglevel=") + two_word_flags+=("--loglevel") + two_word_flags+=("-l") + flags+=("--profile=") + two_word_flags+=("--profile") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_reana-client-go_list() +{ + last_command="reana-client-go_list" + + command_aliases=() + + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--access-token=") + two_word_flags+=("--access-token") + two_word_flags+=("-t") + local_nonpersistent_flags+=("--access-token") + local_nonpersistent_flags+=("--access-token=") + local_nonpersistent_flags+=("-t") + flags+=("--all") + local_nonpersistent_flags+=("--all") + flags+=("--filter=") + two_word_flags+=("--filter") + local_nonpersistent_flags+=("--filter") + local_nonpersistent_flags+=("--filter=") + flags+=("--format=") + two_word_flags+=("--format") + local_nonpersistent_flags+=("--format") + local_nonpersistent_flags+=("--format=") + flags+=("--help") + flags+=("--human-readable") + flags+=("-h") + local_nonpersistent_flags+=("--human-readable") + local_nonpersistent_flags+=("-h") + flags+=("--include-duration") + local_nonpersistent_flags+=("--include-duration") + flags+=("--include-progress") + local_nonpersistent_flags+=("--include-progress") + flags+=("--include-workspace-size") + local_nonpersistent_flags+=("--include-workspace-size") + flags+=("--json") + local_nonpersistent_flags+=("--json") + flags+=("--page=") + two_word_flags+=("--page") + local_nonpersistent_flags+=("--page") + local_nonpersistent_flags+=("--page=") + flags+=("--sessions") + flags+=("-s") + local_nonpersistent_flags+=("--sessions") + local_nonpersistent_flags+=("-s") + flags+=("--shared") + local_nonpersistent_flags+=("--shared") + flags+=("--shared-by=") + two_word_flags+=("--shared-by") + local_nonpersistent_flags+=("--shared-by") + local_nonpersistent_flags+=("--shared-by=") + flags+=("--shared-with=") + two_word_flags+=("--shared-with") + local_nonpersistent_flags+=("--shared-with") + local_nonpersistent_flags+=("--shared-with=") + flags+=("--show-deleted-runs") + local_nonpersistent_flags+=("--show-deleted-runs") + flags+=("--size=") + two_word_flags+=("--size") + local_nonpersistent_flags+=("--size") + local_nonpersistent_flags+=("--size=") + flags+=("--sort=") + two_word_flags+=("--sort") + local_nonpersistent_flags+=("--sort") + local_nonpersistent_flags+=("--sort=") + flags+=("--verbose") + flags+=("-v") + local_nonpersistent_flags+=("--verbose") + local_nonpersistent_flags+=("-v") + flags+=("--workflow=") + two_word_flags+=("--workflow") + two_word_flags+=("-w") + local_nonpersistent_flags+=("--workflow") + local_nonpersistent_flags+=("--workflow=") + local_nonpersistent_flags+=("-w") + flags+=("--loglevel=") + two_word_flags+=("--loglevel") + two_word_flags+=("-l") + flags+=("--profile=") + two_word_flags+=("--profile") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_reana-client-go_logs() +{ + last_command="reana-client-go_logs" + + command_aliases=() + + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--access-token=") + two_word_flags+=("--access-token") + two_word_flags+=("-t") + local_nonpersistent_flags+=("--access-token") + local_nonpersistent_flags+=("--access-token=") + local_nonpersistent_flags+=("-t") + flags+=("--filter=") + two_word_flags+=("--filter") + local_nonpersistent_flags+=("--filter") + local_nonpersistent_flags+=("--filter=") + flags+=("--follow") + local_nonpersistent_flags+=("--follow") + flags+=("--interval=") + two_word_flags+=("--interval") + two_word_flags+=("-i") + local_nonpersistent_flags+=("--interval") + local_nonpersistent_flags+=("--interval=") + local_nonpersistent_flags+=("-i") + flags+=("--json") + local_nonpersistent_flags+=("--json") + flags+=("--page=") + two_word_flags+=("--page") + local_nonpersistent_flags+=("--page") + local_nonpersistent_flags+=("--page=") + flags+=("--size=") + two_word_flags+=("--size") + local_nonpersistent_flags+=("--size") + local_nonpersistent_flags+=("--size=") + flags+=("--workflow=") + two_word_flags+=("--workflow") + two_word_flags+=("-w") + local_nonpersistent_flags+=("--workflow") + local_nonpersistent_flags+=("--workflow=") + local_nonpersistent_flags+=("-w") + flags+=("--loglevel=") + two_word_flags+=("--loglevel") + two_word_flags+=("-l") + flags+=("--profile=") + two_word_flags+=("--profile") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_reana-client-go_ls() +{ + last_command="reana-client-go_ls" + + command_aliases=() + + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--access-token=") + two_word_flags+=("--access-token") + two_word_flags+=("-t") + local_nonpersistent_flags+=("--access-token") + local_nonpersistent_flags+=("--access-token=") + local_nonpersistent_flags+=("-t") + flags+=("--filter=") + two_word_flags+=("--filter") + local_nonpersistent_flags+=("--filter") + local_nonpersistent_flags+=("--filter=") + flags+=("--format=") + two_word_flags+=("--format") + local_nonpersistent_flags+=("--format") + local_nonpersistent_flags+=("--format=") + flags+=("--help") + flags+=("--human-readable") + flags+=("-h") + local_nonpersistent_flags+=("--human-readable") + local_nonpersistent_flags+=("-h") + flags+=("--json") + local_nonpersistent_flags+=("--json") + flags+=("--page=") + two_word_flags+=("--page") + local_nonpersistent_flags+=("--page") + local_nonpersistent_flags+=("--page=") + flags+=("--size=") + two_word_flags+=("--size") + local_nonpersistent_flags+=("--size") + local_nonpersistent_flags+=("--size=") + flags+=("--url") + local_nonpersistent_flags+=("--url") + flags+=("--workflow=") + two_word_flags+=("--workflow") + two_word_flags+=("-w") + local_nonpersistent_flags+=("--workflow") + local_nonpersistent_flags+=("--workflow=") + local_nonpersistent_flags+=("-w") + flags+=("--loglevel=") + two_word_flags+=("--loglevel") + two_word_flags+=("-l") + flags+=("--profile=") + two_word_flags+=("--profile") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_reana-client-go_mv() +{ + last_command="reana-client-go_mv" + + command_aliases=() + + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--access-token=") + two_word_flags+=("--access-token") + two_word_flags+=("-t") + local_nonpersistent_flags+=("--access-token") + local_nonpersistent_flags+=("--access-token=") + local_nonpersistent_flags+=("-t") + flags+=("--workflow=") + two_word_flags+=("--workflow") + two_word_flags+=("-w") + local_nonpersistent_flags+=("--workflow") + local_nonpersistent_flags+=("--workflow=") + local_nonpersistent_flags+=("-w") + flags+=("--loglevel=") + two_word_flags+=("--loglevel") + two_word_flags+=("-l") + flags+=("--profile=") + two_word_flags+=("--profile") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_reana-client-go_open() +{ + last_command="reana-client-go_open" + + command_aliases=() + + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--access-token=") + two_word_flags+=("--access-token") + two_word_flags+=("-t") + local_nonpersistent_flags+=("--access-token") + local_nonpersistent_flags+=("--access-token=") + local_nonpersistent_flags+=("-t") + flags+=("--image=") + two_word_flags+=("--image") + two_word_flags+=("-i") + local_nonpersistent_flags+=("--image") + local_nonpersistent_flags+=("--image=") + local_nonpersistent_flags+=("-i") + flags+=("--workflow=") + two_word_flags+=("--workflow") + two_word_flags+=("-w") + local_nonpersistent_flags+=("--workflow") + local_nonpersistent_flags+=("--workflow=") + local_nonpersistent_flags+=("-w") + flags+=("--loglevel=") + two_word_flags+=("--loglevel") + two_word_flags+=("-l") + flags+=("--profile=") + two_word_flags+=("--profile") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_reana-client-go_ping() +{ + last_command="reana-client-go_ping" + + command_aliases=() + + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--access-token=") + two_word_flags+=("--access-token") + two_word_flags+=("-t") + local_nonpersistent_flags+=("--access-token") + local_nonpersistent_flags+=("--access-token=") + local_nonpersistent_flags+=("-t") + flags+=("--loglevel=") + two_word_flags+=("--loglevel") + two_word_flags+=("-l") + flags+=("--profile=") + two_word_flags+=("--profile") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_reana-client-go_prune() +{ + last_command="reana-client-go_prune" + + command_aliases=() + + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--access-token=") + two_word_flags+=("--access-token") + two_word_flags+=("-t") + local_nonpersistent_flags+=("--access-token") + local_nonpersistent_flags+=("--access-token=") + local_nonpersistent_flags+=("-t") + flags+=("--help") + flags+=("--include-inputs") + flags+=("-i") + local_nonpersistent_flags+=("--include-inputs") + local_nonpersistent_flags+=("-i") + flags+=("--include-outputs") + flags+=("-o") + local_nonpersistent_flags+=("--include-outputs") + local_nonpersistent_flags+=("-o") + flags+=("--workflow=") + two_word_flags+=("--workflow") + two_word_flags+=("-w") + local_nonpersistent_flags+=("--workflow") + local_nonpersistent_flags+=("--workflow=") + local_nonpersistent_flags+=("-w") + flags+=("--loglevel=") + two_word_flags+=("--loglevel") + two_word_flags+=("-l") + flags+=("--profile=") + two_word_flags+=("--profile") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_reana-client-go_quota-show() +{ + last_command="reana-client-go_quota-show" + + command_aliases=() + + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--access-token=") + two_word_flags+=("--access-token") + two_word_flags+=("-t") + local_nonpersistent_flags+=("--access-token") + local_nonpersistent_flags+=("--access-token=") + local_nonpersistent_flags+=("-t") + flags+=("--help") + flags+=("--human-readable") + flags+=("-h") + local_nonpersistent_flags+=("--human-readable") + local_nonpersistent_flags+=("-h") + flags+=("--report=") + two_word_flags+=("--report") + local_nonpersistent_flags+=("--report") + local_nonpersistent_flags+=("--report=") + flags+=("--resource=") + two_word_flags+=("--resource") + local_nonpersistent_flags+=("--resource") + local_nonpersistent_flags+=("--resource=") + flags+=("--resources") + local_nonpersistent_flags+=("--resources") + flags+=("--loglevel=") + two_word_flags+=("--loglevel") + two_word_flags+=("-l") + flags+=("--profile=") + two_word_flags+=("--profile") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_reana-client-go_restart() +{ + last_command="reana-client-go_restart" + + command_aliases=() + + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--access-token=") + two_word_flags+=("--access-token") + two_word_flags+=("-t") + local_nonpersistent_flags+=("--access-token") + local_nonpersistent_flags+=("--access-token=") + local_nonpersistent_flags+=("-t") + flags+=("--file=") + two_word_flags+=("--file") + two_word_flags+=("-f") + local_nonpersistent_flags+=("--file") + local_nonpersistent_flags+=("--file=") + local_nonpersistent_flags+=("-f") + flags+=("--option=") + two_word_flags+=("--option") + two_word_flags+=("-o") + local_nonpersistent_flags+=("--option") + local_nonpersistent_flags+=("--option=") + local_nonpersistent_flags+=("-o") + flags+=("--parameter=") + two_word_flags+=("--parameter") + two_word_flags+=("-p") + local_nonpersistent_flags+=("--parameter") + local_nonpersistent_flags+=("--parameter=") + local_nonpersistent_flags+=("-p") + flags+=("--workflow=") + two_word_flags+=("--workflow") + two_word_flags+=("-w") + local_nonpersistent_flags+=("--workflow") + local_nonpersistent_flags+=("--workflow=") + local_nonpersistent_flags+=("-w") + flags+=("--loglevel=") + two_word_flags+=("--loglevel") + two_word_flags+=("-l") + flags+=("--profile=") + two_word_flags+=("--profile") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_reana-client-go_retention-rules-list() +{ + last_command="reana-client-go_retention-rules-list" + + command_aliases=() + + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--access-token=") + two_word_flags+=("--access-token") + two_word_flags+=("-t") + local_nonpersistent_flags+=("--access-token") + local_nonpersistent_flags+=("--access-token=") + local_nonpersistent_flags+=("-t") + flags+=("--format=") + two_word_flags+=("--format") + local_nonpersistent_flags+=("--format") + local_nonpersistent_flags+=("--format=") + flags+=("--json") + local_nonpersistent_flags+=("--json") + flags+=("--workflow=") + two_word_flags+=("--workflow") + two_word_flags+=("-w") + local_nonpersistent_flags+=("--workflow") + local_nonpersistent_flags+=("--workflow=") + local_nonpersistent_flags+=("-w") + flags+=("--loglevel=") + two_word_flags+=("--loglevel") + two_word_flags+=("-l") + flags+=("--profile=") + two_word_flags+=("--profile") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_reana-client-go_rm() +{ + last_command="reana-client-go_rm" + + command_aliases=() + + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--access-token=") + two_word_flags+=("--access-token") + two_word_flags+=("-t") + local_nonpersistent_flags+=("--access-token") + local_nonpersistent_flags+=("--access-token=") + local_nonpersistent_flags+=("-t") + flags+=("--workflow=") + two_word_flags+=("--workflow") + two_word_flags+=("-w") + local_nonpersistent_flags+=("--workflow") + local_nonpersistent_flags+=("--workflow=") + local_nonpersistent_flags+=("-w") + flags+=("--loglevel=") + two_word_flags+=("--loglevel") + two_word_flags+=("-l") + flags+=("--profile=") + two_word_flags+=("--profile") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_reana-client-go_secrets-add() +{ + last_command="reana-client-go_secrets-add" + + command_aliases=() + + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--access-token=") + two_word_flags+=("--access-token") + two_word_flags+=("-t") + local_nonpersistent_flags+=("--access-token") + local_nonpersistent_flags+=("--access-token=") + local_nonpersistent_flags+=("-t") + flags+=("--env=") + two_word_flags+=("--env") + local_nonpersistent_flags+=("--env") + local_nonpersistent_flags+=("--env=") + flags+=("--file=") + two_word_flags+=("--file") + local_nonpersistent_flags+=("--file") + local_nonpersistent_flags+=("--file=") + flags+=("--overwrite") + local_nonpersistent_flags+=("--overwrite") + flags+=("--loglevel=") + two_word_flags+=("--loglevel") + two_word_flags+=("-l") + flags+=("--profile=") + two_word_flags+=("--profile") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_reana-client-go_secrets-delete() +{ + last_command="reana-client-go_secrets-delete" + + command_aliases=() + + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--access-token=") + two_word_flags+=("--access-token") + two_word_flags+=("-t") + local_nonpersistent_flags+=("--access-token") + local_nonpersistent_flags+=("--access-token=") + local_nonpersistent_flags+=("-t") + flags+=("--loglevel=") + two_word_flags+=("--loglevel") + two_word_flags+=("-l") + flags+=("--profile=") + two_word_flags+=("--profile") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_reana-client-go_secrets-list() +{ + last_command="reana-client-go_secrets-list" + + command_aliases=() + + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--access-token=") + two_word_flags+=("--access-token") + two_word_flags+=("-t") + local_nonpersistent_flags+=("--access-token") + local_nonpersistent_flags+=("--access-token=") + local_nonpersistent_flags+=("-t") + flags+=("--loglevel=") + two_word_flags+=("--loglevel") + two_word_flags+=("-l") + flags+=("--profile=") + two_word_flags+=("--profile") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_reana-client-go_share-add() +{ + last_command="reana-client-go_share-add" + + command_aliases=() + + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--access-token=") + two_word_flags+=("--access-token") + two_word_flags+=("-t") + local_nonpersistent_flags+=("--access-token") + local_nonpersistent_flags+=("--access-token=") + local_nonpersistent_flags+=("-t") + flags+=("--help") + flags+=("-h") + flags+=("--message=") + two_word_flags+=("--message") + two_word_flags+=("-m") + local_nonpersistent_flags+=("--message") + local_nonpersistent_flags+=("--message=") + local_nonpersistent_flags+=("-m") + flags+=("--user=") + two_word_flags+=("--user") + two_word_flags+=("-u") + local_nonpersistent_flags+=("--user") + local_nonpersistent_flags+=("--user=") + local_nonpersistent_flags+=("-u") + flags+=("--valid-until=") + two_word_flags+=("--valid-until") + local_nonpersistent_flags+=("--valid-until") + local_nonpersistent_flags+=("--valid-until=") + flags+=("--workflow=") + two_word_flags+=("--workflow") + two_word_flags+=("-w") + local_nonpersistent_flags+=("--workflow") + local_nonpersistent_flags+=("--workflow=") + local_nonpersistent_flags+=("-w") + flags+=("--loglevel=") + two_word_flags+=("--loglevel") + two_word_flags+=("-l") + flags+=("--profile=") + two_word_flags+=("--profile") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_reana-client-go_share-remove() +{ + last_command="reana-client-go_share-remove" + + command_aliases=() + + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--access-token=") + two_word_flags+=("--access-token") + two_word_flags+=("-t") + local_nonpersistent_flags+=("--access-token") + local_nonpersistent_flags+=("--access-token=") + local_nonpersistent_flags+=("-t") + flags+=("--help") + flags+=("-h") + flags+=("--user=") + two_word_flags+=("--user") + two_word_flags+=("-u") + local_nonpersistent_flags+=("--user") + local_nonpersistent_flags+=("--user=") + local_nonpersistent_flags+=("-u") + flags+=("--workflow=") + two_word_flags+=("--workflow") + two_word_flags+=("-w") + local_nonpersistent_flags+=("--workflow") + local_nonpersistent_flags+=("--workflow=") + local_nonpersistent_flags+=("-w") + flags+=("--loglevel=") + two_word_flags+=("--loglevel") + two_word_flags+=("-l") + flags+=("--profile=") + two_word_flags+=("--profile") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_reana-client-go_share-status() +{ + last_command="reana-client-go_share-status" + + command_aliases=() + + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--access-token=") + two_word_flags+=("--access-token") + two_word_flags+=("-t") + local_nonpersistent_flags+=("--access-token") + local_nonpersistent_flags+=("--access-token=") + local_nonpersistent_flags+=("-t") + flags+=("--format=") + two_word_flags+=("--format") + local_nonpersistent_flags+=("--format") + local_nonpersistent_flags+=("--format=") + flags+=("--help") + flags+=("-h") + flags+=("--json") + local_nonpersistent_flags+=("--json") + flags+=("--workflow=") + two_word_flags+=("--workflow") + two_word_flags+=("-w") + local_nonpersistent_flags+=("--workflow") + local_nonpersistent_flags+=("--workflow=") + local_nonpersistent_flags+=("-w") + flags+=("--loglevel=") + two_word_flags+=("--loglevel") + two_word_flags+=("-l") + flags+=("--profile=") + two_word_flags+=("--profile") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_reana-client-go_start() +{ + last_command="reana-client-go_start" + + command_aliases=() + + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--access-token=") + two_word_flags+=("--access-token") + two_word_flags+=("-t") + local_nonpersistent_flags+=("--access-token") + local_nonpersistent_flags+=("--access-token=") + local_nonpersistent_flags+=("-t") + flags+=("--follow") + local_nonpersistent_flags+=("--follow") + flags+=("--option=") + two_word_flags+=("--option") + two_word_flags+=("-o") + local_nonpersistent_flags+=("--option") + local_nonpersistent_flags+=("--option=") + local_nonpersistent_flags+=("-o") + flags+=("--parameter=") + two_word_flags+=("--parameter") + two_word_flags+=("-p") + local_nonpersistent_flags+=("--parameter") + local_nonpersistent_flags+=("--parameter=") + local_nonpersistent_flags+=("-p") + flags+=("--workflow=") + two_word_flags+=("--workflow") + two_word_flags+=("-w") + local_nonpersistent_flags+=("--workflow") + local_nonpersistent_flags+=("--workflow=") + local_nonpersistent_flags+=("-w") + flags+=("--loglevel=") + two_word_flags+=("--loglevel") + two_word_flags+=("-l") + flags+=("--profile=") + two_word_flags+=("--profile") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_reana-client-go_status() +{ + last_command="reana-client-go_status" + + command_aliases=() + + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--access-token=") + two_word_flags+=("--access-token") + two_word_flags+=("-t") + local_nonpersistent_flags+=("--access-token") + local_nonpersistent_flags+=("--access-token=") + local_nonpersistent_flags+=("-t") + flags+=("--format=") + two_word_flags+=("--format") + local_nonpersistent_flags+=("--format") + local_nonpersistent_flags+=("--format=") + flags+=("--include-duration") + local_nonpersistent_flags+=("--include-duration") + flags+=("--json") + local_nonpersistent_flags+=("--json") + flags+=("--verbose") + flags+=("-v") + local_nonpersistent_flags+=("--verbose") + local_nonpersistent_flags+=("-v") + flags+=("--workflow=") + two_word_flags+=("--workflow") + two_word_flags+=("-w") + local_nonpersistent_flags+=("--workflow") + local_nonpersistent_flags+=("--workflow=") + local_nonpersistent_flags+=("-w") + flags+=("--loglevel=") + two_word_flags+=("--loglevel") + two_word_flags+=("-l") + flags+=("--profile=") + two_word_flags+=("--profile") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_reana-client-go_stop() +{ + last_command="reana-client-go_stop" + + command_aliases=() + + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--access-token=") + two_word_flags+=("--access-token") + two_word_flags+=("-t") + local_nonpersistent_flags+=("--access-token") + local_nonpersistent_flags+=("--access-token=") + local_nonpersistent_flags+=("-t") + flags+=("--force") + local_nonpersistent_flags+=("--force") + flags+=("--workflow=") + two_word_flags+=("--workflow") + two_word_flags+=("-w") + local_nonpersistent_flags+=("--workflow") + local_nonpersistent_flags+=("--workflow=") + local_nonpersistent_flags+=("-w") + flags+=("--loglevel=") + two_word_flags+=("--loglevel") + two_word_flags+=("-l") + flags+=("--profile=") + two_word_flags+=("--profile") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_reana-client-go_upload() +{ + last_command="reana-client-go_upload" + + command_aliases=() + + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--access-token=") + two_word_flags+=("--access-token") + two_word_flags+=("-t") + local_nonpersistent_flags+=("--access-token") + local_nonpersistent_flags+=("--access-token=") + local_nonpersistent_flags+=("-t") + flags+=("--workflow=") + two_word_flags+=("--workflow") + two_word_flags+=("-w") + local_nonpersistent_flags+=("--workflow") + local_nonpersistent_flags+=("--workflow=") + local_nonpersistent_flags+=("-w") + flags+=("--loglevel=") + two_word_flags+=("--loglevel") + two_word_flags+=("-l") + flags+=("--profile=") + two_word_flags+=("--profile") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_reana-client-go_version() +{ + last_command="reana-client-go_version" + + command_aliases=() + + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--loglevel=") + two_word_flags+=("--loglevel") + two_word_flags+=("-l") + flags+=("--profile=") + two_word_flags+=("--profile") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_reana-client-go_root_command() +{ + last_command="reana-client-go" + + command_aliases=() + + commands=() + commands+=("close") + commands+=("completion") + commands+=("delete") + commands+=("diff") + commands+=("download") + commands+=("du") + commands+=("help") + commands+=("info") + commands+=("list") + commands+=("logs") + commands+=("ls") + commands+=("mv") + commands+=("open") + commands+=("ping") + commands+=("prune") + commands+=("quota-show") + commands+=("restart") + commands+=("retention-rules-list") + commands+=("rm") + commands+=("secrets-add") + commands+=("secrets-delete") + commands+=("secrets-list") + commands+=("share-add") + commands+=("share-remove") + commands+=("share-status") + commands+=("start") + commands+=("status") + commands+=("stop") + commands+=("upload") + commands+=("version") + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--loglevel=") + two_word_flags+=("--loglevel") + two_word_flags+=("-l") + flags+=("--profile=") + two_word_flags+=("--profile") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +__start_reana-client-go() +{ + local cur prev words cword split + declare -A flaghash 2>/dev/null || : + declare -A aliashash 2>/dev/null || : + if declare -F _init_completion >/dev/null 2>&1; then + _init_completion -s || return + else + __reana-client-go_init_completion -n "=" || return + fi + + local c=0 + local flag_parsing_disabled= + local flags=() + local two_word_flags=() + local local_nonpersistent_flags=() + local flags_with_completion=() + local flags_completion=() + local commands=("reana-client-go") + local command_aliases=() + local must_have_one_flag=() + local must_have_one_noun=() + local has_completion_function="" + local last_command="" + local nouns=() + local noun_aliases=() + + __reana-client-go_handle_word +} + +if [[ $(type -t compopt) = "builtin" ]]; then + complete -o default -F __start_reana-client-go reana-client-go +else + complete -o default -o nospace -F __start_reana-client-go reana-client-go +fi + +# ex: ts=4 sw=4 et filetype=sh diff --git a/etc/zsh_completion.d/_reana-client-go b/etc/zsh_completion.d/_reana-client-go new file mode 100644 index 0000000..9536162 --- /dev/null +++ b/etc/zsh_completion.d/_reana-client-go @@ -0,0 +1,225 @@ +#compdef reana-client-go +# This file is part of REANA. +# Copyright (C) 2025 CERN. +# +# REANA is free software; you can redistribute it and/or modify it +# under the terms of the MIT License; see LICENSE file for more details. + +# Zsh completion for reana-client-go (Cobra CLI) +# +# Installation: +# Option 1: Add directory to fpath in your .zshrc (before compinit): +# fpath=(/path/to/reana-client-go/etc/zsh_completion.d $fpath) +# autoload -Uz compinit && compinit +# +# Option 2: Copy to system completion directory: +# cp /path/to/reana-client-go/etc/zsh_completion.d/_reana-client-go \ +# /usr/local/share/zsh/site-functions/ +# +# Option 3: Generate dynamically (add to .zshrc): +# source <(reana-client-go completion zsh) +# compdef _reana-client-go reana-client-go + +# zsh completion for reana-client-go -*- shell-script -*- + +__reana-client-go_debug() +{ + local file="$BASH_COMP_DEBUG_FILE" + if [[ -n ${file} ]]; then + echo "$*" >> "${file}" + fi +} + +_reana-client-go() +{ + local shellCompDirectiveError=1 + local shellCompDirectiveNoSpace=2 + local shellCompDirectiveNoFileComp=4 + local shellCompDirectiveFilterFileExt=8 + local shellCompDirectiveFilterDirs=16 + + local lastParam lastChar flagPrefix requestComp out directive comp lastComp noSpace + local -a completions + + __reana-client-go_debug "\n========= starting completion logic ==========" + __reana-client-go_debug "CURRENT: ${CURRENT}, words[*]: ${words[*]}" + + # The user could have moved the cursor backwards on the command-line. + # We need to trigger completion from the $CURRENT location, so we need + # to truncate the command-line ($words) up to the $CURRENT location. + # (We cannot use $CURSOR as its value does not work when a command is an alias.) + words=("${=words[1,CURRENT]}") + __reana-client-go_debug "Truncated words[*]: ${words[*]}," + + lastParam=${words[-1]} + lastChar=${lastParam[-1]} + __reana-client-go_debug "lastParam: ${lastParam}, lastChar: ${lastChar}" + + # For zsh, when completing a flag with an = (e.g., reana-client-go -n=) + # completions must be prefixed with the flag + setopt local_options BASH_REMATCH + if [[ "${lastParam}" =~ '-.*=' ]]; then + # We are dealing with a flag with an = + flagPrefix="-P ${BASH_REMATCH}" + fi + + # Prepare the command to obtain completions + requestComp="${words[1]} __complete ${words[2,-1]}" + if [ "${lastChar}" = "" ]; then + # If the last parameter is complete (there is a space following it) + # We add an extra empty parameter so we can indicate this to the go completion code. + __reana-client-go_debug "Adding extra empty parameter" + requestComp="${requestComp} \"\"" + fi + + __reana-client-go_debug "About to call: eval ${requestComp}" + + # Use eval to handle any environment variables and such + out=$(eval ${requestComp} 2>/dev/null) + __reana-client-go_debug "completion output: ${out}" + + # Extract the directive integer following a : from the last line + local lastLine + while IFS='\n' read -r line; do + lastLine=${line} + done < <(printf "%s\n" "${out[@]}") + __reana-client-go_debug "last line: ${lastLine}" + + if [ "${lastLine[1]}" = : ]; then + directive=${lastLine[2,-1]} + # Remove the directive including the : and the newline + local suffix + (( suffix=${#lastLine}+2)) + out=${out[1,-$suffix]} + else + # There is no directive specified. Leave $out as is. + __reana-client-go_debug "No directive found. Setting do default" + directive=0 + fi + + __reana-client-go_debug "directive: ${directive}" + __reana-client-go_debug "completions: ${out}" + __reana-client-go_debug "flagPrefix: ${flagPrefix}" + + if [ $((directive & shellCompDirectiveError)) -ne 0 ]; then + __reana-client-go_debug "Completion received error. Ignoring completions." + return + fi + + local activeHelpMarker="_activeHelp_ " + local endIndex=${#activeHelpMarker} + local startIndex=$((${#activeHelpMarker}+1)) + local hasActiveHelp=0 + while IFS='\n' read -r comp; do + # Check if this is an activeHelp statement (i.e., prefixed with $activeHelpMarker) + if [ "${comp[1,$endIndex]}" = "$activeHelpMarker" ];then + __reana-client-go_debug "ActiveHelp found: $comp" + comp="${comp[$startIndex,-1]}" + if [ -n "$comp" ]; then + compadd -x "${comp}" + __reana-client-go_debug "ActiveHelp will need delimiter" + hasActiveHelp=1 + fi + + continue + fi + + if [ -n "$comp" ]; then + # If requested, completions are returned with a description. + # The description is preceded by a TAB character. + # For zsh's _describe, we need to use a : instead of a TAB. + # We first need to escape any : as part of the completion itself. + comp=${comp//:/\\:} + + local tab="$(printf '\t')" + comp=${comp//$tab/:} + + __reana-client-go_debug "Adding completion: ${comp}" + completions+=${comp} + lastComp=$comp + fi + done < <(printf "%s\n" "${out[@]}") + + # Add a delimiter after the activeHelp statements, but only if: + # - there are completions following the activeHelp statements, or + # - file completion will be performed (so there will be choices after the activeHelp) + if [ $hasActiveHelp -eq 1 ]; then + if [ ${#completions} -ne 0 ] || [ $((directive & shellCompDirectiveNoFileComp)) -eq 0 ]; then + __reana-client-go_debug "Adding activeHelp delimiter" + compadd -x "--" + hasActiveHelp=0 + fi + fi + + if [ $((directive & shellCompDirectiveNoSpace)) -ne 0 ]; then + __reana-client-go_debug "Activating nospace." + noSpace="-S ''" + fi + + if [ $((directive & shellCompDirectiveFilterFileExt)) -ne 0 ]; then + # File extension filtering + local filteringCmd + filteringCmd='_files' + for filter in ${completions[@]}; do + if [ ${filter[1]} != '*' ]; then + # zsh requires a glob pattern to do file filtering + filter="\*.$filter" + fi + filteringCmd+=" -g $filter" + done + filteringCmd+=" ${flagPrefix}" + + __reana-client-go_debug "File filtering command: $filteringCmd" + _arguments '*:filename:'"$filteringCmd" + elif [ $((directive & shellCompDirectiveFilterDirs)) -ne 0 ]; then + # File completion for directories only + local subdir + subdir="${completions[1]}" + if [ -n "$subdir" ]; then + __reana-client-go_debug "Listing directories in $subdir" + pushd "${subdir}" >/dev/null 2>&1 + else + __reana-client-go_debug "Listing directories in ." + fi + + local result + _arguments '*:dirname:_files -/'" ${flagPrefix}" + result=$? + if [ -n "$subdir" ]; then + popd >/dev/null 2>&1 + fi + return $result + else + __reana-client-go_debug "Calling _describe" + if eval _describe "completions" completions $flagPrefix $noSpace; then + __reana-client-go_debug "_describe found some completions" + + # Return the success of having called _describe + return 0 + else + __reana-client-go_debug "_describe did not find completions." + __reana-client-go_debug "Checking if we should do file completion." + if [ $((directive & shellCompDirectiveNoFileComp)) -ne 0 ]; then + __reana-client-go_debug "deactivating file completion" + + # We must return an error code here to let zsh know that there were no + # completions found by _describe; this is what will trigger other + # matching algorithms to attempt to find completions. + # For example zsh can match letters in the middle of words. + return 1 + else + # Perform file completion + __reana-client-go_debug "Activating file completion" + + # We must return the result of this command, so it must be the + # last command, or else we must store its result to return it. + _arguments '*:filename:_files'" ${flagPrefix}" + fi + fi + fi +} + +# don't run the completion function when being source-ed or eval-ed +if [ "$funcstack[1]" = "_reana-client-go" ]; then + _reana-client-go +fi diff --git a/run-tests.sh b/run-tests.sh index 3141cbd..cb99efe 100755 --- a/run-tests.sh +++ b/run-tests.sh @@ -47,9 +47,38 @@ check_shellcheck () { find . -name "*.sh" -exec shellcheck {} \+ } +check_shell_completions () { + # Check that shell completion files are up-to-date + go build -o reana-client-go . + temp_dir=$(mktemp -d) + trap 'rm -rf "$temp_dir"' EXIT + + ./reana-client-go completion bash > "$temp_dir/reana-client-go" + ./reana-client-go completion zsh > "$temp_dir/_reana-client-go" + + # Compare bash completion (our header is lines 1-19, Cobra output starts at line 20) + if ! diff -q <(tail -n +20 etc/bash_completion.d/reana-client-go) "$temp_dir/reana-client-go" > /dev/null 2>&1; then + echo "✖ Bash completion file is out of date." + echo " Please regenerate with: ./reana-client-go completion bash > etc/bash_completion.d/reana-client-go" + echo " Then restore the file header." + exit 1 + fi + + # Compare zsh completion (line 1 is Cobra's #compdef, lines 2-22 are our header, line 23+ is Cobra's line 3+) + if ! diff -q <(sed -n '1p;23,$p' etc/zsh_completion.d/_reana-client-go) <(sed -n '1p;3,$p' "$temp_dir/_reana-client-go") > /dev/null 2>&1; then + echo "✖ Zsh completion file is out of date." + echo " Please regenerate with: ./reana-client-go completion zsh > etc/zsh_completion.d/_reana-client-go" + echo " Then restore the file header." + exit 1 + fi + + echo "✔ Shell completion files are up-to-date." +} + check_all () { check_commitlint check_shellcheck + check_shell_completions } if [ $# -eq 0 ]; then @@ -61,5 +90,6 @@ arg="$1" case $arg in --check-commitlint) check_commitlint "$@";; --check-shellcheck) check_shellcheck;; + --check-shell-completions) check_shell_completions;; *) echo "[ERROR] Invalid argument '$arg'. Exiting." && exit 1;; esac