From 4f921831ae0370fe5786cd8c8dbd78c3a8799559 Mon Sep 17 00:00:00 2001 From: Gareth Jones Date: Sat, 24 Feb 2024 08:02:06 +1300 Subject: [PATCH] feat: add `gsub_file!` method --- lib/thor/actions/file_manipulation.rb | 26 +++++++ spec/actions/file_manipulation_spec.rb | 98 ++++++++++++++++++++++++++ 2 files changed, 124 insertions(+) diff --git a/lib/thor/actions/file_manipulation.rb b/lib/thor/actions/file_manipulation.rb index abd05ca7..0bccba41 100644 --- a/lib/thor/actions/file_manipulation.rb +++ b/lib/thor/actions/file_manipulation.rb @@ -243,6 +243,32 @@ def inject_into_module(path, module_name, *args, &block) insert_into_file(path, *(args << config), &block) end + # Run a regular expression replacement on a file, raising an error if the + # contents of the file are not changed. + # + # ==== Parameters + # path:: path of the file to be changed + # flag:: the regexp or string to be replaced + # replacement:: the replacement, can be also given as a block + # config:: give :verbose => false to not log the status, and + # :force => true, to force the replacement regardless of runner behavior. + # + # ==== Example + # + # gsub_file 'app/controllers/application_controller.rb', /#\s*(filter_parameter_logging :password)/, '\1' + # + # gsub_file 'README', /rake/, :green do |match| + # match << " no more. Use thor!" + # end + # + def gsub_file!(path, flag, *args, &block) + config = args.last.is_a?(Hash) ? args.pop : {} + + config[:error_on_no_change] = true + + gsub_file(path, flag, *args, config, &block) + end + # Run a regular expression replacement on a file. # # ==== Parameters diff --git a/spec/actions/file_manipulation_spec.rb b/spec/actions/file_manipulation_spec.rb index 5277d5b0..930883b0 100644 --- a/spec/actions/file_manipulation_spec.rb +++ b/spec/actions/file_manipulation_spec.rb @@ -293,6 +293,104 @@ def file end end + describe "#gsub_file!" do + context "with invoke behavior" do + it "replaces the content in the file" do + action :gsub_file!, "doc/README", "__start__", "START" + expect(File.binread(file)).to eq("START\nREADME\n__end__\n") + end + + it "does not replace if pretending" do + runner(pretend: true) + action :gsub_file!, "doc/README", "__start__", "START" + expect(File.binread(file)).to eq("__start__\nREADME\n__end__\n") + end + + it "accepts a block" do + action(:gsub_file!, "doc/README", "__start__") { |match| match.gsub("__", "").upcase } + expect(File.binread(file)).to eq("START\nREADME\n__end__\n") + end + + it "logs status" do + expect(action(:gsub_file!, "doc/README", "__start__", "START")).to eq(" gsub doc/README\n") + end + + it "does not log status if required" do + expect(action(:gsub_file!, file, "__", verbose: false) { |match| match * 2 }).to be_empty + end + + it "cares if the file contents did not change" do + expect do + action :gsub_file!, "doc/README", "___start___", "START" + end.to raise_error(Thor::Error) + + expect(File.binread(file)).to eq("__start__\nREADME\n__end__\n") + end + end + + context "with revoke behavior" do + context "and no force option" do + it "does not replace the content in the file" do + runner({}, :revoke) + action :gsub_file!, "doc/README", "__start__", "START" + expect(File.binread(file)).to eq("__start__\nREADME\n__end__\n") + end + + it "does not replace if pretending" do + runner({pretend: true}, :revoke) + action :gsub_file!, "doc/README", "__start__", "START" + expect(File.binread(file)).to eq("__start__\nREADME\n__end__\n") + end + + it "does not replace the content in the file when given a block" do + runner({}, :revoke) + action(:gsub_file!, "doc/README", "__start__") { |match| match.gsub("__", "").upcase } + expect(File.binread(file)).to eq("__start__\nREADME\n__end__\n") + end + + it "does not log status" do + runner({}, :revoke) + expect(action(:gsub_file!, "doc/README", "__start__", "START")).to be_empty + end + + it "does not log status if required" do + runner({}, :revoke) + expect(action(:gsub_file!, file, "__", verbose: false) { |match| match * 2 }).to be_empty + end + end + + context "and force option" do + it "replaces the content in the file" do + runner({}, :revoke) + action :gsub_file!, "doc/README", "__start__", "START", force: true + expect(File.binread(file)).to eq("START\nREADME\n__end__\n") + end + + it "does not replace if pretending" do + runner({pretend: true}, :revoke) + action :gsub_file!, "doc/README", "__start__", "START", force: true + expect(File.binread(file)).to eq("__start__\nREADME\n__end__\n") + end + + it "replaces the content in the file when given a block" do + runner({}, :revoke) + action(:gsub_file!, "doc/README", "__start__", force: true) { |match| match.gsub("__", "").upcase } + expect(File.binread(file)).to eq("START\nREADME\n__end__\n") + end + + it "logs status" do + runner({}, :revoke) + expect(action(:gsub_file!, "doc/README", "__start__", "START", force: true)).to eq(" gsub doc/README\n") + end + + it "does not log status if required" do + runner({}, :revoke) + expect(action(:gsub_file!, file, "__", verbose: false, force: true) { |match| match * 2 }).to be_empty + end + end + end + end + describe "#gsub_file" do context "with invoke behavior" do it "replaces the content in the file" do