atli 0.1.9 → 0.1.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml 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