...

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

Documentation: github.com/blang/semver

     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 Precendence
    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  // Equals checks if v is equal to o.
    65  func (v Version) Equals(o Version) bool {
    66  	return (v.Compare(o) == 0)
    67  }
    68  
    69  // EQ checks if v is equal to o.
    70  func (v Version) EQ(o Version) bool {
    71  	return (v.Compare(o) == 0)
    72  }
    73  
    74  // NE checks if v is not equal to o.
    75  func (v Version) NE(o Version) bool {
    76  	return (v.Compare(o) != 0)
    77  }
    78  
    79  // GT checks if v is greater than o.
    80  func (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.
    85  func (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.
    90  func (v Version) GE(o Version) bool {
    91  	return (v.Compare(o) >= 0)
    92  }
    93  
    94  // LT checks if v is less than o.
    95  func (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.
   100  func (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.
   105  func (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
   113  func (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
   165  func (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
   192  func 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
   199  func 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
   207  func 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
   227  func 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.
   316  func 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
   325  type PRVersion struct {
   326  	VersionStr string
   327  	VersionNum uint64
   328  	IsNum      bool
   329  }
   330  
   331  // NewPRVersion creates a new valid prerelease version
   332  func 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
   359  func (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
   367  func (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
   392  func (v PRVersion) String() string {
   393  	if v.IsNum {
   394  		return strconv.FormatUint(v.VersionNum, 10)
   395  	}
   396  	return v.VersionStr
   397  }
   398  
   399  func 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  
   405  func hasLeadingZeroes(s string) bool {
   406  	return len(s) > 1 && s[0] == '0'
   407  }
   408  
   409  // NewBuildVersion creates a new valid build version
   410  func 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  

View as plain text