...

Source file src/github.com/Masterminds/semver/v3/version.go

Documentation: github.com/Masterminds/semver/v3

     1  package semver
     2  
     3  import (
     4  	"bytes"
     5  	"database/sql/driver"
     6  	"encoding/json"
     7  	"errors"
     8  	"fmt"
     9  	"regexp"
    10  	"strconv"
    11  	"strings"
    12  )
    13  
    14  // The compiled version of the regex created at init() is cached here so it
    15  // only needs to be created once.
    16  var versionRegex *regexp.Regexp
    17  
    18  var (
    19  	// ErrInvalidSemVer is returned a version is found to be invalid when
    20  	// being parsed.
    21  	ErrInvalidSemVer = errors.New("Invalid Semantic Version")
    22  
    23  	// ErrEmptyString is returned when an empty string is passed in for parsing.
    24  	ErrEmptyString = errors.New("Version string empty")
    25  
    26  	// ErrInvalidCharacters is returned when invalid characters are found as
    27  	// part of a version
    28  	ErrInvalidCharacters = errors.New("Invalid characters in version")
    29  
    30  	// ErrSegmentStartsZero is returned when a version segment starts with 0.
    31  	// This is invalid in SemVer.
    32  	ErrSegmentStartsZero = errors.New("Version segment starts with 0")
    33  
    34  	// ErrInvalidMetadata is returned when the metadata is an invalid format
    35  	ErrInvalidMetadata = errors.New("Invalid Metadata string")
    36  
    37  	// ErrInvalidPrerelease is returned when the pre-release is an invalid format
    38  	ErrInvalidPrerelease = errors.New("Invalid Prerelease string")
    39  )
    40  
    41  // semVerRegex is the regular expression used to parse a semantic version.
    42  const semVerRegex string = `v?([0-9]+)(\.[0-9]+)?(\.[0-9]+)?` +
    43  	`(-([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*))?` +
    44  	`(\+([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*))?`
    45  
    46  // Version represents a single semantic version.
    47  type Version struct {
    48  	major, minor, patch uint64
    49  	pre                 string
    50  	metadata            string
    51  	original            string
    52  }
    53  
    54  func init() {
    55  	versionRegex = regexp.MustCompile("^" + semVerRegex + "$")
    56  }
    57  
    58  const (
    59  	num     string = "0123456789"
    60  	allowed string = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-" + num
    61  )
    62  
    63  // StrictNewVersion parses a given version and returns an instance of Version or
    64  // an error if unable to parse the version. Only parses valid semantic versions.
    65  // Performs checking that can find errors within the version.
    66  // If you want to coerce a version such as 1 or 1.2 and parse it as the 1.x
    67  // releases of semver did, use the NewVersion() function.
    68  func StrictNewVersion(v string) (*Version, error) {
    69  	// Parsing here does not use RegEx in order to increase performance and reduce
    70  	// allocations.
    71  
    72  	if len(v) == 0 {
    73  		return nil, ErrEmptyString
    74  	}
    75  
    76  	// Split the parts into [0]major, [1]minor, and [2]patch,prerelease,build
    77  	parts := strings.SplitN(v, ".", 3)
    78  	if len(parts) != 3 {
    79  		return nil, ErrInvalidSemVer
    80  	}
    81  
    82  	sv := &Version{
    83  		original: v,
    84  	}
    85  
    86  	// check for prerelease or build metadata
    87  	var extra []string
    88  	if strings.ContainsAny(parts[2], "-+") {
    89  		// Start with the build metadata first as it needs to be on the right
    90  		extra = strings.SplitN(parts[2], "+", 2)
    91  		if len(extra) > 1 {
    92  			// build metadata found
    93  			sv.metadata = extra[1]
    94  			parts[2] = extra[0]
    95  		}
    96  
    97  		extra = strings.SplitN(parts[2], "-", 2)
    98  		if len(extra) > 1 {
    99  			// prerelease found
   100  			sv.pre = extra[1]
   101  			parts[2] = extra[0]
   102  		}
   103  	}
   104  
   105  	// Validate the number segments are valid. This includes only having positive
   106  	// numbers and no leading 0's.
   107  	for _, p := range parts {
   108  		if !containsOnly(p, num) {
   109  			return nil, ErrInvalidCharacters
   110  		}
   111  
   112  		if len(p) > 1 && p[0] == '0' {
   113  			return nil, ErrSegmentStartsZero
   114  		}
   115  	}
   116  
   117  	// Extract the major, minor, and patch elements onto the returned Version
   118  	var err error
   119  	sv.major, err = strconv.ParseUint(parts[0], 10, 64)
   120  	if err != nil {
   121  		return nil, err
   122  	}
   123  
   124  	sv.minor, err = strconv.ParseUint(parts[1], 10, 64)
   125  	if err != nil {
   126  		return nil, err
   127  	}
   128  
   129  	sv.patch, err = strconv.ParseUint(parts[2], 10, 64)
   130  	if err != nil {
   131  		return nil, err
   132  	}
   133  
   134  	// No prerelease or build metadata found so returning now as a fastpath.
   135  	if sv.pre == "" && sv.metadata == "" {
   136  		return sv, nil
   137  	}
   138  
   139  	if sv.pre != "" {
   140  		if err = validatePrerelease(sv.pre); err != nil {
   141  			return nil, err
   142  		}
   143  	}
   144  
   145  	if sv.metadata != "" {
   146  		if err = validateMetadata(sv.metadata); err != nil {
   147  			return nil, err
   148  		}
   149  	}
   150  
   151  	return sv, nil
   152  }
   153  
   154  // NewVersion parses a given version and returns an instance of Version or
   155  // an error if unable to parse the version. If the version is SemVer-ish it
   156  // attempts to convert it to SemVer. If you want  to validate it was a strict
   157  // semantic version at parse time see StrictNewVersion().
   158  func NewVersion(v string) (*Version, error) {
   159  	m := versionRegex.FindStringSubmatch(v)
   160  	if m == nil {
   161  		return nil, ErrInvalidSemVer
   162  	}
   163  
   164  	sv := &Version{
   165  		metadata: m[8],
   166  		pre:      m[5],
   167  		original: v,
   168  	}
   169  
   170  	var err error
   171  	sv.major, err = strconv.ParseUint(m[1], 10, 64)
   172  	if err != nil {
   173  		return nil, fmt.Errorf("Error parsing version segment: %s", err)
   174  	}
   175  
   176  	if m[2] != "" {
   177  		sv.minor, err = strconv.ParseUint(strings.TrimPrefix(m[2], "."), 10, 64)
   178  		if err != nil {
   179  			return nil, fmt.Errorf("Error parsing version segment: %s", err)
   180  		}
   181  	} else {
   182  		sv.minor = 0
   183  	}
   184  
   185  	if m[3] != "" {
   186  		sv.patch, err = strconv.ParseUint(strings.TrimPrefix(m[3], "."), 10, 64)
   187  		if err != nil {
   188  			return nil, fmt.Errorf("Error parsing version segment: %s", err)
   189  		}
   190  	} else {
   191  		sv.patch = 0
   192  	}
   193  
   194  	// Perform some basic due diligence on the extra parts to ensure they are
   195  	// valid.
   196  
   197  	if sv.pre != "" {
   198  		if err = validatePrerelease(sv.pre); err != nil {
   199  			return nil, err
   200  		}
   201  	}
   202  
   203  	if sv.metadata != "" {
   204  		if err = validateMetadata(sv.metadata); err != nil {
   205  			return nil, err
   206  		}
   207  	}
   208  
   209  	return sv, nil
   210  }
   211  
   212  // New creates a new instance of Version with each of the parts passed in as
   213  // arguments instead of parsing a version string.
   214  func New(major, minor, patch uint64, pre, metadata string) *Version {
   215  	v := Version{
   216  		major:    major,
   217  		minor:    minor,
   218  		patch:    patch,
   219  		pre:      pre,
   220  		metadata: metadata,
   221  		original: "",
   222  	}
   223  
   224  	v.original = v.String()
   225  
   226  	return &v
   227  }
   228  
   229  // MustParse parses a given version and panics on error.
   230  func MustParse(v string) *Version {
   231  	sv, err := NewVersion(v)
   232  	if err != nil {
   233  		panic(err)
   234  	}
   235  	return sv
   236  }
   237  
   238  // String converts a Version object to a string.
   239  // Note, if the original version contained a leading v this version will not.
   240  // See the Original() method to retrieve the original value. Semantic Versions
   241  // don't contain a leading v per the spec. Instead it's optional on
   242  // implementation.
   243  func (v Version) String() string {
   244  	var buf bytes.Buffer
   245  
   246  	fmt.Fprintf(&buf, "%d.%d.%d", v.major, v.minor, v.patch)
   247  	if v.pre != "" {
   248  		fmt.Fprintf(&buf, "-%s", v.pre)
   249  	}
   250  	if v.metadata != "" {
   251  		fmt.Fprintf(&buf, "+%s", v.metadata)
   252  	}
   253  
   254  	return buf.String()
   255  }
   256  
   257  // Original returns the original value passed in to be parsed.
   258  func (v *Version) Original() string {
   259  	return v.original
   260  }
   261  
   262  // Major returns the major version.
   263  func (v Version) Major() uint64 {
   264  	return v.major
   265  }
   266  
   267  // Minor returns the minor version.
   268  func (v Version) Minor() uint64 {
   269  	return v.minor
   270  }
   271  
   272  // Patch returns the patch version.
   273  func (v Version) Patch() uint64 {
   274  	return v.patch
   275  }
   276  
   277  // Prerelease returns the pre-release version.
   278  func (v Version) Prerelease() string {
   279  	return v.pre
   280  }
   281  
   282  // Metadata returns the metadata on the version.
   283  func (v Version) Metadata() string {
   284  	return v.metadata
   285  }
   286  
   287  // originalVPrefix returns the original 'v' prefix if any.
   288  func (v Version) originalVPrefix() string {
   289  	// Note, only lowercase v is supported as a prefix by the parser.
   290  	if v.original != "" && v.original[:1] == "v" {
   291  		return v.original[:1]
   292  	}
   293  	return ""
   294  }
   295  
   296  // IncPatch produces the next patch version.
   297  // If the current version does not have prerelease/metadata information,
   298  // it unsets metadata and prerelease values, increments patch number.
   299  // If the current version has any of prerelease or metadata information,
   300  // it unsets both values and keeps current patch value
   301  func (v Version) IncPatch() Version {
   302  	vNext := v
   303  	// according to http://semver.org/#spec-item-9
   304  	// Pre-release versions have a lower precedence than the associated normal version.
   305  	// according to http://semver.org/#spec-item-10
   306  	// Build metadata SHOULD be ignored when determining version precedence.
   307  	if v.pre != "" {
   308  		vNext.metadata = ""
   309  		vNext.pre = ""
   310  	} else {
   311  		vNext.metadata = ""
   312  		vNext.pre = ""
   313  		vNext.patch = v.patch + 1
   314  	}
   315  	vNext.original = v.originalVPrefix() + "" + vNext.String()
   316  	return vNext
   317  }
   318  
   319  // IncMinor produces the next minor version.
   320  // Sets patch to 0.
   321  // Increments minor number.
   322  // Unsets metadata.
   323  // Unsets prerelease status.
   324  func (v Version) IncMinor() Version {
   325  	vNext := v
   326  	vNext.metadata = ""
   327  	vNext.pre = ""
   328  	vNext.patch = 0
   329  	vNext.minor = v.minor + 1
   330  	vNext.original = v.originalVPrefix() + "" + vNext.String()
   331  	return vNext
   332  }
   333  
   334  // IncMajor produces the next major version.
   335  // Sets patch to 0.
   336  // Sets minor to 0.
   337  // Increments major number.
   338  // Unsets metadata.
   339  // Unsets prerelease status.
   340  func (v Version) IncMajor() Version {
   341  	vNext := v
   342  	vNext.metadata = ""
   343  	vNext.pre = ""
   344  	vNext.patch = 0
   345  	vNext.minor = 0
   346  	vNext.major = v.major + 1
   347  	vNext.original = v.originalVPrefix() + "" + vNext.String()
   348  	return vNext
   349  }
   350  
   351  // SetPrerelease defines the prerelease value.
   352  // Value must not include the required 'hyphen' prefix.
   353  func (v Version) SetPrerelease(prerelease string) (Version, error) {
   354  	vNext := v
   355  	if len(prerelease) > 0 {
   356  		if err := validatePrerelease(prerelease); err != nil {
   357  			return vNext, err
   358  		}
   359  	}
   360  	vNext.pre = prerelease
   361  	vNext.original = v.originalVPrefix() + "" + vNext.String()
   362  	return vNext, nil
   363  }
   364  
   365  // SetMetadata defines metadata value.
   366  // Value must not include the required 'plus' prefix.
   367  func (v Version) SetMetadata(metadata string) (Version, error) {
   368  	vNext := v
   369  	if len(metadata) > 0 {
   370  		if err := validateMetadata(metadata); err != nil {
   371  			return vNext, err
   372  		}
   373  	}
   374  	vNext.metadata = metadata
   375  	vNext.original = v.originalVPrefix() + "" + vNext.String()
   376  	return vNext, nil
   377  }
   378  
   379  // LessThan tests if one version is less than another one.
   380  func (v *Version) LessThan(o *Version) bool {
   381  	return v.Compare(o) < 0
   382  }
   383  
   384  // GreaterThan tests if one version is greater than another one.
   385  func (v *Version) GreaterThan(o *Version) bool {
   386  	return v.Compare(o) > 0
   387  }
   388  
   389  // Equal tests if two versions are equal to each other.
   390  // Note, versions can be equal with different metadata since metadata
   391  // is not considered part of the comparable version.
   392  func (v *Version) Equal(o *Version) bool {
   393  	return v.Compare(o) == 0
   394  }
   395  
   396  // Compare compares this version to another one. It returns -1, 0, or 1 if
   397  // the version smaller, equal, or larger than the other version.
   398  //
   399  // Versions are compared by X.Y.Z. Build metadata is ignored. Prerelease is
   400  // lower than the version without a prerelease. Compare always takes into account
   401  // prereleases. If you want to work with ranges using typical range syntaxes that
   402  // skip prereleases if the range is not looking for them use constraints.
   403  func (v *Version) Compare(o *Version) int {
   404  	// Compare the major, minor, and patch version for differences. If a
   405  	// difference is found return the comparison.
   406  	if d := compareSegment(v.Major(), o.Major()); d != 0 {
   407  		return d
   408  	}
   409  	if d := compareSegment(v.Minor(), o.Minor()); d != 0 {
   410  		return d
   411  	}
   412  	if d := compareSegment(v.Patch(), o.Patch()); d != 0 {
   413  		return d
   414  	}
   415  
   416  	// At this point the major, minor, and patch versions are the same.
   417  	ps := v.pre
   418  	po := o.Prerelease()
   419  
   420  	if ps == "" && po == "" {
   421  		return 0
   422  	}
   423  	if ps == "" {
   424  		return 1
   425  	}
   426  	if po == "" {
   427  		return -1
   428  	}
   429  
   430  	return comparePrerelease(ps, po)
   431  }
   432  
   433  // UnmarshalJSON implements JSON.Unmarshaler interface.
   434  func (v *Version) UnmarshalJSON(b []byte) error {
   435  	var s string
   436  	if err := json.Unmarshal(b, &s); err != nil {
   437  		return err
   438  	}
   439  	temp, err := NewVersion(s)
   440  	if err != nil {
   441  		return err
   442  	}
   443  	v.major = temp.major
   444  	v.minor = temp.minor
   445  	v.patch = temp.patch
   446  	v.pre = temp.pre
   447  	v.metadata = temp.metadata
   448  	v.original = temp.original
   449  	return nil
   450  }
   451  
   452  // MarshalJSON implements JSON.Marshaler interface.
   453  func (v Version) MarshalJSON() ([]byte, error) {
   454  	return json.Marshal(v.String())
   455  }
   456  
   457  // UnmarshalText implements the encoding.TextUnmarshaler interface.
   458  func (v *Version) UnmarshalText(text []byte) error {
   459  	temp, err := NewVersion(string(text))
   460  	if err != nil {
   461  		return err
   462  	}
   463  
   464  	*v = *temp
   465  
   466  	return nil
   467  }
   468  
   469  // MarshalText implements the encoding.TextMarshaler interface.
   470  func (v Version) MarshalText() ([]byte, error) {
   471  	return []byte(v.String()), nil
   472  }
   473  
   474  // Scan implements the SQL.Scanner interface.
   475  func (v *Version) Scan(value interface{}) error {
   476  	var s string
   477  	s, _ = value.(string)
   478  	temp, err := NewVersion(s)
   479  	if err != nil {
   480  		return err
   481  	}
   482  	v.major = temp.major
   483  	v.minor = temp.minor
   484  	v.patch = temp.patch
   485  	v.pre = temp.pre
   486  	v.metadata = temp.metadata
   487  	v.original = temp.original
   488  	return nil
   489  }
   490  
   491  // Value implements the Driver.Valuer interface.
   492  func (v Version) Value() (driver.Value, error) {
   493  	return v.String(), nil
   494  }
   495  
   496  func compareSegment(v, o uint64) int {
   497  	if v < o {
   498  		return -1
   499  	}
   500  	if v > o {
   501  		return 1
   502  	}
   503  
   504  	return 0
   505  }
   506  
   507  func comparePrerelease(v, o string) int {
   508  	// split the prelease versions by their part. The separator, per the spec,
   509  	// is a .
   510  	sparts := strings.Split(v, ".")
   511  	oparts := strings.Split(o, ".")
   512  
   513  	// Find the longer length of the parts to know how many loop iterations to
   514  	// go through.
   515  	slen := len(sparts)
   516  	olen := len(oparts)
   517  
   518  	l := slen
   519  	if olen > slen {
   520  		l = olen
   521  	}
   522  
   523  	// Iterate over each part of the prereleases to compare the differences.
   524  	for i := 0; i < l; i++ {
   525  		// Since the lentgh of the parts can be different we need to create
   526  		// a placeholder. This is to avoid out of bounds issues.
   527  		stemp := ""
   528  		if i < slen {
   529  			stemp = sparts[i]
   530  		}
   531  
   532  		otemp := ""
   533  		if i < olen {
   534  			otemp = oparts[i]
   535  		}
   536  
   537  		d := comparePrePart(stemp, otemp)
   538  		if d != 0 {
   539  			return d
   540  		}
   541  	}
   542  
   543  	// Reaching here means two versions are of equal value but have different
   544  	// metadata (the part following a +). They are not identical in string form
   545  	// but the version comparison finds them to be equal.
   546  	return 0
   547  }
   548  
   549  func comparePrePart(s, o string) int {
   550  	// Fastpath if they are equal
   551  	if s == o {
   552  		return 0
   553  	}
   554  
   555  	// When s or o are empty we can use the other in an attempt to determine
   556  	// the response.
   557  	if s == "" {
   558  		if o != "" {
   559  			return -1
   560  		}
   561  		return 1
   562  	}
   563  
   564  	if o == "" {
   565  		if s != "" {
   566  			return 1
   567  		}
   568  		return -1
   569  	}
   570  
   571  	// When comparing strings "99" is greater than "103". To handle
   572  	// cases like this we need to detect numbers and compare them. According
   573  	// to the semver spec, numbers are always positive. If there is a - at the
   574  	// start like -99 this is to be evaluated as an alphanum. numbers always
   575  	// have precedence over alphanum. Parsing as Uints because negative numbers
   576  	// are ignored.
   577  
   578  	oi, n1 := strconv.ParseUint(o, 10, 64)
   579  	si, n2 := strconv.ParseUint(s, 10, 64)
   580  
   581  	// The case where both are strings compare the strings
   582  	if n1 != nil && n2 != nil {
   583  		if s > o {
   584  			return 1
   585  		}
   586  		return -1
   587  	} else if n1 != nil {
   588  		// o is a string and s is a number
   589  		return -1
   590  	} else if n2 != nil {
   591  		// s is a string and o is a number
   592  		return 1
   593  	}
   594  	// Both are numbers
   595  	if si > oi {
   596  		return 1
   597  	}
   598  	return -1
   599  }
   600  
   601  // Like strings.ContainsAny but does an only instead of any.
   602  func containsOnly(s string, comp string) bool {
   603  	return strings.IndexFunc(s, func(r rune) bool {
   604  		return !strings.ContainsRune(comp, r)
   605  	}) == -1
   606  }
   607  
   608  // From the spec, "Identifiers MUST comprise only
   609  // ASCII alphanumerics and hyphen [0-9A-Za-z-]. Identifiers MUST NOT be empty.
   610  // Numeric identifiers MUST NOT include leading zeroes.". These segments can
   611  // be dot separated.
   612  func validatePrerelease(p string) error {
   613  	eparts := strings.Split(p, ".")
   614  	for _, p := range eparts {
   615  		if containsOnly(p, num) {
   616  			if len(p) > 1 && p[0] == '0' {
   617  				return ErrSegmentStartsZero
   618  			}
   619  		} else if !containsOnly(p, allowed) {
   620  			return ErrInvalidPrerelease
   621  		}
   622  	}
   623  
   624  	return nil
   625  }
   626  
   627  // From the spec, "Build metadata MAY be denoted by
   628  // appending a plus sign and a series of dot separated identifiers immediately
   629  // following the patch or pre-release version. Identifiers MUST comprise only
   630  // ASCII alphanumerics and hyphen [0-9A-Za-z-]. Identifiers MUST NOT be empty."
   631  func validateMetadata(m string) error {
   632  	eparts := strings.Split(m, ".")
   633  	for _, p := range eparts {
   634  		if !containsOnly(p, allowed) {
   635  			return ErrInvalidMetadata
   636  		}
   637  	}
   638  	return nil
   639  }
   640  

View as plain text