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  }