Skip to content

Commit

Permalink
fix: slice handling (#16)
Browse files Browse the repository at this point in the history
  • Loading branch information
kzantow authored Jul 25, 2023
1 parent bf3a172 commit 329a9a4
Show file tree
Hide file tree
Showing 4 changed files with 152 additions and 15 deletions.
6 changes: 6 additions & 0 deletions load.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ func loadConfig(cfg Config, flags flagRefs, configurations ...any) error {
// unmarshal fully populated viper object onto config
err := v.Unmarshal(configuration, func(dc *mapstructure.DecoderConfig) {
dc.TagName = cfg.TagName
// ZeroFields will use what is present in the config file instead of modifying existing defaults
dc.ZeroFields = true
})
if err != nil {
return err
Expand Down Expand Up @@ -284,6 +286,10 @@ func isStruct(typ reflect.Type) bool {
return typ.Kind() == reflect.Struct
}

func isSlice(typ reflect.Type) bool {
return typ.Kind() == reflect.Slice
}

func isNotFoundErr(err error) bool {
var notFound *viper.ConfigFileNotFoundError
return err != nil && errors.As(err, &notFound)
Expand Down
29 changes: 29 additions & 0 deletions load_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,35 @@ func Test_flagBoolPtrValues(t *testing.T) {
require.Equal(t, true, *a.Bool)
}

func Test_zeroFields(t *testing.T) {
type s struct {
List []string `mapstructure:"list"`
}
a := &s{
List: []string{
"default1",
"default2",
"default3",
},
}

cmd := &cobra.Command{}

cfg := NewConfig("app")
err := Load(cfg, cmd, a)
require.NoError(t, err)

require.Equal(t, []string{"default1", "default2", "default3"}, a.List)

t.Setenv("APP_LIST", "set1,set2")

cfg = NewConfig("app")
err = Load(cfg, cmd, a)
require.NoError(t, err)

require.Equal(t, []string{"set1", "set2"}, a.List)
}

func Test_AllFieldTypes(t *testing.T) {
appName := "app"
envName := func(name string) string {
Expand Down
87 changes: 77 additions & 10 deletions summarize.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ func Summarize(cfg Config, descriptions DescriptionProvider, values ...any) stri
v := reflect.ValueOf(value)
summarize(cfg, descriptions, root, v, nil)
}
return root.stringify()
return root.stringify(cfg)
}

func SummarizeCommand(cfg Config, cmd *cobra.Command, values ...any) string {
Expand Down Expand Up @@ -98,9 +98,71 @@ func summarize(cfg Config, descriptions DescriptionProvider, s *section, value r
}
}

func printVal(value reflect.Value) string {
v, _ := base(value)
if v.CanInterface() {
// printVal prints a value in YAML format
func printVal(cfg Config, value reflect.Value, indent string) string {
buf := bytes.Buffer{}

v, t := base(value)
switch {
case isSlice(t):
if v.Len() == 0 {
return "[]"
}

for i := 0; i < v.Len(); i++ {
v := v.Index(i)
buf.WriteString("\n")
buf.WriteString(indent)
buf.WriteString("- ")

val := printVal(cfg, v, indent+" ")
val = strings.TrimSpace(val)
buf.WriteString(val)

// separate struct entries by an empty line
_, t := base(v)
if isStruct(t) {
buf.WriteString("\n")
}
}

case isStruct(t):
for i := 0; i < t.NumField(); i++ {
f := t.Field(i)
if !f.IsExported() {
continue
}

name := f.Name

if tag, ok := f.Tag.Lookup(cfg.TagName); ok {
parts := strings.Split(tag, ",")
tag = parts[0]
if tag == "-" {
continue
}
switch {
case contains(parts, "squash"):
name = ""
case tag == "":
default:
name = tag
}
}

v := v.Field(i)

buf.WriteString("\n")
buf.WriteString(indent)

val := printVal(cfg, v, indent+" ")

val = fmt.Sprintf("%s: %s", name, val)

buf.WriteString(val)
}

case v.CanInterface():
v := v.Interface()
switch v.(type) {
case string:
Expand All @@ -109,7 +171,11 @@ func printVal(value reflect.Value) string {
return fmt.Sprintf("%v", v)
}
}
return ""

val := buf.String()
// for slices, there will be an extra newline, which we want to remove
val = strings.TrimSuffix(val, "\n")
return val
}

func base(v reflect.Value) (reflect.Value, reflect.Type) {
Expand Down Expand Up @@ -171,13 +237,13 @@ func (s *section) add(log logger.Logger, name string, value reflect.Value, descr
return add
}

func (s *section) stringify() string {
func (s *section) stringify(cfg Config) string {
out := &bytes.Buffer{}
stringifySection(out, s, "")
stringifySection(cfg, out, s, "")
return out.String()
}

func stringifySection(out *bytes.Buffer, s *section, indent string) {
func stringifySection(cfg Config, out *bytes.Buffer, s *section, indent string) {
nextIndent := indent

if s.name != "" {
Expand Down Expand Up @@ -215,14 +281,15 @@ func stringifySection(out *bytes.Buffer, s *section, indent string) {

if s.value.IsValid() {
out.WriteString(" ")
out.WriteString(printVal(s.value))
val := printVal(cfg, s.value, indent+" ")
out.WriteString(val)
}

out.WriteString("\n")
}

for _, s := range s.subsections {
stringifySection(out, s, nextIndent)
stringifySection(cfg, out, s, nextIndent)
if len(s.subsections) == 0 {
out.WriteString(nextIndent)
out.WriteString("\n")
Expand Down
45 changes: 40 additions & 5 deletions summarize_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -255,12 +255,18 @@ var _ FlagAdder = (*Summarize3)(nil)
var _ FieldDescriber = (*Summarize3)(nil)

func Test_SummarizeValuesWithPointers(t *testing.T) {
type Sub struct {
SubValue string
IntSlice []int
}
type T1 struct {
TopBool bool
TopString string
Summarize1 `mapstructure:",squash"`
Pointer *Summarize2 `mapstructure:"ptr"`
NilPointer *Summarize3 `mapstructure:"nil"`
TopBool bool
TopString string
Summarize1 `mapstructure:",squash"`
Pointer *Summarize2 `mapstructure:"ptr"`
NilPointer *Summarize3 `mapstructure:"nil"`
StringSlice []string
SubSlice []Sub
}

cfg := NewConfig("my-app")
Expand All @@ -269,6 +275,19 @@ func Test_SummarizeValuesWithPointers(t *testing.T) {
Name: "summarize2 name",
Val: 2,
},
StringSlice: []string{
"s1",
"s2",
},
SubSlice: []Sub{
{
SubValue: "sv1",
},
{
SubValue: "sv2",
IntSlice: []int{3, 2, 1},
},
},
}

cmd := &cobra.Command{}
Expand Down Expand Up @@ -306,6 +325,22 @@ nil:
# val 2 description (env: MY_APP_NIL_VAL)
Val: 0
# (env: MY_APP_STRINGSLICE)
StringSlice:
- 's1'
- 's2'
# (env: MY_APP_SUBSLICE)
SubSlice:
- SubValue: 'sv1'
IntSlice: []
- SubValue: 'sv2'
IntSlice:
- 3
- 2
- 1
`, s)
}

Expand Down

0 comments on commit 329a9a4

Please sign in to comment.