From ab6c43f3e072d088149c4f5371e0e53ff14ec7ff Mon Sep 17 00:00:00 2001 From: Paul Holzinger Date: Thu, 26 Aug 2021 21:44:53 +0200 Subject: Shell completion for --format with anonymous fields In commit d81021ed265e I introduced shell completion for the `--format` flag. This is a very nice way to complete go template field names. However it did not work correct for anonymous fields. In this case the child fields can be accessed directly from the parent. For example: ``` type Anonymous struct { Field1 string Field2 string ... } type MyType struct { Anonymous } var s = MyType{} ``` Now if you want to access a field from the Anonymous struct you can just do `s.Field1`. The same is allowed for go templates, using `{{.Field1}}` should work. This commit adds this functionality, if the field is anonymous read the child field names recursively and add them to the suggestions. Signed-off-by: Paul Holzinger --- cmd/podman/common/completion.go | 76 +++++++++++++++++++++++------------- cmd/podman/common/completion_test.go | 13 ++++-- cmd/podman/pods/ps.go | 2 +- 3 files changed, 59 insertions(+), 32 deletions(-) (limited to 'cmd') diff --git a/cmd/podman/common/completion.go b/cmd/podman/common/completion.go index 9a4524b46..3966606e3 100644 --- a/cmd/podman/common/completion.go +++ b/cmd/podman/common/completion.go @@ -985,40 +985,14 @@ func AutocompleteFormat(o interface{}) func(cmd *cobra.Command, args []string, t f = f.Elem() } - // // the only supported type is struct + // the only supported type is struct if f.Kind() != reflect.Struct { return nil, cobra.ShellCompDirectiveNoFileComp } // last field get all names to suggest if i == len(fields)-1 { - suggestions := []string{} - for j := 0; j < f.NumField(); j++ { - fname := f.Type().Field(j).Name - suffix := "}}" - kind := f.Type().Field(j).Type.Kind() - if kind == reflect.Ptr { - // make sure to read the actual type when it is a pointer - kind = f.Type().Field(j).Type.Elem().Kind() - } - // when we have a nested struct do not append braces instead append a dot - if kind == reflect.Struct { - suffix = "." - } - if strings.HasPrefix(fname, fields[i]) { - // add field name with closing braces - suggestions = append(suggestions, fname+suffix) - } - } - - for j := 0; j < f.NumMethod(); j++ { - fname := f.Type().Method(j).Name - if strings.HasPrefix(fname, fields[i]) { - // add method name with closing braces - suggestions = append(suggestions, fname+"}}") - } - } - + suggestions := getStructFields(f, fields[i]) // add the current toComplete value in front so that the shell can complete this correctly toCompArr := strings.Split(toComplete, ".") toCompArr[len(toCompArr)-1] = "" @@ -1032,6 +1006,52 @@ 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{} + // follow the pointer first + if f.Kind() == reflect.Ptr { + f = f.Elem() + } + // we only support structs + if f.Kind() != reflect.Struct { + return nil + } + // loop over all field names + for j := 0; j < f.NumField(); j++ { + field := f.Type().Field(j) + fname := field.Name + suffix := "}}" + kind := field.Type.Kind() + if kind == reflect.Ptr { + // make sure to read the actual type when it is a pointer + kind = field.Type.Elem().Kind() + } + // when we have a nested struct do not append braces instead append a dot + if kind == reflect.Struct { + suffix = "." + } + if strings.HasPrefix(fname, prefix) { + // add field name with suffix + suggestions = append(suggestions, fname+suffix) + } + // if field is anonymous add the child fields as well + if field.Anonymous { + suggestions = append(suggestions, getStructFields(f.FieldByIndex([]int{j}), prefix)...) + } + } + + for j := 0; j < f.NumMethod(); j++ { + fname := f.Type().Method(j).Name + if strings.HasPrefix(fname, prefix) { + // add method name with closing braces + suggestions = append(suggestions, fname+"}}") + } + } + + return suggestions +} + // AutocompleteEventFilter - Autocomplete event filter flag options. // -> "container=", "event=", "image=", "pod=", "volume=", "type=" func AutocompleteEventFilter(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { diff --git a/cmd/podman/common/completion_test.go b/cmd/podman/common/completion_test.go index 5bd627b85..84b3c1132 100644 --- a/cmd/podman/common/completion_test.go +++ b/cmd/podman/common/completion_test.go @@ -17,6 +17,10 @@ type Car struct { Extras map[string]string } +type Anonymous struct { + Hello string +} + func (c Car) Type() string { return "" } @@ -30,7 +34,10 @@ func TestAutocompleteFormat(t *testing.T) { Name string Age int Car *Car - }{} + *Anonymous + }{ + Anonymous: &Anonymous{}, + } testStruct.Car = &Car{} testStruct.Car.Extras = map[string]string{"test": "1"} @@ -73,12 +80,12 @@ func TestAutocompleteFormat(t *testing.T) { { "fist level struct field name", "{{.", - []string{"{{.Name}}", "{{.Age}}", "{{.Car."}, + []string{"{{.Name}}", "{{.Age}}", "{{.Car.", "{{.Anonymous.", "{{.Hello}}"}, }, { "fist level struct field name", "{{ .", - []string{"{{ .Name}}", "{{ .Age}}", "{{ .Car."}, + []string{"{{ .Name}}", "{{ .Age}}", "{{ .Car.", "{{ .Anonymous.", "{{ .Hello}}"}, }, { "fist level struct field name", diff --git a/cmd/podman/pods/ps.go b/cmd/podman/pods/ps.go index 14e3e2ea9..60aadf224 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{})) + _ = psCmd.RegisterFlagCompletionFunc(formatFlagName, common.AutocompleteFormat(ListPodReporter{ListPodsReport: &entities.ListPodsReport{}})) flags.Bool("noheading", false, "Do not print headers") flags.BoolVar(&psInput.Namespace, "namespace", false, "Display namespace information of the pod") -- cgit v1.2.3-54-g00ecf