Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support intersection of versions #394

Merged
merged 2 commits into from
May 21, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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