podman
594 строки · 16.2 Кб
1package semver
2
3import (
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.
13type Constraints struct {
14constraints [][]*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.
19func NewConstraint(c string) (*Constraints, error) {
20
21// Rewrite - ranges into a comparison operation.
22c = rewriteRange(c)
23
24ors := strings.Split(c, "||")
25or := make([][]*constraint, len(ors))
26for 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
31if !validConstraintRegex.MatchString(v) {
32return nil, fmt.Errorf("improper constraint: %s", v)
33}
34
35cs := findConstraintRegex.FindAllString(v, -1)
36if cs == nil {
37cs = append(cs, v)
38}
39result := make([]*constraint, len(cs))
40for i, s := range cs {
41pc, err := parseConstraint(s)
42if err != nil {
43return nil, err
44}
45
46result[i] = pc
47}
48or[k] = result
49}
50
51o := &Constraints{constraints: or}
52return o, nil
53}
54
55// Check tests if a version satisfies the constraints.
56func (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
60for _, o := range cs.constraints {
61joy := true
62for _, c := range o {
63if check, _ := c.check(v); !check {
64joy = false
65break
66}
67}
68
69if joy {
70return true
71}
72}
73
74return 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.
79func (cs Constraints) Validate(v *Version) (bool, []error) {
80// loop over the ORs and check the inner ANDs
81var e []error
82
83// Capture the prerelease message only once. When it happens the first time
84// this var is marked
85var prerelesase bool
86for _, o := range cs.constraints {
87joy := true
88for _, 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.
91if c.con.pre == "" && v.pre != "" {
92if !prerelesase {
93em := fmt.Errorf("%s is a prerelease version and the constraint is only looking for release versions", v)
94e = append(e, em)
95prerelesase = true
96}
97joy = false
98
99} else {
100
101if _, err := c.check(v); err != nil {
102e = append(e, err)
103joy = false
104}
105}
106}
107
108if joy {
109return true, []error{}
110}
111}
112
113return false, e
114}
115
116func (cs Constraints) String() string {
117buf := make([]string, len(cs.constraints))
118var tmp bytes.Buffer
119
120for k, v := range cs.constraints {
121tmp.Reset()
122vlen := len(v)
123for kk, c := range v {
124tmp.WriteString(c.string())
125
126// Space separate the AND conditions
127if vlen > 1 && kk < vlen-1 {
128tmp.WriteString(" ")
129}
130}
131buf[k] = tmp.String()
132}
133
134return strings.Join(buf, " || ")
135}
136
137// UnmarshalText implements the encoding.TextUnmarshaler interface.
138func (cs *Constraints) UnmarshalText(text []byte) error {
139temp, err := NewConstraint(string(text))
140if err != nil {
141return err
142}
143
144*cs = *temp
145
146return nil
147}
148
149// MarshalText implements the encoding.TextMarshaler interface.
150func (cs Constraints) MarshalText() ([]byte, error) {
151return []byte(cs.String()), nil
152}
153
154var constraintOps map[string]cfunc
155var constraintRegex *regexp.Regexp
156var constraintRangeRegex *regexp.Regexp
157
158// Used to find individual constraints within a multi-constraint string
159var findConstraintRegex *regexp.Regexp
160
161// Used to validate an segment of ANDs is valid
162var validConstraintRegex *regexp.Regexp
163
164const 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
168func init() {
169constraintOps = 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
184ops := `=||!=|>|<|>=|=>|<=|=<|~|~>|\^`
185
186constraintRegex = regexp.MustCompile(fmt.Sprintf(
187`^\s*(%s)\s*(%s)\s*$`,
188ops,
189cvRegex))
190
191constraintRangeRegex = regexp.MustCompile(fmt.Sprintf(
192`\s*(%s)\s+-\s+(%s)\s*`,
193cvRegex, cvRegex))
194
195findConstraintRegex = regexp.MustCompile(fmt.Sprintf(
196`(%s)\s*(%s)`,
197ops,
198cvRegex))
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.
203validConstraintRegex = regexp.MustCompile(fmt.Sprintf(
204`^(\s*(%s)\s*(%s)\s*)((?:\s+|,\s*)(%s)\s*(%s)\s*)*$`,
205ops,
206cvRegex,
207ops,
208cvRegex))
209}
210
211// An individual constraint
212type 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.
215con *Version
216
217// The original parsed version (e.g., 4.x from != 4.x)
218orig string
219
220// The original operator for the constraint
221origfunc string
222
223// When an x is used as part of the version (e.g., 1.x)
224minorDirty bool
225dirty bool
226patchDirty bool
227}
228
229// Check if a version meets the constraint
230func (c *constraint) check(v *Version) (bool, error) {
231return constraintOps[c.origfunc](v, c)
232}
233
234// String prints an individual constraint into a string
235func (c *constraint) string() string {
236return c.origfunc + c.orig
237}
238
239type cfunc func(v *Version, c *constraint) (bool, error)
240
241func parseConstraint(c string) (*constraint, error) {
242if len(c) > 0 {
243m := constraintRegex.FindStringSubmatch(c)
244if m == nil {
245return nil, fmt.Errorf("improper constraint: %s", c)
246}
247
248cs := &constraint{
249orig: m[2],
250origfunc: m[1],
251}
252
253ver := m[2]
254minorDirty := false
255patchDirty := false
256dirty := false
257if isX(m[3]) || m[3] == "" {
258ver = fmt.Sprintf("0.0.0%s", m[6])
259dirty = true
260} else if isX(strings.TrimPrefix(m[4], ".")) || m[4] == "" {
261minorDirty = true
262dirty = true
263ver = fmt.Sprintf("%s.0.0%s", m[3], m[6])
264} else if isX(strings.TrimPrefix(m[5], ".")) || m[5] == "" {
265dirty = true
266patchDirty = true
267ver = fmt.Sprintf("%s%s.0%s", m[3], m[4], m[6])
268}
269
270con, err := NewVersion(ver)
271if err != nil {
272
273// The constraintRegex should catch any regex parsing errors. So,
274// we should never get here.
275return nil, errors.New("constraint Parser Error")
276}
277
278cs.con = con
279cs.minorDirty = minorDirty
280cs.patchDirty = patchDirty
281cs.dirty = dirty
282
283return 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
288con, err := StrictNewVersion("0.0.0")
289if err != nil {
290
291// The constraintRegex should catch any regex parsing errors. So,
292// we should never get here.
293return nil, errors.New("constraint Parser Error")
294}
295
296cs := &constraint{
297con: con,
298orig: c,
299origfunc: "",
300minorDirty: false,
301patchDirty: false,
302dirty: true,
303}
304return cs, nil
305}
306
307// Constraint functions
308func constraintNotEqual(v *Version, c *constraint) (bool, error) {
309if 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.
314if v.Prerelease() != "" && c.con.Prerelease() == "" {
315return false, fmt.Errorf("%s is a prerelease version and the constraint is only looking for release versions", v)
316}
317
318if c.con.Major() != v.Major() {
319return true, nil
320}
321if c.con.Minor() != v.Minor() && !c.minorDirty {
322return true, nil
323} else if c.minorDirty {
324return false, fmt.Errorf("%s is equal to %s", v, c.orig)
325} else if c.con.Patch() != v.Patch() && !c.patchDirty {
326return true, nil
327} else if c.patchDirty {
328// Need to handle prereleases if present
329if v.Prerelease() != "" || c.con.Prerelease() != "" {
330eq := comparePrerelease(v.Prerelease(), c.con.Prerelease()) != 0
331if eq {
332return true, nil
333}
334return false, fmt.Errorf("%s is equal to %s", v, c.orig)
335}
336return false, fmt.Errorf("%s is equal to %s", v, c.orig)
337}
338}
339
340eq := v.Equal(c.con)
341if eq {
342return false, fmt.Errorf("%s is equal to %s", v, c.orig)
343}
344
345return true, nil
346}
347
348func 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.
353if v.Prerelease() != "" && c.con.Prerelease() == "" {
354return false, fmt.Errorf("%s is a prerelease version and the constraint is only looking for release versions", v)
355}
356
357var eq bool
358
359if !c.dirty {
360eq = v.Compare(c.con) == 1
361if eq {
362return true, nil
363}
364return false, fmt.Errorf("%s is less than or equal to %s", v, c.orig)
365}
366
367if v.Major() > c.con.Major() {
368return true, nil
369} else if v.Major() < c.con.Major() {
370return 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
374return 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
378eq = v.Minor() > c.con.Minor()
379if eq {
380return true, nil
381}
382return 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.
387eq = v.Compare(c.con) == 1
388if eq {
389return true, nil
390}
391return false, fmt.Errorf("%s is less than or equal to %s", v, c.orig)
392}
393
394func 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.
398if v.Prerelease() != "" && c.con.Prerelease() == "" {
399return false, fmt.Errorf("%s is a prerelease version and the constraint is only looking for release versions", v)
400}
401
402eq := v.Compare(c.con) < 0
403if eq {
404return true, nil
405}
406return false, fmt.Errorf("%s is greater than or equal to %s", v, c.orig)
407}
408
409func 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.
414if v.Prerelease() != "" && c.con.Prerelease() == "" {
415return false, fmt.Errorf("%s is a prerelease version and the constraint is only looking for release versions", v)
416}
417
418eq := v.Compare(c.con) >= 0
419if eq {
420return true, nil
421}
422return false, fmt.Errorf("%s is less than %s", v, c.orig)
423}
424
425func 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.
429if v.Prerelease() != "" && c.con.Prerelease() == "" {
430return false, fmt.Errorf("%s is a prerelease version and the constraint is only looking for release versions", v)
431}
432
433var eq bool
434
435if !c.dirty {
436eq = v.Compare(c.con) <= 0
437if eq {
438return true, nil
439}
440return false, fmt.Errorf("%s is greater than %s", v, c.orig)
441}
442
443if v.Major() > c.con.Major() {
444return 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 {
446return false, fmt.Errorf("%s is greater than %s", v, c.orig)
447}
448
449return 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
458func 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.
462if v.Prerelease() != "" && c.con.Prerelease() == "" {
463return false, fmt.Errorf("%s is a prerelease version and the constraint is only looking for release versions", v)
464}
465
466if v.LessThan(c.con) {
467return 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.
472if c.con.Major() == 0 && c.con.Minor() == 0 && c.con.Patch() == 0 &&
473!c.minorDirty && !c.patchDirty {
474return true, nil
475}
476
477if v.Major() != c.con.Major() {
478return false, fmt.Errorf("%s does not have same major version as %s", v, c.orig)
479}
480
481if v.Minor() != c.con.Minor() && !c.minorDirty {
482return false, fmt.Errorf("%s does not have same major and minor version as %s", v, c.orig)
483}
484
485return true, nil
486}
487
488// When there is a .x (dirty) status it automatically opts in to ~. Otherwise
489// it's a straight =
490func 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.
494if v.Prerelease() != "" && c.con.Prerelease() == "" {
495return false, fmt.Errorf("%s is a prerelease version and the constraint is only looking for release versions", v)
496}
497
498if c.dirty {
499return constraintTilde(v, c)
500}
501
502eq := v.Equal(c.con)
503if eq {
504return true, nil
505}
506
507return 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
519func 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.
523if v.Prerelease() != "" && c.con.Prerelease() == "" {
524return 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
528if v.LessThan(c.con) {
529return false, fmt.Errorf("%s is less than %s", v, c.orig)
530}
531
532var eq bool
533
534// ^ when the major > 0 is >=x.y.z < x+1
535if 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.
540eq = v.Major() == c.con.Major()
541if eq {
542return true, nil
543}
544return 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
548if c.con.Major() == 0 && v.Major() > 0 {
549return 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
552if c.con.Minor() > 0 || c.patchDirty {
553eq = v.Minor() == c.con.Minor()
554if eq {
555return true, nil
556}
557return 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
560if c.con.Minor() == 0 && v.Minor() > 0 {
561return 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
566eq = c.con.Patch() == v.Patch()
567if eq {
568return true, nil
569}
570return 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
573func isX(x string) bool {
574switch x {
575case "x", "*", "X":
576return true
577default:
578return false
579}
580}
581
582func rewriteRange(i string) string {
583m := constraintRangeRegex.FindAllStringSubmatch(i, -1)
584if m == nil {
585return i
586}
587o := i
588for _, v := range m {
589t := fmt.Sprintf(">= %s, <= %s", v[1], v[11])
590o = strings.Replace(o, v[0], t, 1)
591}
592
593return o
594}
595