zoukankan      html  css  js  c++  java
  • 使用 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"`
    }
    1. 其中 struct tag 中的第一段内容表示的是当前字段的一个编号,要求唯一且为数值,0 表示入口;
    2. 后面的是解析规则,可以有多条,以逗号分隔,优先级等同;
    3. 每一条规则的第一个字符为触发条件,之后的数字即为前面的编号,当解析器碰到该字符时,即结束当前字段的解析,跳转到其后面指定的编号字段。

    如何实现

    首先定义一个表示每个字段的结构:

     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")
    

    查看完整的实现:https://github.com/issue9/version

  • 相关阅读:
    事件类型
    program的发展史与两个数学方法
    字符统计与基本正则
    bom与dom
    css长度单位及颜色表示
    grid学习
    position定位的认识
    remark:node端口的close-wait
    css background属性设置
    Promise之我发现
  • 原文地址:https://www.cnblogs.com/caixw/p/parse-version-with-go-struct-tag.html
Copyright © 2011-2022 走看看