mirror of
https://github.com/containers/podman.git
synced 2025-06-23 18:59:30 +08:00
shell completion --format: fix embedded struct handling
When a struct is embeeded it is possible that we end up with same names but different types, this results in incorrect completions. The go template logic always preferes the actual field/method name before the one from the embedded one. Thefore the completion logic should do the same. First get all method/fields names from the struct and then only add the field names from the embedded struct when they are not already present in the list. Signed-off-by: Paul Holzinger <pholzing@redhat.com>
This commit is contained in:
@ -960,6 +960,19 @@ func AutocompleteNetworkFlag(cmd *cobra.Command, args []string, toComplete strin
|
||||
return append(networks, suggestions...), dir
|
||||
}
|
||||
|
||||
type formatSuggestion struct {
|
||||
fieldname string
|
||||
suffix string
|
||||
}
|
||||
|
||||
func convertFormatSuggestions(suggestions []formatSuggestion) []string {
|
||||
completions := make([]string, 0, len(suggestions))
|
||||
for _, f := range suggestions {
|
||||
completions = append(completions, f.fieldname+f.suffix)
|
||||
}
|
||||
return completions
|
||||
}
|
||||
|
||||
// AutocompleteFormat - Autocomplete json or a given struct to use for a go template.
|
||||
// The input can be nil, In this case only json will be autocompleted.
|
||||
// This function will only work for structs other types are not supported.
|
||||
@ -999,7 +1012,7 @@ func AutocompleteFormat(o interface{}) func(cmd *cobra.Command, args []string, t
|
||||
toCompArr := strings.Split(toComplete, ".")
|
||||
toCompArr[len(toCompArr)-1] = ""
|
||||
toComplete = strings.Join(toCompArr, ".")
|
||||
return prefixSlice(toComplete, suggestions), cobra.ShellCompDirectiveNoSpace | cobra.ShellCompDirectiveNoFileComp
|
||||
return prefixSlice(toComplete, convertFormatSuggestions(suggestions)), cobra.ShellCompDirectiveNoSpace | cobra.ShellCompDirectiveNoFileComp
|
||||
}
|
||||
|
||||
val := getActualStructType(f)
|
||||
@ -1038,8 +1051,8 @@ func getActualStructType(f reflect.Value) *reflect.Value {
|
||||
}
|
||||
|
||||
// getStructFields reads all struct field names and method names and returns them.
|
||||
func getStructFields(f reflect.Value, prefix string) []string {
|
||||
var suggestions []string
|
||||
func getStructFields(f reflect.Value, prefix string) []formatSuggestion {
|
||||
var suggestions []formatSuggestion
|
||||
if f.IsValid() {
|
||||
suggestions = append(suggestions, getMethodNames(f, prefix)...)
|
||||
}
|
||||
@ -1051,6 +1064,7 @@ func getStructFields(f reflect.Value, prefix string) []string {
|
||||
}
|
||||
f = *val
|
||||
|
||||
var anonymous []formatSuggestion
|
||||
// loop over all field names
|
||||
for j := 0; j < f.NumField(); j++ {
|
||||
field := f.Type().Field(j)
|
||||
@ -1072,17 +1086,28 @@ func getStructFields(f reflect.Value, prefix string) []string {
|
||||
}
|
||||
// if field is anonymous add the child fields as well
|
||||
if field.Anonymous {
|
||||
suggestions = append(suggestions, getStructFields(f.Field(j), prefix)...)
|
||||
} else if strings.HasPrefix(fname, prefix) {
|
||||
// add field name with suffix
|
||||
suggestions = append(suggestions, fname+suffix)
|
||||
anonymous = append(anonymous, getStructFields(f.Field(j), prefix)...)
|
||||
}
|
||||
if strings.HasPrefix(fname, prefix) {
|
||||
// add field name with suffix
|
||||
suggestions = append(suggestions, formatSuggestion{fieldname: fname, suffix: suffix})
|
||||
}
|
||||
}
|
||||
outer:
|
||||
for _, ano := range anonymous {
|
||||
// we should only add anonymous child fields if they are not already present.
|
||||
for _, sug := range suggestions {
|
||||
if ano.fieldname == sug.fieldname {
|
||||
continue outer
|
||||
}
|
||||
}
|
||||
suggestions = append(suggestions, ano)
|
||||
}
|
||||
return suggestions
|
||||
}
|
||||
|
||||
func getMethodNames(f reflect.Value, prefix string) []string {
|
||||
suggestions := make([]string, 0, f.NumMethod())
|
||||
func getMethodNames(f reflect.Value, prefix string) []formatSuggestion {
|
||||
suggestions := make([]formatSuggestion, 0, f.NumMethod())
|
||||
for j := 0; j < f.NumMethod(); j++ {
|
||||
method := f.Type().Method(j)
|
||||
// in a template we can only run functions with one return value
|
||||
@ -1092,7 +1117,7 @@ func getMethodNames(f reflect.Value, prefix string) []string {
|
||||
fname := method.Name
|
||||
if strings.HasPrefix(fname, prefix) {
|
||||
// add method name with closing braces
|
||||
suggestions = append(suggestions, fname+"}}")
|
||||
suggestions = append(suggestions, formatSuggestion{fieldname: fname, suffix: "}}"})
|
||||
}
|
||||
}
|
||||
return suggestions
|
||||
|
@ -19,6 +19,17 @@ type Car struct {
|
||||
|
||||
type Anonymous struct {
|
||||
Hello string
|
||||
// The name should match the testStruct Name below. This is used to make
|
||||
// sure the logic uses the actual struct fields before the embedded ones.
|
||||
Name struct {
|
||||
Suffix string
|
||||
Prefix string
|
||||
}
|
||||
}
|
||||
|
||||
// The name should match the testStruct Age name below.
|
||||
func (a Anonymous) Age() int {
|
||||
return 0
|
||||
}
|
||||
|
||||
func (c Car) Type() string {
|
||||
@ -87,17 +98,17 @@ func TestAutocompleteFormat(t *testing.T) {
|
||||
{
|
||||
"invalid completion",
|
||||
"{{ ..",
|
||||
nil,
|
||||
[]string{},
|
||||
},
|
||||
{
|
||||
"fist level struct field name",
|
||||
"{{.",
|
||||
[]string{"{{.Name}}", "{{.Age}}", "{{.Car.", "{{.Car2.", "{{.Hello}}"},
|
||||
[]string{"{{.Name}}", "{{.Age}}", "{{.Car.", "{{.Car2.", "{{.Anonymous.", "{{.Hello}}"},
|
||||
},
|
||||
{
|
||||
"fist level struct field name",
|
||||
"{{ .",
|
||||
[]string{"{{ .Name}}", "{{ .Age}}", "{{ .Car.", "{{ .Car2.", "{{ .Hello}}"},
|
||||
[]string{"{{ .Name}}", "{{ .Age}}", "{{ .Car.", "{{ .Car2.", "{{ .Anonymous.", "{{ .Hello}}"},
|
||||
},
|
||||
{
|
||||
"fist level struct field name",
|
||||
@ -137,12 +148,12 @@ func TestAutocompleteFormat(t *testing.T) {
|
||||
{
|
||||
"invalid field name",
|
||||
"{{ .Ca.B",
|
||||
nil,
|
||||
[]string{},
|
||||
},
|
||||
{
|
||||
"map key names don't work",
|
||||
"{{ .Car.Extras.",
|
||||
nil,
|
||||
[]string{},
|
||||
},
|
||||
{
|
||||
"two variables struct field name",
|
||||
|
Reference in New Issue
Block a user