]> git.lizzy.rs Git - micro.git/commitdiff
Add semver code to tools directory
authorZachary Yedidia <zyedidia@gmail.com>
Wed, 12 Jul 2017 13:34:59 +0000 (09:34 -0400)
committerZachary Yedidia <zyedidia@gmail.com>
Wed, 12 Jul 2017 13:36:16 +0000 (09:36 -0400)
Ref #736

tools/build-version.go
tools/semver/json.go [new file with mode: 0644]
tools/semver/range.go [new file with mode: 0644]
tools/semver/semver.go [new file with mode: 0644]
tools/semver/sort.go [new file with mode: 0644]
tools/semver/sql.go [new file with mode: 0644]

index 8e7e9b86879fe8a401d97993ebf26d3aad76d6a0..8d94bf56e6b2ef66b5b167395d2035ae0c6cd667 100644 (file)
@@ -5,7 +5,7 @@ import (
        "os/exec"
        "strings"
 
-       "github.com/blang/semver"
+       "./semver"
 )
 
 func getTag(match ...string) (string, *semver.PRVersion) {
diff --git a/tools/semver/json.go b/tools/semver/json.go
new file mode 100644 (file)
index 0000000..a74bf7c
--- /dev/null
@@ -0,0 +1,23 @@
+package semver
+
+import (
+       "encoding/json"
+)
+
+// MarshalJSON implements the encoding/json.Marshaler interface.
+func (v Version) MarshalJSON() ([]byte, error) {
+       return json.Marshal(v.String())
+}
+
+// UnmarshalJSON implements the encoding/json.Unmarshaler interface.
+func (v *Version) UnmarshalJSON(data []byte) (err error) {
+       var versionString string
+
+       if err = json.Unmarshal(data, &versionString); err != nil {
+               return
+       }
+
+       *v, err = Parse(versionString)
+
+       return
+}
diff --git a/tools/semver/range.go b/tools/semver/range.go
new file mode 100644 (file)
index 0000000..fca406d
--- /dev/null
@@ -0,0 +1,416 @@
+package semver
+
+import (
+       "fmt"
+       "strconv"
+       "strings"
+       "unicode"
+)
+
+type wildcardType int
+
+const (
+       noneWildcard  wildcardType = iota
+       majorWildcard wildcardType = 1
+       minorWildcard wildcardType = 2
+       patchWildcard wildcardType = 3
+)
+
+func wildcardTypefromInt(i int) wildcardType {
+       switch i {
+       case 1:
+               return majorWildcard
+       case 2:
+               return minorWildcard
+       case 3:
+               return patchWildcard
+       default:
+               return noneWildcard
+       }
+}
+
+type comparator func(Version, Version) bool
+
+var (
+       compEQ comparator = func(v1 Version, v2 Version) bool {
+               return v1.Compare(v2) == 0
+       }
+       compNE = func(v1 Version, v2 Version) bool {
+               return v1.Compare(v2) != 0
+       }
+       compGT = func(v1 Version, v2 Version) bool {
+               return v1.Compare(v2) == 1
+       }
+       compGE = func(v1 Version, v2 Version) bool {
+               return v1.Compare(v2) >= 0
+       }
+       compLT = func(v1 Version, v2 Version) bool {
+               return v1.Compare(v2) == -1
+       }
+       compLE = func(v1 Version, v2 Version) bool {
+               return v1.Compare(v2) <= 0
+       }
+)
+
+type versionRange struct {
+       v Version
+       c comparator
+}
+
+// rangeFunc creates a Range from the given versionRange.
+func (vr *versionRange) rangeFunc() Range {
+       return Range(func(v Version) bool {
+               return vr.c(v, vr.v)
+       })
+}
+
+// Range represents a range of versions.
+// A Range can be used to check if a Version satisfies it:
+//
+//     range, err := semver.ParseRange(">1.0.0 <2.0.0")
+//     range(semver.MustParse("1.1.1") // returns true
+type Range func(Version) bool
+
+// OR combines the existing Range with another Range using logical OR.
+func (rf Range) OR(f Range) Range {
+       return Range(func(v Version) bool {
+               return rf(v) || f(v)
+       })
+}
+
+// AND combines the existing Range with another Range using logical AND.
+func (rf Range) AND(f Range) Range {
+       return Range(func(v Version) bool {
+               return rf(v) && f(v)
+       })
+}
+
+// ParseRange parses a range and returns a Range.
+// If the range could not be parsed an error is returned.
+//
+// Valid ranges are:
+//   - "<1.0.0"
+//   - "<=1.0.0"
+//   - ">1.0.0"
+//   - ">=1.0.0"
+//   - "1.0.0", "=1.0.0", "==1.0.0"
+//   - "!1.0.0", "!=1.0.0"
+//
+// A Range can consist of multiple ranges separated by space:
+// Ranges can be linked by logical AND:
+//   - ">1.0.0 <2.0.0" would match between both ranges, so "1.1.1" and "1.8.7" but not "1.0.0" or "2.0.0"
+//   - ">1.0.0 <3.0.0 !2.0.3-beta.2" would match every version between 1.0.0 and 3.0.0 except 2.0.3-beta.2
+//
+// Ranges can also be linked by logical OR:
+//   - "<2.0.0 || >=3.0.0" would match "1.x.x" and "3.x.x" but not "2.x.x"
+//
+// AND has a higher precedence than OR. It's not possible to use brackets.
+//
+// Ranges can be combined by both AND and OR
+//
+//  - `>1.0.0 <2.0.0 || >3.0.0 !4.2.1` would match `1.2.3`, `1.9.9`, `3.1.1`, but not `4.2.1`, `2.1.1`
+func ParseRange(s string) (Range, error) {
+       parts := splitAndTrim(s)
+       orParts, err := splitORParts(parts)
+       if err != nil {
+               return nil, err
+       }
+       expandedParts, err := expandWildcardVersion(orParts)
+       if err != nil {
+               return nil, err
+       }
+       var orFn Range
+       for _, p := range expandedParts {
+               var andFn Range
+               for _, ap := range p {
+                       opStr, vStr, err := splitComparatorVersion(ap)
+                       if err != nil {
+                               return nil, err
+                       }
+                       vr, err := buildVersionRange(opStr, vStr)
+                       if err != nil {
+                               return nil, fmt.Errorf("Could not parse Range %q: %s", ap, err)
+                       }
+                       rf := vr.rangeFunc()
+
+                       // Set function
+                       if andFn == nil {
+                               andFn = rf
+                       } else { // Combine with existing function
+                               andFn = andFn.AND(rf)
+                       }
+               }
+               if orFn == nil {
+                       orFn = andFn
+               } else {
+                       orFn = orFn.OR(andFn)
+               }
+
+       }
+       return orFn, nil
+}
+
+// splitORParts splits the already cleaned parts by '||'.
+// Checks for invalid positions of the operator and returns an
+// error if found.
+func splitORParts(parts []string) ([][]string, error) {
+       var ORparts [][]string
+       last := 0
+       for i, p := range parts {
+               if p == "||" {
+                       if i == 0 {
+                               return nil, fmt.Errorf("First element in range is '||'")
+                       }
+                       ORparts = append(ORparts, parts[last:i])
+                       last = i + 1
+               }
+       }
+       if last == len(parts) {
+               return nil, fmt.Errorf("Last element in range is '||'")
+       }
+       ORparts = append(ORparts, parts[last:])
+       return ORparts, nil
+}
+
+// buildVersionRange takes a slice of 2: operator and version
+// and builds a versionRange, otherwise an error.
+func buildVersionRange(opStr, vStr string) (*versionRange, error) {
+       c := parseComparator(opStr)
+       if c == nil {
+               return nil, fmt.Errorf("Could not parse comparator %q in %q", opStr, strings.Join([]string{opStr, vStr}, ""))
+       }
+       v, err := Parse(vStr)
+       if err != nil {
+               return nil, fmt.Errorf("Could not parse version %q in %q: %s", vStr, strings.Join([]string{opStr, vStr}, ""), err)
+       }
+
+       return &versionRange{
+               v: v,
+               c: c,
+       }, nil
+
+}
+
+// inArray checks if a byte is contained in an array of bytes
+func inArray(s byte, list []byte) bool {
+       for _, el := range list {
+               if el == s {
+                       return true
+               }
+       }
+       return false
+}
+
+// splitAndTrim splits a range string by spaces and cleans whitespaces
+func splitAndTrim(s string) (result []string) {
+       last := 0
+       var lastChar byte
+       excludeFromSplit := []byte{'>', '<', '='}
+       for i := 0; i < len(s); i++ {
+               if s[i] == ' ' && !inArray(lastChar, excludeFromSplit) {
+                       if last < i-1 {
+                               result = append(result, s[last:i])
+                       }
+                       last = i + 1
+               } else if s[i] != ' ' {
+                       lastChar = s[i]
+               }
+       }
+       if last < len(s)-1 {
+               result = append(result, s[last:])
+       }
+
+       for i, v := range result {
+               result[i] = strings.Replace(v, " ", "", -1)
+       }
+
+       // parts := strings.Split(s, " ")
+       // for _, x := range parts {
+       //      if s := strings.TrimSpace(x); len(s) != 0 {
+       //              result = append(result, s)
+       //      }
+       // }
+       return
+}
+
+// splitComparatorVersion splits the comparator from the version.
+// Input must be free of leading or trailing spaces.
+func splitComparatorVersion(s string) (string, string, error) {
+       i := strings.IndexFunc(s, unicode.IsDigit)
+       if i == -1 {
+               return "", "", fmt.Errorf("Could not get version from string: %q", s)
+       }
+       return strings.TrimSpace(s[0:i]), s[i:], nil
+}
+
+// getWildcardType will return the type of wildcard that the
+// passed version contains
+func getWildcardType(vStr string) wildcardType {
+       parts := strings.Split(vStr, ".")
+       nparts := len(parts)
+       wildcard := parts[nparts-1]
+
+       possibleWildcardType := wildcardTypefromInt(nparts)
+       if wildcard == "x" {
+               return possibleWildcardType
+       }
+
+       return noneWildcard
+}
+
+// createVersionFromWildcard will convert a wildcard version
+// into a regular version, replacing 'x's with '0's, handling
+// special cases like '1.x.x' and '1.x'
+func createVersionFromWildcard(vStr string) string {
+       // handle 1.x.x
+       vStr2 := strings.Replace(vStr, ".x.x", ".x", 1)
+       vStr2 = strings.Replace(vStr2, ".x", ".0", 1)
+       parts := strings.Split(vStr2, ".")
+
+       // handle 1.x
+       if len(parts) == 2 {
+               return vStr2 + ".0"
+       }
+
+       return vStr2
+}
+
+// incrementMajorVersion will increment the major version
+// of the passed version
+func incrementMajorVersion(vStr string) (string, error) {
+       parts := strings.Split(vStr, ".")
+       i, err := strconv.Atoi(parts[0])
+       if err != nil {
+               return "", err
+       }
+       parts[0] = strconv.Itoa(i + 1)
+
+       return strings.Join(parts, "."), nil
+}
+
+// incrementMajorVersion will increment the minor version
+// of the passed version
+func incrementMinorVersion(vStr string) (string, error) {
+       parts := strings.Split(vStr, ".")
+       i, err := strconv.Atoi(parts[1])
+       if err != nil {
+               return "", err
+       }
+       parts[1] = strconv.Itoa(i + 1)
+
+       return strings.Join(parts, "."), nil
+}
+
+// expandWildcardVersion will expand wildcards inside versions
+// following these rules:
+//
+// * when dealing with patch wildcards:
+// >= 1.2.x    will become    >= 1.2.0
+// <= 1.2.x    will become    <  1.3.0
+// >  1.2.x    will become    >= 1.3.0
+// <  1.2.x    will become    <  1.2.0
+// != 1.2.x    will become    <  1.2.0 >= 1.3.0
+//
+// * when dealing with minor wildcards:
+// >= 1.x      will become    >= 1.0.0
+// <= 1.x      will become    <  2.0.0
+// >  1.x      will become    >= 2.0.0
+// <  1.0      will become    <  1.0.0
+// != 1.x      will become    <  1.0.0 >= 2.0.0
+//
+// * when dealing with wildcards without
+// version operator:
+// 1.2.x       will become    >= 1.2.0 < 1.3.0
+// 1.x         will become    >= 1.0.0 < 2.0.0
+func expandWildcardVersion(parts [][]string) ([][]string, error) {
+       var expandedParts [][]string
+       for _, p := range parts {
+               var newParts []string
+               for _, ap := range p {
+                       if strings.Index(ap, "x") != -1 {
+                               opStr, vStr, err := splitComparatorVersion(ap)
+                               if err != nil {
+                                       return nil, err
+                               }
+
+                               versionWildcardType := getWildcardType(vStr)
+                               flatVersion := createVersionFromWildcard(vStr)
+
+                               var resultOperator string
+                               var shouldIncrementVersion bool
+                               switch opStr {
+                               case ">":
+                                       resultOperator = ">="
+                                       shouldIncrementVersion = true
+                               case ">=":
+                                       resultOperator = ">="
+                               case "<":
+                                       resultOperator = "<"
+                               case "<=":
+                                       resultOperator = "<"
+                                       shouldIncrementVersion = true
+                               case "", "=", "==":
+                                       newParts = append(newParts, ">="+flatVersion)
+                                       resultOperator = "<"
+                                       shouldIncrementVersion = true
+                               case "!=", "!":
+                                       newParts = append(newParts, "<"+flatVersion)
+                                       resultOperator = ">="
+                                       shouldIncrementVersion = true
+                               }
+
+                               var resultVersion string
+                               if shouldIncrementVersion {
+                                       switch versionWildcardType {
+                                       case patchWildcard:
+                                               resultVersion, _ = incrementMinorVersion(flatVersion)
+                                       case minorWildcard:
+                                               resultVersion, _ = incrementMajorVersion(flatVersion)
+                                       }
+                               } else {
+                                       resultVersion = flatVersion
+                               }
+
+                               ap = resultOperator + resultVersion
+                       }
+                       newParts = append(newParts, ap)
+               }
+               expandedParts = append(expandedParts, newParts)
+       }
+
+       return expandedParts, nil
+}
+
+func parseComparator(s string) comparator {
+       switch s {
+       case "==":
+               fallthrough
+       case "":
+               fallthrough
+       case "=":
+               return compEQ
+       case ">":
+               return compGT
+       case ">=":
+               return compGE
+       case "<":
+               return compLT
+       case "<=":
+               return compLE
+       case "!":
+               fallthrough
+       case "!=":
+               return compNE
+       }
+
+       return nil
+}
+
+// MustParseRange is like ParseRange but panics if the range cannot be parsed.
+func MustParseRange(s string) Range {
+       r, err := ParseRange(s)
+       if err != nil {
+               panic(`semver: ParseRange(` + s + `): ` + err.Error())
+       }
+       return r
+}
diff --git a/tools/semver/semver.go b/tools/semver/semver.go
new file mode 100644 (file)
index 0000000..8ee0842
--- /dev/null
@@ -0,0 +1,418 @@
+package semver
+
+import (
+       "errors"
+       "fmt"
+       "strconv"
+       "strings"
+)
+
+const (
+       numbers  string = "0123456789"
+       alphas          = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-"
+       alphanum        = alphas + numbers
+)
+
+// SpecVersion is the latest fully supported spec version of semver
+var SpecVersion = Version{
+       Major: 2,
+       Minor: 0,
+       Patch: 0,
+}
+
+// Version represents a semver compatible version
+type Version struct {
+       Major uint64
+       Minor uint64
+       Patch uint64
+       Pre   []PRVersion
+       Build []string //No Precendence
+}
+
+// Version to string
+func (v Version) String() string {
+       b := make([]byte, 0, 5)
+       b = strconv.AppendUint(b, v.Major, 10)
+       b = append(b, '.')
+       b = strconv.AppendUint(b, v.Minor, 10)
+       b = append(b, '.')
+       b = strconv.AppendUint(b, v.Patch, 10)
+
+       if len(v.Pre) > 0 {
+               b = append(b, '-')
+               b = append(b, v.Pre[0].String()...)
+
+               for _, pre := range v.Pre[1:] {
+                       b = append(b, '.')
+                       b = append(b, pre.String()...)
+               }
+       }
+
+       if len(v.Build) > 0 {
+               b = append(b, '+')
+               b = append(b, v.Build[0]...)
+
+               for _, build := range v.Build[1:] {
+                       b = append(b, '.')
+                       b = append(b, build...)
+               }
+       }
+
+       return string(b)
+}
+
+// Equals checks if v is equal to o.
+func (v Version) Equals(o Version) bool {
+       return (v.Compare(o) == 0)
+}
+
+// EQ checks if v is equal to o.
+func (v Version) EQ(o Version) bool {
+       return (v.Compare(o) == 0)
+}
+
+// NE checks if v is not equal to o.
+func (v Version) NE(o Version) bool {
+       return (v.Compare(o) != 0)
+}
+
+// GT checks if v is greater than o.
+func (v Version) GT(o Version) bool {
+       return (v.Compare(o) == 1)
+}
+
+// GTE checks if v is greater than or equal to o.
+func (v Version) GTE(o Version) bool {
+       return (v.Compare(o) >= 0)
+}
+
+// GE checks if v is greater than or equal to o.
+func (v Version) GE(o Version) bool {
+       return (v.Compare(o) >= 0)
+}
+
+// LT checks if v is less than o.
+func (v Version) LT(o Version) bool {
+       return (v.Compare(o) == -1)
+}
+
+// LTE checks if v is less than or equal to o.
+func (v Version) LTE(o Version) bool {
+       return (v.Compare(o) <= 0)
+}
+
+// LE checks if v is less than or equal to o.
+func (v Version) LE(o Version) bool {
+       return (v.Compare(o) <= 0)
+}
+
+// Compare compares Versions v to o:
+// -1 == v is less than o
+// 0 == v is equal to o
+// 1 == v is greater than o
+func (v Version) Compare(o Version) int {
+       if v.Major != o.Major {
+               if v.Major > o.Major {
+                       return 1
+               }
+               return -1
+       }
+       if v.Minor != o.Minor {
+               if v.Minor > o.Minor {
+                       return 1
+               }
+               return -1
+       }
+       if v.Patch != o.Patch {
+               if v.Patch > o.Patch {
+                       return 1
+               }
+               return -1
+       }
+
+       // Quick comparison if a version has no prerelease versions
+       if len(v.Pre) == 0 && len(o.Pre) == 0 {
+               return 0
+       } else if len(v.Pre) == 0 && len(o.Pre) > 0 {
+               return 1
+       } else if len(v.Pre) > 0 && len(o.Pre) == 0 {
+               return -1
+       }
+
+       i := 0
+       for ; i < len(v.Pre) && i < len(o.Pre); i++ {
+               if comp := v.Pre[i].Compare(o.Pre[i]); comp == 0 {
+                       continue
+               } else if comp == 1 {
+                       return 1
+               } else {
+                       return -1
+               }
+       }
+
+       // If all pr versions are the equal but one has further prversion, this one greater
+       if i == len(v.Pre) && i == len(o.Pre) {
+               return 0
+       } else if i == len(v.Pre) && i < len(o.Pre) {
+               return -1
+       } else {
+               return 1
+       }
+
+}
+
+// Validate validates v and returns error in case
+func (v Version) Validate() error {
+       // Major, Minor, Patch already validated using uint64
+
+       for _, pre := range v.Pre {
+               if !pre.IsNum { //Numeric prerelease versions already uint64
+                       if len(pre.VersionStr) == 0 {
+                               return fmt.Errorf("Prerelease can not be empty %q", pre.VersionStr)
+                       }
+                       if !containsOnly(pre.VersionStr, alphanum) {
+                               return fmt.Errorf("Invalid character(s) found in prerelease %q", pre.VersionStr)
+                       }
+               }
+       }
+
+       for _, build := range v.Build {
+               if len(build) == 0 {
+                       return fmt.Errorf("Build meta data can not be empty %q", build)
+               }
+               if !containsOnly(build, alphanum) {
+                       return fmt.Errorf("Invalid character(s) found in build meta data %q", build)
+               }
+       }
+
+       return nil
+}
+
+// New is an alias for Parse and returns a pointer, parses version string and returns a validated Version or error
+func New(s string) (vp *Version, err error) {
+       v, err := Parse(s)
+       vp = &v
+       return
+}
+
+// Make is an alias for Parse, parses version string and returns a validated Version or error
+func Make(s string) (Version, error) {
+       return Parse(s)
+}
+
+// ParseTolerant allows for certain version specifications that do not strictly adhere to semver
+// specs to be parsed by this library. It does so by normalizing versions before passing them to
+// Parse(). It currently trims spaces, removes a "v" prefix, and adds a 0 patch number to versions
+// with only major and minor components specified
+func ParseTolerant(s string) (Version, error) {
+       s = strings.TrimSpace(s)
+       s = strings.TrimPrefix(s, "v")
+
+       // Split into major.minor.(patch+pr+meta)
+       parts := strings.SplitN(s, ".", 3)
+       if len(parts) < 3 {
+               if strings.ContainsAny(parts[len(parts)-1], "+-") {
+                       return Version{}, errors.New("Short version cannot contain PreRelease/Build meta data")
+               }
+               for len(parts) < 3 {
+                       parts = append(parts, "0")
+               }
+               s = strings.Join(parts, ".")
+       }
+
+       return Parse(s)
+}
+
+// Parse parses version string and returns a validated Version or error
+func Parse(s string) (Version, error) {
+       if len(s) == 0 {
+               return Version{}, errors.New("Version string empty")
+       }
+
+       // Split into major.minor.(patch+pr+meta)
+       parts := strings.SplitN(s, ".", 3)
+       if len(parts) != 3 {
+               return Version{}, errors.New("No Major.Minor.Patch elements found")
+       }
+
+       // Major
+       if !containsOnly(parts[0], numbers) {
+               return Version{}, fmt.Errorf("Invalid character(s) found in major number %q", parts[0])
+       }
+       if hasLeadingZeroes(parts[0]) {
+               return Version{}, fmt.Errorf("Major number must not contain leading zeroes %q", parts[0])
+       }
+       major, err := strconv.ParseUint(parts[0], 10, 64)
+       if err != nil {
+               return Version{}, err
+       }
+
+       // Minor
+       if !containsOnly(parts[1], numbers) {
+               return Version{}, fmt.Errorf("Invalid character(s) found in minor number %q", parts[1])
+       }
+       if hasLeadingZeroes(parts[1]) {
+               return Version{}, fmt.Errorf("Minor number must not contain leading zeroes %q", parts[1])
+       }
+       minor, err := strconv.ParseUint(parts[1], 10, 64)
+       if err != nil {
+               return Version{}, err
+       }
+
+       v := Version{}
+       v.Major = major
+       v.Minor = minor
+
+       var build, prerelease []string
+       patchStr := parts[2]
+
+       if buildIndex := strings.IndexRune(patchStr, '+'); buildIndex != -1 {
+               build = strings.Split(patchStr[buildIndex+1:], ".")
+               patchStr = patchStr[:buildIndex]
+       }
+
+       if preIndex := strings.IndexRune(patchStr, '-'); preIndex != -1 {
+               prerelease = strings.Split(patchStr[preIndex+1:], ".")
+               patchStr = patchStr[:preIndex]
+       }
+
+       if !containsOnly(patchStr, numbers) {
+               return Version{}, fmt.Errorf("Invalid character(s) found in patch number %q", patchStr)
+       }
+       if hasLeadingZeroes(patchStr) {
+               return Version{}, fmt.Errorf("Patch number must not contain leading zeroes %q", patchStr)
+       }
+       patch, err := strconv.ParseUint(patchStr, 10, 64)
+       if err != nil {
+               return Version{}, err
+       }
+
+       v.Patch = patch
+
+       // Prerelease
+       for _, prstr := range prerelease {
+               parsedPR, err := NewPRVersion(prstr)
+               if err != nil {
+                       return Version{}, err
+               }
+               v.Pre = append(v.Pre, parsedPR)
+       }
+
+       // Build meta data
+       for _, str := range build {
+               if len(str) == 0 {
+                       return Version{}, errors.New("Build meta data is empty")
+               }
+               if !containsOnly(str, alphanum) {
+                       return Version{}, fmt.Errorf("Invalid character(s) found in build meta data %q", str)
+               }
+               v.Build = append(v.Build, str)
+       }
+
+       return v, nil
+}
+
+// MustParse is like Parse but panics if the version cannot be parsed.
+func MustParse(s string) Version {
+       v, err := Parse(s)
+       if err != nil {
+               panic(`semver: Parse(` + s + `): ` + err.Error())
+       }
+       return v
+}
+
+// PRVersion represents a PreRelease Version
+type PRVersion struct {
+       VersionStr string
+       VersionNum uint64
+       IsNum      bool
+}
+
+// NewPRVersion creates a new valid prerelease version
+func NewPRVersion(s string) (PRVersion, error) {
+       if len(s) == 0 {
+               return PRVersion{}, errors.New("Prerelease is empty")
+       }
+       v := PRVersion{}
+       if containsOnly(s, numbers) {
+               if hasLeadingZeroes(s) {
+                       return PRVersion{}, fmt.Errorf("Numeric PreRelease version must not contain leading zeroes %q", s)
+               }
+               num, err := strconv.ParseUint(s, 10, 64)
+
+               // Might never be hit, but just in case
+               if err != nil {
+                       return PRVersion{}, err
+               }
+               v.VersionNum = num
+               v.IsNum = true
+       } else if containsOnly(s, alphanum) {
+               v.VersionStr = s
+               v.IsNum = false
+       } else {
+               return PRVersion{}, fmt.Errorf("Invalid character(s) found in prerelease %q", s)
+       }
+       return v, nil
+}
+
+// IsNumeric checks if prerelease-version is numeric
+func (v PRVersion) IsNumeric() bool {
+       return v.IsNum
+}
+
+// Compare compares two PreRelease Versions v and o:
+// -1 == v is less than o
+// 0 == v is equal to o
+// 1 == v is greater than o
+func (v PRVersion) Compare(o PRVersion) int {
+       if v.IsNum && !o.IsNum {
+               return -1
+       } else if !v.IsNum && o.IsNum {
+               return 1
+       } else if v.IsNum && o.IsNum {
+               if v.VersionNum == o.VersionNum {
+                       return 0
+               } else if v.VersionNum > o.VersionNum {
+                       return 1
+               } else {
+                       return -1
+               }
+       } else { // both are Alphas
+               if v.VersionStr == o.VersionStr {
+                       return 0
+               } else if v.VersionStr > o.VersionStr {
+                       return 1
+               } else {
+                       return -1
+               }
+       }
+}
+
+// PreRelease version to string
+func (v PRVersion) String() string {
+       if v.IsNum {
+               return strconv.FormatUint(v.VersionNum, 10)
+       }
+       return v.VersionStr
+}
+
+func containsOnly(s string, set string) bool {
+       return strings.IndexFunc(s, func(r rune) bool {
+               return !strings.ContainsRune(set, r)
+       }) == -1
+}
+
+func hasLeadingZeroes(s string) bool {
+       return len(s) > 1 && s[0] == '0'
+}
+
+// NewBuildVersion creates a new valid build version
+func NewBuildVersion(s string) (string, error) {
+       if len(s) == 0 {
+               return "", errors.New("Buildversion is empty")
+       }
+       if !containsOnly(s, alphanum) {
+               return "", fmt.Errorf("Invalid character(s) found in build meta data %q", s)
+       }
+       return s, nil
+}
diff --git a/tools/semver/sort.go b/tools/semver/sort.go
new file mode 100644 (file)
index 0000000..e18f880
--- /dev/null
@@ -0,0 +1,28 @@
+package semver
+
+import (
+       "sort"
+)
+
+// Versions represents multiple versions.
+type Versions []Version
+
+// Len returns length of version collection
+func (s Versions) Len() int {
+       return len(s)
+}
+
+// Swap swaps two versions inside the collection by its indices
+func (s Versions) Swap(i, j int) {
+       s[i], s[j] = s[j], s[i]
+}
+
+// Less checks if version at index i is less than version at index j
+func (s Versions) Less(i, j int) bool {
+       return s[i].LT(s[j])
+}
+
+// Sort sorts a slice of versions
+func Sort(versions []Version) {
+       sort.Sort(Versions(versions))
+}
diff --git a/tools/semver/sql.go b/tools/semver/sql.go
new file mode 100644 (file)
index 0000000..eb4d802
--- /dev/null
@@ -0,0 +1,30 @@
+package semver
+
+import (
+       "database/sql/driver"
+       "fmt"
+)
+
+// Scan implements the database/sql.Scanner interface.
+func (v *Version) Scan(src interface{}) (err error) {
+       var str string
+       switch src := src.(type) {
+       case string:
+               str = src
+       case []byte:
+               str = string(src)
+       default:
+               return fmt.Errorf("Version.Scan: cannot convert %T to string.", src)
+       }
+
+       if t, err := Parse(str); err == nil {
+               *v = t
+       }
+
+       return
+}
+
+// Value implements the database/sql/driver.Valuer interface.
+func (v Version) Value() (driver.Value, error) {
+       return v.String(), nil
+}