atli 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. checksums.yaml +7 -0
  2. data/.yardopts +8 -0
  3. data/CHANGELOG.md +193 -0
  4. data/CONTRIBUTING.md +20 -0
  5. data/LICENSE.md +24 -0
  6. data/README.md +44 -0
  7. data/atli.gemspec +30 -0
  8. data/bin/thor +6 -0
  9. data/lib/thor.rb +868 -0
  10. data/lib/thor/actions.rb +322 -0
  11. data/lib/thor/actions/create_file.rb +104 -0
  12. data/lib/thor/actions/create_link.rb +60 -0
  13. data/lib/thor/actions/directory.rb +118 -0
  14. data/lib/thor/actions/empty_directory.rb +143 -0
  15. data/lib/thor/actions/file_manipulation.rb +364 -0
  16. data/lib/thor/actions/inject_into_file.rb +109 -0
  17. data/lib/thor/base.rb +773 -0
  18. data/lib/thor/command.rb +192 -0
  19. data/lib/thor/core_ext/hash_with_indifferent_access.rb +97 -0
  20. data/lib/thor/core_ext/io_binary_read.rb +12 -0
  21. data/lib/thor/core_ext/ordered_hash.rb +129 -0
  22. data/lib/thor/error.rb +32 -0
  23. data/lib/thor/group.rb +281 -0
  24. data/lib/thor/invocation.rb +182 -0
  25. data/lib/thor/line_editor.rb +17 -0
  26. data/lib/thor/line_editor/basic.rb +37 -0
  27. data/lib/thor/line_editor/readline.rb +88 -0
  28. data/lib/thor/parser.rb +5 -0
  29. data/lib/thor/parser/argument.rb +70 -0
  30. data/lib/thor/parser/arguments.rb +175 -0
  31. data/lib/thor/parser/option.rb +146 -0
  32. data/lib/thor/parser/options.rb +221 -0
  33. data/lib/thor/parser/shared_option.rb +23 -0
  34. data/lib/thor/rake_compat.rb +71 -0
  35. data/lib/thor/runner.rb +324 -0
  36. data/lib/thor/shell.rb +81 -0
  37. data/lib/thor/shell/basic.rb +439 -0
  38. data/lib/thor/shell/color.rb +149 -0
  39. data/lib/thor/shell/html.rb +126 -0
  40. data/lib/thor/util.rb +268 -0
  41. data/lib/thor/version.rb +22 -0
  42. metadata +114 -0
@@ -0,0 +1,118 @@
1
+ require "thor/actions/empty_directory"
2
+
3
+ class Thor
4
+ module Actions
5
+ # Copies recursively the files from source directory to root directory.
6
+ # If any of the files finishes with .tt, it's considered to be a template
7
+ # and is placed in the destination without the extension .tt. If any
8
+ # empty directory is found, it's copied and all .empty_directory files are
9
+ # ignored. If any file name is wrapped within % signs, the text within
10
+ # the % signs will be executed as a method and replaced with the returned
11
+ # value. Let's suppose a doc directory with the following files:
12
+ #
13
+ # doc/
14
+ # components/.empty_directory
15
+ # README
16
+ # rdoc.rb.tt
17
+ # %app_name%.rb
18
+ #
19
+ # When invoked as:
20
+ #
21
+ # directory "doc"
22
+ #
23
+ # It will create a doc directory in the destination with the following
24
+ # files (assuming that the `app_name` method returns the value "blog"):
25
+ #
26
+ # doc/
27
+ # components/
28
+ # README
29
+ # rdoc.rb
30
+ # blog.rb
31
+ #
32
+ # <b>Encoded path note:</b> Since Thor internals use Object#respond_to? to check if it can
33
+ # expand %something%, this `something` should be a public method in the class calling
34
+ # #directory. If a method is private, Thor stack raises PrivateMethodEncodedError.
35
+ #
36
+ # ==== Parameters
37
+ # source<String>:: the relative path to the source root.
38
+ # destination<String>:: the relative path to the destination root.
39
+ # config<Hash>:: give :verbose => false to not log the status.
40
+ # If :recursive => false, does not look for paths recursively.
41
+ # If :mode => :preserve, preserve the file mode from the source.
42
+ # If :exclude_pattern => /regexp/, prevents copying files that match that regexp.
43
+ #
44
+ # ==== Examples
45
+ #
46
+ # directory "doc"
47
+ # directory "doc", "docs", :recursive => false
48
+ #
49
+ def directory(source, *args, &block)
50
+ config = args.last.is_a?(Hash) ? args.pop : {}
51
+ destination = args.first || source
52
+ action Directory.new(self, source, destination || source, config, &block)
53
+ end
54
+
55
+ class Directory < EmptyDirectory #:nodoc:
56
+ attr_reader :source
57
+
58
+ def initialize(base, source, destination = nil, config = {}, &block)
59
+ @source = File.expand_path(base.find_in_source_paths(source.to_s))
60
+ @block = block
61
+ super(base, destination, {:recursive => true}.merge(config))
62
+ end
63
+
64
+ def invoke!
65
+ base.empty_directory given_destination, config
66
+ execute!
67
+ end
68
+
69
+ def revoke!
70
+ execute!
71
+ end
72
+
73
+ protected
74
+
75
+ def execute!
76
+ lookup = Util.escape_globs(source)
77
+ lookup = config[:recursive] ? File.join(lookup, "**") : lookup
78
+ lookup = file_level_lookup(lookup)
79
+
80
+ files(lookup).sort.each do |file_source|
81
+ next if File.directory?(file_source)
82
+ next if config[:exclude_pattern] && file_source.match(config[:exclude_pattern])
83
+ file_destination = File.join(given_destination, file_source.gsub(source, "."))
84
+ file_destination.gsub!("/./", "/")
85
+
86
+ case file_source
87
+ when /\.empty_directory$/
88
+ dirname = File.dirname(file_destination).gsub(%r{/\.$}, "")
89
+ next if dirname == given_destination
90
+ base.empty_directory(dirname, config)
91
+ when /#{TEMPLATE_EXTNAME}$/
92
+ base.template(file_source, file_destination[0..-4], config, &@block)
93
+ else
94
+ base.copy_file(file_source, file_destination, config, &@block)
95
+ end
96
+ end
97
+ end
98
+
99
+ if RUBY_VERSION < "2.0"
100
+ def file_level_lookup(previous_lookup)
101
+ File.join(previous_lookup, "{*,.[a-z]*}")
102
+ end
103
+
104
+ def files(lookup)
105
+ Dir[lookup]
106
+ end
107
+ else
108
+ def file_level_lookup(previous_lookup)
109
+ File.join(previous_lookup, "*")
110
+ end
111
+
112
+ def files(lookup)
113
+ Dir.glob(lookup, File::FNM_DOTMATCH)
114
+ end
115
+ end
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,143 @@
1
+ class Thor
2
+ module Actions
3
+ # Creates an empty directory.
4
+ #
5
+ # ==== Parameters
6
+ # destination<String>:: the relative path to the destination root.
7
+ # config<Hash>:: give :verbose => false to not log the status.
8
+ #
9
+ # ==== Examples
10
+ #
11
+ # empty_directory "doc"
12
+ #
13
+ def empty_directory(destination, config = {})
14
+ action EmptyDirectory.new(self, destination, config)
15
+ end
16
+
17
+ # Class which holds create directory logic. This is the base class for
18
+ # other actions like create_file and directory.
19
+ #
20
+ # This implementation is based in Templater actions, created by Jonas Nicklas
21
+ # and Michael S. Klishin under MIT LICENSE.
22
+ #
23
+ class EmptyDirectory #:nodoc:
24
+ attr_reader :base, :destination, :given_destination, :relative_destination, :config
25
+
26
+ # Initializes given the source and destination.
27
+ #
28
+ # ==== Parameters
29
+ # base<Thor::Base>:: A Thor::Base instance
30
+ # source<String>:: Relative path to the source of this file
31
+ # destination<String>:: Relative path to the destination of this file
32
+ # config<Hash>:: give :verbose => false to not log the status.
33
+ #
34
+ def initialize(base, destination, config = {})
35
+ @base = base
36
+ @config = {:verbose => true}.merge(config)
37
+ self.destination = destination
38
+ end
39
+
40
+ # Checks if the destination file already exists.
41
+ #
42
+ # ==== Returns
43
+ # Boolean:: true if the file exists, false otherwise.
44
+ #
45
+ def exists?
46
+ ::File.exist?(destination)
47
+ end
48
+
49
+ def invoke!
50
+ invoke_with_conflict_check do
51
+ require "fileutils"
52
+ ::FileUtils.mkdir_p(destination)
53
+ end
54
+ end
55
+
56
+ def revoke!
57
+ say_status :remove, :red
58
+ require "fileutils"
59
+ ::FileUtils.rm_rf(destination) if !pretend? && exists?
60
+ given_destination
61
+ end
62
+
63
+ protected
64
+
65
+ # Shortcut for pretend.
66
+ #
67
+ def pretend?
68
+ base.options[:pretend]
69
+ end
70
+
71
+ # Sets the absolute destination value from a relative destination value.
72
+ # It also stores the given and relative destination. Let's suppose our
73
+ # script is being executed on "dest", it sets the destination root to
74
+ # "dest". The destination, given_destination and relative_destination
75
+ # are related in the following way:
76
+ #
77
+ # inside "bar" do
78
+ # empty_directory "baz"
79
+ # end
80
+ #
81
+ # destination #=> dest/bar/baz
82
+ # relative_destination #=> bar/baz
83
+ # given_destination #=> baz
84
+ #
85
+ def destination=(destination)
86
+ return unless destination
87
+ @given_destination = convert_encoded_instructions(destination.to_s)
88
+ @destination = ::File.expand_path(@given_destination, base.destination_root)
89
+ @relative_destination = base.relative_to_original_destination_root(@destination)
90
+ end
91
+
92
+ # Filenames in the encoded form are converted. If you have a file:
93
+ #
94
+ # %file_name%.rb
95
+ #
96
+ # It calls #file_name from the base and replaces %-string with the
97
+ # return value (should be String) of #file_name:
98
+ #
99
+ # user.rb
100
+ #
101
+ # The method referenced can be either public or private.
102
+ #
103
+ def convert_encoded_instructions(filename)
104
+ filename.gsub(/%(.*?)%/) do |initial_string|
105
+ method = $1.strip
106
+ base.respond_to?(method, true) ? base.send(method) : initial_string
107
+ end
108
+ end
109
+
110
+ # Receives a hash of options and just execute the block if some
111
+ # conditions are met.
112
+ #
113
+ def invoke_with_conflict_check(&block)
114
+ if exists?
115
+ on_conflict_behavior(&block)
116
+ else
117
+ yield unless pretend?
118
+ say_status :create, :green
119
+ end
120
+
121
+ destination
122
+ rescue Errno::EISDIR, Errno::EEXIST
123
+ on_file_clash_behavior
124
+ end
125
+
126
+ def on_file_clash_behavior
127
+ say_status :file_clash, :red
128
+ end
129
+
130
+ # What to do when the destination file already exists.
131
+ #
132
+ def on_conflict_behavior
133
+ say_status :exist, :blue
134
+ end
135
+
136
+ # Shortcut to say_status shell method.
137
+ #
138
+ def say_status(status, color)
139
+ base.shell.say_status status, relative_destination, color if config[:verbose]
140
+ end
141
+ end
142
+ end
143
+ end
@@ -0,0 +1,364 @@
1
+ require "erb"
2
+
3
+ class Thor
4
+ module Actions
5
+ # Copies the file from the relative source to the relative destination. If
6
+ # the destination is not given it's assumed to be equal to the source.
7
+ #
8
+ # ==== Parameters
9
+ # source<String>:: the relative path to the source root.
10
+ # destination<String>:: the relative path to the destination root.
11
+ # config<Hash>:: give :verbose => false to not log the status, and
12
+ # :mode => :preserve, to preserve the file mode from the source.
13
+
14
+ #
15
+ # ==== Examples
16
+ #
17
+ # copy_file "README", "doc/README"
18
+ #
19
+ # copy_file "doc/README"
20
+ #
21
+ def copy_file(source, *args, &block)
22
+ config = args.last.is_a?(Hash) ? args.pop : {}
23
+ destination = args.first || source
24
+ source = File.expand_path(find_in_source_paths(source.to_s))
25
+
26
+ create_file destination, nil, config do
27
+ content = File.binread(source)
28
+ content = yield(content) if block
29
+ content
30
+ end
31
+ if config[:mode] == :preserve
32
+ mode = File.stat(source).mode
33
+ chmod(destination, mode, config)
34
+ end
35
+ end
36
+
37
+ # Links the file from the relative source to the relative destination. If
38
+ # the destination is not given it's assumed to be equal to the source.
39
+ #
40
+ # ==== Parameters
41
+ # source<String>:: the relative path to the source root.
42
+ # destination<String>:: the relative path to the destination root.
43
+ # config<Hash>:: give :verbose => false to not log the status.
44
+ #
45
+ # ==== Examples
46
+ #
47
+ # link_file "README", "doc/README"
48
+ #
49
+ # link_file "doc/README"
50
+ #
51
+ def link_file(source, *args)
52
+ config = args.last.is_a?(Hash) ? args.pop : {}
53
+ destination = args.first || source
54
+ source = File.expand_path(find_in_source_paths(source.to_s))
55
+
56
+ create_link destination, source, config
57
+ end
58
+
59
+ # Gets the content at the given address and places it at the given relative
60
+ # destination. If a block is given instead of destination, the content of
61
+ # the url is yielded and used as location.
62
+ #
63
+ # ==== Parameters
64
+ # source<String>:: the address of the given content.
65
+ # destination<String>:: the relative path to the destination root.
66
+ # config<Hash>:: give :verbose => false to not log the status.
67
+ #
68
+ # ==== Examples
69
+ #
70
+ # get "http://gist.github.com/103208", "doc/README"
71
+ #
72
+ # get "http://gist.github.com/103208" do |content|
73
+ # content.split("\n").first
74
+ # end
75
+ #
76
+ def get(source, *args, &block)
77
+ config = args.last.is_a?(Hash) ? args.pop : {}
78
+ destination = args.first
79
+
80
+ if source =~ %r{^https?\://}
81
+ require "open-uri"
82
+ else
83
+ source = File.expand_path(find_in_source_paths(source.to_s))
84
+ end
85
+
86
+ render = open(source) { |input| input.binmode.read }
87
+
88
+ destination ||= if block_given?
89
+ block.arity == 1 ? yield(render) : yield
90
+ else
91
+ File.basename(source)
92
+ end
93
+
94
+ create_file destination, render, config
95
+ end
96
+
97
+ # Gets an ERB template at the relative source, executes it and makes a copy
98
+ # at the relative destination. If the destination is not given it's assumed
99
+ # to be equal to the source removing .tt from the filename.
100
+ #
101
+ # ==== Parameters
102
+ # source<String>:: the relative path to the source root.
103
+ # destination<String>:: the relative path to the destination root.
104
+ # config<Hash>:: give :verbose => false to not log the status.
105
+ #
106
+ # ==== Examples
107
+ #
108
+ # template "README", "doc/README"
109
+ #
110
+ # template "doc/README"
111
+ #
112
+ def template(source, *args, &block)
113
+ config = args.last.is_a?(Hash) ? args.pop : {}
114
+ destination = args.first || source.sub(/#{TEMPLATE_EXTNAME}$/, "")
115
+
116
+ source = File.expand_path(find_in_source_paths(source.to_s))
117
+ context = config.delete(:context) || instance_eval("binding")
118
+
119
+ create_file destination, nil, config do
120
+ content = CapturableERB.new(::File.binread(source), nil, "-", "@output_buffer").tap do |erb|
121
+ erb.filename = source
122
+ end.result(context)
123
+ content = yield(content) if block
124
+ content
125
+ end
126
+ end
127
+
128
+ # Changes the mode of the given file or directory.
129
+ #
130
+ # ==== Parameters
131
+ # mode<Integer>:: the file mode
132
+ # path<String>:: the name of the file to change mode
133
+ # config<Hash>:: give :verbose => false to not log the status.
134
+ #
135
+ # ==== Example
136
+ #
137
+ # chmod "script/server", 0755
138
+ #
139
+ def chmod(path, mode, config = {})
140
+ return unless behavior == :invoke
141
+ path = File.expand_path(path, destination_root)
142
+ say_status :chmod, relative_to_original_destination_root(path), config.fetch(:verbose, true)
143
+ unless options[:pretend]
144
+ require "fileutils"
145
+ FileUtils.chmod_R(mode, path)
146
+ end
147
+ end
148
+
149
+ # Prepend text to a file. Since it depends on insert_into_file, it's reversible.
150
+ #
151
+ # ==== Parameters
152
+ # path<String>:: path of the file to be changed
153
+ # data<String>:: the data to prepend to the file, can be also given as a block.
154
+ # config<Hash>:: give :verbose => false to not log the status.
155
+ #
156
+ # ==== Example
157
+ #
158
+ # prepend_to_file 'config/environments/test.rb', 'config.gem "rspec"'
159
+ #
160
+ # prepend_to_file 'config/environments/test.rb' do
161
+ # 'config.gem "rspec"'
162
+ # end
163
+ #
164
+ def prepend_to_file(path, *args, &block)
165
+ config = args.last.is_a?(Hash) ? args.pop : {}
166
+ config[:after] = /\A/
167
+ insert_into_file(path, *(args << config), &block)
168
+ end
169
+ alias_method :prepend_file, :prepend_to_file
170
+
171
+ # Append text to a file. Since it depends on insert_into_file, it's reversible.
172
+ #
173
+ # ==== Parameters
174
+ # path<String>:: path of the file to be changed
175
+ # data<String>:: the data to append to the file, can be also given as a block.
176
+ # config<Hash>:: give :verbose => false to not log the status.
177
+ #
178
+ # ==== Example
179
+ #
180
+ # append_to_file 'config/environments/test.rb', 'config.gem "rspec"'
181
+ #
182
+ # append_to_file 'config/environments/test.rb' do
183
+ # 'config.gem "rspec"'
184
+ # end
185
+ #
186
+ def append_to_file(path, *args, &block)
187
+ config = args.last.is_a?(Hash) ? args.pop : {}
188
+ config[:before] = /\z/
189
+ insert_into_file(path, *(args << config), &block)
190
+ end
191
+ alias_method :append_file, :append_to_file
192
+
193
+ # Injects text right after the class definition. Since it depends on
194
+ # insert_into_file, it's reversible.
195
+ #
196
+ # ==== Parameters
197
+ # path<String>:: path of the file to be changed
198
+ # klass<String|Class>:: the class to be manipulated
199
+ # data<String>:: the data to append to the class, can be also given as a block.
200
+ # config<Hash>:: give :verbose => false to not log the status.
201
+ #
202
+ # ==== Examples
203
+ #
204
+ # inject_into_class "app/controllers/application_controller.rb", ApplicationController, " filter_parameter :password\n"
205
+ #
206
+ # inject_into_class "app/controllers/application_controller.rb", ApplicationController do
207
+ # " filter_parameter :password\n"
208
+ # end
209
+ #
210
+ def inject_into_class(path, klass, *args, &block)
211
+ config = args.last.is_a?(Hash) ? args.pop : {}
212
+ config[:after] = /class #{klass}\n|class #{klass} .*\n/
213
+ insert_into_file(path, *(args << config), &block)
214
+ end
215
+
216
+ # Injects text right after the module definition. Since it depends on
217
+ # insert_into_file, it's reversible.
218
+ #
219
+ # ==== Parameters
220
+ # path<String>:: path of the file to be changed
221
+ # module_name<String|Class>:: the module to be manipulated
222
+ # data<String>:: the data to append to the class, can be also given as a block.
223
+ # config<Hash>:: give :verbose => false to not log the status.
224
+ #
225
+ # ==== Examples
226
+ #
227
+ # inject_into_module "app/helpers/application_helper.rb", ApplicationHelper, " def help; 'help'; end\n"
228
+ #
229
+ # inject_into_module "app/helpers/application_helper.rb", ApplicationHelper do
230
+ # " def help; 'help'; end\n"
231
+ # end
232
+ #
233
+ def inject_into_module(path, module_name, *args, &block)
234
+ config = args.last.is_a?(Hash) ? args.pop : {}
235
+ config[:after] = /module #{module_name}\n|module #{module_name} .*\n/
236
+ insert_into_file(path, *(args << config), &block)
237
+ end
238
+
239
+ # Run a regular expression replacement on a file.
240
+ #
241
+ # ==== Parameters
242
+ # path<String>:: path of the file to be changed
243
+ # flag<Regexp|String>:: the regexp or string to be replaced
244
+ # replacement<String>:: the replacement, can be also given as a block
245
+ # config<Hash>:: give :verbose => false to not log the status.
246
+ #
247
+ # ==== Example
248
+ #
249
+ # gsub_file 'app/controllers/application_controller.rb', /#\s*(filter_parameter_logging :password)/, '\1'
250
+ #
251
+ # gsub_file 'README', /rake/, :green do |match|
252
+ # match << " no more. Use thor!"
253
+ # end
254
+ #
255
+ def gsub_file(path, flag, *args, &block)
256
+ return unless behavior == :invoke
257
+ config = args.last.is_a?(Hash) ? args.pop : {}
258
+
259
+ path = File.expand_path(path, destination_root)
260
+ say_status :gsub, relative_to_original_destination_root(path), config.fetch(:verbose, true)
261
+
262
+ unless options[:pretend]
263
+ content = File.binread(path)
264
+ content.gsub!(flag, *args, &block)
265
+ File.open(path, "wb") { |file| file.write(content) }
266
+ end
267
+ end
268
+
269
+ # Uncomment all lines matching a given regex. It will leave the space
270
+ # which existed before the comment hash in tact but will remove any spacing
271
+ # between the comment hash and the beginning of the line.
272
+ #
273
+ # ==== Parameters
274
+ # path<String>:: path of the file to be changed
275
+ # flag<Regexp|String>:: the regexp or string used to decide which lines to uncomment
276
+ # config<Hash>:: give :verbose => false to not log the status.
277
+ #
278
+ # ==== Example
279
+ #
280
+ # uncomment_lines 'config/initializers/session_store.rb', /active_record/
281
+ #
282
+ def uncomment_lines(path, flag, *args)
283
+ flag = flag.respond_to?(:source) ? flag.source : flag
284
+
285
+ gsub_file(path, /^(\s*)#[[:blank:]]*(.*#{flag})/, '\1\2', *args)
286
+ end
287
+
288
+ # Comment all lines matching a given regex. It will leave the space
289
+ # which existed before the beginning of the line in tact and will insert
290
+ # a single space after the comment hash.
291
+ #
292
+ # ==== Parameters
293
+ # path<String>:: path of the file to be changed
294
+ # flag<Regexp|String>:: the regexp or string used to decide which lines to comment
295
+ # config<Hash>:: give :verbose => false to not log the status.
296
+ #
297
+ # ==== Example
298
+ #
299
+ # comment_lines 'config/initializers/session_store.rb', /cookie_store/
300
+ #
301
+ def comment_lines(path, flag, *args)
302
+ flag = flag.respond_to?(:source) ? flag.source : flag
303
+
304
+ gsub_file(path, /^(\s*)([^#|\n]*#{flag})/, '\1# \2', *args)
305
+ end
306
+
307
+ # Removes a file at the given location.
308
+ #
309
+ # ==== Parameters
310
+ # path<String>:: path of the file to be changed
311
+ # config<Hash>:: give :verbose => false to not log the status.
312
+ #
313
+ # ==== Example
314
+ #
315
+ # remove_file 'README'
316
+ # remove_file 'app/controllers/application_controller.rb'
317
+ #
318
+ def remove_file(path, config = {})
319
+ return unless behavior == :invoke
320
+ path = File.expand_path(path, destination_root)
321
+
322
+ say_status :remove, relative_to_original_destination_root(path), config.fetch(:verbose, true)
323
+ if !options[:pretend] && File.exist?(path)
324
+ require "fileutils"
325
+ ::FileUtils.rm_rf(path)
326
+ end
327
+ end
328
+ alias_method :remove_dir, :remove_file
329
+
330
+ attr_accessor :output_buffer
331
+ private :output_buffer, :output_buffer=
332
+
333
+ private
334
+
335
+ def concat(string)
336
+ @output_buffer.concat(string)
337
+ end
338
+
339
+ def capture(*args)
340
+ with_output_buffer { yield(*args) }
341
+ end
342
+
343
+ def with_output_buffer(buf = "".dup) #:nodoc:
344
+ raise ArgumentError, "Buffer can not be a frozen object" if buf.frozen?
345
+ old_buffer = output_buffer
346
+ self.output_buffer = buf
347
+ yield
348
+ output_buffer
349
+ ensure
350
+ self.output_buffer = old_buffer
351
+ end
352
+
353
+ # Thor::Actions#capture depends on what kind of buffer is used in ERB.
354
+ # Thus CapturableERB fixes ERB to use String buffer.
355
+ class CapturableERB < ERB
356
+ def set_eoutvar(compiler, eoutvar = "_erbout")
357
+ compiler.put_cmd = "#{eoutvar}.concat"
358
+ compiler.insert_cmd = "#{eoutvar}.concat"
359
+ compiler.pre_cmd = ["#{eoutvar} = ''.dup"]
360
+ compiler.post_cmd = [eoutvar]
361
+ end
362
+ end
363
+ end
364
+ end