thor 0.20.0 → 1.2.1

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 7fddac6c27d0c13187204ad4a00467eb10abce93
4
- data.tar.gz: 5839be67e757fd66707de47fe1f0c051376e958f
2
+ SHA256:
3
+ metadata.gz: 30e79d2b0a96e87c8e6348467db577c6ad1e9acbbbac5d375417bc3e5a2b7698
4
+ data.tar.gz: 701f1ab842da90e599b96bd00d90481d716eb29e39e0283b5ff527c2033fc742
5
5
  SHA512:
6
- metadata.gz: 24d622cda205874c6ffa47ae8cb616b8f454f05cb3c56d14f6078e6509c6553111a7669c08776c5914c17f8e261296fd0827f59f778e62d48aa00bca6bacb58d
7
- data.tar.gz: bcb09ac8cfa598b845897068ea707f0b6d7686a327dc7e6caa37c073dc2efc581fd95731cfdf16ca4f1d7b606990984652e9e726e2144df6ad4b68bebd7c8797
6
+ metadata.gz: 73b1ac80575d4422204cd8072950b5594739db3b6f3fde0f2f04359d51b1d4428524d25b9a3003ae9ec3f6be615cf635f3057bbc65558e6a17ba490ff045988b
7
+ data.tar.gz: eb7761a5e6f3674cb3231398145978b0eb53a6fa2c10e4cb9e99d8d523988efcefc8cae5dd163b8a3d6ead7424422d58b92311c200790ad6e2416e3f9757a90e
data/README.md CHANGED
@@ -2,16 +2,8 @@ Thor
2
2
  ====
3
3
 
4
4
  [![Gem Version](http://img.shields.io/gem/v/thor.svg)][gem]
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
- [![Code Climate](http://img.shields.io/codeclimate/github/erikhuda/thor.svg)][codeclimate]
8
- [![Coverage Status](http://img.shields.io/coveralls/erikhuda/thor.svg)][coveralls]
9
5
 
10
6
  [gem]: https://rubygems.org/gems/thor
11
- [travis]: http://travis-ci.org/erikhuda/thor
12
- [gemnasium]: https://gemnasium.com/erikhuda/thor
13
- [codeclimate]: https://codeclimate.com/github/erikhuda/thor
14
- [coveralls]: https://coveralls.io/r/erikhuda/thor
15
7
 
16
8
  Description
17
9
  -----------
@@ -21,7 +13,13 @@ utilities. It removes the pain of parsing command line options, writing
21
13
  build tool. The syntax is Rake-like, so it should be familiar to most Rake
22
14
  users.
23
15
 
16
+ Please note: Thor, by design, is a system tool created to allow seamless file and url
17
+ access, which should not receive application user input. It relies on [open-uri][open-uri],
18
+ which combined with application user input would provide a command injection attack
19
+ vector.
20
+
24
21
  [rake]: https://github.com/ruby/rake
22
+ [open-uri]: https://ruby-doc.org/stdlib-2.5.1/libdoc/open-uri/rdoc/index.html
25
23
 
26
24
  Installation
27
25
  ------------
@@ -31,7 +29,7 @@ Usage and documentation
31
29
  -----------------------
32
30
  Please see the [wiki][] for basic usage and other documentation on using Thor. You can also checkout the [official homepage][homepage].
33
31
 
34
- [wiki]: https://github.com/erikhuda/thor/wiki
32
+ [wiki]: https://github.com/rails/thor/wiki
35
33
  [homepage]: http://whatisthor.com/
36
34
 
37
35
  Contributing
@@ -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
@@ -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
@@ -33,7 +33,8 @@ class Thor
33
33
  # Boolean:: true if it is identical, false otherwise.
34
34
  #
35
35
  def identical?
36
- exists? && File.identical?(render, destination)
36
+ source = File.expand_path(render, File.dirname(destination))
37
+ exists? && File.identical?(source, destination)
37
38
  end
38
39
 
39
40
  def invoke!
@@ -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
@@ -23,14 +23,14 @@ class Thor
23
23
  destination = args.first || source
24
24
  source = File.expand_path(find_in_source_paths(source.to_s))
25
25
 
26
- create_file destination, nil, config do
26
+ resulting_destination = create_file destination, nil, config do
27
27
  content = File.binread(source)
28
28
  content = yield(content) if block
29
29
  content
30
30
  end
31
31
  if config[:mode] == :preserve
32
32
  mode = File.stat(source).mode
33
- chmod(destination, mode, config)
33
+ chmod(resulting_destination, mode, config)
34
34
  end
35
35
  end
36
36
 
@@ -60,6 +60,9 @@ class Thor
60
60
  # destination. If a block is given instead of destination, the content of
61
61
  # the url is yielded and used as location.
62
62
  #
63
+ # +get+ relies on open-uri, so passing application user input would provide
64
+ # a command injection attack vector.
65
+ #
63
66
  # ==== Parameters
64
67
  # source<String>:: the address of the given content.
65
68
  # destination<String>:: the relative path to the destination root.
@@ -77,14 +80,14 @@ class Thor
77
80
  config = args.last.is_a?(Hash) ? args.pop : {}
78
81
  destination = args.first
79
82
 
80
- if source =~ %r{^https?\://}
83
+ render = if source =~ %r{^https?\://}
81
84
  require "open-uri"
85
+ URI.send(:open, source) { |input| input.binmode.read }
82
86
  else
83
87
  source = File.expand_path(find_in_source_paths(source.to_s))
88
+ open(source) { |input| input.binmode.read }
84
89
  end
85
90
 
86
- render = open(source) { |input| input.binmode.read }
87
-
88
91
  destination ||= if block_given?
89
92
  block.arity == 1 ? yield(render) : yield
90
93
  else
@@ -117,7 +120,13 @@ class Thor
117
120
  context = config.delete(:context) || instance_eval("binding")
118
121
 
119
122
  create_file destination, nil, config do
120
- content = CapturableERB.new(::File.binread(source), nil, "-", "@output_buffer").tap do |erb|
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|
121
130
  erb.filename = source
122
131
  end.result(context)
123
132
  content = yield(content) if block
@@ -201,9 +210,9 @@ class Thor
201
210
  #
202
211
  # ==== Examples
203
212
  #
204
- # inject_into_class "app/controllers/application_controller.rb", ApplicationController, " filter_parameter :password\n"
213
+ # inject_into_class "app/controllers/application_controller.rb", "ApplicationController", " filter_parameter :password\n"
205
214
  #
206
- # inject_into_class "app/controllers/application_controller.rb", ApplicationController do
215
+ # inject_into_class "app/controllers/application_controller.rb", "ApplicationController" do
207
216
  # " filter_parameter :password\n"
208
217
  # end
209
218
  #
@@ -224,9 +233,9 @@ class Thor
224
233
  #
225
234
  # ==== Examples
226
235
  #
227
- # inject_into_module "app/helpers/application_helper.rb", ApplicationHelper, " def help; 'help'; end\n"
236
+ # inject_into_module "app/helpers/application_helper.rb", "ApplicationHelper", " def help; 'help'; end\n"
228
237
  #
229
- # inject_into_module "app/helpers/application_helper.rb", ApplicationHelper do
238
+ # inject_into_module "app/helpers/application_helper.rb", "ApplicationHelper" do
230
239
  # " def help; 'help'; end\n"
231
240
  # end
232
241
  #
@@ -242,7 +251,8 @@ class Thor
242
251
  # path<String>:: path of the file to be changed
243
252
  # flag<Regexp|String>:: the regexp or string to be replaced
244
253
  # replacement<String>:: the replacement, can be also given as a block
245
- # config<Hash>:: give :verbose => false to not log the status.
254
+ # config<Hash>:: give :verbose => false to not log the status, and
255
+ # :force => true, to force the replacement regardles of runner behavior.
246
256
  #
247
257
  # ==== Example
248
258
  #
@@ -253,9 +263,10 @@ class Thor
253
263
  # end
254
264
  #
255
265
  def gsub_file(path, flag, *args, &block)
256
- return unless behavior == :invoke
257
266
  config = args.last.is_a?(Hash) ? args.pop : {}
258
267
 
268
+ return unless behavior == :invoke || config.fetch(:force, false)
269
+
259
270
  path = File.expand_path(path, destination_root)
260
271
  say_status :gsub, relative_to_original_destination_root(path), config.fetch(:verbose, true)
261
272
 
@@ -301,7 +312,7 @@ class Thor
301
312
  def comment_lines(path, flag, *args)
302
313
  flag = flag.respond_to?(:source) ? flag.source : flag
303
314
 
304
- gsub_file(path, /^(\s*)([^#|\n]*#{flag})/, '\1# \2', *args)
315
+ gsub_file(path, /^(\s*)([^#\n]*#{flag})/, '\1# \2', *args)
305
316
  end
306
317
 
307
318
  # Removes a file at the given location.
@@ -320,7 +331,7 @@ class Thor
320
331
  path = File.expand_path(path, destination_root)
321
332
 
322
333
  say_status :remove, relative_to_original_destination_root(path), config.fetch(:verbose, true)
323
- if !options[:pretend] && File.exist?(path)
334
+ if !options[:pretend] && (File.exist?(path) || File.symlink?(path))
324
335
  require "fileutils"
325
336
  ::FileUtils.rm_rf(path)
326
337
  end
@@ -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
@@ -21,9 +21,14 @@ class Thor
21
21
  # gems.split(" ").map{ |gem| " config.gem :#{gem}" }.join("\n")
22
22
  # end
23
23
  #
24
+ WARNINGS = { unchanged_no_flag: 'File unchanged! The supplied flag value not found!' }
25
+
24
26
  def insert_into_file(destination, *args, &block)
25
27
  data = block_given? ? block : args.shift
26
- config = args.shift
28
+
29
+ config = args.shift || {}
30
+ config[:after] = /\z/ unless config.key?(:before) || config.key?(:after)
31
+
27
32
  action InjectIntoFile.new(self, destination, data, config)
28
33
  end
29
34
  alias_method :inject_into_file, :insert_into_file
@@ -45,8 +50,6 @@ class Thor
45
50
  end
46
51
 
47
52
  def invoke!
48
- say_status :invoke
49
-
50
53
  content = if @behavior == :after
51
54
  '\0' + replacement
52
55
  else
@@ -54,7 +57,11 @@ class Thor
54
57
  end
55
58
 
56
59
  if exists?
57
- replace!(/#{flag}/, content, config[:force])
60
+ if replace!(/#{flag}/, content, config[:force])
61
+ say_status(:invoke)
62
+ else
63
+ say_status(:unchanged, warning: WARNINGS[:unchanged_no_flag], color: :red)
64
+ end
58
65
  else
59
66
  unless pretend?
60
67
  raise Thor::Error, "The file #{ destination } does not appear to exist"
@@ -78,7 +85,7 @@ class Thor
78
85
 
79
86
  protected
80
87
 
81
- def say_status(behavior)
88
+ def say_status(behavior, warning: nil, color: nil)
82
89
  status = if behavior == :invoke
83
90
  if flag == /\A/
84
91
  :prepend
@@ -87,21 +94,24 @@ class Thor
87
94
  else
88
95
  :insert
89
96
  end
97
+ elsif warning
98
+ warning
90
99
  else
91
100
  :subtract
92
101
  end
93
102
 
94
- super(status, config[:verbose])
103
+ super(status, (color || config[:verbose]))
95
104
  end
96
105
 
97
106
  # Adds the content to the file.
98
107
  #
99
108
  def replace!(regexp, string, force)
100
- return if pretend?
101
109
  content = File.read(destination)
102
110
  if force || !content.include?(replacement)
103
- content.gsub!(regexp, string)
104
- File.open(destination, "wb") { |file| file.write(content) }
111
+ success = content.gsub!(regexp, string)
112
+
113
+ File.open(destination, "wb") { |file| file.write(content) } unless pretend?
114
+ success
105
115
  end
106
116
  end
107
117
  end
data/lib/thor/actions.rb CHANGED
@@ -1,17 +1,16 @@
1
- require "uri"
2
- require "thor/core_ext/io_binary_read"
3
- require "thor/actions/create_file"
4
- require "thor/actions/create_link"
5
- require "thor/actions/directory"
6
- require "thor/actions/empty_directory"
7
- require "thor/actions/file_manipulation"
8
- 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"
9
7
 
10
8
  class Thor
11
9
  module Actions
12
10
  attr_accessor :behavior
13
11
 
14
12
  def self.included(base) #:nodoc:
13
+ super(base)
15
14
  base.extend ClassMethods
16
15
  end
17
16
 
@@ -113,8 +112,10 @@ class Thor
113
112
  # the script started).
114
113
  #
115
114
  def relative_to_original_destination_root(path, remove_dot = true)
116
- path = path.dup
117
- 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] = '.'
118
119
  remove_dot ? (path[2..-1] || "") : path
119
120
  else
120
121
  path
@@ -160,6 +161,8 @@ class Thor
160
161
  # to the block you provide. The path is set back to the previous path when
161
162
  # the method exits.
162
163
  #
164
+ # Returns the value yielded by the block.
165
+ #
163
166
  # ==== Parameters
164
167
  # dir<String>:: the directory to move to.
165
168
  # config<Hash>:: give :verbose => true to log and use padding.
@@ -178,16 +181,18 @@ class Thor
178
181
  FileUtils.mkdir_p(destination_root)
179
182
  end
180
183
 
184
+ result = nil
181
185
  if pretend
182
186
  # In pretend mode, just yield down to the block
183
- block.arity == 1 ? yield(destination_root) : yield
187
+ result = block.arity == 1 ? yield(destination_root) : yield
184
188
  else
185
189
  require "fileutils"
186
- FileUtils.cd(destination_root) { block.arity == 1 ? yield(destination_root) : yield }
190
+ FileUtils.cd(destination_root) { result = block.arity == 1 ? yield(destination_root) : yield }
187
191
  end
188
192
 
189
193
  @destination_stack.pop
190
194
  shell.padding -= 1 if verbose
195
+ result
191
196
  end
192
197
 
193
198
  # Goes to the root and execute the given block.
@@ -217,7 +222,8 @@ class Thor
217
222
  shell.padding += 1 if verbose
218
223
 
219
224
  contents = if is_uri
220
- open(path, "Accept" => "application/x-thor-template", &:read)
225
+ require "open-uri"
226
+ URI.open(path, "Accept" => "application/x-thor-template", &:read)
221
227
  else
222
228
  open(path, &:read)
223
229
  end
@@ -252,9 +258,22 @@ class Thor
252
258
 
253
259
  say_status :run, desc, config.fetch(:verbose, true)
254
260
 
255
- unless options[:pretend]
256
- config[:capture] ? `#{command}` : system(command.to_s)
261
+ return if options[:pretend]
262
+
263
+ env_splat = [config[:env]] if config[:env]
264
+
265
+ if config[:capture]
266
+ require "open3"
267
+ result, status = Open3.capture2e(*env_splat, command.to_s)
268
+ success = status.success?
269
+ else
270
+ result = system(*env_splat, command.to_s)
271
+ success = result
257
272
  end
273
+
274
+ abort if !success && config.fetch(:abort_on_failure, self.class.exit_on_failure?)
275
+
276
+ result
258
277
  end
259
278
 
260
279
  # Executes a ruby script (taking into account WIN32 platform quirks).
data/lib/thor/base.rb CHANGED
@@ -1,17 +1,17 @@
1
- require "thor/command"
2
- require "thor/core_ext/hash_with_indifferent_access"
3
- require "thor/core_ext/ordered_hash"
4
- require "thor/error"
5
- require "thor/invocation"
6
- require "thor/parser"
7
- require "thor/shell"
8
- require "thor/line_editor"
9
- require "thor/util"
1
+ require_relative "command"
2
+ require_relative "core_ext/hash_with_indifferent_access"
3
+ require_relative "error"
4
+ require_relative "invocation"
5
+ require_relative "nested_context"
6
+ require_relative "parser"
7
+ require_relative "shell"
8
+ require_relative "line_editor"
9
+ require_relative "util"
10
10
 
11
11
  class Thor
12
- autoload :Actions, "thor/actions"
13
- autoload :RakeCompat, "thor/rake_compat"
14
- autoload :Group, "thor/group"
12
+ autoload :Actions, File.expand_path("actions", __dir__)
13
+ autoload :RakeCompat, File.expand_path("rake_compat", __dir__)
14
+ autoload :Group, File.expand_path("group", __dir__)
15
15
 
16
16
  # Shortcuts for help.
17
17
  HELP_MAPPINGS = %w(-h -? --help -D)
@@ -22,6 +22,15 @@ class Thor
22
22
 
23
23
  TEMPLATE_EXTNAME = ".tt"
24
24
 
25
+ class << self
26
+ def deprecation_warning(message) #:nodoc:
27
+ unless ENV['THOR_SILENCE_DEPRECATION']
28
+ warn "Deprecation warning: #{message}\n" +
29
+ 'You can silence deprecations warning by setting the environment variable THOR_SILENCE_DEPRECATION.'
30
+ end
31
+ end
32
+ end
33
+
25
34
  module Base
26
35
  attr_accessor :options, :parent_options, :args
27
36
 
@@ -89,6 +98,7 @@ class Thor
89
98
 
90
99
  class << self
91
100
  def included(base) #:nodoc:
101
+ super(base)
92
102
  base.extend ClassMethods
93
103
  base.send :include, Invocation
94
104
  base.send :include, Shell
@@ -113,7 +123,7 @@ class Thor
113
123
  end
114
124
 
115
125
  # Whenever a class inherits from Thor or Thor::Group, we should track the
116
- # class and the file on Thor::Base. This is the method responsable for it.
126
+ # class and the file on Thor::Base. This is the method responsible for it.
117
127
  #
118
128
  def register_klass_file(klass) #:nodoc:
119
129
  file = caller[1].match(/(.*):\d+/)[1]
@@ -153,17 +163,20 @@ class Thor
153
163
 
154
164
  # If you want to raise an error when the default value of an option does not match
155
165
  # the type call check_default_type!
156
- # This is disabled by default for compatibility.
166
+ # This will be the default; for compatibility a deprecation warning is issued if necessary.
157
167
  def check_default_type!
158
168
  @check_default_type = true
159
169
  end
160
170
 
161
- def check_default_type #:nodoc:
162
- @check_default_type ||= from_superclass(:check_default_type, false)
171
+ # If you want to use defaults that don't match the type of an option,
172
+ # either specify `check_default_type: false` or call `allow_incompatible_default_type!`
173
+ def allow_incompatible_default_type!
174
+ @check_default_type = false
163
175
  end
164
176
 
165
- def check_default_type? #:nodoc:
166
- !!check_default_type
177
+ def check_default_type #:nodoc:
178
+ @check_default_type = from_superclass(:check_default_type, nil) unless defined?(@check_default_type)
179
+ @check_default_type
167
180
  end
168
181
 
169
182
  # If true, option parsing is suspended as soon as an unknown option or a
@@ -353,22 +366,22 @@ class Thor
353
366
  # Returns the commands for this Thor class.
354
367
  #
355
368
  # ==== Returns
356
- # OrderedHash:: An ordered hash with commands names as keys and Thor::Command
357
- # objects as values.
369
+ # Hash:: An ordered hash with commands names as keys and Thor::Command
370
+ # objects as values.
358
371
  #
359
372
  def commands
360
- @commands ||= Thor::CoreExt::OrderedHash.new
373
+ @commands ||= Hash.new
361
374
  end
362
375
  alias_method :tasks, :commands
363
376
 
364
377
  # Returns the commands for this Thor class and all subclasses.
365
378
  #
366
379
  # ==== Returns
367
- # OrderedHash:: An ordered hash with commands names as keys and Thor::Command
368
- # objects as values.
380
+ # Hash:: An ordered hash with commands names as keys and Thor::Command
381
+ # objects as values.
369
382
  #
370
383
  def all_commands
371
- @all_commands ||= from_superclass(:all_commands, Thor::CoreExt::OrderedHash.new)
384
+ @all_commands ||= from_superclass(:all_commands, Hash.new)
372
385
  @all_commands.merge!(commands)
373
386
  end
374
387
  alias_method :all_tasks, :all_commands
@@ -415,14 +428,20 @@ class Thor
415
428
  # remove_command :this_is_not_a_command
416
429
  # end
417
430
  #
418
- def no_commands
419
- @no_commands = true
420
- yield
421
- ensure
422
- @no_commands = false
431
+ def no_commands(&block)
432
+ no_commands_context.enter(&block)
423
433
  end
434
+
424
435
  alias_method :no_tasks, :no_commands
425
436
 
437
+ def no_commands_context
438
+ @no_commands_context ||= NestedContext.new
439
+ end
440
+
441
+ def no_commands?
442
+ no_commands_context.entered?
443
+ end
444
+
426
445
  # Sets the namespace for the Thor or Thor::Group class. By default the
427
446
  # namespace is retrieved from the class name. If your Thor class is named
428
447
  # Scripts::MyScript, the help method, for example, will be called as:
@@ -466,13 +485,13 @@ class Thor
466
485
  dispatch(nil, given_args.dup, nil, config)
467
486
  rescue Thor::Error => e
468
487
  config[:debug] || ENV["THOR_DEBUG"] == "1" ? (raise e) : config[:shell].error(e.message)
469
- exit(1) if exit_on_failure?
488
+ exit(false) if exit_on_failure?
470
489
  rescue Errno::EPIPE
471
490
  # This happens if a thor command is piped to something like `head`,
472
491
  # which closes the pipe when it's done reading. This will also
473
492
  # mean that if the pipe is closed, further unnecessary
474
493
  # computation will not occur.
475
- exit(0)
494
+ exit(true)
476
495
  end
477
496
 
478
497
  # Allows to use private methods from parent in child classes as commands.
@@ -493,8 +512,7 @@ class Thor
493
512
  alias_method :public_task, :public_command
494
513
 
495
514
  def handle_no_command_error(command, has_namespace = $thor_runner) #:nodoc:
496
- raise UndefinedCommandError, "Could not find command #{command.inspect} in #{namespace.inspect} namespace." if has_namespace
497
- raise UndefinedCommandError, "Could not find command #{command.inspect}."
515
+ raise UndefinedCommandError.new(command, all_commands.keys, (namespace if has_namespace))
498
516
  end
499
517
  alias_method :handle_no_task_error, :handle_no_command_error
500
518
 
@@ -503,10 +521,16 @@ class Thor
503
521
  msg = "ERROR: \"#{basename} #{name}\" was called with ".dup
504
522
  msg << "no arguments" if args.empty?
505
523
  msg << "arguments " << args.inspect unless args.empty?
506
- msg << "\nUsage: #{banner(command).inspect}"
524
+ msg << "\nUsage: \"#{banner(command).split("\n").join("\"\n \"")}\""
507
525
  raise InvocationError, msg
508
526
  end
509
527
 
528
+ # A flag that makes the process exit with status 1 if any error happens.
529
+ def exit_on_failure?
530
+ Thor.deprecation_warning "Thor exit with status 0 on errors. To keep this behavior, you must define `exit_on_failure?` in `#{self.name}`"
531
+ false
532
+ end
533
+
510
534
  protected
511
535
 
512
536
  # Prints the class options per group. If an option does not belong to
@@ -564,7 +588,7 @@ class Thor
564
588
  # options<Hash>:: Described in both class_option and method_option.
565
589
  # scope<Hash>:: Options hash that is being built up
566
590
  def build_option(name, options, scope) #:nodoc:
567
- scope[name] = Thor::Option.new(name, options.merge(:check_default_type => check_default_type?))
591
+ scope[name] = Thor::Option.new(name, {:check_default_type => check_default_type}.merge!(options))
568
592
  end
569
593
 
570
594
  # Receives a hash of options, parse them and add to the scope. This is a
@@ -597,13 +621,15 @@ class Thor
597
621
  # Everytime someone inherits from a Thor class, register the klass
598
622
  # and file into baseclass.
599
623
  def inherited(klass)
624
+ super(klass)
600
625
  Thor::Base.register_klass_file(klass)
601
- klass.instance_variable_set(:@no_commands, false)
626
+ klass.instance_variable_set(:@no_commands, 0)
602
627
  end
603
628
 
604
629
  # Fire this callback whenever a method is added. Added methods are
605
630
  # tracked as commands by invoking the create_command method.
606
631
  def method_added(meth)
632
+ super(meth)
607
633
  meth = meth.to_s
608
634
 
609
635
  if meth == "initialize"
@@ -614,8 +640,7 @@ class Thor
614
640
  # Return if it's not a public instance method
615
641
  return unless public_method_defined?(meth.to_sym)
616
642
 
617
- @no_commands ||= false
618
- return if @no_commands || !create_command(meth)
643
+ return if no_commands? || !create_command(meth)
619
644
 
620
645
  is_thor_reserved_word?(meth, :command)
621
646
  Thor::Base.register_klass_file(self)
@@ -642,11 +667,6 @@ class Thor
642
667
  end
643
668
  end
644
669
 
645
- # A flag that makes the process exit with status 1 if any error happens.
646
- def exit_on_failure?
647
- false
648
- end
649
-
650
670
  #
651
671
  # The basename of the program invoking the thor class.
652
672
  #