shell completion --format: work with nil structs
AutocompleteFormat() takes the format struct as argument. Often the structs are deeply nested and contain other structs. Up until now if there was a pointer to a struct the logic was not able to get the field names from that, simply because the pointer was nil. However it is possible to create a new initialized type with reflect.New(). This allows us to complete all struct fields/functions even when there nil pointers. Therefore we can drop the extra initialization which was done by some callers. Signed-off-by: Paul Holzinger <pholzing@redhat.com>
This commit is contained in:
parent
2b8cafc067
commit
f93ba587c6
|
|
@ -987,14 +987,12 @@ func AutocompleteFormat(o interface{}) func(cmd *cobra.Command, args []string, t
|
|||
fields := strings.Split(field[len(field)-1], ".")
|
||||
f := reflect.ValueOf(o)
|
||||
for i := 1; i < len(fields); i++ {
|
||||
if f.Kind() == reflect.Ptr {
|
||||
f = f.Elem()
|
||||
}
|
||||
|
||||
// the only supported type is struct
|
||||
if f.Kind() != reflect.Struct {
|
||||
val := getActualStructType(f)
|
||||
if val == nil {
|
||||
// no struct return nothing to complete
|
||||
return nil, cobra.ShellCompDirectiveNoFileComp
|
||||
}
|
||||
f = *val
|
||||
|
||||
// last field get all names to suggest
|
||||
if i == len(fields)-1 {
|
||||
|
|
@ -1012,17 +1010,38 @@ func AutocompleteFormat(o interface{}) func(cmd *cobra.Command, args []string, t
|
|||
}
|
||||
}
|
||||
|
||||
// getStructFields reads all struct field names and method names and returns them.
|
||||
func getStructFields(f reflect.Value, prefix string) []string {
|
||||
suggestions := []string{}
|
||||
// getActualStructType take the value and check if it is a struct,
|
||||
// if it is pointer it will dereference it and when it is nil,
|
||||
// it will create a new value from it to get the actual struct
|
||||
// returns nil when type is not a struct
|
||||
func getActualStructType(f reflect.Value) *reflect.Value {
|
||||
// follow the pointer first
|
||||
if f.Kind() == reflect.Ptr {
|
||||
// if the pointer is nil we create a new value from the elements type
|
||||
// this allows us to follow nil pointers and get the actual struct fields
|
||||
if f.IsNil() {
|
||||
f = reflect.New(f.Type().Elem())
|
||||
}
|
||||
f = f.Elem()
|
||||
}
|
||||
// we only support structs
|
||||
if f.Kind() != reflect.Struct {
|
||||
return nil
|
||||
}
|
||||
return &f
|
||||
}
|
||||
|
||||
// getStructFields reads all struct field names and method names and returns them.
|
||||
func getStructFields(f reflect.Value, prefix string) []string {
|
||||
suggestions := []string{}
|
||||
|
||||
val := getActualStructType(f)
|
||||
if val == nil {
|
||||
// no struct return nothing to complete
|
||||
return nil
|
||||
}
|
||||
f = *val
|
||||
|
||||
// loop over all field names
|
||||
for j := 0; j < f.NumField(); j++ {
|
||||
field := f.Type().Field(j)
|
||||
|
|
@ -1043,7 +1062,7 @@ 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.FieldByIndex([]int{j}), prefix)...)
|
||||
suggestions = append(suggestions, getStructFields(f.Field(j), prefix)...)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -34,10 +34,9 @@ func TestAutocompleteFormat(t *testing.T) {
|
|||
Name string
|
||||
Age int
|
||||
Car *Car
|
||||
Car2 *Car
|
||||
*Anonymous
|
||||
}{
|
||||
Anonymous: &Anonymous{},
|
||||
}
|
||||
}{}
|
||||
|
||||
testStruct.Car = &Car{}
|
||||
testStruct.Car.Extras = map[string]string{"test": "1"}
|
||||
|
|
@ -80,12 +79,12 @@ func TestAutocompleteFormat(t *testing.T) {
|
|||
{
|
||||
"fist level struct field name",
|
||||
"{{.",
|
||||
[]string{"{{.Name}}", "{{.Age}}", "{{.Car.", "{{.Anonymous.", "{{.Hello}}"},
|
||||
[]string{"{{.Name}}", "{{.Age}}", "{{.Car.", "{{.Car2.", "{{.Anonymous.", "{{.Hello}}"},
|
||||
},
|
||||
{
|
||||
"fist level struct field name",
|
||||
"{{ .",
|
||||
[]string{"{{ .Name}}", "{{ .Age}}", "{{ .Car.", "{{ .Anonymous.", "{{ .Hello}}"},
|
||||
[]string{"{{ .Name}}", "{{ .Age}}", "{{ .Car.", "{{ .Car2.", "{{ .Anonymous.", "{{ .Hello}}"},
|
||||
},
|
||||
{
|
||||
"fist level struct field name",
|
||||
|
|
@ -102,6 +101,11 @@ func TestAutocompleteFormat(t *testing.T) {
|
|||
"{{ .Car.B",
|
||||
[]string{"{{ .Car.Brand}}"},
|
||||
},
|
||||
{
|
||||
"second level nil struct field name",
|
||||
"{{ .Car2.",
|
||||
[]string{"{{ .Car2.Brand}}", "{{ .Car2.Stats.", "{{ .Car2.Extras}}", "{{ .Car2.Color}}", "{{ .Car2.Type}}"},
|
||||
},
|
||||
{
|
||||
"three level struct field name",
|
||||
"{{ .Car.Stats.",
|
||||
|
|
|
|||
|
|
@ -35,12 +35,7 @@ func init() {
|
|||
|
||||
formatFlagName := "format"
|
||||
flags.StringVarP(&inspectOpts.Format, formatFlagName, "f", "json", "Format the output to a Go template or json")
|
||||
_ = inspectCmd.RegisterFlagCompletionFunc(formatFlagName, common.AutocompleteFormat(define.InspectContainerData{
|
||||
State: &define.InspectContainerState{},
|
||||
NetworkSettings: &define.InspectNetworkSettings{},
|
||||
Config: &define.InspectContainerConfig{},
|
||||
HostConfig: &define.InspectContainerHostConfig{},
|
||||
}))
|
||||
_ = inspectCmd.RegisterFlagCompletionFunc(formatFlagName, common.AutocompleteFormat(define.InspectContainerData{}))
|
||||
|
||||
validate.AddLatestFlag(inspectCmd, &inspectOpts.Latest)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ func init() {
|
|||
|
||||
formatFlagName := "format"
|
||||
flags.StringVar(&psInput.Format, formatFlagName, "", "Pretty-print pods to JSON or using a Go template")
|
||||
_ = psCmd.RegisterFlagCompletionFunc(formatFlagName, common.AutocompleteFormat(ListPodReporter{ListPodsReport: &entities.ListPodsReport{}}))
|
||||
_ = psCmd.RegisterFlagCompletionFunc(formatFlagName, common.AutocompleteFormat(ListPodReporter{}))
|
||||
|
||||
flags.Bool("noheading", false, "Do not print headers")
|
||||
flags.BoolVar(&psInput.Namespace, "namespace", false, "Display namespace information of the pod")
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@ func infoFlags(cmd *cobra.Command) {
|
|||
|
||||
formatFlagName := "format"
|
||||
flags.StringVarP(&inFormat, formatFlagName, "f", "", "Change the output format to JSON or a Go template")
|
||||
_ = cmd.RegisterFlagCompletionFunc(formatFlagName, common.AutocompleteFormat(define.Info{Host: &define.HostInfo{}, Store: &define.StoreInfo{}}))
|
||||
_ = cmd.RegisterFlagCompletionFunc(formatFlagName, common.AutocompleteFormat(define.Info{}))
|
||||
}
|
||||
|
||||
func info(cmd *cobra.Command, args []string) error {
|
||||
|
|
|
|||
Loading…
Reference in New Issue