Go 完整实现版本比较 VersionCompare 函数

VersionCompare — 用于对比两个的版本数字字符串大小。

此函数首先在版本字符串里用一个点 . 替换 _、- 和 +,也会在任意非数字前后插入一个点 .,这样,类似 '4.3.2RC1' 将会变成 '4.3.2.RC.1'。 接下来它会分割结果,然后它会从左往右对比各个部分。如果某部分包含了特定的版本字符串,将会用以下顺序处理:列表中未找到的任意字符串 < dev < alpha = a < beta = b < RC = rc < # < pl = p。 这种方式不仅能够对比类似 '4.1' 和 '4.1.2' 那种不同的版本级别,同时也可以指定对比任何包含开发状态的版本。

VersionCompare

// version_compare()
// The possible operators are: <, lt, <=, le, >, gt, >=, ge, ==, =, eq, !=, <>, ne respectively.
// special version strings these are handled in the following order,
// (any string not found) < dev < alpha = a < beta = b < RC = rc < # < pl = p
// Usage:
// VersionCompare("1.2.3-alpha", "1.2.3RC7", '>=')
// VersionCompare("1.2.3-beta", "1.2.3pl", 'lt')
// VersionCompare("1.1_dev", "1.2any", 'eq')
func VersionCompare(version1, version2, operator string) bool {
    var vcompare func(string, string) int
    var canonicalize func(string) string
    var special func(string, string) int

    // version compare
    vcompare = func(origV1, origV2 string) int {
        if origV1 == "" || origV2 == "" {
            if origV1 == "" && origV2 == "" {
                return 0
            } else {
                if origV1 == "" {
                    return -1
                } else {
                    return 1
                }
            }
        }

        ver1, ver2, compare := "", "", 0
        if origV1[0] == '#' {
            ver1 = origV1
        } else {
            ver1 = canonicalize(origV1)
        }
        if origV2[0] == '#' {
            ver2 = origV2
        } else {
            ver2 = canonicalize(origV2)
        }
        n1, n2 := 0, 0
        for {
            p1, p2 := "", ""
            n1 = strings.IndexByte(ver1, '.')
            if n1 == -1 {
                p1, ver1 = ver1, ""
            } else {
                p1, ver1 = ver1[:n1], ver1[n1+1:]
            }
            n2 = strings.IndexByte(ver2, '.')
            if n2 == -1 {
                p2, ver2 = ver2, ""
            } else {
                p2, ver2 = ver2[:n2], ver2[n2+1:]
            }
            if (p1[0] >= '0' && p1[0] <= '9') && (p2[0] >= '0' && p2[0] <= '9') { // all isdigit
                l1, _ := strconv.Atoi(p1)
                l2, _ := strconv.Atoi(p2)
                if l1 > l2 {
                    compare = 1
                } else if l1 == l2 {
                    compare = 0
                } else {
                    compare = -1
                }
            } else if !(p1[0] >= '0' && p1[0] <= '9') && !(p2[0] >= '0' && p2[0] <= '9') { // all isndigit
                compare = special(p1, p2)
            } else { // part isdigit
                if p1[0] >= '0' && p1[0] <= '9' { // isdigit
                    compare = special("#N#", p2)
                } else {
                    compare = special(p1, "#N#")
                }
            }
            if compare != 0 || n1 == -1 || n2 == -1 {
                break
            }
        }

        if compare == 0 {
            if ver1 != "" {
                if ver1[0] >= '0' && ver1[0] <= '9' {
                    compare = 1
                } else {
                    compare = vcompare(ver1, "#N#")
                }
            } else if ver2 != "" {
                if ver2[0] >= '0' && ver2[0] <= '9' {
                    compare = -1
                } else {
                    compare = vcompare("#N#", ver2)
                }
            }
        }

        return compare
    }

    // canonicalize
    canonicalize = func(version string) string {
        ver := []byte(version)
        l := len(ver)
        if l == 0 {
            return ""
        }
        var buf = make([]byte, l*2)
        j := 0
        for i, v := range ver {
            next := uint8(0)
            if i+1 < l { // Have the next one
                next = ver[i+1]
            }
            if v == '-' || v == '_' || v == '+' { // repalce "-","_","+" to "."
                if j > 0 && buf[j-1] != '.' {
                    buf[j] = '.'
                    j++
                }
            } else if (next > 0) &&
                (!(next >= '0' && next <= '9') && (v >= '0' && v <= '9')) ||
                (!(v >= '0' && v <= '9') && (next >= '0' && next <= '9')) { // Insert '.' before and after a non-digit
                buf[j] = v
                j++
                if v != '.' && next != '.' {
                    buf[j] = '.'
                    j++
                }
                continue
            } else if !((v >= '0' && v <= '9') ||
                (v >= 'a' && v <= 'z') || (v >= 'A' && v <= 'Z')) { // Non-letters and numbers
                if j > 0 && buf[j-1] != '.' {
                    buf[j] = '.'
                    j++
                }
            } else {
                buf[j] = v
                j++
            }
        }

        return string(buf[:j])
    }

    //compare special version forms
    special = func(form1, form2 string) int {
        found1, found2, len1, len2 := -1, -1, len(form1), len(form2)
        // (Any string not found) < dev < alpha = a < beta = b < RC = rc < # < pl = p
        forms := map[string]int{
            "dev":   0,
            "alpha": 1,
            "a":     1,
            "beta":  2,
            "b":     2,
            "RC":    3,
            "rc":    3,
            "#":     4,
            "pl":    5,
            "p":     5,
        }

        for name, order := range forms {
            if len1 < len(name) {
                continue
            }
            if strings.Compare(form1[:len(name)], name) == 0 {
                found1 = order
                break
            }
        }
        for name, order := range forms {
            if len2 < len(name) {
                continue
            }
            if strings.Compare(form2[:len(name)], name) == 0 {
                found2 = order
                break
            }
        }

        if found1 == found2 {
            return 0
        } else if found1 > found2 {
            return 1
        } else {
            return -1
        }
    }

    compare := vcompare(version1, version2)

    switch operator {
    case "<", "lt":
        return compare == -1
    case "<=", "le":
        return compare != 1
    case ">", "gt":
        return compare == 1
    case ">=", "ge":
        return compare != -1
    case "==", "=", "eq":
        return compare == 0
    case "!=", "<>", "ne":
        return compare != 0
    default:
        panic("operator: invalid")
    }
}

Github地址

https://github.com/syyongx/php2go