/
versionutil.go
152 lines (135 loc) · 4.07 KB
/
versionutil.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
package versionutil
import (
"fmt"
"regexp"
"strconv"
"strings"
"github.com/coreos/go-semver/semver"
)
// ByVersion implements sorting versions (by component) using semver if applicable
type ByVersion []string
func (s ByVersion) Len() int {
return len(s)
}
func (s ByVersion) Swap(i, j int) {
s[i], s[j] = s[j], s[i]
}
func (s ByVersion) Less(i, j int) bool {
//we need to compare string by ignoring it's case
return Compare(s[i], s[j], true) < 0
}
// Compare returns 0 if two versions are equal a negative number if this < other and a positive number if this > other
// If this and other are both compliant with semver, then semver sorting rules are used
// Otherwise the versions are compared component-by-component, numerically if both are numeric
// If !strictSort insignificant trailing components are ignored (1.0.0.0 == 1) and the alpha comparison is case-insensitive
func Compare(this string, other string, strictSort bool) int {
// If both versions are compliant with SemVer, use the SemVer comparison rules
thisSemVer, thisSemErr := semver.NewVersion(this)
otherSemVer, otherSemErr := semver.NewVersion(other)
if thisSemErr == nil && otherSemErr == nil {
return thisSemVer.Compare(*otherSemVer)
}
thisVersion := this
otherVersion := other
if !strictSort {
// Unless we need a strict ordering, trailing 0 components of version should be ignored
thisVersion = normalizeForCompare(thisVersion)
otherVersion = normalizeForCompare(otherVersion)
}
thisComponents := strings.Split(thisVersion, ".")
otherComponents := strings.Split(otherVersion, ".")
for i := 0; i < len(thisComponents) && i < len(otherComponents); i++ {
thisNum, errThis := strconv.Atoi(thisComponents[i])
otherNum, errOther := strconv.Atoi(otherComponents[i])
isNumeric := errThis == nil && errOther == nil
if isNumeric {
// If we can compare numbers, compare numbers
if thisNum < otherNum {
return -1
} else if thisNum > otherNum {
return 1
}
} else {
// If either component is not numeric, compare them as text
if thisComponents[i] < otherComponents[i] {
return -1
} else if thisComponents[i] > otherComponents[i] {
return 1
}
}
}
// If all components are equal until we run out of components then the
// version with fewer components is the lesser version
return len(thisComponents) - len(otherComponents)
}
// normalizeForCompare removes components at the end that are numerically equal to 0
func normalizeForCompare(version string) string {
if len(version) == 0 {
return version
}
lenSignificant := len(version)
for i := len(version) - 1; i >= 0; i-- {
if version[i] != '0' && version[i] != '.' {
break
}
if version[i] == '.' {
lenSignificant = i
}
if i == 0 {
lenSignificant = 0
}
}
return version[0:lenSignificant]
}
func IsValidVersion(version string) bool {
_, err := versionOrdinal(strings.TrimSpace(version))
return err == nil
}
// VersionCompare compares two version strings
func VersionCompare(versionl string, versionr string) (result int, err error) {
if versionl, err = versionOrdinal(strings.TrimSpace(versionl)); err != nil {
return 0, err
}
if versionr, err = versionOrdinal(strings.TrimSpace(versionr)); err != nil {
return 0, err
}
if versionl < versionr {
return -1, nil
} else if versionl > versionr {
return 1, nil
} else {
return 0, nil
}
}
func versionOrdinal(version string) (string, error) {
// validate if string is a valid version string
if matched, err := regexp.MatchString("^\\d+(\\.\\d+)*$", version); matched == false || err != nil {
return "", fmt.Errorf("Invalid version string %v", version)
}
// ISO/IEC 14651:2011
const maxByte = 1<<8 - 1
vo := make([]byte, 0, len(version)+8)
j := -1
for i := 0; i < len(version); i++ {
b := version[i]
if '0' > b || b > '9' {
vo = append(vo, b)
j = -1
continue
}
if j == -1 {
vo = append(vo, 0x00)
j = len(vo) - 1
}
if vo[j] == 1 && vo[j+1] == '0' {
vo[j+1] = b
continue
}
if vo[j]+1 > maxByte {
return "", fmt.Errorf("VersionOrdinal: invalid version")
}
vo = append(vo, b)
vo[j]++
}
return string(vo), nil
}