thor 0.16.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 +7 -0
- data/CONTRIBUTING.md +15 -0
- data/README.md +23 -6
- data/bin/thor +1 -1
- data/lib/thor/actions/create_file.rb +34 -35
- data/lib/thor/actions/create_link.rb +9 -5
- data/lib/thor/actions/directory.rb +33 -23
- data/lib/thor/actions/empty_directory.rb +75 -85
- data/lib/thor/actions/file_manipulation.rb +103 -36
- data/lib/thor/actions/inject_into_file.rb +46 -36
- data/lib/thor/actions.rb +90 -68
- data/lib/thor/base.rb +302 -244
- data/lib/thor/command.rb +142 -0
- data/lib/thor/core_ext/hash_with_indifferent_access.rb +52 -24
- data/lib/thor/error.rb +90 -10
- data/lib/thor/group.rb +70 -74
- data/lib/thor/invocation.rb +63 -55
- data/lib/thor/line_editor/basic.rb +37 -0
- data/lib/thor/line_editor/readline.rb +88 -0
- data/lib/thor/line_editor.rb +17 -0
- data/lib/thor/nested_context.rb +29 -0
- data/lib/thor/parser/argument.rb +24 -28
- data/lib/thor/parser/arguments.rb +110 -102
- data/lib/thor/parser/option.rb +53 -15
- data/lib/thor/parser/options.rb +174 -97
- data/lib/thor/parser.rb +4 -4
- data/lib/thor/rake_compat.rb +12 -11
- data/lib/thor/runner.rb +159 -155
- data/lib/thor/shell/basic.rb +216 -93
- data/lib/thor/shell/color.rb +53 -40
- data/lib/thor/shell/html.rb +61 -58
- data/lib/thor/shell.rb +29 -36
- data/lib/thor/util.rb +231 -213
- data/lib/thor/version.rb +1 -1
- data/lib/thor.rb +303 -166
- data/thor.gemspec +27 -24
- metadata +36 -226
- data/.gitignore +0 -44
- data/.rspec +0 -2
- data/.travis.yml +0 -7
- data/CHANGELOG.rdoc +0 -134
- data/Gemfile +0 -15
- data/Thorfile +0 -30
- data/bin/rake2thor +0 -86
- data/lib/thor/core_ext/dir_escape.rb +0 -0
- data/lib/thor/core_ext/file_binary_read.rb +0 -9
- data/lib/thor/core_ext/ordered_hash.rb +0 -100
- data/lib/thor/task.rb +0 -132
- data/spec/actions/create_file_spec.rb +0 -170
- data/spec/actions/create_link_spec.rb +0 -81
- data/spec/actions/directory_spec.rb +0 -149
- data/spec/actions/empty_directory_spec.rb +0 -130
- data/spec/actions/file_manipulation_spec.rb +0 -370
- data/spec/actions/inject_into_file_spec.rb +0 -135
- data/spec/actions_spec.rb +0 -331
- data/spec/base_spec.rb +0 -279
- data/spec/core_ext/hash_with_indifferent_access_spec.rb +0 -43
- data/spec/core_ext/ordered_hash_spec.rb +0 -115
- data/spec/exit_condition_spec.rb +0 -19
- data/spec/fixtures/application.rb +0 -2
- data/spec/fixtures/app{1}/README +0 -3
- data/spec/fixtures/bundle/execute.rb +0 -6
- data/spec/fixtures/bundle/main.thor +0 -1
- data/spec/fixtures/doc/%file_name%.rb.tt +0 -1
- data/spec/fixtures/doc/COMMENTER +0 -10
- data/spec/fixtures/doc/README +0 -3
- data/spec/fixtures/doc/block_helper.rb +0 -3
- data/spec/fixtures/doc/components/.empty_directory +0 -0
- data/spec/fixtures/doc/config.rb +0 -1
- data/spec/fixtures/doc/config.yaml.tt +0 -1
- data/spec/fixtures/enum.thor +0 -10
- data/spec/fixtures/group.thor +0 -114
- data/spec/fixtures/invoke.thor +0 -112
- data/spec/fixtures/path with spaces +0 -0
- data/spec/fixtures/script.thor +0 -190
- data/spec/fixtures/task.thor +0 -10
- data/spec/group_spec.rb +0 -216
- data/spec/invocation_spec.rb +0 -100
- data/spec/parser/argument_spec.rb +0 -53
- data/spec/parser/arguments_spec.rb +0 -66
- data/spec/parser/option_spec.rb +0 -202
- data/spec/parser/options_spec.rb +0 -330
- data/spec/rake_compat_spec.rb +0 -72
- data/spec/register_spec.rb +0 -135
- data/spec/runner_spec.rb +0 -241
- data/spec/shell/basic_spec.rb +0 -300
- data/spec/shell/color_spec.rb +0 -81
- data/spec/shell/html_spec.rb +0 -32
- data/spec/shell_spec.rb +0 -47
- data/spec/spec_helper.rb +0 -59
- data/spec/task_spec.rb +0 -80
- data/spec/thor_spec.rb +0 -418
- data/spec/util_spec.rb +0 -196
data/lib/thor/command.rb
ADDED
@@ -0,0 +1,142 @@
|
|
1
|
+
class Thor
|
2
|
+
class Command < Struct.new(:name, :description, :long_description, :usage, :options, :ancestor_name)
|
3
|
+
FILE_REGEXP = /^#{Regexp.escape(File.dirname(__FILE__))}/
|
4
|
+
|
5
|
+
def initialize(name, description, long_description, usage, options = nil)
|
6
|
+
super(name.to_s, description, long_description, usage, options || {})
|
7
|
+
end
|
8
|
+
|
9
|
+
def initialize_copy(other) #:nodoc:
|
10
|
+
super(other)
|
11
|
+
self.options = other.options.dup if other.options
|
12
|
+
end
|
13
|
+
|
14
|
+
def hidden?
|
15
|
+
false
|
16
|
+
end
|
17
|
+
|
18
|
+
# By default, a command invokes a method in the thor class. You can change this
|
19
|
+
# implementation to create custom commands.
|
20
|
+
def run(instance, args = [])
|
21
|
+
arity = nil
|
22
|
+
|
23
|
+
if private_method?(instance)
|
24
|
+
instance.class.handle_no_command_error(name)
|
25
|
+
elsif public_method?(instance)
|
26
|
+
arity = instance.method(name).arity
|
27
|
+
instance.__send__(name, *args)
|
28
|
+
elsif local_method?(instance, :method_missing)
|
29
|
+
instance.__send__(:method_missing, name.to_sym, *args)
|
30
|
+
else
|
31
|
+
instance.class.handle_no_command_error(name)
|
32
|
+
end
|
33
|
+
rescue ArgumentError => e
|
34
|
+
handle_argument_error?(instance, e, caller) ? instance.class.handle_argument_error(self, e, args, arity) : (raise e)
|
35
|
+
rescue NoMethodError => e
|
36
|
+
handle_no_method_error?(instance, e, caller) ? instance.class.handle_no_command_error(name) : (raise e)
|
37
|
+
end
|
38
|
+
|
39
|
+
# Returns the formatted usage by injecting given required arguments
|
40
|
+
# and required options into the given usage.
|
41
|
+
def formatted_usage(klass, namespace = true, subcommand = false)
|
42
|
+
if ancestor_name
|
43
|
+
formatted = "#{ancestor_name} ".dup # add space
|
44
|
+
elsif namespace
|
45
|
+
namespace = klass.namespace
|
46
|
+
formatted = "#{namespace.gsub(/^(default)/, '')}:".dup
|
47
|
+
end
|
48
|
+
formatted ||= "#{klass.namespace.split(':').last} ".dup if subcommand
|
49
|
+
|
50
|
+
formatted ||= "".dup
|
51
|
+
|
52
|
+
Array(usage).map do |specific_usage|
|
53
|
+
formatted_specific_usage = formatted
|
54
|
+
|
55
|
+
formatted_specific_usage += required_arguments_for(klass, specific_usage)
|
56
|
+
|
57
|
+
# Add required options
|
58
|
+
formatted_specific_usage += " #{required_options}"
|
59
|
+
|
60
|
+
# Strip and go!
|
61
|
+
formatted_specific_usage.strip
|
62
|
+
end.join("\n")
|
63
|
+
end
|
64
|
+
|
65
|
+
protected
|
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
|
+
|
78
|
+
def not_debugging?(instance)
|
79
|
+
!(instance.class.respond_to?(:debugging) && instance.class.debugging)
|
80
|
+
end
|
81
|
+
|
82
|
+
def required_options
|
83
|
+
@required_options ||= options.map { |_, o| o.usage if o.required? }.compact.sort.join(" ")
|
84
|
+
end
|
85
|
+
|
86
|
+
# Given a target, checks if this class name is a public method.
|
87
|
+
def public_method?(instance) #:nodoc:
|
88
|
+
!(instance.public_methods & [name.to_s, name.to_sym]).empty?
|
89
|
+
end
|
90
|
+
|
91
|
+
def private_method?(instance)
|
92
|
+
!(instance.private_methods & [name.to_s, name.to_sym]).empty?
|
93
|
+
end
|
94
|
+
|
95
|
+
def local_method?(instance, name)
|
96
|
+
methods = instance.public_methods(false) + instance.private_methods(false) + instance.protected_methods(false)
|
97
|
+
!(methods & [name.to_s, name.to_sym]).empty?
|
98
|
+
end
|
99
|
+
|
100
|
+
def sans_backtrace(backtrace, caller) #:nodoc:
|
101
|
+
saned = backtrace.reject { |frame| frame =~ FILE_REGEXP || (frame =~ /\.java:/ && RUBY_PLATFORM =~ /java/) || (frame =~ %r{^kernel/} && RUBY_ENGINE =~ /rbx/) }
|
102
|
+
saned - caller
|
103
|
+
end
|
104
|
+
|
105
|
+
def handle_argument_error?(instance, error, caller)
|
106
|
+
not_debugging?(instance) && (error.message =~ /wrong number of arguments/ || error.message =~ /given \d*, expected \d*/) && begin
|
107
|
+
saned = sans_backtrace(error.backtrace, caller)
|
108
|
+
saned.empty? || saned.size == 1
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def handle_no_method_error?(instance, error, caller)
|
113
|
+
not_debugging?(instance) &&
|
114
|
+
error.message =~ /^undefined method `#{name}' for #{Regexp.escape(instance.to_s)}$/
|
115
|
+
end
|
116
|
+
end
|
117
|
+
Task = Command
|
118
|
+
|
119
|
+
# A command that is hidden in help messages but still invocable.
|
120
|
+
class HiddenCommand < Command
|
121
|
+
def hidden?
|
122
|
+
true
|
123
|
+
end
|
124
|
+
end
|
125
|
+
HiddenTask = HiddenCommand
|
126
|
+
|
127
|
+
# A dynamic command that handles method missing scenarios.
|
128
|
+
class DynamicCommand < Command
|
129
|
+
def initialize(name, options = nil)
|
130
|
+
super(name.to_s, "A dynamically-generated command", name.to_s, name.to_s, options)
|
131
|
+
end
|
132
|
+
|
133
|
+
def run(instance, args = [])
|
134
|
+
if (instance.methods & [name.to_s, name.to_sym]).empty?
|
135
|
+
super
|
136
|
+
else
|
137
|
+
instance.class.handle_no_command_error(name)
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
DynamicTask = DynamicCommand
|
142
|
+
end
|
@@ -1,6 +1,5 @@
|
|
1
1
|
class Thor
|
2
2
|
module CoreExt #:nodoc:
|
3
|
-
|
4
3
|
# A hash with indifferent access and magic predicates.
|
5
4
|
#
|
6
5
|
# hash = Thor::CoreExt::HashWithIndifferentAccess.new 'foo' => 'bar', 'baz' => 'bee', 'force' => true
|
@@ -10,8 +9,7 @@ class Thor
|
|
10
9
|
# hash.foo? #=> true
|
11
10
|
#
|
12
11
|
class HashWithIndifferentAccess < ::Hash #:nodoc:
|
13
|
-
|
14
|
-
def initialize(hash={})
|
12
|
+
def initialize(hash = {})
|
15
13
|
super()
|
16
14
|
hash.each do |key, value|
|
17
15
|
self[convert_key(key)] = value
|
@@ -30,8 +28,22 @@ class Thor
|
|
30
28
|
super(convert_key(key))
|
31
29
|
end
|
32
30
|
|
31
|
+
def except(*keys)
|
32
|
+
dup.tap do |hash|
|
33
|
+
keys.each { |key| hash.delete(convert_key(key)) }
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def fetch(key, *args)
|
38
|
+
super(convert_key(key), *args)
|
39
|
+
end
|
40
|
+
|
41
|
+
def key?(key)
|
42
|
+
super(convert_key(key))
|
43
|
+
end
|
44
|
+
|
33
45
|
def values_at(*indices)
|
34
|
-
indices.
|
46
|
+
indices.map { |key| self[convert_key(key)] }
|
35
47
|
end
|
36
48
|
|
37
49
|
def merge(other)
|
@@ -45,31 +57,47 @@ class Thor
|
|
45
57
|
self
|
46
58
|
end
|
47
59
|
|
48
|
-
|
60
|
+
def reverse_merge(other)
|
61
|
+
self.class.new(other).merge(self)
|
62
|
+
end
|
63
|
+
|
64
|
+
def reverse_merge!(other_hash)
|
65
|
+
replace(reverse_merge(other_hash))
|
66
|
+
end
|
49
67
|
|
50
|
-
|
51
|
-
|
52
|
-
|
68
|
+
def replace(other_hash)
|
69
|
+
super(other_hash)
|
70
|
+
end
|
71
|
+
|
72
|
+
# Convert to a Hash with String keys.
|
73
|
+
def to_hash
|
74
|
+
Hash.new(default).merge!(self)
|
75
|
+
end
|
53
76
|
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
77
|
+
protected
|
78
|
+
|
79
|
+
def convert_key(key)
|
80
|
+
key.is_a?(Symbol) ? key.to_s : key
|
81
|
+
end
|
82
|
+
|
83
|
+
# Magic predicates. For instance:
|
84
|
+
#
|
85
|
+
# options.force? # => !!options['force']
|
86
|
+
# options.shebang # => "/usr/lib/local/ruby"
|
87
|
+
# options.test_framework?(:rspec) # => options[:test_framework] == :rspec
|
88
|
+
#
|
89
|
+
def method_missing(method, *args)
|
90
|
+
method = method.to_s
|
91
|
+
if method =~ /^(\w+)\?$/
|
92
|
+
if args.empty?
|
93
|
+
!!self[$1]
|
68
94
|
else
|
69
|
-
self[
|
95
|
+
self[$1] == args.first
|
70
96
|
end
|
97
|
+
else
|
98
|
+
self[method]
|
71
99
|
end
|
72
|
-
|
100
|
+
end
|
73
101
|
end
|
74
102
|
end
|
75
103
|
end
|
data/lib/thor/error.rb
CHANGED
@@ -1,25 +1,98 @@
|
|
1
1
|
class Thor
|
2
|
+
Correctable = if defined?(DidYouMean::SpellChecker) && defined?(DidYouMean::Correctable) # rubocop:disable Naming/ConstantName
|
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
|
#
|
5
20
|
# Errors that are caused by the developer, like declaring a method which
|
6
|
-
# overwrites a thor keyword,
|
21
|
+
# overwrites a thor keyword, SHOULD NOT raise a Thor::Error. This way, we
|
7
22
|
# ensure that developer errors are shown with full backtrace.
|
8
|
-
#
|
9
23
|
class Error < StandardError
|
10
24
|
end
|
11
25
|
|
12
|
-
# Raised when a
|
13
|
-
|
14
|
-
|
26
|
+
# Raised when a command was not found.
|
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
|
15
57
|
end
|
58
|
+
UndefinedTaskError = UndefinedCommandError
|
16
59
|
|
17
|
-
|
18
|
-
|
60
|
+
class AmbiguousCommandError < Error
|
61
|
+
end
|
62
|
+
AmbiguousTaskError = AmbiguousCommandError
|
63
|
+
|
64
|
+
# Raised when a command was found, but not invoked properly.
|
19
65
|
class InvocationError < Error
|
20
66
|
end
|
21
67
|
|
22
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
|
23
96
|
end
|
24
97
|
|
25
98
|
class RequiredArgumentMissingError < InvocationError
|
@@ -28,8 +101,15 @@ class Thor
|
|
28
101
|
class MalformattedArgumentError < InvocationError
|
29
102
|
end
|
30
103
|
|
31
|
-
|
32
|
-
|
33
|
-
|
104
|
+
if Correctable
|
105
|
+
if DidYouMean.respond_to?(:correct_error)
|
106
|
+
DidYouMean.correct_error(Thor::UndefinedCommandError, UndefinedCommandError::SpellChecker)
|
107
|
+
DidYouMean.correct_error(Thor::UnknownArgumentError, UnknownArgumentError::SpellChecker)
|
108
|
+
else
|
109
|
+
DidYouMean::SPELL_CHECKERS.merge!(
|
110
|
+
'Thor::UndefinedCommandError' => UndefinedCommandError::SpellChecker,
|
111
|
+
'Thor::UnknownArgumentError' => UnknownArgumentError::SpellChecker
|
112
|
+
)
|
113
|
+
end
|
34
114
|
end
|
35
115
|
end
|
data/lib/thor/group.rb
CHANGED
@@ -1,9 +1,9 @@
|
|
1
|
-
|
1
|
+
require_relative "base"
|
2
2
|
|
3
3
|
# Thor has a special class called Thor::Group. The main difference to Thor class
|
4
|
-
# is that it invokes all
|
4
|
+
# is that it invokes all commands at once. It also include some methods that allows
|
5
5
|
# invocations to be done at the class method, which are not available to Thor
|
6
|
-
#
|
6
|
+
# commands.
|
7
7
|
class Thor::Group
|
8
8
|
class << self
|
9
9
|
# The description for this Thor::Group. If none is provided, but a source root
|
@@ -13,12 +13,11 @@ class Thor::Group
|
|
13
13
|
# ==== Parameters
|
14
14
|
# description<String>:: The description for this Thor::Group.
|
15
15
|
#
|
16
|
-
def desc(description=nil)
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
@desc = description
|
16
|
+
def desc(description = nil)
|
17
|
+
if description
|
18
|
+
@desc = description
|
19
|
+
else
|
20
|
+
@desc ||= from_superclass(:desc, nil)
|
22
21
|
end
|
23
22
|
end
|
24
23
|
|
@@ -32,7 +31,7 @@ class Thor::Group
|
|
32
31
|
shell.say " #{banner}\n"
|
33
32
|
shell.say
|
34
33
|
class_options_help(shell)
|
35
|
-
shell.say
|
34
|
+
shell.say desc if desc
|
36
35
|
end
|
37
36
|
|
38
37
|
# Stores invocations for this class merging with superclass values.
|
@@ -48,7 +47,7 @@ class Thor::Group
|
|
48
47
|
end
|
49
48
|
|
50
49
|
# Invoke the given namespace or class given. It adds an instance
|
51
|
-
# method that will invoke the klass and
|
50
|
+
# method that will invoke the klass and command. You can give a block to
|
52
51
|
# configure how it will be invoked.
|
53
52
|
#
|
54
53
|
# The namespace/class given will have its options showed on the help
|
@@ -62,14 +61,14 @@ class Thor::Group
|
|
62
61
|
invocations[name] = false
|
63
62
|
invocation_blocks[name] = block if block_given?
|
64
63
|
|
65
|
-
class_eval <<-METHOD, __FILE__, __LINE__
|
64
|
+
class_eval <<-METHOD, __FILE__, __LINE__ + 1
|
66
65
|
def _invoke_#{name.to_s.gsub(/\W/, '_')}
|
67
|
-
klass,
|
66
|
+
klass, command = self.class.prepare_for_invocation(nil, #{name.inspect})
|
68
67
|
|
69
68
|
if klass
|
70
69
|
say_status :invoke, #{name.inspect}, #{verbose.inspect}
|
71
70
|
block = self.class.invocation_blocks[#{name.inspect}]
|
72
|
-
_invoke_for_class_method klass,
|
71
|
+
_invoke_for_class_method klass, command, &block
|
73
72
|
else
|
74
73
|
say_status :error, %(#{name.inspect} [not found]), :red
|
75
74
|
end
|
@@ -100,7 +99,7 @@ class Thor::Group
|
|
100
99
|
# In some cases you want to customize how a specified hook is going to be
|
101
100
|
# invoked. You can do that by overwriting the class method
|
102
101
|
# prepare_for_invocation. The class method must necessarily return a klass
|
103
|
-
# and an optional
|
102
|
+
# and an optional command.
|
104
103
|
#
|
105
104
|
# ==== Custom invocations
|
106
105
|
#
|
@@ -114,25 +113,25 @@ class Thor::Group
|
|
114
113
|
|
115
114
|
names.each do |name|
|
116
115
|
unless class_options.key?(name)
|
117
|
-
raise ArgumentError, "You have to define the option #{name.inspect} "
|
118
|
-
|
116
|
+
raise ArgumentError, "You have to define the option #{name.inspect} " \
|
117
|
+
"before setting invoke_from_option."
|
119
118
|
end
|
120
119
|
|
121
120
|
invocations[name] = true
|
122
121
|
invocation_blocks[name] = block if block_given?
|
123
122
|
|
124
|
-
class_eval <<-METHOD, __FILE__, __LINE__
|
123
|
+
class_eval <<-METHOD, __FILE__, __LINE__ + 1
|
125
124
|
def _invoke_from_option_#{name.to_s.gsub(/\W/, '_')}
|
126
125
|
return unless options[#{name.inspect}]
|
127
126
|
|
128
127
|
value = options[#{name.inspect}]
|
129
128
|
value = #{name.inspect} if TrueClass === value
|
130
|
-
klass,
|
129
|
+
klass, command = self.class.prepare_for_invocation(#{name.inspect}, value)
|
131
130
|
|
132
131
|
if klass
|
133
132
|
say_status :invoke, value, #{verbose.inspect}
|
134
133
|
block = self.class.invocation_blocks[#{name.inspect}]
|
135
|
-
_invoke_for_class_method klass,
|
134
|
+
_invoke_for_class_method klass, command, &block
|
136
135
|
else
|
137
136
|
say_status :error, %(\#{value} [not found]), :red
|
138
137
|
end
|
@@ -149,7 +148,7 @@ class Thor::Group
|
|
149
148
|
#
|
150
149
|
def remove_invocation(*names)
|
151
150
|
names.each do |name|
|
152
|
-
|
151
|
+
remove_command(name)
|
153
152
|
remove_class_option(name)
|
154
153
|
invocations.delete(name)
|
155
154
|
invocation_blocks.delete(name)
|
@@ -159,7 +158,7 @@ class Thor::Group
|
|
159
158
|
# Overwrite class options help to allow invoked generators options to be
|
160
159
|
# shown recursively when invoking a generator.
|
161
160
|
#
|
162
|
-
def class_options_help(shell, groups={}) #:nodoc:
|
161
|
+
def class_options_help(shell, groups = {}) #:nodoc:
|
163
162
|
get_options_from_invocations(groups, class_options) do |klass|
|
164
163
|
klass.send(:get_options_from_invocations, groups, class_options)
|
165
164
|
end
|
@@ -170,7 +169,7 @@ class Thor::Group
|
|
170
169
|
# options are added to group_options hash. Options that already exists
|
171
170
|
# in base_options are not added twice.
|
172
171
|
#
|
173
|
-
def get_options_from_invocations(group_options, base_options) #:nodoc:
|
172
|
+
def get_options_from_invocations(group_options, base_options) #:nodoc: # rubocop:disable MethodLength
|
174
173
|
invocations.each do |name, from_option|
|
175
174
|
value = if from_option
|
176
175
|
option = class_options[name]
|
@@ -189,96 +188,93 @@ class Thor::Group
|
|
189
188
|
group_options[human_name] ||= []
|
190
189
|
group_options[human_name] += klass.class_options.values.select do |class_option|
|
191
190
|
base_options[class_option.name.to_sym].nil? && class_option.group.nil? &&
|
192
|
-
|
191
|
+
!group_options.values.flatten.any? { |i| i.name == class_option.name }
|
193
192
|
end
|
194
193
|
|
195
194
|
yield klass if block_given?
|
196
195
|
end
|
197
196
|
end
|
198
197
|
|
199
|
-
# Returns
|
200
|
-
def
|
198
|
+
# Returns commands ready to be printed.
|
199
|
+
def printable_commands(*)
|
201
200
|
item = []
|
202
201
|
item << banner
|
203
|
-
item << (desc ? "# #{desc.gsub(/\s+/m,' ')}" : "")
|
202
|
+
item << (desc ? "# #{desc.gsub(/\s+/m, ' ')}" : "")
|
204
203
|
[item]
|
205
204
|
end
|
205
|
+
alias_method :printable_tasks, :printable_commands
|
206
206
|
|
207
|
-
def handle_argument_error(
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
msg << ", but it should not."
|
212
|
-
else
|
213
|
-
msg = "You should not pass arguments to #{basename} #{task.name}."
|
214
|
-
end
|
215
|
-
|
207
|
+
def handle_argument_error(command, error, _args, arity) #:nodoc:
|
208
|
+
msg = "#{basename} #{command.name} takes #{arity} argument".dup
|
209
|
+
msg << "s" if arity > 1
|
210
|
+
msg << ", but it should not."
|
216
211
|
raise error, msg
|
217
212
|
end
|
218
213
|
|
219
|
-
|
214
|
+
protected
|
220
215
|
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
216
|
+
# The method responsible for dispatching given the args.
|
217
|
+
def dispatch(command, given_args, given_opts, config) #:nodoc:
|
218
|
+
if Thor::HELP_MAPPINGS.include?(given_args.first)
|
219
|
+
help(config[:shell])
|
220
|
+
return
|
221
|
+
end
|
227
222
|
|
228
|
-
|
229
|
-
|
223
|
+
args, opts = Thor::Options.split(given_args)
|
224
|
+
opts = given_opts || opts
|
230
225
|
|
231
|
-
|
232
|
-
|
233
|
-
args = instance.args
|
226
|
+
instance = new(args, opts, config)
|
227
|
+
yield instance if block_given?
|
234
228
|
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
end
|
229
|
+
if command
|
230
|
+
instance.invoke_command(all_commands[command])
|
231
|
+
else
|
232
|
+
instance.invoke_all
|
240
233
|
end
|
234
|
+
end
|
241
235
|
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
236
|
+
# The banner for this class. You can customize it if you are invoking the
|
237
|
+
# thor class by another ways which is not the Thor::Runner.
|
238
|
+
def banner
|
239
|
+
"#{basename} #{self_command.formatted_usage(self, false)}"
|
240
|
+
end
|
247
241
|
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
242
|
+
# Represents the whole class as a command.
|
243
|
+
def self_command #:nodoc:
|
244
|
+
Thor::DynamicCommand.new(namespace, class_options)
|
245
|
+
end
|
246
|
+
alias_method :self_task, :self_command
|
252
247
|
|
253
|
-
|
254
|
-
|
255
|
-
|
248
|
+
def baseclass #:nodoc:
|
249
|
+
Thor::Group
|
250
|
+
end
|
256
251
|
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
252
|
+
def create_command(meth) #:nodoc:
|
253
|
+
commands[meth.to_s] = Thor::Command.new(meth, nil, nil, nil, nil)
|
254
|
+
true
|
255
|
+
end
|
256
|
+
alias_method :create_task, :create_command
|
261
257
|
end
|
262
258
|
|
263
259
|
include Thor::Base
|
264
260
|
|
265
|
-
|
261
|
+
protected
|
266
262
|
|
267
263
|
# Shortcut to invoke with padding and block handling. Use internally by
|
268
264
|
# invoke and invoke_from_option class methods.
|
269
|
-
def _invoke_for_class_method(klass,
|
265
|
+
def _invoke_for_class_method(klass, command = nil, *args, &block) #:nodoc:
|
270
266
|
with_padding do
|
271
267
|
if block
|
272
268
|
case block.arity
|
273
269
|
when 3
|
274
|
-
|
270
|
+
yield(self, klass, command)
|
275
271
|
when 2
|
276
|
-
|
272
|
+
yield(self, klass)
|
277
273
|
when 1
|
278
274
|
instance_exec(klass, &block)
|
279
275
|
end
|
280
276
|
else
|
281
|
-
invoke klass,
|
277
|
+
invoke klass, command, *args
|
282
278
|
end
|
283
279
|
end
|
284
280
|
end
|