agent/util/versionutil/compare.go (111 lines of code) (raw):

package versionutil import ( "strconv" "strings" "unicode" "unicode/utf8" ) // NOTE: Code below was ported from C++ version of aliyun assistant agent by // direct translation. Review and refactoring are needed. type charType int const ( _typeNumber charType = iota _typePeriod _typeString ) func classifyChar(c rune) charType { if c == '.' { return _typePeriod } else if unicode.IsDigit(c) { return _typeNumber } else { return _typeString } } // splitVersionString splits version string into individual components. A // component is continuous run of characters with the same classification. For // example, "1.20rc3" would be split into ["1",".","20","rc","3"]. func splitVersionString(version string) []string { trimedVersion := strings.TrimSpace(version) if len(trimedVersion) == 0 { return nil } list := []string{} versionRunes := []rune(trimedVersion) len := len(versionRunes) s := []rune{versionRunes[0]} prevType := classifyChar(versionRunes[0]) for i := 1; i < len; i++ { c := versionRunes[i] newType := classifyChar(c) if prevType != newType || prevType == _typePeriod { // We reached a new segment. Period gets special treatment, // because "." always delimiters components in version strings // (and so ".." means there's empty component value). list = append(list, string(s)) s = []rune{c} } else { // Add character to current segment and continue. s = append(s, c) } prevType = newType } // Don't forget to add the last part: list = append(list, string(s)) return list } func CompareVersion(verA string, verB string) int { partsA := splitVersionString(verA) partsB := splitVersionString(verB) // Compare common length of both version strings. n := len(partsA) // Incredible and ridiculous stupidity of google and golang standard library // missing min function for int type. if len(partsB) < n { n = len(partsB) } for i := 0; i < n; i++ { a := partsA[i] b := partsB[i] firstRuneA, _ := utf8.DecodeLastRuneInString(a) typeA := classifyChar(firstRuneA) firstRuneB, _ := utf8.DecodeRuneInString(b) typeB := classifyChar(firstRuneB) if typeA == typeB { if typeA == _typeString { result := strings.Compare(a, b) if result != 0 { return result } } else if typeA == _typeNumber { intA, _ := strconv.Atoi(a) intB, _ := strconv.Atoi(b) if intA > intB { return 1 } else if intA < intB { return -1 } } } else { // components of different types if typeA != _typeString && typeB == _typeString { // 1.2.0 > 1.2rc1 return 1 } else if typeA == _typeString && typeB != _typeString { // 1.2rc1 < 1.2.0 return -1 } else { // One is a number and the other is a period. The period // is invalid. if typeA == _typeNumber { return 1 } else { return -1 } } } } // The versions are equal up to the point where they both still have // parts. Lets check to see if one is larger than the other. if (len(partsA) == len(partsB)) { return 0; // the two strings are identical } // Lets get the next part of the larger version string // Note that 'n' already holds the index of the part we want. var shorterResult, longerResult int var missingPartType charType // ('missing' as in "missing in shorter version") if len(partsA) > len(partsB) { firstRuneAn, _ := utf8.DecodeRuneInString(partsA[n]) missingPartType = classifyChar(firstRuneAn) shorterResult = -1 longerResult = 1 } else { firstRuneBn, _ := utf8.DecodeRuneInString(partsB[n]) missingPartType = classifyChar(firstRuneBn) shorterResult = 1 longerResult = -1 } if missingPartType == _typeString { // 1.5 > 1.5b3 return shorterResult } else { // 1.5.1 > 1.5 return longerResult } }