optiongen is a fork of XSAM/optionGen, a tool to generate go Struct option for test, mock or more flexible. The purpose of this fork is to provide more powerful and flexible option generation.
Functional options are an idiomatic way of creating APIs with options on types. The initial idea for this design pattern can be found in an article published by Rob Pike called Self-referential functions and the design of options.
Install using go install, and this will build the optionGen binary in $GOPATH/bin.
go install github.com/timestee/optiongen/cmd/optiongen@latestoptionGen require goimports to format code which is generated. So you may confirm that goimports has been installed
go install golang.org/x/tools/cmd/goimports@latesttype WatchError = func(loaderName string, confPath string, watchErr error)
//go:generate optiongen --option_with_struct_name=true --xconf=true --usage_tag_name=usage --xconf=true
func RedisOptionDeclareWithDefault() interface{} {
return map[string]interface{}{
"Endpoints": []string{"192.168.0.1", "192.168.0.2"},
"Cluster": true,
"TimeoutsStruct": (Timeouts)(Timeouts{}),
}
}
//go:generate optiongen
func XXXXXXOptionDeclareWithDefault() interface{} {
return map[string]interface{}{
"Endpoints": []string{"10.0.0.1", "10.0.0.2"},
"ReadTimeout": time.Duration(time.Second),
"TypeMapIntString": map[int]string{1: "a", 2: "b"},
"TypeSliceInt64": []int64{1, 2, 3, 4},
"TypeBool": false,
"MapRedis": (map[string]*Redis)(map[string]*Redis{"test": NewRedis()}),
// annotation@Redis(getter="RedisVisitor")
"Redis": (*Redis)(NewRedis()), // 辅助指定类型为*Redis
"OnWatchError": WatchError(nil), // 辅助指定类型为WatchError
"OnWatchErrorNotNil": func(loaderName string, confPath string, watchErr error) {},
"TypeSliceDuratuon": []time.Duration([]time.Duration{time.Second, time.Minute, time.Hour}), // 辅助指定类型为WatchError
}
}- 命令提示:
//go:generate optiongen - 声明XXX
OptionDeclareWithDefault, optiongen会识别后缀为OptionDeclareWithDefault的func声明做进一步处理,XXXOptionDeclareWithDefault的函数约定如上代码段所示。 - 在XXX
OptionDeclareWithDefault函数返回的map[string]interface{}中声明字段名称,默认值- 基础类型如上例中的
Endpoints,TypeBool等基础类型或者基础类型的slice,map等可以直接以字面值给出默认值 - 函数类型如果非空可以直接以字面值给出默认值,如
OnWatchErrorNotNil,但OnWatchError的默认值为nil,则需要辅助性的给出类型定义WatchError time.Duration,[]time.Duration,map[string]*Redis此类的非基础类型的slice或者map都需要辅助指明类型
在使用过程中可以尝试运行,如
optiongen遇到无法处理的类型会给出错误提示,如将TypeSliceDuratuon默认值写为[]time.Duration{time.Second, time.Minute, time.Hour}会得到如下错误提示:panic: optionGen "TypeSliceDuratuon" got type []time.Duration support basic types only - 基础类型如上例中的
- 运行
go generate,会有如下输出🚀 optiongen running => /xxxxxx/github/optiongen/example/config.go:159 [XXXXXXOptionDeclareWithDefault] ...
可以在//go:generate optiongen中根据具体需求加入参数调整代码生成行为,支持的参数可以运行:optiongen --help查看。以上文提到的XXXXXXOptionDeclareWithDefault为例。
-
--debug, bool类型,默认false,是否打开调试模式,调试模式下会输出详尽的运行日志,主要用于开发调试 -
--new_func,生成的Struct的New方法名称,如不指定则默认为默认为NewXXXXXX,可以指定为如NewConf -
--new_func_return,生成的Struct的New方法的返回类型,默认pointerpointer, 返回类型指针:*XXXXXX,比如定义类型为confOptionDeclareWithDefault首字符小写,生成的配置为conf不会被导出,此时将返回类型设定为interface或visitor更为恰当。interface,返回类型接口:XXXXXXInterfacevisitor,返回类型访问接口:XXXXXXVisitor
-
--option_prefix,生成的Option方法前缀- 默认设置下,上例中的
Endpoints字段生成的Option方法签名为WithEndpoints - 为了避免方法签名冲突,也更为明确方法的含义(一个package中定义了多个Option结构),可以指定Option方法前缀如:
WithServer,则生成的Option方法签名为WithServerEndpoints
- 默认设置下,上例中的
-
--option_with_struct_name,默认false,功能与--option_prefix类似,Option名称是否携带Struct名称- 如设定为true,在不设定
--option_prefix的情况下,上例中的Endpoints生成的Option签名为:WithXXXXXXEndpoints - 设定
--option_prefix时,该参数无效
- 如设定为true,在不设定
-
--option_return_previous, bool类型,默认true,生成的Option方法是否返回原始值 返回原始值的Option签名为:// WithReadTimeout option func for ReadTimeout func WithReadTimeout(v time.Duration) XXXXXXOption { return func(cc *XXXXXX) XXXXXXOption { previous := cc.ReadTimeout cc.ReadTimeout = v return WithReadTimeout(previous) } } // 可以在一些测试场景下将参数设定为需要的值,在退出当前场景后将配置恢复 func TestApplyOption(xx XXXXXXInterface) { old := xx.ApplyOption(WithReadTimeout(time.Second)) defer xx.ApplyOption(old...) // ... }
不返回原始值的Option签名:
// WithReadTimeout option func for ReadTimeout func WithReadTimeout(v time.Duration) XXXXXXOption { return func(cc *XXXXXX) { cc.ReadTimeout = v } }
-
--xconf,bool类型,默认false,是否生成XConf支持- 如设定为true,会生成
XConf所需的TAG,更新逻辑等,此时可以将optiongen作为配置存在 - 如有配置热更需求如对接ETCD,Apollo,文件系统等,
XConf解析时传入AtomicETCD,XConf会自动解析配置,自动在配置更新后将最新的配置更新到AtomicETCD返回的指针中。
//go:generate optiongen --option_prefix=WithETCD --xconf=true --usage_tag_name=usage --option_return_previous=false func ETCDOptionDeclareWithDefault() interface{} { return map[string]interface{}{ // annotation@Endpoints(comment="etcd地址") "Endpoints": []string{"10.0.0.1", "10.0.0.2"}, // annotation@TimeoutsPointer(comment="timeout设置") "TimeoutsPointer": (*Timeouts)(&Timeouts{}), // annotation@writeTimeout(private="true",arg=1) "writeTimeout": time.Duration(time.Second), // annotation@Redis(getter="RedisVisitor") "Redis": (*Redis)(NewRedis()), } // ETCD should use NewETCD to initialize it type ETCD struct { // annotation@Endpoints(comment="etcd地址") Endpoints []string `xconf:"endpoints" usage:"etcd地址"` // annotation@TimeoutsPointer(comment="timeout设置") TimeoutsPointer *Timeouts `xconf:"timeouts_pointer" usage:"timeout设置"` } // AtomicSetFunc used for XConf func (cc *ETCD) AtomicSetFunc() func(interface{}) { return AtomicETCDSet } // InstallCallbackOnAtomicETCDSet install callback func InstallCallbackOnAtomicETCDSet(callback func(cc ETCDInterface) bool) { ... } // AtomicETCDSet atomic setter for *ETCD func AtomicETCDSet(update interface{}) {...} // AtomicETCD return atomic *ETCDVisitor func AtomicETCD() ETCDVisitor {...}
- 如设定为true,会生成
-
--usage_tag_name,字符串类型,生成的usage标签名称,默认空,如指定:usage会生生成usage信息,XConf会将这部分信息展现在xonf.Usage以及FlagSet.Usage中。
optiongen支持通过标注的方式对字段级的代码生成进行更为灵活的控制,目前支持通过在注释中定位标注格式如下:
// annotation@Redis(getter="RedisVisitor")
// annotation@ReadTimeout(private="true", xconf="read_timeout_user_define_name")
// annotation@TypeMapStringIntNotLeaf(xconf="type_map_string_int_not_leaf,notleaf")
// annotation@ReadTimeout(arg=1,tag_json=",omitempty")private,指定字段为私有字段,不生成Option,不会影响字段本身的访问属性,字段本身的访问属性设定通过首字符大小写决定,如上例ETCDOptionDeclareWithDefault的writeTimeout字段。arg,指定arg参数的字段不会生成Option方法,并会作为New方法的参数存在- 如上例指定
writeTimeout的arg,则生成的New方法为:NewETCD(writeTimeout time.Duration, opts ...ETCDOption) - 允许设定多个arg,指定参数的index即可,index不可重复
- 如上例指定
xconf,自定义xconf标签inline,将字段inlinegetter,生成的Get接口返回值类型,默认为定义时指定的类型,可通过该方式指定返回类型对应的接口,如上例中Redis的访问接口返回为:RedisVisitoroption, 指定该字段生成的option方法名称,覆盖--option_prefix和--option_with_struct_name规则,deprecated,字符串,指定字段为deprecated,在Option以及Get方法上都会生成//Deprecated注释,如果启用了xconf支持,会一并在xconf标签中生成deprecated支持。tag_{name},其中{name}为tag名称,如json,例如tag_json=",omitempty"