Skip to content

Commit

Permalink
up: support parse env var name, time string and more
Browse files Browse the repository at this point in the history
  • Loading branch information
inhere committed Aug 17, 2022
1 parent 9b724b8 commit 07c687d
Show file tree
Hide file tree
Showing 2 changed files with 83 additions and 41 deletions.
48 changes: 31 additions & 17 deletions options.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ type OpFunc func(opts *Options)

// Options for config
type Options struct {
// Debug open debug mode
Debug bool
// ParseEnv parse ENV var name, default True. eg: "${SHELL}"
ParseEnv bool
// ParseVar reference. eg: "${other.var.name}". default: true
Expand All @@ -18,27 +20,19 @@ type Options struct {
ParseTime bool
// TagName for binding data to struct. default: properties
TagName string

// MapStructConfig for binding data to struct.
MapStructConfig mapstructure.DecoderConfig
// TrimValue trim "\n" for value string. default: false
TrimValue bool

// InlineComment support split inline comments. default: false
//
// allow chars: #, //
InlineComment bool
// InlineSlice support parse the inline slice. eg: [23, 34]. default: false
InlineSlice bool
// TrimMultiLine trim "\n" for multi line value. default: false
TrimMultiLine bool
// BeforeCollect value handle func.
BeforeCollect func(name, value string) (val interface{}, ok bool)
}

func (opts *Options) shouldAddHookFunc() bool {
if opts.MapStructConfig.DecodeHook == nil {
return opts.ParseTime || opts.ParseEnv
}
return false
// MapStructConfig for binding data to struct.
MapStructConfig mapstructure.DecoderConfig
// BeforeCollect value handle func, you can return a new value.
BeforeCollect func(name string, val interface{}) interface{}
}

func (opts *Options) makeDecoderConfig() *mapstructure.DecoderConfig {
Expand All @@ -48,9 +42,9 @@ func (opts *Options) makeDecoderConfig() *mapstructure.DecoderConfig {
decConf.TagName = opts.TagName
}

// add hook on decode value to struct
if opts.shouldAddHookFunc() {
decConf.DecodeHook = ValDecodeHookFunc(opts.ParseEnv, opts.ParseTime)
// parse time string on binding to struct
if opts.ParseTime || decConf.DecodeHook == nil {
decConf.DecodeHook = ValDecodeHookFunc()
}

return &decConf
Expand All @@ -69,6 +63,26 @@ func newDefaultOption() *Options {
}
}

// WithDebug open debug mode
func WithDebug(opts *Options) {
opts.Debug = true
}

// ParseEnv open parse ENV var string.
func ParseEnv(opts *Options) {
opts.ParseEnv = true
}

// ParseTime open parse time string.
func ParseTime(opts *Options) {
opts.ParseTime = true
}

// ParseInlineSlice open parse inline slice
func ParseInlineSlice(opts *Options) {
opts.InlineSlice = true
}

// WithTagName custom tag name on binding struct
func WithTagName(tagName string) OpFunc {
return func(opts *Options) {
Expand Down
76 changes: 52 additions & 24 deletions util.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,49 +7,77 @@ import (
"time"

"github.com/gookit/goutil/envutil"
"github.com/gookit/goutil/strutil"
"github.com/mitchellh/mapstructure"
)

// eg: ${some.other.key} -> some.other.key
var refRegex = regexp.MustCompile(`^[a-z][a-z\d.]+$`)

func parseVarRefName(val string) (string, bool) {
if !strings.HasPrefix(val, VarRefStartChars) || !strings.HasSuffix(val, "}") {
return "", false
}

refName := val[2 : len(val)-1]
if refRegex.MatchString(refName) {
return refName, true
}
return "", false
}

// ValDecodeHookFunc returns a mapstructure.DecodeHookFunc that parse ENV var, and more custom parse
func ValDecodeHookFunc(parseEnv, parseTime bool) mapstructure.DecodeHookFunc {
// ValDecodeHookFunc returns a mapstructure.DecodeHookFunc that parse time string
func ValDecodeHookFunc() mapstructure.DecodeHookFunc {
return func(f reflect.Type, t reflect.Type, data interface{}) (interface{}, error) {
if f.Kind() != reflect.String {
return data, nil
}

str := data.(string)
if len(str) < 2 {
ln := len(str)
if ln < 2 {
return str, nil
}

// start char is number(1-9)
if str[0] > '0' && str[0] < '9' {
// start char is number(1-9) and end char is a-z.
if str[0] > '0' && str[0] < '9' && str[ln-1] > 'a' {
// parse time string. eg: 10s
if parseTime && t.Kind() == reflect.Int64 {
if t.Kind() == reflect.Int64 {
dur, err := time.ParseDuration(str)
if err == nil {
return dur, nil
}
}
} else if parseEnv { // parse ENV value
str = envutil.ParseEnvValue(str)
}

return str, nil
}
}

// that parse ENV var name
func parseEnvVarName(val string) interface{} {
ln := len(val)
if ln < 3 {
return val
}
return envutil.ParseEnvValue(val)
}

// eg: ${some.other.key} -> some.other.key
var refRegex = regexp.MustCompile(`^[a-z][a-z\d.]+$`)

func parseVarRefName(val string) (string, bool) {
if !strings.HasPrefix(val, VarRefStartChars) || !strings.HasSuffix(val, "}") {
return "", false
}

refName := val[2 : len(val)-1]
if refRegex.MatchString(refName) {
return refName, true
}
return "", false
}

func parseInlineSlice(s string, ln int) (ss []string, ok bool) {
// eg: [34, 56]
if s[0] == '[' && s[ln-1] == ']' {
return strutil.Split(s[1:ln-1], ","), true
}
return
}

func splitInlineComment(val string) (string, string) {
if pos := strings.IndexRune(val, '#'); pos > -1 {
return strings.TrimRight(val[0:pos], " "), val[pos:]
}

if pos := strings.Index(val, "//"); pos > -1 {
return strings.TrimRight(val[0:pos], " "), val[pos:]
}

return val, ""
}

0 comments on commit 07c687d

Please sign in to comment.