Created
August 23, 2021 13:01
-
-
Save Jarred-Sumner/d6069033db005fe8d3c102f3d3055015 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//go:generate go-enum -f=$GOFILE --marshal | |
// originally a fork of https://github.com/blang/semver iirc | |
package node_semver | |
import ( | |
"math" | |
"math/bits" | |
"sort" | |
"strconv" | |
"strings" | |
"sync" | |
jsoniter "github.com/json-iterator/go" | |
) | |
type WildcardType byte | |
const ( | |
NoneWildcard WildcardType = iota | |
MajorWildcard WildcardType = 1 | |
MinorWildcard WildcardType = 2 | |
PatchWildcard WildcardType = 3 | |
) | |
type comparator func(Version, Version) bool | |
var ( | |
compEQ comparator = func(v1 Version, v2 Version) bool { | |
return v1.Compare(v2) == 0 | |
} | |
compNE comparator = func(v1 Version, v2 Version) bool { | |
return v1.Compare(v2) != 0 | |
} | |
compGT comparator = func(v1 Version, v2 Version) bool { | |
return v1.Compare(v2) == 1 | |
} | |
compGE comparator = func(v1 Version, v2 Version) bool { | |
return v1.Compare(v2) >= 0 | |
} | |
compLT comparator = func(v1 Version, v2 Version) bool { | |
return v1.Compare(v2) == -1 | |
} | |
compLE comparator = func(v1 Version, v2 Version) bool { | |
return v1.Compare(v2) <= 0 | |
} | |
) | |
type versionRange struct { | |
v Version | |
c comparator | |
} | |
type Version struct { | |
Major uint64 | |
Minor uint64 | |
Patch uint64 | |
Pre []string | |
Build []string | |
} | |
// MarshalJSON implements the encoding/json.Marshaler interface. | |
func (v Version) MarshalJSON() ([]byte, error) { | |
return jsoniter.Marshal(v.String()) | |
} | |
// UnmarshalJSON implements the encoding/json.Unmarshaler interface. | |
func (v Version) UnmarshalJSON(data []byte) (err error) { | |
var versionString string | |
if err = jsoniter.Unmarshal(data, &versionString); err != nil { | |
return | |
} | |
v = *Tokenize(versionString).Version | |
return | |
} | |
// Version to string | |
func (v Version) String() string { | |
b := strings.Builder{} | |
majorLen := bits.Len64(v.Major) / 8 | |
if majorLen == 0 { | |
majorLen = 1 | |
} | |
minorLen := bits.Len64(v.Major) / 8 | |
if minorLen == 0 { | |
minorLen = 1 | |
} | |
patchLen := bits.Len64(v.Major) / 8 | |
if patchLen == 0 { | |
patchLen = 1 | |
} | |
preLen := len(v.Pre) | |
for _, pre := range v.Pre { | |
preLen += len(pre) | |
} | |
buildLen := len(v.Build) | |
for _, pre := range v.Build { | |
preLen += len(pre) | |
} | |
b.Grow(majorLen + minorLen + patchLen + preLen + buildLen + 2) | |
b.WriteString(strconv.FormatUint(v.Major, 10)) | |
b.WriteRune('.') | |
b.WriteString(strconv.FormatUint(v.Minor, 10)) | |
b.WriteRune('.') | |
b.WriteString(strconv.FormatUint(v.Patch, 10)) | |
if len(v.Pre) > 0 { | |
b.WriteRune('-') | |
b.WriteString(v.Pre[0]) | |
for _, pre := range v.Pre[1:] { | |
b.WriteRune('.') | |
b.WriteString(pre) | |
} | |
} | |
if len(v.Build) > 0 { | |
b.WriteRune('+') | |
b.WriteString(v.Build[0]) | |
for _, pre := range v.Build[1:] { | |
b.WriteRune('.') | |
b.WriteString(pre) | |
} | |
} | |
return b.String() | |
} | |
func (v *Version) Reset() { | |
v.Major = 0 | |
v.Minor = 0 | |
v.Patch = 0 | |
v.Build = nil | |
v.Pre = nil | |
} | |
func (v *Version) CopyTo(other *Version) { | |
other.Major = v.Major | |
other.Minor = v.Minor | |
other.Patch = v.Patch | |
other.Build = v.Build | |
other.Pre = v.Pre | |
} | |
func (vv Version) ToRange(comp comparator) Range { | |
return Range(func(v Version) bool { | |
return comp(v, vv) | |
}) | |
} | |
func (vv *Version) ToWildcardRange(wildcard WildcardType) Range { | |
switch wildcard { | |
case MajorWildcard: | |
{ | |
vv.Reset() | |
return vv.ToRange(compGE) | |
} | |
case MinorWildcard: | |
{ | |
v := Version{ | |
Major: vv.Major + 1, | |
} | |
vv.Reset() | |
vv.Major = v.Major - 1 | |
return v.ToRange(compLT).AND(vv.ToRange(compGE)) | |
} | |
case PatchWildcard: | |
{ | |
v := Version{ | |
Major: vv.Major, | |
Minor: vv.Minor + 1, | |
} | |
vv.Build = nil | |
vv.Pre = nil | |
return v.ToRange(compLT).AND(vv.ToRange(compGE)) | |
} | |
default: | |
{ | |
return vv.ToRange(compEQ) | |
} | |
} | |
} | |
func (v *Version) IsEmpty() bool { | |
return v.Build == nil && v.Major == 0 && v.Minor == 0 && v.Patch == 0 && v.Pre == nil | |
} | |
// Equals checks if v is equal to o. | |
func (v Version) Equals(o Version) bool { | |
return (v.Compare(o) == 0) | |
} | |
// EQ checks if v is equal to o. | |
func (v Version) EQ(o Version) bool { | |
return (v.Compare(o) == 0) | |
} | |
// NE checks if v is not equal to o. | |
func (v Version) NE(o Version) bool { | |
return (v.Compare(o) != 0) | |
} | |
// GT checks if v is greater than o. | |
func (v Version) GT(o Version) bool { | |
return (v.Compare(o) == 1) | |
} | |
// GTE checks if v is greater than or equal to o. | |
func (v Version) GTE(o Version) bool { | |
return (v.Compare(o) >= 0) | |
} | |
// GE checks if v is greater than or equal to o. | |
func (v Version) GE(o Version) bool { | |
return (v.Compare(o) >= 0) | |
} | |
// LT checks if v is less than o. | |
func (v Version) LT(o Version) bool { | |
return (v.Compare(o) == -1) | |
} | |
// LTE checks if v is less than or equal to o. | |
func (v Version) LTE(o Version) bool { | |
return (v.Compare(o) <= 0) | |
} | |
// LE checks if v is less than or equal to o. | |
func (v Version) LE(o Version) bool { | |
return (v.Compare(o) <= 0) | |
} | |
func (v Version) SortableCompare(o Version) int { | |
hasPre := len(v.Pre) > 0 | |
otherHasPre := len(o.Pre) > 0 | |
if hasPre != otherHasPre && otherHasPre { | |
return 1 | |
} | |
if v.Major > o.Major { | |
return 1 | |
} else if v.Major < o.Major { | |
return -1 | |
} else if v.Minor > o.Minor { | |
return 1 | |
} else if v.Minor < o.Minor { | |
return -1 | |
} else if v.Patch > o.Patch { | |
return 1 | |
} else if v.Patch < o.Patch { | |
return -1 | |
} | |
hasBuild := len(v.Build) > 0 | |
otherHasBuild := len(o.Build) > 0 | |
if hasBuild && !otherHasBuild { | |
return 1 | |
} else if !hasBuild && otherHasBuild { | |
return -1 | |
} | |
// i := 0 | |
// buildComparator := 0 | |
// preComparator := 0 | |
// if hasPre && otherHasPre { | |
// for ; i < len(v.Pre) && i < len(o.Pre); i++ { | |
// if comp := strings.Compare(v.Pre[i], o.Pre[i]); comp == 0 { | |
// continue | |
// } else if comp == 1 { | |
// preComparator = 1 | |
// break | |
// } else { | |
// preComparator = -1 | |
// break | |
// } | |
// } | |
// } else if hasPre { | |
// preComparator = -1 | |
// } else if otherHasPre { | |
// preComparator = 1 | |
// } | |
// if hasBuild && !hasPre && otherHasPre { | |
// return 1 | |
// } | |
// if hasBuild && hasPre && !otherHasPre && otherHasBuild { | |
// return -1 | |
// } | |
// if hasPre && hasBuild && otherHasPre && !otherHasBuild { | |
// return 1 | |
// } else if hasPre && hasBuild && otherHasPre && otherHasBuild { | |
// return 0 | |
// } else if hasPre && !hasBuild && !otherHasPre && !otherHasBuild { | |
// return -1 | |
// } else if !hasPre && hasBuild && !otherHasPre && !otherHasBuild { | |
// return 1 | |
// } else if !hasPre && hasBuild && otherHasPre && !otherHasBuild { | |
// return 1 | |
// } | |
// if hasPre && otherHasPre { | |
// return 0 | |
// } // if hasBuild && otherHasBuild { | |
// for ; i < len(v.Build) && i < len(o.Build); i++ { | |
// if comp := strings.Compare(v.Build[i], o.Build[i]); comp == 0 { | |
// continue | |
// } else if comp == 1 { | |
// buildComparator = 1 | |
// break | |
// } else { | |
// buildComparator = -1 | |
// break | |
// } | |
// } | |
// } else if hasBuild { | |
// buildComparator = 1 | |
// } else if otherHasBuild { | |
// buildComparator = -1 | |
// } | |
// if preComparator != 0 { | |
// return preComparator | |
// } | |
return 0 | |
// return buildComparator | |
} | |
// Compare compares Versions v to o: | |
// -1 == v is less than o | |
// 0 == v is equal to o | |
// 1 == v is greater than o | |
func (v Version) Compare(o Version) int { | |
hasPre := len(v.Pre) > 0 | |
otherHasPre := len(o.Pre) > 0 | |
if hasPre != otherHasPre && otherHasPre { | |
return 1 | |
} | |
// if hasPre && !otherHasPre { | |
// return -1 | |
// } else if otherHasPre && !hasPre { | |
// return 1 | |
// } | |
if v.Major > o.Major { | |
return 1 | |
} else if v.Major < o.Major { | |
return -1 | |
} else if v.Minor > o.Minor { | |
return 1 | |
} else if v.Minor < o.Minor { | |
return -1 | |
} else if v.Patch > o.Patch { | |
return 1 | |
} else if v.Patch < o.Patch { | |
return -1 | |
} | |
hasBuild := len(v.Build) > 0 | |
otherHasBuild := len(o.Build) > 0 | |
if hasBuild && !otherHasBuild { | |
return 1 | |
} else if !hasBuild && otherHasBuild { | |
return -1 | |
} | |
// i := 0 | |
// buildComparator := 0 | |
// preComparator := 0 | |
// if hasPre && otherHasPre { | |
// for ; i < len(v.Pre) && i < len(o.Pre); i++ { | |
// if comp := strings.Compare(v.Pre[i], o.Pre[i]); comp == 0 { | |
// continue | |
// } else if comp == 1 { | |
// preComparator = 1 | |
// break | |
// } else { | |
// preComparator = -1 | |
// break | |
// } | |
// } | |
// } else if hasPre { | |
// preComparator = -1 | |
// } else if otherHasPre { | |
// preComparator = 1 | |
// } | |
// if hasBuild && !hasPre && otherHasPre { | |
// return 1 | |
// } | |
// if hasBuild && hasPre && !otherHasPre && otherHasBuild { | |
// return -1 | |
// } | |
// if hasPre && hasBuild && otherHasPre && !otherHasBuild { | |
// return 1 | |
// } else if hasPre && hasBuild && otherHasPre && otherHasBuild { | |
// return 0 | |
// } else if hasPre && !hasBuild && !otherHasPre && !otherHasBuild { | |
// return -1 | |
// } else if !hasPre && hasBuild && !otherHasPre && !otherHasBuild { | |
// return 1 | |
// } else if !hasPre && hasBuild && otherHasPre && !otherHasBuild { | |
// return 1 | |
// } | |
// if hasPre && otherHasPre { | |
// return 0 | |
// } // if hasBuild && otherHasBuild { | |
// for ; i < len(v.Build) && i < len(o.Build); i++ { | |
// if comp := strings.Compare(v.Build[i], o.Build[i]); comp == 0 { | |
// continue | |
// } else if comp == 1 { | |
// buildComparator = 1 | |
// break | |
// } else { | |
// buildComparator = -1 | |
// break | |
// } | |
// } | |
// } else if hasBuild { | |
// buildComparator = 1 | |
// } else if otherHasBuild { | |
// buildComparator = -1 | |
// } | |
// if preComparator != 0 { | |
// return preComparator | |
// } | |
// return buildComparator | |
return 0 | |
} | |
// rangeFunc creates a Range from the given versionRange. | |
func (vr *versionRange) rangeFunc() Range { | |
return Range(func(v Version) bool { | |
return vr.c(v, vr.v) | |
}) | |
} | |
type Range func(Version) bool | |
// OR combines the existing Range with another Range using logical OR. | |
func (rf Range) OR(f Range) Range { | |
return Range(func(v Version) bool { | |
return rf(v) || f(v) | |
}) | |
} | |
// AND combines the existing Range with another Range using logical AND. | |
func (rf Range) AND(f Range) Range { | |
return Range(func(v Version) bool { | |
return rf(v) && f(v) | |
}) | |
} | |
/*ENUM( | |
Major | |
Minor | |
Patch | |
Prerelease | |
Prepatch | |
Preminor | |
Premajor | |
)*/ | |
type VersionLevel byte | |
func (v *Version) IncrementBy(level VersionLevel, dest *Version) { | |
switch level { | |
case VersionLevelMajor: | |
{ | |
dest.Major = v.Major + 1 | |
dest.Minor = 0 | |
dest.Patch = 0 | |
if len(dest.Build) > 0 { | |
dest.Build = nil | |
} | |
} | |
case VersionLevelMinor: | |
{ | |
dest.Major = v.Major | |
dest.Minor = v.Minor + 1 | |
dest.Patch = 0 | |
if len(dest.Build) > 0 { | |
dest.Build = nil | |
} | |
} | |
case VersionLevelPatch: | |
{ | |
dest.Major = v.Major | |
dest.Minor = v.Minor | |
dest.Patch = v.Patch + 1 | |
if len(dest.Build) > 0 { | |
dest.Build = nil | |
} | |
} | |
case VersionLevelPrerelease: | |
{ | |
dest.Major = v.Major | |
dest.Minor = v.Minor | |
dest.Patch = v.Patch | |
} | |
} | |
} | |
/*ENUM( | |
None | |
Version | |
Range | |
)*/ | |
type TokenizeResultValue byte | |
type TokenizeResult struct { | |
Version *Version | |
Range Range | |
Value TokenizeResultValue | |
} | |
func (t *TokenizeResult) TestString(input string) bool { | |
alt := Tokenize(input) | |
return t.Test(&alt) | |
} | |
func (t *TokenizeResult) Test(o *TokenizeResult) bool { | |
if o.Value == TokenizeResultValueVersion && t.Value == TokenizeResultValueVersion { | |
return o.Version.EQ(*t.Version) | |
} else if o.Value == TokenizeResultValueVersion && t.Value == TokenizeResultValueRange { | |
return t.Range(*o.Version) | |
} else if o.Value == TokenizeResultValueRange && t.Value == TokenizeResultValueVersion { | |
return o.Range(*t.Version) | |
} else { | |
return false | |
} | |
} | |
func (t *TokenizeResult) IsVersion() bool { | |
return t.Value == TokenizeResultValueVersion | |
} | |
func (t *TokenizeResult) IsRange() bool { | |
return t.Value == TokenizeResultValueRange | |
} | |
var scratchVersionPool = sync.Pool{ | |
New: func() interface{} { | |
v := Version{} | |
return &v | |
}, | |
} | |
func buildRangeFunc(comp comparator, major uint64, minor uint64, patch uint64) Range { | |
vv := Version{Major: major, Minor: minor, Patch: patch} | |
return Range(func(v Version) bool { | |
return comp(v, vv) | |
}) | |
} | |
var preparsedTable = map[string]TokenizeResult{ | |
"*": {Range: buildRangeFunc(compGE, 0, 0, 0), Value: TokenizeResultValueRange}, | |
"X": {Range: buildRangeFunc(compGE, 0, 0, 0), Value: TokenizeResultValueRange}, | |
"x": {Range: buildRangeFunc(compGE, 0, 0, 0), Value: TokenizeResultValueRange}, | |
"0": {Range: buildRangeFunc(compGE, 0, 0, 0).AND(buildRangeFunc(compLT, 1, 0, 0)), Value: TokenizeResultValueRange}, | |
"1": {Range: buildRangeFunc(compGE, 1, 0, 0).AND(buildRangeFunc(compLT, 2, 0, 0)), Value: TokenizeResultValueRange}, | |
"2": {Range: buildRangeFunc(compGE, 2, 0, 0).AND(buildRangeFunc(compLT, 3, 0, 0)), Value: TokenizeResultValueRange}, | |
"3": {Range: buildRangeFunc(compGE, 3, 0, 0).AND(buildRangeFunc(compLT, 4, 0, 0)), Value: TokenizeResultValueRange}, | |
"4": {Range: buildRangeFunc(compGE, 4, 0, 0).AND(buildRangeFunc(compLT, 5, 0, 0)), Value: TokenizeResultValueRange}, | |
"5": {Range: buildRangeFunc(compGE, 5, 0, 0).AND(buildRangeFunc(compLT, 6, 0, 0)), Value: TokenizeResultValueRange}, | |
"6": {Range: buildRangeFunc(compGE, 6, 0, 0).AND(buildRangeFunc(compLT, 7, 0, 0)), Value: TokenizeResultValueRange}, | |
"7": {Range: buildRangeFunc(compGE, 7, 0, 0).AND(buildRangeFunc(compLT, 8, 0, 0)), Value: TokenizeResultValueRange}, | |
"8": {Range: buildRangeFunc(compGE, 8, 0, 0).AND(buildRangeFunc(compLT, 9, 0, 0)), Value: TokenizeResultValueRange}, | |
"9": {Range: buildRangeFunc(compGE, 9, 0, 0).AND(buildRangeFunc(compLT, 10, 0, 0)), Value: TokenizeResultValueRange}, | |
"10": {Range: buildRangeFunc(compGE, 10, 0, 0).AND(buildRangeFunc(compLT, 11, 0, 0)), Value: TokenizeResultValueRange}, | |
"": {Range: buildRangeFunc(compGE, 0, 0, 0), Value: TokenizeResultValueRange}, | |
} | |
func (result *TokenizeResult) AppendVersion(v *Version) { | |
switch result.Value { | |
case TokenizeResultValueNone: | |
{ | |
result.Version = v | |
result.Value = TokenizeResultValueVersion | |
} | |
case TokenizeResultValueRange: | |
{ | |
result.Range = result.Range.OR(v.ToRange(compEQ)) | |
result.Value = TokenizeResultValueRange | |
} | |
case TokenizeResultValueVersion: | |
{ | |
result.Range = result.Version.ToRange(compEQ).OR(v.ToRange(compEQ)) | |
result.Value = TokenizeResultValueRange | |
} | |
} | |
} | |
func (result *TokenizeResult) AppendRange(r Range) { | |
switch result.Value { | |
case TokenizeResultValueNone: | |
{ | |
result.Range = r | |
} | |
case TokenizeResultValueVersion: | |
{ | |
result.Range = result.Version.ToRange(compEQ).AND(r) | |
result.Version = nil | |
} | |
case TokenizeResultValueRange: | |
{ | |
result.Range = result.Range.AND(r) | |
} | |
} | |
result.Value = TokenizeResultValueRange | |
} | |
func (result *TokenizeResult) AppendORRange(r Range) { | |
switch result.Value { | |
case TokenizeResultValueNone: | |
{ | |
result.Range = r | |
} | |
case TokenizeResultValueVersion: | |
{ | |
result.Range = result.Version.ToRange(compEQ).OR(r) | |
result.Version = nil | |
} | |
case TokenizeResultValueRange: | |
{ | |
result.Range = result.Range.OR(r) | |
} | |
} | |
result.Value = TokenizeResultValueRange | |
} | |
func (result *TokenizeResult) AppendWildcard(wildcard WildcardType, v *Version) { | |
if wildcard == NoneWildcard { | |
result.AppendVersion(v) | |
return | |
} | |
switch result.Value { | |
case TokenizeResultValueNone: | |
{ | |
result.Range = v.ToWildcardRange(wildcard) | |
} | |
case TokenizeResultValueVersion: | |
{ | |
result.Range = result.Version.ToRange(compEQ).AND(v.ToWildcardRange(wildcard)) | |
result.Version = nil | |
} | |
case TokenizeResultValueRange: | |
{ | |
result.Range = result.Range.AND(v.ToWildcardRange(wildcard)) | |
} | |
} | |
result.Value = TokenizeResultValueRange | |
} | |
/*ENUM( | |
None | |
OR | |
GT | |
GE | |
LT | |
LE | |
Version | |
Tilda | |
Caret | |
) | |
*/ | |
type sevmerTokenType byte | |
type semverToken struct { | |
Token sevmerTokenType | |
Wildcard WildcardType | |
} | |
func (t *semverToken) ToRange(v *Version) Range { | |
switch t.Token { | |
// Allows changes that do not modify the left-most non-zero element in the [major, minor, patch] tuple. In other words, this allows patch and minor updates for versions 1.0.0 and above, patch updates for versions 0.X >=0.1.0, and no updates for versions 0.0.X. | |
case SevmerTokenTypeCaret: | |
{ | |
if v.Major == 0 { | |
return v.ToWildcardRange(PatchWildcard) | |
} | |
if t.Wildcard == MinorWildcard || (t.Wildcard == NoneWildcard && v.Minor == 0 && v.Patch == 0) { | |
r1 := v.ToRange(compGE) | |
v2 := &Version{} | |
v.CopyTo(v2) | |
v2.Minor = math.MaxUint64 | |
v2.Patch = math.MaxUint64 | |
return r1.AND(v2.ToRange(compLE)) | |
} | |
if v.Minor == 0 && v.Patch == 0 { | |
return v.ToWildcardRange(PatchWildcard) | |
} else { | |
r1 := v.ToRange(compGE) | |
v2 := &Version{} | |
v.CopyTo(v2) | |
v2.Minor = math.MaxUint64 | |
v2.Patch = math.MaxUint64 | |
return r1.AND(v2.ToRange(compLE)) | |
} | |
} | |
case SevmerTokenTypeTilda: | |
{ | |
if v.Minor == 0 || t.Wildcard == MinorWildcard || t.Wildcard == MajorWildcard { | |
return v.ToWildcardRange(MinorWildcard) | |
} else { | |
return v.ToWildcardRange(PatchWildcard) | |
} | |
} | |
case SevmerTokenTypeGE: | |
{ | |
return v.ToRange(compGE) | |
} | |
case SevmerTokenTypeLE: | |
{ | |
return v.ToRange(compLE) | |
} | |
case SevmerTokenTypeGT: | |
{ | |
switch t.Wildcard { | |
case PatchWildcard: | |
{ | |
v.Patch = math.MaxUint64 | |
} | |
case MinorWildcard: | |
{ | |
v.Minor = math.MaxUint64 | |
} | |
} | |
return v.ToRange(compGT) | |
} | |
case SevmerTokenTypeLT: | |
{ | |
switch t.Wildcard { | |
case PatchWildcard: | |
{ | |
v.Patch = 0 | |
v.Minor = 0 | |
} | |
case MinorWildcard: | |
{ | |
v.Minor = 0 | |
v.Patch = 0 | |
} | |
} | |
return v.ToRange(compLT) | |
} | |
} | |
return v.ToWildcardRange(t.Wildcard) | |
} | |
func Tokenize(input string) TokenizeResult { | |
if preparsedRange, hasPreparsedRange := preparsedTable[input]; hasPreparsedRange { | |
return preparsedRange | |
} | |
i := 0 | |
length := len(input) | |
lastNonwhitespace := -1 | |
version := scratchVersionPool.Get().(*Version) | |
version.Reset() | |
result := TokenizeResult{ | |
Version: nil, | |
Range: nil, | |
} | |
var token = semverToken{ | |
Token: SevmerTokenTypeNone, | |
Wildcard: NoneWildcard, | |
} | |
isOR := false | |
count := 0 | |
wipToken := SevmerTokenTypeNone | |
skipRound := false | |
for i < length { | |
skipRound = false | |
switch input[i] { | |
case '>': | |
{ | |
if token.Token == SevmerTokenTypeVersion { | |
isOR = false | |
} | |
if input[i+1] == '=' { | |
wipToken = SevmerTokenTypeGE | |
i++ | |
} else { | |
wipToken = SevmerTokenTypeGT | |
} | |
i++ | |
for i < length && input[i] == ' ' { | |
i++ | |
} | |
} | |
case '<': | |
{ | |
if input[i+1] == '=' { | |
wipToken = SevmerTokenTypeGE | |
i++ | |
} else { | |
wipToken = SevmerTokenTypeLT | |
} | |
i++ | |
for i < length && input[i] == ' ' { | |
i++ | |
} | |
} | |
case '=', 'v': | |
{ | |
wipToken = SevmerTokenTypeVersion | |
isOR = true | |
i++ | |
for i < length && input[i] == ' ' { | |
i++ | |
} | |
} | |
case '~': | |
{ | |
wipToken = SevmerTokenTypeTilda | |
i++ | |
if input[i] == '>' { | |
i++ | |
} | |
for i < length && input[i] == ' ' { | |
i++ | |
} | |
} | |
case '^': | |
{ | |
wipToken = SevmerTokenTypeCaret | |
i++ | |
for i < length && input[i] == ' ' { | |
i++ | |
} | |
} | |
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'X', 'x', '*': | |
{ | |
wipToken = SevmerTokenTypeVersion | |
isOR = true | |
} | |
case '|': | |
{ | |
i++ | |
for i < length && input[i] == '|' { | |
i++ | |
} | |
for i < length && input[i] == ' ' { | |
i++ | |
} | |
isOR = true | |
wipToken = SevmerTokenTypeNone | |
skipRound = true | |
} | |
case '-': | |
{ | |
i++ | |
for i < length && input[i] == ' ' { | |
i++ | |
} | |
} | |
case ' ': | |
{ | |
i++ | |
for i < length && input[i] == ' ' { | |
i++ | |
} | |
skipRound = true | |
} | |
default: | |
{ | |
i++ | |
wipToken = SevmerTokenTypeNone | |
skipRound = true | |
} | |
} | |
if !skipRound { | |
lastNonwhitespace = i | |
if count == 0 && wipToken == SevmerTokenTypeVersion { | |
v := &Version{} | |
version.Reset() | |
token.Token = wipToken | |
token.Wildcard, _, i = parseVersion(input[i:], version) | |
version.CopyTo(v) | |
if token.Wildcard == NoneWildcard { | |
result.AppendVersion(v) | |
} else { | |
result.AppendRange(token.ToRange(v)) | |
} | |
count++ | |
token.Token = SevmerTokenTypeNone | |
token.Wildcard = NoneWildcard | |
version.Reset() | |
} else if count == 0 { | |
v := &Version{} | |
version.Reset() | |
token.Token = wipToken | |
token.Wildcard, _, i = parseVersion(input[i:], version) | |
version.CopyTo(v) | |
result.AppendRange(token.ToRange(v)) | |
token.Token = wipToken | |
version.Reset() | |
count++ | |
} else if isOR { | |
if token.Token != SevmerTokenTypeNone && count > 1 { | |
v := &Version{} | |
version.CopyTo(v) | |
or := token.ToRange(v) | |
version.Reset() | |
token.Token = wipToken | |
token.Wildcard, _, i = parseVersion(input[i:], version) | |
v2 := &Version{} | |
version.CopyTo(v2) | |
result.AppendORRange(or.OR(token.ToRange(v2))) | |
} else { | |
token.Token = wipToken | |
token.Wildcard, _, i = parseVersion(input[i:], version) | |
v2 := &Version{} | |
version.CopyTo(v2) | |
result.AppendORRange(token.ToRange(v2)) | |
} | |
count++ | |
} else { | |
count++ | |
v := &Version{} | |
version.CopyTo(v) | |
or := token.ToRange(v) | |
version.Reset() | |
token.Token = wipToken | |
token.Wildcard, _, i = parseVersion(input[i:], version) | |
v2 := &Version{} | |
version.CopyTo(v2) | |
result.AppendRange(or.AND(token.ToRange(v2))) | |
} | |
i += lastNonwhitespace + 1 | |
isOR = false | |
} | |
} | |
scratchVersionPool.Put(version) | |
return result | |
} | |
func parseVersion(vStr string, parts *Version) (_wildcard WildcardType, isValid bool, stoppedAt int) { | |
partI := 0 | |
partStartI := -1 | |
lastCharI := 0 | |
isValid = true | |
isDone := false | |
count := len(vStr) | |
if count == 0 { | |
return _wildcard, false, 0 | |
} | |
for i, char := range vStr { | |
if isDone { | |
break | |
} | |
stoppedAt = i | |
switch char { | |
case ' ': | |
{ | |
if partI >= 2 { | |
isDone = true | |
break | |
} | |
} | |
case '|', '^', '#', '&', '%', '!': | |
{ | |
isDone = true | |
stoppedAt-- | |
break | |
} | |
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': | |
{ | |
if partStartI == -1 { | |
partStartI = i | |
} | |
lastCharI = i | |
} | |
case '.': | |
{ | |
if partStartI > -1 && partI <= 2 { | |
switch partI { | |
case 0: | |
{ | |
parts.Major = normalizeVersionPart(vStr[partStartI:i]) | |
} | |
case 1: | |
{ | |
parts.Minor = normalizeVersionPart(vStr[partStartI:i]) | |
} | |
} | |
partStartI = -1 | |
partI++ | |
// "fo.o.b.ar" | |
} else if partI > 2 || partStartI == -1 { | |
isValid = false | |
isDone = true | |
break | |
} | |
} | |
case '-', '+': | |
{ | |
if partI == 2 && partStartI > -1 { | |
parts.Patch = normalizeVersionPart(vStr[partStartI:i]) | |
_wildcard = NoneWildcard | |
partStartI = i | |
partI = 3 | |
isDone = true | |
break | |
} else { | |
isValid = false | |
isDone = true | |
break | |
} | |
} | |
case 'x', '*', 'X': | |
{ | |
if partStartI == -1 { | |
partStartI = i | |
} | |
lastCharI = i | |
// We want min wildcard | |
if _wildcard == NoneWildcard { | |
switch partI { | |
case 0: | |
{ | |
_wildcard = MajorWildcard | |
} | |
case 1: | |
{ | |
_wildcard = MinorWildcard | |
} | |
case 2: | |
{ | |
_wildcard = PatchWildcard | |
} | |
} | |
} | |
} | |
default: | |
{ | |
lastCharI = 0 | |
isValid = false | |
isDone = true | |
break | |
} | |
} | |
} | |
if isValid { | |
isValid = partI > -1 | |
} | |
if partStartI == -1 { | |
partStartI = 0 | |
} | |
if lastCharI == -1 || partStartI > lastCharI { | |
lastCharI = len(vStr) - 1 | |
} | |
// Where did we leave off? | |
switch partI { | |
// That means they used a match like this: | |
// "1" | |
// So its a wildcard minor | |
case 0: | |
{ | |
if _wildcard == NoneWildcard { | |
_wildcard = MinorWildcard | |
} | |
parts.Major = normalizeVersionPart(vStr[partStartI : lastCharI+1]) | |
} | |
case 3: | |
{ | |
stoppedAt = parseBuild(parts, vStr[partStartI:]) | |
stoppedAt += partStartI | |
} | |
case 1: | |
{ | |
if _wildcard == NoneWildcard { | |
_wildcard = PatchWildcard | |
} | |
parts.Minor = normalizeVersionPart(vStr[partStartI : lastCharI+1]) | |
} | |
case 2: | |
{ | |
parts.Patch = normalizeVersionPart(vStr[partStartI : lastCharI+1]) | |
} | |
} | |
return _wildcard, isValid, stoppedAt | |
} | |
func parseBuild(parts *Version, input string) int { | |
if len(input) == 0 { | |
return 0 | |
} | |
buildCount := 0 | |
preCount := 0 | |
stoppedAt := 0 | |
for i, char := range input { | |
if char == ' ' { | |
stoppedAt = i | |
break | |
} else if char == '-' { | |
preCount++ | |
} else if char == '+' { | |
buildCount++ | |
} | |
} | |
if buildCount == 0 && preCount == 0 { | |
return stoppedAt | |
} | |
buildSegments := make([]string, 0, buildCount) | |
preSegments := make([]string, 0, preCount) | |
start := 0 | |
trailingIsBuild := false | |
stoppedAt = 0 | |
for i, char := range input { | |
stoppedAt = i | |
if char == ' ' { | |
break | |
} | |
if char == '-' || char == '+' { | |
if i > start && trailingIsBuild { | |
buildSegments = append(buildSegments, input[start:i+1]) | |
} else if i > start && !trailingIsBuild { | |
preSegments = append(preSegments, input[start:i+1]) | |
} | |
start = i + 1 | |
trailingIsBuild = char == '+' | |
} | |
} | |
if start > 0 && trailingIsBuild { | |
buildSegments = append(buildSegments, input[start:]) | |
} else if start > 0 && !trailingIsBuild { | |
preSegments = append(preSegments, input[start:]) | |
} | |
parts.Build = buildSegments | |
parts.Pre = preSegments | |
return stoppedAt | |
} | |
func normalizeVersionPart(part string) uint64 { | |
var b strings.Builder | |
// First, we count. | |
var count int | |
needsNormalization := false | |
for _, char := range part { | |
switch char { | |
case 'x', '*': | |
{ | |
needsNormalization = true | |
count++ | |
} | |
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': | |
{ | |
count++ | |
} | |
default: | |
{ | |
needsNormalization = true | |
} | |
} | |
} | |
if count == 0 { | |
return 0 | |
} else if !needsNormalization { | |
comp, _ := strconv.ParseUint(part, 10, 64) | |
return comp | |
} | |
b.Grow(count) | |
for _, char := range part { | |
switch char { | |
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': | |
{ | |
b.WriteRune(char) | |
} | |
case '*', 'x': | |
{ | |
b.WriteRune('0') | |
} | |
} | |
} | |
comp, _ := strconv.ParseUint(b.String(), 10, 64) | |
return comp | |
} | |
func NewVersion(input string) (v *Version, e error) { | |
return v, e | |
} | |
// func IsRange(input string) bool { | |
// } | |
// Versions represents multiple versions. | |
type Versions []Version | |
// Len returns length of version collection | |
func (s Versions) Len() int { | |
return len(s) | |
} | |
// Swap swaps two versions inside the collection by its indices | |
func (s Versions) Swap(i, j int) { | |
s[i], s[j] = s[j], s[i] | |
} | |
// Less checks if version at index i is less than version at index j | |
func (s Versions) Less(i, j int) bool { | |
return s[i].LT(s[j]) | |
} | |
// Sort sorts a slice of versions | |
func Sort(versions []Version) { | |
sort.Sort(Versions(versions)) | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Code generated by go-enum | |
// DO NOT EDIT! | |
package node_semver | |
import ( | |
"fmt" | |
) | |
const ( | |
// TokenizeResultValueNone is a TokenizeResultValue of type None. | |
TokenizeResultValueNone TokenizeResultValue = iota | |
// TokenizeResultValueVersion is a TokenizeResultValue of type Version. | |
TokenizeResultValueVersion | |
// TokenizeResultValueRange is a TokenizeResultValue of type Range. | |
TokenizeResultValueRange | |
) | |
const _TokenizeResultValueName = "NoneVersionRange" | |
var _TokenizeResultValueMap = map[TokenizeResultValue]string{ | |
0: _TokenizeResultValueName[0:4], | |
1: _TokenizeResultValueName[4:11], | |
2: _TokenizeResultValueName[11:16], | |
} | |
// String implements the Stringer interface. | |
func (x TokenizeResultValue) String() string { | |
if str, ok := _TokenizeResultValueMap[x]; ok { | |
return str | |
} | |
return fmt.Sprintf("TokenizeResultValue(%d)", x) | |
} | |
var _TokenizeResultValueValue = map[string]TokenizeResultValue{ | |
_TokenizeResultValueName[0:4]: 0, | |
_TokenizeResultValueName[4:11]: 1, | |
_TokenizeResultValueName[11:16]: 2, | |
} | |
// ParseTokenizeResultValue attempts to convert a string to a TokenizeResultValue | |
func ParseTokenizeResultValue(name string) (TokenizeResultValue, error) { | |
if x, ok := _TokenizeResultValueValue[name]; ok { | |
return x, nil | |
} | |
return TokenizeResultValue(0), fmt.Errorf("%s is not a valid TokenizeResultValue", name) | |
} | |
// MarshalText implements the text marshaller method | |
func (x TokenizeResultValue) MarshalText() ([]byte, error) { | |
return []byte(x.String()), nil | |
} | |
// UnmarshalText implements the text unmarshaller method | |
func (x *TokenizeResultValue) UnmarshalText(text []byte) error { | |
name := string(text) | |
tmp, err := ParseTokenizeResultValue(name) | |
if err != nil { | |
return err | |
} | |
*x = tmp | |
return nil | |
} | |
const ( | |
// VersionLevelMajor is a VersionLevel of type Major. | |
VersionLevelMajor VersionLevel = iota | |
// VersionLevelMinor is a VersionLevel of type Minor. | |
VersionLevelMinor | |
// VersionLevelPatch is a VersionLevel of type Patch. | |
VersionLevelPatch | |
// VersionLevelPrerelease is a VersionLevel of type Prerelease. | |
VersionLevelPrerelease | |
// VersionLevelPrepatch is a VersionLevel of type Prepatch. | |
VersionLevelPrepatch | |
// VersionLevelPreminor is a VersionLevel of type Preminor. | |
VersionLevelPreminor | |
// VersionLevelPremajor is a VersionLevel of type Premajor. | |
VersionLevelPremajor | |
) | |
const _VersionLevelName = "MajorMinorPatchPrereleasePrepatchPreminorPremajor" | |
var _VersionLevelMap = map[VersionLevel]string{ | |
0: _VersionLevelName[0:5], | |
1: _VersionLevelName[5:10], | |
2: _VersionLevelName[10:15], | |
3: _VersionLevelName[15:25], | |
4: _VersionLevelName[25:33], | |
5: _VersionLevelName[33:41], | |
6: _VersionLevelName[41:49], | |
} | |
// String implements the Stringer interface. | |
func (x VersionLevel) String() string { | |
if str, ok := _VersionLevelMap[x]; ok { | |
return str | |
} | |
return fmt.Sprintf("VersionLevel(%d)", x) | |
} | |
var _VersionLevelValue = map[string]VersionLevel{ | |
_VersionLevelName[0:5]: 0, | |
_VersionLevelName[5:10]: 1, | |
_VersionLevelName[10:15]: 2, | |
_VersionLevelName[15:25]: 3, | |
_VersionLevelName[25:33]: 4, | |
_VersionLevelName[33:41]: 5, | |
_VersionLevelName[41:49]: 6, | |
} | |
// ParseVersionLevel attempts to convert a string to a VersionLevel | |
func ParseVersionLevel(name string) (VersionLevel, error) { | |
if x, ok := _VersionLevelValue[name]; ok { | |
return x, nil | |
} | |
return VersionLevel(0), fmt.Errorf("%s is not a valid VersionLevel", name) | |
} | |
// MarshalText implements the text marshaller method | |
func (x VersionLevel) MarshalText() ([]byte, error) { | |
return []byte(x.String()), nil | |
} | |
// UnmarshalText implements the text unmarshaller method | |
func (x *VersionLevel) UnmarshalText(text []byte) error { | |
name := string(text) | |
tmp, err := ParseVersionLevel(name) | |
if err != nil { | |
return err | |
} | |
*x = tmp | |
return nil | |
} | |
const ( | |
// SevmerTokenTypeNone is a sevmerTokenType of type None. | |
SevmerTokenTypeNone sevmerTokenType = iota | |
// SevmerTokenTypeOR is a sevmerTokenType of type OR. | |
SevmerTokenTypeOR | |
// SevmerTokenTypeGT is a sevmerTokenType of type GT. | |
SevmerTokenTypeGT | |
// SevmerTokenTypeGE is a sevmerTokenType of type GE. | |
SevmerTokenTypeGE | |
// SevmerTokenTypeLT is a sevmerTokenType of type LT. | |
SevmerTokenTypeLT | |
// SevmerTokenTypeLE is a sevmerTokenType of type LE. | |
SevmerTokenTypeLE | |
// SevmerTokenTypeVersion is a sevmerTokenType of type Version. | |
SevmerTokenTypeVersion | |
// SevmerTokenTypeTilda is a sevmerTokenType of type Tilda. | |
SevmerTokenTypeTilda | |
// SevmerTokenTypeCaret is a sevmerTokenType of type Caret. | |
SevmerTokenTypeCaret | |
) | |
const _sevmerTokenTypeName = "NoneORGTGELTLEVersionTildaCaret" | |
var _sevmerTokenTypeMap = map[sevmerTokenType]string{ | |
0: _sevmerTokenTypeName[0:4], | |
1: _sevmerTokenTypeName[4:6], | |
2: _sevmerTokenTypeName[6:8], | |
3: _sevmerTokenTypeName[8:10], | |
4: _sevmerTokenTypeName[10:12], | |
5: _sevmerTokenTypeName[12:14], | |
6: _sevmerTokenTypeName[14:21], | |
7: _sevmerTokenTypeName[21:26], | |
8: _sevmerTokenTypeName[26:31], | |
} | |
// String implements the Stringer interface. | |
func (x sevmerTokenType) String() string { | |
if str, ok := _sevmerTokenTypeMap[x]; ok { | |
return str | |
} | |
return fmt.Sprintf("sevmerTokenType(%d)", x) | |
} | |
var _sevmerTokenTypeValue = map[string]sevmerTokenType{ | |
_sevmerTokenTypeName[0:4]: 0, | |
_sevmerTokenTypeName[4:6]: 1, | |
_sevmerTokenTypeName[6:8]: 2, | |
_sevmerTokenTypeName[8:10]: 3, | |
_sevmerTokenTypeName[10:12]: 4, | |
_sevmerTokenTypeName[12:14]: 5, | |
_sevmerTokenTypeName[14:21]: 6, | |
_sevmerTokenTypeName[21:26]: 7, | |
_sevmerTokenTypeName[26:31]: 8, | |
} | |
// ParsesevmerTokenType attempts to convert a string to a sevmerTokenType | |
func ParsesevmerTokenType(name string) (sevmerTokenType, error) { | |
if x, ok := _sevmerTokenTypeValue[name]; ok { | |
return x, nil | |
} | |
return sevmerTokenType(0), fmt.Errorf("%s is not a valid sevmerTokenType", name) | |
} | |
// MarshalText implements the text marshaller method | |
func (x sevmerTokenType) MarshalText() ([]byte, error) { | |
return []byte(x.String()), nil | |
} | |
// UnmarshalText implements the text unmarshaller method | |
func (x *sevmerTokenType) UnmarshalText(text []byte) error { | |
name := string(text) | |
tmp, err := ParsesevmerTokenType(name) | |
if err != nil { | |
return err | |
} | |
*x = tmp | |
return nil | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package node_semver_test | |
import ( | |
"fmt" | |
"math/rand" | |
"sort" | |
"testing" | |
goblin "github.com/franela/goblin" | |
"github.com/jarred-sumner/devserverless/resolver/node_semver" | |
"github.com/jarred-sumner/devserverless/resolver/node_semver/fixtures" | |
. "github.com/onsi/gomega" | |
"github.com/stretchr/testify/assert" | |
) | |
func assertNotRange(t *testing.T, r *node_semver.TokenizeResult, input string) { | |
assert.False(t, r.Value == node_semver.TokenizeResultValueRange, fmt.Sprintf("Expected %s to not be a Range", input)) | |
} | |
func TestMajorOnlyVersion(t *testing.T) { | |
result := node_semver.Tokenize("1.0.0") | |
assert.NotNil(t, result.Version) | |
assert.Equal(t, result.Version.Major, uint64(1)) | |
assert.Equal(t, result.Version.Minor, uint64(0)) | |
assert.Equal(t, result.Version.Patch, uint64(0)) | |
assertNotRange(t, &result, "1.0.0") | |
} | |
func TestGTVersion(t *testing.T) { | |
result := node_semver.Tokenize(">1.0.0") | |
v := node_semver.Version{ | |
Major: 1, | |
Minor: 0, | |
Patch: 1, | |
} | |
assert.True(t, result.Range(v)) | |
v.Major = 0 | |
assert.False(t, result.Range(v)) | |
v.Major = 1 | |
v.Minor = 1 | |
assert.True(t, result.Range(v)) | |
v.Major = 1 | |
v.Minor = 0 | |
v.Patch = 2 | |
assert.True(t, result.Range(v)) | |
} | |
func TestGTLTVersion(t *testing.T) { | |
result := node_semver.Tokenize(">=1.0.0 <1.0.9") | |
v := node_semver.Version{ | |
Major: 1, | |
Minor: 0, | |
Patch: 1, | |
} | |
assert.True(t, result.Range(v)) | |
v.Major = 0 | |
assert.False(t, result.Range(v)) | |
v.Major = 1 | |
v.Minor = 1 | |
assert.False(t, result.Range(v)) | |
v.Major = 1 | |
v.Minor = 0 | |
v.Patch = 2 | |
assert.True(t, result.Range(v)) | |
} | |
func TestMajorMinorVersion(t *testing.T) { | |
result := node_semver.Tokenize("1.2.0") | |
assert.NotNil(t, result.Version) | |
assert.Equal(t, result.Version.Major, uint64(1)) | |
assert.Equal(t, result.Version.Minor, uint64(2)) | |
assert.Equal(t, result.Version.Patch, uint64(0)) | |
assertNotRange(t, &result, "1.2.0") | |
} | |
func TestMajorMinorPatchVersion(t *testing.T) { | |
result := node_semver.Tokenize("1.2.3") | |
assert.NotNil(t, result.Version) | |
assert.Equal(t, result.Version.Major, uint64(1)) | |
assert.Equal(t, result.Version.Minor, uint64(2)) | |
assert.Equal(t, result.Version.Patch, uint64(3)) | |
assertNotRange(t, &result, "1.2.3") | |
} | |
func TestMultipleVersions(t *testing.T) { | |
result := node_semver.Tokenize("1.2.3 1.2.9") | |
v0 := node_semver.Version{ | |
Major: 1, | |
Minor: 2, | |
Patch: 3, | |
} | |
v1 := node_semver.Version{ | |
Major: 1, | |
Minor: 2, | |
Patch: 9, | |
} | |
assert.True(t, result.Range(v0)) | |
assert.True(t, result.Range(v1)) | |
v1.Major = 2 | |
assert.False(t, result.Range(v1)) | |
} | |
func TestMultipleVersionsWithOR(t *testing.T) { | |
result := node_semver.Tokenize("1.2.3 || 1.2.9") | |
v0 := node_semver.Version{ | |
Major: 1, | |
Minor: 2, | |
Patch: 3, | |
} | |
v1 := node_semver.Version{ | |
Major: 1, | |
Minor: 2, | |
Patch: 9, | |
} | |
assert.True(t, result.Range(v0)) | |
assert.True(t, result.Range(v1)) | |
} | |
func TestSimplePre(t *testing.T) { | |
result := node_semver.Tokenize("1.2.3-pre") | |
v0 := node_semver.Version{ | |
Major: 1, | |
Minor: 2, | |
Patch: 3, | |
Pre: []string{"pre"}, | |
} | |
assert.True(t, result.IsVersion()) | |
assert.True(t, result.Version.EQ(v0)) | |
} | |
func TestSimpleBuild(t *testing.T) { | |
result := node_semver.Tokenize("1.2.3+build") | |
v0 := node_semver.Version{ | |
Major: 1, | |
Minor: 2, | |
Patch: 3, | |
Build: []string{"build"}, | |
} | |
assert.True(t, result.IsVersion()) | |
assert.True(t, result.Version.EQ(v0)) | |
} | |
func TestSimplePreBuild(t *testing.T) { | |
result := node_semver.Tokenize("1.2.3+build-pre") | |
v0 := node_semver.Version{ | |
Major: 1, | |
Minor: 2, | |
Patch: 3, | |
Build: []string{"build"}, | |
Pre: []string{"pre"}, | |
} | |
assert.True(t, result.IsVersion()) | |
assert.True(t, result.Version.EQ(v0)) | |
} | |
func TestMultiPreBuild(t *testing.T) { | |
result := node_semver.Tokenize("1.2.3+build-pre 1.2.8+build-pre") | |
v0 := node_semver.Version{ | |
Major: 1, | |
Minor: 2, | |
Patch: 3, | |
Build: []string{"build"}, | |
Pre: []string{"pre"}, | |
} | |
assert.True(t, result.IsRange()) | |
assert.True(t, result.Range(v0)) | |
} | |
func TestWildcardPatch(t *testing.T) { | |
result := node_semver.Tokenize("1.2.*") | |
v0 := node_semver.Version{ | |
Major: 1, | |
Minor: 2, | |
Patch: 8, | |
} | |
assert.True(t, result.IsRange()) | |
assert.True(t, result.Range(v0)) | |
v0.Minor++ | |
assert.False(t, result.Range(v0)) | |
} | |
func TestWildcardMinor(t *testing.T) { | |
result := node_semver.Tokenize("1.*") | |
v0 := node_semver.Version{ | |
Major: 1, | |
Minor: 2, | |
Patch: 8, | |
} | |
assert.True(t, result.IsRange()) | |
assert.True(t, result.Range(v0)) | |
v0.Major++ | |
assert.False(t, result.Range(v0)) | |
v0.Major-- | |
} | |
func TestCaretWildcardMinor(t *testing.T) { | |
result := node_semver.Tokenize("^1.*") | |
v0 := node_semver.Version{ | |
Major: 1, | |
Minor: 2, | |
Patch: 8, | |
} | |
assert.True(t, result.IsRange()) | |
assert.True(t, result.Range(v0)) | |
v0.Major++ | |
assert.False(t, result.Range(v0)) | |
v0.Major-- | |
} | |
func TestWildcardMajor(t *testing.T) { | |
result := node_semver.Tokenize("*") | |
v0 := node_semver.Version{ | |
Major: 1, | |
Minor: 2, | |
Patch: 8, | |
} | |
assert.True(t, result.IsRange()) | |
assert.True(t, result.Range(v0)) | |
v0.Major = 0 | |
assert.True(t, result.Range(v0)) | |
v0.Minor = 0 | |
assert.True(t, result.Range(v0)) | |
v0.Patch = 0 | |
assert.True(t, result.Range(v0)) | |
} | |
func TestMultipleVersionsWithORLT(t *testing.T) { | |
result := node_semver.Tokenize("<1.2.3 || 1.2.9") | |
v0 := node_semver.Version{ | |
Major: 1, | |
Minor: 2, | |
Patch: 0, | |
} | |
v1 := node_semver.Version{ | |
Major: 1, | |
Minor: 2, | |
Patch: 9, | |
} | |
assert.True(t, result.Range(v0)) | |
assert.True(t, result.Range(v1)) | |
} | |
func TestMultipleVersionsWithORLTFlip(t *testing.T) { | |
result := node_semver.Tokenize("1.2.9 || <1.2.3") | |
v0 := node_semver.Version{ | |
Major: 1, | |
Minor: 2, | |
Patch: 0, | |
} | |
v1 := node_semver.Version{ | |
Major: 1, | |
Minor: 2, | |
Patch: 9, | |
} | |
assert.True(t, result.Range(v0)) | |
assert.True(t, result.Range(v1)) | |
v1.Patch = 8 | |
assert.False(t, result.Range(v1)) | |
} | |
func TestMultipleVersionsAnd(t *testing.T) { | |
assertRangeMatch(t, ">1.2.1 <1.2.3", "1.2.2") | |
assertRangeNotMatch(t, ">1.2.1 <1.2.3", "1.2.1") | |
assertRangeNotMatch(t, ">1.2.1 <1.2.3", "1.2.3") | |
assertRangeNotMatch(t, ">1.2.1 <1.2.3", "1.3.2") | |
assertRangeNotMatch(t, ">1.2.1 <1.2.3", "1.1.2") | |
assertRangeNotMatch(t, ">1.2.1 <1.2.3", "2.1.2") | |
} | |
func TestCaretVersion(t *testing.T) { | |
assertRangeMatch(t, "^1.2.1", "1.2.1") | |
assertRangeMatch(t, "^1.2.1", "1.3.0") | |
assertRangeMatch(t, "^1.2.1", "1.3.1") | |
assertRangeNotMatch(t, "^1.2.1", "1.2.0") | |
assertRangeNotMatch(t, "^ 1.2.1 ", "1.1.1") | |
assertRangeNotMatch(t, "^1.2.1", "2.0.0") | |
} | |
func TestCaretWithWhitespace(t *testing.T) { | |
assertRangeMatch(t, "^ 1 .X || 2.4", "1.2.1") | |
assertRangeMatch(t, "^ 1 .X || 2.4", "1.3.0") | |
assertRangeMatch(t, "^ 1 .X || 2.4", "1.3.1") | |
assertRangeMatch(t, "^ 1 .X || 2.4", "2.4.2") | |
assertRangeNotMatch(t, "^ 1.x || 2.4", "2.2.0") | |
assertRangeNotMatch(t, "^ 1.x || 2.4", "2.3.0") | |
assertRangeNotMatch(t, "^ 1.x || 2.4", "2.5.0") | |
assertRangeNotMatch(t, "^ 1.x || 2.4", "2.0.0") | |
} | |
func TestWhitespaceCheckDoesntPRoduceInvalidResults(t *testing.T) { | |
assertRangeMatch(t, "^ 1 || 2.4", "1.2.1") | |
assertRangeMatch(t, "^ 1 || 2.4", "1.3.0") | |
assertRangeMatch(t, "^ 1 || 2.4", "1.3.1") | |
assertRangeMatch(t, "^ 1 || 2.4", "2.4.2") | |
assertRangeNotMatch(t, "^ 1 || 2.4", "2.2.0") | |
assertRangeNotMatch(t, "^ 1 || 2.4", "2.3.0") | |
assertRangeNotMatch(t, "^ 1 || 2.4", "2.5.0") | |
assertRangeNotMatch(t, "^ 1 || 2.4", "2.0.0") | |
} | |
func TestTildaMajor(t *testing.T) { | |
assertRangeMatch(t, "^ 1 ", "1.0.0") | |
assertRangeMatch(t, "^ 1 ", "1.2.0") | |
assertRangeMatch(t, "^ 1 ", "1.1.0") | |
assertRangeNotMatch(t, "^ 1 ", "2.0.0") | |
assertRangeNotMatch(t, "^ 1 ", "0.0.0") | |
assertRangeNotMatch(t, "^ 1 ", "0.5.0") | |
} | |
func TestDoubleWildcardMinor(t *testing.T) { | |
result := node_semver.Tokenize("2 || 3") | |
v := node_semver.Version{ | |
Major: 1, | |
Minor: 2, | |
Patch: 3, | |
} | |
assert.False(t, result.Range(v)) | |
v.Major++ | |
assert.True(t, result.Range(v)) | |
v.Major++ | |
assert.True(t, result.Range(v)) | |
v.Major++ | |
assert.False(t, result.Range(v)) | |
} | |
func TestMultiWildcardMinor(t *testing.T) { | |
result := node_semver.Tokenize("2 || 3 || 4 || 5") | |
v := node_semver.Version{ | |
Major: 1, | |
Minor: 2, | |
Patch: 3, | |
} | |
assert.False(t, result.Range(v)) | |
v.Major++ | |
assert.True(t, result.Range(v)) | |
v.Major++ | |
assert.True(t, result.Range(v)) | |
v.Major++ | |
assert.True(t, result.Range(v)) | |
v.Major++ | |
assert.True(t, result.Range(v)) | |
v.Major++ | |
assert.False(t, result.Range(v)) | |
} | |
func assertRangeMatch(t *testing.T, input string, other string) { | |
defer func() { | |
recover() | |
}() | |
result := node_semver.Tokenize(input) | |
v := node_semver.Tokenize(other).Version | |
assert.Truef(t, result.Range(*v), "Expected %s to match %s", input, other) | |
} | |
func assertRangeNotMatch(t *testing.T, input string, other string) { | |
defer func() { | |
recover() | |
}() | |
result := node_semver.Tokenize(input) | |
assert.Equal(t, result.Value, node_semver.TokenizeResultValueRange) | |
v := node_semver.Tokenize(other).Version | |
assert.NotNil(t, v) | |
assert.Falsef(t, result.Range(*v), "Expected %s NOT to match %s", input, other) | |
} | |
func TestCaretBehavior(t *testing.T) { | |
// assertRangeNotMatch(t, "^1.20.0||^2.0.0", "1.19.0") | |
assertRangeMatch(t, "^1.20.0||^2.0.0", "2.42.2") | |
assertRangeMatch(t, "^1.20.0||^2.0.0", "1.20.0") | |
assertRangeMatch(t, "^1.20.0||^2.0.0", "1.25.2") | |
assertRangeMatch(t, "^1.20.0||^2.0.0", "2.0.0") | |
assertRangeMatch(t, "^1.20.0||^2.0.0", "2.10.4") | |
} | |
func TestMultiCaret(t *testing.T) { | |
assertRangeMatch(t, "^1.2.3 || ^2.4", "1.2.3") | |
assertRangeMatch(t, "^1.2.3 || ^2.4", "1.3.0") | |
assertRangeMatch(t, "^1.2.3 || ^2.4", "2.4.0") | |
assertRangeMatch(t, "^1.2.3 || ^2.4", "2.4.1") | |
assertRangeMatch(t, "^1.2.3 || ^2.4", "2.4.2") | |
assertRangeMatch(t, "^1.2.3 || ^2.4", "2.5.0") | |
assertRangeMatch(t, "^1.2.3 || ^2.4", "2.5.1") | |
assertRangeMatch(t, "^1.2.3 || ^2.4", "2.6.0") | |
assertRangeMatch(t, "^1.2.3 || ^2.4", "2.6.1") | |
assertRangeNotMatch(t, "^1.2.3 || ^2.4", "1.2.0") | |
assertRangeNotMatch(t, "^1.2.3 || ^2.4", "1.2.1") | |
assertRangeNotMatch(t, "^1.2.3 || ^2.4", "1.2.2") | |
assertRangeNotMatch(t, "^1.2.3 || ^2.4", "2.3.0") | |
assertRangeNotMatch(t, "^1.2.3 || ^2.4", "1.0.0") | |
} | |
func BenchmarkSimple(b *testing.B) { | |
b.ReportAllocs() | |
b.ResetTimer() | |
for n := 0; n < b.N; n++ { | |
node_semver.Tokenize("1.2.3") | |
} | |
} | |
func BenchmarkSimpleCheck(b *testing.B) { | |
b.ReportAllocs() | |
b.ResetTimer() | |
for n := 0; n < b.N; n++ { | |
tok := node_semver.Tokenize("1.2.3") | |
tok.TestString("1.2.3") | |
} | |
} | |
func BenchmarkAdvancedCheck(b *testing.B) { | |
b.ReportAllocs() | |
b.ResetTimer() | |
for n := 0; n < b.N; n++ { | |
tok := node_semver.Tokenize("^1.2.3") | |
tok.TestString("1.2.8") | |
} | |
} | |
func BenchmarkCaret(b *testing.B) { | |
b.ReportAllocs() | |
b.ResetTimer() | |
for n := 0; n < b.N; n++ { | |
node_semver.Tokenize("^1.2.3") | |
} | |
} | |
func BenchmarkCaretComplex(b *testing.B) { | |
b.ReportAllocs() | |
b.ResetTimer() | |
for n := 0; n < b.N; n++ { | |
node_semver.Tokenize("^1.2.3 || ^2.3") | |
} | |
} | |
func TestEqualVersions(t *testing.T) { | |
for _, v := range fixtures.EqualVersions() { | |
result := node_semver.Tokenize(v[0]) | |
plain := node_semver.Tokenize(v[1]).Version | |
assert.NotNil(t, &result.Version, v[0]) | |
assert.True(t, result.Version.EQ(*plain)) | |
} | |
} | |
func TestLTRange(t *testing.T) { | |
assertRangeMatch(t, "<2.0.0", "0.2.9") | |
} | |
func shuffleInPlace(isArray []string) { | |
l := len(isArray) - 1 | |
for i := 0; i <= l; i++ { | |
n := rand.Intn(l) | |
// swap | |
x := isArray[i] | |
isArray[i] = isArray[n] | |
isArray[n] = x | |
} | |
} | |
func TestRanges(t *testing.T) { | |
g := goblin.Goblin(t) | |
RegisterFailHandler(func(m string, _ ...int) { g.Fail(m) }) | |
g.Describe("TestStringify", func() { | |
for _, v := range fixtures.Strings() { | |
g.It(fmt.Sprintf("%s should stringify to %s", v, v), func() { | |
result := node_semver.Tokenize(v) | |
Expect(result.Version.String()).To(Equal(v)) | |
}) | |
} | |
}) | |
g.Describe("Manual Ranges", func() { | |
for _, v := range fixtures.ManualRanges() { | |
g.It(fmt.Sprintf("%s should match %s", v[0], v[1]), func() { | |
result := node_semver.Tokenize(v[0]) | |
Expect(result.TestString(v[1])).To(Equal(true)) | |
}) | |
} | |
}) | |
g.Describe("Compare", func() { | |
g.It("Major > Minor", func() { | |
major := node_semver.Version{Major: 1, Minor: 0, Patch: 0} | |
minor := node_semver.Version{Major: 0, Minor: 1} | |
Expect(major.Compare(minor)).To(Equal(1)) | |
}) | |
g.It("Minor > Patch", func() { | |
major := node_semver.Version{Major: 0, Minor: 1, Patch: 0} | |
minor := node_semver.Version{Patch: 1} | |
Expect(major.Compare(minor)).To(Equal(1)) | |
}) | |
g.It("Pre > Not Pre", func() { | |
major := node_semver.Version{Major: 0, Minor: 1, Patch: 0} | |
minor := node_semver.Version{Patch: 1} | |
Expect(major.Compare(minor)).To(Equal(1)) | |
}) | |
g.It("Sorting Test", func() { | |
correct := fixtures.StringsToSort() | |
shuffled := fixtures.StringsToSort() | |
shuffleInPlace(shuffled) | |
sortable := make(node_semver.Versions, len(shuffled)) | |
for i, v := range shuffled { | |
sortable[i] = *node_semver.Tokenize(v).Version | |
} | |
sort.Sort(sortable) | |
stringified := make([]string, len(sortable)) | |
for i, v := range sortable { | |
stringified[i] = v.String() | |
} | |
Expect(stringified).To(Equal(correct)) | |
}) | |
}) | |
g.Describe("TestRangeInclusive", func() { | |
for _, v := range fixtures.RangesInclusive() { | |
g.It(fmt.Sprintf("%s should match %s", v[0], v[1]), func() { | |
result := node_semver.Tokenize(v[0]) | |
Expect(result.TestString(v[1])).To(BeTrue()) | |
}) | |
} | |
}) | |
g.Describe("TestRangeExclusive", func() { | |
for _, v := range fixtures.RangeExclude() { | |
g.It(fmt.Sprintf("%s should not match %s", v[0], v[1]), func() { | |
result := node_semver.Tokenize(v[0]) | |
Expect(result.TestString(v[1])).To(Equal(false)) | |
}) | |
} | |
}) | |
// [range, version, options] | |
// g.Describe("TestVersionGreaterThanRange", func() { | |
// for _, v := range fixtures.VersionGTRange() { | |
// version := v[1] | |
// g.It(fmt.Sprintf("%s should be greater than %s", version, v[0]), func() { | |
// result := node_semver.Tokenize(version) | |
// Expect(result.TestString(v[0])).To(Equal(false)) | |
// }) | |
// } | |
// }) | |
// g.Describe("TestVersionNOTGreaterThanRange", func() { | |
// for _, v := range fixtures.VersionsNotGTRange() { | |
// version := v[1] | |
// g.It(fmt.Sprintf("%s should NOT be greater than %s", version, v[0]), func() { | |
// result := node_semver.Tokenize(version) | |
// Expect(result.TestString(v[0])).To(Equal(false)) | |
// }) | |
// } | |
// }) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment