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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: d1d536b28ea563e34f3d78cede54f2a9d8c21da2
4
- data.tar.gz: 67573792a5172033627abd159a41bd9f0f1bb983
3
+ metadata.gz: 90391ca54447a2056aa8aa5fb31e615a34e27944
4
+ data.tar.gz: 363b47d89b5a1ba3f9e6fc962ca5f33efae7257e
5
5
  SHA512:
6
- metadata.gz: 30da74bbb70ca3aa22595a13f6a984cddeec184e189fdce51c19ddc7c2eca8d5be9e400ab5bfb945e71b6d530ccd852f6932388aacb069178eafeaa1c9346786
7
- data.tar.gz: 4923f187d07c5a41bf4d47bdff6a90e47ddaf79c07cba061381c4eda383d8fd9e445494b8b22f306625c1c99895df1d5b0e4afac9f296eebb0a55e4ca06894e4
6
+ metadata.gz: 68973c005b9e442fd82724347101afe0f2f6004ba2af497f207111dfb6f1e1518994431de7acdb6e8764856b4ac3feb5c698d76e1c8455d86937efafd1c372c6
7
+ data.tar.gz: df11fa22730a3244652a1cdc88a04cc3255b43047c576e36ce3658bdfa5c37de27acd974f0dbef5aa6f264ecef4a09f449f6b3230c1c105e103fd5b244158bd1
data/.yardopts CHANGED
@@ -1,6 +1,7 @@
1
1
  --protected
2
2
  --output-dir doc/site
3
3
  --markup rdoc
4
+ --plugin activesupport-concern
4
5
  -
5
6
  LICENSE.md
6
7
  README.md
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.8'
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
- class << self
13
- # Allows for custom "Command" package naming.
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
- # 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 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")
34
- end
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
- alias_method :default_task, :default_command
35
+ end
37
36
 
38
- # Registers another Thor subclass as a command.
39
- #
40
- # ==== Parameters
41
- # klass<Class>:: Thor subclass to register
42
- # command<String>:: Subcommand name to use
43
- # usage<String>:: Short usage for the subcommand
44
- # description<String>:: Description for the subcommand
45
- def register(klass, subcommand_name, usage, description, options = {})
46
- if klass <= Thor::Group
47
- desc usage, description, options
48
- define_method(subcommand_name) { |*args| invoke(klass, args) }
49
- else
50
- desc usage, description, options
51
- subcommand subcommand_name, klass
52
- end
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
- # Defines the usage and the description of the next command.
56
- #
57
- # ==== Parameters
58
- # usage<String>
59
- # description<String>
60
- # options<String>
61
- #
62
- def desc(usage, description, options = {})
63
- if options[:for]
64
- command = find_and_refresh_command(options[:for])
65
- command.usage = usage if usage
66
- command.description = description if description
67
- else
68
- @usage = usage
69
- @desc = description
70
- @hide = options[:hide] || false
71
- end
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
- # Defines the long description of the next command.
75
- #
76
- # ==== Parameters
77
- # long description<String>
78
- #
79
- def long_desc(long_description, options = {})
80
- if options[:for]
81
- command = find_and_refresh_command(options[:for])
82
- command.long_description = long_description if long_description
83
- else
84
- @long_desc = long_description
85
- end
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
- # Maps an input to a command. If you define:
89
- #
90
- # map "-T" => "list"
91
- #
92
- # Running:
93
- #
94
- # thor -T
95
- #
96
- # Will invoke the list command.
97
- #
98
- # ==== Parameters
99
- # Hash[String|Array => Symbol]:: Maps the string or the strings in the
100
- # array to the given command.
101
- #
102
- def map(mappings = nil)
103
- @map ||= from_superclass(:map, {})
104
-
105
- if mappings
106
- mappings.each do |key, value|
107
- if key.respond_to?(:each)
108
- key.each { |subkey| @map[subkey] = value }
109
- else
110
- @map[key] = value
111
- end
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
- # Declares the options for the next command to be declared.
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
- # Adds an option to the set of method options. If :for is given as option,
134
- # it allows you to change the options from a previous defined command.
135
- #
136
- # def previous_command
137
- # # magic
138
- # end
139
- #
140
- # method_option :foo => :bar, :for => :previous_command
141
- #
142
- # def next_command
143
- # # magic
144
- # end
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
- build_option(name, options, scope)
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
- # Prints help information for the given command.
173
- #
174
- # @param [Thor::Shell] shell
175
- #
176
- # @param [String] command_name
177
- #
178
- # @param [Boolean] subcommand
179
- # *Alti* *addition* - passed from {#help} when that command is being
180
- # invoked as a subcommand.
181
- #
182
- # The values is passed through to {.banner} and eventually
183
- # {Command#formatted_usage} so that it can properly display the usage
184
- # message
185
- #
186
- # basename subcmd cmd ARGS...
187
- #
188
- # versus what it did when I found it:
189
- #
190
- # basename cmd ARGS...
191
- #
192
- # which, of course, doesn't work if +cmd+ is inside +subcmd+.
193
- #
194
- # @return [nil]
195
- #
196
- def command_help(shell, command_name, subcommand = false)
197
- meth = normalize_command_name(command_name)
198
- command = all_commands[meth]
199
- handle_no_command_error(meth) unless command
200
-
201
- shell.say "Usage:"
202
- shell.say " #{banner(command, nil, subcommand)}"
203
- shell.say
204
-
205
- class_options_help \
206
- shell,
207
- command.options.values.group_by { |option| option.group }
208
-
209
- if command.long_description
210
- shell.say "Description:"
211
- shell.print_wrapped(command.long_description, :indent => 2)
212
- else
213
- shell.say command.description
214
- end
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
- unless command.examples.empty?
217
- shell.say "\n"
218
- shell.say "Examples:"
219
- shell.say "\n"
245
+ command.examples.each_with_index do |example, index|
246
+ lines = example.lines
220
247
 
221
- command.examples.each_with_index do |example, index|
222
- lines = example.lines
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
- shell.say "\n"
252
+ lines[1..-1].each do |line|
253
+ shell.say " #{ line }"
254
+ end
232
255
  end
233
256
 
234
- nil
257
+ shell.say "\n"
235
258
  end
236
- alias_method :task_help, :command_help
259
+
260
+ nil
261
+ end
237
262
 
238
- # Prints help information for this class.
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
- shell.print_table(list, :indent => 2, :truncate => true)
257
- shell.say
258
- class_options_help(shell)
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
- # Returns commands ready to be printed.
262
- def printable_commands(all = true, subcommand = false)
263
- (all ? all_commands : commands).map do |_, command|
264
- next if command.hidden?
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
- alias_method :printable_tasks, :printable_commands
273
-
274
-
275
- # List of subcommand names, including those inherited from super
276
- # classes.
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
- # @return [Array<String>]
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
- def subcommands
281
- @subcommands ||= from_superclass(:subcommands, [])
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
- # @note
289
- # `.subcommands` is not necessarily equal to `.subcommand_classes.keys`
290
- # - it won't be when there are subcommands inherited from super classes.
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
- # @note
293
- # I'm not really sure how this relates to {Thor::Group}... and I'm not
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
- # @return [Hash<String, Class<Thor::Base>]
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
- # Extend check unknown options to accept a hash of conditions.
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
- command = config[:current_command]
347
- return true unless command
444
+ singleton_class.send :alias_method, :subtask, :subcommand
348
445
 
349
- name = command.name
350
446
 
351
- if subcommands.include?(name)
352
- false
353
- elsif options[:except]
354
- !options[:except].include?(name.to_sym)
355
- elsif options[:only]
356
- options[:only].include?(name.to_sym)
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
- true
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
- def stop_on_unknown_option?(command) #:nodoc:
404
- command && stop_on_unknown_option.include?(command.name.to_sym)
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
- # Disable the check for required options for the given commands.
408
- # This is useful if you have a command that does not need the required options
409
- # to work, like help.
410
- #
411
- # ==== Parameters
412
- # Symbol ...:: A list of commands that should be affected.
413
- def disable_required_check!(*command_names)
414
- disable_required_check.merge(command_names)
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
- def disable_required_check?(command) #:nodoc:
418
- command && disable_required_check.include?(command.name.to_sym)
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
- # Atli Public Class Methods
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
- def shared_method_options(options = nil)
428
- @shared_method_options ||= begin
429
- # Reach up the inheritance chain, if there's anyone there
430
- if superclass.respond_to? __method__
431
- superclass.send( __method__ ).dup
432
- else
433
- # Or just default to empty
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
- if options
439
- # We don't support this (yet at least)
440
- raise NotImplementedError,
441
- "Bulk set not supported, use .shared_method_option"
442
- # build_shared_options(options, @shared_method_options)
443
- end
444
- @shared_method_options
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
- # Find shared options given names and groups.
450
- #
451
- # @param [*<Symbol>] names
452
- # Individual shared option names to include.
453
- #
454
- # @param [nil | Symbol | Enumerable<Symbol>] groups:
455
- # Single or list of shared option groups to include.
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 [Hash<Symbol, Thor::SharedOption>]
458
- # Hash mapping option names (as {Symbol}) to instances.
642
+ # @return [String]
643
+ # The banner for the command.
459
644
  #
460
- def find_shared_method_options *names, groups: nil
461
- groups_set = Set[*groups]
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
- shared_method_options.each_with_object( {} ) do |(name, option), results|
464
- match = {}
465
-
466
- if names.include? name
467
- match[:name] = true
468
- end
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
- match_groups = option.groups & groups_set
471
-
472
- unless match_groups.empty?
473
- match[:groups] = match_groups
474
- end
475
-
476
- unless match.empty?
477
- results[name] = {
478
- option: option,
479
- match: match,
480
- }
481
- end
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
- alias_method :find_shared_options, :find_shared_method_options
485
-
486
-
487
- # Declare a shared method option with an optional groups that can then
488
- # be added by name or group to commands.
489
- #
490
- # The shared options can then be added to methods individually by name and
491
- # collectively as groups with {Thor.include_method_options}.
492
- #
493
- # @example
494
- # class MyCLI < Thor
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
- # @param [Symbol] name
520
- # The name of the option.
709
+ # @note
710
+ # Ugh... this *mutates* `args`
521
711
  #
522
- # @param [**<Symbol, V>] options
523
- # Keyword args used to initialize the {Thor::SharedOption}.
524
- #
525
- # All +**options+ are optional.
712
+ # @param [Array<String>] args
526
713
  #
527
- # @option options [Symbol | Array<Symbol>] :groups
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
- # @option options [Boolean] :required
544
- # If the option is required or not.
545
- #
546
- # @option options [Object] :default
547
- # Default value for this argument.
548
- #
549
- # It cannot be +required+ and have default values.
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
- # @option options [String | Array<String>] :aliases
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
- # @option options [Boolean] :hide
568
- # If you want to hide this option from the help.
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
- def stop_on_unknown_option #:nodoc:
611
- @stop_on_unknown_option ||= Set.new
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
- # help command has the required check disabled by default.
615
- def disable_required_check #:nodoc:
616
- @disable_required_check ||= Set.new([:help])
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
- # The method responsible for dispatching given the args.
620
- def dispatch(meth, given_args, given_opts, config) #:nodoc: # rubocop:disable MethodLength
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
- if command
632
- args, opts = Thor::Options.split(given_args)
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
- instance = new(args, opts, config)
650
- yield instance if block_given?
651
- args = instance.args
652
- trailing = args[Range.new(arguments.size, -1)]
653
- instance.invoke_command(command, trailing || [])
654
- end
655
-
656
-
657
- # The banner for this class. You can customize it if you are invoking the
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
- def dynamic_command_class #:nodoc:
686
- Thor::DynamicCommand
687
- end
772
+ possibilities = all_commands.merge(map).keys.select { |name|
773
+ name.start_with? input
774
+ }.sort
688
775
 
689
- def create_command(meth) #:nodoc:
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
- def initialize_added #:nodoc:
722
- class_options.merge!(method_options)
723
- @method_options = nil
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
- # Retrieve the command name from given args.
727
- def retrieve_command_name(args) #:nodoc:
728
- meth = args.first.to_s unless args.empty?
729
- args.shift if meth && (map[meth] || meth !~ /^\-/)
730
- end
731
- alias_method :retrieve_task_name, :retrieve_command_name
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
- if possibilities.empty?
751
- meth ||= default_command
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
- def subcommand_help(cmd)
783
- logger.trace __method__.to_s,
784
- cmd: cmd,
785
- caller: caller
786
-
787
- desc "help [COMMAND]", "Describe subcommands or one specific subcommand"
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
- # Atli - This used to be {#class_eval} (maybe to support really old
790
- # Rubies? Who knows...) but that made it really hard to find in
791
- # stack traces, so I switched it to {#define_method}.
792
- #
793
- define_method :help do |*args|
794
-
795
- # Add the `is_subcommand = true` trailing arg
796
- case args[-1]
797
- when true
798
- # pass
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
- # Build a Thor::SharedOption and add it to Thor.shared_method_options.
817
- #
818
- # The Thor::SharedOption is returned.
819
- #
820
- # ==== Parameters
821
- # name<Symbol>:: The name of the argument.
822
- # options<Hash>:: Described in both class_option and method_option,
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
- # END protected Class Methods ********************************************
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
- end # class << self ********************************************************
849
+ public # END protected Class Methods ***************************************
834
850
 
835
851
 
836
852
  protected # Instance Methods