...

Source file src/github.com/blang/semver/v4/semver.go

Documentation: github.com/blang/semver/v4

     1  package semver
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"strconv"
     7  	"strings"
     8  )
     9  
    10  const (
    11  	numbers  string = "0123456789"
    12  	alphas          = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-"
    13  	alphanum        = alphas + numbers
    14  )
    15  
    16  // SpecVersion is the latest fully supported spec version of semver
    17  var SpecVersion = Version{
    18  	Major: 2,
    19  	Minor: 0,
    20  	Patch: 0,
    21  }
    22  
    23  // Version represents a semver compatible version
    24  type 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
    33  func (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.
    66  func (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.
    77  func (v Version) Equals(o Version) bool {
    78  	return (v.Compare(o) == 0)
    79  }
    80  
    81  // EQ checks if v is equal to o.
    82  func (v Version) EQ(o Version) bool {
    83  	return (v.Compare(o) == 0)
    84  }
    85  
    86  // NE checks if v is not equal to o.
    87  func (v Version) NE(o Version) bool {
    88  	return (v.Compare(o) != 0)
    89  }
    90  
    91  // GT checks if v is greater than o.
    92  func (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.
    97  func (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.
   102  func (v Version) GE(o Version) bool {
   103  	return (v.Compare(o) >= 0)
   104  }
   105  
   106  // LT checks if v is less than o.
   107  func (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.
   112  func (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.
   117  func (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
   125  func (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
   177  func (v *Version) IncrementPatch() error {
   178  	v.Patch++
   179  	return nil
   180  }
   181  
   182  // IncrementMinor increments the minor version
   183  func (v *Version) IncrementMinor() error {
   184  	v.Minor++
   185  	v.Patch = 0
   186  	return nil
   187  }
   188  
   189  // IncrementMajor increments the major version
   190  func (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
   198  func (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
   225  func 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
   232  func 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.
   240  func 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
   271  func 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.
   360  func 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
   369  type PRVersion struct {
   370  	VersionStr string
   371  	VersionNum uint64
   372  	IsNum      bool
   373  }
   374  
   375  // NewPRVersion creates a new valid prerelease version
   376  func 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
   403  func (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
   411  func (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
   436  func (v PRVersion) String() string {
   437  	if v.IsNum {
   438  		return strconv.FormatUint(v.VersionNum, 10)
   439  	}
   440  	return v.VersionStr
   441  }
   442  
   443  func 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  
   449  func hasLeadingZeroes(s string) bool {
   450  	return len(s) > 1 && s[0] == '0'
   451  }
   452  
   453  // NewBuildVersion creates a new valid build version
   454  func 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.
   466  func 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  

View as plain text