atli 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
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