pkg.re/essentialkaos/ek.10@v12.41.0+incompatible/version/version.go (about) 1 // Package version provides methods for working with semver version info 2 package version 3 4 // ////////////////////////////////////////////////////////////////////////////////// // 5 // // 6 // Copyright (c) 2022 ESSENTIAL KAOS // 7 // Apache License, Version 2.0 <https://www.apache.org/licenses/LICENSE-2.0> // 8 // // 9 // ////////////////////////////////////////////////////////////////////////////////// // 10 11 import ( 12 "errors" 13 "regexp" 14 "strconv" 15 "strings" 16 ) 17 18 // ////////////////////////////////////////////////////////////////////////////////// // 19 20 // Version contains version data 21 type Version struct { 22 raw string 23 slice [3]int 24 preRelease string 25 build string 26 size int 27 } 28 29 // ////////////////////////////////////////////////////////////////////////////////// // 30 31 var ( 32 // ErrEmpty is returned is given version is empty 33 ErrEmpty = errors.New("Version can't be empty") 34 // ErrEmptyBuild is returned if build number is empty 35 ErrEmptyBuild = errors.New("Build number is empty") 36 // ErrEmptyPrerelease is returned is prerelease number is empty 37 ErrEmptyPrerelease = errors.New("Prerelease number is empty") 38 ) 39 40 // ////////////////////////////////////////////////////////////////////////////////// // 41 42 var preRegExp = regexp.MustCompile(`([a-zA-Z-.]{1,})([0-9]{0,})`) 43 44 // ////////////////////////////////////////////////////////////////////////////////// // 45 46 // Parse parses version string and return version struct 47 func Parse(v string) (Version, error) { 48 if v == "" { 49 return Version{}, ErrEmpty 50 } 51 52 var slice = [3]int{0, 0, 0} 53 var raw = v 54 55 var ( 56 preRelease string 57 build string 58 size int 59 ) 60 61 if strings.Contains(v, "+") { 62 bs := strings.Split(v, "+") 63 64 if bs[1] == "" { 65 return Version{}, ErrEmptyBuild 66 } 67 68 v = bs[0] 69 build = bs[1] 70 } 71 72 if strings.Contains(v, "-") { 73 ps := strings.Split(v, "-") 74 75 if ps[1] == "" { 76 return Version{}, ErrEmptyPrerelease 77 } 78 79 v = ps[0] 80 preRelease = ps[1] 81 } 82 83 for index, version := range strings.Split(v, ".") { 84 if index < 3 { 85 iv, err := strconv.Atoi(version) 86 87 if err != nil { 88 return Version{}, err 89 } 90 91 slice[index] = iv 92 size = index + 1 93 } 94 } 95 96 return Version{ 97 raw: raw, 98 slice: slice, 99 preRelease: preRelease, 100 build: build, 101 size: size, 102 }, nil 103 } 104 105 // ////////////////////////////////////////////////////////////////////////////////// // 106 107 // Major returns major version 108 func (v Version) Major() int { 109 if v.raw == "" || len(v.slice) == 0 { 110 return -1 111 } 112 113 return v.slice[0] 114 } 115 116 // Minor returns minor version 117 func (v Version) Minor() int { 118 if v.raw == "" || len(v.slice) == 0 { 119 return -1 120 } 121 122 return v.slice[1] 123 } 124 125 // Patch returns patch version 126 func (v Version) Patch() int { 127 if v.raw == "" || len(v.slice) == 0 { 128 return -1 129 } 130 131 return v.slice[2] 132 } 133 134 // PreRelease returns prerelease version 135 func (v Version) PreRelease() string { 136 if v.raw == "" { 137 return "" 138 } 139 140 return v.preRelease 141 } 142 143 // Build returns build string 144 func (v Version) Build() string { 145 if v.raw == "" { 146 return "" 147 } 148 149 return v.build 150 } 151 152 // Simple returns simple version (without prerelease and build info, 153 // with major, minor and patch) 154 func (v Version) Simple() string { 155 if v.raw == "" || len(v.slice) == 0 { 156 return "0.0.0" 157 } 158 159 return strconv.Itoa(v.slice[0]) + "." + strconv.Itoa(v.slice[1]) + "." + strconv.Itoa(v.slice[2]) 160 } 161 162 // Equal return true if version are equal to given 163 func (v Version) Equal(version Version) bool { 164 if v.Major() != version.Major() { 165 return false 166 } 167 168 if v.Minor() != version.Minor() { 169 return false 170 } 171 172 if v.Patch() != version.Patch() { 173 return false 174 } 175 176 if v.PreRelease() != version.PreRelease() { 177 return false 178 } 179 180 if v.Build() != version.Build() { 181 return false 182 } 183 184 return true 185 } 186 187 // Less returns true if given version is greater 188 func (v Version) Less(version Version) bool { 189 if v.Int() < version.Int() { 190 return true 191 } 192 193 pr1, pr2 := v.PreRelease(), version.PreRelease() 194 195 if pr1 != pr2 { 196 return prereleaseLess(pr1, pr2) 197 } 198 199 return false 200 } 201 202 // Greater returns true if given version is less 203 func (v Version) Greater(version Version) bool { 204 if v.Int() > version.Int() { 205 return true 206 } 207 208 pr1, pr2 := v.PreRelease(), version.PreRelease() 209 210 if pr1 != pr2 { 211 return !prereleaseLess(pr1, pr2) 212 } 213 214 return false 215 } 216 217 // Contains checks is current version contains given version 218 func (v Version) Contains(version Version) bool { 219 if v.Major() != version.Major() { 220 return false 221 } 222 223 if v.size == 1 { 224 return true 225 } 226 227 if v.Minor() != version.Minor() { 228 return false 229 } 230 231 if v.size == 2 { 232 return true 233 } 234 235 if v.Patch() != version.Patch() { 236 return false 237 } 238 239 return false 240 } 241 242 // Int returns version as integer 243 func (v Version) Int() int { 244 result := v.Major() * 1000000 245 result += v.Minor() * 1000 246 result += v.Patch() 247 248 return result 249 } 250 251 // IsZero returns if version is zero (0.0.0) 252 func (v Version) IsZero() bool { 253 return v.raw == "" 254 } 255 256 // String returns version as string 257 func (v Version) String() string { 258 return v.raw 259 } 260 261 // ////////////////////////////////////////////////////////////////////////////////// // 262 263 func prereleaseLess(pr1, pr2 string) bool { 264 // Current version is release and given is prerelease 265 if pr1 == "" && pr2 != "" { 266 return false 267 } 268 269 // Current version is prerelease and given is release 270 if pr1 != "" && pr2 == "" { 271 return true 272 } 273 274 // Parse prerelease 275 pr1Re := preRegExp.FindStringSubmatch(pr1) 276 pr2Re := preRegExp.FindStringSubmatch(pr2) 277 278 pr1Name := pr1Re[1] 279 pr2Name := pr2Re[1] 280 281 if pr1Name > pr2Name { 282 return false 283 } 284 285 if pr1Name < pr2Name { 286 return true 287 } 288 289 // Errors not important, because if subver is empty 290 // Atoi return 0 291 pr1Ver, _ := strconv.Atoi(pr1Re[2]) 292 pr2Ver, _ := strconv.Atoi(pr2Re[2]) 293 294 return pr1Ver < pr2Ver 295 }