From f93ba587c608e2678b176f1460b32e8a144edf1a Mon Sep 17 00:00:00 2001 From: Paul Holzinger Date: Thu, 28 Apr 2022 14:49:03 +0200 Subject: 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 --- cmd/podman/common/completion.go | 39 +++++++++++++++++++++++++++--------- cmd/podman/common/completion_test.go | 14 ++++++++----- cmd/podman/containers/inspect.go | 7 +------ cmd/podman/pods/ps.go | 2 +- cmd/podman/system/info.go | 2 +- 5 files changed, 41 insertions(+), 23 deletions(-) (limited to 'cmd/podman') diff --git a/cmd/podman/common/completion.go b/cmd/podman/common/completion.go index c7d5d6d60..6f13eb5a2 100644 --- a/cmd/podman/common/completion.go +++ b/cmd/podman/common/completion.go @@ -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)...) } } diff --git a/cmd/podman/common/completion_test.go b/cmd/podman/common/completion_test.go index d28ac3928..bfff9a96e 100644 --- a/cmd/podman/common/completion_test.go +++ b/cmd/podman/common/completion_test.go @@ -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.", diff --git a/cmd/podman/containers/inspect.go b/cmd/podman/containers/inspect.go index 8c219b67c..9c4549e38 100644 --- a/cmd/podman/containers/inspect.go +++ b/cmd/podman/containers/inspect.go @@ -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) } diff --git a/cmd/podman/pods/ps.go b/cmd/podman/pods/ps.go index 4a049541a..8cbf7b0ba 100644 --- a/cmd/podman/pods/ps.go +++ b/cmd/podman/pods/ps.go @@ -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") diff --git a/cmd/podman/system/info.go b/cmd/podman/system/info.go index e95e9336d..87ceaa7cd 100644 --- a/cmd/podman/system/info.go +++ b/cmd/podman/system/info.go @@ -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 { -- cgit v1.2.3-54-g00ecf