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 Precendence 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// Equals checks if v is equal to o. 65func (v Version) Equals(o Version) bool { 66 return (v.Compare(o) == 0) 67} 68 69// EQ checks if v is equal to o. 70func (v Version) EQ(o Version) bool { 71 return (v.Compare(o) == 0) 72} 73 74// NE checks if v is not equal to o. 75func (v Version) NE(o Version) bool { 76 return (v.Compare(o) != 0) 77} 78 79// GT checks if v is greater than o. 80func (v Version) GT(o Version) bool { 81 return (v.Compare(o) == 1) 82} 83 84// GTE checks if v is greater than or equal to o. 85func (v Version) GTE(o Version) bool { 86 return (v.Compare(o) >= 0) 87} 88 89// GE checks if v is greater than or equal to o. 90func (v Version) GE(o Version) bool { 91 return (v.Compare(o) >= 0) 92} 93 94// LT checks if v is less than o. 95func (v Version) LT(o Version) bool { 96 return (v.Compare(o) == -1) 97} 98 99// LTE checks if v is less than or equal to o. 100func (v Version) LTE(o Version) bool { 101 return (v.Compare(o) <= 0) 102} 103 104// LE checks if v is less than or equal to o. 105func (v Version) LE(o Version) bool { 106 return (v.Compare(o) <= 0) 107} 108 109// Compare compares Versions v to o: 110// -1 == v is less than o 111// 0 == v is equal to o 112// 1 == v is greater than o 113func (v Version) Compare(o Version) int { 114 if v.Major != o.Major { 115 if v.Major > o.Major { 116 return 1 117 } 118 return -1 119 } 120 if v.Minor != o.Minor { 121 if v.Minor > o.Minor { 122 return 1 123 } 124 return -1 125 } 126 if v.Patch != o.Patch { 127 if v.Patch > o.Patch { 128 return 1 129 } 130 return -1 131 } 132 133 // Quick comparison if a version has no prerelease versions 134 if len(v.Pre) == 0 && len(o.Pre) == 0 { 135 return 0 136 } else if len(v.Pre) == 0 && len(o.Pre) > 0 { 137 return 1 138 } else if len(v.Pre) > 0 && len(o.Pre) == 0 { 139 return -1 140 } 141 142 i := 0 143 for ; i < len(v.Pre) && i < len(o.Pre); i++ { 144 if comp := v.Pre[i].Compare(o.Pre[i]); comp == 0 { 145 continue 146 } else if comp == 1 { 147 return 1 148 } else { 149 return -1 150 } 151 } 152 153 // If all pr versions are the equal but one has further prversion, this one greater 154 if i == len(v.Pre) && i == len(o.Pre) { 155 return 0 156 } else if i == len(v.Pre) && i < len(o.Pre) { 157 return -1 158 } else { 159 return 1 160 } 161 162} 163 164// Validate validates v and returns error in case 165func (v Version) Validate() error { 166 // Major, Minor, Patch already validated using uint64 167 168 for _, pre := range v.Pre { 169 if !pre.IsNum { //Numeric prerelease versions already uint64 170 if len(pre.VersionStr) == 0 { 171 return fmt.Errorf("Prerelease can not be empty %q", pre.VersionStr) 172 } 173 if !containsOnly(pre.VersionStr, alphanum) { 174 return fmt.Errorf("Invalid character(s) found in prerelease %q", pre.VersionStr) 175 } 176 } 177 } 178 179 for _, build := range v.Build { 180 if len(build) == 0 { 181 return fmt.Errorf("Build meta data can not be empty %q", build) 182 } 183 if !containsOnly(build, alphanum) { 184 return fmt.Errorf("Invalid character(s) found in build meta data %q", build) 185 } 186 } 187 188 return nil 189} 190 191// New is an alias for Parse and returns a pointer, parses version string and returns a validated Version or error 192func New(s string) (vp *Version, err error) { 193 v, err := Parse(s) 194 vp = &v 195 return 196} 197 198// Make is an alias for Parse, parses version string and returns a validated Version or error 199func Make(s string) (Version, error) { 200 return Parse(s) 201} 202 203// ParseTolerant allows for certain version specifications that do not strictly adhere to semver 204// specs to be parsed by this library. It does so by normalizing versions before passing them to 205// Parse(). It currently trims spaces, removes a "v" prefix, and adds a 0 patch number to versions 206// with only major and minor components specified 207func ParseTolerant(s string) (Version, error) { 208 s = strings.TrimSpace(s) 209 s = strings.TrimPrefix(s, "v") 210 211 // Split into major.minor.(patch+pr+meta) 212 parts := strings.SplitN(s, ".", 3) 213 if len(parts) < 3 { 214 if strings.ContainsAny(parts[len(parts)-1], "+-") { 215 return Version{}, errors.New("Short version cannot contain PreRelease/Build meta data") 216 } 217 for len(parts) < 3 { 218 parts = append(parts, "0") 219 } 220 s = strings.Join(parts, ".") 221 } 222 223 return Parse(s) 224} 225 226// Parse parses version string and returns a validated Version or error 227func Parse(s string) (Version, error) { 228 if len(s) == 0 { 229 return Version{}, errors.New("Version string empty") 230 } 231 232 // Split into major.minor.(patch+pr+meta) 233 parts := strings.SplitN(s, ".", 3) 234 if len(parts) != 3 { 235 return Version{}, errors.New("No Major.Minor.Patch elements found") 236 } 237 238 // Major 239 if !containsOnly(parts[0], numbers) { 240 return Version{}, fmt.Errorf("Invalid character(s) found in major number %q", parts[0]) 241 } 242 if hasLeadingZeroes(parts[0]) { 243 return Version{}, fmt.Errorf("Major number must not contain leading zeroes %q", parts[0]) 244 } 245 major, err := strconv.ParseUint(parts[0], 10, 64) 246 if err != nil { 247 return Version{}, err 248 } 249 250 // Minor 251 if !containsOnly(parts[1], numbers) { 252 return Version{}, fmt.Errorf("Invalid character(s) found in minor number %q", parts[1]) 253 } 254 if hasLeadingZeroes(parts[1]) { 255 return Version{}, fmt.Errorf("Minor number must not contain leading zeroes %q", parts[1]) 256 } 257 minor, err := strconv.ParseUint(parts[1], 10, 64) 258 if err != nil { 259 return Version{}, err 260 } 261 262 v := Version{} 263 v.Major = major 264 v.Minor = minor 265 266 var build, prerelease []string 267 patchStr := parts[2] 268 269 if buildIndex := strings.IndexRune(patchStr, '+'); buildIndex != -1 { 270 build = strings.Split(patchStr[buildIndex+1:], ".") 271 patchStr = patchStr[:buildIndex] 272 } 273 274 if preIndex := strings.IndexRune(patchStr, '-'); preIndex != -1 { 275 prerelease = strings.Split(patchStr[preIndex+1:], ".") 276 patchStr = patchStr[:preIndex] 277 } 278 279 if !containsOnly(patchStr, numbers) { 280 return Version{}, fmt.Errorf("Invalid character(s) found in patch number %q", patchStr) 281 } 282 if hasLeadingZeroes(patchStr) { 283 return Version{}, fmt.Errorf("Patch number must not contain leading zeroes %q", patchStr) 284 } 285 patch, err := strconv.ParseUint(patchStr, 10, 64) 286 if err != nil { 287 return Version{}, err 288 } 289 290 v.Patch = patch 291 292 // Prerelease 293 for _, prstr := range prerelease { 294 parsedPR, err := NewPRVersion(prstr) 295 if err != nil { 296 return Version{}, err 297 } 298 v.Pre = append(v.Pre, parsedPR) 299 } 300 301 // Build meta data 302 for _, str := range build { 303 if len(str) == 0 { 304 return Version{}, errors.New("Build meta data is empty") 305 } 306 if !containsOnly(str, alphanum) { 307 return Version{}, fmt.Errorf("Invalid character(s) found in build meta data %q", str) 308 } 309 v.Build = append(v.Build, str) 310 } 311 312 return v, nil 313} 314 315// MustParse is like Parse but panics if the version cannot be parsed. 316func MustParse(s string) Version { 317 v, err := Parse(s) 318 if err != nil { 319 panic(`semver: Parse(` + s + `): ` + err.Error()) 320 } 321 return v 322} 323 324// PRVersion represents a PreRelease Version 325type PRVersion struct { 326 VersionStr string 327 VersionNum uint64 328 IsNum bool 329} 330 331// NewPRVersion creates a new valid prerelease version 332func NewPRVersion(s string) (PRVersion, error) { 333 if len(s) == 0 { 334 return PRVersion{}, errors.New("Prerelease is empty") 335 } 336 v := PRVersion{} 337 if containsOnly(s, numbers) { 338 if hasLeadingZeroes(s) { 339 return PRVersion{}, fmt.Errorf("Numeric PreRelease version must not contain leading zeroes %q", s) 340 } 341 num, err := strconv.ParseUint(s, 10, 64) 342 343 // Might never be hit, but just in case 344 if err != nil { 345 return PRVersion{}, err 346 } 347 v.VersionNum = num 348 v.IsNum = true 349 } else if containsOnly(s, alphanum) { 350 v.VersionStr = s 351 v.IsNum = false 352 } else { 353 return PRVersion{}, fmt.Errorf("Invalid character(s) found in prerelease %q", s) 354 } 355 return v, nil 356} 357 358// IsNumeric checks if prerelease-version is numeric 359func (v PRVersion) IsNumeric() bool { 360 return v.IsNum 361} 362 363// Compare compares two PreRelease Versions v and o: 364// -1 == v is less than o 365// 0 == v is equal to o 366// 1 == v is greater than o 367func (v PRVersion) Compare(o PRVersion) int { 368 if v.IsNum && !o.IsNum { 369 return -1 370 } else if !v.IsNum && o.IsNum { 371 return 1 372 } else if v.IsNum && o.IsNum { 373 if v.VersionNum == o.VersionNum { 374 return 0 375 } else if v.VersionNum > o.VersionNum { 376 return 1 377 } else { 378 return -1 379 } 380 } else { // both are Alphas 381 if v.VersionStr == o.VersionStr { 382 return 0 383 } else if v.VersionStr > o.VersionStr { 384 return 1 385 } else { 386 return -1 387 } 388 } 389} 390 391// PreRelease version to string 392func (v PRVersion) String() string { 393 if v.IsNum { 394 return strconv.FormatUint(v.VersionNum, 10) 395 } 396 return v.VersionStr 397} 398 399func containsOnly(s string, set string) bool { 400 return strings.IndexFunc(s, func(r rune) bool { 401 return !strings.ContainsRune(set, r) 402 }) == -1 403} 404 405func hasLeadingZeroes(s string) bool { 406 return len(s) > 1 && s[0] == '0' 407} 408 409// NewBuildVersion creates a new valid build version 410func NewBuildVersion(s string) (string, error) { 411 if len(s) == 0 { 412 return "", errors.New("Buildversion is empty") 413 } 414 if !containsOnly(s, alphanum) { 415 return "", fmt.Errorf("Invalid character(s) found in build meta data %q", s) 416 } 417 return s, nil 418} 419