11 numbers string = "0123456789"
12 alphas = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-"
13 alphanum = alphas + numbers
16 // SpecVersion is the latest fully supported spec version of semver
17 var SpecVersion = Version{
23 // Version represents a semver compatible version
29 Build []string //No Precendence
33 func (v Version) String() string {
34 b := make([]byte, 0, 5)
35 b = strconv.AppendUint(b, v.Major, 10)
37 b = strconv.AppendUint(b, v.Minor, 10)
39 b = strconv.AppendUint(b, v.Patch, 10)
43 b = append(b, v.Pre[0].String()...)
45 for _, pre := range v.Pre[1:] {
47 b = append(b, pre.String()...)
53 b = append(b, v.Build[0]...)
55 for _, build := range v.Build[1:] {
57 b = append(b, build...)
64 // Equals checks if v is equal to o.
65 func (v Version) Equals(o Version) bool {
66 return (v.Compare(o) == 0)
69 // EQ checks if v is equal to o.
70 func (v Version) EQ(o Version) bool {
71 return (v.Compare(o) == 0)
74 // NE checks if v is not equal to o.
75 func (v Version) NE(o Version) bool {
76 return (v.Compare(o) != 0)
79 // GT checks if v is greater than o.
80 func (v Version) GT(o Version) bool {
81 return (v.Compare(o) == 1)
84 // GTE checks if v is greater than or equal to o.
85 func (v Version) GTE(o Version) bool {
86 return (v.Compare(o) >= 0)
89 // GE checks if v is greater than or equal to o.
90 func (v Version) GE(o Version) bool {
91 return (v.Compare(o) >= 0)
94 // LT checks if v is less than o.
95 func (v Version) LT(o Version) bool {
96 return (v.Compare(o) == -1)
99 // LTE checks if v is less than or equal to o.
100 func (v Version) LTE(o Version) bool {
101 return (v.Compare(o) <= 0)
104 // LE checks if v is less than or equal to o.
105 func (v Version) LE(o Version) bool {
106 return (v.Compare(o) <= 0)
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
113 func (v Version) Compare(o Version) int {
114 if v.Major != o.Major {
115 if v.Major > o.Major {
120 if v.Minor != o.Minor {
121 if v.Minor > o.Minor {
126 if v.Patch != o.Patch {
127 if v.Patch > o.Patch {
133 // Quick comparison if a version has no prerelease versions
134 if len(v.Pre) == 0 && len(o.Pre) == 0 {
136 } else if len(v.Pre) == 0 && len(o.Pre) > 0 {
138 } else if len(v.Pre) > 0 && len(o.Pre) == 0 {
143 for ; i < len(v.Pre) && i < len(o.Pre); i++ {
144 if comp := v.Pre[i].Compare(o.Pre[i]); comp == 0 {
146 } else if comp == 1 {
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) {
156 } else if i == len(v.Pre) && i < len(o.Pre) {
164 // Validate validates v and returns error in case
165 func (v Version) Validate() error {
166 // Major, Minor, Patch already validated using uint64
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)
173 if !containsOnly(pre.VersionStr, alphanum) {
174 return fmt.Errorf("Invalid character(s) found in prerelease %q", pre.VersionStr)
179 for _, build := range v.Build {
181 return fmt.Errorf("Build meta data can not be empty %q", build)
183 if !containsOnly(build, alphanum) {
184 return fmt.Errorf("Invalid character(s) found in build meta data %q", build)
191 // New is an alias for Parse and returns a pointer, parses version string and returns a validated Version or error
192 func New(s string) (vp *Version, err error) {
198 // Make is an alias for Parse, parses version string and returns a validated Version or error
199 func Make(s string) (Version, error) {
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
207 func ParseTolerant(s string) (Version, error) {
208 s = strings.TrimSpace(s)
209 s = strings.TrimPrefix(s, "v")
211 // Split into major.minor.(patch+pr+meta)
212 parts := strings.SplitN(s, ".", 3)
214 if strings.ContainsAny(parts[len(parts)-1], "+-") {
215 return Version{}, errors.New("Short version cannot contain PreRelease/Build meta data")
218 parts = append(parts, "0")
220 s = strings.Join(parts, ".")
226 // Parse parses version string and returns a validated Version or error
227 func Parse(s string) (Version, error) {
229 return Version{}, errors.New("Version string empty")
232 // Split into major.minor.(patch+pr+meta)
233 parts := strings.SplitN(s, ".", 3)
235 return Version{}, errors.New("No Major.Minor.Patch elements found")
239 if !containsOnly(parts[0], numbers) {
240 return Version{}, fmt.Errorf("Invalid character(s) found in major number %q", parts[0])
242 if hasLeadingZeroes(parts[0]) {
243 return Version{}, fmt.Errorf("Major number must not contain leading zeroes %q", parts[0])
245 major, err := strconv.ParseUint(parts[0], 10, 64)
247 return Version{}, err
251 if !containsOnly(parts[1], numbers) {
252 return Version{}, fmt.Errorf("Invalid character(s) found in minor number %q", parts[1])
254 if hasLeadingZeroes(parts[1]) {
255 return Version{}, fmt.Errorf("Minor number must not contain leading zeroes %q", parts[1])
257 minor, err := strconv.ParseUint(parts[1], 10, 64)
259 return Version{}, err
266 var build, prerelease []string
269 if buildIndex := strings.IndexRune(patchStr, '+'); buildIndex != -1 {
270 build = strings.Split(patchStr[buildIndex+1:], ".")
271 patchStr = patchStr[:buildIndex]
274 if preIndex := strings.IndexRune(patchStr, '-'); preIndex != -1 {
275 prerelease = strings.Split(patchStr[preIndex+1:], ".")
276 patchStr = patchStr[:preIndex]
279 if !containsOnly(patchStr, numbers) {
280 return Version{}, fmt.Errorf("Invalid character(s) found in patch number %q", patchStr)
282 if hasLeadingZeroes(patchStr) {
283 return Version{}, fmt.Errorf("Patch number must not contain leading zeroes %q", patchStr)
285 patch, err := strconv.ParseUint(patchStr, 10, 64)
287 return Version{}, err
293 for _, prstr := range prerelease {
294 parsedPR, err := NewPRVersion(prstr)
296 return Version{}, err
298 v.Pre = append(v.Pre, parsedPR)
302 for _, str := range build {
304 return Version{}, errors.New("Build meta data is empty")
306 if !containsOnly(str, alphanum) {
307 return Version{}, fmt.Errorf("Invalid character(s) found in build meta data %q", str)
309 v.Build = append(v.Build, str)
315 // MustParse is like Parse but panics if the version cannot be parsed.
316 func MustParse(s string) Version {
319 panic(`semver: Parse(` + s + `): ` + err.Error())
324 // PRVersion represents a PreRelease Version
325 type PRVersion struct {
331 // NewPRVersion creates a new valid prerelease version
332 func NewPRVersion(s string) (PRVersion, error) {
334 return PRVersion{}, errors.New("Prerelease is empty")
337 if containsOnly(s, numbers) {
338 if hasLeadingZeroes(s) {
339 return PRVersion{}, fmt.Errorf("Numeric PreRelease version must not contain leading zeroes %q", s)
341 num, err := strconv.ParseUint(s, 10, 64)
343 // Might never be hit, but just in case
345 return PRVersion{}, err
349 } else if containsOnly(s, alphanum) {
353 return PRVersion{}, fmt.Errorf("Invalid character(s) found in prerelease %q", s)
358 // IsNumeric checks if prerelease-version is numeric
359 func (v PRVersion) IsNumeric() bool {
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
367 func (v PRVersion) Compare(o PRVersion) int {
368 if v.IsNum && !o.IsNum {
370 } else if !v.IsNum && o.IsNum {
372 } else if v.IsNum && o.IsNum {
373 if v.VersionNum == o.VersionNum {
375 } else if v.VersionNum > o.VersionNum {
380 } else { // both are Alphas
381 if v.VersionStr == o.VersionStr {
383 } else if v.VersionStr > o.VersionStr {
391 // PreRelease version to string
392 func (v PRVersion) String() string {
394 return strconv.FormatUint(v.VersionNum, 10)
399 func containsOnly(s string, set string) bool {
400 return strings.IndexFunc(s, func(r rune) bool {
401 return !strings.ContainsRune(set, r)
405 func hasLeadingZeroes(s string) bool {
406 return len(s) > 1 && s[0] == '0'
409 // NewBuildVersion creates a new valid build version
410 func NewBuildVersion(s string) (string, error) {
412 return "", errors.New("Buildversion is empty")
414 if !containsOnly(s, alphanum) {
415 return "", fmt.Errorf("Invalid character(s) found in build meta data %q", s)