addFieldsImpl is the underlying implementation of [addFields]. The path is the current path state, the cmdPath is the current path state without command-associated names, and usedNames is a map keyed by used CamelCase names with values of their associated fields, used to track naming conflicts. The
(obj any, path string, cmdPath string, allFields *fields, usedNames map[string]*field, cmd string)
| 60 | // the "cmd" struct tag, as the user already knows what command they are |
| 61 | // running, so they do not need that duplicated specificity for every flag. |
| 62 | func addFieldsImpl(obj any, path string, cmdPath string, allFields *fields, usedNames map[string]*field, cmd string) { |
| 63 | if reflectx.AnyIsNil(obj) { |
| 64 | return |
| 65 | } |
| 66 | ov := reflect.ValueOf(obj) |
| 67 | if ov.Kind() == reflect.Pointer && ov.IsNil() { |
| 68 | return |
| 69 | } |
| 70 | val := reflectx.NonPointerValue(ov) |
| 71 | typ := val.Type() |
| 72 | |
| 73 | for i := 0; i < typ.NumField(); i++ { |
| 74 | f := typ.Field(i) |
| 75 | fv := val.Field(i) |
| 76 | pval := reflectx.PointerValue(fv) |
| 77 | cmdTag, hct := f.Tag.Lookup("cmd") |
| 78 | cmds := strings.Split(cmdTag, ",") |
| 79 | if hct && !slices.Contains(cmds, cmd) && !slices.Contains(cmds, addAllFields) { // if we are associated with a different command, skip |
| 80 | continue |
| 81 | } |
| 82 | if reflectx.NonPointerType(f.Type).Kind() == reflect.Struct { |
| 83 | nwPath := f.Name |
| 84 | if path != "" { |
| 85 | nwPath = path + "." + nwPath |
| 86 | } |
| 87 | nwCmdPath := f.Name |
| 88 | // if we have a command tag, we don't scope our command path with our name, |
| 89 | // as we don't need it because we ran that command and know we are in it |
| 90 | if hct { |
| 91 | nwCmdPath = "" |
| 92 | } |
| 93 | if cmdPath != "" { |
| 94 | nwCmdPath = cmdPath + "." + nwCmdPath |
| 95 | } |
| 96 | |
| 97 | addFieldsImpl(reflectx.PointerValue(fv).Interface(), nwPath, nwCmdPath, allFields, usedNames, cmd) |
| 98 | // we still add ourself if we are a struct, so we keep going, |
| 99 | // unless we are associated with a command, in which case there |
| 100 | // is no point in adding ourself |
| 101 | if hct { |
| 102 | continue |
| 103 | } |
| 104 | } |
| 105 | // we first add our unqualified command name, which is the best case scenario |
| 106 | name := f.Name |
| 107 | names := []string{name} |
| 108 | // then, we set our future [Field.Name] to the fully path scoped version (but we don't add it as a command name) |
| 109 | if path != "" { |
| 110 | name = path + "." + name |
| 111 | } |
| 112 | // then, we set add our command path scoped name as a command name |
| 113 | if cmdPath != "" { |
| 114 | names = append(names, cmdPath+"."+f.Name) |
| 115 | } |
| 116 | |
| 117 | flagTag, ok := f.Tag.Lookup("flag") |
| 118 | if ok { |
| 119 | names = strings.Split(flagTag, ",") |
no test coverage detected