Skip to content

Commit

Permalink
v2.0 options
Browse files Browse the repository at this point in the history
  • Loading branch information
antonmashko committed Sep 22, 2023
1 parent e3164d7 commit 6d88a43
Show file tree
Hide file tree
Showing 14 changed files with 394 additions and 214 deletions.
55 changes: 28 additions & 27 deletions config_sources.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,29 +8,6 @@ import (
"strings"
)

type ConfigSource int

const (
FlagVariable ConfigSource = iota
EnvVariable
ExternalSource
DefaultValue
)

func (s ConfigSource) String() string {
switch s {
case FlagVariable:
return "Flag"
case EnvVariable:
return "Environment"
case ExternalSource:
return "External"
case DefaultValue:
return "Default"
}
return ""
}

const (
tagFlag = "flag"
tagEnv = "env"
Expand All @@ -56,7 +33,7 @@ var (
// Var is configuration variable for defining primitive data types
type Var interface {
Name() string
Value() (string, bool)
Value() (interface{}, bool)
}

type flagSource struct {
Expand Down Expand Up @@ -87,7 +64,7 @@ func (s *flagSource) Name() string {
return s.name
}

func (s *flagSource) Value() (string, bool) {
func (s *flagSource) Value() (interface{}, bool) {
if s.name == tagIgnored {
return "", false
}
Expand Down Expand Up @@ -126,13 +103,37 @@ func (s *envSource) Name() string {
return s.name
}

func (s *envSource) Value() (string, bool) {
func (s *envSource) Value() (interface{}, bool) {
if s.name != tagIgnored {
return os.LookupEnv(s.name)
}
return "", false
}

type externalValueSource struct {
f field
ext *externalConfig
}

func newExternalValueSource(f field, ext *externalConfig) *externalValueSource {
return &externalValueSource{
f: f,
ext: ext,
}
}

func (s *externalValueSource) Name() string {
name, ok := s.f.structField().Tag.Lookup(tagEnv)
if !ok {
name = s.f.name()
}
return name
}

func (s *externalValueSource) Value() (interface{}, bool) {
return s.ext.get(s.f)
}

type defaultValueSource struct {
defined bool
v string
Expand All @@ -148,6 +149,6 @@ func (s *defaultValueSource) Name() string {
return tagDefault
}

func (s *defaultValueSource) Value() (string, bool) {
func (s *defaultValueSource) Value() (interface{}, bool) {
return s.v, s.defined
}
5 changes: 5 additions & 0 deletions example/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,16 @@ type Example struct {
FlagField string `flag:"*"`
EnvVarField string `env:"*"`
}
Password string `default:"pass1"`
API struct {
Token string `default:"token1"`
}
}

// Run `go run main.go --help` for getting help output with auto-generated names
func main() {
var cfg Example
// envconf.SetLogger(log.New(os.Stdout, "envconf", log.Ldate|log.Ltime))
if err := envconf.Parse(&cfg); err != nil {
panic(err)
}
Expand Down
17 changes: 13 additions & 4 deletions external.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,15 @@ import (
)

// External config source
// Implementation of this interface should be able to `Unmarshal` data into map[string]interface{},
// where interface{} should be also same map type for the nested structures
type External interface {
// TagName is key name in golang struct tag (json, yaml, toml etc.).
TagName() string
Unmarshal(interface{}) error
// Unmarshal parses the external data and stores the result
// in the value pointed to by v.
// Usually, it just wraps the existing `Unmarshal` function of third-party libraries
Unmarshal(v interface{}) error
}

type emptyExt struct{}
Expand Down Expand Up @@ -94,9 +100,12 @@ func (c *externalConfig) findField(key string, s *structType) (field, bool) {
for _, f := range s.fields {
// if annotation exists matching only by it
sf := f.structField()
extName := c.validateAndFix(sf.Tag.Get(c.ext.TagName()))
if extName != "" && key == extName {
return f, true
tagName, ok := sf.Tag.Lookup(c.ext.TagName())
if ok {
extName := c.validateAndFix(tagName)
if key == extName {
return f, true
}
}

// unexportable field. looking for any first match with EqualFold
Expand Down
38 changes: 0 additions & 38 deletions help.go

This file was deleted.

25 changes: 0 additions & 25 deletions logger.go

This file was deleted.

50 changes: 50 additions & 0 deletions option/client_option.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package option

import "reflect"

type FieldInitializedArg struct {
Name string
FullName string
Type reflect.Type
Required bool
Description string

FlagName string
EnvName string
DefaultValue interface{}
}

type FieldDefinedArg struct {
Name string
FullName string
Type reflect.Type
Required bool
Description string

FlagName string
EnvName string
DefaultValue interface{}

Source ConfigSource
Value interface{}
}

type FieldDefineErrorArg struct {
Name string
FullName string
Type reflect.Type
Err error
}

type Options struct {
PriorityOrder []ConfigSource
FlagParsed func() error
Usage func()
OnFieldInitialized []func(FieldInitializedArg)
OnFieldDefined []func(FieldDefinedArg)
OnFieldDefineErr []func(FieldDefineErrorArg)
}

type ClientOption interface {
Apply(*Options)
}
61 changes: 61 additions & 0 deletions option/config_source.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package option

type ConfigSource int

const (
FlagVariable ConfigSource = iota
EnvVariable
ExternalSource
DefaultValue
)

func (s ConfigSource) String() string {
switch s {
case FlagVariable:
return "Flag"
case EnvVariable:
return "Environment"
case ExternalSource:
return "External"
case DefaultValue:
return "Default"
}
return ""
}

type priorityOrder []ConfigSource

func (p priorityOrder) Apply(opts *Options) {
opts.PriorityOrder = p
}

// WithPriorityOrder overrides default priority order, with an order from function argument.
// Default priority order is: Flag, Environment variable, External source, Default value.
func WithPriorityOrder(s ...ConfigSource) ClientOption {
defaultOrder := []ConfigSource{
FlagVariable, EnvVariable, ExternalSource, DefaultValue,
}
if len(s) == 0 {
return priorityOrder(defaultOrder)
}
po := make(map[ConfigSource]int)
var idx int
for _, p := range s {
if p != FlagVariable && p != EnvVariable &&
p != ExternalSource && p != DefaultValue {
continue
}
if _, ok := po[p]; !ok {
po[p] = idx
idx++
}
}
if len(po) == 0 {
return priorityOrder(defaultOrder)
}
result := make([]ConfigSource, len(po))
for s, idx := range po {
result[idx] = s
}
return priorityOrder(result)
}
14 changes: 14 additions & 0 deletions option/flag_parsed.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package option

type flagParsedFunc func() error

func (fP flagParsedFunc) Apply(opts *Options) {
opts.FlagParsed = fP
}

// WithFlagParsed define this callback when you need handle flags
// This callback will raise after method flag.Parse()
// return not nil error interrupt pasring
func WithFlagParsed(flagParsed func() error) ClientOption {
return flagParsedFunc(flagParsed)
}
50 changes: 50 additions & 0 deletions option/help.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package option

import (
"fmt"
"os"
)

type help struct {
fields []FieldInitializedArg
}

func (h *help) usage() {
fmt.Fprintln(os.Stdout, "Usage:")
fmt.Fprintln(os.Stdout, "")
h.print()
}

func (h *help) print() {
for _, f := range h.fields {
h.printValue(f)
}
}

func (h *help) printValue(f FieldInitializedArg) {
// TODO: help should be configurable
fmt.Fprintf(os.Stdout, "%s <%s> %s\n", f.FullName, f.Type.Name(), f.DefaultValue)
fmt.Fprintf(os.Stdout, "\tflag: %s\n", f.FlagName)
fmt.Fprintf(os.Stdout, "\tenvironment variable: %s\n", f.EnvName)
fmt.Fprintf(os.Stdout, "\trequired: %t\n", f.Required)
if f.Description != "" {
fmt.Fprintf(os.Stdout, "\tdescription: \"%s\"\n", f.Description)
}
fmt.Fprintln(os.Stdout)
}

func (h *help) addField(arg FieldInitializedArg) {
h.fields = append(h.fields, arg)
}

func (h *help) Apply(opts *Options) {
opts.Usage = h.usage
opts.OnFieldInitialized = append(opts.OnFieldInitialized, h.addField)
}

func WithFlagUsage() ClientOption {
h := &help{
fields: make([]FieldInitializedArg, 0),
}
return h
}
Loading

0 comments on commit 6d88a43

Please sign in to comment.