使用 Go 的 struct tag 来解析版本号字符串
各类软件的版本号定义虽然都不尽相同,但是其基本原理基本上还是相通的:通过特写的字符对字符串进行分割。我们把这一规则稍作整理,放到 struct tag 中,告诉解析器如何解析,下面就以 semver 为例作个示范:
type SemVersion struct {
Major int `version:"0,.1"`
Minor int `version:"1,.2"`
Patch int `version:"2,+4,-3"`
PreRelease string `version:"3,+4"`
Build string `version:"4"`
}
- 其中 struct tag 中的第一段内容表示的是当前字段的一个编号,要求唯一且为数值,0 表示入口;
- 后面的是解析规则,可以有多条,以逗号分隔,优先级等同;
- 每一条规则的第一个字符为触发条件,之后的数字即为前面的编号,当解析器碰到该字符时,即结束当前字段的解析,跳转到其后面指定的编号字段。
如何实现
首先定义一个表示每个字段的结构:
type struct field {
value reflect.Value // 指赂字段的值
routes map[byte]int // 解析的跳转规则
}
然后将整个结构体解析到一个 map
中,其键名即为字段的编号:
func getFields(obj interface{}) (map[int]*field, error) {
v := reflect.ValueOf(obj)
t := v.Type()
fields := make(map[int]*field, v.NumField())
for i := 0; i < v.NumField(); i++ {
tags := strings.Split(t.Field(i).Tag.Get("version"), ",")
if len(tags) < 1 {
return nil, errors.New("缺少标签内容")
}
index, err := strconv.Atoi(tags[0])
if err != nil {
return nil, err
}
if _, found := fields[index]; found {
return nil, errors.New("重复的字段编号")
}
field := &field{routes: make(map[byte]int, 2)}
for _, vv := range tags[1:] {
n, err := strconv.Atoi(vv[1:])
if err != nil {
return nil, err
}
field.routes[vv[0]] = n
}
field.value = v.Field(i)
fields[index] = field
}
return fields, nil
}
然后通过一个函数将字符串解析到结构中:
func Parse(obj interface{}, ver string) {
fields, _ := getFields(obj)
start := 0
field := fields[0]
for i := 0; i < len(ver)+1; i++ {
var nextIndex int
if i < len(ver) { // 未结束
index, found := field.routes[ver[i]]
if !found {
continue
}
nextIndex = index
}
switch field.value.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
n, err := strconv.ParseInt(ver[start:i], 10, 64)
if err != nil {
panic(err)
}
field.value.SetInt(n)
case reflect.String:
field.value.SetString(ver[start:i])
default:
panic("无效的类型")
}
i++ // 过滤掉当前字符
start = i
field = fields[nextIndex] // 下一个 field
} // end for
}
之后我们只需要定义各类版本号的相关结构体,然后传给 Parse
就可以了:
// Major_Version_Number.Minor_Version_Number[.Revision_Number[.Build_Number]]
type GNUVersion struct {
Major int `version:"0,.1"`
Minor int `version:"1,.2"`
Revision int `version:"2, 3"`
Build string `version:"3"`
}
gnu := &GNUVersion{}
sem := &SemVersion{}
Parse(gnu, "1.2.0 build-1124")
Parse(sem, "1.2.0+20160615")
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· .NET周刊【3月第1期 2025-03-02】
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· [AI/GPT/综述] AI Agent的设计模式综述