github.com/verrazzano/verrazzano@v1.7.0/pkg/semver/semver.go (about) 1 // Copyright (c) 2020, 2023, Oracle and/or its affiliates. 2 // Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl. 3 4 package semver 5 6 import ( 7 "errors" 8 "fmt" 9 "regexp" 10 "strconv" 11 "strings" 12 13 "go.uber.org/zap" 14 ) 15 16 const semverRegex = "^[v|V](0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$" 17 18 // SemVersion Implements a basic notion a semantic version (see https://semver.org/, test page: https://regex101.com/r/vkijKf/1/) 19 type SemVersion struct { 20 Major int64 21 Minor int64 22 Patch int64 23 Prerelease string 24 Build string 25 } 26 27 var compiledRegEx *regexp.Regexp 28 29 func getRegex() (*regexp.Regexp, error) { 30 if compiledRegEx != nil { 31 return compiledRegEx, nil 32 } 33 var err error 34 compiledRegEx, err = regexp.Compile(semverRegex) 35 if err != nil { 36 return nil, err 37 } 38 return compiledRegEx, nil 39 } 40 41 // NewSemVersion Create an instance of a SemVersion 42 func NewSemVersion(version string) (*SemVersion, error) { 43 if len(version) == 0 { 44 return nil, errors.New("SemVersion string cannot be empty") 45 } 46 savedVersion := version 47 if !strings.HasPrefix(version, "v") && !strings.HasPrefix(version, "V") { 48 if version[0] >= '0' && version[0] <= '9' { 49 version = "v" + version 50 } else { 51 return nil, invalidVersionError(savedVersion) 52 } 53 } 54 regex, err := getRegex() 55 if err != nil { 56 return nil, err 57 } 58 59 allMatches := regex.FindAllStringSubmatch(version, -1) 60 zap.S().Debugf("allMatches: %v", allMatches) 61 if len(allMatches) == 0 { 62 return nil, invalidVersionError(savedVersion) 63 } 64 65 versionComponents := allMatches[0] 66 zap.S().Debugf("components: %v", versionComponents) 67 numComponents := len(versionComponents) 68 if numComponents < 3 { 69 return nil, invalidVersionError(savedVersion) 70 } 71 majorVer, err := strconv.ParseInt(versionComponents[1], 10, 64) 72 if err != nil { 73 return nil, err 74 } 75 minorVer, err := strconv.ParseInt(versionComponents[2], 10, 64) 76 if err != nil { 77 return nil, err 78 } 79 80 var patchVer int64 81 if numComponents > 3 { 82 patchVer, err = strconv.ParseInt(versionComponents[3], 10, 64) 83 if err != nil { 84 return nil, err 85 } 86 } 87 88 var prereleaseVer string 89 if numComponents > 4 { 90 prereleaseVer = versionComponents[4] 91 } 92 93 var buildVer string 94 if numComponents > 5 { 95 buildVer = versionComponents[5] 96 } 97 semVersion := SemVersion{ 98 Major: majorVer, 99 Minor: minorVer, 100 Patch: patchVer, 101 Prerelease: prereleaseVer, 102 Build: buildVer, 103 } 104 return &semVersion, nil 105 } 106 107 // CompareTo Compares the current version to another version 108 // - if from > this, -1 is returned 109 // - if from < this, 1 is returned 110 // - if they are equal, 0 is returned 111 func (v *SemVersion) CompareTo(from *SemVersion) int { 112 var result int 113 if result = compareVersion(from.Major, v.Major); result == 0 { 114 if result = compareVersion(from.Minor, v.Minor); result == 0 { 115 if result = compareVersion(from.Patch, v.Patch); result == 0 { 116 if result = compareVersionSubstring(from.Prerelease, v.Prerelease); result == 0 { 117 result = compareVersionSubstring(from.Build, v.Build) 118 } 119 } 120 } 121 } 122 return result 123 } 124 125 // CompareToPrereleaseInts Compares the current version to another version treating the Prerelease field as an int 126 // - if from > this, -1 is returned 127 // - if from < this, 1 is returned 128 // - if they are equal, 0 is returned 129 // - if the prerelease field can not be converted to an int, it will return an error 130 func (v *SemVersion) CompareToPrereleaseInts(from *SemVersion) (int, error) { 131 var result int 132 var err error 133 if result = compareVersion(from.Major, v.Major); result == 0 { 134 if result = compareVersion(from.Minor, v.Minor); result == 0 { 135 if result = compareVersion(from.Patch, v.Patch); result == 0 { 136 fromPrereleaseStr := from.Prerelease 137 if fromPrereleaseStr == "" { 138 fromPrereleaseStr = "0" 139 } 140 vPrereleaseStr := v.Prerelease 141 if vPrereleaseStr == "" { 142 vPrereleaseStr = "0" 143 } 144 fromPrerelease, err := strconv.Atoi(fromPrereleaseStr) 145 if err != nil { 146 return 0, err 147 } 148 vPrerelease, err := strconv.Atoi(vPrereleaseStr) 149 if err != nil { 150 return 0, err 151 } 152 if result = compareVersion(int64(fromPrerelease), int64(vPrerelease)); result == 0 { 153 result = compareVersionSubstring(from.Build, v.Build) 154 } 155 } 156 } 157 } 158 return result, err 159 } 160 161 // IsEqualTo Returns true if to == from 162 func (v *SemVersion) IsEqualTo(from *SemVersion) bool { 163 return v.CompareTo(from) == 0 164 } 165 166 // IsGreatherThan Returns true if to > from 167 func (v *SemVersion) IsGreatherThan(from *SemVersion) bool { 168 return v.CompareTo(from) > 0 169 } 170 171 // IsLessThan Returns true if to < from 172 func (v *SemVersion) IsLessThan(from *SemVersion) bool { 173 return v.CompareTo(from) < 0 174 } 175 176 // ToString Convert to a valid semver string representation 177 func (v *SemVersion) ToString() string { 178 if v.Build != "" && v.Prerelease != "" { 179 return fmt.Sprintf("%v.%v.%v-%v+%v", v.Major, v.Minor, v.Patch, v.Prerelease, v.Build) 180 } else if v.Build == "" && v.Prerelease != "" { 181 return fmt.Sprintf("%v.%v.%v-%v", v.Major, v.Minor, v.Patch, v.Prerelease) 182 } else if v.Build != "" && v.Prerelease == "" { 183 return fmt.Sprintf("%v.%v.%v+%v", v.Major, v.Minor, v.Patch, v.Build) 184 } else { 185 return fmt.Sprintf("%v.%v.%v", v.Major, v.Minor, v.Patch) 186 } 187 } 188 189 // ToStringWithoutBuildAndPrerelease Convert to a valid semver string representation without the build and pre-release fields 190 func (v *SemVersion) ToStringWithoutBuildAndPrerelease() string { 191 return fmt.Sprintf("%v.%v.%v", v.Major, v.Minor, v.Patch) 192 } 193 194 // invalidVersionError returns an invalid version error message 195 func invalidVersionError(version string) error { 196 return fmt.Errorf("Invalid version string: %s (valid format is vn.n.n or n.n.n)", version) 197 } 198 199 // Returns 200 // - 1 if v2 > v1 201 // - -1 if v1 > v2 202 // - 0 of v1 == v2 203 func compareVersion(v1 int64, v2 int64) int { 204 if v1 < v2 { 205 return 1 206 } 207 if v1 > v2 { 208 return -1 209 } 210 return 0 211 } 212 213 // Returns 0 if the strings are equal, or 1 if not 214 func compareVersionSubstring(v1 string, v2 string) int { 215 if strings.Compare(v1, v2) == 0 { 216 return 0 217 } 218 return 1 219 } 220 221 // IsGreaterThanOrEqualTo Returns true if to >= from 222 func (v *SemVersion) IsGreaterThanOrEqualTo(from *SemVersion) bool { 223 return v.IsGreatherThan(from) || v.IsEqualTo(from) 224 } 225 226 // IsEqualOrPatchVersionOf Returns true if to == from or to is a patch version of from 227 func (v *SemVersion) IsEqualToOrPatchVersionOf(from *SemVersion) bool { 228 return v.IsEqualTo(from) || (from.Patch == int64(0) && v.Major == from.Major && v.Minor == from.Minor) 229 }