Skip to content

Commit

Permalink
Support intersection of versions (crystal-lang#394)
Browse files Browse the repository at this point in the history
  • Loading branch information
waj authored and taylor committed Aug 11, 2020
1 parent 8220268 commit f040e8f
Show file tree
Hide file tree
Showing 7 changed files with 60 additions and 15 deletions.
3 changes: 2 additions & 1 deletion SPEC.md
Original file line number Diff line number Diff line change
Expand Up @@ -167,8 +167,9 @@ A version requirement (String).
- It may be a version number.
- It may be `"*"` if any version will do.
- The version number may be prefixed by an operator: `<`, `<=`, `>`, `>=` or `~>`.
- Multiple requirements can be separated by commas.

Examples: `1.2.3`, `>= 1.0.0` or `~> 2.0`.
Examples: `1.2.3`, `>= 1.0.0`, `>= 1.0.0, < 2.0` or `~> 2.0`.

Most of the version operators, like `>= 1.0.0`, are self-explanatory, but
the `~>` operator has a special meaning, best shown by example:
Expand Down
8 changes: 8 additions & 0 deletions spec/integration/install_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,14 @@ describe "install" do
end
end

it "resolves intersection" do
metadata = {dependencies: {web: ">= 1.1.0, < 2.0"}}
with_shard(metadata) do
run "shards install"
assert_installed "web", "1.2.0"
end
end

it "fails when spec is missing" do
Dir.cd(application_path) do
ex = expect_raises(FailedCommand) { run "shards install --no-color" }
Expand Down
23 changes: 23 additions & 0 deletions spec/unit/version_req_spec.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
require "./spec_helper"

module Shards
describe VersionReq do
it "parses" do
VersionReq.new("~> 1.0").patterns.should eq(["~> 1.0"])
VersionReq.new("~> 1.0, < 1.8").patterns.should eq(["~> 1.0", "< 1.8"])
VersionReq.new("~> 1.0,, < 1.8").patterns.should eq(["~> 1.0", "< 1.8"])
end

it "to_s" do
VersionReq.new("~> 1.0").to_s.should eq("~> 1.0")
VersionReq.new("~> 1.0,< 1.8").to_s.should eq("~> 1.0, < 1.8")
end

it "prerelease?" do
VersionReq.new("~> 1.0").prerelease?.should be_false
VersionReq.new("~> 1.0-a").prerelease?.should be_true
VersionReq.new("~> 1.0, < 1.8").prerelease?.should be_false
VersionReq.new("~> 1.0, < 1.8-a").prerelease?.should be_true
end
end
end
9 changes: 9 additions & 0 deletions spec/unit/versions_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,12 @@ module Shards
resolve(["0.1"], "~> 0.1.0").should eq(["0.1"])
end

it "resolve intersection" do
versions = %w(0.0.1 0.1.0 0.1.1 0.1.2 0.2.0 0.10.0)

resolve(versions, ">= 0.1.0, < 0.2.0").should eq(["0.1.0", "0.1.1", "0.1.2"])
end

it "matches?" do
matches?("0.1.0", "*").should be_true
matches?("1.0.0", "*").should be_true
Expand Down Expand Up @@ -218,6 +224,9 @@ module Shards
matches?("1.0.0", "~> 1.1").should be_false
matches?("1.0.1", "~> 1.0.0").should be_true
matches?("1.0.0", "~> 1.0.1").should be_false

matches?("1.0.0", "> 0.1.0, < 1.0.1").should be_true
matches?("1.0.1", "> 0.1.0, < 1.0.1").should be_false
end
end
end
2 changes: 1 addition & 1 deletion src/dependency.cr
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ module Shards
resolver = resolver_data[:type].find_resolver(resolver_data[:key], name, resolver_data[:source])
requirement = resolver.parse_requirement(params)
if is_lock && requirement.is_a?(VersionReq)
requirement = Version.new(requirement.pattern)
requirement = Version.new(requirement.to_s)
end

Dependency.new(name, resolver, requirement)
Expand Down
13 changes: 8 additions & 5 deletions src/requirement.cr
Original file line number Diff line number Diff line change
@@ -1,21 +1,24 @@
module Shards
struct VersionReq
getter pattern : String
getter patterns : Array(String)

def initialize(@pattern)
def initialize(patterns)
@patterns = patterns.split(',', remove_empty: true).map &.strip
end

def prerelease?
Versions.prerelease? @pattern
patterns.any? do |pattern|
Versions.prerelease? pattern
end
end

def to_s(io)
io << pattern
patterns.join(", ", io)
end

def to_yaml(yaml)
yaml.scalar "version"
yaml.scalar @pattern
yaml.scalar to_s
end
end

Expand Down
17 changes: 9 additions & 8 deletions src/versions.cr
Original file line number Diff line number Diff line change
Expand Up @@ -167,16 +167,17 @@ module Shards
end

def self.resolve(versions : Array(Version), requirement : VersionReq)
case requirement.pattern
when "*", ""
versions
else
versions.select { |version| matches?(version, requirement) }
end
versions.select { |version| matches?(version, requirement) }
end

def self.matches?(version : Version, requirement : VersionReq)
case requirement.pattern
requirement.patterns.all? do |pattern|
matches_single_pattern?(version, pattern)
end
end

private def self.matches_single_pattern?(version : Version, pattern : String)
case pattern
when "*", ""
true
when /~>\s*([^\s]+)\d*/
Expand All @@ -189,7 +190,7 @@ module Shards
when /\s*(~>|>=|<=|>|<|=)\s*([^~<>=\s]+)\s*/
matches_operator?(version.value, $1, $2)
else
matches_operator?(version.value, "=", requirement.pattern)
matches_operator?(version.value, "=", pattern)
end
end

Expand Down

0 comments on commit f040e8f

Please sign in to comment.