Skip to content

Commit

Permalink
Shell completion for --format with anonymous fields
Browse files Browse the repository at this point in the history
In commit d81021e 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 <[email protected]>
  • Loading branch information
Luap99 committed Aug 27, 2021
1 parent 94c37d7 commit ab6c43f
Show file tree
Hide file tree
Showing 3 changed files with 59 additions and 32 deletions.
76 changes: 48 additions & 28 deletions cmd/podman/common/completion.go
Original file line number Diff line number Diff line change
Expand Up @@ -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] = ""
Expand All @@ -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) {
Expand Down
13 changes: 10 additions & 3 deletions cmd/podman/common/completion_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ type Car struct {
Extras map[string]string
}

type Anonymous struct {
Hello string
}

func (c Car) Type() string {
return ""
}
Expand All @@ -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"}
Expand Down Expand Up @@ -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",
Expand Down
2 changes: 1 addition & 1 deletion cmd/podman/pods/ps.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down

0 comments on commit ab6c43f

Please sign in to comment.