1package version 2 3import ( 4 "bytes" 5 "fmt" 6 "reflect" 7 "regexp" 8 "strconv" 9 "strings" 10) 11 12// The compiled regular expression used to test the validity of a version. 13var versionRegexp *regexp.Regexp 14 15// The raw regular expression string used for testing the validity 16// of a version. 17const VersionRegexpRaw string = `v?([0-9]+(\.[0-9]+)*?)` + 18 `(-?([0-9A-Za-z\-~]+(\.[0-9A-Za-z\-~]+)*))?` + 19 `(\+([0-9A-Za-z\-~]+(\.[0-9A-Za-z\-~]+)*))?` + 20 `?` 21 22// Version represents a single version. 23type Version struct { 24 metadata string 25 pre string 26 segments []int64 27 si int 28} 29 30func init() { 31 versionRegexp = regexp.MustCompile("^" + VersionRegexpRaw + "$") 32} 33 34// NewVersion parses the given version and returns a new 35// Version. 36func NewVersion(v string) (*Version, error) { 37 matches := versionRegexp.FindStringSubmatch(v) 38 if matches == nil { 39 return nil, fmt.Errorf("Malformed version: %s", v) 40 } 41 segmentsStr := strings.Split(matches[1], ".") 42 segments := make([]int64, len(segmentsStr)) 43 si := 0 44 for i, str := range segmentsStr { 45 val, err := strconv.ParseInt(str, 10, 64) 46 if err != nil { 47 return nil, fmt.Errorf( 48 "Error parsing version: %s", err) 49 } 50 51 segments[i] = int64(val) 52 si++ 53 } 54 55 // Even though we could support more than three segments, if we 56 // got less than three, pad it with 0s. This is to cover the basic 57 // default usecase of semver, which is MAJOR.MINOR.PATCH at the minimum 58 for i := len(segments); i < 3; i++ { 59 segments = append(segments, 0) 60 } 61 62 return &Version{ 63 metadata: matches[7], 64 pre: matches[4], 65 segments: segments, 66 si: si, 67 }, nil 68} 69 70// Must is a helper that wraps a call to a function returning (*Version, error) 71// and panics if error is non-nil. 72func Must(v *Version, err error) *Version { 73 if err != nil { 74 panic(err) 75 } 76 77 return v 78} 79 80// Compare compares this version to another version. This 81// returns -1, 0, or 1 if this version is smaller, equal, 82// or larger than the other version, respectively. 83// 84// If you want boolean results, use the LessThan, Equal, 85// or GreaterThan methods. 86func (v *Version) Compare(other *Version) int { 87 // A quick, efficient equality check 88 if v.String() == other.String() { 89 return 0 90 } 91 92 segmentsSelf := v.Segments64() 93 segmentsOther := other.Segments64() 94 95 // If the segments are the same, we must compare on prerelease info 96 if reflect.DeepEqual(segmentsSelf, segmentsOther) { 97 preSelf := v.Prerelease() 98 preOther := other.Prerelease() 99 if preSelf == "" && preOther == "" { 100 return 0 101 } 102 if preSelf == "" { 103 return 1 104 } 105 if preOther == "" { 106 return -1 107 } 108 109 return comparePrereleases(preSelf, preOther) 110 } 111 112 // Get the highest specificity (hS), or if they're equal, just use segmentSelf length 113 lenSelf := len(segmentsSelf) 114 lenOther := len(segmentsOther) 115 hS := lenSelf 116 if lenSelf < lenOther { 117 hS = lenOther 118 } 119 // Compare the segments 120 // Because a constraint could have more/less specificity than the version it's 121 // checking, we need to account for a lopsided or jagged comparison 122 for i := 0; i < hS; i++ { 123 if i > lenSelf-1 { 124 // This means Self had the lower specificity 125 // Check to see if the remaining segments in Other are all zeros 126 if !allZero(segmentsOther[i:]) { 127 // if not, it means that Other has to be greater than Self 128 return -1 129 } 130 break 131 } else if i > lenOther-1 { 132 // this means Other had the lower specificity 133 // Check to see if the remaining segments in Self are all zeros - 134 if !allZero(segmentsSelf[i:]) { 135 //if not, it means that Self has to be greater than Other 136 return 1 137 } 138 break 139 } 140 lhs := segmentsSelf[i] 141 rhs := segmentsOther[i] 142 if lhs == rhs { 143 continue 144 } else if lhs < rhs { 145 return -1 146 } 147 // Otherwis, rhs was > lhs, they're not equal 148 return 1 149 } 150 151 // if we got this far, they're equal 152 return 0 153} 154 155func allZero(segs []int64) bool { 156 for _, s := range segs { 157 if s != 0 { 158 return false 159 } 160 } 161 return true 162} 163 164func comparePart(preSelf string, preOther string) int { 165 if preSelf == preOther { 166 return 0 167 } 168 169 var selfInt int64 170 selfNumeric := true 171 selfInt, err := strconv.ParseInt(preSelf, 10, 64) 172 if err != nil { 173 selfNumeric = false 174 } 175 176 var otherInt int64 177 otherNumeric := true 178 otherInt, err = strconv.ParseInt(preOther, 10, 64) 179 if err != nil { 180 otherNumeric = false 181 } 182 183 // if a part is empty, we use the other to decide 184 if preSelf == "" { 185 if otherNumeric { 186 return -1 187 } 188 return 1 189 } 190 191 if preOther == "" { 192 if selfNumeric { 193 return 1 194 } 195 return -1 196 } 197 198 if selfNumeric && !otherNumeric { 199 return -1 200 } else if !selfNumeric && otherNumeric { 201 return 1 202 } else if !selfNumeric && !otherNumeric && preSelf > preOther { 203 return 1 204 } else if selfInt > otherInt { 205 return 1 206 } 207 208 return -1 209} 210 211func comparePrereleases(v string, other string) int { 212 // the same pre release! 213 if v == other { 214 return 0 215 } 216 217 // split both pre releases for analyse their parts 218 selfPreReleaseMeta := strings.Split(v, ".") 219 otherPreReleaseMeta := strings.Split(other, ".") 220 221 selfPreReleaseLen := len(selfPreReleaseMeta) 222 otherPreReleaseLen := len(otherPreReleaseMeta) 223 224 biggestLen := otherPreReleaseLen 225 if selfPreReleaseLen > otherPreReleaseLen { 226 biggestLen = selfPreReleaseLen 227 } 228 229 // loop for parts to find the first difference 230 for i := 0; i < biggestLen; i = i + 1 { 231 partSelfPre := "" 232 if i < selfPreReleaseLen { 233 partSelfPre = selfPreReleaseMeta[i] 234 } 235 236 partOtherPre := "" 237 if i < otherPreReleaseLen { 238 partOtherPre = otherPreReleaseMeta[i] 239 } 240 241 compare := comparePart(partSelfPre, partOtherPre) 242 // if parts are equals, continue the loop 243 if compare != 0 { 244 return compare 245 } 246 } 247 248 return 0 249} 250 251// Equal tests if two versions are equal. 252func (v *Version) Equal(o *Version) bool { 253 return v.Compare(o) == 0 254} 255 256// GreaterThan tests if this version is greater than another version. 257func (v *Version) GreaterThan(o *Version) bool { 258 return v.Compare(o) > 0 259} 260 261// LessThan tests if this version is less than another version. 262func (v *Version) LessThan(o *Version) bool { 263 return v.Compare(o) < 0 264} 265 266// Metadata returns any metadata that was part of the version 267// string. 268// 269// Metadata is anything that comes after the "+" in the version. 270// For example, with "1.2.3+beta", the metadata is "beta". 271func (v *Version) Metadata() string { 272 return v.metadata 273} 274 275// Prerelease returns any prerelease data that is part of the version, 276// or blank if there is no prerelease data. 277// 278// Prerelease information is anything that comes after the "-" in the 279// version (but before any metadata). For example, with "1.2.3-beta", 280// the prerelease information is "beta". 281func (v *Version) Prerelease() string { 282 return v.pre 283} 284 285// Segments returns the numeric segments of the version as a slice of ints. 286// 287// This excludes any metadata or pre-release information. For example, 288// for a version "1.2.3-beta", segments will return a slice of 289// 1, 2, 3. 290func (v *Version) Segments() []int { 291 segmentSlice := make([]int, len(v.segments)) 292 for i, v := range v.segments { 293 segmentSlice[i] = int(v) 294 } 295 return segmentSlice 296} 297 298// Segments64 returns the numeric segments of the version as a slice of int64s. 299// 300// This excludes any metadata or pre-release information. For example, 301// for a version "1.2.3-beta", segments will return a slice of 302// 1, 2, 3. 303func (v *Version) Segments64() []int64 { 304 return v.segments 305} 306 307// String returns the full version string included pre-release 308// and metadata information. 309func (v *Version) String() string { 310 var buf bytes.Buffer 311 fmtParts := make([]string, len(v.segments)) 312 for i, s := range v.segments { 313 // We can ignore err here since we've pre-parsed the values in segments 314 str := strconv.FormatInt(s, 10) 315 fmtParts[i] = str 316 } 317 fmt.Fprintf(&buf, strings.Join(fmtParts, ".")) 318 if v.pre != "" { 319 fmt.Fprintf(&buf, "-%s", v.pre) 320 } 321 if v.metadata != "" { 322 fmt.Fprintf(&buf, "+%s", v.metadata) 323 } 324 325 return buf.String() 326} 327