1package semver 2 3import ( 4 "bytes" 5 "encoding/json" 6 "errors" 7 "fmt" 8 "regexp" 9 "strconv" 10 "strings" 11) 12 13// The compiled version of the regex created at init() is cached here so it 14// only needs to be created once. 15var versionRegex *regexp.Regexp 16var validPrereleaseRegex *regexp.Regexp 17 18var ( 19 // ErrInvalidSemVer is returned a version is found to be invalid when 20 // being parsed. 21 ErrInvalidSemVer = errors.New("Invalid Semantic Version") 22 23 // ErrInvalidMetadata is returned when the metadata is an invalid format 24 ErrInvalidMetadata = errors.New("Invalid Metadata string") 25 26 // ErrInvalidPrerelease is returned when the pre-release is an invalid format 27 ErrInvalidPrerelease = errors.New("Invalid Prerelease string") 28) 29 30// SemVerRegex is the regular expression used to parse a semantic version. 31const SemVerRegex string = `v?([0-9]+)(\.[0-9]+)?(\.[0-9]+)?` + 32 `(-([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*))?` + 33 `(\+([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*))?` 34 35// ValidPrerelease is the regular expression which validates 36// both prerelease and metadata values. 37const ValidPrerelease string = `^([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*)` 38 39// Version represents a single semantic version. 40type Version struct { 41 major, minor, patch int64 42 pre string 43 metadata string 44 original string 45} 46 47func init() { 48 versionRegex = regexp.MustCompile("^" + SemVerRegex + "$") 49 validPrereleaseRegex = regexp.MustCompile(ValidPrerelease) 50} 51 52// NewVersion parses a given version and returns an instance of Version or 53// an error if unable to parse the version. 54func NewVersion(v string) (*Version, error) { 55 m := versionRegex.FindStringSubmatch(v) 56 if m == nil { 57 return nil, ErrInvalidSemVer 58 } 59 60 sv := &Version{ 61 metadata: m[8], 62 pre: m[5], 63 original: v, 64 } 65 66 var temp int64 67 temp, err := strconv.ParseInt(m[1], 10, 64) 68 if err != nil { 69 return nil, fmt.Errorf("Error parsing version segment: %s", err) 70 } 71 sv.major = temp 72 73 if m[2] != "" { 74 temp, err = strconv.ParseInt(strings.TrimPrefix(m[2], "."), 10, 64) 75 if err != nil { 76 return nil, fmt.Errorf("Error parsing version segment: %s", err) 77 } 78 sv.minor = temp 79 } else { 80 sv.minor = 0 81 } 82 83 if m[3] != "" { 84 temp, err = strconv.ParseInt(strings.TrimPrefix(m[3], "."), 10, 64) 85 if err != nil { 86 return nil, fmt.Errorf("Error parsing version segment: %s", err) 87 } 88 sv.patch = temp 89 } else { 90 sv.patch = 0 91 } 92 93 return sv, nil 94} 95 96// MustParse parses a given version and panics on error. 97func MustParse(v string) *Version { 98 sv, err := NewVersion(v) 99 if err != nil { 100 panic(err) 101 } 102 return sv 103} 104 105// String converts a Version object to a string. 106// Note, if the original version contained a leading v this version will not. 107// See the Original() method to retrieve the original value. Semantic Versions 108// don't contain a leading v per the spec. Instead it's optional on 109// impelementation. 110func (v *Version) String() string { 111 var buf bytes.Buffer 112 113 fmt.Fprintf(&buf, "%d.%d.%d", v.major, v.minor, v.patch) 114 if v.pre != "" { 115 fmt.Fprintf(&buf, "-%s", v.pre) 116 } 117 if v.metadata != "" { 118 fmt.Fprintf(&buf, "+%s", v.metadata) 119 } 120 121 return buf.String() 122} 123 124// Original returns the original value passed in to be parsed. 125func (v *Version) Original() string { 126 return v.original 127} 128 129// Major returns the major version. 130func (v *Version) Major() int64 { 131 return v.major 132} 133 134// Minor returns the minor version. 135func (v *Version) Minor() int64 { 136 return v.minor 137} 138 139// Patch returns the patch version. 140func (v *Version) Patch() int64 { 141 return v.patch 142} 143 144// Prerelease returns the pre-release version. 145func (v *Version) Prerelease() string { 146 return v.pre 147} 148 149// Metadata returns the metadata on the version. 150func (v *Version) Metadata() string { 151 return v.metadata 152} 153 154// originalVPrefix returns the original 'v' prefix if any. 155func (v *Version) originalVPrefix() string { 156 157 // Note, only lowercase v is supported as a prefix by the parser. 158 if v.original != "" && v.original[:1] == "v" { 159 return v.original[:1] 160 } 161 return "" 162} 163 164// IncPatch produces the next patch version. 165// If the current version does not have prerelease/metadata information, 166// it unsets metadata and prerelease values, increments patch number. 167// If the current version has any of prerelease or metadata information, 168// it unsets both values and keeps curent patch value 169func (v Version) IncPatch() Version { 170 vNext := v 171 // according to http://semver.org/#spec-item-9 172 // Pre-release versions have a lower precedence than the associated normal version. 173 // according to http://semver.org/#spec-item-10 174 // Build metadata SHOULD be ignored when determining version precedence. 175 if v.pre != "" { 176 vNext.metadata = "" 177 vNext.pre = "" 178 } else { 179 vNext.metadata = "" 180 vNext.pre = "" 181 vNext.patch = v.patch + 1 182 } 183 vNext.original = v.originalVPrefix() + "" + vNext.String() 184 return vNext 185} 186 187// IncMinor produces the next minor version. 188// Sets patch to 0. 189// Increments minor number. 190// Unsets metadata. 191// Unsets prerelease status. 192func (v Version) IncMinor() Version { 193 vNext := v 194 vNext.metadata = "" 195 vNext.pre = "" 196 vNext.patch = 0 197 vNext.minor = v.minor + 1 198 vNext.original = v.originalVPrefix() + "" + vNext.String() 199 return vNext 200} 201 202// IncMajor produces the next major version. 203// Sets patch to 0. 204// Sets minor to 0. 205// Increments major number. 206// Unsets metadata. 207// Unsets prerelease status. 208func (v Version) IncMajor() Version { 209 vNext := v 210 vNext.metadata = "" 211 vNext.pre = "" 212 vNext.patch = 0 213 vNext.minor = 0 214 vNext.major = v.major + 1 215 vNext.original = v.originalVPrefix() + "" + vNext.String() 216 return vNext 217} 218 219// SetPrerelease defines the prerelease value. 220// Value must not include the required 'hypen' prefix. 221func (v Version) SetPrerelease(prerelease string) (Version, error) { 222 vNext := v 223 if len(prerelease) > 0 && !validPrereleaseRegex.MatchString(prerelease) { 224 return vNext, ErrInvalidPrerelease 225 } 226 vNext.pre = prerelease 227 vNext.original = v.originalVPrefix() + "" + vNext.String() 228 return vNext, nil 229} 230 231// SetMetadata defines metadata value. 232// Value must not include the required 'plus' prefix. 233func (v Version) SetMetadata(metadata string) (Version, error) { 234 vNext := v 235 if len(metadata) > 0 && !validPrereleaseRegex.MatchString(metadata) { 236 return vNext, ErrInvalidMetadata 237 } 238 vNext.metadata = metadata 239 vNext.original = v.originalVPrefix() + "" + vNext.String() 240 return vNext, nil 241} 242 243// LessThan tests if one version is less than another one. 244func (v *Version) LessThan(o *Version) bool { 245 return v.Compare(o) < 0 246} 247 248// GreaterThan tests if one version is greater than another one. 249func (v *Version) GreaterThan(o *Version) bool { 250 return v.Compare(o) > 0 251} 252 253// Equal tests if two versions are equal to each other. 254// Note, versions can be equal with different metadata since metadata 255// is not considered part of the comparable version. 256func (v *Version) Equal(o *Version) bool { 257 return v.Compare(o) == 0 258} 259 260// Compare compares this version to another one. It returns -1, 0, or 1 if 261// the version smaller, equal, or larger than the other version. 262// 263// Versions are compared by X.Y.Z. Build metadata is ignored. Prerelease is 264// lower than the version without a prerelease. 265func (v *Version) Compare(o *Version) int { 266 // Compare the major, minor, and patch version for differences. If a 267 // difference is found return the comparison. 268 if d := compareSegment(v.Major(), o.Major()); d != 0 { 269 return d 270 } 271 if d := compareSegment(v.Minor(), o.Minor()); d != 0 { 272 return d 273 } 274 if d := compareSegment(v.Patch(), o.Patch()); d != 0 { 275 return d 276 } 277 278 // At this point the major, minor, and patch versions are the same. 279 ps := v.pre 280 po := o.Prerelease() 281 282 if ps == "" && po == "" { 283 return 0 284 } 285 if ps == "" { 286 return 1 287 } 288 if po == "" { 289 return -1 290 } 291 292 return comparePrerelease(ps, po) 293} 294 295// UnmarshalJSON implements JSON.Unmarshaler interface. 296func (v *Version) UnmarshalJSON(b []byte) error { 297 var s string 298 if err := json.Unmarshal(b, &s); err != nil { 299 return err 300 } 301 temp, err := NewVersion(s) 302 if err != nil { 303 return err 304 } 305 v.major = temp.major 306 v.minor = temp.minor 307 v.patch = temp.patch 308 v.pre = temp.pre 309 v.metadata = temp.metadata 310 v.original = temp.original 311 temp = nil 312 return nil 313} 314 315// MarshalJSON implements JSON.Marshaler interface. 316func (v *Version) MarshalJSON() ([]byte, error) { 317 return json.Marshal(v.String()) 318} 319 320func compareSegment(v, o int64) int { 321 if v < o { 322 return -1 323 } 324 if v > o { 325 return 1 326 } 327 328 return 0 329} 330 331func comparePrerelease(v, o string) int { 332 333 // split the prelease versions by their part. The separator, per the spec, 334 // is a . 335 sparts := strings.Split(v, ".") 336 oparts := strings.Split(o, ".") 337 338 // Find the longer length of the parts to know how many loop iterations to 339 // go through. 340 slen := len(sparts) 341 olen := len(oparts) 342 343 l := slen 344 if olen > slen { 345 l = olen 346 } 347 348 // Iterate over each part of the prereleases to compare the differences. 349 for i := 0; i < l; i++ { 350 // Since the lentgh of the parts can be different we need to create 351 // a placeholder. This is to avoid out of bounds issues. 352 stemp := "" 353 if i < slen { 354 stemp = sparts[i] 355 } 356 357 otemp := "" 358 if i < olen { 359 otemp = oparts[i] 360 } 361 362 d := comparePrePart(stemp, otemp) 363 if d != 0 { 364 return d 365 } 366 } 367 368 // Reaching here means two versions are of equal value but have different 369 // metadata (the part following a +). They are not identical in string form 370 // but the version comparison finds them to be equal. 371 return 0 372} 373 374func comparePrePart(s, o string) int { 375 // Fastpath if they are equal 376 if s == o { 377 return 0 378 } 379 380 // When s or o are empty we can use the other in an attempt to determine 381 // the response. 382 if s == "" { 383 if o != "" { 384 return -1 385 } 386 return 1 387 } 388 389 if o == "" { 390 if s != "" { 391 return 1 392 } 393 return -1 394 } 395 396 // When comparing strings "99" is greater than "103". To handle 397 // cases like this we need to detect numbers and compare them. 398 399 oi, n1 := strconv.ParseInt(o, 10, 64) 400 si, n2 := strconv.ParseInt(s, 10, 64) 401 402 // The case where both are strings compare the strings 403 if n1 != nil && n2 != nil { 404 if s > o { 405 return 1 406 } 407 return -1 408 } else if n1 != nil { 409 // o is a string and s is a number 410 return -1 411 } else if n2 != nil { 412 // s is a string and o is a number 413 return 1 414 } 415 // Both are numbers 416 if si > oi { 417 return 1 418 } 419 return -1 420 421} 422