Skip to content

Commit

Permalink
New parser (#129)
Browse files Browse the repository at this point in the history
  • Loading branch information
cristaloleg committed Dec 26, 2023
1 parent 3a81b29 commit 6e7f9a5
Show file tree
Hide file tree
Showing 6 changed files with 768 additions and 73 deletions.
84 changes: 67 additions & 17 deletions aconfig.go
Expand Up @@ -12,7 +12,8 @@ import (
// Loader of user configuration.
type Loader struct {
config Config
dst interface{}
dst any
parser *structParser
fields []*fieldData
fsys fs.FS
flagSet *flag.FlagSet
Expand All @@ -21,6 +22,11 @@ type Loader struct {

// Config to configure configuration loader.
type Config struct {
// NewParser set to true enables a new and better struct parser.
// Default is false because there might be bugs.
// In the future new parser will be enabled by default.
NewParser bool

SkipDefaults bool // SkipDefaults set to true will not load config from 'default' tag.
SkipFiles bool // SkipFiles set to true will not load config from files.
SkipEnv bool // SkipEnv set to true will not load config from environment variables.
Expand Down Expand Up @@ -97,7 +103,7 @@ type Config struct {
// FileDecoder is used to read config from files. See aconfig submodules.
type FileDecoder interface {
Format() string
DecodeFile(filename string) (map[string]interface{}, error)
DecodeFile(filename string) (map[string]any, error)
// Init(fsys fs.FS)
}

Expand All @@ -116,7 +122,7 @@ type Field interface {

// LoaderFor creates a new Loader based on a given configuration structure.
// Supports only non-nil structures.
func LoaderFor(dst interface{}, cfg Config) *Loader {
func LoaderFor(dst any, cfg Config) *Loader {
assertStruct(dst)

l := &Loader{
Expand Down Expand Up @@ -164,24 +170,37 @@ func (l *Loader) init() {
l.config.Args = os.Args[1:]
}

l.fields = l.getFields(l.dst)
if l.config.NewParser {
l.parser = newStructParser(l.config)
if err := l.parser.parseStruct(l.dst); err != nil {
l.errInit = err
return
}
} else {
l.fields = l.getFields(l.dst)
}

l.flagSet = flag.NewFlagSet(l.config.FlagPrefix, flag.ContinueOnError)
if !l.config.SkipFlags {
names := make(map[string]bool, len(l.fields))
for _, field := range l.fields {
flagName := l.fullTag(l.config.FlagPrefix, field, "flag")
if flagName == "" {
continue
}
if names[flagName] && !l.config.AllowDuplicates {
l.errInit = fmt.Errorf("duplicate flag %q", flagName)
return
if l.config.NewParser {
l.flagSet = l.parser.flagSet
} else {
for _, field := range l.fields {
flagName := l.fullTag(l.config.FlagPrefix, field, "flag")
if flagName == "" {
continue
}
if names[flagName] && !l.config.AllowDuplicates {
l.errInit = fmt.Errorf("duplicate flag %q", flagName)
return
}
names[flagName] = true
l.flagSet.String(flagName, field.Tag("default"), field.Tag("usage"))
}
names[flagName] = true
l.flagSet.String(flagName, field.Tag("default"), field.Tag("usage"))
}
}

if l.config.FileFlag != "" {
// TODO: should be prefixed ?
l.flagSet.String(l.config.FileFlag, "", "config file param")
Expand Down Expand Up @@ -257,6 +276,12 @@ func (l *Loader) loadSources() error {
return fmt.Errorf("load flags: %w", err)
}
}

if l.config.NewParser {
if err := l.parser.apply(l.dst); err != nil {
return fmt.Errorf("apply: %w", err)
}
}
return nil
}

Expand All @@ -278,6 +303,10 @@ func (l *Loader) checkRequired() error {
}

func (l *Loader) loadDefaults() error {
if l.config.NewParser {
return nil
}

for _, field := range l.fields {
defaultValue := field.Tag("default")
if err := l.setFieldData(field, defaultValue); err != nil {
Expand Down Expand Up @@ -328,6 +357,13 @@ func (l *Loader) loadFile(file string) error {

tag := decoder.Format()

if l.config.NewParser {
if err := l.parser.applyLevel(tag, actualFields); err != nil {
return fmt.Errorf("apply %s: %w", tag, err)
}
return nil
}

for _, field := range l.fields {
name := l.fullTag("", field, tag)
if name == "" {
Expand Down Expand Up @@ -380,6 +416,13 @@ func (l *Loader) loadEnvironment() error {
actualEnvs := getEnv(l.config.Envs)
dupls := make(map[string]struct{})

if l.config.NewParser {
if err := l.parser.applyFlat("env", actualEnvs); err != nil {
return fmt.Errorf("apply env: %w", err)
}
return nil
}

for _, field := range l.fields {
envName := l.fullTag(l.config.EnvPrefix, field, "env")
if envName == "" {
Expand All @@ -392,7 +435,7 @@ func (l *Loader) loadEnvironment() error {
return l.postEnvCheck(actualEnvs, dupls)
}

func (l *Loader) postEnvCheck(values map[string]interface{}, dupls map[string]struct{}) error {
func (l *Loader) postEnvCheck(values map[string]any, dupls map[string]struct{}) error {
if l.config.AllowUnknownEnvs || l.config.EnvPrefix == "" {
return nil
}
Expand All @@ -411,6 +454,13 @@ func (l *Loader) loadFlags() error {
actualFlags := getFlags(l.flagSet)
dupls := make(map[string]struct{})

if l.config.NewParser {
if err := l.parser.applyFlat("flag", actualFlags); err != nil {
return fmt.Errorf("apply flag: %w", err)
}
return nil
}

for _, field := range l.fields {
flagName := l.fullTag(l.config.FlagPrefix, field, "flag")
if flagName == "" {
Expand All @@ -423,7 +473,7 @@ func (l *Loader) loadFlags() error {
return l.postFlagCheck(actualFlags, dupls)
}

func (l *Loader) postFlagCheck(values map[string]interface{}, dupls map[string]struct{}) error {
func (l *Loader) postFlagCheck(values map[string]any, dupls map[string]struct{}) error {
if l.config.AllowUnknownFlags || l.config.FlagPrefix == "" {
return nil
}
Expand All @@ -439,7 +489,7 @@ func (l *Loader) postFlagCheck(values map[string]interface{}, dupls map[string]s
}

// TODO(cristaloleg): revisit.
func (l *Loader) setField(field *fieldData, name string, values map[string]interface{}, dupls map[string]struct{}) error {
func (l *Loader) setField(field *fieldData, name string, values map[string]any, dupls map[string]struct{}) error {
if !l.config.AllowDuplicates {
if _, ok := dupls[name]; ok {
return fmt.Errorf("field %q is duplicated", name)
Expand Down

0 comments on commit 6e7f9a5

Please sign in to comment.