Skip to content

feat(autocomplete): global flag profile values #3036

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

Merged
merged 3 commits into from
Apr 18, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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