Skip to content

Commit

Permalink
fix review
Browse files Browse the repository at this point in the history
Signed-off-by: Patrik Cyvoct <[email protected]>
  • Loading branch information
Sh4d1 committed Apr 22, 2020
1 parent 7ea464e commit 2a0784f
Show file tree
Hide file tree
Showing 7 changed files with 79 additions and 72 deletions.
10 changes: 5 additions & 5 deletions internal/core/autocomplete.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package core

import (
"context"
"os"
"regexp"
"sort"
"strconv"
Expand Down Expand Up @@ -195,9 +194,9 @@ func (node *AutoCompleteNode) isLeafCommand() bool {
}

// BuildAutoCompleteTree builds the autocomplete tree from the commands, subcommands and arguments
func BuildAutoCompleteTree(commands *Commands) *AutoCompleteNode {
func BuildAutoCompleteTree(commands *Commands, meta *meta) *AutoCompleteNode {
root := NewAutoCompleteCommandNode()
scwCommand := root.GetChildOrCreate(os.Args[0])
scwCommand := root.GetChildOrCreate(meta.BinaryName)
scwCommand.addGlobalFlags()
for _, cmd := range commands.commands {
node := scwCommand
Expand Down Expand Up @@ -247,9 +246,10 @@ func BuildAutoCompleteTree(commands *Commands) *AutoCompleteNode {
// eg: scw test flower create name=p -o=jso
func AutoComplete(ctx context.Context, leftWords []string, wordToComplete string, rightWords []string) *AutocompleteResponse {
commands := ExtractCommands(ctx)
meta := extractMeta(ctx)

// Create AutoComplete Tree
commandTreeRoot := BuildAutoCompleteTree(commands)
commandTreeRoot := BuildAutoCompleteTree(commands, meta)

// For each left word that is not a flag nor an argument, we try to go deeper in the autocomplete tree and store the current node in `node`.
node := commandTreeRoot
Expand All @@ -260,7 +260,7 @@ func AutoComplete(ctx context.Context, leftWords []string, wordToComplete string
if i == 0 {
// override the command name with the real one
// useful when command is renamed or behind a shell function
word = os.Args[0]
word = meta.BinaryName
}

children, childrenExists := node.Children[word]
Expand Down
1 change: 1 addition & 0 deletions internal/core/bootstrap.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ func Bootstrap(config *BootstrapConfig) (exitCode int, result interface{}, err e
// Meta store globally available variables like SDK client.
// Meta is injected in a context object that will be passed to all commands.
meta := &meta{
BinaryName: config.Args[0],
BuildInfo: config.BuildInfo,
stdout: config.Stdout,
stderr: config.Stderr,
Expand Down
5 changes: 2 additions & 3 deletions internal/core/cobra_builder.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package core

import (
"os"
"strings"

"github.com/spf13/cobra"
Expand All @@ -26,7 +25,7 @@ func (b *cobraBuilder) build() *cobra.Command {
commandsIndex := map[string]*Command{}

rootCmd := &cobra.Command{
Use: os.Args[0],
Use: b.meta.BinaryName,

// Do not display error with cobra, we handle it in bootstrap.
SilenceErrors: true,
Expand Down Expand Up @@ -105,7 +104,7 @@ func (b *cobraBuilder) hydrateCobra(cobraCmd *cobra.Command, cmd *Command) {
}

if cmd.Examples != nil {
cobraCmd.Annotations["Examples"] = buildExamples(cmd)
cobraCmd.Annotations["Examples"] = buildExamples(cmd, b.meta)
}

if cmd.SeeAlsos != nil {
Expand Down
5 changes: 2 additions & 3 deletions internal/core/cobra_usage_builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import (
"encoding/json"
"fmt"
"io"
"os"
"strings"
"text/tabwriter"

Expand Down Expand Up @@ -71,7 +70,7 @@ func _buildArgShort(as *ArgSpec) string {

// buildExamples builds usage examples string.
// This string will be used by cobra usage template.
func buildExamples(cmd *Command) string {
func buildExamples(cmd *Command, meta *meta) string {
// Build the examples array.
var examples []string

Expand Down Expand Up @@ -111,7 +110,7 @@ func buildExamples(cmd *Command) string {

// Build command line example.
commandParts := []string{
os.Args[0],
meta.BinaryName,
cmd.Namespace,
cmd.Resource,
cmd.Verb,
Expand Down
13 changes: 6 additions & 7 deletions internal/core/cobra_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"context"
"errors"
"fmt"
"os"
"reflect"
"strings"

Expand All @@ -27,7 +26,7 @@ func cobraRun(ctx context.Context, cmd *Command) func(*cobra.Command, []string)
cmdArgs := reflect.New(cmd.ArgsType).Interface()

// Handle positional argument by catching first argument `<value>` and rewrite it to `<arg-name>=<value>`.
if err = handlePositionalArg(cmd, rawArgs); err != nil {
if err = handlePositionalArg(cmd, rawArgs, meta); err != nil {
return err
}

Expand Down Expand Up @@ -114,7 +113,7 @@ func cobraRun(ctx context.Context, cmd *Command) func(*cobra.Command, []string)
// - no positional argument is found.
// - an unknown positional argument exists in the comand.
// - an argument duplicates a positional argument.
func handlePositionalArg(cmd *Command, rawArgs []string) error {
func handlePositionalArg(cmd *Command, rawArgs []string, meta *meta) error {
positionalArg := cmd.ArgSpecs.GetPositionalArg()

// Command does not have a positional argument.
Expand All @@ -132,7 +131,7 @@ func handlePositionalArg(cmd *Command, rawArgs []string) error {
otherArgs := append(rawArgs[:i], rawArgs[i+1:]...)
return &CliError{
Err: fmt.Errorf("a positional argument is required for this command"),
Hint: positionalArgHint(cmd, argumentValue, otherArgs, positionalArgumentFound),
Hint: positionalArgHint(cmd, argumentValue, otherArgs, positionalArgumentFound, meta),
}
}
}
Expand All @@ -146,12 +145,12 @@ func handlePositionalArg(cmd *Command, rawArgs []string) error {
// No positional argument found.
return &CliError{
Err: fmt.Errorf("a positional argument is required for this command"),
Hint: positionalArgHint(cmd, "<"+positionalArg.Name+">", rawArgs, false),
Hint: positionalArgHint(cmd, "<"+positionalArg.Name+">", rawArgs, false, meta),
}
}

// positionalArgHint formats the positional argument error hint.
func positionalArgHint(cmd *Command, hintValue string, otherArgs []string, positionalArgumentFound bool) string {
func positionalArgHint(cmd *Command, hintValue string, otherArgs []string, positionalArgumentFound bool, meta *meta) string {
suggestedArgs := []string{}

// If no positional argument exists, suggest one.
Expand All @@ -162,7 +161,7 @@ func positionalArgHint(cmd *Command, hintValue string, otherArgs []string, posit
// Suggest to use the other arguments.
suggestedArgs = append(suggestedArgs, otherArgs...)

suggestedCommand := append([]string{os.Args[0], cmd.GetCommandLine()}, suggestedArgs...)
suggestedCommand := append([]string{meta.BinaryName, cmd.GetCommandLine()}, suggestedArgs...)
return "Try running: " + strings.Join(suggestedCommand, " ")
}

Expand Down
7 changes: 6 additions & 1 deletion internal/core/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import (

// meta store globally available variables like sdk client or global Flags.
type meta struct {
BinaryName string

ProfileFlag string
DebugModeFlag bool
PrinterTypeFlag printer.Type
Expand Down Expand Up @@ -89,7 +91,10 @@ func ExtractEnv(ctx context.Context, envKey string) string {

return os.Getenv(envKey)
}

func ExtractUserHomeDir(ctx context.Context) string {
return ExtractEnv(ctx, "HOME")
}

func ExtractBinaryName(ctx context.Context) string {
return extractMeta(ctx).BinaryName
}
110 changes: 57 additions & 53 deletions internal/namespaces/autocomplete/autocomplete.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,20 +38,21 @@ var homePath, _ = os.UserHomeDir()

// autocompleteScripts regroups the autocomplete scripts for the different shells
// The key is the path of the shell.
var autocompleteScripts = map[string]autocompleteScript{
"bash": {
// If `scw` is the first word on the command line,
// after hitting [tab] arguments are sent to `scw autocomplete complete bash`:
// - COMP_LINE: the complete command line
// - cword: the index of the word being completed (source COMP_CWORD)
// - words: the words composing the command line (source COMP_WORDS)
//
// Note that `=` signs are excluding from $COMP_WORDBREAKS. As a result, they are NOT be
// considered as breaking words and arguments like `image=` will not be split.
//
// Then `scw autocomplete complete bash` process the line, and tries to returns suggestions.
// These scw suggestions are put into `COMPREPLY` which is used by Bash to provides the shell suggestions.
CompleteFunc: fmt.Sprintf(`
func autocompleteScripts(binaryName string) map[string]autocompleteScript {
return map[string]autocompleteScript{
"bash": {
// If `scw` is the first word on the command line,
// after hitting [tab] arguments are sent to `scw autocomplete complete bash`:
// - COMP_LINE: the complete command line
// - cword: the index of the word being completed (source COMP_CWORD)
// - words: the words composing the command line (source COMP_WORDS)
//
// Note that `=` signs are excluding from $COMP_WORDBREAKS. As a result, they are NOT be
// considered as breaking words and arguments like `image=` will not be split.
//
// Then `scw autocomplete complete bash` process the line, and tries to returns suggestions.
// These scw suggestions are put into `COMPREPLY` which is used by Bash to provides the shell suggestions.
CompleteFunc: fmt.Sprintf(`
_%[1]s() {
_get_comp_words_by_ref -n = cword words
Expand All @@ -62,42 +63,42 @@ var autocompleteScripts = map[string]autocompleteScript{
return
}
complete -F _%[1]s %[1]s
`, os.Args[0]),
CompleteScript: fmt.Sprintf(`eval "$(%s autocomplete script shell=bash)"`, os.Args[0]),
ShellConfigurationFile: map[string]string{
"darwin": path.Join(homePath, ".bash_profile"),
"linux": path.Join(homePath, ".bashrc"),
`, binaryName),
CompleteScript: fmt.Sprintf(`eval "$(%s autocomplete script shell=bash)"`, os.Args[0]),
ShellConfigurationFile: map[string]string{
"darwin": path.Join(homePath, ".bash_profile"),
"linux": path.Join(homePath, ".bashrc"),
},
},
},
"fish": {
// (commandline) complete command line
// (commandline --cursor) position of the cursor, as number of chars in the command line
// (commandline --current-token) word to complete
// (commandline --tokenize --cut-at-cursor) tokenized selection up until the current cursor position
// formatted as one string-type token per line
//
// If files are shown although --no-files is set,
// it might be because you are using an alias for scw, such as :
// alias scw='go run "$HOME"/scaleway-cli/cmd/scw/main.go'
// You might want to run 'complete --erase --command go' during development.
//
// TODO: send rightWords
CompleteFunc: fmt.Sprintf(`
"fish": {
// (commandline) complete command line
// (commandline --cursor) position of the cursor, as number of chars in the command line
// (commandline --current-token) word to complete
// (commandline --tokenize --cut-at-cursor) tokenized selection up until the current cursor position
// formatted as one string-type token per line
//
// If files are shown although --no-files is set,
// it might be because you are using an alias for scw, such as :
// alias scw='go run "$HOME"/scaleway-cli/cmd/scw/main.go'
// You might want to run 'complete --erase --command go' during development.
//
// TODO: send rightWords
CompleteFunc: fmt.Sprintf(`
complete --erase --command %[1]s;
complete --command %[1]s --no-files;
complete --command %[1]s --arguments '(%[1]s autocomplete complete fish -- (commandline) (commandline --cursor) (commandline --current-token) (commandline --current-process --tokenize --cut-at-cursor))';
`, os.Args[0]),
CompleteScript: fmt.Sprintf(`eval (%s autocomplete script shell=fish)`, os.Args[0]),
ShellConfigurationFile: map[string]string{
"darwin": path.Join(homePath, ".config/fish/config.fish"),
"linux": path.Join(homePath, ".config/fish/config.fish"),
`, binaryName),
CompleteScript: fmt.Sprintf(`eval (%s autocomplete script shell=fish)`, os.Args[0]),
ShellConfigurationFile: map[string]string{
"darwin": path.Join(homePath, ".config/fish/config.fish"),
"linux": path.Join(homePath, ".config/fish/config.fish"),
},
},
},
"zsh": {
// If you are using an alias for scw, such as :
// alias scw='go run "$HOME"/scaleway-cli/cmd/scw/main.go'
// you might want to run 'compdef _scw go' during development.
CompleteFunc: fmt.Sprintf(`
"zsh": {
// If you are using an alias for scw, such as :
// alias scw='go run "$HOME"/scaleway-cli/cmd/scw/main.go'
// you might want to run 'compdef _scw go' during development.
CompleteFunc: fmt.Sprintf(`
autoload -U compinit && compinit
_%[1]s () {
output=($(%[1]s autocomplete complete zsh -- ${CURRENT} ${words}))
Expand All @@ -108,13 +109,14 @@ var autocompleteScripts = map[string]autocompleteScript{
compadd "${opts[@]}" -- "${output[@]}"
}
compdef _%[1]s %[1]s
`, os.Args[0]),
CompleteScript: fmt.Sprintf(`eval "$(%s autocomplete script shell=zsh)"`, os.Args[0]),
ShellConfigurationFile: map[string]string{
"darwin": path.Join(homePath, ".zshrc"),
"linux": path.Join(homePath, ".zshrc"),
`, binaryName),
CompleteScript: fmt.Sprintf(`eval "$(%s autocomplete script shell=zsh)"`, os.Args[0]),
ShellConfigurationFile: map[string]string{
"darwin": path.Join(homePath, ".zshrc"),
"linux": path.Join(homePath, ".zshrc"),
},
},
},
}
}

type InstallArgs struct {
Expand All @@ -141,6 +143,7 @@ func autocompleteInstallCommand() *core.Command {
func InstallCommandRun(ctx context.Context, argsI interface{}) (i interface{}, e error) {
// Warning
_, _ = interactive.Println("To enable autocomplete, scw needs to update your shell configuration.")
binaryName := core.ExtractBinaryName(ctx)

// If `shell=` is empty, ask for a value for `shell=`.
shellArg := argsI.(*InstallArgs).Shell
Expand All @@ -161,7 +164,7 @@ func InstallCommandRun(ctx context.Context, argsI interface{}) (i interface{}, e

shellName := filepath.Base(shellArg)

script, exists := autocompleteScripts[shellName]
script, exists := autocompleteScripts(binaryName)[shellName]
if !exists {
return nil, unsupportedShellError(shellName)
}
Expand Down Expand Up @@ -353,8 +356,9 @@ func autocompleteScriptCommand() *core.Command {
},
ArgsType: reflect.TypeOf(autocompleteShowArgs{}),
Run: func(ctx context.Context, argsI interface{}) (i interface{}, e error) {
binaryName := core.ExtractBinaryName(ctx)
shell := filepath.Base(argsI.(*autocompleteShowArgs).Shell)
script, exists := autocompleteScripts[shell]
script, exists := autocompleteScripts(binaryName)[shell]
if !exists {
return nil, unsupportedShellError(shell)
}
Expand Down

0 comments on commit 2a0784f

Please sign in to comment.