Skip to content

Commit c049fb6

Browse files
authored
Merge pull request #908 from G-Rath/new-inject-into-file-helper
feat: support `insert_into_file` erroring if the file is not changed, and add `insert_into_file`
2 parents 6dfb6e2 + 93f4b62 commit c049fb6

File tree

2 files changed

+197
-2
lines changed

2 files changed

+197
-2
lines changed

lib/thor/actions/inject_into_file.rb

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,38 @@
22

33
class Thor
44
module Actions
5+
WARNINGS = {unchanged_no_flag: "File unchanged! Either the supplied flag value not found or the content has already been inserted!"}
6+
7+
# Injects the given content into a file, raising an error if the contents of
8+
# the file are not changed. Different from gsub_file, this method is reversible.
9+
#
10+
# ==== Parameters
11+
# destination<String>:: Relative path to the destination root
12+
# data<String>:: Data to add to the file. Can be given as a block.
13+
# config<Hash>:: give :verbose => false to not log the status and the flag
14+
# for injection (:after or :before) or :force => true for
15+
# insert two or more times the same content.
16+
#
17+
# ==== Examples
18+
#
19+
# insert_into_file "config/environment.rb", "config.gem :thor", :after => "Rails::Initializer.run do |config|\n"
20+
#
21+
# insert_into_file "config/environment.rb", :after => "Rails::Initializer.run do |config|\n" do
22+
# gems = ask "Which gems would you like to add?"
23+
# gems.split(" ").map{ |gem| " config.gem :#{gem}" }.join("\n")
24+
# end
25+
#
26+
def insert_into_file!(destination, *args, &block)
27+
data = block_given? ? block : args.shift
28+
29+
config = args.shift || {}
30+
config[:after] = /\z/ unless config.key?(:before) || config.key?(:after)
31+
config = config.merge({error_on_no_change: true})
32+
33+
action InjectIntoFile.new(self, destination, data, config)
34+
end
35+
alias_method :inject_into_file!, :insert_into_file!
36+
537
# Injects the given content into a file. Different from gsub_file, this
638
# method is reversible.
739
#
@@ -21,8 +53,6 @@ module Actions
2153
# gems.split(" ").map{ |gem| " config.gem :#{gem}" }.join("\n")
2254
# end
2355
#
24-
WARNINGS = {unchanged_no_flag: "File unchanged! Either the supplied flag value not found or the content has already been inserted!"}
25-
2656
def insert_into_file(destination, *args, &block)
2757
data = block_given? ? block : args.shift
2858

@@ -47,6 +77,7 @@ def initialize(base, destination, data, config)
4777

4878
@replacement = data.is_a?(Proc) ? data.call : data
4979
@flag = Regexp.escape(@flag) unless @flag.is_a?(Regexp)
80+
@error_on_no_change = @config.fetch(:error_on_no_change, false)
5081
end
5182

5283
def invoke!
@@ -59,6 +90,8 @@ def invoke!
5990
if exists?
6091
if replace!(/#{flag}/, content, config[:force])
6192
say_status(:invoke)
93+
elsif @error_on_no_change
94+
raise Thor::Error, "The content of #{destination} did not change"
6295
elsif replacement_present?
6396
say_status(:unchanged, color: :blue)
6497
else

spec/actions/inject_into_file_spec.rb

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@ def invoke!(*args, &block)
2020
capture(:stdout) { invoker.insert_into_file(*args, &block) }
2121
end
2222

23+
def invoke_with_a_bang!(*args, &block)
24+
capture(:stdout) { invoker.insert_into_file!(*args, &block) }
25+
end
26+
2327
def revoke!(*args, &block)
2428
capture(:stdout) { revoker.insert_into_file(*args, &block) }
2529
end
@@ -170,6 +174,164 @@ def file
170174
end
171175
end
172176
end
177+
178+
context "with a bang" do
179+
it "changes the file adding content after the flag" do
180+
invoke_with_a_bang! "doc/README", "\nmore content", after: "__start__"
181+
expect(File.read(file)).to eq("__start__\nmore content\nREADME\n__end__\n")
182+
end
183+
184+
it "changes the file adding content before the flag" do
185+
invoke_with_a_bang! "doc/README", "more content\n", before: "__end__"
186+
expect(File.read(file)).to eq("__start__\nREADME\nmore content\n__end__\n")
187+
end
188+
189+
it "appends content to the file if before and after arguments not provided" do
190+
invoke_with_a_bang!("doc/README", "more content\n")
191+
expect(File.read(file)).to eq("__start__\nREADME\n__end__\nmore content\n")
192+
end
193+
194+
it "does not change the file and raises an error if replacement present in the file" do
195+
invoke_with_a_bang!("doc/README", "more specific content\n")
196+
expect do
197+
invoke_with_a_bang!("doc/README", "more specific content\n")
198+
end.to raise_error(Thor::Error, "The content of #{destination_root}/doc/README did not change")
199+
end
200+
201+
it "does not change the file and raises an error if flag not found in the file" do
202+
expect do
203+
invoke_with_a_bang!("doc/README", "more content\n", after: "whatever")
204+
end.to raise_error(Thor::Error, "The content of #{destination_root}/doc/README did not change")
205+
end
206+
207+
it "accepts data as a block" do
208+
invoke_with_a_bang! "doc/README", before: "__end__" do
209+
"more content\n"
210+
end
211+
212+
expect(File.read(file)).to eq("__start__\nREADME\nmore content\n__end__\n")
213+
end
214+
215+
it "logs status" do
216+
expect(invoke_with_a_bang!("doc/README", "\nmore content", after: "__start__")).to eq(" insert doc/README\n")
217+
end
218+
219+
it "logs status if pretending" do
220+
invoker(pretend: true)
221+
expect(invoke_with_a_bang!("doc/README", "\nmore content", after: "__start__")).to eq(" insert doc/README\n")
222+
end
223+
224+
it "does not change the file if pretending" do
225+
invoker pretend: true
226+
invoke_with_a_bang! "doc/README", "\nmore content", after: "__start__"
227+
expect(File.read(file)).to eq("__start__\nREADME\n__end__\n")
228+
end
229+
230+
it "does not change the file and raises an error if already includes content" do
231+
invoke_with_a_bang! "doc/README", before: "__end__" do
232+
"more content\n"
233+
end
234+
235+
expect(File.read(file)).to eq("__start__\nREADME\nmore content\n__end__\n")
236+
237+
expect do
238+
invoke_with_a_bang! "doc/README", before: "__end__" do
239+
"more content\n"
240+
end
241+
end.to raise_error(Thor::Error, "The content of #{destination_root}/doc/README did not change")
242+
243+
expect(File.read(file)).to eq("__start__\nREADME\nmore content\n__end__\n")
244+
end
245+
246+
it "does not change the file and raises an error if already includes content using before with capture" do
247+
invoke_with_a_bang! "doc/README", before: /(__end__)/ do
248+
"more content\n"
249+
end
250+
251+
expect(File.read(file)).to eq("__start__\nREADME\nmore content\n__end__\n")
252+
253+
expect do
254+
invoke_with_a_bang! "doc/README", before: /(__end__)/ do
255+
"more content\n"
256+
end
257+
end.to raise_error(Thor::Error, "The content of #{destination_root}/doc/README did not change")
258+
259+
expect(File.read(file)).to eq("__start__\nREADME\nmore content\n__end__\n")
260+
end
261+
262+
it "does not change the file and raises an error if already includes content using after with capture" do
263+
invoke_with_a_bang! "doc/README", after: /(README\n)/ do
264+
"more content\n"
265+
end
266+
267+
expect(File.read(file)).to eq("__start__\nREADME\nmore content\n__end__\n")
268+
269+
expect do
270+
invoke_with_a_bang! "doc/README", after: /(README\n)/ do
271+
"more content\n"
272+
end
273+
end.to raise_error(Thor::Error, "The content of #{destination_root}/doc/README did not change")
274+
275+
expect(File.read(file)).to eq("__start__\nREADME\nmore content\n__end__\n")
276+
end
277+
278+
it "does not attempt to change the file if it doesn't exist - instead raises Thor::Error" do
279+
expect do
280+
invoke_with_a_bang! "idontexist", before: "something" do
281+
"any content"
282+
end
283+
end.to raise_error(Thor::Error, /does not appear to exist/)
284+
expect(File.exist?("idontexist")).to be_falsey
285+
end
286+
287+
it "does not attempt to change the file if it doesn't exist and pretending" do
288+
expect do
289+
invoker pretend: true
290+
invoke_with_a_bang! "idontexist", before: "something" do
291+
"any content"
292+
end
293+
end.not_to raise_error
294+
expect(File.exist?("idontexist")).to be_falsey
295+
end
296+
297+
it "does change the file if already includes content and :force is true" do
298+
invoke_with_a_bang! "doc/README", before: "__end__" do
299+
"more content\n"
300+
end
301+
302+
expect(File.read(file)).to eq("__start__\nREADME\nmore content\n__end__\n")
303+
304+
invoke_with_a_bang! "doc/README", before: "__end__", force: true do
305+
"more content\n"
306+
end
307+
308+
expect(File.read(file)).to eq("__start__\nREADME\nmore content\nmore content\n__end__\n")
309+
end
310+
311+
it "can insert chinese" do
312+
encoding_original = Encoding.default_external
313+
314+
begin
315+
silence_warnings do
316+
Encoding.default_external = Encoding.find("UTF-8")
317+
end
318+
invoke_with_a_bang! "doc/README.zh", "\n中文", after: "__start__"
319+
expect(File.read(File.join(destination_root, "doc/README.zh"))).to eq("__start__\n中文\n说明\n__end__\n")
320+
ensure
321+
silence_warnings do
322+
Encoding.default_external = encoding_original
323+
end
324+
end
325+
end
326+
327+
it "does not mutate the provided config" do
328+
config = {after: "__start__"}
329+
invoke_with_a_bang! "doc/README", "\nmore content", config
330+
expect(File.read(file)).to eq("__start__\nmore content\nREADME\n__end__\n")
331+
332+
expect(config).to eq({after: "__start__"})
333+
end
334+
end
173335
end
174336

175337
describe "#revoke!" do

0 commit comments

Comments
 (0)