podman

Форк
0
594 строки · 16.2 Кб
1
package semver
2

3
import (
4
	"bytes"
5
	"errors"
6
	"fmt"
7
	"regexp"
8
	"strings"
9
)
10

11
// Constraints is one or more constraint that a semantic version can be
12
// checked against.
13
type Constraints struct {
14
	constraints [][]*constraint
15
}
16

17
// NewConstraint returns a Constraints instance that a Version instance can
18
// be checked against. If there is a parse error it will be returned.
19
func NewConstraint(c string) (*Constraints, error) {
20

21
	// Rewrite - ranges into a comparison operation.
22
	c = rewriteRange(c)
23

24
	ors := strings.Split(c, "||")
25
	or := make([][]*constraint, len(ors))
26
	for k, v := range ors {
27

28
		// TODO: Find a way to validate and fetch all the constraints in a simpler form
29

30
		// Validate the segment
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
// Check tests if a version satisfies the constraints.
56
func (cs Constraints) Check(v *Version) bool {
57
	// TODO(mattfarina): For v4 of this library consolidate the Check and Validate
58
	// functions as the underlying functions make that possible now.
59
	// loop over the ORs and check the inner ANDs
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
// Validate checks if a version satisfies a constraint. If not a slice of
78
// reasons for the failure are returned in addition to a bool.
79
func (cs Constraints) Validate(v *Version) (bool, []error) {
80
	// loop over the ORs and check the inner ANDs
81
	var e []error
82

83
	// Capture the prerelease message only once. When it happens the first time
84
	// this var is marked
85
	var prerelesase bool
86
	for _, o := range cs.constraints {
87
		joy := true
88
		for _, c := range o {
89
			// Before running the check handle the case there the version is
90
			// a prerelease and the check is not searching for prereleases.
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
			// Space separate the AND conditions
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
// UnmarshalText implements the encoding.TextUnmarshaler interface.
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
// MarshalText implements the encoding.TextMarshaler interface.
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
// Used to find individual constraints within a multi-constraint string
159
var findConstraintRegex *regexp.Regexp
160

161
// Used to validate an segment of ANDs is valid
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
	// The first time a constraint shows up will look slightly different from
201
	// future times it shows up due to a leading space or comma in a given
202
	// string.
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
// An individual constraint
212
type constraint struct {
213
	// The version used in the constraint check. For example, if a constraint
214
	// is '<= 2.0.0' the con a version instance representing 2.0.0.
215
	con *Version
216

217
	// The original parsed version (e.g., 4.x from != 4.x)
218
	orig string
219

220
	// The original operator for the constraint
221
	origfunc string
222

223
	// When an x is used as part of the version (e.g., 1.x)
224
	minorDirty bool
225
	dirty      bool
226
	patchDirty bool
227
}
228

229
// Check if a version meets the constraint
230
func (c *constraint) check(v *Version) (bool, error) {
231
	return constraintOps[c.origfunc](v, c)
232
}
233

234
// String prints an individual constraint into a string
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
			// The constraintRegex should catch any regex parsing errors. So,
274
			// we should never get here.
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
	// The rest is the special case where an empty string was passed in which
287
	// is equivalent to * or >=0.0.0
288
	con, err := StrictNewVersion("0.0.0")
289
	if err != nil {
290

291
		// The constraintRegex should catch any regex parsing errors. So,
292
		// we should never get here.
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
// Constraint functions
308
func constraintNotEqual(v *Version, c *constraint) (bool, error) {
309
	if c.dirty {
310

311
		// If there is a pre-release on the version but the constraint isn't looking
312
		// for them assume that pre-releases are not compatible. See issue 21 for
313
		// more details.
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
			// Need to handle prereleases if present
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
	// If there is a pre-release on the version but the constraint isn't looking
351
	// for them assume that pre-releases are not compatible. See issue 21 for
352
	// more details.
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
		// This is a range case such as >11. When the version is something like
373
		// 11.1.0 is it not > 11. For that we would need 12 or higher
374
		return false, fmt.Errorf("%s is less than or equal to %s", v, c.orig)
375
	} else if c.patchDirty {
376
		// This is for ranges such as >11.1. A version of 11.1.1 is not greater
377
		// which one of 11.2.1 is greater
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
	// If we have gotten here we are not comparing pre-preleases and can use the
386
	// Compare function to accomplish that.
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
	// If there is a pre-release on the version but the constraint isn't looking
396
	// for them assume that pre-releases are not compatible. See issue 21 for
397
	// more details.
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
	// If there is a pre-release on the version but the constraint isn't looking
412
	// for them assume that pre-releases are not compatible. See issue 21 for
413
	// more details.
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
	// If there is a pre-release on the version but the constraint isn't looking
427
	// for them assume that pre-releases are not compatible. See issue 21 for
428
	// more details.
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
// ~*, ~>* --> >= 0.0.0 (any)
453
// ~2, ~2.x, ~2.x.x, ~>2, ~>2.x ~>2.x.x --> >=2.0.0, <3.0.0
454
// ~2.0, ~2.0.x, ~>2.0, ~>2.0.x --> >=2.0.0, <2.1.0
455
// ~1.2, ~1.2.x, ~>1.2, ~>1.2.x --> >=1.2.0, <1.3.0
456
// ~1.2.3, ~>1.2.3 --> >=1.2.3, <1.3.0
457
// ~1.2.0, ~>1.2.0 --> >=1.2.0, <1.3.0
458
func constraintTilde(v *Version, c *constraint) (bool, error) {
459
	// If there is a pre-release on the version but the constraint isn't looking
460
	// for them assume that pre-releases are not compatible. See issue 21 for
461
	// more details.
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
	// ~0.0.0 is a special case where all constraints are accepted. It's
471
	// equivalent to >= 0.0.0.
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
// When there is a .x (dirty) status it automatically opts in to ~. Otherwise
489
// it's a straight =
490
func constraintTildeOrEqual(v *Version, c *constraint) (bool, error) {
491
	// If there is a pre-release on the version but the constraint isn't looking
492
	// for them assume that pre-releases are not compatible. See issue 21 for
493
	// more details.
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
// ^*      -->  (any)
511
// ^1.2.3  -->  >=1.2.3 <2.0.0
512
// ^1.2    -->  >=1.2.0 <2.0.0
513
// ^1      -->  >=1.0.0 <2.0.0
514
// ^0.2.3  -->  >=0.2.3 <0.3.0
515
// ^0.2    -->  >=0.2.0 <0.3.0
516
// ^0.0.3  -->  >=0.0.3 <0.0.4
517
// ^0.0    -->  >=0.0.0 <0.1.0
518
// ^0      -->  >=0.0.0 <1.0.0
519
func constraintCaret(v *Version, c *constraint) (bool, error) {
520
	// If there is a pre-release on the version but the constraint isn't looking
521
	// for them assume that pre-releases are not compatible. See issue 21 for
522
	// more details.
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
	// This less than handles prereleases
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
	// ^ when the major > 0 is >=x.y.z < x+1
535
	if c.con.Major() > 0 || c.minorDirty {
536

537
		// ^ has to be within a major range for > 0. Everything less than was
538
		// filtered out with the LessThan call above. This filters out those
539
		// that greater but not within the same major range.
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
	// ^ when the major is 0 and minor > 0 is >=0.y.z < 0.y+1
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
	// If the con Minor is > 0 it is not dirty
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
	// ^ when the minor is 0 and minor > 0 is =0.0.z
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
	// At this point the major is 0 and the minor is 0 and not dirty. The patch
565
	// is not dirty so we need to check if they are equal. If they are not equal
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

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.