package config import ( "errors" "fmt" "io" "strings" "unicode" "github.com/alecthomas/kong" "gopkg.in/yaml.v3" ) type KeyFormatterChain []KeyFormatter func (c KeyFormatterChain) Replace(in string) string { for _, f := range c { in = f.Replace(in) } return in } type KeyFormatter interface { Replace(in string) string } var KebabToPascalCase KeyFormatter = KeyFormatterFunc(func(in string) string { inLength := len(in) out := make([]rune, 0, inLength) for i := 0; i < inLength; i++ { if in[i] == '-' { if i < inLength-1 { i++ out = append(out, unicode.ToUpper((rune)(in[i]))) } } else { out = append(out, (rune)(in[i])) } } return string(out) }) type Yaml struct { KeySeparator string KeyFormatter KeyFormatter } func (y Yaml) Loader(r io.Reader) (kong.Resolver, error) { decoder := yaml.NewDecoder(r) config := make(map[string]any) if err := decoder.Decode(&config); err != nil && !errors.Is(err, io.EOF) { return nil, fmt.Errorf("YAML config decoding error: %w", err) } return kong.ResolverFunc(func(context *kong.Context, parent *kong.Path, flag *kong.Flag) (any, error) { path := strings.Split(flag.Name, y.separator()) if n := parent.Node(); n != nil && n.Type != kong.ApplicationNode { for _, prefix := range append(n.Aliases, n.Name) { if val := lookup(config, y.normalize(append([]string{prefix}, path...))); val != nil { return val, nil } } } return nil, nil }), nil } func (y Yaml) normalize(path []string) []string { if y.KeyFormatter != nil { for i := range path { path[i] = y.KeyFormatter.Replace(path[i]) } } return path } func (y Yaml) separator() string { if y.KeySeparator == "" { return "." } return y.KeySeparator } func lookup(config map[string]any, path []string) any { if len(path) == 0 { return config } if len(path) > 1 { val, ok := config[path[0]] if !ok { return nil } if valMap, ok := val.(map[string]any); ok { return lookup(valMap, path[1:]) } else { return nil } } return config[path[0]] } type KeyFormatterFunc func(in string) string func (f KeyFormatterFunc) Replace(in string) string { return f(in) }