1package semver 2 3import ( 4 "errors" 5 "fmt" 6 "strconv" 7 "strings" 8) 9 10const ( 11 numbers string = "0123456789" 12 alphas = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-" 13 alphanum = alphas + numbers 14) 15 16// SpecVersion is the latest fully supported spec version of semver 17var SpecVersion = Version{ 18 Major: 2, 19 Minor: 0, 20 Patch: 0, 21} 22 23// Version represents a semver compatible version 24type Version struct { 25 Major uint64 26 Minor uint64 27 Patch uint64 28 Pre []PRVersion 29 Build []string //No Precedence 30} 31 32// Version to string 33func (v Version) String() string { 34 b := make([]byte, 0, 5) 35 b = strconv.AppendUint(b, v.Major, 10) 36 b = append(b, '.') 37 b = strconv.AppendUint(b, v.Minor, 10) 38 b = append(b, '.') 39 b = strconv.AppendUint(b, v.Patch, 10) 40 41 if len(v.Pre) > 0 { 42 b = append(b, '-') 43 b = append(b, v.Pre[0].String()...) 44 45 for _, pre := range v.Pre[1:] { 46 b = append(b, '.') 47 b = append(b, pre.String()...) 48 } 49 } 50 51 if len(v.Build) > 0 { 52 b = append(b, '+') 53 b = append(b, v.Build[0]...) 54 55 for _, build := range v.Build[1:] { 56 b = append(b, '.') 57 b = append(b, build...) 58 } 59 } 60 61 return string(b) 62} 63 64// FinalizeVersion discards prerelease and build number and only returns 65// major, minor and patch number. 66func (v Version) FinalizeVersion() string { 67 b := make([]byte, 0, 5) 68 b = strconv.AppendUint(b, v.Major, 10) 69 b = append(b, '.') 70 b = strconv.AppendUint(b, v.Minor, 10) 71 b = append(b, '.') 72 b = strconv.AppendUint(b, v.Patch, 10) 73 return string(b) 74} 75 76// Equals checks if v is equal to o. 77func (v Version) Equals(o Version) bool { 78 return (v.Compare(o) == 0) 79} 80 81// EQ checks if v is equal to o. 82func (v Version) EQ(o Version) bool { 83 return (v.Compare(o) == 0) 84} 85 86// NE checks if v is not equal to o. 87func (v Version) NE(o Version) bool { 88 return (v.Compare(o) != 0) 89} 90 91// GT checks if v is greater than o. 92func (v Version) GT(o Version) bool { 93 return (v.Compare(o) == 1) 94} 95 96// GTE checks if v is greater than or equal to o. 97func (v Version) GTE(o Version) bool { 98 return (v.Compare(o) >= 0) 99} 100 101// GE checks if v is greater than or equal to o. 102func (v Version) GE(o Version) bool { 103 return (v.Compare(o) >= 0) 104} 105 106// LT checks if v is less than o. 107func (v Version) LT(o Version) bool { 108 return (v.Compare(o) == -1) 109} 110 111// LTE checks if v is less than or equal to o. 112func (v Version) LTE(o Version) bool { 113 return (v.Compare(o) <= 0) 114} 115 116// LE checks if v is less than or equal to o. 117func (v Version) LE(o Version) bool { 118 return (v.Compare(o) <= 0) 119} 120 121// Compare compares Versions v to o: 122// -1 == v is less than o 123// 0 == v is equal to o 124// 1 == v is greater than o 125func (v Version) Compare(o Version) int { 126 if v.Major != o.Major { 127 if v.Major > o.Major { 128 return 1 129 } 130 return -1 131 } 132 if v.Minor != o.Minor { 133 if v.Minor > o.Minor { 134 return 1 135 } 136 return -1 137 } 138 if v.Patch != o.Patch { 139 if v.Patch > o.Patch { 140 return 1 141 } 142 return -1 143 } 144 145 // Quick comparison if a version has no prerelease versions 146 if len(v.Pre) == 0 && len(o.Pre) == 0 { 147 return 0 148 } else if len(v.Pre) == 0 && len(o.Pre) > 0 { 149 return 1 150 } else if len(v.Pre) > 0 && len(o.Pre) == 0 { 151 return -1 152 } 153 154 i := 0 155 for ; i < len(v.Pre) && i < len(o.Pre); i++ { 156 if comp := v.Pre[i].Compare(o.Pre[i]); comp == 0 { 157 continue 158 } else if comp == 1 { 159 return 1 160 } else { 161 return -1 162 } 163 } 164 165 // If all pr versions are the equal but one has further prversion, this one greater 166 if i == len(v.Pre) && i == len(o.Pre) { 167 return 0 168 } else if i == len(v.Pre) && i < len(o.Pre) { 169 return -1 170 } else { 171 return 1 172 } 173 174} 175 176// IncrementPatch increments the patch version 177func (v *Version) IncrementPatch() error { 178 v.Patch++ 179 return nil 180} 181 182// IncrementMinor increments the minor version 183func (v *Version) IncrementMinor() error { 184 v.Minor++ 185 v.Patch = 0 186 return nil 187} 188 189// IncrementMajor increments the major version 190func (v *Version) IncrementMajor() error { 191 v.Major++ 192 v.Minor = 0 193 v.Patch = 0 194 return nil 195} 196 197// Validate validates v and returns error in case 198func (v Version) Validate() error { 199 // Major, Minor, Patch already validated using uint64 200 201 for _, pre := range v.Pre { 202 if !pre.IsNum { //Numeric prerelease versions already uint64 203 if len(pre.VersionStr) == 0 { 204 return fmt.Errorf("Prerelease can not be empty %q", pre.VersionStr) 205 } 206 if !containsOnly(pre.VersionStr, alphanum) { 207 return fmt.Errorf("Invalid character(s) found in prerelease %q", pre.VersionStr) 208 } 209 } 210 } 211 212 for _, build := range v.Build { 213 if len(build) == 0 { 214 return fmt.Errorf("Build meta data can not be empty %q", build) 215 } 216 if !containsOnly(build, alphanum) { 217 return fmt.Errorf("Invalid character(s) found in build meta data %q", build) 218 } 219 } 220 221 return nil 222} 223 224// New is an alias for Parse and returns a pointer, parses version string and returns a validated Version or error 225func New(s string) (*Version, error) { 226 v, err := Parse(s) 227 vp := &v 228 return vp, err 229} 230 231// Make is an alias for Parse, parses version string and returns a validated Version or error 232func Make(s string) (Version, error) { 233 return Parse(s) 234} 235 236// ParseTolerant allows for certain version specifications that do not strictly adhere to semver 237// specs to be parsed by this library. It does so by normalizing versions before passing them to 238// Parse(). It currently trims spaces, removes a "v" prefix, adds a 0 patch number to versions 239// with only major and minor components specified, and removes leading 0s. 240func ParseTolerant(s string) (Version, error) { 241 s = strings.TrimSpace(s) 242 s = strings.TrimPrefix(s, "v") 243 244 // Split into major.minor.(patch+pr+meta) 245 parts := strings.SplitN(s, ".", 3) 246 // Remove leading zeros. 247 for i, p := range parts { 248 if len(p) > 1 { 249 p = strings.TrimLeft(p, "0") 250 if len(p) == 0 || !strings.ContainsAny(p[0:1], "0123456789") { 251 p = "0" + p 252 } 253 parts[i] = p 254 } 255 } 256 // Fill up shortened versions. 257 if len(parts) < 3 { 258 if strings.ContainsAny(parts[len(parts)-1], "+-") { 259 return Version{}, errors.New("Short version cannot contain PreRelease/Build meta data") 260 } 261 for len(parts) < 3 { 262 parts = append(parts, "0") 263 } 264 } 265 s = strings.Join(parts, ".") 266 267 return Parse(s) 268} 269 270// Parse parses version string and returns a validated Version or error 271func Parse(s string) (Version, error) { 272 if len(s) == 0 { 273 return Version{}, errors.New("Version string empty") 274 } 275 276 // Split into major.minor.(patch+pr+meta) 277 parts := strings.SplitN(s, ".", 3) 278 if len(parts) != 3 { 279 return Version{}, errors.New("No Major.Minor.Patch elements found") 280 } 281 282 // Major 283 if !containsOnly(parts[0], numbers) { 284 return Version{}, fmt.Errorf("Invalid character(s) found in major number %q", parts[0]) 285 } 286 if hasLeadingZeroes(parts[0]) { 287 return Version{}, fmt.Errorf("Major number must not contain leading zeroes %q", parts[0]) 288 } 289 major, err := strconv.ParseUint(parts[0], 10, 64) 290 if err != nil { 291 return Version{}, err 292 } 293 294 // Minor 295 if !containsOnly(parts[1], numbers) { 296 return Version{}, fmt.Errorf("Invalid character(s) found in minor number %q", parts[1]) 297 } 298 if hasLeadingZeroes(parts[1]) { 299 return Version{}, fmt.Errorf("Minor number must not contain leading zeroes %q", parts[1]) 300 } 301 minor, err := strconv.ParseUint(parts[1], 10, 64) 302 if err != nil { 303 return Version{}, err 304 } 305 306 v := Version{} 307 v.Major = major 308 v.Minor = minor 309 310 var build, prerelease []string 311 patchStr := parts[2] 312 313 if buildIndex := strings.IndexRune(patchStr, '+'); buildIndex != -1 { 314 build = strings.Split(patchStr[buildIndex+1:], ".") 315 patchStr = patchStr[:buildIndex] 316 } 317 318 if preIndex := strings.IndexRune(patchStr, '-'); preIndex != -1 { 319 prerelease = strings.Split(patchStr[preIndex+1:], ".") 320 patchStr = patchStr[:preIndex] 321 } 322 323 if !containsOnly(patchStr, numbers) { 324 return Version{}, fmt.Errorf("Invalid character(s) found in patch number %q", patchStr) 325 } 326 if hasLeadingZeroes(patchStr) { 327 return Version{}, fmt.Errorf("Patch number must not contain leading zeroes %q", patchStr) 328 } 329 patch, err := strconv.ParseUint(patchStr, 10, 64) 330 if err != nil { 331 return Version{}, err 332 } 333 334 v.Patch = patch 335 336 // Prerelease 337 for _, prstr := range prerelease { 338 parsedPR, err := NewPRVersion(prstr) 339 if err != nil { 340 return Version{}, err 341 } 342 v.Pre = append(v.Pre, parsedPR) 343 } 344 345 // Build meta data 346 for _, str := range build { 347 if len(str) == 0 { 348 return Version{}, errors.New("Build meta data is empty") 349 } 350 if !containsOnly(str, alphanum) { 351 return Version{}, fmt.Errorf("Invalid character(s) found in build meta data %q", str) 352 } 353 v.Build = append(v.Build, str) 354 } 355 356 return v, nil 357} 358 359// MustParse is like Parse but panics if the version cannot be parsed. 360func MustParse(s string) Version { 361 v, err := Parse(s) 362 if err != nil { 363 panic(`semver: Parse(` + s + `): ` + err.Error()) 364 } 365 return v 366} 367 368// PRVersion represents a PreRelease Version 369type PRVersion struct { 370 VersionStr string 371 VersionNum uint64 372 IsNum bool 373} 374 375// NewPRVersion creates a new valid prerelease version 376func NewPRVersion(s string) (PRVersion, error) { 377 if len(s) == 0 { 378 return PRVersion{}, errors.New("Prerelease is empty") 379 } 380 v := PRVersion{} 381 if containsOnly(s, numbers) { 382 if hasLeadingZeroes(s) { 383 return PRVersion{}, fmt.Errorf("Numeric PreRelease version must not contain leading zeroes %q", s) 384 } 385 num, err := strconv.ParseUint(s, 10, 64) 386 387 // Might never be hit, but just in case 388 if err != nil { 389 return PRVersion{}, err 390 } 391 v.VersionNum = num 392 v.IsNum = true 393 } else if containsOnly(s, alphanum) { 394 v.VersionStr = s 395 v.IsNum = false 396 } else { 397 return PRVersion{}, fmt.Errorf("Invalid character(s) found in prerelease %q", s) 398 } 399 return v, nil 400} 401 402// IsNumeric checks if prerelease-version is numeric 403func (v PRVersion) IsNumeric() bool { 404 return v.IsNum 405} 406 407// Compare compares two PreRelease Versions v and o: 408// -1 == v is less than o 409// 0 == v is equal to o 410// 1 == v is greater than o 411func (v PRVersion) Compare(o PRVersion) int { 412 if v.IsNum && !o.IsNum { 413 return -1 414 } else if !v.IsNum && o.IsNum { 415 return 1 416 } else if v.IsNum && o.IsNum { 417 if v.VersionNum == o.VersionNum { 418 return 0 419 } else if v.VersionNum > o.VersionNum { 420 return 1 421 } else { 422 return -1 423 } 424 } else { // both are Alphas 425 if v.VersionStr == o.VersionStr { 426 return 0 427 } else if v.VersionStr > o.VersionStr { 428 return 1 429 } else { 430 return -1 431 } 432 } 433} 434 435// PreRelease version to string 436func (v PRVersion) String() string { 437 if v.IsNum { 438 return strconv.FormatUint(v.VersionNum, 10) 439 } 440 return v.VersionStr 441} 442 443func containsOnly(s string, set string) bool { 444 return strings.IndexFunc(s, func(r rune) bool { 445 return !strings.ContainsRune(set, r) 446 }) == -1 447} 448 449func hasLeadingZeroes(s string) bool { 450 return len(s) > 1 && s[0] == '0' 451} 452 453// NewBuildVersion creates a new valid build version 454func NewBuildVersion(s string) (string, error) { 455 if len(s) == 0 { 456 return "", errors.New("Buildversion is empty") 457 } 458 if !containsOnly(s, alphanum) { 459 return "", fmt.Errorf("Invalid character(s) found in build meta data %q", s) 460 } 461 return s, nil 462} 463 464// FinalizeVersion returns the major, minor and patch number only and discards 465// prerelease and build number. 466func FinalizeVersion(s string) (string, error) { 467 v, err := Parse(s) 468 if err != nil { 469 return "", err 470 } 471 v.Pre = nil 472 v.Build = nil 473 474 finalVer := v.String() 475 return finalVer, nil 476} 477