1 package semver
2
3 import (
4 "bytes"
5 "errors"
6 "fmt"
7 "regexp"
8 "strings"
9 )
10
11
12
13 type Constraints struct {
14 constraints [][]*constraint
15 }
16
17
18
19 func NewConstraint(c string) (*Constraints, error) {
20
21
22 c = rewriteRange(c)
23
24 ors := strings.Split(c, "||")
25 or := make([][]*constraint, len(ors))
26 for k, v := range ors {
27
28
29
30
31 if !validConstraintRegex.MatchString(v) {
32 return nil, fmt.Errorf("improper constraint: %s", v)
33 }
34
35 cs := findConstraintRegex.FindAllString(v, -1)
36 if cs == nil {
37 cs = append(cs, v)
38 }
39 result := make([]*constraint, len(cs))
40 for i, s := range cs {
41 pc, err := parseConstraint(s)
42 if err != nil {
43 return nil, err
44 }
45
46 result[i] = pc
47 }
48 or[k] = result
49 }
50
51 o := &Constraints{constraints: or}
52 return o, nil
53 }
54
55
56 func (cs Constraints) Check(v *Version) bool {
57
58
59
60 for _, o := range cs.constraints {
61 joy := true
62 for _, c := range o {
63 if check, _ := c.check(v); !check {
64 joy = false
65 break
66 }
67 }
68
69 if joy {
70 return true
71 }
72 }
73
74 return false
75 }
76
77
78
79 func (cs Constraints) Validate(v *Version) (bool, []error) {
80
81 var e []error
82
83
84
85 var prerelesase bool
86 for _, o := range cs.constraints {
87 joy := true
88 for _, c := range o {
89
90
91 if c.con.pre == "" && v.pre != "" {
92 if !prerelesase {
93 em := fmt.Errorf("%s is a prerelease version and the constraint is only looking for release versions", v)
94 e = append(e, em)
95 prerelesase = true
96 }
97 joy = false
98
99 } else {
100
101 if _, err := c.check(v); err != nil {
102 e = append(e, err)
103 joy = false
104 }
105 }
106 }
107
108 if joy {
109 return true, []error{}
110 }
111 }
112
113 return false, e
114 }
115
116 func (cs Constraints) String() string {
117 buf := make([]string, len(cs.constraints))
118 var tmp bytes.Buffer
119
120 for k, v := range cs.constraints {
121 tmp.Reset()
122 vlen := len(v)
123 for kk, c := range v {
124 tmp.WriteString(c.string())
125
126
127 if vlen > 1 && kk < vlen-1 {
128 tmp.WriteString(" ")
129 }
130 }
131 buf[k] = tmp.String()
132 }
133
134 return strings.Join(buf, " || ")
135 }
136
137
138 func (cs *Constraints) UnmarshalText(text []byte) error {
139 temp, err := NewConstraint(string(text))
140 if err != nil {
141 return err
142 }
143
144 *cs = *temp
145
146 return nil
147 }
148
149
150 func (cs Constraints) MarshalText() ([]byte, error) {
151 return []byte(cs.String()), nil
152 }
153
154 var constraintOps map[string]cfunc
155 var constraintRegex *regexp.Regexp
156 var constraintRangeRegex *regexp.Regexp
157
158
159 var findConstraintRegex *regexp.Regexp
160
161
162 var validConstraintRegex *regexp.Regexp
163
164 const cvRegex string = `v?([0-9|x|X|\*]+)(\.[0-9|x|X|\*]+)?(\.[0-9|x|X|\*]+)?` +
165 `(-([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*))?` +
166 `(\+([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*))?`
167
168 func init() {
169 constraintOps = map[string]cfunc{
170 "": constraintTildeOrEqual,
171 "=": constraintTildeOrEqual,
172 "!=": constraintNotEqual,
173 ">": constraintGreaterThan,
174 "<": constraintLessThan,
175 ">=": constraintGreaterThanEqual,
176 "=>": constraintGreaterThanEqual,
177 "<=": constraintLessThanEqual,
178 "=<": constraintLessThanEqual,
179 "~": constraintTilde,
180 "~>": constraintTilde,
181 "^": constraintCaret,
182 }
183
184 ops := `=||!=|>|<|>=|=>|<=|=<|~|~>|\^`
185
186 constraintRegex = regexp.MustCompile(fmt.Sprintf(
187 `^\s*(%s)\s*(%s)\s*$`,
188 ops,
189 cvRegex))
190
191 constraintRangeRegex = regexp.MustCompile(fmt.Sprintf(
192 `\s*(%s)\s+-\s+(%s)\s*`,
193 cvRegex, cvRegex))
194
195 findConstraintRegex = regexp.MustCompile(fmt.Sprintf(
196 `(%s)\s*(%s)`,
197 ops,
198 cvRegex))
199
200
201
202
203 validConstraintRegex = regexp.MustCompile(fmt.Sprintf(
204 `^(\s*(%s)\s*(%s)\s*)((?:\s+|,\s*)(%s)\s*(%s)\s*)*$`,
205 ops,
206 cvRegex,
207 ops,
208 cvRegex))
209 }
210
211
212 type constraint struct {
213
214
215 con *Version
216
217
218 orig string
219
220
221 origfunc string
222
223
224 minorDirty bool
225 dirty bool
226 patchDirty bool
227 }
228
229
230 func (c *constraint) check(v *Version) (bool, error) {
231 return constraintOps[c.origfunc](v, c)
232 }
233
234
235 func (c *constraint) string() string {
236 return c.origfunc + c.orig
237 }
238
239 type cfunc func(v *Version, c *constraint) (bool, error)
240
241 func parseConstraint(c string) (*constraint, error) {
242 if len(c) > 0 {
243 m := constraintRegex.FindStringSubmatch(c)
244 if m == nil {
245 return nil, fmt.Errorf("improper constraint: %s", c)
246 }
247
248 cs := &constraint{
249 orig: m[2],
250 origfunc: m[1],
251 }
252
253 ver := m[2]
254 minorDirty := false
255 patchDirty := false
256 dirty := false
257 if isX(m[3]) || m[3] == "" {
258 ver = fmt.Sprintf("0.0.0%s", m[6])
259 dirty = true
260 } else if isX(strings.TrimPrefix(m[4], ".")) || m[4] == "" {
261 minorDirty = true
262 dirty = true
263 ver = fmt.Sprintf("%s.0.0%s", m[3], m[6])
264 } else if isX(strings.TrimPrefix(m[5], ".")) || m[5] == "" {
265 dirty = true
266 patchDirty = true
267 ver = fmt.Sprintf("%s%s.0%s", m[3], m[4], m[6])
268 }
269
270 con, err := NewVersion(ver)
271 if err != nil {
272
273
274
275 return nil, errors.New("constraint Parser Error")
276 }
277
278 cs.con = con
279 cs.minorDirty = minorDirty
280 cs.patchDirty = patchDirty
281 cs.dirty = dirty
282
283 return cs, nil
284 }
285
286
287
288 con, err := StrictNewVersion("0.0.0")
289 if err != nil {
290
291
292
293 return nil, errors.New("constraint Parser Error")
294 }
295
296 cs := &constraint{
297 con: con,
298 orig: c,
299 origfunc: "",
300 minorDirty: false,
301 patchDirty: false,
302 dirty: true,
303 }
304 return cs, nil
305 }
306
307
308 func constraintNotEqual(v *Version, c *constraint) (bool, error) {
309 if c.dirty {
310
311
312
313
314 if v.Prerelease() != "" && c.con.Prerelease() == "" {
315 return false, fmt.Errorf("%s is a prerelease version and the constraint is only looking for release versions", v)
316 }
317
318 if c.con.Major() != v.Major() {
319 return true, nil
320 }
321 if c.con.Minor() != v.Minor() && !c.minorDirty {
322 return true, nil
323 } else if c.minorDirty {
324 return false, fmt.Errorf("%s is equal to %s", v, c.orig)
325 } else if c.con.Patch() != v.Patch() && !c.patchDirty {
326 return true, nil
327 } else if c.patchDirty {
328
329 if v.Prerelease() != "" || c.con.Prerelease() != "" {
330 eq := comparePrerelease(v.Prerelease(), c.con.Prerelease()) != 0
331 if eq {
332 return true, nil
333 }
334 return false, fmt.Errorf("%s is equal to %s", v, c.orig)
335 }
336 return false, fmt.Errorf("%s is equal to %s", v, c.orig)
337 }
338 }
339
340 eq := v.Equal(c.con)
341 if eq {
342 return false, fmt.Errorf("%s is equal to %s", v, c.orig)
343 }
344
345 return true, nil
346 }
347
348 func constraintGreaterThan(v *Version, c *constraint) (bool, error) {
349
350
351
352
353 if v.Prerelease() != "" && c.con.Prerelease() == "" {
354 return false, fmt.Errorf("%s is a prerelease version and the constraint is only looking for release versions", v)
355 }
356
357 var eq bool
358
359 if !c.dirty {
360 eq = v.Compare(c.con) == 1
361 if eq {
362 return true, nil
363 }
364 return false, fmt.Errorf("%s is less than or equal to %s", v, c.orig)
365 }
366
367 if v.Major() > c.con.Major() {
368 return true, nil
369 } else if v.Major() < c.con.Major() {
370 return false, fmt.Errorf("%s is less than or equal to %s", v, c.orig)
371 } else if c.minorDirty {
372
373
374 return false, fmt.Errorf("%s is less than or equal to %s", v, c.orig)
375 } else if c.patchDirty {
376
377
378 eq = v.Minor() > c.con.Minor()
379 if eq {
380 return true, nil
381 }
382 return false, fmt.Errorf("%s is less than or equal to %s", v, c.orig)
383 }
384
385
386
387 eq = v.Compare(c.con) == 1
388 if eq {
389 return true, nil
390 }
391 return false, fmt.Errorf("%s is less than or equal to %s", v, c.orig)
392 }
393
394 func constraintLessThan(v *Version, c *constraint) (bool, error) {
395
396
397
398 if v.Prerelease() != "" && c.con.Prerelease() == "" {
399 return false, fmt.Errorf("%s is a prerelease version and the constraint is only looking for release versions", v)
400 }
401
402 eq := v.Compare(c.con) < 0
403 if eq {
404 return true, nil
405 }
406 return false, fmt.Errorf("%s is greater than or equal to %s", v, c.orig)
407 }
408
409 func constraintGreaterThanEqual(v *Version, c *constraint) (bool, error) {
410
411
412
413
414 if v.Prerelease() != "" && c.con.Prerelease() == "" {
415 return false, fmt.Errorf("%s is a prerelease version and the constraint is only looking for release versions", v)
416 }
417
418 eq := v.Compare(c.con) >= 0
419 if eq {
420 return true, nil
421 }
422 return false, fmt.Errorf("%s is less than %s", v, c.orig)
423 }
424
425 func constraintLessThanEqual(v *Version, c *constraint) (bool, error) {
426
427
428
429 if v.Prerelease() != "" && c.con.Prerelease() == "" {
430 return false, fmt.Errorf("%s is a prerelease version and the constraint is only looking for release versions", v)
431 }
432
433 var eq bool
434
435 if !c.dirty {
436 eq = v.Compare(c.con) <= 0
437 if eq {
438 return true, nil
439 }
440 return false, fmt.Errorf("%s is greater than %s", v, c.orig)
441 }
442
443 if v.Major() > c.con.Major() {
444 return false, fmt.Errorf("%s is greater than %s", v, c.orig)
445 } else if v.Major() == c.con.Major() && v.Minor() > c.con.Minor() && !c.minorDirty {
446 return false, fmt.Errorf("%s is greater than %s", v, c.orig)
447 }
448
449 return true, nil
450 }
451
452
453
454
455
456
457
458 func constraintTilde(v *Version, c *constraint) (bool, error) {
459
460
461
462 if v.Prerelease() != "" && c.con.Prerelease() == "" {
463 return false, fmt.Errorf("%s is a prerelease version and the constraint is only looking for release versions", v)
464 }
465
466 if v.LessThan(c.con) {
467 return false, fmt.Errorf("%s is less than %s", v, c.orig)
468 }
469
470
471
472 if c.con.Major() == 0 && c.con.Minor() == 0 && c.con.Patch() == 0 &&
473 !c.minorDirty && !c.patchDirty {
474 return true, nil
475 }
476
477 if v.Major() != c.con.Major() {
478 return false, fmt.Errorf("%s does not have same major version as %s", v, c.orig)
479 }
480
481 if v.Minor() != c.con.Minor() && !c.minorDirty {
482 return false, fmt.Errorf("%s does not have same major and minor version as %s", v, c.orig)
483 }
484
485 return true, nil
486 }
487
488
489
490 func constraintTildeOrEqual(v *Version, c *constraint) (bool, error) {
491
492
493
494 if v.Prerelease() != "" && c.con.Prerelease() == "" {
495 return false, fmt.Errorf("%s is a prerelease version and the constraint is only looking for release versions", v)
496 }
497
498 if c.dirty {
499 return constraintTilde(v, c)
500 }
501
502 eq := v.Equal(c.con)
503 if eq {
504 return true, nil
505 }
506
507 return false, fmt.Errorf("%s is not equal to %s", v, c.orig)
508 }
509
510
511
512
513
514
515
516
517
518
519 func constraintCaret(v *Version, c *constraint) (bool, error) {
520
521
522
523 if v.Prerelease() != "" && c.con.Prerelease() == "" {
524 return false, fmt.Errorf("%s is a prerelease version and the constraint is only looking for release versions", v)
525 }
526
527
528 if v.LessThan(c.con) {
529 return false, fmt.Errorf("%s is less than %s", v, c.orig)
530 }
531
532 var eq bool
533
534
535 if c.con.Major() > 0 || c.minorDirty {
536
537
538
539
540 eq = v.Major() == c.con.Major()
541 if eq {
542 return true, nil
543 }
544 return false, fmt.Errorf("%s does not have same major version as %s", v, c.orig)
545 }
546
547
548 if c.con.Major() == 0 && v.Major() > 0 {
549 return false, fmt.Errorf("%s does not have same major version as %s", v, c.orig)
550 }
551
552 if c.con.Minor() > 0 || c.patchDirty {
553 eq = v.Minor() == c.con.Minor()
554 if eq {
555 return true, nil
556 }
557 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)
558 }
559
560 if c.con.Minor() == 0 && v.Minor() > 0 {
561 return false, fmt.Errorf("%s does not have same minor version as %s", v, c.orig)
562 }
563
564
565
566 eq = c.con.Patch() == v.Patch()
567 if eq {
568 return true, nil
569 }
570 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)
571 }
572
573 func isX(x string) bool {
574 switch x {
575 case "x", "*", "X":
576 return true
577 default:
578 return false
579 }
580 }
581
582 func rewriteRange(i string) string {
583 m := constraintRangeRegex.FindAllStringSubmatch(i, -1)
584 if m == nil {
585 return i
586 }
587 o := i
588 for _, v := range m {
589 t := fmt.Sprintf(">= %s, <= %s ", v[1], v[11])
590 o = strings.Replace(o, v[0], t, 1)
591 }
592
593 return o
594 }
595
View as plain text