From b44b7fb8c0da9652fe68a48702156b8fd6b49ff1 Mon Sep 17 00:00:00 2001 From: Pat Downey Date: Wed, 7 May 2025 23:07:44 +0100 Subject: [PATCH 1/2] Feature: Introduce `InheritDeprecated` and `InheritHidden` flags. When specified as options to the `Parse*` functions they change the behaviour of flag generation such that any structs tagged as `deprecated` or `hidden` tags applied to structs will be inherited by all child values. --- README.md | 17 +++++++--- parser.go | 84 +++++++++++++++++++++++++++++++++++------------ parser_test.go | 88 ++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 164 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index 574ffd0..5681a59 100644 --- a/README.md +++ b/README.md @@ -176,15 +176,18 @@ If you specify description in description tag (`desc` by default) it will be use ```golang Addr string `desc:"HTTP host"` +```` +This description produces something like: ```sh -this description produces something like: -``` -addr value HTTP host (default 127.0.0.1) ``` ## Options for env tag - +If you specify environment variable name in `env` tag, it will be used to set the value of the field. +```golang +SSL bool `env:"HTTP_SSL_VALUE"` +``` ## Options for Parse function: @@ -212,8 +215,14 @@ func EnvDivider(val string) // Check existed validators in sflags/validator package. func Validator(val ValidateFunc) -// Set to false if you don't want anonymous structure fields to be flatten. +// Set to false if you don't want anonymous structure fields to be flattened. func Flatten(val bool) + +// InheritHidden sets if fields should inherit the value of the hidden tag from parent structs. +func InheritHidden() + +// InheritDeprecated sets if fields should inherit the value of the deprecated tag from parent structs. +func InheritDeprecated() ``` diff --git a/parser.go b/parser.go index 47a7c60..dcd7b4f 100644 --- a/parser.go +++ b/parser.go @@ -7,12 +7,16 @@ import ( ) const ( - defaultDescTag = "desc" - defaultFlagTag = "flag" - defaultEnvTag = "env" - defaultFlagDivider = "-" - defaultEnvDivider = "_" - defaultFlatten = true + defaultDescTag = "desc" + defaultFlagTag = "flag" + defaultEnvTag = "env" + defaultFlagDivider = "-" + defaultEnvDivider = "_" + defaultFlatten = true + defaultInheritHidden = false + defaultHidden = false + defaultInheritDeprecated = false + defaultDeprecated = false ) // ValidateFunc describes a validation func, @@ -22,14 +26,18 @@ const ( type ValidateFunc func(val string, field reflect.StructField, cfg interface{}) error type opts struct { - descTag string - flagTag string - prefix string - envPrefix string - flagDivider string - envDivider string - flatten bool - validator ValidateFunc + descTag string + flagTag string + prefix string + envPrefix string + flagDivider string + envDivider string + flatten bool + validator ValidateFunc + inheritHidden bool + hidden bool + inheritDeprecated bool + deprecated bool } func (o opts) apply(optFuncs ...OptFunc) opts { @@ -51,6 +59,22 @@ func FlagTag(val string) OptFunc { return func(opt *opts) { opt.flagTag = val } // Prefix sets prefix that will be applied for all flags (if they are not marked as ~). func Prefix(val string) OptFunc { return func(opt *opts) { opt.prefix = val } } +// InheritHidden enables inheriting the hidden flag for all nested flags if set for a parent flag +func InheritHidden() OptFunc { return func(opt *opts) { opt.inheritHidden = true } } + +// hidden sets the hidden flag for all nested flags if set for a parent flag +func hidden(val bool) OptFunc { + return func(opt *opts) { opt.hidden = val } +} + +// InheritDeprecated enables inheriting the deprecated flag for all nested flags if set for a parent flag +func InheritDeprecated() OptFunc { return func(opt *opts) { opt.inheritDeprecated = true } } + +// deprecated sets the deprecated flag for all nested flags if set for a parent flag +func deprecated(val bool) OptFunc { + return func(opt *opts) { opt.deprecated = val } +} + // EnvPrefix sets prefix that will be applied for all environment variables (if they are not marked as ~). func EnvPrefix(val string) OptFunc { return func(opt *opts) { opt.envPrefix = val } } @@ -82,11 +106,15 @@ func hasOption(options []string, option string) bool { func defOpts() opts { return opts{ - descTag: defaultDescTag, - flagTag: defaultFlagTag, - flagDivider: defaultFlagDivider, - envDivider: defaultEnvDivider, - flatten: defaultFlatten, + descTag: defaultDescTag, + flagTag: defaultFlagTag, + flagDivider: defaultFlagDivider, + envDivider: defaultEnvDivider, + flatten: defaultFlatten, + inheritHidden: defaultInheritHidden, + hidden: defaultHidden, + inheritDeprecated: defaultInheritDeprecated, + deprecated: defaultDeprecated, } } @@ -120,6 +148,13 @@ func parseFlagTag(field reflect.StructField, opt opts) *Flag { if opt.prefix != "" && !ignoreFlagPrefix { flag.Name = opt.prefix + flag.Name } + + if opt.deprecated { + flag.Deprecated = opt.deprecated + } + if opt.hidden { + flag.Hidden = opt.hidden + } return &flag } @@ -264,9 +299,16 @@ fields: prefix = opt.prefix } + nestedOpts := []OptFunc{copyOpts(opt), Prefix(prefix)} + if opt.inheritHidden { + nestedOpts = append(nestedOpts, hidden(flag.Hidden)) + } + if opt.inheritDeprecated { + nestedOpts = append(nestedOpts, deprecated(flag.Deprecated)) + } + nestedFlags, val := parseVal(fieldValue, - copyOpts(opt), - Prefix(prefix), + nestedOpts..., ) // field contains a simple value. diff --git a/parser_test.go b/parser_test.go index 8cf3a2f..a57896a 100644 --- a/parser_test.go +++ b/parser_test.go @@ -96,6 +96,47 @@ func TestParseStruct(t *testing.T) { }, }, } + hiddenNestedCfg := &struct { + Sub struct { + Name string + Sub2 struct { + Name string + } + } `flag:",hidden"` + Sub3 struct { + Name string + } + }{ + Sub: struct { + Name string + Sub2 struct { + Name string + } + }{ + Name: "name_value", + Sub2: struct{ Name string }{ + Name: "other_value", + }, + }, + Sub3: struct{ Name string }{ + Name: "name_value", + }, + } + deprecatedNestedCfg := &struct { + Sub struct { + Name string + } `flag:",deprecated"` + Sub2 struct { + Name string + } + }{ + Sub: struct{ Name string }{ + Name: "name_value", + }, + Sub2: struct{ Name string }{ + Name: "name_value", + }, + } descCfg := &struct { Name string `desc:"name description"` Name2 string `description:"name2 description"` @@ -347,6 +388,53 @@ func TestParseStruct(t *testing.T) { }, expErr: nil, }, + { + name: "Inherit hidden parent flag", + cfg: hiddenNestedCfg, + optFuncs: []OptFunc{InheritHidden()}, + expFlagSet: []*Flag{ + { + Name: "sub-name", + EnvNames: []string{"SUB_NAME"}, + DefValue: "name_value", + Value: newStringValue(&hiddenNestedCfg.Sub.Name), + Hidden: true, + }, + { + Name: "sub-sub2-name", + EnvNames: []string{"SUB_SUB2_NAME"}, + DefValue: "other_value", + Value: newStringValue(&hiddenNestedCfg.Sub.Sub2.Name), + Hidden: true, + }, + { + Name: "sub3-name", + EnvNames: []string{"SUB3_NAME"}, + DefValue: "name_value", + Value: newStringValue(&hiddenNestedCfg.Sub3.Name), + }, + }, + }, + { + name: "Inherit deprecated parent flag", + cfg: deprecatedNestedCfg, + optFuncs: []OptFunc{InheritDeprecated()}, + expFlagSet: []*Flag{ + { + Name: "sub-name", + EnvNames: []string{"SUB_NAME"}, + DefValue: "name_value", + Value: newStringValue(&deprecatedNestedCfg.Sub.Name), + Deprecated: true, + }, + { + Name: "sub2-name", + EnvNames: []string{"SUB2_NAME"}, + DefValue: "name_value", + Value: newStringValue(&deprecatedNestedCfg.Sub2.Name), + }, + }, + }, { name: "DescCfg with custom desc tag", cfg: descCfg, From 1f87c75611110125bba5311a2cfb55513f37f88e Mon Sep 17 00:00:00 2001 From: Pat Downey Date: Wed, 16 Jul 2025 23:35:03 +0100 Subject: [PATCH 2/2] fix: fix up deprecation inheritance test to actually test inheritance --- parser_test.go | 30 ++++++++++++++++++++++++------ 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/parser_test.go b/parser_test.go index a57896a..0a5f586 100644 --- a/parser_test.go +++ b/parser_test.go @@ -125,15 +125,26 @@ func TestParseStruct(t *testing.T) { deprecatedNestedCfg := &struct { Sub struct { Name string + Sub2 struct { + Name string + } } `flag:",deprecated"` - Sub2 struct { + Sub3 struct { Name string } }{ - Sub: struct{ Name string }{ + Sub: struct { + Name string + Sub2 struct { + Name string + } + }{ Name: "name_value", + Sub2: struct{ Name string }{ + Name: "other_value", + }, }, - Sub2: struct{ Name string }{ + Sub3: struct{ Name string }{ Name: "name_value", }, } @@ -428,10 +439,17 @@ func TestParseStruct(t *testing.T) { Deprecated: true, }, { - Name: "sub2-name", - EnvNames: []string{"SUB2_NAME"}, + Name: "sub-sub2-name", + EnvNames: []string{"SUB_SUB2_NAME"}, + DefValue: "other_value", + Value: newStringValue(&deprecatedNestedCfg.Sub.Sub2.Name), + Deprecated: true, + }, + { + Name: "sub3-name", + EnvNames: []string{"SUB3_NAME"}, DefValue: "name_value", - Value: newStringValue(&deprecatedNestedCfg.Sub2.Name), + Value: newStringValue(&deprecatedNestedCfg.Sub3.Name), }, }, },