thor 0.19.4 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 5fd71663b46487af27e6f0b6a0206f5140d0d196
4
- data.tar.gz: d67e482d506417552648630f8a7bc4b1886fc06c
2
+ SHA256:
3
+ metadata.gz: 0fc8dc6eec2f5a8963a04be9954614a50272049dc1ee40ac444a8de4961bc982
4
+ data.tar.gz: 0ea9eb9c0a1685ebf30a7c3b245d7f364dc03a1d3bd52a9af90135a561fd8e5e
5
5
  SHA512:
6
- metadata.gz: f15702a93adea15d623fd708962193b42bc38bf2b86dff66fe43f1078971fc38f5dedff0d6a78bd1bf9241315e83ede0c216ffedea8c452c55b57aff17d0d051
7
- data.tar.gz: 1e7093648f0913e9c7e32c796ea795e819ca59331588885059bfcae9867172ea50144033c342d22c4d5c8ab628f135821bb9a0ab8645f7784c396482370b98c0
6
+ metadata.gz: 88f22ea77e8939f637d5b01c7178ec0b4ce12c92dfeab0d747a5d4b00c5fef90caff0a95fef0ff3b7d80c360ac15afdc222f77322729ec1de55252cdf76fd644
7
+ data.tar.gz: 49364d76cf884f66a68bc12536668881bd144237017aad4d38bc5bd9d041672b9b68ce0642139d97e98990e39899c522b6d4c37eb6591f826d276a26a1bf5e11
@@ -1,3 +1,56 @@
1
+ # 1.0.0
2
+ * Drop support to Ruby 1.8 and 1.9.
3
+ * Deprecate relying on default `exit_on_failure?`.
4
+ In preparation to make Thor commands exit when there is a failure we are deprecating
5
+ defining a command without defining what behavior is expected when there is a failure.
6
+
7
+ To fix the deprecation you need to define a class method called `exit_on_failure?` returning
8
+
9
+ `false` if you want the current behavior or `true` if you want the new behavior.
10
+ * Deprecate defining an option with the default value using a different type as defined in the option.
11
+ * Allow options to be repeatable. See #674.
12
+
13
+ # 0.20.3
14
+ * Support old versions of `did_you_mean`.
15
+
16
+ # 0.20.2
17
+ * Fix `did_you_mean` support.
18
+
19
+ # 0.20.1
20
+ * Support new versions of ERB.
21
+ * Fix `check_unknown_options!` to not check the content that was not parsed, i.e. after a `--` or after the first unknown with `stop_on_unknown_option!`
22
+ * Add `did_you_mean` support.
23
+
24
+ ## 0.20.0
25
+ * Add `check_default_type!` to check if the default value of an option matches the defined type.
26
+ It removes the warning on usage and gives the command authors the possibility to check for programming errors.
27
+
28
+ * Add `disable_required_check!` to disable check for required options in some commands.
29
+ It is a substitute of `disable_class_options` that was not working as intended.
30
+
31
+ * Add `inject_into_module`.
32
+
33
+ ## 0.19.4, release 2016-11-28
34
+ * Rename `Thor::Base#thor_reserved_word?` to `#is_thor_reserved_word?`
35
+
36
+ ## 0.19.3, release 2016-11-27
37
+ * Output a warning instead of raising an exception when a default option value doesn't match its specified type
38
+
39
+ ## 0.19.2, release 2016-11-26
40
+ * Fix bug with handling of colors passed to `ask` (and methods like `yes?` and `no?` which it underpins)
41
+ * Allow numeric arguments to be negative
42
+ * Ensure that default option values are of the specified type (e.g. you can't specify `"foo"` as the default for a numeric option), but make symbols and strings interchangeable
43
+ * Add `Thor::Shell::Basic#indent` method for intending output
44
+ * Fix `remove_command` for an inherited command (see #451)
45
+ * Allow hash arguments to only have each key provided once (see #455)
46
+ * Allow commands to disable class options, for instance for "help" commands (see #363)
47
+ * Do not generate a negative option (`--no-no-foo`) for already negative boolean options (`--no-foo`)
48
+ * Improve compatibility of `Thor::CoreExt::HashWithIndifferentAccess` with Ruby standard library `Hash`
49
+ * Allow specifying a custom binding for template evaluation (e.g. `#key?` and `#fetch`)
50
+ * Fix support for subcommand-specific "help"s
51
+ * Use a string buffer when handling ERB for Ruby 2.3 compatibility
52
+ * Update dependencies
53
+
1
54
  ## 0.19.1, release 2014-03-24
2
55
  * Fix `say` non-String break regression
3
56
 
data/README.md CHANGED
@@ -3,13 +3,11 @@ Thor
3
3
 
4
4
  [![Gem Version](http://img.shields.io/gem/v/thor.svg)][gem]
5
5
  [![Build Status](http://img.shields.io/travis/erikhuda/thor.svg)][travis]
6
- [![Dependency Status](http://img.shields.io/gemnasium/erikhuda/thor.svg)][gemnasium]
7
6
  [![Code Climate](http://img.shields.io/codeclimate/github/erikhuda/thor.svg)][codeclimate]
8
7
  [![Coverage Status](http://img.shields.io/coveralls/erikhuda/thor.svg)][coveralls]
9
8
 
10
9
  [gem]: https://rubygems.org/gems/thor
11
10
  [travis]: http://travis-ci.org/erikhuda/thor
12
- [gemnasium]: https://gemnasium.com/erikhuda/thor
13
11
  [codeclimate]: https://codeclimate.com/github/erikhuda/thor
14
12
  [coveralls]: https://coveralls.io/r/erikhuda/thor
15
13
 
@@ -21,7 +19,13 @@ utilities. It removes the pain of parsing command line options, writing
21
19
  build tool. The syntax is Rake-like, so it should be familiar to most Rake
22
20
  users.
23
21
 
22
+ Please note: Thor, by design, is a system tool created to allow seamless file and url
23
+ access, which should not receive application user input. It relies on [open-uri][open-uri],
24
+ which combined with application user input would provide a command injection attack
25
+ vector.
26
+
24
27
  [rake]: https://github.com/ruby/rake
28
+ [open-uri]: https://ruby-doc.org/stdlib-2.5.1/libdoc/open-uri/rdoc/index.html
25
29
 
26
30
  Installation
27
31
  ------------
@@ -1,5 +1,5 @@
1
1
  require "set"
2
- require "thor/base"
2
+ require_relative "thor/base"
3
3
 
4
4
  class Thor
5
5
  class << self
@@ -90,9 +90,14 @@ class Thor
90
90
  # ==== Parameters
91
91
  # Hash[String|Array => Symbol]:: Maps the string or the strings in the array to the given command.
92
92
  #
93
- def map(mappings = nil)
93
+ def map(mappings = nil, **kw)
94
94
  @map ||= from_superclass(:map, {})
95
95
 
96
+ if mappings && !kw.empty?
97
+ mappings = kw.merge!(mappings)
98
+ else
99
+ mappings ||= kw
100
+ end
96
101
  if mappings
97
102
  mappings.each do |key, value|
98
103
  if key.respond_to?(:each)
@@ -158,10 +163,6 @@ class Thor
158
163
  end
159
164
  alias_method :option, :method_option
160
165
 
161
- def disable_class_options
162
- @disable_class_options = true
163
- end
164
-
165
166
  # Prints help information for the given command.
166
167
  #
167
168
  # ==== Parameters
@@ -174,7 +175,7 @@ class Thor
174
175
  handle_no_command_error(meth) unless command
175
176
 
176
177
  shell.say "Usage:"
177
- shell.say " #{banner(command)}"
178
+ shell.say " #{banner(command).split("\n").join("\n ")}"
178
179
  shell.say
179
180
  class_options_help(shell, nil => command.options.values)
180
181
  if command.long_description
@@ -241,6 +242,9 @@ class Thor
241
242
  invoke_args.unshift "help" if opts.delete("--help") || opts.delete("-h")
242
243
  invoke subcommand_class, *invoke_args
243
244
  end
245
+ subcommand_class.commands.each do |_meth, command|
246
+ command.ancestor_name = subcommand
247
+ end
244
248
  end
245
249
  alias_method :subtask, :subcommand
246
250
 
@@ -326,12 +330,38 @@ class Thor
326
330
  command && stop_on_unknown_option.include?(command.name.to_sym)
327
331
  end
328
332
 
333
+ # Disable the check for required options for the given commands.
334
+ # This is useful if you have a command that does not need the required options
335
+ # to work, like help.
336
+ #
337
+ # ==== Parameters
338
+ # Symbol ...:: A list of commands that should be affected.
339
+ def disable_required_check!(*command_names)
340
+ disable_required_check.merge(command_names)
341
+ end
342
+
343
+ def disable_required_check?(command) #:nodoc:
344
+ command && disable_required_check.include?(command.name.to_sym)
345
+ end
346
+
347
+ def deprecation_warning(message) #:nodoc:
348
+ unless ENV['THOR_SILENCE_DEPRECATION']
349
+ warn "Deprecation warning: #{message}\n" +
350
+ 'You can silence deprecations warning by setting the environment variable THOR_SILENCE_DEPRECATION.'
351
+ end
352
+ end
353
+
329
354
  protected
330
355
 
331
356
  def stop_on_unknown_option #:nodoc:
332
357
  @stop_on_unknown_option ||= Set.new
333
358
  end
334
359
 
360
+ # help command has the required check disabled by default.
361
+ def disable_required_check #:nodoc:
362
+ @disable_required_check ||= Set.new([:help])
363
+ end
364
+
335
365
  # The method responsible for dispatching given the args.
336
366
  def dispatch(meth, given_args, given_opts, config) #:nodoc: # rubocop:disable MethodLength
337
367
  meth ||= retrieve_command_name(given_args)
@@ -375,7 +405,10 @@ class Thor
375
405
  # the namespace should be displayed as arguments.
376
406
  #
377
407
  def banner(command, namespace = nil, subcommand = false)
378
- "#{basename} #{command.formatted_usage(self, $thor_runner, subcommand)}"
408
+ $thor_runner ||= false
409
+ command.formatted_usage(self, $thor_runner, subcommand).split("\n").map do |formatted_usage|
410
+ "#{basename} #{formatted_usage}"
411
+ end.join("\n")
379
412
  end
380
413
 
381
414
  def baseclass #:nodoc:
@@ -390,12 +423,12 @@ class Thor
390
423
  @usage ||= nil
391
424
  @desc ||= nil
392
425
  @long_desc ||= nil
393
- @disable_class_options ||= nil
426
+ @hide ||= nil
394
427
 
395
428
  if @usage && @desc
396
429
  base_class = @hide ? Thor::HiddenCommand : Thor::Command
397
- commands[meth] = base_class.new(meth, @desc, @long_desc, @usage, method_options, @disable_class_options)
398
- @usage, @desc, @long_desc, @method_options, @hide, @disable_class_options = nil
430
+ commands[meth] = base_class.new(meth, @desc, @long_desc, @usage, method_options)
431
+ @usage, @desc, @long_desc, @method_options, @hide = nil
399
432
  true
400
433
  elsif all_commands[meth] || meth == "method_missing"
401
434
  true
@@ -477,7 +510,6 @@ class Thor
477
510
  map HELP_MAPPINGS => :help
478
511
 
479
512
  desc "help [COMMAND]", "Describe available commands or one specific command"
480
- disable_class_options
481
513
  def help(command = nil, subcommand = false)
482
514
  if command
483
515
  if self.class.subcommands.include? command
@@ -1,18 +1,16 @@
1
- require "fileutils"
2
- require "uri"
3
- require "thor/core_ext/io_binary_read"
4
- require "thor/actions/create_file"
5
- require "thor/actions/create_link"
6
- require "thor/actions/directory"
7
- require "thor/actions/empty_directory"
8
- require "thor/actions/file_manipulation"
9
- require "thor/actions/inject_into_file"
1
+ require_relative "actions/create_file"
2
+ require_relative "actions/create_link"
3
+ require_relative "actions/directory"
4
+ require_relative "actions/empty_directory"
5
+ require_relative "actions/file_manipulation"
6
+ require_relative "actions/inject_into_file"
10
7
 
11
8
  class Thor
12
9
  module Actions
13
10
  attr_accessor :behavior
14
11
 
15
12
  def self.included(base) #:nodoc:
13
+ super(base)
16
14
  base.extend ClassMethods
17
15
  end
18
16
 
@@ -114,8 +112,10 @@ class Thor
114
112
  # the script started).
115
113
  #
116
114
  def relative_to_original_destination_root(path, remove_dot = true)
117
- path = path.dup
118
- if path.gsub!(@destination_stack[0], ".")
115
+ root = @destination_stack[0]
116
+ if path.start_with?(root) && [File::SEPARATOR, File::ALT_SEPARATOR, nil, ''].include?(path[root.size..root.size])
117
+ path = path.dup
118
+ path[0...root.size] = '.'
119
119
  remove_dot ? (path[2..-1] || "") : path
120
120
  else
121
121
  path
@@ -141,7 +141,7 @@ class Thor
141
141
  end
142
142
  end
143
143
 
144
- message = "Could not find #{file.inspect} in any of your source paths. "
144
+ message = "Could not find #{file.inspect} in any of your source paths. ".dup
145
145
 
146
146
  unless self.class.source_root
147
147
  message << "Please invoke #{self.class.name}.source_root(PATH) with the PATH containing your templates. "
@@ -175,6 +175,7 @@ class Thor
175
175
 
176
176
  # If the directory doesnt exist and we're not pretending
177
177
  if !File.exist?(destination_root) && !pretend
178
+ require "fileutils"
178
179
  FileUtils.mkdir_p(destination_root)
179
180
  end
180
181
 
@@ -182,6 +183,7 @@ class Thor
182
183
  # In pretend mode, just yield down to the block
183
184
  block.arity == 1 ? yield(destination_root) : yield
184
185
  else
186
+ require "fileutils"
185
187
  FileUtils.cd(destination_root) { block.arity == 1 ? yield(destination_root) : yield }
186
188
  end
187
189
 
@@ -216,6 +218,7 @@ class Thor
216
218
  shell.padding += 1 if verbose
217
219
 
218
220
  contents = if is_uri
221
+ require "open-uri"
219
222
  open(path, "Accept" => "application/x-thor-template", &:read)
220
223
  else
221
224
  open(path, &:read)
@@ -251,7 +254,22 @@ class Thor
251
254
 
252
255
  say_status :run, desc, config.fetch(:verbose, true)
253
256
 
254
- !options[:pretend] && config[:capture] ? `#{command}` : system(command.to_s)
257
+ return if options[:pretend]
258
+
259
+ env_splat = [config[:env]] if config[:env]
260
+
261
+ if config[:capture]
262
+ require "open3"
263
+ result, status = Open3.capture2e(*env_splat, command.to_s)
264
+ success = status.success?
265
+ else
266
+ result = system(*env_splat, command.to_s)
267
+ success = result
268
+ end
269
+
270
+ abort if !success && config.fetch(:abort_on_failure, self.class.exit_on_failure?)
271
+
272
+ result
255
273
  end
256
274
 
257
275
  # Executes a ruby script (taking into account WIN32 platform quirks).
@@ -1,4 +1,4 @@
1
- require "thor/actions/empty_directory"
1
+ require_relative "empty_directory"
2
2
 
3
3
  class Thor
4
4
  module Actions
@@ -58,6 +58,7 @@ class Thor
58
58
 
59
59
  def invoke!
60
60
  invoke_with_conflict_check do
61
+ require "fileutils"
61
62
  FileUtils.mkdir_p(File.dirname(destination))
62
63
  File.open(destination, "wb") { |f| f.write render }
63
64
  end
@@ -1,4 +1,4 @@
1
- require "thor/actions/create_file"
1
+ require_relative "create_file"
2
2
 
3
3
  class Thor
4
4
  module Actions
@@ -38,6 +38,7 @@ class Thor
38
38
 
39
39
  def invoke!
40
40
  invoke_with_conflict_check do
41
+ require "fileutils"
41
42
  FileUtils.mkdir_p(File.dirname(destination))
42
43
  # Create a symlink by default
43
44
  config[:symbolic] = true if config[:symbolic].nil?
@@ -1,4 +1,4 @@
1
- require "thor/actions/empty_directory"
1
+ require_relative "empty_directory"
2
2
 
3
3
  class Thor
4
4
  module Actions
@@ -56,7 +56,7 @@ class Thor
56
56
  attr_reader :source
57
57
 
58
58
  def initialize(base, source, destination = nil, config = {}, &block)
59
- @source = File.expand_path(base.find_in_source_paths(source.to_s))
59
+ @source = File.expand_path(Dir[Util.escape_globs(base.find_in_source_paths(source.to_s))].first)
60
60
  @block = block
61
61
  super(base, destination, {:recursive => true}.merge(config))
62
62
  end
@@ -96,22 +96,12 @@ class Thor
96
96
  end
97
97
  end
98
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
99
+ def file_level_lookup(previous_lookup)
100
+ File.join(previous_lookup, "*")
101
+ end
111
102
 
112
- def files(lookup)
113
- Dir.glob(lookup, File::FNM_DOTMATCH)
114
- end
103
+ def files(lookup)
104
+ Dir.glob(lookup, File::FNM_DOTMATCH)
115
105
  end
116
106
  end
117
107
  end
@@ -48,12 +48,14 @@ class Thor
48
48
 
49
49
  def invoke!
50
50
  invoke_with_conflict_check do
51
+ require "fileutils"
51
52
  ::FileUtils.mkdir_p(destination)
52
53
  end
53
54
  end
54
55
 
55
56
  def revoke!
56
57
  say_status :remove, :red
58
+ require "fileutils"
57
59
  ::FileUtils.rm_rf(destination) if !pretend? && exists?
58
60
  given_destination
59
61
  end
@@ -112,11 +114,17 @@ class Thor
112
114
  if exists?
113
115
  on_conflict_behavior(&block)
114
116
  else
115
- say_status :create, :green
116
117
  yield unless pretend?
118
+ say_status :create, :green
117
119
  end
118
120
 
119
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
120
128
  end
121
129
 
122
130
  # What to do when the destination file already exists.
@@ -1,5 +1,4 @@
1
1
  require "erb"
2
- require "open-uri"
3
2
 
4
3
  class Thor
5
4
  module Actions
@@ -24,14 +23,14 @@ class Thor
24
23
  destination = args.first || source
25
24
  source = File.expand_path(find_in_source_paths(source.to_s))
26
25
 
27
- create_file destination, nil, config do
26
+ resulting_destination = create_file destination, nil, config do
28
27
  content = File.binread(source)
29
28
  content = yield(content) if block
30
29
  content
31
30
  end
32
31
  if config[:mode] == :preserve
33
32
  mode = File.stat(source).mode
34
- chmod(destination, mode, config)
33
+ chmod(resulting_destination, mode, config)
35
34
  end
36
35
  end
37
36
 
@@ -61,6 +60,9 @@ class Thor
61
60
  # destination. If a block is given instead of destination, the content of
62
61
  # the url is yielded and used as location.
63
62
  #
63
+ # +get+ relies on open-uri, so passing application user input would provide
64
+ # a command injection attack vector.
65
+ #
64
66
  # ==== Parameters
65
67
  # source<String>:: the address of the given content.
66
68
  # destination<String>:: the relative path to the destination root.
@@ -78,8 +80,13 @@ class Thor
78
80
  config = args.last.is_a?(Hash) ? args.pop : {}
79
81
  destination = args.first
80
82
 
81
- source = File.expand_path(find_in_source_paths(source.to_s)) unless source =~ %r{^https?\://}
82
- render = open(source) { |input| input.binmode.read }
83
+ render = if source =~ %r{^https?\://}
84
+ require "open-uri"
85
+ URI.send(:open, source) { |input| input.binmode.read }
86
+ else
87
+ source = File.expand_path(find_in_source_paths(source.to_s))
88
+ open(source) { |input| input.binmode.read }
89
+ end
83
90
 
84
91
  destination ||= if block_given?
85
92
  block.arity == 1 ? yield(render) : yield
@@ -113,7 +120,15 @@ class Thor
113
120
  context = config.delete(:context) || instance_eval("binding")
114
121
 
115
122
  create_file destination, nil, config do
116
- content = CapturableERB.new(::File.binread(source), nil, "-", "@output_buffer").result(context)
123
+ match = ERB.version.match(/(\d+\.\d+\.\d+)/)
124
+ capturable_erb = if match && match[1] >= "2.2.0" # Ruby 2.6+
125
+ CapturableERB.new(::File.binread(source), :trim_mode => "-", :eoutvar => "@output_buffer")
126
+ else
127
+ CapturableERB.new(::File.binread(source), nil, "-", "@output_buffer")
128
+ end
129
+ content = capturable_erb.tap do |erb|
130
+ erb.filename = source
131
+ end.result(context)
117
132
  content = yield(content) if block
118
133
  content
119
134
  end
@@ -134,7 +149,10 @@ class Thor
134
149
  return unless behavior == :invoke
135
150
  path = File.expand_path(path, destination_root)
136
151
  say_status :chmod, relative_to_original_destination_root(path), config.fetch(:verbose, true)
137
- FileUtils.chmod_R(mode, path) unless options[:pretend]
152
+ unless options[:pretend]
153
+ require "fileutils"
154
+ FileUtils.chmod_R(mode, path)
155
+ end
138
156
  end
139
157
 
140
158
  # Prepend text to a file. Since it depends on insert_into_file, it's reversible.
@@ -204,6 +222,29 @@ class Thor
204
222
  insert_into_file(path, *(args << config), &block)
205
223
  end
206
224
 
225
+ # Injects text right after the module definition. Since it depends on
226
+ # insert_into_file, it's reversible.
227
+ #
228
+ # ==== Parameters
229
+ # path<String>:: path of the file to be changed
230
+ # module_name<String|Class>:: the module to be manipulated
231
+ # data<String>:: the data to append to the class, can be also given as a block.
232
+ # config<Hash>:: give :verbose => false to not log the status.
233
+ #
234
+ # ==== Examples
235
+ #
236
+ # inject_into_module "app/helpers/application_helper.rb", ApplicationHelper, " def help; 'help'; end\n"
237
+ #
238
+ # inject_into_module "app/helpers/application_helper.rb", ApplicationHelper do
239
+ # " def help; 'help'; end\n"
240
+ # end
241
+ #
242
+ def inject_into_module(path, module_name, *args, &block)
243
+ config = args.last.is_a?(Hash) ? args.pop : {}
244
+ config[:after] = /module #{module_name}\n|module #{module_name} .*\n/
245
+ insert_into_file(path, *(args << config), &block)
246
+ end
247
+
207
248
  # Run a regular expression replacement on a file.
208
249
  #
209
250
  # ==== Parameters
@@ -269,7 +310,7 @@ class Thor
269
310
  def comment_lines(path, flag, *args)
270
311
  flag = flag.respond_to?(:source) ? flag.source : flag
271
312
 
272
- gsub_file(path, /^(\s*)([^#|\n]*#{flag})/, '\1# \2', *args)
313
+ gsub_file(path, /^(\s*)([^#\n]*#{flag})/, '\1# \2', *args)
273
314
  end
274
315
 
275
316
  # Removes a file at the given location.
@@ -288,7 +329,10 @@ class Thor
288
329
  path = File.expand_path(path, destination_root)
289
330
 
290
331
  say_status :remove, relative_to_original_destination_root(path), config.fetch(:verbose, true)
291
- ::FileUtils.rm_rf(path) if !options[:pretend] && File.exist?(path)
332
+ if !options[:pretend] && File.exist?(path)
333
+ require "fileutils"
334
+ ::FileUtils.rm_rf(path)
335
+ end
292
336
  end
293
337
  alias_method :remove_dir, :remove_file
294
338
 
@@ -305,8 +349,10 @@ class Thor
305
349
  with_output_buffer { yield(*args) }
306
350
  end
307
351
 
308
- def with_output_buffer(buf = "") #:nodoc:
309
- self.output_buffer, old_buffer = buf, output_buffer
352
+ def with_output_buffer(buf = "".dup) #:nodoc:
353
+ raise ArgumentError, "Buffer can not be a frozen object" if buf.frozen?
354
+ old_buffer = output_buffer
355
+ self.output_buffer = buf
310
356
  yield
311
357
  output_buffer
312
358
  ensure
@@ -319,7 +365,7 @@ class Thor
319
365
  def set_eoutvar(compiler, eoutvar = "_erbout")
320
366
  compiler.put_cmd = "#{eoutvar}.concat"
321
367
  compiler.insert_cmd = "#{eoutvar}.concat"
322
- compiler.pre_cmd = ["#{eoutvar} = ''"]
368
+ compiler.pre_cmd = ["#{eoutvar} = ''.dup"]
323
369
  compiler.post_cmd = [eoutvar]
324
370
  end
325
371
  end