github.com/lmorg/murex@v0.0.0-20240217211045-e081c89cd4ef/utils/semver/semver.go (about) 1 package semver 2 3 import ( 4 "errors" 5 "fmt" 6 "strconv" 7 "strings" 8 ) 9 10 type Version struct { 11 Major int 12 Minor int 13 Patch int 14 } 15 16 func raiseError(err error, context string, values ...any) (*Version, error) { 17 if err != nil { 18 return nil, errors.New(fmt.Sprintf(context, values...) + ": " + err.Error()) 19 } 20 return nil, fmt.Errorf(context, values...) 21 } 22 23 // Parse takes a version string with the following pattern: major.minor.patch 24 // and returns its component parts 25 func Parse(s string) (*Version, error) { 26 if len(s) == 0 { 27 return nil, errors.New("empty version string") 28 } 29 30 var err error 31 ver := new(Version) 32 33 split := strings.Split(s, ".") 34 35 switch len(split) { 36 case 3: 37 ver.Patch, err = strconv.Atoi(split[2]) 38 if err != nil { 39 return raiseError(err, "patch value is not a number") 40 } 41 fallthrough 42 43 case 2: 44 ver.Minor, err = strconv.Atoi(split[1]) 45 if err != nil { 46 return raiseError(err, "minor value is not a number") 47 } 48 fallthrough 49 50 case 1: 51 ver.Major, err = strconv.Atoi(split[0]) 52 if err != nil { 53 return raiseError(err, "major value is not a number") 54 } 55 56 default: 57 return raiseError(nil, "too many full stops / period or not a valid version string") 58 } 59 60 return ver, err 61 } 62 63 func Compare(version string, comparison string) (bool, error) { 64 cond, comp := parseComparison(comparison) 65 cond = strings.TrimSpace(cond) 66 comp = strings.TrimSpace(comp) 67 68 ver, err := Parse(version) 69 if err != nil { 70 return false, fmt.Errorf("cannot parse version string: '%s'", err.Error()) 71 } 72 73 compV, err := parseCompVersion(comp) 74 if err != nil { 75 return false, fmt.Errorf("cannot parse comparison string: '%s'", err.Error()) 76 } 77 78 switch cond { 79 case ">": 80 return compare(ver, compV) == greaterThan, nil 81 case ">=": 82 return compare(ver, compV) != lessThan, nil 83 case "", "=", "==": 84 return compare(ver, compV) == equalTo, nil 85 case "<=": 86 return compare(ver, compV) != greaterThan, nil 87 case "<": 88 return compare(ver, compV) == lessThan, nil 89 default: 90 return false, fmt.Errorf("unknown comparison token '%s'", cond) 91 } 92 } 93 94 func parseComparison(comparison string) (string, string) { 95 for i := range comparison { 96 switch { 97 case comparison[i] == ' ': 98 continue 99 case comparison[i] <= '9' && '0' <= comparison[i]: 100 return comparison[:i], comparison[i:] 101 } 102 } 103 return "", "" 104 } 105 106 func parseCompVersion(s string) (*Version, error) { 107 if len(s) == 0 { 108 return nil, errors.New("empty version string") 109 } 110 111 var err error 112 ver := new(Version) 113 114 split := strings.Split(s, ".") 115 116 switch len(split) { 117 case 3: 118 ver.Patch, err = strconv.Atoi(split[2]) 119 if err != nil { 120 return raiseError(err, "patch value is not a number") 121 } 122 ver.Patch += 2 123 fallthrough 124 125 case 2: 126 ver.Patch-- 127 ver.Minor, err = strconv.Atoi(split[1]) 128 if err != nil { 129 return raiseError(err, "minor value is not a number") 130 } 131 ver.Minor++ 132 fallthrough 133 134 case 1: 135 ver.Major, err = strconv.Atoi(split[0]) 136 if err != nil { 137 return raiseError(err, "major value is not a number") 138 } 139 ver.Patch-- 140 ver.Minor-- 141 142 default: 143 return raiseError(nil, "too many full stops / period or not a valid version string") 144 } 145 146 return ver, err 147 } 148 149 const ( 150 lessThan = -1 151 equalTo = 0 152 greaterThan = 1 153 ) 154 155 func compare(version *Version, comparison *Version) int { 156 switch { 157 case version.Major > comparison.Major: 158 return greaterThan 159 case version.Major < comparison.Major: 160 return lessThan 161 162 case comparison.Minor < 0: 163 return equalTo 164 165 case version.Minor > comparison.Minor: 166 return greaterThan 167 case version.Minor < comparison.Minor: 168 return lessThan 169 170 case comparison.Patch < 0: 171 return equalTo 172 173 case version.Patch > comparison.Patch: 174 return greaterThan 175 case version.Patch < comparison.Patch: 176 return lessThan 177 178 default: 179 return equalTo 180 } 181 }