atli 0.1.9 → 0.1.10
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.yardopts +1 -0
- data/atli.gemspec +3 -1
- data/lib/thor.rb +748 -732
- data/lib/thor/base.rb +160 -92
- data/lib/thor/base/arguments_concern.rb +298 -0
- data/lib/thor/base/class_methods.rb +31 -120
- data/lib/thor/base/shared_concern.rb +123 -0
- data/lib/thor/base/shared_options_concern.rb +235 -0
- data/lib/thor/command.rb +51 -6
- data/lib/thor/completion/bash.rb +71 -126
- data/lib/thor/completion/bash/argument_mixin.rb +83 -0
- data/lib/thor/completion/bash/command_mixin.rb +236 -0
- data/lib/thor/completion/bash/request.rb +48 -0
- data/lib/thor/completion/bash/subcmd.rb +136 -0
- data/lib/thor/completion/bash/thor_mixin.rb +250 -0
- data/lib/thor/core_ext/hash_with_indifferent_access.rb +15 -1
- data/lib/thor/execution.rb +2 -1
- data/lib/thor/parser/argument.rb +61 -10
- data/lib/thor/parser/arguments.rb +3 -1
- data/lib/thor/parser/option.rb +49 -0
- data/lib/thor/parser/options.rb +10 -7
- data/lib/thor/version.rb +13 -2
- metadata +26 -5
- data/lib/thor/core_ext/ordered_hash.rb +0 -129
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 90391ca54447a2056aa8aa5fb31e615a34e27944
|
4
|
+
data.tar.gz: 363b47d89b5a1ba3f9e6fc962ca5f33efae7257e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 68973c005b9e442fd82724347101afe0f2f6004ba2af497f207111dfb6f1e1518994431de7acdb6e8764856b4ac3feb5c698d76e1c8455d86937efafd1c372c6
|
7
|
+
data.tar.gz: df11fa22730a3244652a1cdc88a04cc3255b43047c576e36ce3658bdfa5c37de27acd974f0dbef5aa6f264ecef4a09f449f6b3230c1c105e103fd5b244158bd1
|
data/.yardopts
CHANGED
data/atli.gemspec
CHANGED
@@ -23,7 +23,7 @@ Gem::Specification.new do |spec|
|
|
23
23
|
# ============================================================================
|
24
24
|
|
25
25
|
# My guns
|
26
|
-
spec.add_dependency "nrser", '~> 0.3.
|
26
|
+
spec.add_dependency "nrser", '~> 0.3.9'
|
27
27
|
|
28
28
|
|
29
29
|
# Development Dependencies
|
@@ -46,6 +46,8 @@ Gem::Specification.new do |spec|
|
|
46
46
|
|
47
47
|
# Doc site generation with `yard`
|
48
48
|
spec.add_development_dependency 'yard', '~> 0.9.12'
|
49
|
+
# Add support for {ActiveSupport::Concern} to Yard
|
50
|
+
spec.add_development_dependency 'yard-activesupport-concern', '~> 0.0.1'
|
49
51
|
|
50
52
|
# This being installed *seems* to help Yard do the right things with
|
51
53
|
# markdown files...
|
data/lib/thor.rb
CHANGED
@@ -1,836 +1,852 @@
|
|
1
1
|
require "set"
|
2
2
|
require 'nrser'
|
3
|
-
require 'semantic_logger'
|
4
3
|
require "thor/base"
|
5
4
|
require 'thor/example'
|
6
|
-
require 'thor/completion/bash'
|
7
5
|
|
8
6
|
|
9
7
|
class Thor
|
10
|
-
include Thor::Completion::Bash::Thor
|
11
8
|
|
12
|
-
|
13
|
-
|
14
|
-
#
|
15
|
-
# === Parameters
|
16
|
-
# name<String>
|
17
|
-
# options<Hash>
|
18
|
-
#
|
19
|
-
def package_name(name, _ = {})
|
20
|
-
@package_name = name.nil? || name == "" ? nil : name
|
21
|
-
end
|
9
|
+
# Class Methods
|
10
|
+
# ==========================================================================
|
22
11
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
12
|
+
# Allows for custom "Command" package naming.
|
13
|
+
#
|
14
|
+
# === Parameters
|
15
|
+
# name<String>
|
16
|
+
# options<Hash>
|
17
|
+
#
|
18
|
+
def self.package_name(name, _ = {})
|
19
|
+
@package_name = name.nil? || name == "" ? nil : name
|
20
|
+
end
|
21
|
+
|
22
|
+
|
23
|
+
# Sets the default command when thor is executed without an explicit
|
24
|
+
# command to be called.
|
25
|
+
#
|
26
|
+
# ==== Parameters
|
27
|
+
# meth<Symbol>:: name of the default command
|
28
|
+
#
|
29
|
+
def self.default_command(meth = nil)
|
30
|
+
if meth
|
31
|
+
@default_command = meth == :none ? "help" : meth.to_s
|
32
|
+
else
|
33
|
+
@default_command ||= from_superclass(:default_command, "help")
|
35
34
|
end
|
36
|
-
|
35
|
+
end
|
37
36
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
37
|
+
singleton_class.send :alias_method, :default_task, :default_command
|
38
|
+
|
39
|
+
|
40
|
+
# Registers another Thor subclass as a command.
|
41
|
+
#
|
42
|
+
# ==== Parameters
|
43
|
+
# klass<Class>:: Thor subclass to register
|
44
|
+
# command<String>:: Subcommand name to use
|
45
|
+
# usage<String>:: Short usage for the subcommand
|
46
|
+
# description<String>:: Description for the subcommand
|
47
|
+
def self.register(klass, subcommand_name, usage, description, options = {})
|
48
|
+
if klass <= Thor::Group
|
49
|
+
desc usage, description, options
|
50
|
+
define_method(subcommand_name) { |*args| invoke(klass, args) }
|
51
|
+
else
|
52
|
+
desc usage, description, options
|
53
|
+
subcommand subcommand_name, klass
|
53
54
|
end
|
55
|
+
end
|
54
56
|
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
57
|
+
|
58
|
+
# Defines the usage and the description of the next command.
|
59
|
+
#
|
60
|
+
# ==== Parameters
|
61
|
+
# usage<String>
|
62
|
+
# description<String>
|
63
|
+
# options<String>
|
64
|
+
#
|
65
|
+
def self.desc(usage, description, options = {})
|
66
|
+
if options[:for]
|
67
|
+
command = find_and_refresh_command(options[:for])
|
68
|
+
command.usage = usage if usage
|
69
|
+
command.description = description if description
|
70
|
+
else
|
71
|
+
@usage = usage
|
72
|
+
@desc = description
|
73
|
+
@hide = options[:hide] || false
|
72
74
|
end
|
75
|
+
end
|
73
76
|
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
77
|
+
|
78
|
+
# Defines the long description of the next command.
|
79
|
+
#
|
80
|
+
# ==== Parameters
|
81
|
+
# long description<String>
|
82
|
+
#
|
83
|
+
def self.long_desc(long_description, options = {})
|
84
|
+
if options[:for]
|
85
|
+
command = find_and_refresh_command(options[:for])
|
86
|
+
command.long_description = long_description if long_description
|
87
|
+
else
|
88
|
+
@long_desc = long_description
|
86
89
|
end
|
90
|
+
end
|
87
91
|
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
92
|
+
|
93
|
+
# Maps an input to a command. If you define:
|
94
|
+
#
|
95
|
+
# map "-T" => "list"
|
96
|
+
#
|
97
|
+
# Running:
|
98
|
+
#
|
99
|
+
# thor -T
|
100
|
+
#
|
101
|
+
# Will invoke the list command.
|
102
|
+
#
|
103
|
+
# @example Map a single alias to a command
|
104
|
+
# map 'ls' => :list
|
105
|
+
#
|
106
|
+
# @example Map multiple aliases to a command
|
107
|
+
# map ['ls', 'show'] => :list
|
108
|
+
#
|
109
|
+
# @note
|
110
|
+
#
|
111
|
+
#
|
112
|
+
#
|
113
|
+
# @param [nil | Hash<#to_s | Array<#to_s>, #to_s>?] mappings
|
114
|
+
# When `nil`, all mappings for the class are returned.
|
115
|
+
#
|
116
|
+
# When a {Hash} is provided, sets the `mappings` before returning
|
117
|
+
# all mappings.
|
118
|
+
#
|
119
|
+
# @return [HashWithIndifferentAccess<String, Symbol>]
|
120
|
+
# Mapping of command aliases to command method names.
|
121
|
+
#
|
122
|
+
def self.map mappings = nil
|
123
|
+
@map ||= from_superclass :map, HashWithIndifferentAccess.new
|
124
|
+
|
125
|
+
if mappings
|
126
|
+
mappings.each do |key, value|
|
127
|
+
if key.respond_to? :each
|
128
|
+
key.each { |subkey| @map[ subkey.to_s ] = value.to_s }
|
129
|
+
else
|
130
|
+
@map[ key.to_s ] = value.to_s
|
112
131
|
end
|
113
132
|
end
|
114
|
-
|
115
|
-
@map
|
116
133
|
end
|
117
134
|
|
118
|
-
|
119
|
-
|
120
|
-
# ==== Parameters
|
121
|
-
# Hash[Symbol => Object]:: The hash key is the name of the option and the value
|
122
|
-
# is the type of the option. Can be :string, :array, :hash, :boolean, :numeric
|
123
|
-
# or :required (string). If you give a value, the type of the value is used.
|
124
|
-
#
|
125
|
-
def method_options(options = nil)
|
126
|
-
@method_options ||= {}
|
127
|
-
build_options(options, @method_options) if options
|
128
|
-
@method_options
|
129
|
-
end
|
135
|
+
@map
|
136
|
+
end
|
130
137
|
|
131
|
-
alias_method :options, :method_options
|
132
138
|
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
#
|
146
|
-
# ==== Parameters
|
147
|
-
# name<Symbol>:: The name of the argument.
|
148
|
-
# options<Hash>:: Described below.
|
149
|
-
#
|
150
|
-
# ==== Options
|
151
|
-
# :desc - Description for the argument.
|
152
|
-
# :required - If the argument is required or not.
|
153
|
-
# :default - Default value for this argument. It cannot be required and
|
154
|
-
# have default values.
|
155
|
-
# :aliases - Aliases for this option.
|
156
|
-
# :type - The type of the argument, can be :string, :hash, :array,
|
157
|
-
# :numeric or :boolean.
|
158
|
-
# :banner - String to show on usage notes.
|
159
|
-
# :hide - If you want to hide this option from the help.
|
160
|
-
#
|
161
|
-
def method_option(name, options = {})
|
162
|
-
scope = if options[:for]
|
163
|
-
find_and_refresh_command(options[:for]).options
|
164
|
-
else
|
165
|
-
method_options
|
166
|
-
end
|
139
|
+
# Declares the options for the next command to be declared.
|
140
|
+
#
|
141
|
+
# ==== Parameters
|
142
|
+
# Hash[Symbol => Object]:: The hash key is the name of the option and the value
|
143
|
+
# is the type of the option. Can be :string, :array, :hash, :boolean, :numeric
|
144
|
+
# or :required (string). If you give a value, the type of the value is used.
|
145
|
+
#
|
146
|
+
def self.method_options(options = nil)
|
147
|
+
@method_options ||= HashWithIndifferentAccess.new
|
148
|
+
build_options(options, @method_options) if options
|
149
|
+
@method_options
|
150
|
+
end
|
167
151
|
|
168
|
-
|
152
|
+
singleton_class.send :alias_method, :options, :method_options
|
153
|
+
|
154
|
+
|
155
|
+
# Adds an option to the set of method options. If :for is given as option,
|
156
|
+
# it allows you to change the options from a previous defined command.
|
157
|
+
#
|
158
|
+
# def previous_command
|
159
|
+
# # magic
|
160
|
+
# end
|
161
|
+
#
|
162
|
+
# method_option :foo => :bar, :for => :previous_command
|
163
|
+
#
|
164
|
+
# def next_command
|
165
|
+
# # magic
|
166
|
+
# end
|
167
|
+
#
|
168
|
+
# ==== Parameters
|
169
|
+
# name<Symbol>:: The name of the argument.
|
170
|
+
# options<Hash>:: Described below.
|
171
|
+
#
|
172
|
+
# ==== Options
|
173
|
+
# :desc - Description for the argument.
|
174
|
+
# :required - If the argument is required or not.
|
175
|
+
# :default - Default value for this argument. It cannot be required and
|
176
|
+
# have default values.
|
177
|
+
# :aliases - Aliases for this option.
|
178
|
+
# :type - The type of the argument, can be :string, :hash, :array,
|
179
|
+
# :numeric or :boolean.
|
180
|
+
# :banner - String to show on usage notes.
|
181
|
+
# :hide - If you want to hide this option from the help.
|
182
|
+
#
|
183
|
+
def self.method_option name, **options
|
184
|
+
scope = if options[:for]
|
185
|
+
find_and_refresh_command(options[:for]).options
|
186
|
+
else
|
187
|
+
method_options
|
169
188
|
end
|
170
|
-
alias_method :option, :method_option
|
171
189
|
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
190
|
+
build_option(name, options, scope)
|
191
|
+
end
|
192
|
+
|
193
|
+
singleton_class.send :alias_method, :option, :method_option
|
194
|
+
|
195
|
+
|
196
|
+
# Prints help information for the given command.
|
197
|
+
#
|
198
|
+
# @param [Thor::Shell] shell
|
199
|
+
#
|
200
|
+
# @param [String] command_name
|
201
|
+
#
|
202
|
+
# @param [Boolean] subcommand
|
203
|
+
# *Alti* *addition* - passed from {#help} when that command is being
|
204
|
+
# invoked as a subcommand.
|
205
|
+
#
|
206
|
+
# The values is passed through to {.banner} and eventually
|
207
|
+
# {Command#formatted_usage} so that it can properly display the usage
|
208
|
+
# message
|
209
|
+
#
|
210
|
+
# basename subcmd cmd ARGS...
|
211
|
+
#
|
212
|
+
# versus what it did when I found it:
|
213
|
+
#
|
214
|
+
# basename cmd ARGS...
|
215
|
+
#
|
216
|
+
# which, of course, doesn't work if +cmd+ is inside +subcmd+.
|
217
|
+
#
|
218
|
+
# @return [nil]
|
219
|
+
#
|
220
|
+
def self.command_help(shell, command_name, subcommand = false)
|
221
|
+
meth = normalize_command_name(command_name)
|
222
|
+
command = all_commands[meth]
|
223
|
+
handle_no_command_error(meth) unless command
|
224
|
+
|
225
|
+
shell.say "Usage:"
|
226
|
+
shell.say " #{banner(command, nil, subcommand)}"
|
227
|
+
shell.say
|
228
|
+
|
229
|
+
class_options_help \
|
230
|
+
shell,
|
231
|
+
command.options.values.group_by { |option| option.group }
|
232
|
+
|
233
|
+
if command.long_description
|
234
|
+
shell.say "Description:"
|
235
|
+
shell.print_wrapped(command.long_description, :indent => 2)
|
236
|
+
else
|
237
|
+
shell.say command.description
|
238
|
+
end
|
239
|
+
|
240
|
+
unless command.examples.empty?
|
241
|
+
shell.say "\n"
|
242
|
+
shell.say "Examples:"
|
243
|
+
shell.say "\n"
|
215
244
|
|
216
|
-
|
217
|
-
|
218
|
-
shell.say "Examples:"
|
219
|
-
shell.say "\n"
|
245
|
+
command.examples.each_with_index do |example, index|
|
246
|
+
lines = example.lines
|
220
247
|
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
shell.say "1. #{ lines[0] }"
|
225
|
-
|
226
|
-
lines[1..-1].each do |line|
|
227
|
-
shell.say " #{ line }"
|
228
|
-
end
|
229
|
-
end
|
248
|
+
bullet = "#{ index + 1}.".ljust 4
|
249
|
+
|
250
|
+
shell.say "#{ bullet }#{ lines[0] }"
|
230
251
|
|
231
|
-
|
252
|
+
lines[1..-1].each do |line|
|
253
|
+
shell.say " #{ line }"
|
254
|
+
end
|
232
255
|
end
|
233
256
|
|
234
|
-
|
257
|
+
shell.say "\n"
|
235
258
|
end
|
236
|
-
|
259
|
+
|
260
|
+
nil
|
261
|
+
end
|
237
262
|
|
238
|
-
|
239
|
-
#
|
240
|
-
# @param [Thor::Shell] shell
|
241
|
-
# @return (see Thor::Base::ClassMethods#class_options_help)
|
242
|
-
#
|
243
|
-
def help(shell, subcommand = false)
|
244
|
-
list = printable_commands(true, subcommand)
|
245
|
-
Thor::Util.thor_classes_in(self).each do |klass|
|
246
|
-
list += klass.printable_commands(false)
|
247
|
-
end
|
248
|
-
list.sort! { |a, b| a[0] <=> b[0] }
|
263
|
+
singleton_class.send :alias_method, :task_help, :command_help
|
249
264
|
|
250
|
-
if defined?(@package_name) && @package_name
|
251
|
-
shell.say "#{@package_name} commands:"
|
252
|
-
else
|
253
|
-
shell.say "Commands:"
|
254
|
-
end
|
255
265
|
|
256
|
-
|
257
|
-
|
258
|
-
|
266
|
+
# Prints help information for this class.
|
267
|
+
#
|
268
|
+
# @param [Thor::Shell] shell
|
269
|
+
# @return (see Thor::Base::ClassMethods#class_options_help)
|
270
|
+
#
|
271
|
+
def self.help(shell, subcommand = false)
|
272
|
+
list = printable_commands(true, subcommand)
|
273
|
+
Thor::Util.thor_classes_in(self).each do |klass|
|
274
|
+
list += klass.printable_commands(false)
|
259
275
|
end
|
276
|
+
list.sort! { |a, b| a[0] <=> b[0] }
|
260
277
|
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
item = []
|
266
|
-
item << banner(command, false, subcommand)
|
267
|
-
item << ( command.description ?
|
268
|
-
"# #{command.description.gsub(/\s+/m, ' ')}" : "" )
|
269
|
-
item
|
270
|
-
end.compact
|
278
|
+
if defined?(@package_name) && @package_name
|
279
|
+
shell.say "#{@package_name} commands:"
|
280
|
+
else
|
281
|
+
shell.say "Commands:"
|
271
282
|
end
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
283
|
+
|
284
|
+
shell.print_table(list, :indent => 2, :truncate => true)
|
285
|
+
shell.say
|
286
|
+
class_options_help(shell)
|
287
|
+
end
|
288
|
+
|
289
|
+
|
290
|
+
# Returns commands ready to be printed.
|
291
|
+
#
|
292
|
+
# @param [Boolean] all
|
293
|
+
# When `true`,
|
294
|
+
#
|
295
|
+
# @return [Array<(String, String)>]
|
296
|
+
# Array of pairs with:
|
297
|
+
#
|
298
|
+
# - `[0]` - The "usage" format.
|
299
|
+
# - `[1]` - The command description.
|
300
|
+
#
|
301
|
+
def self.printable_commands all = true, subcommand = false
|
302
|
+
(all ? all_commands : commands).map do |_, command|
|
303
|
+
next if command.hidden?
|
304
|
+
item = []
|
305
|
+
item << banner(command, false, subcommand)
|
306
|
+
item << ( command.description ?
|
307
|
+
"# #{command.description.gsub(/\s+/m, ' ')}" : "" )
|
308
|
+
item
|
309
|
+
end.compact
|
310
|
+
end
|
311
|
+
singleton_class.send :alias_method, :printable_tasks, :printable_commands
|
312
|
+
|
313
|
+
|
314
|
+
# List of subcommand names, including those inherited from super
|
315
|
+
# classes.
|
316
|
+
#
|
317
|
+
# @return [Array<String>]
|
318
|
+
#
|
319
|
+
def self.subcommands
|
320
|
+
@subcommands ||= from_superclass(:subcommands, [])
|
321
|
+
end
|
322
|
+
singleton_class.send :alias_method, :subtasks, :subcommands
|
323
|
+
|
324
|
+
|
325
|
+
# Map of subcommand names to Thor classes for *this* Thor class only.
|
326
|
+
#
|
327
|
+
# @note
|
328
|
+
# `.subcommands` is not necessarily equal to `.subcommand_classes.keys`
|
329
|
+
# - it won't be when there are subcommands inherited from super classes.
|
330
|
+
#
|
331
|
+
# @note
|
332
|
+
# I'm not really sure how this relates to {Thor::Group}... and I'm not
|
333
|
+
# going to take the time to find out now.
|
334
|
+
#
|
335
|
+
# @return [Hash<String, Class<Thor::Base>]
|
336
|
+
#
|
337
|
+
def self.subcommand_classes
|
338
|
+
@subcommand_classes ||= {}
|
339
|
+
end
|
340
|
+
|
341
|
+
|
342
|
+
# Declare a subcommand by providing an UI name and subcommand class.
|
343
|
+
#
|
344
|
+
# @example
|
345
|
+
# class MySub < Thor
|
346
|
+
# desc 'blah', "Blah!"
|
347
|
+
# def blah
|
348
|
+
# # ...
|
349
|
+
# end
|
350
|
+
# end
|
351
|
+
#
|
352
|
+
# class Main < Thor
|
353
|
+
# subcommand 'my-sub', MySub, desc: "Do sub stuff"
|
354
|
+
# end
|
355
|
+
#
|
356
|
+
# @param [String | Symbol] ui_name
|
357
|
+
# The name as you would like it to appear in the user interface
|
358
|
+
# (help, etc.).
|
359
|
+
#
|
360
|
+
# {String#underscore} is called on this argument to create the "method
|
361
|
+
# name", which is used as the name of the method that will handle
|
362
|
+
# subcommand invokation, and as the "internal name" that is added to
|
363
|
+
# {#subcommands} and used as it's key in {#subcommand_classes}.
|
364
|
+
#
|
365
|
+
# The subcommand should be callable from the CLI using both names.
|
366
|
+
#
|
367
|
+
# @param [Thor::Base] subcommand_class
|
368
|
+
# The subcommand class.
|
369
|
+
#
|
370
|
+
# Note that I have not tried using {Thor::Group} as subcommand classes,
|
371
|
+
# and the documentation and online search results around it are typically
|
372
|
+
# vague and uncertain.
|
373
|
+
#
|
374
|
+
# @param [String?] desc:
|
375
|
+
# Optional description of the subcommand for help messages.
|
376
|
+
#
|
377
|
+
# If this option is provided, a {Thor.desc} call will be issued before
|
378
|
+
# the subcommand hanlder method is dynamically added.
|
379
|
+
#
|
380
|
+
# If you don't provide this option, you probably want to make your
|
381
|
+
# own call to {Thor.desc} before calling `.subcommand` like:
|
382
|
+
#
|
383
|
+
# desc 'my-sub SUBCOMMAND...', "Do subcommand stuff."
|
384
|
+
# subcommand 'my-sub', MySub
|
385
|
+
#
|
386
|
+
# The `desc:` keyword args lets you consolidate this into one call:
|
387
|
+
#
|
388
|
+
# subcommand 'my-sub', MySub, desc: "Do subcommand stuff."
|
389
|
+
#
|
390
|
+
# @return [nil]
|
391
|
+
# This seemed to just return "whatever" (`void` as we say), but I threw
|
392
|
+
# `nil` in there to be clear.
|
393
|
+
#
|
394
|
+
def self.subcommand ui_name, subcommand_class, desc: nil
|
395
|
+
ui_name = ui_name.to_s
|
396
|
+
method_name = ui_name.underscore
|
397
|
+
subcommands << method_name
|
398
|
+
subcommand_class.subcommand_help ui_name
|
399
|
+
subcommand_classes[method_name] = subcommand_class
|
400
|
+
|
401
|
+
if desc
|
402
|
+
self.desc "#{ ui_name } SUBCOMMAND...", desc
|
403
|
+
end
|
404
|
+
|
405
|
+
define_method method_name do |*args|
|
406
|
+
args, opts = Thor::Arguments.split(args)
|
407
|
+
invoke_args = [
|
408
|
+
args,
|
409
|
+
opts,
|
410
|
+
{:invoked_via_subcommand => true, :class_options => options}
|
411
|
+
]
|
412
|
+
invoke_args.unshift "help" if opts.delete("--help") || opts.delete("-h")
|
413
|
+
invoke subcommand_class, *invoke_args
|
414
|
+
end
|
415
|
+
|
416
|
+
# Sigh... this used to just be `subcommand_class.commands...`, but that
|
417
|
+
# fails when:
|
277
418
|
#
|
278
|
-
#
|
419
|
+
# 1. A Thor class is created with commands defined, then
|
420
|
+
# 2. That Thor class is subclassed, then
|
421
|
+
# 3. The *subclass* as added as a {.subcommand}.
|
279
422
|
#
|
280
|
-
|
281
|
-
|
282
|
-
end
|
283
|
-
alias_method :subtasks, :subcommands
|
284
|
-
|
285
|
-
|
286
|
-
# Map of subcommand names to Thor classes for *this* Thor class only.
|
423
|
+
# This is because then the commands that need {Command#ancestor_name}
|
424
|
+
# set are not part of {.commands}, only {.all_commands}.
|
287
425
|
#
|
288
|
-
#
|
289
|
-
#
|
290
|
-
#
|
426
|
+
# At the moment, this *seems* like an architectural problem, since
|
427
|
+
# the commands being mutated via {Thor::Command#ancestor_name=} do
|
428
|
+
# not *belong* to `self` - they are simply part of superclass, which
|
429
|
+
# could logically be re-used across through additional sub-classes
|
430
|
+
# by multiple {Thor} *unless* this is not party of the paradigm...
|
431
|
+
# but the paradigm fundametals and constraints are never really laid
|
432
|
+
# out anywhere.
|
291
433
|
#
|
292
|
-
#
|
293
|
-
#
|
294
|
-
# going to take the time to find out now.
|
434
|
+
# Anyways, switching to {subcommand_classes.all_commands...} hacks
|
435
|
+
# the problem away for the moment.
|
295
436
|
#
|
296
|
-
|
297
|
-
|
298
|
-
def subcommand_classes
|
299
|
-
@subcommand_classes ||= {}
|
437
|
+
subcommand_class.all_commands.each do |_meth, command|
|
438
|
+
command.ancestor_name ||= ui_name
|
300
439
|
end
|
301
|
-
|
302
|
-
|
303
|
-
def subcommand(subcommand, subcommand_class)
|
304
|
-
subcommands << subcommand.to_s
|
305
|
-
subcommand_class.subcommand_help subcommand
|
306
|
-
subcommand_classes[subcommand.to_s] = subcommand_class
|
307
|
-
|
308
|
-
define_method(subcommand) do |*args|
|
309
|
-
args, opts = Thor::Arguments.split(args)
|
310
|
-
invoke_args = [
|
311
|
-
args,
|
312
|
-
opts,
|
313
|
-
{:invoked_via_subcommand => true, :class_options => options}
|
314
|
-
]
|
315
|
-
invoke_args.unshift "help" if opts.delete("--help") || opts.delete("-h")
|
316
|
-
invoke subcommand_class, *invoke_args
|
317
|
-
end
|
318
|
-
subcommand_class.commands.each do |_meth, command|
|
319
|
-
command.ancestor_name = subcommand
|
320
|
-
end
|
321
|
-
end
|
322
|
-
alias_method :subtask, :subcommand
|
323
440
|
|
324
|
-
|
325
|
-
|
326
|
-
# === Parameters
|
327
|
-
# options<Hash>: A hash containing :only and/or :except keys
|
328
|
-
def check_unknown_options!(options = {})
|
329
|
-
@check_unknown_options ||= {}
|
330
|
-
options.each do |key, value|
|
331
|
-
if value
|
332
|
-
@check_unknown_options[key] = Array(value)
|
333
|
-
else
|
334
|
-
@check_unknown_options.delete(key)
|
335
|
-
end
|
336
|
-
end
|
337
|
-
@check_unknown_options
|
338
|
-
end
|
339
|
-
|
340
|
-
# Overwrite check_unknown_options? to take subcommands and options into
|
341
|
-
# account.
|
342
|
-
def check_unknown_options?(config) #:nodoc:
|
343
|
-
options = check_unknown_options
|
344
|
-
return false unless options
|
441
|
+
nil
|
442
|
+
end
|
345
443
|
|
346
|
-
|
347
|
-
return true unless command
|
444
|
+
singleton_class.send :alias_method, :subtask, :subcommand
|
348
445
|
|
349
|
-
name = command.name
|
350
446
|
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
447
|
+
# Extend check unknown options to accept a hash of conditions.
|
448
|
+
#
|
449
|
+
# === Parameters
|
450
|
+
# options<Hash>: A hash containing :only and/or :except keys
|
451
|
+
def self.check_unknown_options!(options = {})
|
452
|
+
@check_unknown_options ||= {}
|
453
|
+
options.each do |key, value|
|
454
|
+
if value
|
455
|
+
@check_unknown_options[key] = Array(value)
|
357
456
|
else
|
358
|
-
|
457
|
+
@check_unknown_options.delete(key)
|
359
458
|
end
|
360
459
|
end
|
460
|
+
@check_unknown_options
|
461
|
+
end
|
361
462
|
|
362
|
-
# Stop parsing of options as soon as an unknown option or a regular
|
363
|
-
# argument is encountered. All remaining arguments are passed to the command.
|
364
|
-
# This is useful if you have a command that can receive arbitrary additional
|
365
|
-
# options, and where those additional options should not be handled by
|
366
|
-
# Thor.
|
367
|
-
#
|
368
|
-
# ==== Example
|
369
|
-
#
|
370
|
-
# To better understand how this is useful, let's consider a command that calls
|
371
|
-
# an external command. A user may want to pass arbitrary options and
|
372
|
-
# arguments to that command. The command itself also accepts some options,
|
373
|
-
# which should be handled by Thor.
|
374
|
-
#
|
375
|
-
# class_option "verbose", :type => :boolean
|
376
|
-
# stop_on_unknown_option! :exec
|
377
|
-
# check_unknown_options! :except => :exec
|
378
|
-
#
|
379
|
-
# desc "exec", "Run a shell command"
|
380
|
-
# def exec(*args)
|
381
|
-
# puts "diagnostic output" if options[:verbose]
|
382
|
-
# Kernel.exec(*args)
|
383
|
-
# end
|
384
|
-
#
|
385
|
-
# Here +exec+ can be called with +--verbose+ to get diagnostic output,
|
386
|
-
# e.g.:
|
387
|
-
#
|
388
|
-
# $ thor exec --verbose echo foo
|
389
|
-
# diagnostic output
|
390
|
-
# foo
|
391
|
-
#
|
392
|
-
# But if +--verbose+ is given after +echo+, it is passed to +echo+ instead:
|
393
|
-
#
|
394
|
-
# $ thor exec echo --verbose foo
|
395
|
-
# --verbose foo
|
396
|
-
#
|
397
|
-
# ==== Parameters
|
398
|
-
# Symbol ...:: A list of commands that should be affected.
|
399
|
-
def stop_on_unknown_option!(*command_names)
|
400
|
-
stop_on_unknown_option.merge(command_names)
|
401
|
-
end
|
402
463
|
|
403
|
-
|
404
|
-
|
464
|
+
# Overwrite check_unknown_options? to take subcommands and options into
|
465
|
+
# account.
|
466
|
+
def self.check_unknown_options?(config) #:nodoc:
|
467
|
+
options = check_unknown_options
|
468
|
+
return false unless options
|
469
|
+
|
470
|
+
command = config[:current_command]
|
471
|
+
return true unless command
|
472
|
+
|
473
|
+
name = command.name
|
474
|
+
|
475
|
+
if subcommands.include?(name)
|
476
|
+
false
|
477
|
+
elsif options[:except]
|
478
|
+
!options[:except].include?(name.to_sym)
|
479
|
+
elsif options[:only]
|
480
|
+
options[:only].include?(name.to_sym)
|
481
|
+
else
|
482
|
+
true
|
405
483
|
end
|
484
|
+
end
|
406
485
|
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
486
|
+
|
487
|
+
# Stop parsing of options as soon as an unknown option or a regular
|
488
|
+
# argument is encountered. All remaining arguments are passed to the command.
|
489
|
+
# This is useful if you have a command that can receive arbitrary additional
|
490
|
+
# options, and where those additional options should not be handled by
|
491
|
+
# Thor.
|
492
|
+
#
|
493
|
+
# ==== Example
|
494
|
+
#
|
495
|
+
# To better understand how this is useful, let's consider a command that calls
|
496
|
+
# an external command. A user may want to pass arbitrary options and
|
497
|
+
# arguments to that command. The command itself also accepts some options,
|
498
|
+
# which should be handled by Thor.
|
499
|
+
#
|
500
|
+
# class_option "verbose", :type => :boolean
|
501
|
+
# stop_on_unknown_option! :exec
|
502
|
+
# check_unknown_options! :except => :exec
|
503
|
+
#
|
504
|
+
# desc "exec", "Run a shell command"
|
505
|
+
# def exec(*args)
|
506
|
+
# puts "diagnostic output" if options[:verbose]
|
507
|
+
# Kernel.exec(*args)
|
508
|
+
# end
|
509
|
+
#
|
510
|
+
# Here +exec+ can be called with +--verbose+ to get diagnostic output,
|
511
|
+
# e.g.:
|
512
|
+
#
|
513
|
+
# $ thor exec --verbose echo foo
|
514
|
+
# diagnostic output
|
515
|
+
# foo
|
516
|
+
#
|
517
|
+
# But if +--verbose+ is given after +echo+, it is passed to +echo+ instead:
|
518
|
+
#
|
519
|
+
# $ thor exec echo --verbose foo
|
520
|
+
# --verbose foo
|
521
|
+
#
|
522
|
+
# ==== Parameters
|
523
|
+
# Symbol ...:: A list of commands that should be affected.
|
524
|
+
def self.stop_on_unknown_option!(*command_names)
|
525
|
+
stop_on_unknown_option.merge(command_names)
|
526
|
+
end
|
527
|
+
|
528
|
+
|
529
|
+
def self.stop_on_unknown_option?(command) #:nodoc:
|
530
|
+
command && stop_on_unknown_option.include?(command.name.to_sym)
|
531
|
+
end
|
532
|
+
|
533
|
+
|
534
|
+
# Disable the check for required options for the given commands.
|
535
|
+
# This is useful if you have a command that does not need the required options
|
536
|
+
# to work, like help.
|
537
|
+
#
|
538
|
+
# ==== Parameters
|
539
|
+
# Symbol ...:: A list of commands that should be affected.
|
540
|
+
def self.disable_required_check!(*command_names)
|
541
|
+
disable_required_check.merge(command_names)
|
542
|
+
end
|
543
|
+
|
544
|
+
|
545
|
+
def self.disable_required_check?(command) #:nodoc:
|
546
|
+
command && disable_required_check.include?(command.name.to_sym)
|
547
|
+
end
|
548
|
+
|
549
|
+
|
550
|
+
protected # Class Methods
|
551
|
+
# ============================================================================
|
552
|
+
|
553
|
+
def self.stop_on_unknown_option #:nodoc:
|
554
|
+
@stop_on_unknown_option ||= Set.new
|
415
555
|
end
|
416
556
|
|
417
|
-
|
418
|
-
|
557
|
+
|
558
|
+
# help command has the required check disabled by default.
|
559
|
+
def self.disable_required_check #:nodoc:
|
560
|
+
@disable_required_check ||= Set.new([:help])
|
419
561
|
end
|
420
|
-
|
421
|
-
|
422
|
-
#
|
423
|
-
|
424
|
-
# @return [Hash<Symbol, Thor::SharedOption]
|
425
|
-
# Get all shared options
|
562
|
+
|
563
|
+
|
564
|
+
# The method responsible for dispatching given the args.
|
426
565
|
#
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
|
566
|
+
# @param [nil | String | Symbol] meth
|
567
|
+
# The method name of the command to run, which I *think* will be `nil`
|
568
|
+
# when running the "default command"? haven't messed with that yet...
|
569
|
+
#
|
570
|
+
# @param [Array<String>] given_args
|
571
|
+
#
|
572
|
+
def self.dispatch meth, given_args, given_opts, config # rubocop:disable MethodLength
|
573
|
+
logger.trace "START #{ self.safe_name }.#{ __method__ }",
|
574
|
+
meth: meth,
|
575
|
+
given_args: given_args,
|
576
|
+
given_opts: given_opts,
|
577
|
+
config: config
|
578
|
+
|
579
|
+
meth ||= retrieve_command_name( given_args ).tap { |new_meth|
|
580
|
+
logger.trace "meth set via .retrieve_command_name",
|
581
|
+
meth: new_meth,
|
582
|
+
map: map
|
583
|
+
}
|
584
|
+
|
585
|
+
normalized_name = normalize_command_name meth
|
586
|
+
command = all_commands[normalized_name]
|
587
|
+
|
588
|
+
logger.trace "Fetched command",
|
589
|
+
command: command,
|
590
|
+
all_commands: all_commands.keys,
|
591
|
+
normalized_name: normalized_name
|
592
|
+
|
593
|
+
if !command && config[:invoked_via_subcommand]
|
594
|
+
# We're a subcommand and our first argument didn't match any of our
|
595
|
+
# commands. So we put it back and call our default command.
|
596
|
+
given_args.unshift(meth)
|
597
|
+
command = all_commands[normalize_command_name(default_command)]
|
598
|
+
end
|
599
|
+
|
600
|
+
if command
|
601
|
+
args, opts = Thor::Options.split(given_args)
|
602
|
+
if stop_on_unknown_option?(command) && !args.empty?
|
603
|
+
# given_args starts with a non-option, so we treat everything as
|
604
|
+
# ordinary arguments
|
605
|
+
args.concat opts
|
606
|
+
opts.clear
|
435
607
|
end
|
608
|
+
else
|
609
|
+
args = given_args
|
610
|
+
opts = nil
|
611
|
+
command = dynamic_command_class.new(meth)
|
436
612
|
end
|
437
|
-
|
438
|
-
|
439
|
-
|
440
|
-
|
441
|
-
|
442
|
-
|
443
|
-
|
444
|
-
|
613
|
+
|
614
|
+
opts = given_opts || opts || []
|
615
|
+
config[:current_command] = command
|
616
|
+
config[:command_options] = command.options
|
617
|
+
|
618
|
+
instance = new(args, opts, config)
|
619
|
+
yield instance if block_given?
|
620
|
+
args = instance.args
|
621
|
+
trailing = args[ arguments( command: command ).size..-1 ]
|
622
|
+
instance.invoke_command(command, trailing || [])
|
445
623
|
end
|
446
|
-
alias_method :shared_options, :shared_method_options
|
447
624
|
|
448
625
|
|
449
|
-
#
|
450
|
-
#
|
451
|
-
#
|
452
|
-
#
|
453
|
-
#
|
454
|
-
# @param [
|
455
|
-
#
|
626
|
+
# The banner for this class. You can customize it if you are invoking the
|
627
|
+
# thor class by another ways which is not the Thor::Runner. It receives
|
628
|
+
# the command that is going to be invoked and a boolean which indicates if
|
629
|
+
# the namespace should be displayed as arguments.
|
630
|
+
#
|
631
|
+
# @param [Thor::Command] command
|
632
|
+
# The command to render the banner for.
|
633
|
+
#
|
634
|
+
# @param [nil | ?] namespace
|
635
|
+
# *Atli*: this argument is _not_ _used_ _at_ _all_. I don't know what it
|
636
|
+
# could or should be, but it doesn't seem like it matters at all :/
|
637
|
+
#
|
638
|
+
# @param [Boolean] subcommand
|
639
|
+
# Should be +true+ if the command was invoked as a sub-command; passed
|
640
|
+
# on to {Command#formatted_usage} so it can render correctly.
|
456
641
|
#
|
457
|
-
# @return [
|
458
|
-
#
|
642
|
+
# @return [String]
|
643
|
+
# The banner for the command.
|
459
644
|
#
|
460
|
-
def
|
461
|
-
|
645
|
+
def self.banner(command, namespace = nil, subcommand = false)
|
646
|
+
"#{basename} #{command.formatted_usage(self, $thor_runner, subcommand)}"
|
647
|
+
end
|
648
|
+
|
649
|
+
|
650
|
+
def self.baseclass #:nodoc:
|
651
|
+
Thor
|
652
|
+
end
|
653
|
+
|
654
|
+
|
655
|
+
def self.dynamic_command_class #:nodoc:
|
656
|
+
Thor::DynamicCommand
|
657
|
+
end
|
658
|
+
|
659
|
+
|
660
|
+
def self.create_command(meth) #:nodoc:
|
661
|
+
@usage ||= nil
|
662
|
+
@desc ||= nil
|
663
|
+
@long_desc ||= nil
|
664
|
+
@hide ||= nil
|
462
665
|
|
463
|
-
|
464
|
-
|
465
|
-
|
466
|
-
|
467
|
-
|
468
|
-
|
666
|
+
examples = @examples || []
|
667
|
+
@examples = []
|
668
|
+
|
669
|
+
if @usage && @desc
|
670
|
+
base_class = @hide ? Thor::HiddenCommand : Thor::Command
|
671
|
+
commands[meth] = base_class.new \
|
672
|
+
name: meth,
|
673
|
+
description: @desc,
|
674
|
+
long_description: @long_desc,
|
675
|
+
usage: @usage,
|
676
|
+
examples: examples,
|
677
|
+
options: method_options,
|
678
|
+
arguments: method_arguments
|
469
679
|
|
470
|
-
|
471
|
-
|
472
|
-
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
-
|
477
|
-
|
478
|
-
|
479
|
-
|
480
|
-
|
481
|
-
|
680
|
+
@usage,
|
681
|
+
@desc,
|
682
|
+
@long_desc,
|
683
|
+
@method_options,
|
684
|
+
@hide,
|
685
|
+
@method_arguments = nil
|
686
|
+
|
687
|
+
true
|
688
|
+
elsif all_commands[meth] || meth == "method_missing"
|
689
|
+
true
|
690
|
+
else
|
691
|
+
puts "[WARNING] Attempted to create command #{meth.inspect} without usage or description. " \
|
692
|
+
"Call desc if you want this method to be available as command or declare it inside a " \
|
693
|
+
"no_commands{} block. Invoked from #{caller[1].inspect}."
|
694
|
+
false
|
482
695
|
end
|
483
696
|
end
|
484
|
-
|
485
|
-
|
486
|
-
|
487
|
-
|
488
|
-
|
489
|
-
|
490
|
-
|
491
|
-
|
492
|
-
|
493
|
-
|
494
|
-
#
|
495
|
-
#
|
496
|
-
# # Declare a shared option:
|
497
|
-
# shared_option :force,
|
498
|
-
# groups: :write,
|
499
|
-
# desc: "Force the operation",
|
500
|
-
# type: :boolean
|
501
|
-
#
|
502
|
-
# # ...
|
503
|
-
#
|
504
|
-
# desc "write [OPTIONS] path",
|
505
|
-
# "Write to a path"
|
506
|
-
#
|
507
|
-
# # Add the shared options to the method:
|
508
|
-
# include_options groups: :write
|
509
|
-
#
|
510
|
-
# def write path
|
511
|
-
#
|
512
|
-
# # Get a slice of `#options` with any of the `:write` group options
|
513
|
-
# # that were provided and use it in a method call:
|
514
|
-
# MyModule.write path, **option_kwds( groups: :write )
|
515
|
-
#
|
516
|
-
# end
|
517
|
-
# end
|
697
|
+
|
698
|
+
singleton_class.send :alias_method, :create_task, :create_command
|
699
|
+
|
700
|
+
|
701
|
+
def self.initialize_added #:nodoc:
|
702
|
+
class_options.merge!(method_options)
|
703
|
+
@method_options = nil
|
704
|
+
end
|
705
|
+
|
706
|
+
|
707
|
+
# Retrieve the command name from given args.
|
518
708
|
#
|
519
|
-
# @
|
520
|
-
#
|
709
|
+
# @note
|
710
|
+
# Ugh... this *mutates* `args`
|
521
711
|
#
|
522
|
-
# @param [
|
523
|
-
# Keyword args used to initialize the {Thor::SharedOption}.
|
524
|
-
#
|
525
|
-
# All +**options+ are optional.
|
712
|
+
# @param [Array<String>] args
|
526
713
|
#
|
527
|
-
# @
|
528
|
-
# One or more _shared_ _option_ _group_ that the new option will belong
|
529
|
-
# to.
|
530
|
-
#
|
531
|
-
# Examples:
|
532
|
-
# groups: :read
|
533
|
-
# groups: [:read, :write]
|
714
|
+
# @return [nil]`
|
534
715
|
#
|
535
|
-
# *NOTE* The keyword is +groups+ with an +s+! {Thor::Option} already has
|
536
|
-
# a +group+ string attribute that, as far as I can tell, is only
|
537
|
-
#
|
538
|
-
#
|
539
|
-
#
|
540
|
-
# @option options [String] :desc
|
541
|
-
# Description for the option for help and feedback.
|
542
716
|
#
|
543
|
-
|
544
|
-
|
545
|
-
|
546
|
-
|
547
|
-
|
548
|
-
|
549
|
-
|
717
|
+
def self.retrieve_command_name args
|
718
|
+
meth = args.first.to_s unless args.empty?
|
719
|
+
args.shift if meth && (map[meth] || meth !~ /^\-/)
|
720
|
+
end
|
721
|
+
|
722
|
+
singleton_class.send :alias_method, :retrieve_task_name,
|
723
|
+
:retrieve_command_name
|
724
|
+
|
725
|
+
|
726
|
+
# Receives a (possibly nil) command name and returns a name that is in
|
727
|
+
# the commands hash. In addition to normalizing aliases, this logic
|
728
|
+
# will determine if a shortened command is an unambiguous substring of
|
729
|
+
# a command or alias.
|
730
|
+
#
|
731
|
+
# {.normalize_command_name} also converts names like `animal-prison`
|
732
|
+
# into `animal_prison`.
|
550
733
|
#
|
551
|
-
# @
|
552
|
-
# Aliases for this option.
|
734
|
+
# @param [nil | ] meth
|
553
735
|
#
|
554
|
-
# Examples:
|
555
|
-
# aliases: '-s'
|
556
|
-
# aliases: '--other-name'
|
557
|
-
# aliases: ['-s', '--other-name']
|
558
|
-
#
|
559
|
-
# @option options [:string | :hash | :array | :numeric | :boolean] :type
|
560
|
-
# Type of acceptable values, see
|
561
|
-
# {types for method options}[https://github.com/erikhuda/thor/wiki/Method-Options#types-for-method_options]
|
562
|
-
# in the Thor wiki.
|
563
|
-
#
|
564
|
-
# @option options [String] :banner
|
565
|
-
# String to show on usage notes.
|
566
736
|
#
|
567
|
-
|
568
|
-
|
569
|
-
#
|
570
|
-
# @return (see .build_shared_option)
|
571
|
-
#
|
572
|
-
def shared_method_option name, **options
|
573
|
-
# Don't think the `:for` option makes sense... that would just be a
|
574
|
-
# regular method option, right? I guess `:for` could be an array and
|
575
|
-
# apply the option to each command, but it seems like that would just
|
576
|
-
# be better as an extension to the {.method_option} behavior.
|
577
|
-
#
|
578
|
-
# So, we raise if we see it
|
579
|
-
if options.key? :for
|
580
|
-
raise ArgumentError,
|
581
|
-
".shared_method_option does not accept the `:for` option"
|
582
|
-
end
|
583
|
-
|
584
|
-
build_shared_option(name, options)
|
585
|
-
end # #shared_method_option
|
586
|
-
alias_method :shared_option, :shared_method_option
|
587
|
-
|
588
|
-
|
589
|
-
# Add the {Thor::SharedOption} instances with +names+ and in +groups+ to
|
590
|
-
# the next defined command method.
|
591
|
-
#
|
592
|
-
# @param (see .find_shared_method_options)
|
593
|
-
# @return (see .find_shared_method_options)
|
594
|
-
#
|
595
|
-
def include_method_options *names, groups: nil
|
596
|
-
find_shared_method_options( *names, groups: groups ).
|
597
|
-
each do |name, result|
|
598
|
-
method_options[name] = Thor::IncludedOption.new **result
|
599
|
-
end
|
600
|
-
end
|
601
|
-
|
602
|
-
alias_method :include_options, :include_method_options
|
603
|
-
|
604
|
-
# END Atli Public Class Methods ******************************************
|
605
|
-
|
606
|
-
|
607
|
-
protected # Class Methods
|
608
|
-
# ============================================================================
|
737
|
+
def self.normalize_command_name meth #:nodoc:
|
738
|
+
return default_command.to_s.tr("-", "_") unless meth
|
609
739
|
|
610
|
-
|
611
|
-
|
740
|
+
possibilities = find_command_possibilities(meth)
|
741
|
+
|
742
|
+
if possibilities.size > 1
|
743
|
+
raise AmbiguousTaskError,
|
744
|
+
"Ambiguous command #{meth} matches [#{possibilities.join(', ')}]"
|
612
745
|
end
|
613
746
|
|
614
|
-
|
615
|
-
|
616
|
-
|
747
|
+
if possibilities.empty?
|
748
|
+
meth ||= default_command
|
749
|
+
elsif map[meth]
|
750
|
+
meth = map[meth]
|
751
|
+
else
|
752
|
+
meth = possibilities.first
|
617
753
|
end
|
618
754
|
|
619
|
-
|
620
|
-
|
621
|
-
meth ||= retrieve_command_name(given_args)
|
622
|
-
command = all_commands[normalize_command_name(meth)]
|
623
|
-
|
624
|
-
if !command && config[:invoked_via_subcommand]
|
625
|
-
# We're a subcommand and our first argument didn't match any of our
|
626
|
-
# commands. So we put it back and call our default command.
|
627
|
-
given_args.unshift(meth)
|
628
|
-
command = all_commands[normalize_command_name(default_command)]
|
629
|
-
end
|
755
|
+
meth.to_s.tr("-", "_") # treat foo-bar as foo_bar
|
756
|
+
end
|
630
757
|
|
631
|
-
|
632
|
-
|
633
|
-
if stop_on_unknown_option?(command) && !args.empty?
|
634
|
-
# given_args starts with a non-option, so we treat everything as
|
635
|
-
# ordinary arguments
|
636
|
-
args.concat opts
|
637
|
-
opts.clear
|
638
|
-
end
|
639
|
-
else
|
640
|
-
args = given_args
|
641
|
-
opts = nil
|
642
|
-
command = dynamic_command_class.new(meth)
|
643
|
-
end
|
758
|
+
singleton_class.send :alias_method, :normalize_task_name,
|
759
|
+
:normalize_command_name
|
644
760
|
|
645
|
-
opts = given_opts || opts || []
|
646
|
-
config[:current_command] = command
|
647
|
-
config[:command_options] = command.options
|
648
761
|
|
649
|
-
|
650
|
-
|
651
|
-
|
652
|
-
|
653
|
-
|
654
|
-
|
655
|
-
|
656
|
-
|
657
|
-
|
658
|
-
# thor class by another ways which is not the Thor::Runner. It receives
|
659
|
-
# the command that is going to be invoked and a boolean which indicates if
|
660
|
-
# the namespace should be displayed as arguments.
|
661
|
-
#
|
662
|
-
# @param [Thor::Command] command
|
663
|
-
# The command to render the banner for.
|
664
|
-
#
|
665
|
-
# @param [nil | ?] namespace
|
666
|
-
# *Atli*: this argument is _not_ _used_ _at_ _all_. I don't know what it
|
667
|
-
# could or should be, but it doesn't seem like it matters at all :/
|
668
|
-
#
|
669
|
-
# @param [Boolean] subcommand
|
670
|
-
# Should be +true+ if the command was invoked as a sub-command; passed
|
671
|
-
# on to {Command#formatted_usage} so it can render correctly.
|
672
|
-
#
|
673
|
-
# @return [String]
|
674
|
-
# The banner for the command.
|
675
|
-
#
|
676
|
-
def banner(command, namespace = nil, subcommand = false)
|
677
|
-
"#{basename} #{command.formatted_usage(self, $thor_runner, subcommand)}"
|
678
|
-
end
|
679
|
-
|
680
|
-
|
681
|
-
def baseclass #:nodoc:
|
682
|
-
Thor
|
683
|
-
end
|
762
|
+
# This is the logic that takes the command name passed in by the user
|
763
|
+
# and determines whether it is an unambiguous prefix of a command or
|
764
|
+
# alias name.
|
765
|
+
#
|
766
|
+
# @param [#to_s] input
|
767
|
+
# Input to match to command names.
|
768
|
+
#
|
769
|
+
def self.find_command_possibilities input
|
770
|
+
input = input.to_s
|
684
771
|
|
685
|
-
|
686
|
-
|
687
|
-
|
772
|
+
possibilities = all_commands.merge(map).keys.select { |name|
|
773
|
+
name.start_with? input
|
774
|
+
}.sort
|
688
775
|
|
689
|
-
|
690
|
-
@usage ||= nil
|
691
|
-
@desc ||= nil
|
692
|
-
@long_desc ||= nil
|
693
|
-
@hide ||= nil
|
694
|
-
|
695
|
-
examples = @examples || []
|
696
|
-
@examples = []
|
697
|
-
|
698
|
-
if @usage && @desc
|
699
|
-
base_class = @hide ? Thor::HiddenCommand : Thor::Command
|
700
|
-
commands[meth] = base_class.new \
|
701
|
-
name: meth,
|
702
|
-
description: @desc,
|
703
|
-
long_description: @long_desc,
|
704
|
-
usage: @usage,
|
705
|
-
examples: examples,
|
706
|
-
options: method_options
|
707
|
-
|
708
|
-
@usage, @desc, @long_desc, @method_options, @hide = nil
|
709
|
-
true
|
710
|
-
elsif all_commands[meth] || meth == "method_missing"
|
711
|
-
true
|
712
|
-
else
|
713
|
-
puts "[WARNING] Attempted to create command #{meth.inspect} without usage or description. " \
|
714
|
-
"Call desc if you want this method to be available as command or declare it inside a " \
|
715
|
-
"no_commands{} block. Invoked from #{caller[1].inspect}."
|
716
|
-
false
|
717
|
-
end
|
718
|
-
end
|
719
|
-
alias_method :create_task, :create_command
|
776
|
+
unique_possibilities = possibilities.map { |k| map[k] || k }.uniq
|
720
777
|
|
721
|
-
|
722
|
-
|
723
|
-
|
778
|
+
results = if possibilities.include? input
|
779
|
+
[ input ]
|
780
|
+
elsif unique_possibilities.size == 1
|
781
|
+
unique_possibilities
|
782
|
+
else
|
783
|
+
possibilities
|
724
784
|
end
|
725
785
|
|
726
|
-
#
|
727
|
-
|
728
|
-
|
729
|
-
|
730
|
-
|
731
|
-
|
732
|
-
|
733
|
-
# receives a (possibly nil) command name and returns a name that is in
|
734
|
-
# the commands hash. In addition to normalizing aliases, this logic
|
735
|
-
# will determine if a shortened command is an unambiguous substring of
|
736
|
-
# a command or alias.
|
737
|
-
#
|
738
|
-
# +normalize_command_name+ also converts names like +animal-prison+
|
739
|
-
# into +animal_prison+.
|
740
|
-
def normalize_command_name(meth) #:nodoc:
|
741
|
-
return default_command.to_s.tr("-", "_") unless meth
|
742
|
-
|
743
|
-
possibilities = find_command_possibilities(meth)
|
744
|
-
|
745
|
-
if possibilities.size > 1
|
746
|
-
raise AmbiguousTaskError,
|
747
|
-
"Ambiguous command #{meth} matches [#{possibilities.join(', ')}]"
|
748
|
-
end
|
786
|
+
logger.trace "Found #{ results.length } command possibilities",
|
787
|
+
results: results,
|
788
|
+
possibilities: possibilities,
|
789
|
+
unique_possibilities: unique_possibilities
|
790
|
+
|
791
|
+
results
|
792
|
+
end
|
749
793
|
|
750
|
-
|
751
|
-
|
752
|
-
elsif map[meth]
|
753
|
-
meth = map[meth]
|
754
|
-
else
|
755
|
-
meth = possibilities.first
|
756
|
-
end
|
794
|
+
singleton_class.send :alias_method, :find_task_possibilities,
|
795
|
+
:find_command_possibilities
|
757
796
|
|
758
|
-
meth.to_s.tr("-", "_") # treat foo-bar as foo_bar
|
759
|
-
end
|
760
|
-
alias_method :normalize_task_name, :normalize_command_name
|
761
|
-
|
762
|
-
# this is the logic that takes the command name passed in by the user
|
763
|
-
# and determines whether it is an unambiguous substrings of a command or
|
764
|
-
# alias name.
|
765
|
-
def find_command_possibilities(meth)
|
766
|
-
len = meth.to_s.length
|
767
|
-
possibilities = all_commands.merge(map).keys.select { |n|
|
768
|
-
meth == n[0, len]
|
769
|
-
}.sort
|
770
|
-
unique_possibilities = possibilities.map { |k| map[k] || k }.uniq
|
771
|
-
|
772
|
-
if possibilities.include?(meth)
|
773
|
-
[meth]
|
774
|
-
elsif unique_possibilities.size == 1
|
775
|
-
unique_possibilities
|
776
|
-
else
|
777
|
-
possibilities
|
778
|
-
end
|
779
|
-
end
|
780
|
-
alias_method :find_task_possibilities, :find_command_possibilities
|
781
797
|
|
782
|
-
|
783
|
-
|
784
|
-
|
785
|
-
|
786
|
-
|
787
|
-
|
798
|
+
def self.subcommand_help(cmd)
|
799
|
+
# logger.trace __method__.to_s,
|
800
|
+
# cmd: cmd,
|
801
|
+
# caller: caller
|
802
|
+
|
803
|
+
desc "help [COMMAND]", "Describe subcommands or one specific subcommand"
|
804
|
+
|
805
|
+
# Atli - This used to be {#class_eval} (maybe to support really old
|
806
|
+
# Rubies? Who knows...) but that made it really hard to find in
|
807
|
+
# stack traces, so I switched it to {#define_method}.
|
808
|
+
#
|
809
|
+
define_method :help do |*args|
|
788
810
|
|
789
|
-
#
|
790
|
-
|
791
|
-
|
792
|
-
|
793
|
-
|
794
|
-
|
795
|
-
|
796
|
-
|
797
|
-
|
798
|
-
|
799
|
-
when false
|
800
|
-
# Weird, `false` was explicitly passed... whatever, set it to `true`
|
801
|
-
args[-1] = true
|
802
|
-
else
|
803
|
-
# "Normal" case, append it
|
804
|
-
args << true
|
805
|
-
end
|
806
|
-
|
807
|
-
super( *args )
|
811
|
+
# Add the `is_subcommand = true` trailing arg
|
812
|
+
case args[-1]
|
813
|
+
when true
|
814
|
+
# pass
|
815
|
+
when false
|
816
|
+
# Weird, `false` was explicitly passed... whatever, set it to `true`
|
817
|
+
args[-1] = true
|
818
|
+
else
|
819
|
+
# "Normal" case, append it
|
820
|
+
args << true
|
808
821
|
end
|
809
822
|
|
823
|
+
super( *args )
|
810
824
|
end
|
811
|
-
alias_method :subtask_help, :subcommand_help
|
812
|
-
|
813
|
-
# Atli Protected Class Methods
|
814
|
-
# ======================================================================
|
815
825
|
|
816
|
-
|
817
|
-
|
818
|
-
|
819
|
-
|
820
|
-
|
821
|
-
|
822
|
-
|
823
|
-
# with the additional `:groups` shared option keyword.
|
824
|
-
def build_shared_option(name, options)
|
825
|
-
shared_method_options[name] = Thor::SharedOption.new(
|
826
|
-
name,
|
827
|
-
options.merge(:check_default_type => check_default_type?)
|
828
|
-
)
|
829
|
-
end # #build_shared_option
|
826
|
+
end
|
827
|
+
|
828
|
+
singleton_class.send :alias_method, :subtask_help, :subcommand_help
|
829
|
+
|
830
|
+
|
831
|
+
# Atli Protected Class Methods
|
832
|
+
# ======================================================================
|
830
833
|
|
831
|
-
#
|
834
|
+
# Build a Thor::SharedOption and add it to Thor.shared_method_options.
|
835
|
+
#
|
836
|
+
# The Thor::SharedOption is returned.
|
837
|
+
#
|
838
|
+
# ==== Parameters
|
839
|
+
# name<Symbol>:: The name of the argument.
|
840
|
+
# options<Hash>:: Described in both class_option and method_option,
|
841
|
+
# with the additional `:groups` shared option keyword.
|
842
|
+
def self.build_shared_option(name, options)
|
843
|
+
shared_method_options[name] = Thor::SharedOption.new(
|
844
|
+
name,
|
845
|
+
options.merge(:check_default_type => check_default_type?)
|
846
|
+
)
|
847
|
+
end # .build_shared_option
|
832
848
|
|
833
|
-
|
849
|
+
public # END protected Class Methods ***************************************
|
834
850
|
|
835
851
|
|
836
852
|
protected # Instance Methods
|