pax_global_header00006660000000000000000000000064137401752670014526gustar00rootroot0000000000000052 comment=0273b66e30e086376d882af4afdccee5b620be45 go-versions-1.0.1/000077500000000000000000000000001374017526700140005ustar00rootroot00000000000000go-versions-1.0.1/.travis.sh000077500000000000000000000004011374017526700157200ustar00rootroot00000000000000#!/bin/bash set -e echo "" > coverage.txt for d in $(go list ./... | grep -v vendor); do go test -coverprofile=profile.out -covermode=atomic $d if [ -f profile.out ]; then cat profile.out >> coverage.txt rm profile.out fi done go-versions-1.0.1/.travis.yml000066400000000000000000000002561374017526700161140ustar00rootroot00000000000000language: go go: - 1.13.x - 1.14.x - tip before_install: - go get -t -v ./... script: - ./.travis.sh after_success: - bash <(curl -s https://codecov.io/bash) go-versions-1.0.1/LICENSE000066400000000000000000000020561374017526700150100ustar00rootroot00000000000000MIT License Copyright (c) 2018 Martin Atkins Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. go-versions-1.0.1/README.md000066400000000000000000000371141374017526700152650ustar00rootroot00000000000000# go-versions [![](https://godoc.org/github.com/apparentlymart/go-versions/versions?status.svg)](https://godoc.org/github.com/apparentlymart/go-versions/versions) `versions` is a library for wrangling versions, lists of versions, and sets of versions. Its idea of "version" is that defined by [semantic versioning](https://semver.org/). There are _many_ Go libraries out there for dealing with versions in general and semantic versioning in particular, but many of them don't meet all of the following requirements that this library seeks to meet: * Version string and constraint string parsing _with good, user-oriented error messages in case of syntax problems_. * Built-in mechanisms for filtering and sorting lists of candidate versions based on constraints. * Ergonomic API for the calling application. Whether _this_ library meets those requirements is of course subjective, but these are certainly its goals. To whet your appetite, here's an example program that solves the common problem of taking a list of available versions and a version constraint and then returning the newest available version that meets the constraint. ```go package main import "fmt" import "os" import "github.com/apparentlymart/go-versions/versions" func main() { // In a real program, the version list would probably come // from some registry API, but we'll hard-code it for // example here. available := versions.List{ versions.MustParseVersion("0.8.0"), versions.MustParseVersion("1.0.1"), versions.MustParseVersion("0.9.1"), versions.MustParseVersion("2.0.0-beta.1"), versions.MustParseVersion("2.1.0"), versions.MustParseVersion("1.0.0"), versions.MustParseVersion("0.9.0"), versions.MustParseVersion("1.1.0"), versions.MustParseVersion("2.0.0"), } allowed, err := versions.MeetingConstraintsString("^1.0.0") if err != nil { fmt.Fprintf(os.Stderr, "invalid version constraint: %s", err) os.Exit(1) } candidates := available.Filter(allowed) chosen := candidates.Newest() fmt.Printf("Would install v%s\n", chosen) // => Would install v1.1.0 } ``` ## Version Sets and Version Lists Many version libraries stop at just parsing and representing exact versions, but most applications that need to process versions need also to represent version constraints, ordered lists of versions, etc. This library has a simple representation of versions as defined in the semver spec, but its main focus is on _version sets_ and _version lists_, which is reflected in the plural package name `versions`. A version set is primarily used to represent _permitted_ versions, and version sets are usually created from user-supplied constraint strings that specify concisely which versions are members of the set: ```go allowed, err := versions.MeetingConstraintsString("^1.0.0") // (handle error) fmt.Println(allowed.Has(MustParseVersion("1.0.0"))) // => true fmt.Println(allowed.Has(MustParseVersion("0.0.1"))) // => false fmt.Println(allowed.Has(MustParseVersion("2.0.0"))) // => false ``` Version sets can also be created and composed using the `versions` package API, with the following predefined sets and set functions: | Expression | Returns | | ---------- | ------- | | `versions.All` | Set of all possible versions. | | `versions.None` | Set containing no versions at all. | | `versions.Released` | Set of all "released" versions (not betas, alphas, etc). | | `versions.Prerelease` | The opposite of `versions.Released`. | | `versions.InitialDevelopment` | Contains all versions less than `1.0.0`, defined by semver as initial development releases where semver promises do not necessarily apply. | | `versions.AtLeast(v)` | Set of versions greater than or equal to `v`. | | `versions.AtMost(v)` | Set of versions less than or equal to `v`. | | `versions.NewerThan(v)` | Set of versions greater than `v`. | | `versions.OlderThan(v)` | Set of versions less than `v`. | | `versions.Only(v)` | Set containing only the given version `v`. | | `versions.Selection(vs...)` | Set containing only the given versions `vs`. | | `versions.Intersection(sets...)` | Set containing the versions that all of the given sets have in common. | | `versions.Union(sets...)` | Set containing all of the versions from all of the given sets. | | `set1.Subtract(set2)` | Set containing the versions from `set1` that are not in `set2`. | ```go v1 := versions.MustParseVersion("1.0.0") fmt.Println(versions.All.Has(v1)) // => true fmt.Println(versions.Releasaed.Has(v1)) // => true fmt.Println(versions.None.Has(v1)) // => false fmt.Println(versions.AtLeast(v1).Has(v1)) // => true fmt.Println(versions.NewerThan(v1).Has(v1)) // => false fmt.Println(versions.InitialDevelopment.Has(v1)) // => false ``` Whereas version sets contain an unordered collection of possibly-infinite versions, version _lists_ are finite and ordered. A version list is in fact just a named type around `[]Version` which adds some additional helper methods for common operations with versions: | Statement | Effect | | --------- | ------ | | `list = list.Filter(set)` | Removes from the list any members not in the given set, modifying the backing array in-place, and returns the new slice. | | `list.Sort()` | Sorts in-place the list in increasing order by version, so the newest versions are at the end of the list. | | `v = list.Newest()` | Returns the newest version in the list. (Strictly, _one of_ the newest versions, if the same version appears multiple times with different build metadata) | | `v = list.NewestList()` | Returns a `List` of all of the versions that are newest in the list. May return more than one if there are multiple versions differing only in build metadata. | | `v = list.NewestInSet(set)` | Like `Newest`, but considers only versions that are in the given set, without modifying the list. | ## Version values The representation of versions themselves is, by comparison, very simple. As defined by the semver spec, versions have major, minor, and patch segments that are numeric, and also have more free-form strings representing prerelease versions and build metadata. Versions are usually passed as values and so can be compared for exact equality using the standard `==` operator. However, most operations are instead concerned primarily with the notion of _priority_ defined by the semantic version spec, which is implemented in the following methods: | Expression | Returns | | ---------- | ------- | | `v1.Same(v2)` | True if `v1` and `v2` are identical aside from their "metadata" | | `v1.LessThan(v2)` | True if `v1` has a lower semver priority than `v2`. | | `v1.GreaterThan(v2)` | True if `v1` has a higher semver priority than `v2`. | These comparison functions are the basis of the `List.Sort` method. Note that it is possible for two non-equal versions to be neither less than nor greater than each other if they have differing `Metadata`. When considering set membership, the entire version value is considered including metadata. Applications that do not have any need for metadata may choose to strip it out using `v.Comparable()`, which returns a new version that is identical to the receiver except that its metadata is empty. ### Unspecified Versions The special version value `versions.Unspecified` is the zero value of `Version` and represents the absense of a version. Its representation is the same as for the version string `0.0.0`, and so that string is not a valid version number according to this package. The only version set that contains `versions.Unspecified` is `versions.All`. This is true even of the set returned by `versions.Only(versions.Unspecified)`, which is a useless expression. ## Text and JSON serialization The `versions.Version` type implements `encoding.TextMarshaler` and `encoding.TextUnmarshaler`, using the same syntax expected by `versions.ParseVersion` and `versions.MustParseVersion`. This allows version values to be included in structs used with encoding packages that make use of these interfaces, including `encoding/json`: ```go type Package struct { Name string `json:"name"` Version versions.Version `json:"version"` } ``` The `versions.Set` type also supports `encoding.TextUnmarshaler`, so it can be used for _unmarshalling_ of constrants into sets via the canonical constraint syntax. Sets cannot be marshalled because the set model implemented by this package contains features that cannot be expressed in the constraint language. ```go type Requirement struct { PackageName string `json:"packageName"` Versions versions.Set `json:"versions"` } ``` In practice the asymmetry of version set marshalling is not usually a problem because constraint sets are more often written by humans than by machines. In future the `constraints` package may get support for serializing its own constraint model, should a compelling use-case emerge. If you have one, please open a GitHub issue to discuss it! ## Finite vs. Infinite Version Sets Most version sets contain an infinite number of versions that lay within some bounds, such as the set returned by `versions.AtLeast(...)`. Some version sets contain only a finite number of versions, though. For example, `versions.Only(...)` returns a set containing only one version. The `set.IsFinite()` method allows a calling application to recognize if a particular set is finite. Some set operations in this package guarantee a finite set when certain conditions are met, avoiding the need to check this method; see [the package godoc](https://godoc.org/github.com/apparentlymart/go-versions/versions) for full details. A finite set can be converted into a list using `set.List()`. (This method will panic if used on an infinite set.) ## Constraint String Parsers Earlier examples showed the function `versions.MeetingConstraintsString`, which is a straightforward way to take a version string provided by the user and obtain a version set containing all of the versions it selects. The constraint syntax is implemented by the sub-package [`constraints`](https://godoc.org/github.com/apparentlymart/go-versions/versions/constraints), which contains a model for representing constraint specifications and some parser functions. Applications with more specific needs may wish to call directly into the functions in this package, for example to parse constraints using a `rubygems`-like syntax rather than the "npm-like" syntax this package uses by default. Full details of this package's _canonical_ constraint syntax (the "npm-like" one) are in the documentation for [`constraints.Parse`](https://godoc.org/github.com/apparentlymart/go-versions/versions/constraints#Parse). The "ruby-like" parsers use the same basic structure but use alternative operators inspired by the `rubygems` constraint syntax, including the "pessimistic" operator `~>`. Neither constraint syntax is 100% compatible with the system it takes inspiration from, but the goal is to be familiar enough to allow for a good user experience for users that have worked in these other systems. The constraint string parsers are designed to produce helpful error messages that are suitable to return directly an English-speaking end-user that has authored an invalid constraint string. For example: | Invalid String | Error Message | | -------------- | ------------- | | `1.0.0.0` | too many numbered portions; only three are allowed (major, minor, patch) | | `=>1.1.1` | invalid constraint operator `=>`; did you mean `>=`? | | `1.0.0, 2.0.0` | commas are not needed to separate version selections; separate with spaces instead | ## Requested Versions In addition to the usual idea of a set either containing or not containing a version, a version set has an additional concept of a version being _requested_. The requested versions of a set form a subset of that set, inferred from any exact version selections (`versions.Only(...)` and `versions.Selection(...)`) made in the construction of that set. In most cases this distinction is unimportant, but it is particularly interesting when dealing with pre-release versions, since these should generally be considered only if explicitly requested. Constraints processed using `versions.MeetingConstraints(...)` and `versions.MeetingConstraintsString(...)` will automatically exclude all unreleased versions that are not explicitly requested: ```go beta1 := versions.MustParseVersion("2.0-beta.1") allowed := versions.MustMakeSet(versions.MeetingConstraintsString(">=1.0")) fmt.Println(allowed.Has(beta1)) // false ``` Version sets constructed manually using the constructor functions do not have this characteristic, and will return pre-release versions unless they are specifically excluded from the set: ```go beta1 := versions.MustParseVersion("2.0-beta.1") beta2 := versions.MustParseVersion("2.0-beta.2") min := versions.MustParseVersion("1.0.0") allowed := versions.AtLeast(min) fmt.Println(allowed.Has(beta1)) // => true fmt.Println(allowed.Has(beta2)) // => true // Construct a new version set containing only _released_ versions that meet // our constraint. onlyReleased = allowed.Intersection(versions.Released) fmt.Println(onlyReleased.Has(beta1)) // => false fmt.Println(onlyReleased.Has(beta2)) // => false ``` The _requested versions set_ of a set can be used to obtain any versions that are requested exactly by a set, in order to implement the pre-release version selection behavior done automatically by `versions.MeetingConstraints`: ```go beta1 := versions.MustParseVersion("2.0-beta.1") beta2 := versions.MustParseVersion("2.0-beta.2") min := versions.MustParseVersion("1.0.0") allowed := versions.Union( versions.AtLeast(min), // allow any version >1.0.0 versions.Only(beta1), // also allow beta1 ) fmt.Println(allowed.Has(beta1)) // => true fmt.Println(allowed.Has(beta2)) // => true // Exclude pre-release versions onlyReleased = allowed.Intersection(versions.Released) fmt.Println(onlyReleased.Has(beta1)) // => false fmt.Println(onlyReleased.Has(beta2)) // => false // Now re-allow the explicitly-requested version, beta1 allowed = Union( allowed.AllRequested(), // set containing only beta1 onlyReleased, // set containing released versions >=1.0.0 ) fmt.Println(allowed.Has(beta1)) // => true, because it was requested fmt.Println(allowed.Has(beta2)) // => false, because it was not requested ``` Because excluding pre-releases unless explicitly requested is usually desirable, a helper method is provided to automatically implement the above for any arbitrary set: ```go beta1 := versions.MustParseVersion("2.0-beta.1") beta2 := versions.MustParseVersion("2.0-beta.2") min := versions.MustParseVersion("1.0.0") allowed := versions.Union( versions.AtLeast(min), // allow any version >1.0.0 versions.Only(beta1), // also allow beta1 ) fmt.Println(allowed.Has(beta1)) // => true fmt.Println(allowed.Has(beta2)) // => true allowed = allowed.WithoutUnrequestedPrereleases() fmt.Println(allowed.Has(beta1)) // => true, because it was requested fmt.Println(allowed.Has(beta2)) // => false, because it was not requested ``` The set of requested versions for a set is always a finite set, by definition. It can therefore be converted to a version list with `set.List()` if required. Requested versions are subject to the same set operations as normal set members, due to the rule that all requested versions must also be set members. For example, `versions.Only(versions.MustParseVersion("1.0-beta.1")).Subtract(versions.Prerelease)` does not request `1.0-beta.1`, because that member was removed by the `Subtract` operation. Most callers will just pass in constraint strings authored by the user and thus not need to worry about requested version sets. However, the functionality is available to directly interact with this concept for the benefit of applications that wish to implement different rules for pre-release versions. go-versions-1.0.1/go.mod000066400000000000000000000003101374017526700151000ustar00rootroot00000000000000module github.com/apparentlymart/go-versions go 1.14 require ( github.com/davecgh/go-spew v1.1.0 github.com/go-test/deep v1.0.1 github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348 ) go-versions-1.0.1/go.sum000066400000000000000000000010711374017526700151320ustar00rootroot00000000000000github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-test/deep v1.0.1 h1:UQhStjbkDClarlmv0am7OXXO4/GaPdCGiUiMTvi28sg= github.com/go-test/deep v1.0.1/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348 h1:MtvEpTB6LX3vkb4ax0b5D2DHbNAUsen0Gx5wZoq3lV4= github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k= go-versions-1.0.1/versions/000077500000000000000000000000001374017526700156505ustar00rootroot00000000000000go-versions-1.0.1/versions/constraints/000077500000000000000000000000001374017526700202175ustar00rootroot00000000000000go-versions-1.0.1/versions/constraints/canon_style.go000066400000000000000000000315541374017526700230740ustar00rootroot00000000000000package constraints import ( "fmt" "strings" ) // Parse parses a constraint string using a syntax similar to that used by // npm, Go "dep", Rust's "cargo", etc. Exact compatibility with any of these // systems is not guaranteed, but instead we aim for familiarity in the choice // of operators and their meanings. The syntax described here is considered the // canonical syntax for this package, but a Ruby-style syntax is also offered // via the function "ParseRubyStyle". // // A constraint string is a sequence of selection sets delimited by ||, with // each selection set being a whitespace-delimited sequence of selections. // Each selection is then the combination of a matching operator and a boundary // version. The following is an example of a complex constraint string // illustrating all of these features: // // >=1.0.0 <2.0.0 || 1.0.0-beta1 || =2.0.2 // // In practice constraint strings are usually simpler than this, but this // complex example allows us to identify each of the parts by example: // // Selection Sets: ">=1.0.0 <2.0.0" // "1.0.0-beta1" // "=2.0.2" // Selections: ">=1.0.0" // "<2.0.0" // "1.0.0-beta1" // "=2.0.2" // Matching Operators: ">=", "<", "=" are explicit operators // "1.0.0-beta1" has an implicit "=" operator // Boundary Versions: "1.0.0", "2.0.0", "1.0.0-beta1", "2.0.2" // // A constraint string describes the members of a version set by adding exact // versions or ranges of versions to that set. A version is in the set if // any one of the selection sets match that version. A selection set matches // a version if all of its selections match that version. A selection matches // a version if the version has the indicated relationship with the given // boundary version. // // In the above example, the first selection set matches all released versions // whose major segment is 1, since both selections must apply. However, the // remaining two selection sets describe two specific versions outside of that // range that are also admitted, in addition to those in the indicated range. // // The available matching operators are: // // < Less than // <= Less than or equal // > Greater than // >= Greater than or equal // = Equal // ! Not equal // ~ Greater than with implied upper limit (described below) // ^ Greater than excluding new major releases (described below) // // If no operator is specified, the operator is implied to be "equal" for a // full version specification, or a special additional "match" operator for // a version containing wildcards as described below. // // The "~" matching operator is a shorthand for expressing both a lower and // upper limit within a single expression. The effect of this operator depends // on how many segments are specified in the boundary version: if only one // segment is specified then new minor and patch versions are accepted, whereas // if two or three segments are specified then only patch versions are accepted. // For example: // // ~1 is equivalent to >=1.0.0 <2.0.0 // ~1.0 is equivalent to >=1.0.0 <1.1.0 // ~1.2 is equivalent to >=1.2.0 <1.3.0 // ~1.2.0 is equivalent to >=1.2.0 <1.3.0 // ~1.2.3 is equivalent to >=1.2.3 <1.3.0 // // The "^" matching operator is similar to "~" except that it always constrains // only the major version number. It has an additional special behavior for // when the major version number is zero: in that case, the minor release // number is constrained, reflecting the common semver convention that initial // development releases mark breaking changes by incrementing the minor version. // For example: // // ^1 is equivalent to >=1.0.0 <2.0.0 // ^1.2 is equivalent to >=1.2.0 <2.0.0 // ^1.2.3 is equivalent to >=1.2.3 <2.0.0 // ^0.1.0 is equivalent to >=0.1.0 <0.2.0 // ^0.1.2 is equivalent to >=0.1.2 <0.2.0 // // The boundary version can contain wildcards for the major, minor or patch // segments, which are specified using the markers "*", "x", or "X". When used // in a selection with no explicit operator, these specify the implied "match" // operator and define ranges with similar meaning to the "~" and "^" operators: // // 1.* is equivalent to >=1.0.0 <2.0.0 // 1.*.* is equivalent to >=1.0.0 <2.0.0 // 1.0.* is equivalent to >=1.0.0 <1.1.0 // // When wildcards are used, the first segment specified as a wildcard implies // that all of the following segments are also wildcards. A version // specification like "1.*.2" is invalid, because a wildcard minor version // implies that the patch version must also be a wildcard. // // Wildcards have no special meaning when used with explicit operators, and so // they are merely replaced with zeros in such cases. // // Explicit range syntax using a hyphen creates inclusive upper and lower // bounds: // // 1.0.0 - 2.0.0 is equivalent to >=1.0.0 <=2.0.0 // 1.2.3 - 2.3.4 is equivalent to >=1.2.3 <=2.3.4 // // Requests of exact pre-release versions with the equals operator have // no special meaning to the constraint parser, but are interpreted as explicit // requests for those versions when interpreted by the MeetingConstraints // function (and related functions) in the "versions" package, in the parent // directory. Pre-release versions that are not explicitly requested are // excluded from selection so that e.g. "^1.0.0" will not match a version // "2.0.0-beta.1". // // The result is always a UnionSpec, whose members are IntersectionSpecs // each describing one selection set. In the common case where a string // contains only one selection, both the UnionSpec and the IntersectionSpec // will have only one element and can thus be effectively ignored by the // caller. (Union and intersection of single sets are both no-op.) // A valid string must contain at least one selection; if an empty selection // is to be considered as either "no versions" or "all versions" then this // special case must be handled by the caller prior to calling this function. // // If there are syntax errors or ambiguities in the provided string then an // error is returned. All errors returned by this function are suitable for // display to English-speaking end-users, and avoid any Go-specific // terminology. func Parse(str string) (UnionSpec, error) { str = strings.TrimSpace(str) if str == "" { return nil, fmt.Errorf("empty specification") } // Most constraint strings contain only one selection, so we'll // allocate under that assumption and re-allocate if needed. uspec := make(UnionSpec, 0, 1) ispec := make(IntersectionSpec, 0, 1) remain := str for { var selection SelectionSpec var err error selection, remain, err = parseSelection(remain) if err != nil { return nil, err } remain = strings.TrimSpace(remain) if len(remain) > 0 && remain[0] == '-' { // Looks like user wants to make a range expression, so we'll // look for another selection. remain = strings.TrimSpace(remain[1:]) if remain == "" { return nil, fmt.Errorf(`operator "-" must be followed by another version selection to specify the upper limit of the range`) } var lower, upper SelectionSpec lower = selection upper, remain, err = parseSelection(remain) remain = strings.TrimSpace(remain) if err != nil { return nil, err } if lower.Operator != OpUnconstrained { return nil, fmt.Errorf(`lower bound of range specified with "-" operator must be an exact version`) } if upper.Operator != OpUnconstrained { return nil, fmt.Errorf(`upper bound of range specified with "-" operator must be an exact version`) } lower.Operator = OpGreaterThanOrEqual lower.Boundary = lower.Boundary.ConstrainToZero() if upper.Boundary.IsExact() { upper.Operator = OpLessThanOrEqual } else { upper.Operator = OpLessThan upper.Boundary = upper.Boundary.ConstrainToUpperBound() } ispec = append(ispec, lower, upper) } else { if selection.Operator == OpUnconstrained { // Select a default operator based on whether the version // specification contains wildcards. if selection.Boundary.IsExact() { selection.Operator = OpEqual } else { selection.Operator = OpMatch } } if selection.Operator != OpMatch { switch selection.Operator { case OpMatch: // nothing to do case OpLessThanOrEqual: if !selection.Boundary.IsExact() { selection.Operator = OpLessThan selection.Boundary = selection.Boundary.ConstrainToUpperBound() } case OpGreaterThan: if !selection.Boundary.IsExact() { // If "greater than" has an imprecise boundary then we'll // turn it into a "greater than or equal to" and use the // upper bound of the boundary, so e.g.: // >1.*.* means >=2.0.0, because that's greater than // everything matched by 1.*.*. selection.Operator = OpGreaterThanOrEqual selection.Boundary = selection.Boundary.ConstrainToUpperBound() } default: selection.Boundary = selection.Boundary.ConstrainToZero() } } ispec = append(ispec, selection) } if len(remain) == 0 { // All done! break } if remain[0] == ',' { return nil, fmt.Errorf(`commas are not needed to separate version selections; separate with spaces instead`) } if remain[0] == '|' { if !strings.HasPrefix(remain, "||") { // User was probably trying for "||", so we'll produce a specialized error return nil, fmt.Errorf(`single "|" is not a valid operator; did you mean "||" to specify an alternative?`) } remain = strings.TrimSpace(remain[2:]) if remain == "" { return nil, fmt.Errorf(`operator "||" must be followed by another version selection`) } // Begin a new IntersectionSpec, added to our single UnionSpec uspec = append(uspec, ispec) ispec = make(IntersectionSpec, 0, 1) } } uspec = append(uspec, ispec) return uspec, nil } // parseSelection parses one canon-style selection from the prefix of the // given string, returning the result along with the remaining unconsumed // string for the caller to use for further processing. func parseSelection(str string) (SelectionSpec, string, error) { raw, remain := scanConstraint(str) var spec SelectionSpec if len(str) == len(remain) { if len(remain) > 0 && remain[0] == 'v' { // User seems to be trying to use a "v" prefix, like "v1.0.0" return spec, remain, fmt.Errorf(`a "v" prefix should not be used when specifying versions`) } // If we made no progress at all then the selection must be entirely invalid. return spec, remain, fmt.Errorf("the sequence %q is not valid", remain) } switch raw.op { case "": // We'll deal with this situation in the caller spec.Operator = OpUnconstrained case "=": spec.Operator = OpEqual case "!": spec.Operator = OpNotEqual case ">": spec.Operator = OpGreaterThan case ">=": spec.Operator = OpGreaterThanOrEqual case "<": spec.Operator = OpLessThan case "<=": spec.Operator = OpLessThanOrEqual case "~": if raw.numCt > 1 { spec.Operator = OpGreaterThanOrEqualPatchOnly } else { spec.Operator = OpGreaterThanOrEqualMinorOnly } case "^": if len(raw.nums[0]) > 0 && raw.nums[0][0] == '0' { // Special case for major version 0, which is initial development: // we treat the minor number as if it's the major number. spec.Operator = OpGreaterThanOrEqualPatchOnly } else { spec.Operator = OpGreaterThanOrEqualMinorOnly } case "=<": return spec, remain, fmt.Errorf("invalid constraint operator %q; did you mean \"<=\"?", raw.op) case "=>": return spec, remain, fmt.Errorf("invalid constraint operator %q; did you mean \">=\"?", raw.op) default: return spec, remain, fmt.Errorf("invalid constraint operator %q", raw.op) } if raw.sep != "" { return spec, remain, fmt.Errorf("no spaces allowed after operator %q", raw.op) } if raw.numCt > 3 { return spec, remain, fmt.Errorf("too many numbered portions; only three are allowed (major, minor, patch)") } // Unspecified portions are either zero or wildcard depending on whether // any explicit wildcards are present. seenWild := false for i, s := range raw.nums { switch { case isWildcardNum(s): seenWild = true case i >= raw.numCt: if seenWild { raw.nums[i] = "*" } else { raw.nums[i] = "0" } default: // If we find a non-wildcard after we've already seen a wildcard // then this specification is inconsistent, which is an error. if seenWild { return spec, remain, fmt.Errorf("can't use exact %s segment after a previous segment was wildcard", rawNumNames[i]) } } } if seenWild { if raw.pre != "" { return spec, remain, fmt.Errorf(`can't use prerelease segment (introduced by "-") in a version with wildcards`) } if raw.meta != "" { return spec, remain, fmt.Errorf(`can't use build metadata segment (introduced by "+") in a version with wildcards`) } } spec.Boundary = raw.VersionSpec() return spec, remain, nil } go-versions-1.0.1/versions/constraints/canon_style_test.go000066400000000000000000000310671374017526700241320ustar00rootroot00000000000000package constraints import ( "testing" "github.com/davecgh/go-spew/spew" "github.com/go-test/deep" ) func TestParse(t *testing.T) { tests := []struct { Input string Want UnionSpec WantErr string }{ { "", nil, "empty specification", }, { "1", UnionSpec{ IntersectionSpec{ SelectionSpec{ Operator: OpEqual, Boundary: VersionSpec{ Major: NumConstraint{Num: 1}, Minor: NumConstraint{Num: 0}, Patch: NumConstraint{Num: 0}, }, }, }, }, "", }, { "1.1", UnionSpec{ IntersectionSpec{ SelectionSpec{ Operator: OpEqual, Boundary: VersionSpec{ Major: NumConstraint{Num: 1}, Minor: NumConstraint{Num: 1}, Patch: NumConstraint{Num: 0}, }, }, }, }, "", }, { "1.1.1", UnionSpec{ IntersectionSpec{ SelectionSpec{ Operator: OpEqual, Boundary: VersionSpec{ Major: NumConstraint{Num: 1}, Minor: NumConstraint{Num: 1}, Patch: NumConstraint{Num: 1}, }, }, }, }, "", }, { "1.0.0.0", nil, "too many numbered portions; only three are allowed (major, minor, patch)", }, { "v1.0.0", nil, `a "v" prefix should not be used when specifying versions`, }, { "1.0.0-beta2", UnionSpec{ IntersectionSpec{ SelectionSpec{ Operator: OpEqual, Boundary: VersionSpec{ Major: NumConstraint{Num: 1}, Minor: NumConstraint{Num: 0}, Patch: NumConstraint{Num: 0}, Prerelease: "beta2", }, }, }, }, "", }, { "1.0-beta2", UnionSpec{ IntersectionSpec{ SelectionSpec{ Operator: OpEqual, Boundary: VersionSpec{ Major: NumConstraint{Num: 1}, Minor: NumConstraint{Num: 0}, Patch: NumConstraint{Num: 0}, // implied by the prerelease tag to ensure constraint consistency Prerelease: "beta2", }, }, }, }, "", }, { "1.0.0-beta.2", UnionSpec{ IntersectionSpec{ SelectionSpec{ Operator: OpEqual, Boundary: VersionSpec{ Major: NumConstraint{Num: 1}, Minor: NumConstraint{Num: 0}, Patch: NumConstraint{Num: 0}, Prerelease: "beta.2", }, }, }, }, "", }, { "1.0.0+foo", UnionSpec{ IntersectionSpec{ SelectionSpec{ Operator: OpEqual, Boundary: VersionSpec{ Major: NumConstraint{Num: 1}, Minor: NumConstraint{Num: 0}, Patch: NumConstraint{Num: 0}, Metadata: "foo", }, }, }, }, "", }, { "1.0.0+foo.bar", UnionSpec{ IntersectionSpec{ SelectionSpec{ Operator: OpEqual, Boundary: VersionSpec{ Major: NumConstraint{Num: 1}, Minor: NumConstraint{Num: 0}, Patch: NumConstraint{Num: 0}, Metadata: "foo.bar", }, }, }, }, "", }, { "1.0.0-beta1+foo.bar", UnionSpec{ IntersectionSpec{ SelectionSpec{ Operator: OpEqual, Boundary: VersionSpec{ Major: NumConstraint{Num: 1}, Minor: NumConstraint{Num: 0}, Patch: NumConstraint{Num: 0}, Prerelease: "beta1", Metadata: "foo.bar", }, }, }, }, "", }, { ">1.1.1", UnionSpec{ IntersectionSpec{ SelectionSpec{ Operator: OpGreaterThan, Boundary: VersionSpec{ Major: NumConstraint{Num: 1}, Minor: NumConstraint{Num: 1}, Patch: NumConstraint{Num: 1}, }, }, }, }, ``, }, { ">2.*.*", UnionSpec{ IntersectionSpec{ SelectionSpec{ Operator: OpGreaterThanOrEqual, Boundary: VersionSpec{ Major: NumConstraint{Num: 3}, Minor: NumConstraint{Num: 0}, Patch: NumConstraint{Num: 0}, }, }, }, }, ``, }, { ">=1.1.1", UnionSpec{ IntersectionSpec{ SelectionSpec{ Operator: OpGreaterThanOrEqual, Boundary: VersionSpec{ Major: NumConstraint{Num: 1}, Minor: NumConstraint{Num: 1}, Patch: NumConstraint{Num: 1}, }, }, }, }, ``, }, { ">=2.*.*", UnionSpec{ IntersectionSpec{ SelectionSpec{ Operator: OpGreaterThanOrEqual, Boundary: VersionSpec{ Major: NumConstraint{Num: 2}, Minor: NumConstraint{Num: 0}, Patch: NumConstraint{Num: 0}, }, }, }, }, ``, }, { "=>1.1.1", nil, `invalid constraint operator "=>"; did you mean ">="?`, }, { "<1.1.1", UnionSpec{ IntersectionSpec{ SelectionSpec{ Operator: OpLessThan, Boundary: VersionSpec{ Major: NumConstraint{Num: 1}, Minor: NumConstraint{Num: 1}, Patch: NumConstraint{Num: 1}, }, }, }, }, ``, }, { "<2.*.*", UnionSpec{ IntersectionSpec{ SelectionSpec{ Operator: OpLessThan, Boundary: VersionSpec{ Major: NumConstraint{Num: 2}, Minor: NumConstraint{Num: 0}, Patch: NumConstraint{Num: 0}, }, }, }, }, ``, }, { "<=1.1.1", UnionSpec{ IntersectionSpec{ SelectionSpec{ Operator: OpLessThanOrEqual, Boundary: VersionSpec{ Major: NumConstraint{Num: 1}, Minor: NumConstraint{Num: 1}, Patch: NumConstraint{Num: 1}, }, }, }, }, ``, }, { "<=2.*.*", UnionSpec{ IntersectionSpec{ SelectionSpec{ Operator: OpLessThan, Boundary: VersionSpec{ Major: NumConstraint{Num: 3}, Minor: NumConstraint{Num: 0}, Patch: NumConstraint{Num: 0}, }, }, }, }, ``, }, { "=<1.1.1", nil, `invalid constraint operator "=<"; did you mean "<="?`, }, { "~1.1.1", UnionSpec{ IntersectionSpec{ SelectionSpec{ Operator: OpGreaterThanOrEqualPatchOnly, Boundary: VersionSpec{ Major: NumConstraint{Num: 1}, Minor: NumConstraint{Num: 1}, Patch: NumConstraint{Num: 1}, }, }, }, }, ``, }, { "~1.1", UnionSpec{ IntersectionSpec{ SelectionSpec{ Operator: OpGreaterThanOrEqualPatchOnly, Boundary: VersionSpec{ Major: NumConstraint{Num: 1}, Minor: NumConstraint{Num: 1}, Patch: NumConstraint{Num: 0}, }, }, }, }, ``, }, { "~1", UnionSpec{ IntersectionSpec{ SelectionSpec{ Operator: OpGreaterThanOrEqualMinorOnly, Boundary: VersionSpec{ Major: NumConstraint{Num: 1}, Minor: NumConstraint{Num: 0}, Patch: NumConstraint{Num: 0}, }, }, }, }, ``, }, { "^1.1.1", UnionSpec{ IntersectionSpec{ SelectionSpec{ Operator: OpGreaterThanOrEqualMinorOnly, Boundary: VersionSpec{ Major: NumConstraint{Num: 1}, Minor: NumConstraint{Num: 1}, Patch: NumConstraint{Num: 1}, }, }, }, }, ``, }, { "^1.1", UnionSpec{ IntersectionSpec{ SelectionSpec{ Operator: OpGreaterThanOrEqualMinorOnly, Boundary: VersionSpec{ Major: NumConstraint{Num: 1}, Minor: NumConstraint{Num: 1}, Patch: NumConstraint{Num: 0}, }, }, }, }, ``, }, { "^0.1", UnionSpec{ IntersectionSpec{ SelectionSpec{ Operator: OpGreaterThanOrEqualPatchOnly, Boundary: VersionSpec{ Major: NumConstraint{Num: 0}, Minor: NumConstraint{Num: 1}, Patch: NumConstraint{Num: 0}, }, }, }, }, ``, }, { "^1", UnionSpec{ IntersectionSpec{ SelectionSpec{ Operator: OpGreaterThanOrEqualMinorOnly, Boundary: VersionSpec{ Major: NumConstraint{Num: 1}, Minor: NumConstraint{Num: 0}, Patch: NumConstraint{Num: 0}, }, }, }, }, ``, }, { "=1.1.1", UnionSpec{ IntersectionSpec{ SelectionSpec{ Operator: OpEqual, Boundary: VersionSpec{ Major: NumConstraint{Num: 1}, Minor: NumConstraint{Num: 1}, Patch: NumConstraint{Num: 1}, }, }, }, }, ``, }, { "!1.1.1", UnionSpec{ IntersectionSpec{ SelectionSpec{ Operator: OpNotEqual, Boundary: VersionSpec{ Major: NumConstraint{Num: 1}, Minor: NumConstraint{Num: 1}, Patch: NumConstraint{Num: 1}, }, }, }, }, ``, }, { "1.*.*", UnionSpec{ IntersectionSpec{ SelectionSpec{ Operator: OpMatch, Boundary: VersionSpec{ Major: NumConstraint{Num: 1}, Minor: NumConstraint{Unconstrained: true}, Patch: NumConstraint{Unconstrained: true}, }, }, }, }, ``, }, { "=1.*.*", UnionSpec{ IntersectionSpec{ SelectionSpec{ Operator: OpEqual, Boundary: VersionSpec{ Major: NumConstraint{Num: 1}, Minor: NumConstraint{Num: 0}, Patch: NumConstraint{Num: 0}, }, }, }, }, ``, }, { "1.0.x", UnionSpec{ IntersectionSpec{ SelectionSpec{ Operator: OpMatch, Boundary: VersionSpec{ Major: NumConstraint{Num: 1}, Minor: NumConstraint{Num: 0}, Patch: NumConstraint{Unconstrained: true}, }, }, }, }, ``, }, { "1.0.0 - 2.0.0", UnionSpec{ IntersectionSpec{ SelectionSpec{ Operator: OpGreaterThanOrEqual, Boundary: VersionSpec{ Major: NumConstraint{Num: 1}, Minor: NumConstraint{Num: 0}, Patch: NumConstraint{Num: 0}, }, }, SelectionSpec{ Operator: OpLessThanOrEqual, Boundary: VersionSpec{ Major: NumConstraint{Num: 2}, Minor: NumConstraint{Num: 0}, Patch: NumConstraint{Num: 0}, }, }, }, }, ``, }, { "1.*.* - 2.*.*", UnionSpec{ IntersectionSpec{ SelectionSpec{ Operator: OpGreaterThanOrEqual, Boundary: VersionSpec{ Major: NumConstraint{Num: 1}, Minor: NumConstraint{Num: 0}, Patch: NumConstraint{Num: 0}, }, }, SelectionSpec{ Operator: OpLessThan, Boundary: VersionSpec{ Major: NumConstraint{Num: 3}, Minor: NumConstraint{Num: 0}, Patch: NumConstraint{Num: 0}, }, }, }, }, ``, }, { ">1.0.0 - 2.0.0", nil, `lower bound of range specified with "-" operator must be an exact version`, }, { "1.0.0 - >2.0.0", nil, `upper bound of range specified with "-" operator must be an exact version`, }, { ">=1.0.0 <2.0.0", UnionSpec{ IntersectionSpec{ SelectionSpec{ Operator: OpGreaterThanOrEqual, Boundary: VersionSpec{ Major: NumConstraint{Num: 1}, Minor: NumConstraint{Num: 0}, Patch: NumConstraint{Num: 0}, }, }, SelectionSpec{ Operator: OpLessThan, Boundary: VersionSpec{ Major: NumConstraint{Num: 2}, Minor: NumConstraint{Num: 0}, Patch: NumConstraint{Num: 0}, }, }, }, }, ``, }, { ">=1.0 <2 || 2.0-beta.1", UnionSpec{ IntersectionSpec{ SelectionSpec{ Operator: OpGreaterThanOrEqual, Boundary: VersionSpec{ Major: NumConstraint{Num: 1}, Minor: NumConstraint{Num: 0}, Patch: NumConstraint{Num: 0}, }, }, SelectionSpec{ Operator: OpLessThan, Boundary: VersionSpec{ Major: NumConstraint{Num: 2}, Minor: NumConstraint{Num: 0}, Patch: NumConstraint{Num: 0}, }, }, }, IntersectionSpec{ SelectionSpec{ Operator: OpEqual, Boundary: VersionSpec{ Major: NumConstraint{Num: 2}, Minor: NumConstraint{Num: 0}, Patch: NumConstraint{Num: 0}, Prerelease: "beta.1", }, }, }, }, ``, }, { "1.0.0, 2.0.0", nil, `commas are not needed to separate version selections; separate with spaces instead`, }, { "= 1.1.1", nil, `no spaces allowed after operator "="`, }, { "= 1.1.1", nil, `no spaces allowed after operator "="`, }, { "garbage", nil, `the sequence "garbage" is not valid`, }, { "&1.1.0", nil, `invalid constraint operator "&"`, }, } for _, test := range tests { t.Run(test.Input, func(t *testing.T) { got, err := Parse(test.Input) var gotErr string if err != nil { gotErr = err.Error() } if gotErr != test.WantErr { t.Errorf("wrong error\ngot: %s\nwant: %s", gotErr, test.WantErr) return } if err != nil { return } t.Logf("got: %s", spew.Sdump(got)) t.Logf("want: %s", spew.Sdump(test.Want)) for _, problem := range deep.Equal(got, test.Want) { t.Error(problem) } }) } } go-versions-1.0.1/versions/constraints/constraintdepth_string.go000066400000000000000000000010231374017526700253410ustar00rootroot00000000000000// Code generated by "stringer -type ConstraintDepth"; DO NOT EDIT. package constraints import "strconv" const _ConstraintDepth_name = "UnconstrainedConstrainedMajorConstrainedMinorConstrainedPatch" var _ConstraintDepth_index = [...]uint8{0, 13, 29, 45, 61} func (i ConstraintDepth) String() string { if i < 0 || i >= ConstraintDepth(len(_ConstraintDepth_index)-1) { return "ConstraintDepth(" + strconv.FormatInt(int64(i), 10) + ")" } return _ConstraintDepth_name[_ConstraintDepth_index[i]:_ConstraintDepth_index[i+1]] } go-versions-1.0.1/versions/constraints/doc.go000066400000000000000000000012771374017526700213220ustar00rootroot00000000000000// Package constraints contains a high-level representation of version // constraints that retains enough information for direct analysis and // serialization as a string. // // The package also contains parsers to produce that representation from // various compact constraint specification formats. // // The main "versions" package, available in the parent directory, can consume // the high-level constraint representation from this package to construct // a version set that contains all versions meeting the given constraints. // Package "constraints" does not contain any functionalty for checking versions // against constraints since that is provided by package "versions". package constraints go-versions-1.0.1/versions/constraints/raw.go000066400000000000000000000035651374017526700213500ustar00rootroot00000000000000package constraints import ( "strconv" ) //go:generate ragel -G1 -Z raw_scan.rl //go:generate gofmt -w raw_scan.go // rawConstraint is a tokenization of a constraint string, used internally // as the first layer of parsing. type rawConstraint struct { op string sep string nums [3]string numCt int pre string meta string } // VersionSpec turns the receiver into a VersionSpec in a reasonable // default way. This method assumes that the raw constraint was already // validated, and will panic or produce undefined results if it contains // anything invalid. // // In particular, numbers are automatically marked as unconstrained if they // are omitted or set to wildcards, so the caller must apply any additional // validation rules on the usage of unconstrained numbers before calling. func (raw rawConstraint) VersionSpec() VersionSpec { return VersionSpec{ Major: parseRawNumConstraint(raw.nums[0]), Minor: parseRawNumConstraint(raw.nums[1]), Patch: parseRawNumConstraint(raw.nums[2]), Prerelease: raw.pre, Metadata: raw.meta, } } var rawNumNames = [...]string{"major", "minor", "patch"} func isWildcardNum(s string) bool { switch s { case "*", "x", "X": return true default: return false } } // parseRawNum parses a raw number string which the caller has already // determined is non-empty and non-wildcard. If the string is not numeric // then this function will panic. func parseRawNum(s string) uint64 { v, err := strconv.ParseUint(s, 10, 64) if err != nil { panic(err) } return v } // parseRawNumConstraint parses a raw number into a NumConstraint, setting it // to unconstrained if the value is empty or a wildcard. func parseRawNumConstraint(s string) NumConstraint { switch { case s == "" || isWildcardNum(s): return NumConstraint{ Unconstrained: true, } default: return NumConstraint{ Num: parseRawNum(s), } } } go-versions-1.0.1/versions/constraints/raw_scan.go000066400000000000000000000203361374017526700223470ustar00rootroot00000000000000// line 1 "raw_scan.rl" // This file is generated from raw_scan.rl. DO NOT EDIT. // line 5 "raw_scan.rl" package constraints // line 12 "raw_scan.go" var _scan_eof_actions []byte = []byte{ 0, 1, 1, 7, 9, 9, 9, 11, 14, 15, 11, } const scan_start int = 1 const scan_first_final int = 7 const scan_error int = 0 const scan_en_main int = 1 // line 11 "raw_scan.rl" func scanConstraint(data string) (rawConstraint, string) { var constraint rawConstraint var numIdx int var extra string // Ragel state p := 0 // "Pointer" into data pe := len(data) // End-of-data "pointer" cs := 0 // constraint state (will be initialized by ragel-generated code) ts := 0 te := 0 eof := pe // Keep Go compiler happy even if generated code doesn't use these _ = ts _ = te _ = eof // line 47 "raw_scan.go" { cs = scan_start } // line 52 "raw_scan.go" { if p == pe { goto _test_eof } if cs == 0 { goto _out } _resume: switch cs { case 1: switch data[p] { case 32: goto tr1 case 42: goto tr2 case 46: goto tr3 case 88: goto tr2 case 120: goto tr2 } switch { case data[p] < 48: if 9 <= data[p] && data[p] <= 13 { goto tr1 } case data[p] > 57: switch { case data[p] > 90: if 97 <= data[p] && data[p] <= 122 { goto tr3 } case data[p] >= 65: goto tr3 } default: goto tr4 } goto tr0 case 2: switch data[p] { case 32: goto tr6 case 42: goto tr7 case 46: goto tr3 case 88: goto tr7 case 120: goto tr7 } switch { case data[p] < 48: if 9 <= data[p] && data[p] <= 13 { goto tr6 } case data[p] > 57: switch { case data[p] > 90: if 97 <= data[p] && data[p] <= 122 { goto tr3 } case data[p] >= 65: goto tr3 } default: goto tr8 } goto tr5 case 3: switch data[p] { case 32: goto tr10 case 42: goto tr11 case 88: goto tr11 case 120: goto tr11 } switch { case data[p] > 13: if 48 <= data[p] && data[p] <= 57 { goto tr12 } case data[p] >= 9: goto tr10 } goto tr9 case 0: goto _out case 7: switch data[p] { case 43: goto tr19 case 45: goto tr20 case 46: goto tr21 } goto tr18 case 4: switch { case data[p] < 48: if 45 <= data[p] && data[p] <= 46 { goto tr14 } case data[p] > 57: switch { case data[p] > 90: if 97 <= data[p] && data[p] <= 122 { goto tr14 } case data[p] >= 65: goto tr14 } default: goto tr14 } goto tr13 case 8: switch { case data[p] < 48: if 45 <= data[p] && data[p] <= 46 { goto tr14 } case data[p] > 57: switch { case data[p] > 90: if 97 <= data[p] && data[p] <= 122 { goto tr14 } case data[p] >= 65: goto tr14 } default: goto tr14 } goto tr22 case 5: switch { case data[p] < 48: if 45 <= data[p] && data[p] <= 46 { goto tr15 } case data[p] > 57: switch { case data[p] > 90: if 97 <= data[p] && data[p] <= 122 { goto tr15 } case data[p] >= 65: goto tr15 } default: goto tr15 } goto tr13 case 9: if data[p] == 43 { goto tr24 } switch { case data[p] < 48: if 45 <= data[p] && data[p] <= 46 { goto tr15 } case data[p] > 57: switch { case data[p] > 90: if 97 <= data[p] && data[p] <= 122 { goto tr15 } case data[p] >= 65: goto tr15 } default: goto tr15 } goto tr23 case 6: switch data[p] { case 42: goto tr16 case 88: goto tr16 case 120: goto tr16 } if 48 <= data[p] && data[p] <= 57 { goto tr17 } goto tr13 case 10: switch data[p] { case 43: goto tr19 case 45: goto tr20 case 46: goto tr21 } if 48 <= data[p] && data[p] <= 57 { goto tr25 } goto tr18 } tr3: cs = 0 goto f0 tr9: cs = 0 goto f6 tr13: cs = 0 goto f8 tr18: cs = 0 goto f10 tr22: cs = 0 goto f13 tr23: cs = 0 goto f14 tr5: cs = 2 goto _again tr0: cs = 2 goto f1 tr10: cs = 3 goto _again tr1: cs = 3 goto f2 tr6: cs = 3 goto f4 tr19: cs = 4 goto f11 tr24: cs = 4 goto f15 tr20: cs = 5 goto f11 tr21: cs = 6 goto f12 tr2: cs = 7 goto f3 tr7: cs = 7 goto f5 tr11: cs = 7 goto f7 tr16: cs = 7 goto f9 tr14: cs = 8 goto _again tr15: cs = 9 goto _again tr25: cs = 10 goto _again tr4: cs = 10 goto f3 tr8: cs = 10 goto f5 tr12: cs = 10 goto f7 tr17: cs = 10 goto f9 f9: // line 38 "raw_scan.rl" ts = p goto _again f12: // line 52 "raw_scan.rl" te = p constraint.numCt++ if numIdx < len(constraint.nums) { constraint.nums[numIdx] = data[ts:p] numIdx++ } goto _again f8: // line 71 "raw_scan.rl" extra = data[p:] goto _again f1: // line 33 "raw_scan.rl" numIdx = 0 constraint = rawConstraint{} // line 38 "raw_scan.rl" ts = p goto _again f4: // line 42 "raw_scan.rl" te = p constraint.op = data[ts:p] // line 38 "raw_scan.rl" ts = p goto _again f7: // line 47 "raw_scan.rl" te = p constraint.sep = data[ts:p] // line 38 "raw_scan.rl" ts = p goto _again f6: // line 47 "raw_scan.rl" te = p constraint.sep = data[ts:p] // line 71 "raw_scan.rl" extra = data[p:] goto _again f11: // line 52 "raw_scan.rl" te = p constraint.numCt++ if numIdx < len(constraint.nums) { constraint.nums[numIdx] = data[ts:p] numIdx++ } // line 38 "raw_scan.rl" ts = p goto _again f10: // line 52 "raw_scan.rl" te = p constraint.numCt++ if numIdx < len(constraint.nums) { constraint.nums[numIdx] = data[ts:p] numIdx++ } // line 71 "raw_scan.rl" extra = data[p:] goto _again f15: // line 61 "raw_scan.rl" te = p constraint.pre = data[ts+1 : p] // line 38 "raw_scan.rl" ts = p goto _again f14: // line 61 "raw_scan.rl" te = p constraint.pre = data[ts+1 : p] // line 71 "raw_scan.rl" extra = data[p:] goto _again f13: // line 66 "raw_scan.rl" te = p constraint.meta = data[ts+1 : p] // line 71 "raw_scan.rl" extra = data[p:] goto _again f2: // line 33 "raw_scan.rl" numIdx = 0 constraint = rawConstraint{} // line 38 "raw_scan.rl" ts = p // line 42 "raw_scan.rl" te = p constraint.op = data[ts:p] goto _again f5: // line 42 "raw_scan.rl" te = p constraint.op = data[ts:p] // line 38 "raw_scan.rl" ts = p // line 47 "raw_scan.rl" te = p constraint.sep = data[ts:p] goto _again f0: // line 42 "raw_scan.rl" te = p constraint.op = data[ts:p] // line 47 "raw_scan.rl" te = p constraint.sep = data[ts:p] // line 71 "raw_scan.rl" extra = data[p:] goto _again f3: // line 33 "raw_scan.rl" numIdx = 0 constraint = rawConstraint{} // line 38 "raw_scan.rl" ts = p // line 42 "raw_scan.rl" te = p constraint.op = data[ts:p] // line 47 "raw_scan.rl" te = p constraint.sep = data[ts:p] goto _again _again: if cs == 0 { goto _out } if p++; p != pe { goto _resume } _test_eof: { } if p == eof { switch _scan_eof_actions[cs] { case 9: // line 71 "raw_scan.rl" extra = data[p:] case 7: // line 47 "raw_scan.rl" te = p constraint.sep = data[ts:p] // line 71 "raw_scan.rl" extra = data[p:] case 11: // line 52 "raw_scan.rl" te = p constraint.numCt++ if numIdx < len(constraint.nums) { constraint.nums[numIdx] = data[ts:p] numIdx++ } // line 71 "raw_scan.rl" extra = data[p:] case 15: // line 61 "raw_scan.rl" te = p constraint.pre = data[ts+1 : p] // line 71 "raw_scan.rl" extra = data[p:] case 14: // line 66 "raw_scan.rl" te = p constraint.meta = data[ts+1 : p] // line 71 "raw_scan.rl" extra = data[p:] case 1: // line 42 "raw_scan.rl" te = p constraint.op = data[ts:p] // line 47 "raw_scan.rl" te = p constraint.sep = data[ts:p] // line 71 "raw_scan.rl" extra = data[p:] // line 610 "raw_scan.go" } } _out: { } } // line 92 "raw_scan.rl" return constraint, extra } go-versions-1.0.1/versions/constraints/raw_scan.rl000066400000000000000000000035371374017526700223630ustar00rootroot00000000000000// This file is generated from raw_scan.rl. DO NOT EDIT. %%{ # (except you are actually in raw_scan.rl here, so edit away!) machine scan; }%% package constraints %%{ write data; }%% func scanConstraint(data string) (rawConstraint, string) { var constraint rawConstraint var numIdx int var extra string // Ragel state p := 0 // "Pointer" into data pe := len(data) // End-of-data "pointer" cs := 0 // constraint state (will be initialized by ragel-generated code) ts := 0 te := 0 eof := pe // Keep Go compiler happy even if generated code doesn't use these _ = ts _ = te _ = eof %%{ action enterConstraint { numIdx = 0 constraint = rawConstraint{} } action ts { ts = p } action finishOp { te = p constraint.op = data[ts:p] } action finishSep { te = p constraint.sep = data[ts:p] } action finishNum { te = p constraint.numCt++ if numIdx < len(constraint.nums) { constraint.nums[numIdx] = data[ts:p] numIdx++ } } action finishPre { te = p constraint.pre = data[ts+1:p] } action finishMeta { te = p constraint.meta = data[ts+1:p] } action finishExtra { extra = data[p:] } num = (digit+ | '*' | 'x' | 'X') >ts %finishNum %err(finishNum) %eof(finishNum); op = ((any - (digit | space | alpha | '.' | '*'))**) >ts %finishOp %err(finishOp) %eof(finishOp); likelyOp = ('^' | '>' | '<' | '-' | '~' | '!'); sep = (space**) >ts %finishSep %err(finishSep) %eof(finishSep); nums = (num ('.' num)*); extraStr = (alnum | '.' | '-')+; pre = ('-' extraStr) >ts %finishPre %err(finishPre) %eof(finishPre); meta = ('+' extraStr) >ts %finishMeta %err(finishMeta) %eof(finishMeta); constraint = (op sep nums pre? meta?) >enterConstraint; main := (constraint) @/finishExtra %/finishExtra $!finishExtra; write init; write exec; }%% return constraint, extra } go-versions-1.0.1/versions/constraints/raw_test.go000066400000000000000000000120051374017526700223740ustar00rootroot00000000000000package constraints import ( "testing" "github.com/kylelemons/godebug/pretty" ) func TestScanConstraints(t *testing.T) { tests := []struct { Input string Want rawConstraint WantRemain string }{ { "", rawConstraint{}, "", }, { "garbage", rawConstraint{}, "garbage", }, { "1.0.0", rawConstraint{ nums: [...]string{"1", "0", "0"}, numCt: 3, }, "", }, { "1.0", rawConstraint{ nums: [...]string{"1", "0", ""}, numCt: 2, }, "", }, { "1", rawConstraint{ nums: [...]string{"1", "", ""}, numCt: 1, }, "", }, { "10.0.0", rawConstraint{ nums: [...]string{"10", "0", "0"}, numCt: 3, }, "", }, { "10.0.0.0", rawConstraint{ nums: [...]string{"10", "0", "0"}, numCt: 4, }, "", }, { "*", rawConstraint{ nums: [...]string{"*", "", ""}, numCt: 1, }, "", }, { "*.*", rawConstraint{ nums: [...]string{"*", "*", ""}, numCt: 2, }, "", }, { "*.*.*", rawConstraint{ nums: [...]string{"*", "*", "*"}, numCt: 3, }, "", }, { "1.0.*", rawConstraint{ nums: [...]string{"1", "0", "*"}, numCt: 3, }, "", }, { "x", rawConstraint{ nums: [...]string{"x", "", ""}, numCt: 1, }, "", }, { "x.x", rawConstraint{ nums: [...]string{"x", "x", ""}, numCt: 2, }, "", }, { "x.x.x", rawConstraint{ nums: [...]string{"x", "x", "x"}, numCt: 3, }, "", }, { "1.0.x", rawConstraint{ nums: [...]string{"1", "0", "x"}, numCt: 3, }, "", }, { "1.0.0-beta1", rawConstraint{ nums: [...]string{"1", "0", "0"}, numCt: 3, pre: "beta1", }, "", }, { "1.0.0+abc123", rawConstraint{ nums: [...]string{"1", "0", "0"}, numCt: 3, meta: "abc123", }, "", }, { "1.0.0-beta1+abc123", rawConstraint{ nums: [...]string{"1", "0", "0"}, numCt: 3, pre: "beta1", meta: "abc123", }, "", }, { "1.0.0garbage", rawConstraint{ nums: [...]string{"1", "0", "0"}, numCt: 3, }, "garbage", }, // Rubygems-style operators { ">= 1.0.0", rawConstraint{ op: ">=", sep: " ", nums: [...]string{"1", "0", "0"}, numCt: 3, }, "", }, { "<= 1.0.0", rawConstraint{ op: "<=", sep: " ", nums: [...]string{"1", "0", "0"}, numCt: 3, }, "", }, { "> 1.0.0", rawConstraint{ op: ">", sep: " ", nums: [...]string{"1", "0", "0"}, numCt: 3, }, "", }, { "< 1.0.0", rawConstraint{ op: "<", sep: " ", nums: [...]string{"1", "0", "0"}, numCt: 3, }, "", }, { "= 1.0.0", rawConstraint{ op: "=", sep: " ", nums: [...]string{"1", "0", "0"}, numCt: 3, }, "", }, { "!= 1.0.0", rawConstraint{ op: "!=", sep: " ", nums: [...]string{"1", "0", "0"}, numCt: 3, }, "", }, { "~> 1.0.0", rawConstraint{ op: "~>", sep: " ", nums: [...]string{"1", "0", "0"}, numCt: 3, }, "", }, { // comma separated, as sometimes seen in ruby-ish tools "1.0.0, 2.0.0", rawConstraint{ op: "", sep: "", nums: [...]string{"1", "0", "0"}, numCt: 3, }, ", 2.0.0", }, // npm-style operators { "<1.0.0", rawConstraint{ op: "<", sep: "", nums: [...]string{"1", "0", "0"}, numCt: 3, }, "", }, { "<=1.0.0", rawConstraint{ op: "<=", sep: "", nums: [...]string{"1", "0", "0"}, numCt: 3, }, "", }, { ">1.0.0", rawConstraint{ op: ">", sep: "", nums: [...]string{"1", "0", "0"}, numCt: 3, }, "", }, { ">=1.0.0", rawConstraint{ op: ">=", sep: "", nums: [...]string{"1", "0", "0"}, numCt: 3, }, "", }, { "=1.0.0", rawConstraint{ op: "=", sep: "", nums: [...]string{"1", "0", "0"}, numCt: 3, }, "", }, { "~1.0.0", rawConstraint{ op: "~", sep: "", nums: [...]string{"1", "0", "0"}, numCt: 3, }, "", }, { "^1.0.0", rawConstraint{ op: "^", sep: "", nums: [...]string{"1", "0", "0"}, numCt: 3, }, "", }, { // npm-style range operator "1.0.0 - 2.0.0", rawConstraint{ op: "", sep: "", nums: [...]string{"1", "0", "0"}, numCt: 3, }, " - 2.0.0", }, { // npm-style "or" operator "1.0.0 || 2.0.0", rawConstraint{ op: "", sep: "", nums: [...]string{"1", "0", "0"}, numCt: 3, }, " || 2.0.0", }, } for _, test := range tests { t.Run(test.Input, func(t *testing.T) { got, remain := scanConstraint(test.Input) if remain != test.WantRemain { t.Errorf("wrong remain\ngot: %q\nwant: %q", remain, test.WantRemain) } if diff := pretty.Compare(test.Want, got); diff != "" { t.Errorf("wrong result\n%s", diff) } }) } } go-versions-1.0.1/versions/constraints/ruby_style.go000066400000000000000000000132301374017526700227460ustar00rootroot00000000000000package constraints import ( "fmt" "strings" ) // ParseRubyStyle parses a single selection constraint using a syntax similar // to that used by rubygems and other Ruby tools. // // Exact compatibility with rubygems is not guaranteed; "ruby-style" here // just means that users familiar with rubygems should find familiar the choice // of operators and their meanings. // // ParseRubyStyle parses only a single specification, mimicking the usual // rubygems approach of providing each selection as a separate string. // The result can be combined with other results to create an IntersectionSpec // that describes the effect of multiple such constraints. func ParseRubyStyle(str string) (SelectionSpec, error) { if strings.TrimSpace(str) == "" { return SelectionSpec{}, fmt.Errorf("empty specification") } spec, remain, err := parseRubyStyle(str) if err != nil { return spec, err } if remain != "" { remain = strings.TrimSpace(remain) switch { case remain == "": return spec, fmt.Errorf("extraneous spaces at end of specification") case strings.HasPrefix(remain, "v"): // User seems to be trying to use a "v" prefix, like "v1.0.0" return spec, fmt.Errorf(`a "v" prefix should not be used`) case strings.HasPrefix(remain, "||") || strings.HasPrefix(remain, ","): // User seems to be trying to specify multiple constraints return spec, fmt.Errorf(`only one constraint may be specified`) case strings.HasPrefix(remain, "-"): // User seems to be trying to use npm-style range constraints return spec, fmt.Errorf(`range constraints are not supported`) default: return spec, fmt.Errorf("invalid characters %q", remain) } } return spec, nil } // ParseRubyStyleAll is a helper wrapper around ParseRubyStyle that accepts // multiple selection strings and combines them together into a single // IntersectionSpec. func ParseRubyStyleAll(strs ...string) (IntersectionSpec, error) { spec := make(IntersectionSpec, 0, len(strs)) for _, str := range strs { subSpec, err := ParseRubyStyle(str) if err != nil { return nil, fmt.Errorf("invalid specification %q: %s", str, err) } spec = append(spec, subSpec) } return spec, nil } // ParseRubyStyleMulti is similar to ParseRubyStyle, but rather than parsing // only a single selection specification it instead expects one or more // comma-separated specifications, returning the result as an // IntersectionSpec. func ParseRubyStyleMulti(str string) (IntersectionSpec, error) { var spec IntersectionSpec remain := strings.TrimSpace(str) for remain != "" { if strings.TrimSpace(remain) == "" { break } var subSpec SelectionSpec var err error var newRemain string subSpec, newRemain, err = parseRubyStyle(remain) consumed := remain[:len(remain)-len(newRemain)] if err != nil { return nil, fmt.Errorf("invalid specification %q: %s", consumed, err) } remain = strings.TrimSpace(newRemain) if remain != "" { if strings.HasPrefix(remain, "v") { return nil, fmt.Errorf(`a "v" prefix should not be used`) } if !strings.HasPrefix(remain, ",") { return nil, fmt.Errorf("missing comma after %q", consumed) } // Eat the separator comma remain = strings.TrimSpace(remain[1:]) } spec = append(spec, subSpec) } return spec, nil } // parseRubyStyle parses a ruby-style constraint from the prefix of the given // string and returns the remaining unconsumed string for the caller to use // for further processing. func parseRubyStyle(str string) (SelectionSpec, string, error) { raw, remain := scanConstraint(str) var spec SelectionSpec switch raw.op { case "=", "": spec.Operator = OpEqual case "!=": spec.Operator = OpNotEqual case ">": spec.Operator = OpGreaterThan case ">=": spec.Operator = OpGreaterThanOrEqual case "<": spec.Operator = OpLessThan case "<=": spec.Operator = OpLessThanOrEqual case "~>": // Ruby-style pessimistic can be either a minor-only or patch-only // constraint, depending on how many digits were given. switch raw.numCt { case 3: spec.Operator = OpGreaterThanOrEqualPatchOnly default: spec.Operator = OpGreaterThanOrEqualMinorOnly } case "=<": return spec, remain, fmt.Errorf("invalid constraint operator %q; did you mean \"<=\"?", raw.op) case "=>": return spec, remain, fmt.Errorf("invalid constraint operator %q; did you mean \">=\"?", raw.op) default: return spec, remain, fmt.Errorf("invalid constraint operator %q", raw.op) } switch raw.sep { case "": // No separator is always okay. Although all of the examples in the // rubygems docs show a space separator, the parser doesn't actually // require it. case " ": if raw.op == "" { return spec, remain, fmt.Errorf("extraneous spaces at start of specification") } default: if raw.op == "" { return spec, remain, fmt.Errorf("extraneous spaces at start of specification") } else { return spec, remain, fmt.Errorf("only one space is expected after the operator %q", raw.op) } } if raw.numCt > 3 { return spec, remain, fmt.Errorf("too many numbered portions; only three are allowed (major, minor, patch)") } // Ruby-style doesn't use explicit wildcards for i, s := range raw.nums { switch { case isWildcardNum(s): // Can't use wildcards in an exact specification return spec, remain, fmt.Errorf("can't use wildcard for %s number; omit segments that should be unconstrained", rawNumNames[i]) } } if raw.pre != "" || raw.meta != "" { // If either the prerelease or meta portions are set then any unconstrained // segments are implied to be zero in order to guarantee constraint // consistency. for i, s := range raw.nums { if s == "" { raw.nums[i] = "0" } } } spec.Boundary = raw.VersionSpec() return spec, remain, nil } go-versions-1.0.1/versions/constraints/ruby_style_test.go000066400000000000000000000202211374017526700240030ustar00rootroot00000000000000package constraints import ( "testing" "github.com/go-test/deep" ) func TestParseRubyStyle(t *testing.T) { tests := []struct { Input string Want SelectionSpec WantErr string }{ { "", SelectionSpec{}, "empty specification", }, { "1", SelectionSpec{ Operator: OpEqual, Boundary: VersionSpec{ Major: NumConstraint{Num: 1}, Minor: NumConstraint{Unconstrained: true}, Patch: NumConstraint{Unconstrained: true}, }, }, "", }, { "1.1", SelectionSpec{ Operator: OpEqual, Boundary: VersionSpec{ Major: NumConstraint{Num: 1}, Minor: NumConstraint{Num: 1}, Patch: NumConstraint{Unconstrained: true}, }, }, "", }, { "1.1.1", SelectionSpec{ Operator: OpEqual, Boundary: VersionSpec{ Major: NumConstraint{Num: 1}, Minor: NumConstraint{Num: 1}, Patch: NumConstraint{Num: 1}, }, }, "", }, { "1.0.0.0", SelectionSpec{}, "too many numbered portions; only three are allowed (major, minor, patch)", }, { "v1.0.0", SelectionSpec{}, `a "v" prefix should not be used`, }, { "1.0.0-beta2", SelectionSpec{ Operator: OpEqual, Boundary: VersionSpec{ Major: NumConstraint{Num: 1}, Minor: NumConstraint{Num: 0}, Patch: NumConstraint{Num: 0}, Prerelease: "beta2", }, }, "", }, { "1.0-beta2", SelectionSpec{ Operator: OpEqual, Boundary: VersionSpec{ Major: NumConstraint{Num: 1}, Minor: NumConstraint{Num: 0}, Patch: NumConstraint{Num: 0}, // implied by the prerelease tag to ensure constraint consistency Prerelease: "beta2", }, }, "", }, { "1.0.0-beta.2", SelectionSpec{ Operator: OpEqual, Boundary: VersionSpec{ Major: NumConstraint{Num: 1}, Minor: NumConstraint{Num: 0}, Patch: NumConstraint{Num: 0}, Prerelease: "beta.2", }, }, "", }, { "1.0.0+foo", SelectionSpec{ Operator: OpEqual, Boundary: VersionSpec{ Major: NumConstraint{Num: 1}, Minor: NumConstraint{Num: 0}, Patch: NumConstraint{Num: 0}, Metadata: "foo", }, }, "", }, { "1.0.0+foo.bar", SelectionSpec{ Operator: OpEqual, Boundary: VersionSpec{ Major: NumConstraint{Num: 1}, Minor: NumConstraint{Num: 0}, Patch: NumConstraint{Num: 0}, Metadata: "foo.bar", }, }, "", }, { "1.0.0-beta1+foo.bar", SelectionSpec{ Operator: OpEqual, Boundary: VersionSpec{ Major: NumConstraint{Num: 1}, Minor: NumConstraint{Num: 0}, Patch: NumConstraint{Num: 0}, Prerelease: "beta1", Metadata: "foo.bar", }, }, "", }, { "> 1.1.1", SelectionSpec{ Operator: OpGreaterThan, Boundary: VersionSpec{ Major: NumConstraint{Num: 1}, Minor: NumConstraint{Num: 1}, Patch: NumConstraint{Num: 1}, }, }, ``, }, { ">1.1.1", SelectionSpec{ Operator: OpGreaterThan, Boundary: VersionSpec{ Major: NumConstraint{Num: 1}, Minor: NumConstraint{Num: 1}, Patch: NumConstraint{Num: 1}, }, }, ``, }, { ">= 1.1.1", SelectionSpec{ Operator: OpGreaterThanOrEqual, Boundary: VersionSpec{ Major: NumConstraint{Num: 1}, Minor: NumConstraint{Num: 1}, Patch: NumConstraint{Num: 1}, }, }, ``, }, { ">=1.1.1", SelectionSpec{ Operator: OpGreaterThanOrEqual, Boundary: VersionSpec{ Major: NumConstraint{Num: 1}, Minor: NumConstraint{Num: 1}, Patch: NumConstraint{Num: 1}, }, }, ``, }, { "=> 1.1.1", SelectionSpec{}, `invalid constraint operator "=>"; did you mean ">="?`, }, { "=>1.1.1", SelectionSpec{}, `invalid constraint operator "=>"; did you mean ">="?`, }, { "< 1.1.1", SelectionSpec{ Operator: OpLessThan, Boundary: VersionSpec{ Major: NumConstraint{Num: 1}, Minor: NumConstraint{Num: 1}, Patch: NumConstraint{Num: 1}, }, }, ``, }, { "<= 1.1.1", SelectionSpec{ Operator: OpLessThanOrEqual, Boundary: VersionSpec{ Major: NumConstraint{Num: 1}, Minor: NumConstraint{Num: 1}, Patch: NumConstraint{Num: 1}, }, }, ``, }, { "=< 1.1.1", SelectionSpec{}, `invalid constraint operator "=<"; did you mean "<="?`, }, { "~> 1.1.1", SelectionSpec{ Operator: OpGreaterThanOrEqualPatchOnly, Boundary: VersionSpec{ Major: NumConstraint{Num: 1}, Minor: NumConstraint{Num: 1}, Patch: NumConstraint{Num: 1}, }, }, ``, }, { "~> 1.1", SelectionSpec{ Operator: OpGreaterThanOrEqualMinorOnly, Boundary: VersionSpec{ Major: NumConstraint{Num: 1}, Minor: NumConstraint{Num: 1}, Patch: NumConstraint{Unconstrained: true}, }, }, ``, }, { "~> 1", SelectionSpec{ Operator: OpGreaterThanOrEqualMinorOnly, Boundary: VersionSpec{ Major: NumConstraint{Num: 1}, Minor: NumConstraint{Unconstrained: true}, Patch: NumConstraint{Unconstrained: true}, }, }, ``, }, { "= 1.1.1", SelectionSpec{ Operator: OpEqual, Boundary: VersionSpec{ Major: NumConstraint{Num: 1}, Minor: NumConstraint{Num: 1}, Patch: NumConstraint{Num: 1}, }, }, ``, }, { "!= 1.1.1", SelectionSpec{ Operator: OpNotEqual, Boundary: VersionSpec{ Major: NumConstraint{Num: 1}, Minor: NumConstraint{Num: 1}, Patch: NumConstraint{Num: 1}, }, }, ``, }, { "= 1.1.1", SelectionSpec{}, `only one space is expected after the operator "="`, }, { "garbage", SelectionSpec{}, `invalid characters "garbage"`, }, { "& 1.1.0", SelectionSpec{}, `invalid constraint operator "&"`, }, { "1.*.*", SelectionSpec{}, `can't use wildcard for minor number; omit segments that should be unconstrained`, }, { "1.0.x", SelectionSpec{}, `can't use wildcard for patch number; omit segments that should be unconstrained`, }, { "1.0 || 2.0", SelectionSpec{}, `only one constraint may be specified`, }, { "1.0.0, 2.0.0", SelectionSpec{}, `only one constraint may be specified`, }, { "1.0.0 - 2.0.0", SelectionSpec{}, `range constraints are not supported`, }, } for _, test := range tests { t.Run(test.Input, func(t *testing.T) { got, err := ParseRubyStyle(test.Input) var gotErr string if err != nil { gotErr = err.Error() } if gotErr != test.WantErr { t.Errorf("wrong error\ngot: %s\nwant: %s", gotErr, test.WantErr) return } if err != nil { return } for _, problem := range deep.Equal(got, test.Want) { t.Error(problem) } }) } } func TestParseRubyStyleMulti(t *testing.T) { tests := []struct { Input string Want IntersectionSpec WantErr string }{ { "", nil, "", }, { "1.1.1", IntersectionSpec{ SelectionSpec{ Operator: OpEqual, Boundary: VersionSpec{ Major: NumConstraint{Num: 1}, Minor: NumConstraint{Num: 1}, Patch: NumConstraint{Num: 1}, }, }, }, "", }, { "~> v1.1.1", nil, `a "v" prefix should not be used`, }, { ">= 1.0, < 2", IntersectionSpec{ SelectionSpec{ Operator: OpGreaterThanOrEqual, Boundary: VersionSpec{ Major: NumConstraint{Num: 1}, Minor: NumConstraint{Num: 0}, Patch: NumConstraint{Unconstrained: true}, }, }, SelectionSpec{ Operator: OpLessThan, Boundary: VersionSpec{ Major: NumConstraint{Num: 2}, Minor: NumConstraint{Unconstrained: true}, Patch: NumConstraint{Unconstrained: true}, }, }, }, "", }, { ">= 1.0 < 2", nil, `missing comma after ">= 1.0"`, }, } for _, test := range tests { t.Run(test.Input, func(t *testing.T) { got, err := ParseRubyStyleMulti(test.Input) var gotErr string if err != nil { gotErr = err.Error() } if gotErr != test.WantErr { t.Errorf("wrong error\ngot: %s\nwant: %s", gotErr, test.WantErr) return } if err != nil { return } for _, problem := range deep.Equal(got, test.Want) { t.Error(problem) } }) } } go-versions-1.0.1/versions/constraints/selectionop_string.go000066400000000000000000000021731374017526700244630ustar00rootroot00000000000000// Code generated by "stringer -type SelectionOp"; DO NOT EDIT. package constraints import "strconv" const ( _SelectionOp_name_0 = "OpUnconstrained" _SelectionOp_name_1 = "OpMatch" _SelectionOp_name_2 = "OpLessThanOpEqualOpGreaterThan" _SelectionOp_name_3 = "OpGreaterThanOrEqualMinorOnly" _SelectionOp_name_4 = "OpGreaterThanOrEqualPatchOnly" _SelectionOp_name_5 = "OpNotEqual" _SelectionOp_name_6 = "OpLessThanOrEqualOpGreaterThanOrEqual" ) var ( _SelectionOp_index_2 = [...]uint8{0, 10, 17, 30} _SelectionOp_index_6 = [...]uint8{0, 17, 37} ) func (i SelectionOp) String() string { switch { case i == 0: return _SelectionOp_name_0 case i == 42: return _SelectionOp_name_1 case 60 <= i && i <= 62: i -= 60 return _SelectionOp_name_2[_SelectionOp_index_2[i]:_SelectionOp_index_2[i+1]] case i == 94: return _SelectionOp_name_3 case i == 126: return _SelectionOp_name_4 case i == 8800: return _SelectionOp_name_5 case 8804 <= i && i <= 8805: i -= 8804 return _SelectionOp_name_6[_SelectionOp_index_6[i]:_SelectionOp_index_6[i+1]] default: return "SelectionOp(" + strconv.FormatInt(int64(i), 10) + ")" } } go-versions-1.0.1/versions/constraints/spec.go000066400000000000000000000161151374017526700215040ustar00rootroot00000000000000package constraints import ( "bytes" "fmt" "strconv" ) // Spec is an interface type that UnionSpec, IntersectionSpec, SelectionSpec, // and VersionSpec all belong to. // // It's provided to allow generic code to be written that accepts and operates // on all specs, but such code must still handle each type separately using // e.g. a type switch. This is a closed type that will not have any new // implementations added in future. type Spec interface { isSpec() } // UnionSpec represents an "or" operation on nested version constraints. // // This is not directly representable in all of our supported constraint // syntaxes. type UnionSpec []IntersectionSpec func (s UnionSpec) isSpec() {} // IntersectionSpec represents an "and" operation on nested version constraints. type IntersectionSpec []SelectionSpec func (s IntersectionSpec) isSpec() {} // SelectionSpec represents applying a single operator to a particular // "boundary" version. type SelectionSpec struct { Boundary VersionSpec Operator SelectionOp } func (s SelectionSpec) isSpec() {} // VersionSpec represents the boundary within a SelectionSpec. type VersionSpec struct { Major NumConstraint Minor NumConstraint Patch NumConstraint Prerelease string Metadata string } func (s VersionSpec) isSpec() {} // IsExact returns bool if all of the version numbers in the receiver are // fully-constrained. This is the same as s.ConstraintDepth() == ConstrainedPatch func (s VersionSpec) IsExact() bool { return s.ConstraintDepth() == ConstrainedPatch } // ConstraintDepth returns the constraint depth of the receiver, which is // the most specifc version number segment that is exactly constrained. // // The constraints must be consistent, which means that if a given segment // is unconstrained then all of the deeper segments must also be unconstrained. // If not, this method will panic. Version specs produced by the parsers in // this package are guaranteed to be consistent. func (s VersionSpec) ConstraintDepth() ConstraintDepth { if s == (VersionSpec{}) { // zero value is a degenerate case meaning completely unconstrained return Unconstrained } switch { case s.Major.Unconstrained: if !(s.Minor.Unconstrained && s.Patch.Unconstrained && s.Prerelease == "" && s.Metadata == "") { panic("inconsistent constraint depth") } return Unconstrained case s.Minor.Unconstrained: if !(s.Patch.Unconstrained && s.Prerelease == "" && s.Metadata == "") { panic("inconsistent constraint depth") } return ConstrainedMajor case s.Patch.Unconstrained: if s.Prerelease != "" || s.Metadata != "" { panic(fmt.Errorf("inconsistent constraint depth: wildcard major, minor and patch followed by prerelease %q and metadata %q", s.Prerelease, s.Metadata)) } return ConstrainedMinor default: return ConstrainedPatch } } // ConstraintBounds returns two exact VersionSpecs that represent the upper // and lower bounds of the possibly-inexact receiver. If the receiver // is already exact then the two bounds are identical and have operator // OpEqual. If they are different then the lower bound is OpGreaterThanOrEqual // and the upper bound is OpLessThan. // // As a special case, if the version spec is entirely unconstrained the // two bounds will be identical and the zero value of SelectionSpec. For // consistency, this result is also returned if the receiver is already // the zero value of VersionSpec, since a zero spec represents a lack of // constraint. // // The constraints must be consistent as defined by ConstraintDepth, or this // method will panic. func (s VersionSpec) ConstraintBounds() (SelectionSpec, SelectionSpec) { switch s.ConstraintDepth() { case Unconstrained: return SelectionSpec{}, SelectionSpec{} case ConstrainedMajor: lowerBound := s.ConstrainToZero() lowerBound.Metadata = "" upperBound := lowerBound upperBound.Major.Num++ upperBound.Minor.Num = 0 upperBound.Patch.Num = 0 upperBound.Prerelease = "" upperBound.Metadata = "" return SelectionSpec{ Operator: OpGreaterThanOrEqual, Boundary: lowerBound, }, SelectionSpec{ Operator: OpLessThan, Boundary: upperBound, } case ConstrainedMinor: lowerBound := s.ConstrainToZero() lowerBound.Metadata = "" upperBound := lowerBound upperBound.Minor.Num++ upperBound.Patch.Num = 0 upperBound.Metadata = "" return SelectionSpec{ Operator: OpGreaterThanOrEqual, Boundary: lowerBound, }, SelectionSpec{ Operator: OpLessThan, Boundary: upperBound, } default: eq := SelectionSpec{ Operator: OpEqual, Boundary: s, } return eq, eq } } // ConstrainToZero returns a copy of the receiver with all of its // unconstrained numeric segments constrained to zero. func (s VersionSpec) ConstrainToZero() VersionSpec { switch s.ConstraintDepth() { case Unconstrained: s.Major = NumConstraint{Num: 0} s.Minor = NumConstraint{Num: 0} s.Patch = NumConstraint{Num: 0} s.Prerelease = "" s.Metadata = "" case ConstrainedMajor: s.Minor = NumConstraint{Num: 0} s.Patch = NumConstraint{Num: 0} s.Prerelease = "" s.Metadata = "" case ConstrainedMinor: s.Patch = NumConstraint{Num: 0} s.Prerelease = "" s.Metadata = "" } return s } // ConstrainToUpperBound returns a copy of the receiver with all of its // unconstrained numeric segments constrained to zero and its last // constrained segment increased by one. // // This operation is not meaningful for an entirely unconstrained VersionSpec, // so will return the zero value of the type in that case. func (s VersionSpec) ConstrainToUpperBound() VersionSpec { switch s.ConstraintDepth() { case Unconstrained: return VersionSpec{} case ConstrainedMajor: s.Major.Num++ s.Minor = NumConstraint{Num: 0} s.Patch = NumConstraint{Num: 0} s.Prerelease = "" s.Metadata = "" case ConstrainedMinor: s.Minor.Num++ s.Patch = NumConstraint{Num: 0} s.Prerelease = "" s.Metadata = "" } return s } func (s VersionSpec) String() string { var buf bytes.Buffer fmt.Fprintf(&buf, "%s.%s.%s", s.Major, s.Minor, s.Patch) if s.Prerelease != "" { fmt.Fprintf(&buf, "-%s", s.Prerelease) } if s.Metadata != "" { fmt.Fprintf(&buf, "+%s", s.Metadata) } return buf.String() } type SelectionOp rune //go:generate stringer -type SelectionOp const ( OpUnconstrained SelectionOp = 0 OpGreaterThan SelectionOp = '>' OpLessThan SelectionOp = '<' OpGreaterThanOrEqual SelectionOp = '≥' OpGreaterThanOrEqualPatchOnly SelectionOp = '~' OpGreaterThanOrEqualMinorOnly SelectionOp = '^' OpLessThanOrEqual SelectionOp = '≤' OpEqual SelectionOp = '=' OpNotEqual SelectionOp = '≠' OpMatch SelectionOp = '*' ) type NumConstraint struct { Num uint64 Unconstrained bool } func (c NumConstraint) String() string { if c.Unconstrained { return "*" } else { return strconv.FormatUint(c.Num, 10) } } type ConstraintDepth int //go:generate stringer -type ConstraintDepth const ( Unconstrained ConstraintDepth = 0 ConstrainedMajor ConstraintDepth = 1 ConstrainedMinor ConstraintDepth = 2 ConstrainedPatch ConstraintDepth = 3 ) go-versions-1.0.1/versions/constraints/version.go000066400000000000000000000055601374017526700222410ustar00rootroot00000000000000package constraints import ( "fmt" "strings" ) // ParseExactVersion parses a string that must contain the specification of a // single, exact version, and then returns it as a VersionSpec. // // This is primarily here to allow versions.ParseVersion to re-use the // constraint grammar, and isn't very useful for direct use from calling // applications. func ParseExactVersion(vs string) (VersionSpec, error) { spec := VersionSpec{} if strings.TrimSpace(vs) == "" { return spec, fmt.Errorf("empty specification") } raw, remain := scanConstraint(vs) switch strings.TrimSpace(raw.op) { case ">", ">=", "<", "<=", "!", "!=", "~>", "^", "~": // If it looks like the user was trying to write a constraint string // then we'll help them out with a more specialized error. return spec, fmt.Errorf("can't use constraint operator %q; an exact version is required", raw.op) case "": // Empty operator is okay as long as we don't also have separator spaces. // (Caller can trim off spaces beforehand if they want to tolerate this.) if raw.sep != "" { return spec, fmt.Errorf("extraneous spaces at start of specification") } default: return spec, fmt.Errorf("invalid sequence %q at start of specification", raw.op) } if remain != "" { remain = strings.TrimSpace(remain) switch { case remain == "": return spec, fmt.Errorf("extraneous spaces at end of specification") case strings.HasPrefix(vs, "v"): // User seems to be trying to use a "v" prefix, like "v1.0.0" return spec, fmt.Errorf(`a "v" prefix should not be used`) case strings.HasPrefix(remain, ",") || strings.HasPrefix(remain, "|"): // User seems to be trying to list/combine multiple versions return spec, fmt.Errorf("can't specify multiple versions; a single exact version is required") case strings.HasPrefix(remain, "-"): // User seems to be trying to use the npm-style range operator return spec, fmt.Errorf("can't specify version range; a single exact version is required") case strings.HasPrefix(strings.TrimSpace(vs), remain): // Whole string is invalid, then. return spec, fmt.Errorf("invalid specification; required format is three positive integers separated by periods") default: return spec, fmt.Errorf("invalid characters %q", remain) } } if raw.numCt > 3 { return spec, fmt.Errorf("too many numbered portions; only three are allowed (major, minor, patch)") } for i := raw.numCt; i < len(raw.nums); i++ { raw.nums[i] = "0" } for i, s := range raw.nums { switch { case isWildcardNum(s): // Can't use wildcards in an exact specification return spec, fmt.Errorf("can't use wildcard for %s number; an exact version is required", rawNumNames[i]) } } // Since we eliminated all of the unconstrained cases above, either by normalizing // or returning an error, we are guaranteed to get constrained numbers here. spec = raw.VersionSpec() return spec, nil } go-versions-1.0.1/versions/constraints/version_test.go000066400000000000000000000066061374017526700233020ustar00rootroot00000000000000package constraints import ( "testing" "github.com/go-test/deep" ) func TestParseExactVersion(t *testing.T) { tests := []struct { Input string Want VersionSpec WantErr string }{ { "", VersionSpec{}, "empty specification", }, { "1", VersionSpec{ Major: NumConstraint{Num: 1}, Minor: NumConstraint{Num: 0}, Patch: NumConstraint{Num: 0}, }, "", }, { "1.1", VersionSpec{ Major: NumConstraint{Num: 1}, Minor: NumConstraint{Num: 1}, Patch: NumConstraint{Num: 0}, }, "", }, { "1.1.1", VersionSpec{ Major: NumConstraint{Num: 1}, Minor: NumConstraint{Num: 1}, Patch: NumConstraint{Num: 1}, }, "", }, { "1.0.0.0", VersionSpec{}, "too many numbered portions; only three are allowed (major, minor, patch)", }, { "v1.0.0", VersionSpec{}, `a "v" prefix should not be used`, }, { "1.0.0-beta2", VersionSpec{ Major: NumConstraint{Num: 1}, Minor: NumConstraint{Num: 0}, Patch: NumConstraint{Num: 0}, Prerelease: "beta2", }, "", }, { "1.0-beta2", VersionSpec{ Major: NumConstraint{Num: 1}, Minor: NumConstraint{Num: 0}, Patch: NumConstraint{Num: 0}, Prerelease: "beta2", }, "", }, { "1.0.0-beta.2", VersionSpec{ Major: NumConstraint{Num: 1}, Minor: NumConstraint{Num: 0}, Patch: NumConstraint{Num: 0}, Prerelease: "beta.2", }, "", }, { "1.0.0+foo", VersionSpec{ Major: NumConstraint{Num: 1}, Minor: NumConstraint{Num: 0}, Patch: NumConstraint{Num: 0}, Metadata: "foo", }, "", }, { "1.0.0+foo.bar", VersionSpec{ Major: NumConstraint{Num: 1}, Minor: NumConstraint{Num: 0}, Patch: NumConstraint{Num: 0}, Metadata: "foo.bar", }, "", }, { "1.0.0-beta1+foo.bar", VersionSpec{ Major: NumConstraint{Num: 1}, Minor: NumConstraint{Num: 0}, Patch: NumConstraint{Num: 0}, Prerelease: "beta1", Metadata: "foo.bar", }, "", }, { "> 1.1.1", VersionSpec{}, `can't use constraint operator ">"; an exact version is required`, }, { "garbage", VersionSpec{}, `invalid specification; required format is three positive integers separated by periods`, }, { "& 1.1.0", VersionSpec{}, `invalid sequence "&" at start of specification`, }, { "1.*.*", VersionSpec{}, `can't use wildcard for minor number; an exact version is required`, }, { "1.0.x", VersionSpec{}, `can't use wildcard for patch number; an exact version is required`, }, { "1.0 || 2.0", VersionSpec{}, `can't specify multiple versions; a single exact version is required`, }, { "1.0.0, 2.0.0", VersionSpec{}, `can't specify multiple versions; a single exact version is required`, }, { "1.0.0 - 2.0.0", VersionSpec{}, `can't specify version range; a single exact version is required`, }, } for _, test := range tests { t.Run(test.Input, func(t *testing.T) { got, err := ParseExactVersion(test.Input) var gotErr string if err != nil { gotErr = err.Error() } if gotErr != test.WantErr { t.Errorf("wrong error\ngot: %s\nwant: %s", gotErr, test.WantErr) return } if err != nil { return } for _, problem := range deep.Equal(got, test.Want) { t.Error(problem) } }) } } go-versions-1.0.1/versions/doc.go000066400000000000000000000014541374017526700167500ustar00rootroot00000000000000// Package versions is a library for wrangling version numbers in Go. // // There are many libraries offering some or all of this functionality. // This package aims to distinguish itself by offering a more convenient and // ergonomic API than seen in some other libraries. Code that is resolving // versions and version constraints tends to be hairy and complex already, so // an expressive API for talking about these concepts will hopefully help to // make that code more readable. // // The version model is based on Semantic Versioning as defined at // https://semver.org/ . Semantic Versioning does not include any specification // for constraints, so the constraint model is based on that used by rubygems, // allowing for upper and lower bounds as well as individual version exclusions. package versions go-versions-1.0.1/versions/list.go000066400000000000000000000101411374017526700171470ustar00rootroot00000000000000package versions import ( "sort" ) // List is a slice of Version that implements sort.Interface, and also includes // some other helper functions. type List []Version // Filter removes from the receiver any elements that are not in the given // set, moving retained elements to lower indices to close any gaps and // modifying the underlying array in-place. The return value is a slice // describing the new bounds within the existing backing array. The relative // ordering of the retained elements is preserved. // // The result must always be either the same length or shorter than the // initial value, so no allocation is required. // // As a special case, if the result would be a slice of length zero then a // nil slice is returned instead, leaving the backing array untouched. func (l List) Filter(set Set) List { writeI := 0 for readI := range l { if set.Has(l[readI]) { l[writeI] = l[readI] writeI++ } } if writeI == 0 { return nil } return l[:writeI:len(l)] } // Newest returns the newest version in the list, or Unspecified if the list // is empty. // // Since build metadata does not participate in precedence, it is possible // that a given list may have multiple equally-new versions; in that case // Newest will return an arbitrary version from that subset. func (l List) Newest() Version { ret := Unspecified for i := len(l) - 1; i >= 0; i-- { if l[i].GreaterThan(ret) { ret = l[i] } } return ret } // NewestInSet is like Filter followed by Newest, except that it does not // modify the underlying array. This is convenient for the common case of // selecting the newest version from a set derived from a user-supplied // constraint. // // Similar to Newest, the result is Unspecified if the list is empty or if // none of the items are in the given set. Also similar to newest, if there // are multiple newest versions (possibly differentiated only by metadata) // then one is arbitrarily chosen. func (l List) NewestInSet(set Set) Version { ret := Unspecified for i := len(l) - 1; i >= 0; i-- { if l[i].GreaterThan(ret) && set.Has(l[i]) { ret = l[i] } } return ret } // NewestList returns a List containing all of the list items that have the // highest precedence. // // For an already-sorted list, the returned slice is a sub-slice of the // receiver, sharing the same backing array. For an unsorted list, a new // array is allocated for the result. For an empty list, the result is always // nil. // // Relative ordering of elements in the receiver is preserved in the output. func (l List) NewestList() List { if len(l) == 0 { return nil } if l.IsSorted() { // This is a happy path since we can just count off items from the // end of our existing list until we find one that is not the same // as the last. var i int n := len(l) for i = n - 1; i >= 0; i-- { if !l[i].Same(l[n-1]) { break } } if i < 0 { i = 0 } return l[i:] } // For an unsorted list we'll allocate so that we can construct a new, // filtered slice. ret := make(List, 0, 1) // one item is the common case, in the absense of build metadata example := l.Newest() for _, v := range l { if v.Same(example) { ret = append(ret, v) } } return ret } // Set returns a finite Set containing the versions in the receiver. // // Although it is possible to recover a list from the return value using // its List method, the result may be in a different order and will have // any duplicate elements from the receiving list consolidated. func (l List) Set() Set { return Selection(l...) } func (l List) Len() int { return len(l) } func (l List) Less(i, j int) bool { return l[i].LessThan(l[j]) } func (l List) Swap(i, j int) { l[i], l[j] = l[j], l[i] } // Sort applies an in-place sort on the list, preserving the relative order of // any elements that differ only in build metadata. Earlier versions sort // first, so the newest versions will be at the highest indices in the list // once this method returns. func (l List) Sort() { sort.Stable(l) } // IsSorted returns true if the list is already in ascending order by // version priority. func (l List) IsSorted() bool { return sort.IsSorted(l) } go-versions-1.0.1/versions/list_test.go000066400000000000000000000001071374017526700202070ustar00rootroot00000000000000package versions import ( "sort" ) var _ sort.Interface = List(nil) go-versions-1.0.1/versions/parse.go000066400000000000000000000202711374017526700173130ustar00rootroot00000000000000package versions import ( "fmt" "github.com/apparentlymart/go-versions/versions/constraints" ) // ParseVersion attempts to parse the given string as a semantic version // specification, and returns the result if successful. // // If the given string is not parseable then an error is returned that is // suitable for display directly to a hypothetical end-user that provided this // version string, as long as they can read English. func ParseVersion(s string) (Version, error) { spec, err := constraints.ParseExactVersion(s) if err != nil { return Unspecified, err } return versionFromExactVersionSpec(spec), nil } // MustParseVersion is the same as ParseVersion except that it will panic // instead of returning an error. func MustParseVersion(s string) Version { v, err := ParseVersion(s) if err != nil { panic(err) } return v } // MeetingConstraints returns a version set that contains all of the versions // that meet the given constraints, specified using the Spec type from the // constraints package. // // The resulting Set has all pre-release versions excluded, except any that // are explicitly mentioned as exact selections. For example, the constraint // "2.0.0-beta1 || >2" contains 2.0.0-beta1 but not 2.0.0-beta2 or 3.0.0-beta1. // This additional constraint on pre-releases can be avoided by calling // MeetingConstraintsExact instead, at which point the caller can apply other // logic to deal with prereleases. // // This function expects an internally-consistent Spec like what would be // generated by that package's constraint parsers. Behavior is undefined -- // including the possibility of panics -- if specs are hand-created and the // expected invariants aren't met. func MeetingConstraints(spec constraints.Spec) Set { exact := MeetingConstraintsExact(spec) reqd := exact.AllRequested().List() set := Intersection(Released, exact) reqd = reqd.Filter(Prerelease) if len(reqd) != 0 { set = Union(Selection(reqd...), set) } return set } // MeetingConstraintsExact is like MeetingConstraints except that it doesn't // apply the extra rules to exclude pre-release versions that are not // explicitly requested. // // This means that given a constraint ">=1.0.0 <2.0.0" a hypothetical version // 2.0.0-beta1 _is_ in the returned set, because prerelease versions have // lower precedence than their corresponding release. // // A caller can use this to implement its own specialized handling of // pre-release versions by applying additional set operations to the result, // such as intersecting it with the predefined set versions.Released to // remove prerelease versions altogether. func MeetingConstraintsExact(spec constraints.Spec) Set { if spec == nil { return All } switch ts := spec.(type) { case constraints.VersionSpec: lowerBound, upperBound := ts.ConstraintBounds() switch lowerBound.Operator { case constraints.OpUnconstrained: return All case constraints.OpEqual: return Only(versionFromExactVersionSpec(lowerBound.Boundary)) default: return AtLeast( versionFromExactVersionSpec(lowerBound.Boundary), ).Intersection( OlderThan(versionFromExactVersionSpec(upperBound.Boundary))) } case constraints.SelectionSpec: lower := ts.Boundary.ConstrainToZero() if ts.Operator != constraints.OpEqual && ts.Operator != constraints.OpNotEqual { lower.Metadata = "" // metadata is only considered for exact matches } switch ts.Operator { case constraints.OpUnconstrained: // Degenerate case, but we'll allow it. return All case constraints.OpMatch: // The match operator uses the constraints implied by the // Boundary version spec as the specification. // Note that we discard "lower" in this case, because we do want // to match our metadata if it's specified. return MeetingConstraintsExact(ts.Boundary) case constraints.OpEqual, constraints.OpNotEqual: set := Only(versionFromExactVersionSpec(lower)) if ts.Operator == constraints.OpNotEqual { // We want everything _except_ what's in our set, then. set = All.Subtract(set) } return set case constraints.OpGreaterThan: return NewerThan(versionFromExactVersionSpec(lower)) case constraints.OpGreaterThanOrEqual: return AtLeast(versionFromExactVersionSpec(lower)) case constraints.OpLessThan: return OlderThan(versionFromExactVersionSpec(lower)) case constraints.OpLessThanOrEqual: return AtMost(versionFromExactVersionSpec(lower)) case constraints.OpGreaterThanOrEqualMinorOnly: upper := lower upper.Major.Num++ upper.Minor.Num = 0 upper.Patch.Num = 0 upper.Prerelease = "" return AtLeast( versionFromExactVersionSpec(lower), ).Intersection( OlderThan(versionFromExactVersionSpec(upper))) case constraints.OpGreaterThanOrEqualPatchOnly: upper := lower upper.Minor.Num++ upper.Patch.Num = 0 upper.Prerelease = "" return AtLeast( versionFromExactVersionSpec(lower), ).Intersection( OlderThan(versionFromExactVersionSpec(upper))) default: panic(fmt.Errorf("unsupported constraints.SelectionOp %s", ts.Operator)) } case constraints.UnionSpec: if len(ts) == 0 { return All } if len(ts) == 1 { return MeetingConstraintsExact(ts[0]) } union := make(setUnion, len(ts)) for i, subSpec := range ts { union[i] = MeetingConstraintsExact(subSpec).setI } return Set{setI: union} case constraints.IntersectionSpec: if len(ts) == 0 { return All } if len(ts) == 1 { return MeetingConstraintsExact(ts[0]) } intersection := make(setIntersection, len(ts)) for i, subSpec := range ts { intersection[i] = MeetingConstraintsExact(subSpec).setI } return Set{setI: intersection} default: // should never happen because the above cases are exhaustive for // all valid constraint implementations. panic(fmt.Errorf("unsupported constraints.Spec implementation %T", spec)) } } // MeetingConstraintsString attempts to parse the given spec as a constraints // string in our canonical format, which is most similar to the syntax used by // npm, Go's "dep" tool, Rust's "cargo", etc. // // This is a covenience wrapper around calling constraints.Parse and then // passing the result to MeetingConstraints. Call into the constraints package // yourself for access to the constraint tree. // // If unsuccessful, the error from the underlying parser is returned verbatim. // Parser errors are suitable for showing to an end-user in situations where // the given spec came from user input. func MeetingConstraintsString(spec string) (Set, error) { s, err := constraints.Parse(spec) if err != nil { return None, err } return MeetingConstraints(s), nil } // MeetingConstraintsStringRuby attempts to parse the given spec as a // "Ruby-style" version constraint string, and returns the set of versions // that match the constraint if successful. // // If unsuccessful, the error from the underlying parser is returned verbatim. // Parser errors are suitable for showing to an end-user in situations where // the given spec came from user input. // // "Ruby-style" here is not a promise of exact compatibility with rubygems // or any other Ruby tools. Rather, it refers to this parser using a syntax // that is intended to feel familiar to those who are familiar with rubygems // syntax. // // Constraints are parsed in "multi" mode, allowing multiple comma-separated // constraints that are combined with the Intersection operator. For more // control over the parsing process, use the constraints package API directly // and then call MeetingConstraints. func MeetingConstraintsStringRuby(spec string) (Set, error) { s, err := constraints.ParseRubyStyleMulti(spec) if err != nil { return None, err } return MeetingConstraints(s), nil } // MustMakeSet can be used to wrap any function that returns a set and an error // to make it panic if an error occurs and return the set otherwise. // // This is intended for tests and other situations where input is from // known-good constants. func MustMakeSet(set Set, err error) Set { if err != nil { panic(err) } return set } func versionFromExactVersionSpec(spec constraints.VersionSpec) Version { return Version{ Major: spec.Major.Num, Minor: spec.Minor.Num, Patch: spec.Patch.Num, Prerelease: VersionExtra(spec.Prerelease), Metadata: VersionExtra(spec.Metadata), } } go-versions-1.0.1/versions/parse_test.go000066400000000000000000000170211374017526700203510ustar00rootroot00000000000000package versions import ( "reflect" "testing" "github.com/davecgh/go-spew/spew" ) func TestMeetingConstraintsCanon(t *testing.T) { tests := []struct { Input string Want Set }{ { `1.0.0`, Intersection( Released, Only(MustParseVersion(`1.0.0`)), ), }, { `=1.0.0`, Intersection( Released, Only(MustParseVersion(`1.0.0`)), ), }, { `1.0-beta.1`, // This result is sub-optimal since it mentions the pre-release // version twice, but it works. Perhaps later we'll try to // optimize this situation, but not too bothered for now. Union( Only(MustParseVersion(`1.0-beta.1`)), Intersection( Released, Only(MustParseVersion(`1.0-beta.1`)), ), ), }, { `^1.0 || 2.0-beta.1 || 2.0-beta.2`, // This result is even less optimal, but again is functionally // correct. Union( Selection( MustParseVersion(`2.0-beta.1`), MustParseVersion(`2.0-beta.2`), ), Intersection( Released, Union( Intersection( AtLeast(MustParseVersion("1.0.0")), OlderThan(MustParseVersion("2.0.0")), ), Only(MustParseVersion(`2.0-beta.1`)), Only(MustParseVersion(`2.0-beta.2`)), ), ), ), }, { `!1.0.0`, Intersection( Released, All.Subtract(Only(MustParseVersion(`1.0.0`))), ), }, { `>1.0.0`, Intersection( Released, NewerThan(MustParseVersion(`1.0.0`)), ), }, { `>1.0`, Intersection( Released, NewerThan(MustParseVersion(`1.0.0`)), ), }, { `<1.0.0`, Intersection( Released, OlderThan(MustParseVersion(`1.0.0`)), ), }, { `>=1.0.0`, Intersection( Released, AtLeast(MustParseVersion(`1.0.0`)), ), }, { `<=1.0.0`, Intersection( Released, AtMost(MustParseVersion(`1.0.0`)), ), }, { `~1.2.3`, Intersection( Released, AtLeast(MustParseVersion(`1.2.3`)), OlderThan(MustParseVersion(`1.3.0`)), ), }, { `~1.2`, Intersection( Released, AtLeast(MustParseVersion(`1.2.0`)), OlderThan(MustParseVersion(`1.3.0`)), ), }, { `~1`, Intersection( Released, AtLeast(MustParseVersion(`1.0.0`)), OlderThan(MustParseVersion(`2.0.0`)), ), }, { `^1.2.3`, Intersection( Released, AtLeast(MustParseVersion(`1.2.3`)), OlderThan(MustParseVersion(`2.0.0`)), ), }, { `^0.2.3`, Intersection( Released, AtLeast(MustParseVersion(`0.2.3`)), OlderThan(MustParseVersion(`0.3.0`)), ), }, { `^1.2`, Intersection( Released, AtLeast(MustParseVersion(`1.2.0`)), OlderThan(MustParseVersion(`2.0.0`)), ), }, { `^1`, Intersection( Released, AtLeast(MustParseVersion(`1.0.0`)), OlderThan(MustParseVersion(`2.0.0`)), ), }, { `>=1 <2`, Intersection( Released, AtLeast(MustParseVersion(`1.0.0`)), OlderThan(MustParseVersion(`2.0.0`)), ), }, { `1.*`, Intersection( Released, AtLeast(MustParseVersion(`1.0.0`)), OlderThan(MustParseVersion(`2.0.0`)), ), }, { `1.2.*`, Intersection( Released, AtLeast(MustParseVersion(`1.2.0`)), OlderThan(MustParseVersion(`1.3.0`)), ), }, { `*`, Released, }, { `*.*`, Released, }, { `*.*.*`, Released, }, { `1.0.0 2.0.0`, Intersection( Released, Only(MustParseVersion(`1.0.0`)), Only(MustParseVersion(`2.0.0`)), ), }, { `>=1.0 || >=0.9 <0.10`, Intersection( Released, Union( AtLeast(MustParseVersion("1.0")), Intersection( AtLeast(MustParseVersion("0.9")), OlderThan(MustParseVersion("0.10")), ), ), ), }, { `1.0.0 1.0.0`, // redundant Intersection( Released, Only(MustParseVersion(`1.0.0`)), // the duplicates don't get optimized away (yet?) Only(MustParseVersion(`1.0.0`)), // probably not worth the effort but will test someday ), }, { `1.0.0 || 1.0.0`, // redundant Intersection( Released, Union( Only(MustParseVersion(`1.0.0`)), // the duplicates don't get optimized away (yet?) Only(MustParseVersion(`1.0.0`)), // probably not worth the effort but will test someday ), ), }, { `1.0.0 !1.0.0`, // degenerate empty set Intersection( Released, Only(MustParseVersion(`1.0.0`)), All.Subtract(Only(MustParseVersion(`1.0.0`))), ), }, } for _, test := range tests { t.Run(test.Input, func(t *testing.T) { got, err := MeetingConstraintsString(test.Input) if err != nil { t.Fatal(err) } if !reflect.DeepEqual(got, test.Want) { gotStr := got.GoString() wantStr := test.Want.GoString() if gotStr != wantStr { t.Errorf("wrong result\ngot: %s\nwant: %s", gotStr, wantStr) } else { // Sometimes our GoString implementations hide differences that // DeepEqual thinks are significant. t.Errorf("wrong result\ngot: %swant: %s", spew.Sdump(got), spew.Sdump(test.Want)) } } }) } } func TestMeetingConstraintsRuby(t *testing.T) { tests := []struct { Input string Want Set }{ { `1.0.0`, Intersection( Released, Only(MustParseVersion(`1.0.0`)), ), }, { `= 1.0.0`, Intersection( Released, Only(MustParseVersion(`1.0.0`)), ), }, { `!= 1.0.0`, Intersection( Released, All.Subtract(Only(MustParseVersion(`1.0.0`))), ), }, { `> 1.0.0`, Intersection( Released, NewerThan(MustParseVersion(`1.0.0`)), ), }, { `> 1.0`, Intersection( Released, NewerThan(MustParseVersion(`1.0.0`)), ), }, { `< 1.0.0`, Intersection( Released, OlderThan(MustParseVersion(`1.0.0`)), ), }, { `>= 1.0.0`, Intersection( Released, AtLeast(MustParseVersion(`1.0.0`)), ), }, { `<= 1.0.0`, Intersection( Released, AtMost(MustParseVersion(`1.0.0`)), ), }, { `~> 1.2.3`, Intersection( Released, AtLeast(MustParseVersion(`1.2.3`)), OlderThan(MustParseVersion(`1.3.0`)), ), }, { `~> 1.2`, Intersection( Released, AtLeast(MustParseVersion(`1.2.0`)), OlderThan(MustParseVersion(`2.0.0`)), ), }, { `~> 1`, Intersection( Released, AtLeast(MustParseVersion(`1.0.0`)), OlderThan(MustParseVersion(`2.0.0`)), ), }, { `>= 1, < 2`, Intersection( Released, AtLeast(MustParseVersion(`1.0.0`)), OlderThan(MustParseVersion(`2.0.0`)), ), }, { `1.0.0, 2.0.0`, Intersection( Released, Only(MustParseVersion(`1.0.0`)), Only(MustParseVersion(`2.0.0`)), ), }, { `1.0.0, 1.0.0`, // redundant Intersection( Released, Only(MustParseVersion(`1.0.0`)), // the duplicates don't get optimized away (yet?) Only(MustParseVersion(`1.0.0`)), // probably not worth the effort but will test someday ), }, { `1.0.0, != 1.0.0`, // degenerate empty set Intersection( Released, Only(MustParseVersion(`1.0.0`)), All.Subtract(Only(MustParseVersion(`1.0.0`))), ), }, } for _, test := range tests { t.Run(test.Input, func(t *testing.T) { got, err := MeetingConstraintsStringRuby(test.Input) if err != nil { t.Fatal(err) } if !reflect.DeepEqual(got, test.Want) { gotStr := got.GoString() wantStr := test.Want.GoString() if gotStr != wantStr { t.Errorf("wrong result\ngot: %s\nwant: %s", gotStr, wantStr) } else { // Sometimes our GoString implementations hide differences that // DeepEqual thinks are significant. t.Errorf("wrong result\ngot: %swant: %s", spew.Sdump(got), spew.Sdump(test.Want)) } } }) } } go-versions-1.0.1/versions/set.go000066400000000000000000000061541374017526700170000ustar00rootroot00000000000000package versions // Set is a set of versions, usually created by parsing a constraint string. type Set struct { setI } // setI is the private interface implemented by our various constraint // operators. type setI interface { Has(v Version) bool AllRequested() Set GoString() string } // Has returns true if the given version is a member of the receiving set. func (s Set) Has(v Version) bool { // The special Unspecified version is excluded as soon as any sort of // constraint is applied, and so the only set it is a member of is // the special All set. if v == Unspecified { return s == All } return s.setI.Has(v) } // Requests returns true if the given version is specifically requested by // the receiving set. // // Requesting is a stronger form of set membership that represents an explicit // request for a particular version, as opposed to the version just happening // to match some criteria. // // The functions Only and Selection mark their arguments as requested in // their returned sets. Exact version constraints given in constraint strings // also mark their versions as requested. // // The concept of requesting is intended to help deal with pre-release versions // in a safe and convenient way. When given generic version constraints like // ">= 1.0.0" the user generally does not intend to match a pre-release version // like "2.0.0-beta1", but it is important to stil be able to use that // version if explicitly requested using the constraint string "2.0.0-beta1". func (s Set) Requests(v Version) bool { return s.AllRequested().Has(v) } // AllRequested returns a subset of the receiver containing only the requested // versions, as defined in the documentation for the method Requests. // // This can be used in conjunction with the predefined set "Released" to // include pre-release versions only by explicit request, which is supported // via the helper method WithoutUnrequestedPrereleases. // // The result of AllRequested is always a finite set. func (s Set) AllRequested() Set { return s.setI.AllRequested() } // WithoutUnrequestedPrereleases returns a new set that includes all released // versions from the receiving set, plus any explicitly-requested pre-releases, // but does not include any unrequested pre-releases. // // "Requested" here is as defined in the documentation for the "Requests" method. // // This method is equivalent to the following set operations: // // versions.Union(s.AllRequested(), s.Intersection(versions.Released)) func (s Set) WithoutUnrequestedPrereleases() Set { return Union(s.AllRequested(), Released.Intersection(s)) } // UnmarshalText is an implementation of encoding.TextUnmarshaler, allowing // sets to be automatically unmarshalled from strings in text-based // serialization formats, including encoding/json. // // The format expected is what is accepted by MeetingConstraintsString. Any // parser errors are passed on verbatim to the caller. func (s *Set) UnmarshalText(text []byte) error { str := string(text) new, err := MeetingConstraintsString(str) if err != nil { return err } *s = new return nil } var InitialDevelopment Set = OlderThan(MustParseVersion("1.0.0")) go-versions-1.0.1/versions/set_bound.go000066400000000000000000000036661374017526700201740ustar00rootroot00000000000000package versions import ( "fmt" ) type setBound struct { v Version op setBoundOp } func (s setBound) Has(v Version) bool { switch s.op { case setBoundGT: return v.GreaterThan(s.v) case setBoundGTE: return v.GreaterThan(s.v) || v.Same(s.v) case setBoundLT: return v.LessThan(s.v) case setBoundLTE: return v.LessThan(s.v) || v.Same(s.v) default: // Should never happen because the above is exhaustive panic("invalid setBound operator") } } func (s setBound) AllRequested() Set { // Inequalities request nothing. return None } func (s setBound) GoString() string { switch s.op { case setBoundGT: return fmt.Sprintf("versions.NewerThan(%#v)", s.v) case setBoundGTE: return fmt.Sprintf("versions.AtLeast(%#v)", s.v) case setBoundLT: return fmt.Sprintf("versions.OlderThan(%#v)", s.v) case setBoundLTE: return fmt.Sprintf("versions.AtMost(%#v)", s.v) default: // Should never happen because the above is exhaustive return fmt.Sprintf("versions.Set{versions.setBound{v:%#v,op:%#v}}", s.v, s.op) } } // NewerThan returns a set containing all versions greater than the given // version, non-inclusive. func NewerThan(v Version) Set { return Set{ setI: setBound{ v: v, op: setBoundGT, }, } } // OlderThan returns a set containing all versions lower than the given // version, non-inclusive. func OlderThan(v Version) Set { return Set{ setI: setBound{ v: v, op: setBoundLT, }, } } // AtLeast returns a set containing all versions greater than or equal to // the given version. func AtLeast(v Version) Set { return Set{ setI: setBound{ v: v, op: setBoundGTE, }, } } // AtMost returns a set containing all versions less than or equal to the given // version, non-inclusive. func AtMost(v Version) Set { return Set{ setI: setBound{ v: v, op: setBoundLTE, }, } } type setBoundOp rune const setBoundGT = '>' const setBoundGTE = '≥' const setBoundLT = '<' const setBoundLTE = '≤' go-versions-1.0.1/versions/set_exact.go000066400000000000000000000036651374017526700201700ustar00rootroot00000000000000package versions import ( "bytes" "fmt" ) type setExact map[Version]struct{} func (s setExact) Has(v Version) bool { _, has := s[v] return has } func (s setExact) AllRequested() Set { // We just return the receiver verbatim here, because everything in it // is explicitly requested. return Set{setI: s} } func (s setExact) GoString() string { if len(s) == 0 { // Degenerate case; caller should use None instead return "versions.Set{setExact{}}" } if len(s) == 1 { var first Version for v := range s { first = v break } return fmt.Sprintf("versions.Only(%#v)", first) } var buf bytes.Buffer fmt.Fprint(&buf, "versions.Selection(") versions := s.listVersions() versions.Sort() for i, version := range versions { if i == 0 { fmt.Fprint(&buf, version.GoString()) } else { fmt.Fprintf(&buf, ", %#v", version) } } fmt.Fprint(&buf, ")") return buf.String() } // Only returns a version set containing only the given version. // // This function is guaranteed to produce a finite set. func Only(v Version) Set { return Set{ setI: setExact{v: struct{}{}}, } } // Selection returns a version set containing only the versions given // as arguments. // // This function is guaranteed to produce a finite set. func Selection(vs ...Version) Set { if len(vs) == 0 { return None } ret := make(setExact) for _, v := range vs { ret[v] = struct{}{} } return Set{setI: ret} } // Exactly returns true if and only if the receiving set is finite and // contains only a single version that is the same as the version given. func (s Set) Exactly(v Version) bool { if !s.IsFinite() { return false } l := s.List() if len(l) != 1 { return false } return v.Same(l[0]) } var _ setFinite = setExact(nil) func (s setExact) isFinite() bool { return true } func (s setExact) listVersions() List { if len(s) == 0 { return nil } ret := make(List, 0, len(s)) for v := range s { ret = append(ret, v) } return ret } go-versions-1.0.1/versions/set_extremes.go000066400000000000000000000014301374017526700207040ustar00rootroot00000000000000package versions // All is an infinite set containing all possible versions. var All Set // None is a finite set containing no versions. var None Set type setExtreme bool func (s setExtreme) Has(v Version) bool { return bool(s) } func (s setExtreme) AllRequested() Set { // The extreme sets request nothing. return None } func (s setExtreme) GoString() string { switch bool(s) { case true: return "versions.All" case false: return "versions.None" default: panic("strange new boolean value") } } var _ setFinite = setExtreme(false) func (s setExtreme) isFinite() bool { // Only None is finite return !bool(s) } func (s setExtreme) listVersions() List { return nil } func init() { All = Set{ setI: setExtreme(true), } None = Set{ setI: setExtreme(false), } } go-versions-1.0.1/versions/set_finite.go000066400000000000000000000017631374017526700203370ustar00rootroot00000000000000package versions // setFinite is the interface implemented by set implementations that // represent a finite number of versions, and can thus list those versions. type setFinite interface { isFinite() bool listVersions() List } // IsFinite returns true if the set represents a finite number of versions, // and can thus support List without panicking. func (s Set) IsFinite() bool { return isFinite(s.setI) } // List returns the specific versions represented by a finite list, in an // undefined order. If desired, the caller can sort the resulting list // using its Sort method. // // If the set is not finite, this method will panic. Use IsFinite to check // unless a finite set was guaranteed by whatever operation(s) constructed // the set. func (s Set) List() List { finite, ok := s.setI.(setFinite) if !ok || !finite.isFinite() { panic("List called on infinite set") } return finite.listVersions() } func isFinite(s setI) bool { finite, ok := s.(setFinite) return ok && finite.isFinite() } go-versions-1.0.1/versions/set_intersection.go000066400000000000000000000052771374017526700215730ustar00rootroot00000000000000package versions import ( "bytes" "fmt" ) type setIntersection []setI func (s setIntersection) Has(v Version) bool { if len(s) == 0 { // Weird to have an intersection with no elements, but we'll // allow it and return something sensible. return false } for _, ss := range s { if !ss.Has(v) { return false } } return true } func (s setIntersection) AllRequested() Set { // The requested set for an intersection is the union of all of its // members requested sets intersection the receiver. Therefore we'll // borrow the same logic from setUnion's implementation here but // then wrap it up in a setIntersection before we return. asUnion := setUnion(s) ar := asUnion.AllRequested() si := make(setIntersection, len(s)+1) si[0] = ar.setI copy(si[1:], s) return Set{setI: si} } func (s setIntersection) GoString() string { var buf bytes.Buffer fmt.Fprint(&buf, "versions.Intersection(") for i, ss := range s { if i == 0 { fmt.Fprint(&buf, ss.GoString()) } else { fmt.Fprintf(&buf, ", %#v", ss) } } fmt.Fprint(&buf, ")") return buf.String() } // Intersection creates a new set that contains the versions that all of the // given sets have in common. // // The result is finite if any of the given sets are finite. func Intersection(sets ...Set) Set { if len(sets) == 0 { return None } r := make(setIntersection, 0, len(sets)) for _, set := range sets { if set == All { continue } if set == None { return None } if su, ok := set.setI.(setIntersection); ok { r = append(r, su...) } else { r = append(r, set.setI) } } if len(r) == 1 { return Set{setI: r[0]} } return Set{setI: r} } // Intersection returns a new set that contains all of the versions that // the receiver and the given sets have in common. // // The result is a finite set if the receiver or any of the given sets are // finite. func (s Set) Intersection(others ...Set) Set { r := make(setIntersection, 1, len(others)+1) r[0] = s.setI for _, ss := range others { if ss == All { continue } if ss == None { return None } if su, ok := ss.setI.(setIntersection); ok { r = append(r, su...) } else { r = append(r, ss.setI) } } if len(r) == 1 { return Set{setI: r[0]} } return Set{setI: r} } var _ setFinite = setIntersection{} func (s setIntersection) isFinite() bool { // intersection is finite if any of its members are, or if it is empty if len(s) == 0 { return true } for _, ss := range s { if isFinite(ss) { return true } } return false } func (s setIntersection) listVersions() List { var ret List for _, ss := range s { if isFinite(ss) { ret = append(ret, ss.(setFinite).listVersions()...) } } ret.Filter(Set{setI: s}) return ret } go-versions-1.0.1/versions/set_released.go000066400000000000000000000012551374017526700206410ustar00rootroot00000000000000package versions type setReleased struct{} func (s setReleased) Has(v Version) bool { return v.Prerelease == "" } func (s setReleased) AllRequested() Set { // The set of all released versions requests nothing. return None } func (s setReleased) GoString() string { return "versions.Released" } // Released is a set containing all versions that have an empty prerelease // string. var Released Set // Prerelease is a set containing all versions that have a prerelease marker. // This is the complement of Released, or in other words it is // All.Subtract(Released). var Prerelease Set func init() { Released = Set{setI: setReleased{}} Prerelease = All.Subtract(Released) } go-versions-1.0.1/versions/set_subtract.go000066400000000000000000000024211374017526700207000ustar00rootroot00000000000000package versions import "fmt" type setSubtract struct { from setI sub setI } func (s setSubtract) Has(v Version) bool { return s.from.Has(v) && !s.sub.Has(v) } func (s setSubtract) AllRequested() Set { // Our set requests anything that is requested by "from", unless it'd // be excluded by "sub". Notice that the whole of "sub" is used, rather // than just the requested parts, because requesting is a positive // action only. return Set{setI: s.from}.AllRequested().Subtract(Set{setI: s.sub}) } func (s setSubtract) GoString() string { return fmt.Sprintf("(%#v).Subtract(%#v)", s.from, s.sub) } // Subtract returns a new set that has all of the versions from the receiver // except for any versions in the other given set. // // If the receiver is finite then the returned set is also finite. func (s Set) Subtract(other Set) Set { if other == None || s == None { return s } if other == All { return None } return Set{ setI: setSubtract{ from: s.setI, sub: other.setI, }, } } var _ setFinite = setSubtract{} func (s setSubtract) isFinite() bool { // subtract is finite if its "from" is finite return isFinite(s.from) } func (s setSubtract) listVersions() List { ret := s.from.(setFinite).listVersions() ret = ret.Filter(Set{setI: s.sub}) return ret } go-versions-1.0.1/versions/set_test.go000066400000000000000000000161721374017526700200400ustar00rootroot00000000000000package versions import ( "encoding/json" "reflect" "testing" ) // Make sure our set implementations all actually implement the interface var _ setI = setBound{} var _ setI = setExact{} var _ setI = setExtreme(true) var _ setI = setIntersection{} var _ setI = setSubtract{} var _ setI = setUnion{} var _ setI = setReleased{} func TestSetHas(t *testing.T) { tests := []struct { Set Set Has Version Want bool }{ { All, Unspecified, true, }, { None, Unspecified, false, }, { All.Subtract(Only(MustParseVersion("1.0.0"))), Unspecified, false, // any sort of constraint removes the special Unspecified version }, { InitialDevelopment, Unspecified, false, }, { InitialDevelopment, MustParseVersion("0.0.2"), true, }, { InitialDevelopment, MustParseVersion("1.0.0"), false, }, { Released, MustParseVersion("1.0.0"), true, }, { Released, MustParseVersion("1.0.0-beta1"), false, }, { Prerelease, MustParseVersion("1.0.0"), false, }, { Prerelease, MustParseVersion("1.0.0-beta1"), true, }, { Union( Only(MustParseVersion("1.0.0")), Only(MustParseVersion("1.1.0")), ), MustParseVersion("1.0.0"), true, }, { Union( Only(MustParseVersion("1.0.0")), Only(MustParseVersion("1.1.0")), ), MustParseVersion("1.1.0"), true, }, { Union( Only(MustParseVersion("1.0.0")), Only(MustParseVersion("1.1.0")), ), MustParseVersion("1.2.0"), false, }, { Union( Only(MustParseVersion("1.0.0")), Only(MustParseVersion("1.1.0")), Only(MustParseVersion("1.2.0")), ), MustParseVersion("1.2.0"), true, }, { Intersection( AtLeast(MustParseVersion("1.0.0")), OlderThan(MustParseVersion("2.0.0")), ), MustParseVersion("0.0.2"), false, }, { Intersection( AtLeast(MustParseVersion("1.0.0")), OlderThan(MustParseVersion("2.0.0")), ), MustParseVersion("1.0.0"), true, }, { Intersection( AtLeast(MustParseVersion("1.0.0")), OlderThan(MustParseVersion("2.0.0")), ), MustParseVersion("1.2.3"), true, }, { Intersection( AtLeast(MustParseVersion("1.0.0")), OlderThan(MustParseVersion("2.0.0")), ), MustParseVersion("2.0.0"), false, }, { Intersection( AtLeast(MustParseVersion("1.0.0")), OlderThan(MustParseVersion("2.0.0")), ), MustParseVersion("2.0.1"), false, }, { All.Subtract(Only(MustParseVersion("0.9.0"))), MustParseVersion("0.9.0"), false, }, { All.Subtract(Only(MustParseVersion("0.9.0"))), MustParseVersion("0.9.1"), true, }, { All.Subtract(Only(MustParseVersion("0.9.0"))), MustParseVersion("0.9.1"), true, }, { Union( All, Only(MustParseVersion("1.0.1")), ).AllRequested(), MustParseVersion("1.0.1"), true, }, { Union( All, Only(MustParseVersion("1.0.1")), ).AllRequested(), MustParseVersion("1.0.2"), false, }, { Intersection( All, Only(MustParseVersion("1.0.1")), ).AllRequested(), MustParseVersion("1.0.1"), true, }, { Intersection( All, Only(MustParseVersion("1.0.1")), ).AllRequested(), MustParseVersion("1.0.2"), false, }, { Intersection( AtLeast(MustParseVersion("2.0.0")), Only(MustParseVersion("1.0.1")), ).AllRequested(), MustParseVersion("1.0.1"), false, }, { Only( MustParseVersion("1.0.1"), ).Subtract( AtLeast(MustParseVersion("1.0.0")), ).AllRequested(), MustParseVersion("1.0.1"), false, }, { MustMakeSet(MeetingConstraintsStringRuby(">= 1.0.0")), MustParseVersion("1.0.0"), true, }, { MustMakeSet(MeetingConstraintsStringRuby(">= 1.0.0")), MustParseVersion("1.0.0-beta1"), false, }, { MustMakeSet(MeetingConstraintsStringRuby(">= 1.0.0")), MustParseVersion("2.0.0-beta1"), false, }, { MustMakeSet(MeetingConstraintsStringRuby("2.0.0-beta1")), MustParseVersion("2.0.0-beta1"), true, }, { MustMakeSet(MeetingConstraintsStringRuby(">= 1.0.0")), MustParseVersion("1.0.1"), true, }, { MustMakeSet(MeetingConstraintsStringRuby(">= 1.0.0")).AllRequested(), MustParseVersion("0.0.1"), false, }, { MustMakeSet(MeetingConstraintsStringRuby(">= 1.0.0")).AllRequested(), MustParseVersion("1.0.0-beta1"), false, }, { MustMakeSet(MeetingConstraintsStringRuby(">= 1.0.0")).AllRequested(), MustParseVersion("2.0.0-beta1"), false, }, { MustMakeSet(MeetingConstraintsStringRuby("2.0.0-beta1")).AllRequested(), MustParseVersion("2.0.0-beta1"), true, }, { MustMakeSet(MeetingConstraintsStringRuby(">= 1.0.0")).WithoutUnrequestedPrereleases(), MustParseVersion("0.0.1"), false, }, { MustMakeSet(MeetingConstraintsStringRuby(">= 1.0.0")).WithoutUnrequestedPrereleases(), MustParseVersion("1.0.0"), true, }, { MustMakeSet(MeetingConstraintsStringRuby(">= 1.0.0")).WithoutUnrequestedPrereleases(), MustParseVersion("1.0.0-beta1"), false, }, { MustMakeSet(MeetingConstraintsStringRuby(">= 1.0.0")).WithoutUnrequestedPrereleases(), MustParseVersion("2.0.0-beta1"), false, }, { MustMakeSet(MeetingConstraintsStringRuby("2.0.0-beta1")).WithoutUnrequestedPrereleases(), MustParseVersion("2.0.0-beta1"), true, }, { MustMakeSet(MeetingConstraintsStringRuby("~> 1.2.3")), MustParseVersion("1.2.3"), true, }, { MustMakeSet(MeetingConstraintsStringRuby("~> 1.2.3")), MustParseVersion("1.2.5"), true, }, { MustMakeSet(MeetingConstraintsStringRuby("~> 1.2.3")), MustParseVersion("1.3.0"), false, }, { MustMakeSet(MeetingConstraintsStringRuby("~> 1.2")), MustParseVersion("1.2.3"), true, }, { MustMakeSet(MeetingConstraintsStringRuby("~> 1.2")), MustParseVersion("1.2.5"), true, }, { MustMakeSet(MeetingConstraintsStringRuby("~> 1.2")), MustParseVersion("1.3.0"), true, }, { MustMakeSet(MeetingConstraintsStringRuby("~> 1.2")), MustParseVersion("2.0.0"), false, }, { MustMakeSet(MeetingConstraintsStringRuby("~> 1")), MustParseVersion("1.2.3"), true, }, { MustMakeSet(MeetingConstraintsStringRuby("~> 1")), MustParseVersion("1.2.5"), true, }, { MustMakeSet(MeetingConstraintsStringRuby("~> 1")), MustParseVersion("1.3.0"), true, }, { MustMakeSet(MeetingConstraintsStringRuby("~> 1")), MustParseVersion("2.0.0"), false, }, } for _, test := range tests { t.Run(test.Set.GoString(), func(t *testing.T) { got := test.Set.Has(test.Has) if got != test.Want { t.Errorf( "wrong result\nset: %#v\nversion: %#v\ngot: %#v\nwant: %#v", test.Set, test.Has, got, test.Want, ) } }) } } func TestSetJSON(t *testing.T) { j := []byte(`"^1 || 2.0.0"`) var got Set err := json.Unmarshal(j, &got) if err != nil { t.Fatal(err) } want := Intersection( Released, Union( Intersection( AtLeast(MustParseVersion("1.0.0")), OlderThan(MustParseVersion("2.0.0")), ), Only(MustParseVersion("2.0.0")), ), ) if !reflect.DeepEqual(got, want) { t.Errorf("wrong result\ngot: %#v\nwant :%#v", got, want) } } go-versions-1.0.1/versions/set_union.go000066400000000000000000000044111374017526700202020ustar00rootroot00000000000000package versions import ( "bytes" "fmt" ) type setUnion []setI func (s setUnion) Has(v Version) bool { for _, ss := range s { if ss.Has(v) { return true } } return false } func (s setUnion) AllRequested() Set { // Since a union includes everything from its members, it includes all // of the requested versions from its members too. if len(s) == 0 { return None } si := make(setUnion, 0, len(s)) for _, ss := range s { ar := ss.AllRequested() if ar == None { continue } si = append(si, ar.setI) } if len(si) == 1 { return Set{setI: si[0]} } return Set{setI: si} } func (s setUnion) GoString() string { var buf bytes.Buffer fmt.Fprint(&buf, "versions.Union(") for i, ss := range s { if i == 0 { fmt.Fprint(&buf, ss.GoString()) } else { fmt.Fprintf(&buf, ", %#v", ss) } } fmt.Fprint(&buf, ")") return buf.String() } // Union creates a new set that contains all of the given versions. // // The result is finite only if the receiver and all of the other given sets // are finite. func Union(sets ...Set) Set { if len(sets) == 0 { return None } r := make(setUnion, 0, len(sets)) for _, set := range sets { if set == None { continue } if su, ok := set.setI.(setUnion); ok { r = append(r, su...) } else { r = append(r, set.setI) } } if len(r) == 1 { return Set{setI: r[0]} } return Set{setI: r} } // Union returns a new set that contains all of the versions from the // receiver and all of the versions from each of the other given sets. // // The result is finite only if the receiver and all of the other given sets // are finite. func (s Set) Union(others ...Set) Set { r := make(setUnion, 1, len(others)+1) r[0] = s.setI for _, ss := range others { if ss == None { continue } if su, ok := ss.setI.(setUnion); ok { r = append(r, su...) } else { r = append(r, ss.setI) } } if len(r) == 1 { return Set{setI: r[0]} } return Set{setI: r} } var _ setFinite = setUnion{} func (s setUnion) isFinite() bool { // union is finite only if all of its members are finite for _, ss := range s { if !isFinite(ss) { return false } } return true } func (s setUnion) listVersions() List { var ret List for _, ss := range s { ret = append(ret, ss.(setFinite).listVersions()...) } return ret } go-versions-1.0.1/versions/version.go000066400000000000000000000133201374017526700176630ustar00rootroot00000000000000package versions import ( "fmt" "strings" ) // Version represents a single version. type Version struct { Major uint64 Minor uint64 Patch uint64 Prerelease VersionExtra Metadata VersionExtra } // Unspecified is the zero value of Version and represents the absense of a // version number. // // Note that this is indistinguishable from the explicit version that // results from parsing the string "0.0.0". var Unspecified Version // Same returns true if the receiver has the same precedence as the other // given version. In other words, it has the same major, minor and patch // version number and an identical prerelease portion. The Metadata, if // any, is not considered. func (v Version) Same(other Version) bool { return (v.Major == other.Major && v.Minor == other.Minor && v.Patch == other.Patch && v.Prerelease == other.Prerelease) } // Comparable returns a version that is the same as the receiver but its // metadata is the empty string. For Comparable versions, the standard // equality operator == is equivalent to method Same. func (v Version) Comparable() Version { v.Metadata = "" return v } // String is an implementation of fmt.Stringer that returns the receiver // in the canonical "semver" format. func (v Version) String() string { s := fmt.Sprintf("%d.%d.%d", v.Major, v.Minor, v.Patch) if v.Prerelease != "" { s = fmt.Sprintf("%s-%s", s, v.Prerelease) } if v.Metadata != "" { s = fmt.Sprintf("%s+%s", s, v.Metadata) } return s } func (v Version) GoString() string { return fmt.Sprintf("versions.MustParseVersion(%q)", v.String()) } // LessThan returns true if the receiver has a lower precedence than the // other given version, as defined by the semantic versioning specification. func (v Version) LessThan(other Version) bool { switch { case v.Major != other.Major: return v.Major < other.Major case v.Minor != other.Minor: return v.Minor < other.Minor case v.Patch != other.Patch: return v.Patch < other.Patch case v.Prerelease != other.Prerelease: if v.Prerelease == "" { return false } if other.Prerelease == "" { return true } return v.Prerelease.LessThan(other.Prerelease) default: return false } } // GreaterThan returns true if the receiver has a higher precedence than the // other given version, as defined by the semantic versioning specification. func (v Version) GreaterThan(other Version) bool { switch { case v.Major != other.Major: return v.Major > other.Major case v.Minor != other.Minor: return v.Minor > other.Minor case v.Patch != other.Patch: return v.Patch > other.Patch case v.Prerelease != other.Prerelease: if v.Prerelease == "" { return true } if other.Prerelease == "" { return false } return !v.Prerelease.LessThan(other.Prerelease) default: return false } } // MarshalText is an implementation of encoding.TextMarshaler, allowing versions // to be automatically marshalled for text-based serialization formats, // including encoding/json. // // The format used is that returned by String, which can be parsed using // ParseVersion. func (v Version) MarshalText() (text []byte, err error) { return []byte(v.String()), nil } // UnmarshalText is an implementation of encoding.TextUnmarshaler, allowing // versions to be automatically unmarshalled from strings in text-based // serialization formats, including encoding/json. // // The format expected is what is accepted by ParseVersion. Any parser errors // are passed on verbatim to the caller. func (v *Version) UnmarshalText(text []byte) error { str := string(text) new, err := ParseVersion(str) if err != nil { return err } *v = new return nil } // VersionExtra represents a string containing dot-delimited tokens, as used // in the pre-release and build metadata portions of a Semantic Versioning // version expression. type VersionExtra string // Parts tokenizes the string into its separate parts by splitting on dots. // // The result is undefined if the receiver is not valid per the semver spec, func (e VersionExtra) Parts() []string { return strings.Split(string(e), ".") } func (e VersionExtra) Raw() string { return string(e) } // LessThan returns true if the receiever has lower precedence than the // other given VersionExtra string, per the rules defined in the semver // spec for pre-release versions. // // Build metadata has no defined precedence rules, so it is not meaningful // to call this method on a VersionExtra representing build metadata. func (e VersionExtra) LessThan(other VersionExtra) bool { if e == other { // Easy path return false } s1 := string(e) s2 := string(other) for { d1 := strings.IndexByte(s1, '.') d2 := strings.IndexByte(s2, '.') switch { case d1 == -1 && d2 != -1: // s1 has fewer parts, so it precedes s2 return true case d2 == -1 && d1 != -1: // s1 has more parts, so it succeeds s2 return false case d1 == -1: // d2 must be -1 too, because of the above // this is our last portion to compare return lessThanStr(s1, s2) default: s1s := s1[:d1] s2s := s2[:d2] if s1s != s2s { return lessThanStr(s1s, s2s) } s1 = s1[d1+1:] s2 = s2[d2+1:] } } } func lessThanStr(s1, s2 string) bool { // How we compare here depends on whether the string is entirely consistent of digits s1Numeric := true s2Numeric := true for _, c := range s1 { if c < '0' || c > '9' { s1Numeric = false break } } for _, c := range s2 { if c < '0' || c > '9' { s2Numeric = false break } } switch { case s1Numeric && !s2Numeric: return true case s2Numeric && !s1Numeric: return false case s1Numeric: // s2Numeric must also be true switch { case len(s1) < len(s2): return true case len(s2) < len(s1): return false default: return s1 < s2 } default: return s1 < s2 } } go-versions-1.0.1/versions/version_test.go000066400000000000000000000016261374017526700207300ustar00rootroot00000000000000package versions import ( "encoding/json" "testing" "github.com/go-test/deep" ) func TestVersionJSON(t *testing.T) { v := Version{ Major: 1, Minor: 2, Patch: 3, Prerelease: "beta.1", Metadata: "tci.12345", } j, err := json.Marshal(v) if err != nil { t.Fatal(err) } if got, want := string(j), `"1.2.3-beta.1+tci.12345"`; got != want { t.Errorf("wrong result\ngot: %s\nwant: %s", got, want) } var v2 Version err = json.Unmarshal(j, &v2) if err != nil { t.Fatal(err) } for _, problem := range deep.Equal(v2, v) { t.Error(problem) } bad := []byte(`"garbage"`) err = json.Unmarshal(bad, &v2) var errText string if err != nil { errText = err.Error() } if got, want := errText, `invalid specification; required format is three positive integers separated by periods`; got != want { t.Errorf("wrong error for garbage\ngot: %s\nwant: %s", got, want) } }