enumgen

command
v0.2.3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Dec 8, 2025 License: MIT Imports: 7 Imported by: 0

README

enumgen

中文 | English

enumgen 是一个 Go 枚举代码生成工具,为带有 enumgen:@enum 注解的类型生成辅助方法。

安装

go install github.com/tlipoca9/devgen/cmd/enumgen@latest

使用

enumgen ./...              # 所有包
enumgen ./pkg/status       # 指定包

注解

@enum - 标记枚举类型

在类型定义上方添加注解,指定要生成的方法:

// Status 表示状态
// enumgen:@enum(string, json, text, sql)
type Status int

const (
    StatusPending Status = iota
    StatusActive
    StatusInactive
)

支持的选项:

  • string - 生成 String() 方法
  • json - 生成 MarshalJSON() / UnmarshalJSON() 方法
  • text - 生成 MarshalText() / UnmarshalText() 方法
  • sql - 生成 Value() (driver.Valuer) / Scan() (sql.Scanner) 方法

支持的底层类型

  • 整数类型: int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64
  • 字符串类型: string
字符串底层类型

enumgen 支持 string 作为底层类型:

// Color 颜色枚举
// enumgen:@enum(string)
type Color string

const (
    ColorRed   Color = "red"
    ColorGreen Color = "green"
    ColorBlue  Color = "blue"
)

生成的方法(字符串类型):

  • IsValid() - 检查值是否有效
  • String() - 返回字符串表示
  • {Type}Enums.List() - 返回所有有效值
  • {Type}Enums.Contains(string) - 检查字符串是否为有效值
  • {Type}Enums.Parse(string) - 解析字符串为枚举值

注意:字符串类型枚举不生成 Name()Names()ContainsName() 方法,也不支持 @name 注解。

@name - 自定义值名称

默认情况下,枚举值的字符串名称会自动去除类型名前缀(如 StatusPendingPending)。

使用 @name 可以自定义名称:

// Level 表示日志级别
// enumgen:@enum(string, json)
type Level int

const (
    // enumgen:@name(DEBUG)
    LevelDebug Level = iota + 1
    // enumgen:@name(INFO)
    LevelInfo
    // enumgen:@name(WARN)
    LevelWarn
    // enumgen:@name(ERROR)
    LevelError
)

注意@name 的值不能重复,否则会报错。

生成的代码

对于带有 enumgen:@enum(string, json, text, sql) 注解的 Status 类型,会生成以下代码:

类型方法

无论选择哪些选项,都会生成 IsValid() 方法:

// IsValid reports whether x is a valid Status.
func (x Status) IsValid() bool {
    return StatusEnums.Contains(x)
}

根据注解选项生成对应的方法:

string 选项:

// String returns the string representation of Status.
func (x Status) String() string {
    return StatusEnums.Name(x)
}

json 选项:

// MarshalJSON implements json.Marshaler.
func (x Status) MarshalJSON() ([]byte, error) {
    return json.Marshal(StatusEnums.Name(x))
}

// UnmarshalJSON implements json.Unmarshaler.
func (x *Status) UnmarshalJSON(data []byte) error {
    var s string
    if err := json.Unmarshal(data, &s); err != nil {
        return err
    }
    v, err := StatusEnums.Parse(s)
    if err != nil {
        return err
    }
    *x = v
    return nil
}

text 选项:

// MarshalText implements encoding.TextMarshaler.
func (x Status) MarshalText() ([]byte, error) {
    return []byte(StatusEnums.Name(x)), nil
}

// UnmarshalText implements encoding.TextUnmarshaler.
func (x *Status) UnmarshalText(data []byte) error {
    v, err := StatusEnums.Parse(string(data))
    if err != nil {
        return err
    }
    *x = v
    return nil
}

sql 选项:

// Value implements driver.Valuer.
func (x Status) Value() (driver.Value, error) {
    return StatusEnums.Name(x), nil
}

// Scan implements sql.Scanner.
func (x *Status) Scan(src any) error {
    if src == nil {
        return nil
    }
    var s string
    switch v := src.(type) {
    case string:
        s = v
    case []byte:
        s = string(v)
    default:
        return fmt.Errorf("cannot scan %T into Status", src)
    }
    v, err := StatusEnums.Parse(s)
    if err != nil {
        return err
    }
    *x = v
    return nil
}
辅助变量 StatusEnums

无论选择哪些选项,都会生成辅助变量和类型:

// StatusEnums is the enum helper for Status.
var StatusEnums = _StatusEnums{
    values: []Status{
        StatusPending,
        StatusActive,
        StatusInactive,
    },
    names: map[Status]string{
        StatusPending:  "Pending",
        StatusActive:   "Active",
        StatusInactive: "Inactive",
    },
    byName: map[string]Status{
        "Pending":  StatusPending,
        "Active":   StatusActive,
        "Inactive": StatusInactive,
    },
}

// _StatusEnums provides enum metadata and validation for Status.
type _StatusEnums struct {
    values []Status
    names  map[Status]string
    byName map[string]Status
}
辅助方法
方法 说明
IsValid() bool 检查当前值是否有效(类型方法,始终生成)
List() []Status 返回所有有效的枚举值
Contains(v Status) bool 检查值是否有效
ContainsName(name string) bool 检查名称是否有效
Parse(s string) (Status, error) 从字符串解析枚举值
Name(v Status) string 获取枚举值的字符串名称
Names() []string 返回所有有效的名称列表

完整示例

定义
package order

// OrderStatus 订单状态
// enumgen:@enum(string, json, sql)
type OrderStatus int

const (
    OrderStatusPending    OrderStatus = iota + 1 // 待处理
    OrderStatusProcessing                        // 处理中
    OrderStatusCompleted                         // 已完成
    // enumgen:@name(Cancelled)
    OrderStatusCanceled                          // 已取消(自定义名称)
)

运行代码生成:

enumgen ./...
使用
package main

import (
    "encoding/json"
    "fmt"
    
    "example.com/order"
)

func main() {
    status := order.OrderStatusPending
    
    // String
    fmt.Println(status.String()) // Output: Pending
    
    // JSON 序列化
    data, _ := json.Marshal(status)
    fmt.Println(string(data)) // Output: "Pending"
    
    // JSON 反序列化
    var s order.OrderStatus
    json.Unmarshal([]byte(`"Completed"`), &s)
    fmt.Println(s) // Output: Completed
    
    // 解析字符串
    parsed, err := order.OrderStatusEnums.Parse("Processing")
    if err == nil {
        fmt.Println(parsed) // Output: Processing
    }
    
    // 列出所有值
    for _, v := range order.OrderStatusEnums.List() {
        fmt.Printf("%d: %s\n", v, order.OrderStatusEnums.Name(v))
    }
    // Output:
    // 1: Pending
    // 2: Processing
    // 3: Completed
    // 4: Cancelled
    
    // 验证
    fmt.Println(order.OrderStatusEnums.Contains(order.OrderStatusPending)) // true
    fmt.Println(order.OrderStatusEnums.ContainsName("Invalid"))            // false
}

测试与基准测试

运行单元测试
# 运行所有测试
go test -v ./cmd/enumgen/generator/...

# 运行测试并查看覆盖率
go test -v ./cmd/enumgen/generator/... -coverprofile=coverage.out
go tool cover -func=coverage.out

当前测试覆盖率:99.6%(46 个测试用例)

运行基准测试

基准测试使用 Ginkgo gmeasure 实现,可以获得统计学意义上的性能数据。

Step 1: 运行基准测试
# 运行测试(包含基准测试)
cd /path/to/devgen
go test -v ./cmd/enumgen/generator/... -count=1
Step 2: 生成报告文件
# 生成 JSON 格式报告
ginkgo --json-report=benchmark_report.json ./cmd/enumgen/generator/...

# 生成 JUnit XML 格式报告
ginkgo --junit-report=benchmark_report.xml ./cmd/enumgen/generator/...

# 同时生成两种格式
ginkgo --json-report=benchmark_report.json --junit-report=benchmark_report.xml ./cmd/enumgen/generator/...
Step 3: 查看详细输出

运行测试时会在控制台输出详细的基准测试结果,包括:

  • Mean(平均值)
  • StdDev(标准差)
  • Min/Max(最小/最大值)
  • 采样次数和迭代次数
基准测试结果摘要 (2025-12-07)

测试环境

  • OS: darwin (macOS)
  • Arch: arm64
  • CPU: Apple M4 Pro
  • 每个方法执行 1000 次迭代,采样 100 次

测试配置

  • RandomSeed: 1765086644
  • TotalSpecs: 70
  • SuiteSucceeded: true
  • RunTime: ~290ms
方法 Mean (1000次) 单次约 说明
IsValid/valid 1.54µs 1.5ns 验证有效枚举值
IsValid/invalid 2.77µs 2.8ns 验证无效枚举值
String/valid 2.26µs 2.3ns 有效值转字符串
String/invalid 69.87µs 70ns 无效值转字符串(需格式化)
MarshalJSON/direct 53.66µs 54ns 直接调用 MarshalJSON
MarshalJSON/via_json_Marshal 124.24µs 124ns 通过 json.Marshal 调用
UnmarshalJSON/direct 95.93µs 96ns 直接调用 UnmarshalJSON
UnmarshalJSON/via_json_Unmarshal 157.38µs 157ns 通过 json.Unmarshal 调用
MarshalText 15.32µs 15ns 文本序列化
UnmarshalText 15.15µs 15ns 文本反序列化
Value 11.61µs 12ns SQL driver.Valuer
Scan/string 7.61µs 7.6ns SQL Scanner (string)
Scan/bytes 30.09µs 30ns SQL Scanner ([]byte)
Scan/nil 1.11µs 1.1ns SQL Scanner (nil)
Parse/valid 5.29µs 5.3ns 解析有效字符串
Parse/invalid 95.01µs 95ns 解析无效字符串(需创建 error)
Contains/valid 1.80µs 1.8ns 检查有效值
Contains/invalid 2.57µs 2.6ns 检查无效值
ContainsName/valid 5.36µs 5.4ns 检查有效名称
ContainsName/invalid 6.16µs 6.2ns 检查无效名称
Name/valid 2.31µs 2.3ns 获取有效值名称
Name/invalid 55.12µs 55ns 获取无效值名称(需格式化)
List 301ns 0.3ns 返回所有枚举值
Names 24.36µs 24ns 返回所有名称
性能分析

性能亮点

  • 核心方法(IsValidStringContainsParse)在有效输入时性能极佳(< 10ns/op)
  • List() 方法仅需约 0.3ns,因为直接返回预分配的切片
  • 直接调用 MarshalJSON 比通过 json.Marshal 快约 2.3 倍
  • 直接调用 UnmarshalJSON 比通过 json.Unmarshal 快约 1.6 倍

性能差异原因

  • 无效值处理较慢是因为需要格式化错误信息或生成默认字符串
  • Scan/bytesScan/string 慢是因为需要进行类型转换
  • 通过标准库调用比直接调用慢是因为有反射开销
基准测试代码位置

基准测试代码位于 cmd/enumgen/generator/generator_benchmark_test.go,使用 Ginkgo gmeasure 包实现:

experiment := gmeasure.NewExperiment("EnumGen Benchmark")
AddReportEntry(experiment.Name, experiment)

experiment.SampleDuration("IsValid/valid", func(idx int) {
    for i := 0; i < iterations; i++ {
        _ = gen.GenerateOptionString.IsValid()
    }
}, gmeasure.SamplingConfig{N: samples, Duration: time.Second})

Documentation

Overview

Command enumgen generates enum helper methods.

Directories

Path Synopsis
Package generator provides enum code generation functionality.
Package generator provides enum code generation functionality.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL