mirror of
https://github.com/containers/podman.git
synced 2025-06-24 03:08:13 +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
|
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.
|
// 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.
|
// 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.
|
// 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 := strings.Split(toComplete, ".")
|
||||||
toCompArr[len(toCompArr)-1] = ""
|
toCompArr[len(toCompArr)-1] = ""
|
||||||
toComplete = strings.Join(toCompArr, ".")
|
toComplete = strings.Join(toCompArr, ".")
|
||||||
return prefixSlice(toComplete, suggestions), cobra.ShellCompDirectiveNoSpace | cobra.ShellCompDirectiveNoFileComp
|
return prefixSlice(toComplete, convertFormatSuggestions(suggestions)), cobra.ShellCompDirectiveNoSpace | cobra.ShellCompDirectiveNoFileComp
|
||||||
}
|
}
|
||||||
|
|
||||||
val := getActualStructType(f)
|
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.
|
// getStructFields reads all struct field names and method names and returns them.
|
||||||
func getStructFields(f reflect.Value, prefix string) []string {
|
func getStructFields(f reflect.Value, prefix string) []formatSuggestion {
|
||||||
var suggestions []string
|
var suggestions []formatSuggestion
|
||||||
if f.IsValid() {
|
if f.IsValid() {
|
||||||
suggestions = append(suggestions, getMethodNames(f, prefix)...)
|
suggestions = append(suggestions, getMethodNames(f, prefix)...)
|
||||||
}
|
}
|
||||||
@ -1051,6 +1064,7 @@ func getStructFields(f reflect.Value, prefix string) []string {
|
|||||||
}
|
}
|
||||||
f = *val
|
f = *val
|
||||||
|
|
||||||
|
var anonymous []formatSuggestion
|
||||||
// loop over all field names
|
// loop over all field names
|
||||||
for j := 0; j < f.NumField(); j++ {
|
for j := 0; j < f.NumField(); j++ {
|
||||||
field := f.Type().Field(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 is anonymous add the child fields as well
|
||||||
if field.Anonymous {
|
if field.Anonymous {
|
||||||
suggestions = append(suggestions, getStructFields(f.Field(j), prefix)...)
|
anonymous = append(anonymous, getStructFields(f.Field(j), prefix)...)
|
||||||
} else if strings.HasPrefix(fname, prefix) {
|
|
||||||
// add field name with suffix
|
|
||||||
suggestions = append(suggestions, fname+suffix)
|
|
||||||
}
|
}
|
||||||
|
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
|
return suggestions
|
||||||
}
|
}
|
||||||
|
|
||||||
func getMethodNames(f reflect.Value, prefix string) []string {
|
func getMethodNames(f reflect.Value, prefix string) []formatSuggestion {
|
||||||
suggestions := make([]string, 0, f.NumMethod())
|
suggestions := make([]formatSuggestion, 0, f.NumMethod())
|
||||||
for j := 0; j < f.NumMethod(); j++ {
|
for j := 0; j < f.NumMethod(); j++ {
|
||||||
method := f.Type().Method(j)
|
method := f.Type().Method(j)
|
||||||
// in a template we can only run functions with one return value
|
// 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
|
fname := method.Name
|
||||||
if strings.HasPrefix(fname, prefix) {
|
if strings.HasPrefix(fname, prefix) {
|
||||||
// add method name with closing braces
|
// add method name with closing braces
|
||||||
suggestions = append(suggestions, fname+"}}")
|
suggestions = append(suggestions, formatSuggestion{fieldname: fname, suffix: "}}"})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return suggestions
|
return suggestions
|
||||||
|
@ -19,6 +19,17 @@ type Car struct {
|
|||||||
|
|
||||||
type Anonymous struct {
|
type Anonymous struct {
|
||||||
Hello string
|
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 {
|
func (c Car) Type() string {
|
||||||
@ -87,17 +98,17 @@ func TestAutocompleteFormat(t *testing.T) {
|
|||||||
{
|
{
|
||||||
"invalid completion",
|
"invalid completion",
|
||||||
"{{ ..",
|
"{{ ..",
|
||||||
nil,
|
[]string{},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fist level struct field name",
|
"fist level struct field name",
|
||||||
"{{.",
|
"{{.",
|
||||||
[]string{"{{.Name}}", "{{.Age}}", "{{.Car.", "{{.Car2.", "{{.Hello}}"},
|
[]string{"{{.Name}}", "{{.Age}}", "{{.Car.", "{{.Car2.", "{{.Anonymous.", "{{.Hello}}"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fist level struct field name",
|
"fist level struct field name",
|
||||||
"{{ .",
|
"{{ .",
|
||||||
[]string{"{{ .Name}}", "{{ .Age}}", "{{ .Car.", "{{ .Car2.", "{{ .Hello}}"},
|
[]string{"{{ .Name}}", "{{ .Age}}", "{{ .Car.", "{{ .Car2.", "{{ .Anonymous.", "{{ .Hello}}"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fist level struct field name",
|
"fist level struct field name",
|
||||||
@ -137,12 +148,12 @@ func TestAutocompleteFormat(t *testing.T) {
|
|||||||
{
|
{
|
||||||
"invalid field name",
|
"invalid field name",
|
||||||
"{{ .Ca.B",
|
"{{ .Ca.B",
|
||||||
nil,
|
[]string{},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"map key names don't work",
|
"map key names don't work",
|
||||||
"{{ .Car.Extras.",
|
"{{ .Car.Extras.",
|
||||||
nil,
|
[]string{},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"two variables struct field name",
|
"two variables struct field name",
|
||||||
|
Reference in New Issue
Block a user