thor 0.20.0 → 1.2.1

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: 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
  #