README
¶
go-option
Go 函数选项模式(Functional Options Pattern)代码自动生成工具。
根据结构体定义,自动生成 Option 类型、New 构造函数和 With 选项函数,消除手写样板代码。
支持两种生成风格:接口模式(interface)和闭包模式(closure),完整支持泛型。
本项目基于 go-optioner 改进,新增接口模式、关键字安全、通道方向、联合约束等能力,并修复了多项已知缺陷。
安装
go install github.com/gtkit/go-option/cmd/go-option@latest
验证安装:
go-option
输出:
go-option is a tool for generating functional options pattern.
Usage:
go-option [flags]
Flags:
-type <struct name>
-output <output path>, default: srcDir/opt_xxx_gen.go
-prefix <With function prefix>, default: With{FieldName}
-mode <file writing mode>, default: write
- write: Overwrites or creates a new file.
- append: Adds to the end of the file.
-style <code generation style>, default: interface
- interface: Interface-based options pattern.
- closure: Closure-based options pattern (go-optioner compatible).
如果提示命令找不到,请确认
$GOPATH/bin已添加到系统环境变量PATH中。
快速开始
1. 定义结构体
package example
//go:generate go-option -type User
type User struct {
Name string `opt:"-"` // 必填字段:作为构造函数的必传参数
Age int // 可选字段:生成 WithAge 函数
Gender string // 可选字段:生成 WithGender 函数
}
标签规则:
opt:"-":标记为必填字段,作为NewUser()构造函数的必传参数,不生成With函数。- 无标签或其他值:标记为可选字段,生成对应的
WithXxx()函数。
2. 生成代码
方式一:直接执行命令
go-option -type User
方式二:使用 go generate
go generate ./...
3. 生成的代码(interface 模式,默认)
// Generated by [go-option] -type User; DO NOT EDIT
package example
type UserOption interface {
apply(*User)
}
func NewUser(name string, opts ...UserOption) *User {
user := &User{
Name: name,
}
for _, opt := range opts {
opt.apply(user)
}
return user
}
type userAgeOpt struct {
age int
}
func (u userAgeOpt) apply(opt *User) {
opt.Age = u.age
}
func WithAge(age int) UserOption {
return userAgeOpt{age: age}
}
// ... WithGender 类似
4. 使用生成的代码
// 只传必填参数
user := NewUser("张三")
// 传必填参数 + 可选参数
user := NewUser("张三", WithAge(25), WithGender("male"))
生成风格
接口模式(默认)
go-option -type User -style interface
每个可选字段生成一个实现了 apply 方法的 unexported 结构体,类型安全,零额外分配。
闭包模式(兼容 go-optioner)
go-option -type User -style closure
生成的代码:
type UserOption func(*User)
func NewUser(name string, opts ...UserOption) *User {
user := &User{Name: name}
for _, opt := range opts {
opt(user)
}
return user
}
func WithAge(age int) UserOption {
return func(user *User) {
user.Age = age
}
}
闭包模式更简洁,Option 类型为 func(*T),每个 With 函数返回一个闭包。
命令行参数
| 参数 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
-type |
string | 是 | - | 目标结构体名称 |
-output |
string | 否 | opt_<snake>_gen.go |
输出文件路径 |
-prefix |
string | 否 | 空 | With 函数的自定义前缀 |
-with_prefix |
string | 否 | 空 | 同 -prefix(兼容 go-optioner) |
-mode |
enum | 否 | write |
write(覆盖)或 append(追加) |
-style |
enum | 否 | interface |
interface(接口)或 closure(闭包) |
进阶用法
自定义 With 函数前缀
当同一个包中有多个结构体需要生成选项,为避免 WithName 等函数名冲突:
//go:generate go-option -type User -prefix User
//go:generate go-option -type Server -prefix Server
生成 WithUserName、WithServerName 而非都叫 WithName。
自定义输出路径
//go:generate go-option -type User -output ./options/user_option.go
追加模式(多结构体共用一个文件)
//go:generate go-option -type User -mode write -output ./options.go
//go:generate go-option -type Server -mode append -output ./options.go
第一个结构体使用 write 创建文件,后续使用 append 追加到同一文件。
泛型结构体
//go:generate go-option -type Container -style closure
type Container[T any, U comparable] struct {
ID string `opt:"-"`
Value T
Index U
}
生成的代码完整保留泛型参数:
type ContainerOption[T any, U comparable] func(*Container[T, U])
func NewContainer[T any, U comparable](id string, opts ...ContainerOption[T, U]) *Container[T, U] {
// ...
}
func WithValue[T any, U comparable](value T) ContainerOption[T, U] {
// ...
}
联合类型约束
type Number interface {
~int | ~float64
}
//go:generate go-option -type Calc
type Calc[T Number] struct {
Name string `opt:"-"`
Value T
}
嵌入结构体
type Base struct {
ID int
}
//go:generate go-option -type Service
type Service struct {
Base // 可选嵌入字段,生成 WithBase
Name string `opt:"-"` // 必填
Port int // 可选
}
跨包类型字段
import "crypto/tls"
//go:generate go-option -type Server
type Server struct {
Addr string `opt:"-"`
TLSConfig *tls.Config // 自动处理跨包导入
}
生成的文件会自动添加 import "crypto/tls" 导入语句。
go:generate 批量示例
package example
import "time"
//go:generate go-option -type RedisClient -prefix Redis
type RedisClient struct {
Addr string `opt:"-"` // 必填:Redis 地址
Password string // 可选:密码
DB int // 可选:数据库编号
DialTimeout time.Duration // 可选:连接超时
ReadTimeout time.Duration // 可选:读超时
WriteTimeout time.Duration // 可选:写超时
PoolSize int // 可选:连接池大小
}
执行 go generate ./... 后生成:
NewRedisClient(addr string, opts ...RedisClientOption) *RedisClientWithRedisPassword(password string) RedisClientOptionWithRedisDB(db int) RedisClientOptionWithRedisDialTimeout(dialTimeout time.Duration) RedisClientOption- ...
支持的字段类型
| 类型 | 示例 | 支持 |
|---|---|---|
| 基本类型 | string, int, bool |
✅ |
| 指针 | *string, *Config |
✅ |
| 切片 | []int, []string |
✅ |
| 数组 | [3]byte, [10]int |
✅ |
| Map | map[string]int |
✅ |
| 通道 | chan int, chan<- int, <-chan int |
✅ |
| 函数 | func(), func(int) error |
✅ |
| 接口 | interface{}, io.Reader |
✅ |
| 结构体 | struct{} |
✅ |
| 跨包类型 | tls.Config, time.Duration |
✅ |
| 嵌入结构体 | Embedded, *Embedded |
✅ |
| 泛型实例化 | Pair[string, int] |
✅ |
| 可变参数 | func(...int) |
✅ |
| 联合约束 | ~int | ~float64 |
✅ |
安全特性
- 关键字安全:字段名转换后若为 Go 关键字(如
Func→func),自动追加_后缀避免编译错误。 - 类型名防冲突:interface 模式的内部 option 结构体使用 unexported 命名(如
userAgeOpt),不会与包中已有类型冲突。 - 闭包变量防冲突:closure 模式使用完整结构体名(如
user)而非单字母作为闭包参数,避免与单字母字段名冲突。 - 导入自动管理:使用
goimports自动添加/移除导入语句,支持跨包类型。
项目结构
go-option/
├── cmd/go-option/main.go # CLI 入口
├── options/
│ ├── options_generator.go # 核心代码生成逻辑
│ ├── options_generator_test.go # 全面测试(含编译验证)
│ ├── tool.go # 字符串工具函数
│ ├── tool_test.go # 工具函数测试
│ └── testdata/ # 测试用例数据
├── templates/
│ ├── tmpl.go # 模板 embed 声明
│ └── tpl/ # 模板文件
│ ├── option.tmpl # 接口模式(write)
│ ├── option_closure.tmpl # 闭包模式(write)
│ ├── additional.tmpl # 接口模式(append)
│ ├── additional_closure.tmpl # 闭包模式(append)
│ └── header.tmpl # 文件头注释
├── example/ # 使用示例
├── test/ # AST 解析测试
├── go.mod
├── Makefile
└── .golangci.yml
License
Click to show internal directories.
Click to hide internal directories.