Skip to content

Commit

Permalink
feat(autocomplete): global flag profile values (#3036)
Browse files Browse the repository at this point in the history
  • Loading branch information
Codelax committed Apr 18, 2023
1 parent 0d3def4 commit 2dfca5d
Show file tree
Hide file tree
Showing 5 changed files with 120 additions and 56 deletions.
84 changes: 28 additions & 56 deletions internal/core/autocomplete.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,48 +57,11 @@ type FlagSpec struct {
EnumValues []string
}

func (node *AutoCompleteNode) addGlobalFlags() {
printerTypes := []string{
PrinterTypeHuman.String(),
PrinterTypeJSON.String(),
PrinterTypeYAML.String(),
PrinterTypeTemplate.String(),
}

node.Children["-c"] = NewAutoCompleteFlagNode(node, &FlagSpec{
Name: "-c",
})
node.Children["--config"] = NewAutoCompleteFlagNode(node, &FlagSpec{
Name: "--config",
})
node.Children["-D"] = NewAutoCompleteFlagNode(node, &FlagSpec{
Name: "-D",
})
node.Children["--debug"] = NewAutoCompleteFlagNode(node, &FlagSpec{
Name: "--debug",
})
node.Children["-h"] = NewAutoCompleteFlagNode(node, &FlagSpec{
Name: "-h",
})
node.Children["--help"] = NewAutoCompleteFlagNode(node, &FlagSpec{
Name: "--help",
})
node.Children["-o"] = NewAutoCompleteFlagNode(node, &FlagSpec{
Name: "-o",
EnumValues: printerTypes,
})
node.Children["--output"] = NewAutoCompleteFlagNode(node, &FlagSpec{
Name: "--output",
EnumValues: printerTypes,
})
node.Children["-p"] = NewAutoCompleteFlagNode(node, &FlagSpec{
Name: "-p",
HasVariableValue: true,
})
node.Children["--profile"] = NewAutoCompleteFlagNode(node, &FlagSpec{
Name: "--profile",
HasVariableValue: true,
})
func (node *AutoCompleteNode) addFlags(flags []FlagSpec) {
for i := range flags {
flag := &flags[i]
node.Children[flag.Name] = NewAutoCompleteFlagNode(node, flag)
}
}

// newAutoCompleteResponse builds a new AutocompleteResponse
Expand All @@ -111,11 +74,13 @@ func newAutoCompleteResponse(suggestions []string) *AutocompleteResponse {

// NewAutoCompleteCommandNode creates a new node corresponding to a command or subcommand.
// These nodes are not necessarily leaf nodes.
func NewAutoCompleteCommandNode() *AutoCompleteNode {
return &AutoCompleteNode{
Children: make(map[string]*AutoCompleteNode),
func NewAutoCompleteCommandNode(flags []FlagSpec) *AutoCompleteNode {
node := &AutoCompleteNode{
Children: make(map[string]*AutoCompleteNode, len(flags)),
Type: AutoCompleteNodeTypeCommand,
}
node.addFlags(flags)
return node
}

// NewAutoCompleteArgNode creates a new node corresponding to a command argument.
Expand All @@ -136,10 +101,18 @@ func NewAutoCompleteArgNode(cmd *Command, argSpec *ArgSpec) *AutoCompleteNode {
// or the lowest nodes are the possible values if the exist.
func NewAutoCompleteFlagNode(parent *AutoCompleteNode, flagSpec *FlagSpec) *AutoCompleteNode {
node := &AutoCompleteNode{
Children: make(map[string]*AutoCompleteNode),
Type: AutoCompleteNodeTypeFlag,
Name: flagSpec.Name,
Type: AutoCompleteNodeTypeFlag,
Name: flagSpec.Name,
}
childrenCount := len(flagSpec.EnumValues)
if flagSpec.HasVariableValue {
childrenCount++
}

if childrenCount > 0 {
node.Children = make(map[string]*AutoCompleteNode, childrenCount)
}

if flagSpec.HasVariableValue {
node.Children[positionalValueNodeID] = &AutoCompleteNode{
Children: parent.Children,
Expand All @@ -161,9 +134,9 @@ func NewAutoCompleteFlagNode(parent *AutoCompleteNode, flagSpec *FlagSpec) *Auto
// GetChildOrCreate search a child node by name,
// and either returns it if found
// or create new children with the given name and aliases, and returns it.
func (node *AutoCompleteNode) GetChildOrCreate(name string, aliases []string) *AutoCompleteNode {
func (node *AutoCompleteNode) GetChildOrCreate(name string, aliases []string, flags []FlagSpec) *AutoCompleteNode {
if _, exist := node.Children[name]; !exist {
childNode := NewAutoCompleteCommandNode()
childNode := NewAutoCompleteCommandNode(flags)
node.Children[name] = childNode
for _, alias := range aliases {
node.Children[alias] = childNode
Expand Down Expand Up @@ -209,17 +182,16 @@ func (node *AutoCompleteNode) isLeafCommand() bool {
}

// BuildAutoCompleteTree builds the autocomplete tree from the commands, subcommands and arguments
func BuildAutoCompleteTree(commands *Commands) *AutoCompleteNode {
root := NewAutoCompleteCommandNode()
root.addGlobalFlags()
func BuildAutoCompleteTree(ctx context.Context, commands *Commands) *AutoCompleteNode {
globalFlags := getGlobalFlags(ctx)
root := NewAutoCompleteCommandNode(globalFlags)
for _, cmd := range commands.commands {
node := root

// Creates nodes for namespaces, resources, verbs
for _, part := range []string{cmd.Namespace, cmd.Resource, cmd.Verb} {
if part != "" {
node = node.GetChildOrCreate(part, cmd.Aliases)
node.addGlobalFlags()
node = node.GetChildOrCreate(part, cmd.Aliases, globalFlags)
}
}

Expand Down Expand Up @@ -256,7 +228,7 @@ func AutoComplete(ctx context.Context, leftWords []string, wordToComplete string
commands := ExtractCommands(ctx)

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

// 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 Down
35 changes: 35 additions & 0 deletions internal/core/autocomplete_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"strings"
"testing"

"github.com/scaleway/scaleway-sdk-go/scw"
"github.com/stretchr/testify/assert"
)

Expand Down Expand Up @@ -78,6 +79,12 @@ func runAutocompleteTest(ctx context.Context, tc *autoCompleteTestCase) func(*te
if len(words) == 0 {
name := strings.Replace(t.Name(), "TestAutocomplete/", "", -1)
name = strings.Replace(name, "_", " ", -1)
// Test can contain a sharp if duplicated
// MyTest/scw_-flag_#01
sharpIndex := strings.Index(name, "#")
if sharpIndex != -1 {
name = name[:sharpIndex]
}
words = strings.Split(name, " ")
}

Expand Down Expand Up @@ -252,3 +259,31 @@ func TestAutocompleteArgs(t *testing.T) {
t.Run("scw test flower get material-name=mat ", run(&testCase{Suggestions: AutocompleteSuggestions{"flower1", "flower2"}}))
t.Run("scw test flower create name=", run(&testCase{Suggestions: AutocompleteSuggestions(nil)}))
}

func TestAutocompleteProfiles(t *testing.T) {
commands := testAutocompleteGetCommands()
ctx := injectMeta(context.Background(), &meta{
Commands: commands,
betaMode: true,
})

type testCase = autoCompleteTestCase

run := func(tc *testCase) func(*testing.T) {
return runAutocompleteTest(ctx, tc)
}
t.Run("scw -p ", run(&testCase{Suggestions: nil}))
t.Run("scw test -p ", run(&testCase{Suggestions: nil}))
t.Run("scw test flower --profile ", run(&testCase{Suggestions: nil}))

injectConfig(ctx, &scw.Config{
Profiles: map[string]*scw.Profile{
"p1": nil,
"p2": nil,
},
})

t.Run("scw -p ", run(&testCase{Suggestions: AutocompleteSuggestions{"p1", "p2"}}))
t.Run("scw test -p ", run(&testCase{Suggestions: AutocompleteSuggestions{"p1", "p2"}}))
t.Run("scw test flower --profile ", run(&testCase{Suggestions: AutocompleteSuggestions{"p1", "p2"}}))
}
44 changes: 44 additions & 0 deletions internal/core/autocomplete_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,50 @@ import (

var autoCompleteCache *cache.Cache

// getGlobalFlags returns the list of flags that should be added to all commands
func getGlobalFlags(ctx context.Context) []FlagSpec {
printerTypes := []string{
PrinterTypeHuman.String(),
PrinterTypeJSON.String(),
PrinterTypeYAML.String(),
PrinterTypeTemplate.String(),
}
profiles := []string(nil)
cfg := extractConfig(ctx)
if cfg != nil {
for profile := range cfg.Profiles {
profiles = append(profiles, profile)
}
}

return []FlagSpec{
{Name: "-c"},
{Name: "--config"},
{Name: "-D"},
{Name: "--debug"},
{Name: "-h"},
{Name: "--help"},
{
Name: "-o",
EnumValues: printerTypes,
},
{
Name: "--output",
EnumValues: printerTypes,
},
{
Name: "-p",
HasVariableValue: true,
EnumValues: profiles,
},
{
Name: "--profile",
HasVariableValue: true,
EnumValues: profiles,
},
}
}

func AutocompleteProfileName() AutoCompleteArgFunc {
return func(ctx context.Context, prefix string) AutocompleteSuggestions {
res := AutocompleteSuggestions(nil)
Expand Down
3 changes: 3 additions & 0 deletions internal/core/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ func createClient(ctx context.Context, httpClient *http.Client, buildInfo *Build
return nil, err

default:
// Store latest version of config in context
injectConfig(ctx, config)

// found and loaded a config file -> merge with env
activeProfile, err := config.GetProfile(profileName)
if err != nil {
Expand Down
10 changes: 10 additions & 0 deletions internal/core/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ type meta struct {
stdin io.Reader
result interface{}
httpClient *http.Client
config *scw.Config
isClientFromBootstrapConfig bool
betaMode bool
}
Expand All @@ -53,6 +54,15 @@ func extractMeta(ctx context.Context) *meta {
return ctx.Value(metaContextKey).(*meta)
}

// injectSDKConfig add config to a meta context
func injectConfig(ctx context.Context, config *scw.Config) {
extractMeta(ctx).config = config
}

func extractConfig(ctx context.Context) *scw.Config {
return extractMeta(ctx).config
}

func ExtractCommands(ctx context.Context) *Commands {
return extractMeta(ctx).Commands
}
Expand Down

0 comments on commit 2dfca5d

Please sign in to comment.