Skip to content

timestee/optiongen

 
 

Repository files navigation

optiongen

GoDoc Go Report CardSourcegraph

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

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

Install using go install, and this will build the optionGen binary in $GOPATH/bin.

go install github.com/timestee/optiongen/cmd/optiongen@master

optionGen 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@latest

快速开始

type 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
  • 声明XXXOptionDeclareWithDefault, optiongen会识别后缀为OptionDeclareWithDefault的func声明做进一步处理,XXXOptionDeclareWithDefault的函数约定如上代码段所示。
  • 在XXXOptionDeclareWithDefault函数返回的 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] ...

使用帮助

optiongen支持的参数

可以在//go:generate optiongen中根据具体需求加入参数调整代码生成行为,支持的参数可以运行:optiongen --help查看。以上文提到的XXXXXXOptionDeclareWithDefault为例。

  • --debug, bool类型,默认false,是否打开调试模式,调试模式下会输出详尽的运行日志,主要用于开发调试

  • --new_func,生成的Struct的New方法名称,如不指定则默认为默认为NewXXXXXX,可以指定为如NewConf

  • --new_func_return,生成的Struct的New方法的返回类型,默认 pointer

    • pointer, 返回类型指针: *XXXXXX,比如定义类型为confOptionDeclareWithDefault首字符小写,生成的配置为conf不会被导出,此时将返回类型设定为interfacevisitor更为恰当。
    • interface,返回类型接口: XXXXXXInterface
    • visitor,返回类型访问接口: 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时,该参数无效
  • --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 {...}
  • --usage_tag_name,字符串类型,生成的usage标签名称,默认空,如指定:usage会生生成usage信息,XConf会将这部分信息展现在xonf.Usage以及FlagSet.Usage中。

optiongen支持的标注

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,不会影响字段本身的访问属性,字段本身的访问属性设定通过首字符大小写决定,如上例ETCDOptionDeclareWithDefaultwriteTimeout字段。
  • arg,指定arg参数的字段不会生成Option方法,并会作为New方法的参数存在
    • 如上例指定writeTimeout的arg,则生成的New方法为:NewETCD(writeTimeout time.Duration, opts ...ETCDOption)
    • 允许设定多个arg,指定参数的index即可,index不可重复
  • xconf,自定义xconf标签
  • inline,将字段inline
  • getter,生成的Get接口返回值类型,默认为定义时指定的类型,可通过该方式指定返回类型对应的接口,如上例中Redis的访问接口返回为:RedisVisitor
  • option, 指定该字段生成的option方法名称,覆盖--option_prefix--option_with_struct_name规则,
  • deprecated,字符串,指定字段为deprecated,在Option以及Get方法上都会生成//Deprecated注释,如果启用了xconf支持,会一并在xconf标签中生成deprecated支持。
  • tag_{name},其中{name}为tag名称,如json,例如tag_json=",omitempty"