diff --git a/build.go b/build.go index 374897c..85e18dc 100644 --- a/build.go +++ b/build.go @@ -24,6 +24,7 @@ func build(k *Kong, ast interface{}) (app *Application, err error) { for _, flag := range extraFlags { seenFlags[flag.Name] = true } + node, err := buildNode(k, iv, ApplicationNode, seenFlags) if err != nil { return nil, err @@ -112,7 +113,15 @@ func buildNode(k *Kong, v reflect.Value, typ NodeType, seenFlags map[string]bool if err != nil { return nil, err } + +MAIN: for _, field := range fields { + for _, r := range k.ignoreFieldsRegex { + if r.MatchString(v.Type().Name() + "." + field.field.Name) { + continue MAIN + } + } + ft := field.field fv := field.value diff --git a/kong.go b/kong.go index dd4b966..14f5c2d 100644 --- a/kong.go +++ b/kong.go @@ -6,6 +6,7 @@ import ( "os" "path/filepath" "reflect" + "regexp" "strings" ) @@ -48,10 +49,11 @@ type Kong struct { Stdout io.Writer Stderr io.Writer - bindings bindings - loader ConfigurationLoader - resolvers []Resolver - registry *Registry + bindings bindings + loader ConfigurationLoader + resolvers []Resolver + registry *Registry + ignoreFieldsRegex []*regexp.Regexp noDefaultHelp bool usageOnError usageOnError @@ -73,13 +75,14 @@ type Kong struct { // See the README (https://github.com/alecthomas/kong) for usage instructions. func New(grammar interface{}, options ...Option) (*Kong, error) { k := &Kong{ - Exit: os.Exit, - Stdout: os.Stdout, - Stderr: os.Stderr, - registry: NewRegistry().RegisterDefaults(), - vars: Vars{}, - bindings: bindings{}, - helpFormatter: DefaultHelpValueFormatter, + Exit: os.Exit, + Stdout: os.Stdout, + Stderr: os.Stderr, + registry: NewRegistry().RegisterDefaults(), + vars: Vars{}, + bindings: bindings{}, + helpFormatter: DefaultHelpValueFormatter, + ignoreFieldsRegex: make([]*regexp.Regexp, 0), } options = append(options, Bind(k)) diff --git a/kong_test.go b/kong_test.go index ca8bafe..7d80d40 100644 --- a/kong_test.go +++ b/kong_test.go @@ -1297,3 +1297,65 @@ func TestHydratePointerCommands(t *testing.T) { require.NoError(t, err) require.Equal(t, &cmd{Flag: true}, cli.Cmd) } + +// nolint +type testIgnoreFields struct { + Foo struct { + Bar bool + Sub struct { + SubFlag1 bool `kong:"name=subflag1"` + XXX_SubFlag2 bool `kong:"name=subflag2"` + } `kong:"cmd"` + } `kong:"cmd"` + XXX_Baz struct { + Boo bool + } `kong:"cmd,name=baz"` +} + +func TestIgnoreRegex(t *testing.T) { + cli := testIgnoreFields{} + + k, err := kong.New(&cli, kong.IgnoreFieldsRegex(`.*\.XXX_.+`)) + require.NoError(t, err) + + _, err = k.Parse([]string{"foo", "sub"}) + require.NoError(t, err) + + _, err = k.Parse([]string{"foo", "sub", "--subflag1"}) + require.NoError(t, err) + + _, err = k.Parse([]string{"foo", "sub", "--subflag2"}) + require.Error(t, err) + require.Contains(t, err.Error(), "unknown flag --subflag2") + + _, err = k.Parse([]string{"baz"}) + require.Error(t, err) + require.Contains(t, err.Error(), "unexpected argument baz") +} + +// Verify that passing a nil regex will work +func TestIgnoreRegexEmpty(t *testing.T) { + cli := testIgnoreFields{} + + _, err := kong.New(&cli, kong.IgnoreFieldsRegex("")) + require.Error(t, err) + require.Contains(t, "regex input cannot be empty", err.Error()) +} + +type optionWithErr struct{} + +func (o *optionWithErr) Apply(k *kong.Kong) error { + return errors.New("option returned err") +} + +func TestOptionReturnsErr(t *testing.T) { + cli := struct { + Test bool + }{} + + optWithError := &optionWithErr{} + + _, err := kong.New(cli, optWithError) + require.Error(t, err) + require.Equal(t, "option returned err", err.Error()) +} diff --git a/options.go b/options.go index aec57d6..6036d91 100644 --- a/options.go +++ b/options.go @@ -6,6 +6,7 @@ import ( "os/user" "path/filepath" "reflect" + "regexp" "strings" "github.com/pkg/errors" @@ -319,6 +320,31 @@ func Resolvers(resolvers ...Resolver) Option { }) } +// IgnoreFieldsRegex will cause kong.New() to skip field names that match any +// of the provided regex patterns. This is useful if you are not able to add a +// kong="-" struct tag to a struct/element before the call to New. +// +// Example: When referencing protoc generated structs, you will likely want to +// ignore/skip XXX_* fields. +func IgnoreFieldsRegex(regexes ...string) Option { + return OptionFunc(func(k *Kong) error { + for _, r := range regexes { + if r == "" { + return errors.New("regex input cannot be empty") + } + + re, err := regexp.Compile(r) + if err != nil { + return errors.Wrap(err, "unable to compile regex") + } + + k.ignoreFieldsRegex = append(k.ignoreFieldsRegex, re) + } + + return nil + }) +} + // ConfigurationLoader is a function that builds a resolver from a file. type ConfigurationLoader func(r io.Reader) (Resolver, error)