package debian import ( "strconv" "strings" ) // CompareVersions compares two Debian package versions the way dpkg does (Debian Policy // 5.6.12): epoch first, then the upstream part, then the Debian revision. Within a part, // alternating non-digit and digit runs are compared; letters sort before non-letters, `~` // sorts before everything including the empty string, and digit runs compare numerically. // It returns -1, 0 or 1. func CompareVersions(a, b string) int { ea, ua, ra := SplitVersion(a) eb, ub, rb := SplitVersion(b) if ea != eb { if ea < eb { return -1 } return 1 } if c := verrevcmp(ua, ub); c != 0 { return c } return verrevcmp(ra, rb) } // SplitVersion splits a version into epoch, upstream version and Debian revision. // "1:1.3.dfsg+really1.3.2-3" -> (1, "1.3.dfsg+really1.3.2", "3"); a native version has an // empty revision. func SplitVersion(v string) (epoch int, upstream, revision string) { v = strings.TrimSpace(v) if i := strings.Index(v, ":"); i >= 0 { if n, err := strconv.Atoi(v[:i]); err == nil { epoch = n v = v[i+1:] } } if i := strings.LastIndex(v, "-"); i >= 0 { return epoch, v[:i], v[i+1:] } return epoch, v, "" } func order(c byte) int { switch { case c >= '0' && c <= '9': return 0 case (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'): return int(c) case c == '~': return -1 case c == 0: return 0 default: return int(c) + 256 } } func isDigit(c byte) bool { return c >= '0' && c <= '9' } // verrevcmp is a transliteration of dpkg's lib/dpkg/version.c verrevcmp. func verrevcmp(a, b string) int { i, j := 0, 0 for i < len(a) || j < len(b) { first := 0 for (i < len(a) && !isDigit(a[i])) || (j < len(b) && !isDigit(b[j])) { var ac, bc int if i < len(a) { ac = order(a[i]) } if j < len(b) { bc = order(b[j]) } if ac != bc { return sign(ac - bc) } if i < len(a) { i++ } if j < len(b) { j++ } } for i < len(a) && a[i] == '0' { i++ } for j < len(b) && b[j] == '0' { j++ } for i < len(a) && isDigit(a[i]) && j < len(b) && isDigit(b[j]) { if first == 0 { first = int(a[i]) - int(b[j]) } i++ j++ } if i < len(a) && isDigit(a[i]) { return 1 } if j < len(b) && isDigit(b[j]) { return -1 } if first != 0 { return sign(first) } } return 0 } func sign(n int) int { switch { case n < 0: return -1 case n > 0: return 1 } return 0 }