diff --git a/flagutil.go b/flagutil.go index 08e2b55..a048790 100644 --- a/flagutil.go +++ b/flagutil.go @@ -37,14 +37,14 @@ func (fn PrinterFunc) Name(ctx context.Context, fs parse.FlagSet) (func(*flag.Fl type parser struct { Parser - stash func(*flag.Flag) bool - ignoreUndefined bool + stash func(*flag.Flag) bool + ignoreUndefined bool + allowResetSpecified bool } type config struct { parsers []*parser parserOptions []ParserOption - ignoreUndefined bool customUsage bool unquoteUsageMode UnquoteUsageMode } @@ -67,16 +67,12 @@ func buildConfig(opts []ParseOption) config { func Parse(ctx context.Context, flags *flag.FlagSet, opts ...ParseOption) (err error) { c := buildConfig(opts) - fs := parse.NewFlagSet(flags, - parse.WithIgnoreUndefined(c.ignoreUndefined), - ) + fs := parse.NewFlagSet(flags) for _, p := range c.parsers { parse.NextLevel(fs) parse.Stash(fs, p.stash) - if p.ignoreUndefined { - // User may ask to ignore undefined per parser as well. - parse.IgnoreUndefined(fs) - } + parse.IgnoreUndefined(fs, p.ignoreUndefined) + parse.AllowResetSpecified(fs, p.allowResetSpecified) if err = p.Parse(ctx, fs); err != nil { if err == flag.ErrHelp { diff --git a/options.go b/options.go index d58b369..613f961 100644 --- a/options.go +++ b/options.go @@ -26,13 +26,17 @@ type ParseOptionFunc func(*config) func (fn ParseOptionFunc) setupParseConfig(c *config) { fn(c) } -type ParserOptionFunc func(*config, *parser) +type ParserOptionFunc func(*parser) -func (fn ParserOptionFunc) setupParseConfig(c *config) { fn(c, nil) } -func (fn ParserOptionFunc) setupParserConfig(p *parser) { fn(nil, p) } +func (fn ParserOptionFunc) setupParserConfig(p *parser) { fn(p) } -func stashFunc(check func(*flag.Flag) bool) (opt ParserOptionFunc) { - return ParserOptionFunc(func(c *config, p *parser) { +type ParseOrParserOptionFunc func(*config, *parser) + +func (fn ParseOrParserOptionFunc) setupParseConfig(c *config) { fn(c, nil) } +func (fn ParseOrParserOptionFunc) setupParserConfig(p *parser) { fn(nil, p) } + +func stashFunc(check func(*flag.Flag) bool) (opt ParseOrParserOptionFunc) { + return ParseOrParserOptionFunc(func(c *config, p *parser) { if c != nil { c.parserOptions = append(c.parserOptions, opt) return @@ -47,24 +51,30 @@ func stashFunc(check func(*flag.Flag) bool) (opt ParserOptionFunc) { }) } -func WithStashName(name string) ParserOptionFunc { +func WithStashName(name string) ParseOrParserOptionFunc { return stashFunc(func(f *flag.Flag) bool { return f.Name == name }) } -func WithStashPrefix(prefix string) ParserOptionFunc { +func WithStashPrefix(prefix string) ParseOrParserOptionFunc { return stashFunc(func(f *flag.Flag) bool { return strings.HasPrefix(f.Name, prefix) }) } -func WithStashRegexp(re *regexp.Regexp) ParserOptionFunc { +func WithStashRegexp(re *regexp.Regexp) ParseOrParserOptionFunc { return stashFunc(func(f *flag.Flag) bool { return re.MatchString(f.Name) }) } +func WithResetSpecified() ParserOptionFunc { + return ParserOptionFunc(func(p *parser) { + p.allowResetSpecified = true + }) +} + // WithParser returns a parse option and makes p to be used during Parse(). func WithParser(p Parser, opts ...ParserOption) ParseOptionFunc { x := &parser{ @@ -79,17 +89,28 @@ func WithParser(p Parser, opts ...ParserOption) ParseOptionFunc { } // WithIgnoreUndefined makes Parse() to not fail on setting undefined flag. -func WithIgnoreUndefined() ParserOptionFunc { - return ParserOptionFunc(func(c *config, p *parser) { +func WithIgnoreUndefined() (opt ParseOrParserOptionFunc) { + return ParseOrParserOptionFunc(func(c *config, p *parser) { switch { case c != nil: - c.ignoreUndefined = true + c.parserOptions = append(c.parserOptions, opt) case p != nil: p.ignoreUndefined = true } }) } +func WithAllowResetSpecified() (opt ParseOrParserOptionFunc) { + return ParseOrParserOptionFunc(func(c *config, p *parser) { + switch { + case c != nil: + c.parserOptions = append(c.parserOptions, opt) + case p != nil: + p.allowResetSpecified = true + } + }) +} + // WithCustomUsage makes Parse() to ignore flag.FlagSet.Usage field when // receiving flag.ErrHelp error from some parser and print results of // flagutil.PrintDefaults() instead. diff --git a/parse/flagset.go b/parse/flagset.go index fe0a9b4..b68e224 100644 --- a/parse/flagset.go +++ b/parse/flagset.go @@ -39,22 +39,28 @@ func Stash(fs FlagSet, fn func(*flag.Flag) bool) { fset.stash = fn } -func IgnoreUndefined(fs FlagSet) { +func IgnoreUndefined(fs FlagSet, ignore bool) { fset := fs.(*flagSet) - fset.ignoreUndefined = true + fset.ignoreUndefined = ignore +} + +func AllowResetSpecified(fs FlagSet, allow bool) { + fset := fs.(*flagSet) + fset.allowResetSpecified = allow } type flagSet struct { - dest *flag.FlagSet - ignoreUndefined bool - provided map[string]bool - stash func(*flag.Flag) bool + dest *flag.FlagSet + ignoreUndefined bool + allowResetSpecified bool + specified map[string]bool + stash func(*flag.Flag) bool } func NewFlagSet(flags *flag.FlagSet, opts ...FlagSetOption) FlagSet { fs := &flagSet{ - dest: flags, - provided: make(map[string]bool), + dest: flags, + specified: make(map[string]bool), } for _, opt := range opts { opt(fs) @@ -64,7 +70,7 @@ func NewFlagSet(flags *flag.FlagSet, opts ...FlagSetOption) FlagSet { } func (fs *flagSet) Set(name, value string) error { - if fs.provided[name] { + if fs.specified[name] && !fs.allowResetSpecified { return nil } f := fs.dest.Lookup(name) @@ -92,13 +98,13 @@ func (fs *flagSet) stashed(f *flag.Flag) bool { func (fs *flagSet) update() { fs.dest.Visit(func(f *flag.Flag) { - fs.provided[f.Name] = true + fs.specified[f.Name] = true }) } func (fs *flagSet) VisitUnspecified(fn func(*flag.Flag)) { fs.dest.VisitAll(func(f *flag.Flag) { - if !fs.provided[f.Name] && !fs.stashed(f) { + if !fs.specified[f.Name] && !fs.stashed(f) { fn(fs.clone(f)) } })