Skip to content

Commit

Permalink
parser customization
Browse files Browse the repository at this point in the history
Signed-off-by: Artem Bortnikov <[email protected]>
  • Loading branch information
BROngineer committed Jun 18, 2024
1 parent 2466601 commit cb25f46
Show file tree
Hide file tree
Showing 8 changed files with 193 additions and 112 deletions.
19 changes: 16 additions & 3 deletions examples/custom/example.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (

"github.com/brongineer/helium"
"github.com/brongineer/helium/flag"
"github.com/brongineer/helium/parser"
)

type params struct {
Expand All @@ -28,7 +29,11 @@ type logParams struct {
DevMode bool `json:"devMode"`
}

func parser[T any](input string) (any, error) {
type customParser[T any] struct {
*parser.DefaultParser
}

func (p *customParser[T]) ParseCmd(input string) (any, error) {
var (
b []byte
err error
Expand All @@ -47,10 +52,18 @@ func parser[T any](input string) (any, error) {
return &opts, nil
}

func (p *customParser[T]) ParseEnv(_ string) (any, error) {
return nil, nil
}

func newCustomParser[T any]() *customParser[T] {
return &customParser[T]{&parser.DefaultParser{}}
}

func parse(args []string) (params, error) {
fs := helium.NewFlagSet()
helium.CustomFlag[srvParams](fs, "server-config", flag.CommandLineParser(parser[srvParams]))
helium.CustomFlag[logParams](fs, "log-config", flag.CommandLineParser(parser[logParams]))
helium.CustomFlag[srvParams](fs, "server-config", flag.Parser(newCustomParser[srvParams]()))
helium.CustomFlag[logParams](fs, "log-config", flag.Parser(newCustomParser[logParams]()))

if err := fs.Parse(args); err != nil {
return params{}, err
Expand Down
30 changes: 0 additions & 30 deletions flag/custom.go
Original file line number Diff line number Diff line change
@@ -1,39 +1,9 @@
package flag

import "github.com/brongineer/helium/errors"

type Custom[T any] struct {
*flag[T]
}

func (f *Custom[T]) FromCommandLine(input string) error {
if f.CommandLineParser() == nil {
return errors.NoParserDefined(f.Name())
}

val, err := customParse[T](f.CommandLineParser(), input, f.Name())
if err != nil {
return err
}
f.value = &val
f.visited = true
return nil
}

func (f *Custom[T]) FromEnvVariable(input string) error {
if f.EnvVariableParser() == nil {
return errors.NoParserDefined(f.Name())
}

val, err := customParse[T](f.EnvVariableParser(), input, f.Name())
if err != nil {
return err
}
f.value = &val
f.visited = true
return nil
}

func NewCustom[T any](name string, opts ...Option) *Custom[T] {
f := newFlag[T](name)
applyForFlag(f, opts...)
Expand Down
48 changes: 19 additions & 29 deletions flag/flag_test.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package flag

import (
"reflect"
"strconv"
"testing"
"time"
Expand All @@ -19,8 +18,7 @@ type flagPropertyGetter interface {
IsVisited() bool
FromCommandLine(string) error
FromEnvVariable(string) error
CommandLineParser() func(string) (any, error)
EnvVariableParser() func(string) (any, error)
Parser() flagParser
}

type expected struct {
Expand All @@ -30,8 +28,6 @@ type expected struct {
defaultValue any
required bool
shared bool
cmdParser func(string) (any, error)
envParser func(string) (any, error)
}

func (e *expected) Description() string {
Expand Down Expand Up @@ -61,14 +57,6 @@ func (e *expected) Shared() bool {
return e.shared
}

func (e *expected) CommandLineParser() func(string) (any, error) {
return e.cmdParser
}

func (e *expected) EnvVariableParser() func(string) (any, error) {
return e.envParser
}

type result[T any] struct {
some *T
err bool
Expand Down Expand Up @@ -102,16 +90,6 @@ func assertFlag[T any](t *testing.T, f flagPropertyGetter, tt flagTest) {
assert.Equal(t, tt.expected.DefaultValue(), *actual)
}
assert.Equal(t, tt.expected.Shared(), f.IsShared())
if tt.expected.CommandLineParser() == nil {
assert.Nil(t, f.CommandLineParser())
} else {
assert.Equal(t, reflect.ValueOf(tt.expected.CommandLineParser()).Pointer(), reflect.ValueOf(f.CommandLineParser()).Pointer())
}
if tt.expected.EnvVariableParser() == nil {
assert.Nil(t, f.EnvVariableParser())
} else {
assert.Equal(t, reflect.ValueOf(tt.expected.EnvVariableParser()).Pointer(), reflect.ValueOf(f.EnvVariableParser()).Pointer())
}
}

func assertGetFlag[T any](t *testing.T, f flagPropertyGetter, tt getFlagTest[T]) {
Expand All @@ -132,14 +110,26 @@ type custom struct {
field int
}

func parser(input string) (any, error) {
v, err := strconv.Atoi(input)
type customParser struct {
*embeddedParser
}

func newCustomParser() *customParser {
return &customParser{&embeddedParser{}}
}

func (p *customParser) ParseCmd(s string) (any, error) {
v, err := strconv.Atoi(s)
if err != nil {
return nil, err
}
return &custom{field: v}, nil
}

func (p *customParser) ParseEnv(_ string) (any, error) {
return nil, nil
}

func TestFlag_Custom(t *testing.T) {
t.Parallel()
tests := []flagTest{
Expand All @@ -150,8 +140,8 @@ func TestFlag_Custom(t *testing.T) {
},
{
"sample",
[]Option{CommandLineParser(parser)},
expected{cmdParser: parser},
[]Option{Parser(newCustomParser())},
expected{},
},
}
for _, tc := range tests {
Expand All @@ -175,7 +165,7 @@ func TestFlag_GetCustom(t *testing.T) {
},
{
"sample",
[]Option{CommandLineParser(parser)},
[]Option{Parser(newCustomParser())},
ptrTo("10"),
result[custom]{
ptrTo(custom{field: 10}),
Expand All @@ -184,7 +174,7 @@ func TestFlag_GetCustom(t *testing.T) {
},
{
"sample",
[]Option{CommandLineParser(parser)},
[]Option{Parser(newCustomParser())},
ptrTo("invalid"),
result[custom]{err: true},
},
Expand Down
95 changes: 65 additions & 30 deletions flag/generic.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,27 @@ import (

const defaultSliceSeparator = ","

type flagParser interface {
SetVisited(bool)
SetSeparator(string)
SetCurrentValue(any)
IsVisited() bool
Separator() string
CurrentValue() any
ParseCmd(string) (any, error)
ParseEnv(string) (any, error)
}

type flag[T any] struct {
name string
description string
shorthand string
shared bool
visited bool
defaultValue *T
value *T
separator string
commandLineParser func(string) (any, error)
envVariableParser func(string) (any, error)
name string
description string
shorthand string
shared bool
visited bool
defaultValue *T
value *T
separator string
parser flagParser
}

func (f *flag[T]) Value() any {
Expand Down Expand Up @@ -56,12 +66,8 @@ func (f *flag[T]) IsVisited() bool {
return f.visited
}

func (f *flag[T]) CommandLineParser() func(string) (any, error) {
return f.commandLineParser
}

func (f *flag[T]) EnvVariableParser() func(string) (any, error) {
return f.envVariableParser
func (f *flag[T]) Parser() flagParser {
return f.parser
}

func newFlag[T any](name string) *flag[T] {
Expand Down Expand Up @@ -96,29 +102,58 @@ func (f *flag[T]) setSeparator(separator string) {
f.separator = separator
}

func (f *flag[T]) setCommandLineParser(parser func(string) (any, error)) {
f.commandLineParser = parser
func (f *flag[T]) setParser(p flagParser) {
f.parser = p
}

func (f *flag[T]) setEnvVariableParser(parser func(string) (any, error)) {
f.envVariableParser = parser
func (f *flag[T]) reflectStateToParser() {
f.parser.SetVisited(f.IsVisited())
f.parser.SetSeparator(f.Separator())
f.parser.SetCurrentValue(f.value)
}

func customParse[T any](parser func(string) (any, error), input, flag string) (T, error) {
func (f *flag[T]) FromCommandLine(input string) error {
var (
val T
parsed any
valPtr *T
val any
parsed *T
err error
)
parsed, err = parser(input)
if f.parser == nil {
return errors.ErrNoParserDefined
}
f.reflectStateToParser()
val, err = f.parser.ParseCmd(input)
if err != nil {
return errors.ParseError(f.Name(), err)
}
parsed, err = value[T](val)
if err != nil {
return errors.ParseError(f.Name(), err)
}
f.value = parsed
f.visited = true
return nil
}

func (f *flag[T]) FromEnvVariable(input string) error {
var (
val any
parsed *T
err error
)
if f.parser == nil {
return errors.ErrNoParserDefined
}
f.reflectStateToParser()
val, err = f.parser.ParseCmd(input)
if err != nil {
return val, errors.ParseError(flag, err)
return errors.ParseError(f.Name(), err)
}
valPtr, err = value[T](parsed)
parsed, err = value[T](val)
if err != nil {
return val, errors.ParseError(flag, err)
return errors.ParseError(f.Name(), err)
}
val = *valPtr
return val, nil
f.value = parsed
f.visited = true
return nil
}
23 changes: 7 additions & 16 deletions flag/option.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@ type flagPropertySetter interface {
setShared()
setDefaultValue(any)
setSeparator(string)
setCommandLineParser(func(string) (any, error))
setEnvVariableParser(func(string) (any, error))
setParser(flagParser)
}

type Option interface {
Expand Down Expand Up @@ -72,24 +71,16 @@ func Separator(value string) Option {
return separator{value}
}

type commandLineParser func(string) (any, error)

func (o commandLineParser) apply(f flagPropertySetter) {
f.setCommandLineParser(o)
}

func CommandLineParser(parser func(string) (any, error)) Option {
return commandLineParser(parser)
type fParser struct {
flagParser
}

type envVariableParser func(string) (any, error)

func (o envVariableParser) apply(f flagPropertySetter) {
f.setEnvVariableParser(o)
func (o fParser) apply(f flagPropertySetter) {
f.setParser(o)
}

func EnvVariableParser(parser func(string) (any, error)) Option {
return envVariableParser(parser)
func Parser(p flagParser) Option {
return fParser{p}
}

func applyForFlag(f flagPropertySetter, opts ...Option) {
Expand Down
31 changes: 31 additions & 0 deletions flag/parser.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package flag

type embeddedParser struct {
visited bool
separator string
currentValue any
}

func (p *embeddedParser) SetVisited(v bool) {
p.visited = v
}

func (p *embeddedParser) IsVisited() bool {
return p.visited
}

func (p *embeddedParser) SetSeparator(s string) {
p.separator = s
}

func (p *embeddedParser) Separator() string {
return p.separator
}

func (p *embeddedParser) SetCurrentValue(v any) {
p.currentValue = v
}

func (p *embeddedParser) CurrentValue() any {
return p.currentValue
}
Loading

0 comments on commit cb25f46

Please sign in to comment.