Merge pull request #18614 from Homebrew/inreplace-non-global

utils/inreplace: allow non-global substitution
This commit is contained in:
Michael Cho 2024-10-31 14:05:27 -04:00 committed by GitHub
commit f18e1ea86a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 112 additions and 41 deletions

View File

@ -11,11 +11,13 @@ RSpec.describe Utils::Inreplace do
a a
b b
c c
aa
EOS EOS
end end
after { file.unlink } after { file.unlink }
describe ".inreplace" do
it "raises error if there are no files given to replace" do it "raises error if there are no files given to replace" do
expect do expect do
described_class.inreplace [], "d", "f" described_class.inreplace [], "d", "f"
@ -45,15 +47,6 @@ RSpec.describe Utils::Inreplace do
end.to raise_error(Utils::Inreplace::Error) end.to raise_error(Utils::Inreplace::Error)
end end
describe "#inreplace_pairs" do
it "raises error if there is no old value" do
expect do
described_class.inreplace_pairs(file.path, [[nil, "f"]])
end.to raise_error(Utils::Inreplace::Error)
end
end
describe "#gsub!" do
it "substitutes pathname within file" do it "substitutes pathname within file" do
# For a specific instance of this, see https://github.com/Homebrew/homebrew-core/blob/a8b0b10/Formula/loki.rb#L48 # For a specific instance of this, see https://github.com/Homebrew/homebrew-core/blob/a8b0b10/Formula/loki.rb#L48
described_class.inreplace(file.path) do |s| described_class.inreplace(file.path) do |s|
@ -63,7 +56,74 @@ RSpec.describe Utils::Inreplace do
a a
f f
c c
aa
EOS
end
it "substitutes all occurrences within file when `global: true`" do
described_class.inreplace(file.path, "a", "foo")
expect(File.binread(file)).to eq <<~EOS
foo
b
c
foofoo
EOS
end
it "substitutes only the first occurrence when `global: false`" do
described_class.inreplace(file.path, "a", "foo", global: false)
expect(File.binread(file)).to eq <<~EOS
foo
b
c
aa
EOS EOS
end end
end end
describe ".inreplace_pairs" do
it "raises error if there is no old value" do
expect do
described_class.inreplace_pairs(file.path, [[nil, "f"]])
end.to raise_error(Utils::Inreplace::Error)
end
it "substitutes returned string but not file when `read_only_run: true`" do
expect(described_class.inreplace_pairs(file.path, [["a", "foo"]], read_only_run: true)).to eq <<~EOS
foo
b
c
foofoo
EOS
expect(File.binread(file)).to eq <<~EOS
a
b
c
aa
EOS
end
it "substitutes both returned string and file when `read_only_run: false`" do
replace_result = <<~TEXT
foo
b
c
foofoo
TEXT
expect(described_class.inreplace_pairs(file.path, [["a", "foo"]])).to eq replace_result
expect(File.binread(file)).to eq replace_result
end
it "substitutes multiple pairs in order" do
pairs = [["a", "b"], ["bb", "test"], ["b", "z"]]
replace_result = <<~TEXT
z
z
c
test
TEXT
expect(described_class.inreplace_pairs(file.path, pairs)).to eq replace_result
expect(File.binread(file)).to eq replace_result
end
end
end end

View File

@ -249,12 +249,20 @@ RSpec.describe StringInreplaceExtension do
it "replaces the first occurrence" do it "replaces the first occurrence" do
string_extension.sub!("o", "e") string_extension.sub!("o", "e")
expect(string_extension.inreplace_string).to eq("feo") expect(string_extension.inreplace_string).to eq("feo")
expect(string_extension.errors).to be_empty
end end
it "adds an error to #errors when no replacement was made" do it "adds an error to #errors when no replacement was made" do
string_extension.sub! "not here", "test" string_extension.sub! "not here", "test"
expect(string_extension.inreplace_string).to eq(string)
expect(string_extension.errors).to eq(['expected replacement of "not here" with "test"']) expect(string_extension.errors).to eq(['expected replacement of "not here" with "test"'])
end end
it "doesn't add an error to #errors when no replace was made and `audit_result: false`" do
string_extension.sub! "not here", "test", audit_result: false
expect(string_extension.inreplace_string).to eq(string)
expect(string_extension.errors).to be_empty
end
end end
describe "#gsub!" do describe "#gsub!" do

View File

@ -47,10 +47,11 @@ module Utils
before: T.nilable(T.any(Pathname, Regexp, String)), before: T.nilable(T.any(Pathname, Regexp, String)),
after: T.nilable(T.any(Pathname, String, Symbol)), after: T.nilable(T.any(Pathname, String, Symbol)),
audit_result: T::Boolean, audit_result: T::Boolean,
global: T::Boolean,
block: T.nilable(T.proc.params(s: StringInreplaceExtension).void), block: T.nilable(T.proc.params(s: StringInreplaceExtension).void),
).void ).void
} }
def self.inreplace(paths, before = nil, after = nil, audit_result: true, &block) def self.inreplace(paths, before = nil, after = nil, audit_result: true, global: true, &block)
paths = Array(paths) paths = Array(paths)
after &&= after.to_s after &&= after.to_s
before = before.to_s if before.is_a?(Pathname) before = before.to_s if before.is_a?(Pathname)
@ -67,8 +68,10 @@ module Utils
raise ArgumentError, "Must supply a block or before/after params" unless block raise ArgumentError, "Must supply a block or before/after params" unless block
yield s yield s
else elsif global
s.gsub!(T.must(before), T.must(after), audit_result:) s.gsub!(T.must(before), T.must(after), audit_result:)
else
s.sub!(T.must(before), T.must(after), audit_result:)
end end
errors[path] = s.errors unless s.errors.empty? errors[path] = s.errors unless s.errors.empty?

View File

@ -1,7 +1,7 @@
# typed: strict # typed: strong
# frozen_string_literal: true # frozen_string_literal: true
# Used by the `inreplace` function (in `utils.rb`). # Used by the {Utils::Inreplace.inreplace} function.
class StringInreplaceExtension class StringInreplaceExtension
sig { returns(T::Array[String]) } sig { returns(T::Array[String]) }
attr_accessor :errors attr_accessor :errors
@ -18,10 +18,10 @@ class StringInreplaceExtension
# Same as `String#sub!`, but warns if nothing was replaced. # Same as `String#sub!`, but warns if nothing was replaced.
# #
# @api public # @api public
sig { params(before: T.any(Regexp, String), after: String).returns(T.nilable(String)) } sig { params(before: T.any(Regexp, String), after: String, audit_result: T::Boolean).returns(T.nilable(String)) }
def sub!(before, after) def sub!(before, after, audit_result: true)
result = inreplace_string.sub!(before, after) result = inreplace_string.sub!(before, after)
errors << "expected replacement of #{before.inspect} with #{after.inspect}" unless result errors << "expected replacement of #{before.inspect} with #{after.inspect}" if audit_result && result.nil?
result result
end end