thor 0.19.4 → 1.0.0
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 +5 -5
- data/CHANGELOG.md +53 -0
- data/README.md +6 -2
- data/lib/thor.rb +44 -12
- data/lib/thor/actions.rb +31 -13
- data/lib/thor/actions/create_file.rb +2 -1
- data/lib/thor/actions/create_link.rb +2 -1
- data/lib/thor/actions/directory.rb +7 -17
- data/lib/thor/actions/empty_directory.rb +9 -1
- data/lib/thor/actions/file_manipulation.rb +58 -12
- data/lib/thor/actions/inject_into_file.rb +27 -10
- data/lib/thor/base.rb +75 -41
- data/lib/thor/command.rb +30 -21
- data/lib/thor/core_ext/hash_with_indifferent_access.rb +12 -0
- data/lib/thor/error.rb +78 -0
- data/lib/thor/group.rb +4 -4
- data/lib/thor/invocation.rb +1 -0
- data/lib/thor/line_editor.rb +2 -2
- data/lib/thor/line_editor/basic.rb +2 -0
- data/lib/thor/line_editor/readline.rb +6 -6
- data/lib/thor/nested_context.rb +29 -0
- data/lib/thor/parser.rb +4 -4
- data/lib/thor/parser/arguments.rb +2 -2
- data/lib/thor/parser/option.rb +22 -9
- data/lib/thor/parser/options.rb +25 -9
- data/lib/thor/rake_compat.rb +1 -0
- data/lib/thor/runner.rb +9 -6
- data/lib/thor/shell.rb +4 -4
- data/lib/thor/shell/basic.rb +72 -17
- data/lib/thor/shell/color.rb +6 -2
- data/lib/thor/shell/html.rb +3 -3
- data/lib/thor/util.rb +17 -1
- data/lib/thor/version.rb +1 -1
- data/thor.gemspec +2 -2
- metadata +13 -9
- data/lib/thor/core_ext/io_binary_read.rb +0 -12
- data/lib/thor/core_ext/ordered_hash.rb +0 -129
@@ -1,4 +1,4 @@
|
|
1
|
-
|
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
|
-
|
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,15 +50,23 @@ 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
|
53
56
|
replacement + '\0'
|
54
57
|
end
|
55
58
|
|
56
|
-
|
59
|
+
if exists?
|
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
|
65
|
+
else
|
66
|
+
unless pretend?
|
67
|
+
raise Thor::Error, "The file #{ destination } does not appear to exist"
|
68
|
+
end
|
69
|
+
end
|
57
70
|
end
|
58
71
|
|
59
72
|
def revoke!
|
@@ -72,7 +85,7 @@ class Thor
|
|
72
85
|
|
73
86
|
protected
|
74
87
|
|
75
|
-
def say_status(behavior)
|
88
|
+
def say_status(behavior, warning: nil, color: nil)
|
76
89
|
status = if behavior == :invoke
|
77
90
|
if flag == /\A/
|
78
91
|
:prepend
|
@@ -81,21 +94,25 @@ class Thor
|
|
81
94
|
else
|
82
95
|
:insert
|
83
96
|
end
|
97
|
+
elsif warning
|
98
|
+
warning
|
84
99
|
else
|
85
100
|
:subtract
|
86
101
|
end
|
87
102
|
|
88
|
-
super(status, config[:verbose])
|
103
|
+
super(status, (color || config[:verbose]))
|
89
104
|
end
|
90
105
|
|
91
106
|
# Adds the content to the file.
|
92
107
|
#
|
93
108
|
def replace!(regexp, string, force)
|
94
|
-
return if
|
95
|
-
content = File.
|
109
|
+
return if pretend?
|
110
|
+
content = File.read(destination)
|
96
111
|
if force || !content.include?(replacement)
|
97
|
-
content.gsub!(regexp, string)
|
112
|
+
success = content.gsub!(regexp, string)
|
113
|
+
|
98
114
|
File.open(destination, "wb") { |file| file.write(content) }
|
115
|
+
success
|
99
116
|
end
|
100
117
|
end
|
101
118
|
end
|
data/lib/thor/base.rb
CHANGED
@@ -1,17 +1,17 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
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, "
|
13
|
-
autoload :RakeCompat, "
|
14
|
-
autoload :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)
|
@@ -42,7 +42,7 @@ class Thor
|
|
42
42
|
# config<Hash>:: Configuration for this Thor class.
|
43
43
|
#
|
44
44
|
def initialize(args = [], local_options = {}, config = {})
|
45
|
-
parse_options =
|
45
|
+
parse_options = self.class.class_options
|
46
46
|
|
47
47
|
# The start method splits inbound arguments at the first argument
|
48
48
|
# that looks like an option (starts with - or --). It then calls
|
@@ -65,7 +65,8 @@ class Thor
|
|
65
65
|
# declared options from the array. This will leave us with
|
66
66
|
# a list of arguments that weren't declared.
|
67
67
|
stop_on_unknown = self.class.stop_on_unknown_option? config[:current_command]
|
68
|
-
|
68
|
+
disable_required_check = self.class.disable_required_check? config[:current_command]
|
69
|
+
opts = Thor::Options.new(parse_options, hash_options, stop_on_unknown, disable_required_check)
|
69
70
|
self.options = opts.parse(array_options)
|
70
71
|
self.options = config[:class_options].merge(options) if config[:class_options]
|
71
72
|
|
@@ -88,6 +89,7 @@ class Thor
|
|
88
89
|
|
89
90
|
class << self
|
90
91
|
def included(base) #:nodoc:
|
92
|
+
super(base)
|
91
93
|
base.extend ClassMethods
|
92
94
|
base.send :include, Invocation
|
93
95
|
base.send :include, Shell
|
@@ -112,7 +114,7 @@ class Thor
|
|
112
114
|
end
|
113
115
|
|
114
116
|
# Whenever a class inherits from Thor or Thor::Group, we should track the
|
115
|
-
# class and the file on Thor::Base. This is the method
|
117
|
+
# class and the file on Thor::Base. This is the method responsible for it.
|
116
118
|
#
|
117
119
|
def register_klass_file(klass) #:nodoc:
|
118
120
|
file = caller[1].match(/(.*):\d+/)[1]
|
@@ -150,6 +152,24 @@ class Thor
|
|
150
152
|
!!check_unknown_options
|
151
153
|
end
|
152
154
|
|
155
|
+
# If you want to raise an error when the default value of an option does not match
|
156
|
+
# the type call check_default_type!
|
157
|
+
# This will be the default; for compatibility a deprecation warning is issued if necessary.
|
158
|
+
def check_default_type!
|
159
|
+
@check_default_type = true
|
160
|
+
end
|
161
|
+
|
162
|
+
# If you want to use defaults that don't match the type of an option,
|
163
|
+
# either specify `check_default_type: false` or call `allow_incompatible_default_type!`
|
164
|
+
def allow_incompatible_default_type!
|
165
|
+
@check_default_type = false
|
166
|
+
end
|
167
|
+
|
168
|
+
def check_default_type #:nodoc:
|
169
|
+
@check_default_type = from_superclass(:check_default_type, nil) unless defined?(@check_default_type)
|
170
|
+
@check_default_type
|
171
|
+
end
|
172
|
+
|
153
173
|
# If true, option parsing is suspended as soon as an unknown option or a
|
154
174
|
# regular argument is encountered. All remaining arguments are passed to
|
155
175
|
# the command as regular arguments.
|
@@ -157,6 +177,12 @@ class Thor
|
|
157
177
|
false
|
158
178
|
end
|
159
179
|
|
180
|
+
# If true, option set will not suspend the execution of the command when
|
181
|
+
# a required option is not provided.
|
182
|
+
def disable_required_check?(command_name) #:nodoc:
|
183
|
+
false
|
184
|
+
end
|
185
|
+
|
160
186
|
# If you want only strict string args (useful when cascading thor classes),
|
161
187
|
# call strict_args_position! This is disabled by default to allow dynamic
|
162
188
|
# invocations.
|
@@ -331,22 +357,22 @@ class Thor
|
|
331
357
|
# Returns the commands for this Thor class.
|
332
358
|
#
|
333
359
|
# ==== Returns
|
334
|
-
#
|
335
|
-
#
|
360
|
+
# Hash:: An ordered hash with commands names as keys and Thor::Command
|
361
|
+
# objects as values.
|
336
362
|
#
|
337
363
|
def commands
|
338
|
-
@commands ||=
|
364
|
+
@commands ||= Hash.new
|
339
365
|
end
|
340
366
|
alias_method :tasks, :commands
|
341
367
|
|
342
368
|
# Returns the commands for this Thor class and all subclasses.
|
343
369
|
#
|
344
370
|
# ==== Returns
|
345
|
-
#
|
346
|
-
#
|
371
|
+
# Hash:: An ordered hash with commands names as keys and Thor::Command
|
372
|
+
# objects as values.
|
347
373
|
#
|
348
374
|
def all_commands
|
349
|
-
@all_commands ||= from_superclass(:all_commands,
|
375
|
+
@all_commands ||= from_superclass(:all_commands, Hash.new)
|
350
376
|
@all_commands.merge!(commands)
|
351
377
|
end
|
352
378
|
alias_method :all_tasks, :all_commands
|
@@ -393,14 +419,20 @@ class Thor
|
|
393
419
|
# remove_command :this_is_not_a_command
|
394
420
|
# end
|
395
421
|
#
|
396
|
-
def no_commands
|
397
|
-
|
398
|
-
yield
|
399
|
-
ensure
|
400
|
-
@no_commands = false
|
422
|
+
def no_commands(&block)
|
423
|
+
no_commands_context.enter(&block)
|
401
424
|
end
|
425
|
+
|
402
426
|
alias_method :no_tasks, :no_commands
|
403
427
|
|
428
|
+
def no_commands_context
|
429
|
+
@no_commands_context ||= NestedContext.new
|
430
|
+
end
|
431
|
+
|
432
|
+
def no_commands?
|
433
|
+
no_commands_context.entered?
|
434
|
+
end
|
435
|
+
|
404
436
|
# Sets the namespace for the Thor or Thor::Group class. By default the
|
405
437
|
# namespace is retrieved from the class name. If your Thor class is named
|
406
438
|
# Scripts::MyScript, the help method, for example, will be called as:
|
@@ -444,13 +476,13 @@ class Thor
|
|
444
476
|
dispatch(nil, given_args.dup, nil, config)
|
445
477
|
rescue Thor::Error => e
|
446
478
|
config[:debug] || ENV["THOR_DEBUG"] == "1" ? (raise e) : config[:shell].error(e.message)
|
447
|
-
exit(
|
479
|
+
exit(false) if exit_on_failure?
|
448
480
|
rescue Errno::EPIPE
|
449
481
|
# This happens if a thor command is piped to something like `head`,
|
450
482
|
# which closes the pipe when it's done reading. This will also
|
451
483
|
# mean that if the pipe is closed, further unnecessary
|
452
484
|
# computation will not occur.
|
453
|
-
exit(
|
485
|
+
exit(true)
|
454
486
|
end
|
455
487
|
|
456
488
|
# Allows to use private methods from parent in child classes as commands.
|
@@ -471,19 +503,25 @@ class Thor
|
|
471
503
|
alias_method :public_task, :public_command
|
472
504
|
|
473
505
|
def handle_no_command_error(command, has_namespace = $thor_runner) #:nodoc:
|
474
|
-
raise UndefinedCommandError,
|
475
|
-
raise UndefinedCommandError, "Could not find command #{command.inspect}."
|
506
|
+
raise UndefinedCommandError.new(command, all_commands.keys, (namespace if has_namespace))
|
476
507
|
end
|
477
508
|
alias_method :handle_no_task_error, :handle_no_command_error
|
478
509
|
|
479
510
|
def handle_argument_error(command, error, args, arity) #:nodoc:
|
480
|
-
|
511
|
+
name = [command.ancestor_name, command.name].compact.join(" ")
|
512
|
+
msg = "ERROR: \"#{basename} #{name}\" was called with ".dup
|
481
513
|
msg << "no arguments" if args.empty?
|
482
514
|
msg << "arguments " << args.inspect unless args.empty?
|
483
|
-
msg << "\nUsage: #{banner(command).
|
515
|
+
msg << "\nUsage: \"#{banner(command).split("\n").join("\"\n \"")}\""
|
484
516
|
raise InvocationError, msg
|
485
517
|
end
|
486
518
|
|
519
|
+
# A flag that makes the process exit with status 1 if any error happens.
|
520
|
+
def exit_on_failure?
|
521
|
+
Thor.deprecation_warning "Thor exit with status 0 on errors. To keep this behavior, you must define `exit_on_failure?` in `#{self.name}`"
|
522
|
+
false
|
523
|
+
end
|
524
|
+
|
487
525
|
protected
|
488
526
|
|
489
527
|
# Prints the class options per group. If an option does not belong to
|
@@ -541,7 +579,7 @@ class Thor
|
|
541
579
|
# options<Hash>:: Described in both class_option and method_option.
|
542
580
|
# scope<Hash>:: Options hash that is being built up
|
543
581
|
def build_option(name, options, scope) #:nodoc:
|
544
|
-
scope[name] = Thor::Option.new(name, options)
|
582
|
+
scope[name] = Thor::Option.new(name, {:check_default_type => check_default_type}.merge!(options))
|
545
583
|
end
|
546
584
|
|
547
585
|
# Receives a hash of options, parse them and add to the scope. This is a
|
@@ -574,13 +612,15 @@ class Thor
|
|
574
612
|
# Everytime someone inherits from a Thor class, register the klass
|
575
613
|
# and file into baseclass.
|
576
614
|
def inherited(klass)
|
615
|
+
super(klass)
|
577
616
|
Thor::Base.register_klass_file(klass)
|
578
|
-
klass.instance_variable_set(:@no_commands,
|
617
|
+
klass.instance_variable_set(:@no_commands, 0)
|
579
618
|
end
|
580
619
|
|
581
620
|
# Fire this callback whenever a method is added. Added methods are
|
582
621
|
# tracked as commands by invoking the create_command method.
|
583
622
|
def method_added(meth)
|
623
|
+
super(meth)
|
584
624
|
meth = meth.to_s
|
585
625
|
|
586
626
|
if meth == "initialize"
|
@@ -591,8 +631,7 @@ class Thor
|
|
591
631
|
# Return if it's not a public instance method
|
592
632
|
return unless public_method_defined?(meth.to_sym)
|
593
633
|
|
594
|
-
|
595
|
-
return if @no_commands || !create_command(meth)
|
634
|
+
return if no_commands? || !create_command(meth)
|
596
635
|
|
597
636
|
is_thor_reserved_word?(meth, :command)
|
598
637
|
Thor::Base.register_klass_file(self)
|
@@ -619,11 +658,6 @@ class Thor
|
|
619
658
|
end
|
620
659
|
end
|
621
660
|
|
622
|
-
# A flag that makes the process exit with status 1 if any error happens.
|
623
|
-
def exit_on_failure?
|
624
|
-
false
|
625
|
-
end
|
626
|
-
|
627
661
|
#
|
628
662
|
# The basename of the program invoking the thor class.
|
629
663
|
#
|
data/lib/thor/command.rb
CHANGED
@@ -1,9 +1,9 @@
|
|
1
1
|
class Thor
|
2
|
-
class Command < Struct.new(:name, :description, :long_description, :usage, :options, :
|
2
|
+
class Command < Struct.new(:name, :description, :long_description, :usage, :options, :ancestor_name)
|
3
3
|
FILE_REGEXP = /^#{Regexp.escape(File.dirname(__FILE__))}/
|
4
4
|
|
5
|
-
def initialize(name, description, long_description, usage, options = nil
|
6
|
-
super(name.to_s, description, long_description, usage, options || {}
|
5
|
+
def initialize(name, description, long_description, usage, options = nil)
|
6
|
+
super(name.to_s, description, long_description, usage, options || {})
|
7
7
|
end
|
8
8
|
|
9
9
|
def initialize_copy(other) #:nodoc:
|
@@ -39,32 +39,42 @@ class Thor
|
|
39
39
|
# Returns the formatted usage by injecting given required arguments
|
40
40
|
# and required options into the given usage.
|
41
41
|
def formatted_usage(klass, namespace = true, subcommand = false)
|
42
|
-
if
|
42
|
+
if ancestor_name
|
43
|
+
formatted = "#{ancestor_name} ".dup # add space
|
44
|
+
elsif namespace
|
43
45
|
namespace = klass.namespace
|
44
|
-
formatted = "#{namespace.gsub(/^(default)/, '')}:"
|
46
|
+
formatted = "#{namespace.gsub(/^(default)/, '')}:".dup
|
45
47
|
end
|
46
|
-
formatted
|
48
|
+
formatted ||= "#{klass.namespace.split(':').last} ".dup if subcommand
|
47
49
|
|
48
|
-
formatted ||= ""
|
50
|
+
formatted ||= "".dup
|
49
51
|
|
50
|
-
|
51
|
-
|
52
|
-
usage.to_s.gsub(/^#{name}/) do |match|
|
53
|
-
match << " " << klass.arguments.map(&:usage).compact.join(" ")
|
54
|
-
end
|
55
|
-
else
|
56
|
-
usage.to_s
|
57
|
-
end
|
52
|
+
Array(usage).map do |specific_usage|
|
53
|
+
formatted_specific_usage = formatted
|
58
54
|
|
59
|
-
|
60
|
-
formatted << " #{required_options}"
|
55
|
+
formatted_specific_usage += required_arguments_for(klass, specific_usage)
|
61
56
|
|
62
|
-
|
63
|
-
|
57
|
+
# Add required options
|
58
|
+
formatted_specific_usage += " #{required_options}"
|
59
|
+
|
60
|
+
# Strip and go!
|
61
|
+
formatted_specific_usage.strip
|
62
|
+
end.join("\n")
|
64
63
|
end
|
65
64
|
|
66
65
|
protected
|
67
66
|
|
67
|
+
# Add usage with required arguments
|
68
|
+
def required_arguments_for(klass, usage)
|
69
|
+
if klass && !klass.arguments.empty?
|
70
|
+
usage.to_s.gsub(/^#{name}/) do |match|
|
71
|
+
match << " " << klass.arguments.map(&:usage).compact.join(" ")
|
72
|
+
end
|
73
|
+
else
|
74
|
+
usage.to_s
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
68
78
|
def not_debugging?(instance)
|
69
79
|
!(instance.class.respond_to?(:debugging) && instance.class.debugging)
|
70
80
|
end
|
@@ -95,8 +105,7 @@ class Thor
|
|
95
105
|
def handle_argument_error?(instance, error, caller)
|
96
106
|
not_debugging?(instance) && (error.message =~ /wrong number of arguments/ || error.message =~ /given \d*, expected \d*/) && begin
|
97
107
|
saned = sans_backtrace(error.backtrace, caller)
|
98
|
-
|
99
|
-
saned.empty? || (saned.size == 1 && RUBY_VERSION >= "1.9")
|
108
|
+
saned.empty? || saned.size == 1
|
100
109
|
end
|
101
110
|
end
|
102
111
|
|
@@ -51,6 +51,18 @@ class Thor
|
|
51
51
|
self
|
52
52
|
end
|
53
53
|
|
54
|
+
def reverse_merge(other)
|
55
|
+
self.class.new(other).merge(self)
|
56
|
+
end
|
57
|
+
|
58
|
+
def reverse_merge!(other_hash)
|
59
|
+
replace(reverse_merge(other_hash))
|
60
|
+
end
|
61
|
+
|
62
|
+
def replace(other_hash)
|
63
|
+
super(other_hash)
|
64
|
+
end
|
65
|
+
|
54
66
|
# Convert to a Hash with String keys.
|
55
67
|
def to_hash
|
56
68
|
Hash.new(default).merge!(self)
|
data/lib/thor/error.rb
CHANGED
@@ -1,4 +1,19 @@
|
|
1
1
|
class Thor
|
2
|
+
Correctable = if defined?(DidYouMean::SpellChecker) && defined?(DidYouMean::Correctable)
|
3
|
+
# In order to support versions of Ruby that don't have keyword
|
4
|
+
# arguments, we need our own spell checker class that doesn't take key
|
5
|
+
# words. Even though this code wouldn't be hit because of the check
|
6
|
+
# above, it's still necessary because the interpreter would otherwise be
|
7
|
+
# unable to parse the file.
|
8
|
+
class NoKwargSpellChecker < DidYouMean::SpellChecker # :nodoc:
|
9
|
+
def initialize(dictionary)
|
10
|
+
@dictionary = dictionary
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
DidYouMean::Correctable
|
15
|
+
end
|
16
|
+
|
2
17
|
# Thor::Error is raised when it's caused by wrong usage of thor classes. Those
|
3
18
|
# errors have their backtrace suppressed and are nicely shown to the user.
|
4
19
|
#
|
@@ -10,6 +25,35 @@ class Thor
|
|
10
25
|
|
11
26
|
# Raised when a command was not found.
|
12
27
|
class UndefinedCommandError < Error
|
28
|
+
class SpellChecker
|
29
|
+
attr_reader :error
|
30
|
+
|
31
|
+
def initialize(error)
|
32
|
+
@error = error
|
33
|
+
end
|
34
|
+
|
35
|
+
def corrections
|
36
|
+
@corrections ||= spell_checker.correct(error.command).map(&:inspect)
|
37
|
+
end
|
38
|
+
|
39
|
+
def spell_checker
|
40
|
+
NoKwargSpellChecker.new(error.all_commands)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
attr_reader :command, :all_commands
|
45
|
+
|
46
|
+
def initialize(command, all_commands, namespace)
|
47
|
+
@command = command
|
48
|
+
@all_commands = all_commands
|
49
|
+
|
50
|
+
message = "Could not find command #{command.inspect}"
|
51
|
+
message = namespace ? "#{message} in #{namespace.inspect} namespace." : "#{message}."
|
52
|
+
|
53
|
+
super(message)
|
54
|
+
end
|
55
|
+
|
56
|
+
prepend Correctable if Correctable
|
13
57
|
end
|
14
58
|
UndefinedTaskError = UndefinedCommandError
|
15
59
|
|
@@ -22,6 +66,33 @@ class Thor
|
|
22
66
|
end
|
23
67
|
|
24
68
|
class UnknownArgumentError < Error
|
69
|
+
class SpellChecker
|
70
|
+
attr_reader :error
|
71
|
+
|
72
|
+
def initialize(error)
|
73
|
+
@error = error
|
74
|
+
end
|
75
|
+
|
76
|
+
def corrections
|
77
|
+
@corrections ||=
|
78
|
+
error.unknown.flat_map { |unknown| spell_checker.correct(unknown) }.uniq.map(&:inspect)
|
79
|
+
end
|
80
|
+
|
81
|
+
def spell_checker
|
82
|
+
@spell_checker ||= NoKwargSpellChecker.new(error.switches)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
attr_reader :switches, :unknown
|
87
|
+
|
88
|
+
def initialize(switches, unknown)
|
89
|
+
@switches = switches
|
90
|
+
@unknown = unknown
|
91
|
+
|
92
|
+
super("Unknown switches #{unknown.map(&:inspect).join(', ')}")
|
93
|
+
end
|
94
|
+
|
95
|
+
prepend Correctable if Correctable
|
25
96
|
end
|
26
97
|
|
27
98
|
class RequiredArgumentMissingError < InvocationError
|
@@ -29,4 +100,11 @@ class Thor
|
|
29
100
|
|
30
101
|
class MalformattedArgumentError < InvocationError
|
31
102
|
end
|
103
|
+
|
104
|
+
if Correctable
|
105
|
+
DidYouMean::SPELL_CHECKERS.merge!(
|
106
|
+
'Thor::UndefinedCommandError' => UndefinedCommandError::SpellChecker,
|
107
|
+
'Thor::UnknownArgumentError' => UnknownArgumentError::SpellChecker
|
108
|
+
)
|
109
|
+
end
|
32
110
|
end
|