123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418 |
- 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
- }
|