Skip to content

Commit 4c2cafa

Browse files
hsbtclaude
andcommitted
Add Bundler::Override value object
Introduce a value object that holds a target/field/operation triple for the upcoming Gemfile `override` DSL. apply_to dispatches on the operation: a version spec string replaces the requirement absolutely, :ignore_upper strips < and <= while folding ~> into >=, and nil collapses to Gem::Requirement.default. The :ignore_upper logic is taken from Shopify/bundler-ignore-dependency. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 2a47650 commit 4c2cafa

5 files changed

Lines changed: 119 additions & 0 deletions

File tree

Manifest.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,7 @@ bundler/lib/bundler/match_platform.rb
156156
bundler/lib/bundler/match_remote_metadata.rb
157157
bundler/lib/bundler/materialization.rb
158158
bundler/lib/bundler/mirror.rb
159+
bundler/lib/bundler/override.rb
159160
bundler/lib/bundler/plugin.rb
160161
bundler/lib/bundler/plugin/api.rb
161162
bundler/lib/bundler/plugin/api/source.rb

bundler/lib/bundler.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ module Bundler
6262
autoload :MatchRemoteMetadata, File.expand_path("bundler/match_remote_metadata", __dir__)
6363
autoload :Materialization, File.expand_path("bundler/materialization", __dir__)
6464
autoload :NULL, File.expand_path("bundler/constants", __dir__)
65+
autoload :Override, File.expand_path("bundler/override", __dir__)
6566
autoload :ProcessLock, File.expand_path("bundler/process_lock", __dir__)
6667
autoload :RemoteSpecification, File.expand_path("bundler/remote_specification", __dir__)
6768
autoload :Resolver, File.expand_path("bundler/resolver", __dir__)

bundler/lib/bundler/override.rb

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
# frozen_string_literal: true
2+
3+
module Bundler
4+
class Override
5+
LOWER_BOUND_OPERATORS = [">=", ">", "="].freeze
6+
7+
attr_reader :target, :field, :operation
8+
9+
def initialize(target, field, operation)
10+
@target = target
11+
@field = field
12+
@operation = operation
13+
end
14+
15+
def apply_to(requirement)
16+
case operation
17+
when nil
18+
Gem::Requirement.default
19+
when :ignore_upper
20+
remove_upper_bounds(requirement)
21+
when String
22+
Gem::Requirement.new(operation)
23+
else
24+
raise ArgumentError, "unsupported override operation: #{operation.inspect}"
25+
end
26+
end
27+
28+
private
29+
30+
def remove_upper_bounds(requirement)
31+
return Gem::Requirement.default if requirement.nil? || requirement.none?
32+
33+
lower_bounds = requirement.requirements.filter_map do |op, version|
34+
if LOWER_BOUND_OPERATORS.include?(op)
35+
[op, version]
36+
elsif op == "~>"
37+
[">=", version]
38+
end
39+
end
40+
41+
return Gem::Requirement.default if lower_bounds.empty?
42+
43+
Gem::Requirement.new(lower_bounds.map {|op, v| "#{op} #{v}" })
44+
end
45+
end
46+
end

spec/bundler/override_spec.rb

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
# frozen_string_literal: true
2+
3+
RSpec.describe Bundler::Override do
4+
describe "#apply_to" do
5+
context "when operation is a version spec string" do
6+
it "replaces the existing requirement entirely" do
7+
override = described_class.new("rails", :version, ">= 8.0")
8+
result = override.apply_to(Gem::Requirement.new(">= 1.0", "< 2.0"))
9+
expect(result).to eq(Gem::Requirement.new(">= 8.0"))
10+
end
11+
12+
it "ignores the existing requirement regardless of its content" do
13+
override = described_class.new("rails", :version, "= 1.0")
14+
result = override.apply_to(Gem::Requirement.new(">= 99.0"))
15+
expect(result).to eq(Gem::Requirement.new("= 1.0"))
16+
end
17+
end
18+
19+
context "when operation is :ignore_upper" do
20+
it "removes < and <= operators" do
21+
override = described_class.new("rails", :version, :ignore_upper)
22+
result = override.apply_to(Gem::Requirement.new(">= 1.0", "< 2.0"))
23+
expect(result).to eq(Gem::Requirement.new(">= 1.0"))
24+
end
25+
26+
it "keeps >, >=, = operators" do
27+
override = described_class.new("rails", :version, :ignore_upper)
28+
result = override.apply_to(Gem::Requirement.new("> 1.0", "<= 2.0"))
29+
expect(result).to eq(Gem::Requirement.new("> 1.0"))
30+
end
31+
32+
it "converts ~> to >= preserving the lower bound" do
33+
override = described_class.new("rails", :version, :ignore_upper)
34+
result = override.apply_to(Gem::Requirement.new("~> 1.5"))
35+
expect(result).to eq(Gem::Requirement.new(">= 1.5"))
36+
end
37+
38+
it "returns the default requirement when only upper bounds remain" do
39+
override = described_class.new("rails", :version, :ignore_upper)
40+
result = override.apply_to(Gem::Requirement.new("< 2.0"))
41+
expect(result).to eq(Gem::Requirement.default)
42+
end
43+
44+
it "returns the default requirement when the input is nil" do
45+
override = described_class.new("rails", :version, :ignore_upper)
46+
expect(override.apply_to(nil)).to eq(Gem::Requirement.default)
47+
end
48+
49+
it "returns the default requirement when the input is already the default" do
50+
override = described_class.new("rails", :version, :ignore_upper)
51+
expect(override.apply_to(Gem::Requirement.default)).to eq(Gem::Requirement.default)
52+
end
53+
end
54+
55+
context "when operation is nil" do
56+
it "returns the default requirement" do
57+
override = described_class.new("rails", :version, nil)
58+
result = override.apply_to(Gem::Requirement.new(">= 1.0", "< 2.0"))
59+
expect(result).to eq(Gem::Requirement.default)
60+
end
61+
end
62+
63+
context "when operation is unsupported" do
64+
it "raises ArgumentError" do
65+
override = described_class.new("rails", :version, 42)
66+
expect { override.apply_to(Gem::Requirement.default) }.to raise_error(ArgumentError, /unsupported override operation/)
67+
end
68+
end
69+
end
70+
end

spec/support/windows_tag_group.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,7 @@ module WindowsTagGroup
189189
"spec/bundler/uri_normalizer_spec.rb",
190190
"spec/install/gems/no_build_extension_spec.rb",
191191
"spec/install/gems/no_install_plugin_spec.rb",
192+
"spec/bundler/override_spec.rb",
192193
],
193194
}.freeze
194195
end

0 commit comments

Comments
 (0)