github.com/machinefi/w3bstream@v1.6.5-rc9.0.20240426031326-b8c7c4876e72/pkg/depends/base/ver/ver_constraints.go (about) 1 package ver 2 3 import ( 4 "bytes" 5 "errors" 6 "fmt" 7 "regexp" 8 "strings" 9 ) 10 11 type Constraints struct { 12 constraints [][]*constraint 13 } 14 15 func NewConstraint(c string) (*Constraints, error) { 16 c = rewriteRange(c) 17 18 ors := strings.Split(c, "||") 19 or := make([][]*constraint, len(ors)) 20 for k, v := range ors { 21 22 if !regexpValidConstraint.MatchString(v) { 23 return nil, fmt.Errorf("improper constraint: %s", v) 24 } 25 26 cs := regexpFindConstraint.FindAllString(v, -1) 27 if cs == nil { 28 cs = append(cs, v) 29 } 30 result := make([]*constraint, len(cs)) 31 for i, s := range cs { 32 pc, err := parseConstraint(s) 33 if err != nil { 34 return nil, err 35 } 36 37 result[i] = pc 38 } 39 or[k] = result 40 } 41 42 o := &Constraints{constraints: or} 43 return o, nil 44 } 45 46 func (cs Constraints) Check(v *Version) bool { 47 for _, o := range cs.constraints { 48 joy := true 49 for _, c := range o { 50 if check, _ := c.check(v); !check { 51 joy = false 52 break 53 } 54 } 55 56 if joy { 57 return true 58 } 59 } 60 61 return false 62 } 63 64 func (cs Constraints) Validate(v *Version) (bool, []error) { 65 var e []error 66 67 var prerelesase bool 68 for _, o := range cs.constraints { 69 joy := true 70 for _, c := range o { 71 if c.con.prerelease == "" && v.prerelease != "" { 72 if !prerelesase { 73 em := fmt.Errorf("%s is a prerelease version and the constraint is only looking for release versions", v) 74 e = append(e, em) 75 prerelesase = true 76 } 77 joy = false 78 79 } else { 80 81 if _, err := c.check(v); err != nil { 82 e = append(e, err) 83 joy = false 84 } 85 } 86 } 87 88 if joy { 89 return true, []error{} 90 } 91 } 92 93 return false, e 94 } 95 96 func (cs Constraints) String() string { 97 buf := make([]string, len(cs.constraints)) 98 var tmp bytes.Buffer 99 100 for k, v := range cs.constraints { 101 tmp.Reset() 102 vlen := len(v) 103 for kk, c := range v { 104 tmp.WriteString(c.string()) 105 106 if vlen > 1 && kk < vlen-1 { 107 tmp.WriteString(" ") 108 } 109 } 110 buf[k] = tmp.String() 111 } 112 113 return strings.Join(buf, " || ") 114 } 115 116 var ( 117 constraintOps map[string]cfunc 118 regexpConstraint *regexp.Regexp 119 regexpConstraintRange *regexp.Regexp 120 regexpFindConstraint *regexp.Regexp 121 regexpValidConstraint *regexp.Regexp 122 regexpConstraintVersion = regexp.MustCompile(`v?([0-9|x|X|\*]+)(\.[0-9|x|X|\*]+)?(\.[0-9|x|X|\*]+)?(-([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*))?(\+([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*))?`) 123 ) 124 125 func init() { 126 constraintOps = map[string]cfunc{ 127 "": constraintTildeOrEqual, 128 "=": constraintTildeOrEqual, 129 "!=": constraintNotEqual, 130 ">": constraintGreaterThan, 131 "<": constraintLessThan, 132 ">=": constraintGreaterThanEqual, 133 "=>": constraintGreaterThanEqual, 134 "<=": constraintLessThanEqual, 135 "=<": constraintLessThanEqual, 136 "~": constraintTilde, 137 "~>": constraintTilde, 138 "^": constraintCaret, 139 } 140 141 ops := make([]string, 0, len(constraintOps)) 142 for k := range constraintOps { 143 ops = append(ops, regexp.QuoteMeta(k)) 144 } 145 146 regexpConstraint = regexp.MustCompile(fmt.Sprintf(`^\s*(%s)\s*(%s)\s*$`, strings.Join(ops, "|"), regexpConstraintVersion)) 147 regexpConstraintRange = regexp.MustCompile(fmt.Sprintf(`\s*(%s)\s+-\s+(%s)\s*`, regexpConstraintVersion, regexpConstraintVersion)) 148 regexpFindConstraint = regexp.MustCompile(fmt.Sprintf(`(%s)\s*(%s)`, strings.Join(ops, "|"), regexpConstraintVersion)) 149 regexpValidConstraint = regexp.MustCompile(fmt.Sprintf(`^(\s*(%s)\s*(%s)\s*\,?)+$`, strings.Join(ops, "|"), regexpConstraintVersion)) 150 } 151 152 type constraint struct { 153 con *Version 154 155 orig string 156 157 origfunc string 158 159 minorDirty bool 160 dirty bool 161 patchDirty bool 162 } 163 164 func (c *constraint) check(v *Version) (bool, error) { 165 return constraintOps[c.origfunc](v, c) 166 } 167 168 func (c *constraint) string() string { 169 return c.origfunc + c.orig 170 } 171 172 type cfunc func(v *Version, c *constraint) (bool, error) 173 174 func parseConstraint(c string) (*constraint, error) { 175 if len(c) > 0 { 176 m := regexpConstraint.FindStringSubmatch(c) 177 if m == nil { 178 return nil, fmt.Errorf("improper constraint: %s", c) 179 } 180 181 cs := &constraint{ 182 orig: m[2], 183 origfunc: m[1], 184 } 185 186 ver := m[2] 187 minorDirty := false 188 patchDirty := false 189 dirty := false 190 if isX(m[3]) || m[3] == "" { 191 ver = "0.0.0" 192 dirty = true 193 } else if isX(strings.TrimPrefix(m[4], ".")) || m[4] == "" { 194 minorDirty = true 195 dirty = true 196 ver = fmt.Sprintf("%s.0.0%s", m[3], m[6]) 197 } else if isX(strings.TrimPrefix(m[5], ".")) || m[5] == "" { 198 dirty = true 199 patchDirty = true 200 ver = fmt.Sprintf("%s%s.0%s", m[3], m[4], m[6]) 201 } 202 203 con, err := ParseVersion(ver) 204 if err != nil { 205 206 return nil, errors.New("constraint Parser Error") 207 } 208 209 cs.con = con 210 cs.minorDirty = minorDirty 211 cs.patchDirty = patchDirty 212 cs.dirty = dirty 213 214 return cs, nil 215 } 216 217 con, err := ParseVersion("0.0.0") 218 if err != nil { 219 220 return nil, errors.New("constraint Parser Error") 221 } 222 223 cs := &constraint{ 224 con: con, 225 orig: c, 226 origfunc: "", 227 minorDirty: false, 228 patchDirty: false, 229 dirty: true, 230 } 231 return cs, nil 232 } 233 234 func constraintNotEqual(v *Version, c *constraint) (bool, error) { 235 if c.dirty { 236 237 if v.Prerelease() != "" && c.con.Prerelease() == "" { 238 return false, fmt.Errorf("%s is a prerelease version and the constraint is only looking for release versions", v) 239 } 240 241 if c.con.Major() != v.Major() { 242 return true, nil 243 } 244 if c.con.Minor() != v.Minor() && !c.minorDirty { 245 return true, nil 246 } else if c.minorDirty { 247 return false, fmt.Errorf("%s is equal to %s", v, c.orig) 248 } else if c.con.Patch() != v.Patch() && !c.patchDirty { 249 return true, nil 250 } else if c.patchDirty { 251 if v.Prerelease() != "" || c.con.Prerelease() != "" { 252 eq := comparePrerelease(v.Prerelease(), c.con.Prerelease()) != 0 253 if eq { 254 return true, nil 255 } 256 return false, fmt.Errorf("%s is equal to %s", v, c.orig) 257 } 258 return false, fmt.Errorf("%s is equal to %s", v, c.orig) 259 } 260 } 261 262 eq := v.Equal(c.con) 263 if eq { 264 return false, fmt.Errorf("%s is equal to %s", v, c.orig) 265 } 266 267 return true, nil 268 } 269 270 func constraintGreaterThan(v *Version, c *constraint) (bool, error) { 271 272 if v.Prerelease() != "" && c.con.Prerelease() == "" { 273 return false, fmt.Errorf("%s is a prerelease version and the constraint is only looking for release versions", v) 274 } 275 276 var eq bool 277 278 if !c.dirty { 279 eq = v.Compare(c.con) == 1 280 if eq { 281 return true, nil 282 } 283 return false, fmt.Errorf("%s is less than or equal to %s", v, c.orig) 284 } 285 286 if v.Major() > c.con.Major() { 287 return true, nil 288 } else if v.Major() < c.con.Major() { 289 return false, fmt.Errorf("%s is less than or equal to %s", v, c.orig) 290 } else if c.minorDirty { 291 return false, fmt.Errorf("%s is less than or equal to %s", v, c.orig) 292 } else if c.patchDirty { 293 eq = v.Minor() > c.con.Minor() 294 if eq { 295 return true, nil 296 } 297 return false, fmt.Errorf("%s is less than or equal to %s", v, c.orig) 298 } 299 300 eq = v.Compare(c.con) == 1 301 if eq { 302 return true, nil 303 } 304 return false, fmt.Errorf("%s is less than or equal to %s", v, c.orig) 305 } 306 307 func constraintLessThan(v *Version, c *constraint) (bool, error) { 308 if v.Prerelease() != "" && c.con.Prerelease() == "" { 309 return false, fmt.Errorf("%s is a prerelease version and the constraint is only looking for release versions", v) 310 } 311 312 eq := v.Compare(c.con) < 0 313 if eq { 314 return true, nil 315 } 316 return false, fmt.Errorf("%s is greater than or equal to %s", v, c.orig) 317 } 318 319 func constraintGreaterThanEqual(v *Version, c *constraint) (bool, error) { 320 321 if v.Prerelease() != "" && c.con.Prerelease() == "" { 322 return false, fmt.Errorf("%s is a prerelease version and the constraint is only looking for release versions", v) 323 } 324 325 eq := v.Compare(c.con) >= 0 326 if eq { 327 return true, nil 328 } 329 return false, fmt.Errorf("%s is less than %s", v, c.orig) 330 } 331 332 func constraintLessThanEqual(v *Version, c *constraint) (bool, error) { 333 if v.Prerelease() != "" && c.con.Prerelease() == "" { 334 return false, fmt.Errorf("%s is a prerelease version and the constraint is only looking for release versions", v) 335 } 336 337 var eq bool 338 339 if !c.dirty { 340 eq = v.Compare(c.con) <= 0 341 if eq { 342 return true, nil 343 } 344 return false, fmt.Errorf("%s is greater than %s", v, c.orig) 345 } 346 347 if v.Major() > c.con.Major() { 348 return false, fmt.Errorf("%s is greater than %s", v, c.orig) 349 } else if v.Major() == c.con.Major() && v.Minor() > c.con.Minor() && !c.minorDirty { 350 return false, fmt.Errorf("%s is greater than %s", v, c.orig) 351 } 352 353 return true, nil 354 } 355 356 func constraintTilde(v *Version, c *constraint) (bool, error) { 357 if v.Prerelease() != "" && c.con.Prerelease() == "" { 358 return false, fmt.Errorf("%s is a prerelease version and the constraint is only looking for release versions", v) 359 } 360 361 if v.LessThan(c.con) { 362 return false, fmt.Errorf("%s is less than %s", v, c.orig) 363 } 364 365 if c.con.Major() == 0 && c.con.Minor() == 0 && c.con.Patch() == 0 && 366 !c.minorDirty && !c.patchDirty { 367 return true, nil 368 } 369 370 if v.Major() != c.con.Major() { 371 return false, fmt.Errorf("%s does not have same major version as %s", v, c.orig) 372 } 373 374 if v.Minor() != c.con.Minor() && !c.minorDirty { 375 return false, fmt.Errorf("%s does not have same major and minor version as %s", v, c.orig) 376 } 377 378 return true, nil 379 } 380 381 func constraintTildeOrEqual(v *Version, c *constraint) (bool, error) { 382 if v.Prerelease() != "" && c.con.Prerelease() == "" { 383 return false, fmt.Errorf("%s is a prerelease version and the constraint is only looking for release versions", v) 384 } 385 386 if c.dirty { 387 return constraintTilde(v, c) 388 } 389 390 eq := v.Equal(c.con) 391 if eq { 392 return true, nil 393 } 394 395 return false, fmt.Errorf("%s is not equal to %s", v, c.orig) 396 } 397 398 func constraintCaret(v *Version, c *constraint) (bool, error) { 399 if v.Prerelease() != "" && c.con.Prerelease() == "" { 400 return false, fmt.Errorf("%s is a prerelease version and the constraint is only looking for release versions", v) 401 } 402 403 if v.LessThan(c.con) { 404 return false, fmt.Errorf("%s is less than %s", v, c.orig) 405 } 406 407 var eq bool 408 409 if c.con.Major() > 0 || c.minorDirty { 410 411 eq = v.Major() == c.con.Major() 412 if eq { 413 return true, nil 414 } 415 return false, fmt.Errorf("%s does not have same major version as %s", v, c.orig) 416 } 417 418 if c.con.Major() == 0 && v.Major() > 0 { 419 return false, fmt.Errorf("%s does not have same major version as %s", v, c.orig) 420 } 421 if c.con.Minor() > 0 || c.patchDirty { 422 eq = v.Minor() == c.con.Minor() 423 if eq { 424 return true, nil 425 } 426 return false, fmt.Errorf("%s does not have same minor version as %s. Expected minor versions to match when constraint major version is 0", v, c.orig) 427 } 428 429 eq = c.con.Patch() == v.Patch() 430 if eq { 431 return true, nil 432 } 433 return false, fmt.Errorf("%s does not equal %s. Expect version and constraint to equal when major and minor versions are 0", v, c.orig) 434 } 435 436 func isX(x string) bool { 437 switch x { 438 case "x", "*", "X": 439 return true 440 default: 441 return false 442 } 443 } 444 445 func rewriteRange(i string) string { 446 m := regexpConstraintRange.FindAllStringSubmatch(i, -1) 447 if m == nil { 448 return i 449 } 450 o := i 451 for _, v := range m { 452 t := fmt.Sprintf(">= %s, <= %s", v[1], v[11]) 453 o = strings.Replace(o, v[0], t, 1) 454 } 455 456 return o 457 }