github.com/Lephar/snapd@v0.0.0-20210825215435-c7fba9cef4d2/strutil/version.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2014-2017 Canonical Ltd 5 * 6 * This program is free software: you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License version 3 as 8 * published by the Free Software Foundation. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License 16 * along with this program. If not, see <http://www.gnu.org/licenses/>. 17 * 18 */ 19 20 package strutil 21 22 import ( 23 "fmt" 24 "strings" 25 ) 26 27 // golang: seriously? that's sad! 28 func max(a, b int) int { 29 if a < b { 30 return b 31 } 32 return a 33 } 34 35 //go:generate go run ./chrorder/main.go -package=strutil -output=chrorder.go 36 37 func cmpString(as, bs string) int { 38 for i := 0; i < max(len(as), len(bs)); i++ { 39 var a uint8 40 var b uint8 41 if i < len(as) { 42 a = as[i] 43 } 44 if i < len(bs) { 45 b = bs[i] 46 } 47 if chOrder[a] < chOrder[b] { 48 return -1 49 } 50 if chOrder[a] > chOrder[b] { 51 return +1 52 } 53 } 54 return 0 55 } 56 57 func trimLeadingZeroes(a string) string { 58 for i := 0; i < len(a); i++ { 59 if a[i] != '0' { 60 return a[i:] 61 } 62 } 63 return "" 64 } 65 66 // a and b both match /[0-9]+/ 67 func cmpNumeric(a, b string) int { 68 a = trimLeadingZeroes(a) 69 b = trimLeadingZeroes(b) 70 71 switch d := len(a) - len(b); { 72 case d > 0: 73 return 1 74 case d < 0: 75 return -1 76 } 77 for i := 0; i < len(a); i++ { 78 switch { 79 case a[i] > b[i]: 80 return 1 81 case a[i] < b[i]: 82 return -1 83 } 84 } 85 return 0 86 } 87 88 func matchEpoch(a string) bool { 89 if len(a) == 0 { 90 return false 91 } 92 if a[0] < '0' || a[0] > '9' { 93 return false 94 } 95 var i int 96 for i = 1; i < len(a) && a[i] >= '0' && a[i] <= '9'; i++ { 97 } 98 return i < len(a) && a[i] == ':' 99 } 100 101 func atMostOneDash(a string) bool { 102 seen := false 103 for i := 0; i < len(a); i++ { 104 if a[i] == '-' { 105 if seen { 106 return false 107 } 108 seen = true 109 } 110 } 111 return true 112 } 113 114 // VersionIsValid returns true if the given string is a valid 115 // version number according to the debian policy 116 func VersionIsValid(a string) bool { 117 if matchEpoch(a) { 118 return false 119 } 120 return atMostOneDash(a) 121 } 122 123 func nextFrag(s string) (frag, rest string, numeric bool) { 124 if len(s) == 0 { 125 return "", "", false 126 } 127 128 var i int 129 if s[0] >= '0' && s[0] <= '9' { 130 // is digit 131 for i = 1; i < len(s) && s[i] >= '0' && s[i] <= '9'; i++ { 132 } 133 numeric = true 134 } else { 135 // not digit 136 for i = 1; i < len(s) && (s[i] < '0' || s[i] > '9'); i++ { 137 } 138 } 139 return s[:i], s[i:], numeric 140 } 141 142 func compareSubversion(va, vb string) int { 143 var a, b string 144 var anum, bnum bool 145 var res int 146 for res == 0 { 147 a, va, anum = nextFrag(va) 148 b, vb, bnum = nextFrag(vb) 149 if a == "" && b == "" { 150 break 151 } 152 if anum && bnum { 153 res = cmpNumeric(a, b) 154 } else { 155 res = cmpString(a, b) 156 } 157 } 158 return res 159 } 160 161 // VersionCompare compare two version strings that follow the debian 162 // version policy and 163 // Returns: 164 // -1 if a is smaller than b 165 // 0 if a equals b 166 // +1 if a is bigger than b 167 func VersionCompare(va, vb string) (res int, err error) { 168 // FIXME: return err here instead 169 if !VersionIsValid(va) { 170 return 0, fmt.Errorf("invalid version %q", va) 171 } 172 if !VersionIsValid(vb) { 173 return 0, fmt.Errorf("invalid version %q", vb) 174 } 175 176 var sa, sb string 177 if ia := strings.IndexByte(va, '-'); ia < 0 { 178 sa = "0" 179 } else { 180 va, sa = va[:ia], va[ia+1:] 181 } 182 if ib := strings.IndexByte(vb, '-'); ib < 0 { 183 sb = "0" 184 } else { 185 vb, sb = vb[:ib], vb[ib+1:] 186 } 187 188 // the main version number (before the "-") 189 res = compareSubversion(va, vb) 190 if res != 0 { 191 return res, nil 192 } 193 194 // the subversion revision behind the "-" 195 return compareSubversion(sa, sb), nil 196 }