atli 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. checksums.yaml +7 -0
  2. data/.yardopts +8 -0
  3. data/CHANGELOG.md +193 -0
  4. data/CONTRIBUTING.md +20 -0
  5. data/LICENSE.md +24 -0
  6. data/README.md +44 -0
  7. data/atli.gemspec +30 -0
  8. data/bin/thor +6 -0
  9. data/lib/thor.rb +868 -0
  10. data/lib/thor/actions.rb +322 -0
  11. data/lib/thor/actions/create_file.rb +104 -0
  12. data/lib/thor/actions/create_link.rb +60 -0
  13. data/lib/thor/actions/directory.rb +118 -0
  14. data/lib/thor/actions/empty_directory.rb +143 -0
  15. data/lib/thor/actions/file_manipulation.rb +364 -0
  16. data/lib/thor/actions/inject_into_file.rb +109 -0
  17. data/lib/thor/base.rb +773 -0
  18. data/lib/thor/command.rb +192 -0
  19. data/lib/thor/core_ext/hash_with_indifferent_access.rb +97 -0
  20. data/lib/thor/core_ext/io_binary_read.rb +12 -0
  21. data/lib/thor/core_ext/ordered_hash.rb +129 -0
  22. data/lib/thor/error.rb +32 -0
  23. data/lib/thor/group.rb +281 -0
  24. data/lib/thor/invocation.rb +182 -0
  25. data/lib/thor/line_editor.rb +17 -0
  26. data/lib/thor/line_editor/basic.rb +37 -0
  27. data/lib/thor/line_editor/readline.rb +88 -0
  28. data/lib/thor/parser.rb +5 -0
  29. data/lib/thor/parser/argument.rb +70 -0
  30. data/lib/thor/parser/arguments.rb +175 -0
  31. data/lib/thor/parser/option.rb +146 -0
  32. data/lib/thor/parser/options.rb +221 -0
  33. data/lib/thor/parser/shared_option.rb +23 -0
  34. data/lib/thor/rake_compat.rb +71 -0
  35. data/lib/thor/runner.rb +324 -0
  36. data/lib/thor/shell.rb +81 -0
  37. data/lib/thor/shell/basic.rb +439 -0
  38. data/lib/thor/shell/color.rb +149 -0
  39. data/lib/thor/shell/html.rb +126 -0
  40. data/lib/thor/util.rb +268 -0
  41. data/lib/thor/version.rb +22 -0
  42. metadata +114 -0
@@ -0,0 +1,109 @@
1
+ require "thor/actions/empty_directory"
2
+
3
+ class Thor
4
+ module Actions
5
+ # Injects the given content into a file. Different from gsub_file, this
6
+ # method is reversible.
7
+ #
8
+ # ==== Parameters
9
+ # destination<String>:: Relative path to the destination root
10
+ # data<String>:: Data to add to the file. Can be given as a block.
11
+ # config<Hash>:: give :verbose => false to not log the status and the flag
12
+ # for injection (:after or :before) or :force => true for
13
+ # insert two or more times the same content.
14
+ #
15
+ # ==== Examples
16
+ #
17
+ # insert_into_file "config/environment.rb", "config.gem :thor", :after => "Rails::Initializer.run do |config|\n"
18
+ #
19
+ # insert_into_file "config/environment.rb", :after => "Rails::Initializer.run do |config|\n" do
20
+ # gems = ask "Which gems would you like to add?"
21
+ # gems.split(" ").map{ |gem| " config.gem :#{gem}" }.join("\n")
22
+ # end
23
+ #
24
+ def insert_into_file(destination, *args, &block)
25
+ data = block_given? ? block : args.shift
26
+ config = args.shift
27
+ action InjectIntoFile.new(self, destination, data, config)
28
+ end
29
+ alias_method :inject_into_file, :insert_into_file
30
+
31
+ class InjectIntoFile < EmptyDirectory #:nodoc:
32
+ attr_reader :replacement, :flag, :behavior
33
+
34
+ def initialize(base, destination, data, config)
35
+ super(base, destination, {:verbose => true}.merge(config))
36
+
37
+ @behavior, @flag = if @config.key?(:after)
38
+ [:after, @config.delete(:after)]
39
+ else
40
+ [:before, @config.delete(:before)]
41
+ end
42
+
43
+ @replacement = data.is_a?(Proc) ? data.call : data
44
+ @flag = Regexp.escape(@flag) unless @flag.is_a?(Regexp)
45
+ end
46
+
47
+ def invoke!
48
+ say_status :invoke
49
+
50
+ content = if @behavior == :after
51
+ '\0' + replacement
52
+ else
53
+ replacement + '\0'
54
+ end
55
+
56
+ if exists?
57
+ replace!(/#{flag}/, content, config[:force])
58
+ else
59
+ unless pretend?
60
+ raise Thor::Error, "The file #{ destination } does not appear to exist"
61
+ end
62
+ end
63
+ end
64
+
65
+ def revoke!
66
+ say_status :revoke
67
+
68
+ regexp = if @behavior == :after
69
+ content = '\1\2'
70
+ /(#{flag})(.*)(#{Regexp.escape(replacement)})/m
71
+ else
72
+ content = '\2\3'
73
+ /(#{Regexp.escape(replacement)})(.*)(#{flag})/m
74
+ end
75
+
76
+ replace!(regexp, content, true)
77
+ end
78
+
79
+ protected
80
+
81
+ def say_status(behavior)
82
+ status = if behavior == :invoke
83
+ if flag == /\A/
84
+ :prepend
85
+ elsif flag == /\z/
86
+ :append
87
+ else
88
+ :insert
89
+ end
90
+ else
91
+ :subtract
92
+ end
93
+
94
+ super(status, config[:verbose])
95
+ end
96
+
97
+ # Adds the content to the file.
98
+ #
99
+ def replace!(regexp, string, force)
100
+ return if pretend?
101
+ content = File.read(destination)
102
+ if force || !content.include?(replacement)
103
+ content.gsub!(regexp, string)
104
+ File.open(destination, "wb") { |file| file.write(content) }
105
+ end
106
+ end
107
+ end
108
+ end
109
+ end
data/lib/thor/base.rb ADDED
@@ -0,0 +1,773 @@
1
+ require "thor/command"
2
+ require "thor/core_ext/hash_with_indifferent_access"
3
+ require "thor/core_ext/ordered_hash"
4
+ require "thor/error"
5
+ require "thor/invocation"
6
+ require "thor/parser"
7
+ require "thor/shell"
8
+ require "thor/line_editor"
9
+ require "thor/util"
10
+
11
+ class Thor
12
+ autoload :Actions, "thor/actions"
13
+ autoload :RakeCompat, "thor/rake_compat"
14
+ autoload :Group, "thor/group"
15
+
16
+ # Shortcuts for help.
17
+ HELP_MAPPINGS = %w(-h -? --help -D)
18
+
19
+ # Thor methods that should not be overwritten by the user.
20
+ THOR_RESERVED_WORDS = %w(invoke shell options behavior root destination_root
21
+ relative_root action add_file create_file in_root
22
+ inside run run_ruby_script)
23
+
24
+ TEMPLATE_EXTNAME = ".tt"
25
+
26
+ # Shared base behavior included in {Thor} and {Thor::Group}.
27
+ #
28
+ module Base
29
+ attr_accessor :options, :parent_options, :args
30
+
31
+ # It receives arguments in an Array and two hashes, one for options and
32
+ # other for configuration.
33
+ #
34
+ # Notice that it does not check if all required arguments were supplied.
35
+ # It should be done by the parser.
36
+ #
37
+ # ==== Parameters
38
+ # args<Array[Object]>:: An array of objects. The objects are applied to
39
+ # their respective accessors declared with
40
+ # <tt>argument</tt>.
41
+ #
42
+ # options<Hash>:: An options hash that will be available as self.options.
43
+ # The hash given is converted to a hash with indifferent
44
+ # access, magic predicates (options.skip?) and then frozen.
45
+ #
46
+ # config<Hash>:: Configuration for this Thor class.
47
+ #
48
+ def initialize(args = [], local_options = {}, config = {})
49
+ logger.trace "#{ self.class.name }#initialize",
50
+ args: args,
51
+ local_options: local_options,
52
+ config: config
53
+
54
+ parse_options = self.class.class_options
55
+
56
+ # The start method splits inbound arguments at the first argument
57
+ # that looks like an option (starts with - or --). It then calls
58
+ # new, passing in the two halves of the arguments Array as the
59
+ # first two parameters.
60
+
61
+ command_options = config.delete(:command_options) # hook for start
62
+ parse_options = parse_options.merge(command_options) if command_options
63
+ if local_options.is_a?(Array)
64
+ array_options = local_options
65
+ hash_options = {}
66
+ else
67
+ # Handle the case where the class was explicitly instantiated
68
+ # with pre-parsed options.
69
+ array_options = []
70
+ hash_options = local_options
71
+ end
72
+
73
+ # Let Thor::Options parse the options first, so it can remove
74
+ # declared options from the array. This will leave us with
75
+ # a list of arguments that weren't declared.
76
+ stop_on_unknown = \
77
+ self.class.stop_on_unknown_option? config[:current_command]
78
+ disable_required_check = \
79
+ self.class.disable_required_check? config[:current_command]
80
+ opts = Thor::Options.new( parse_options,
81
+ hash_options,
82
+ stop_on_unknown,
83
+ disable_required_check )
84
+ self.options = opts.parse(array_options)
85
+ if config[:class_options]
86
+ self.options = config[:class_options].merge(options)
87
+ end
88
+
89
+ # If unknown options are disallowed, make sure that none of the
90
+ # remaining arguments looks like an option.
91
+ opts.check_unknown! if self.class.check_unknown_options?(config)
92
+
93
+ # Add the remaining arguments from the options parser to the
94
+ # arguments passed in to initialize. Then remove any positional
95
+ # arguments declared using #argument (this is primarily used
96
+ # by Thor::Group). Tis will leave us with the remaining
97
+ # positional arguments.
98
+ to_parse = args
99
+ to_parse += opts.remaining unless self.class.strict_args_position?(config)
100
+
101
+ thor_args = Thor::Arguments.new(self.class.arguments)
102
+ thor_args.parse(to_parse).each { |k, v| __send__("#{k}=", v) }
103
+ @args = thor_args.remaining
104
+ end
105
+
106
+
107
+ protected
108
+ # ========================================================================
109
+
110
+ # Atli addition: An error handling hook that is called from
111
+ # {Thor::Command#run} when running a command raises an unhandled
112
+ # exception.
113
+ #
114
+ # I don't believe errors are *recoverable* at this point, but this
115
+ # hook allows the {Thor} subclass to respond to expected errors and
116
+ # gracefully inform the user.
117
+ #
118
+ # It's basically `goto fail` or whatever.
119
+ #
120
+ # User overrides should always exit or re-raise the error.
121
+ #
122
+ # The base implementation here simply re-raises.
123
+ #
124
+ # Note that {ArgumentError} and {NoMethodError} are both rescued in
125
+ # {Thor::Command#run} and passed off to Thor's relevant
126
+ # `.handle_*_error` methods, so you probably won't be able to intercept
127
+ # any of those.
128
+ #
129
+ # Generally, it's best to use this to respond to custom, specific errors
130
+ # so you can easily bail out with a `raise` from anywhere in the
131
+ # application and still provide a properly formatted response and exit
132
+ # status to the user.
133
+ #
134
+ # Errors that are only expected in a single command
135
+ #
136
+ # @param [Exception] error
137
+ # The error the bubbled up to {Thor::Command#run}.
138
+ #
139
+ # @param [Thor::Command] command
140
+ # The command instance that was running when the error occurred.
141
+ #
142
+ # @param [Array<String>] args
143
+ # The arguments to the command that was running.
144
+ #
145
+ def on_run_error error, command, args
146
+ raise error
147
+ end
148
+
149
+ # end protected
150
+
151
+
152
+ class << self
153
+ def included(base) #:nodoc:
154
+ base.extend ClassMethods
155
+ base.send :include, Invocation
156
+ base.send :include, Shell
157
+ end
158
+
159
+ # Returns the classes that inherits from Thor or Thor::Group.
160
+ #
161
+ # ==== Returns
162
+ # Array[Class]
163
+ #
164
+ def subclasses
165
+ @subclasses ||= []
166
+ end
167
+
168
+ # Returns the files where the subclasses are kept.
169
+ #
170
+ # ==== Returns
171
+ # Hash[path<String> => Class]
172
+ #
173
+ def subclass_files
174
+ @subclass_files ||= Hash.new { |h, k| h[k] = [] }
175
+ end
176
+
177
+ # Whenever a class inherits from Thor or Thor::Group, we should track the
178
+ # class and the file on Thor::Base. This is the method responsible for it.
179
+ #
180
+ def register_klass_file(klass) #:nodoc:
181
+ file = caller[1].match(/(.*):\d+/)[1]
182
+ unless Thor::Base.subclasses.include?(klass)
183
+ Thor::Base.subclasses << klass
184
+ end
185
+
186
+ file_subclasses = Thor::Base.subclass_files[File.expand_path(file)]
187
+ file_subclasses << klass unless file_subclasses.include?(klass)
188
+ end
189
+ end
190
+
191
+ module ClassMethods
192
+ def attr_reader(*) #:nodoc:
193
+ no_commands { super }
194
+ end
195
+
196
+ def attr_writer(*) #:nodoc:
197
+ no_commands { super }
198
+ end
199
+
200
+ def attr_accessor(*) #:nodoc:
201
+ no_commands { super }
202
+ end
203
+
204
+ # If you want to raise an error for unknown options, call
205
+ # check_unknown_options!
206
+ # This is disabled by default to allow dynamic invocations.
207
+ def check_unknown_options!
208
+ @check_unknown_options = true
209
+ end
210
+
211
+ def check_unknown_options #:nodoc:
212
+ @check_unknown_options ||= from_superclass(:check_unknown_options, false)
213
+ end
214
+
215
+ def check_unknown_options?(config) #:nodoc:
216
+ !!check_unknown_options
217
+ end
218
+
219
+ # If you want to raise an error when the default value of an option does
220
+ # not match the type call check_default_type!
221
+ # This is disabled by default for compatibility.
222
+ def check_default_type!
223
+ @check_default_type = true
224
+ end
225
+
226
+ def check_default_type #:nodoc:
227
+ @check_default_type ||= from_superclass(:check_default_type, false)
228
+ end
229
+
230
+ def check_default_type? #:nodoc:
231
+ !!check_default_type
232
+ end
233
+
234
+ # If true, option parsing is suspended as soon as an unknown option or a
235
+ # regular argument is encountered. All remaining arguments are passed to
236
+ # the command as regular arguments.
237
+ def stop_on_unknown_option?(command_name) #:nodoc:
238
+ false
239
+ end
240
+
241
+ # If true, option set will not suspend the execution of the command when
242
+ # a required option is not provided.
243
+ def disable_required_check?(command_name) #:nodoc:
244
+ false
245
+ end
246
+
247
+ # If you want only strict string args (useful when cascading thor classes),
248
+ # call strict_args_position! This is disabled by default to allow dynamic
249
+ # invocations.
250
+ def strict_args_position!
251
+ @strict_args_position = true
252
+ end
253
+
254
+ def strict_args_position #:nodoc:
255
+ @strict_args_position ||= from_superclass(:strict_args_position, false)
256
+ end
257
+
258
+ def strict_args_position?(config) #:nodoc:
259
+ !!strict_args_position
260
+ end
261
+
262
+ # Adds an argument to the class and creates an attr_accessor for it.
263
+ #
264
+ # Arguments are different from options in several aspects. The first one
265
+ # is how they are parsed from the command line, arguments are retrieved
266
+ # from position:
267
+ #
268
+ # thor command NAME
269
+ #
270
+ # Instead of:
271
+ #
272
+ # thor command --name=NAME
273
+ #
274
+ # Besides, arguments are used inside your code as an accessor
275
+ # (self.argument), while options are all kept in a hash (self.options).
276
+ #
277
+ # Finally, arguments cannot have type :default or :boolean but can be
278
+ # optional (supplying :optional => :true or :required => false), although
279
+ # you cannot have a required argument after a non-required argument. If
280
+ # you try it, an error is raised.
281
+ #
282
+ # ==== Parameters
283
+ # name<Symbol>:: The name of the argument.
284
+ # options<Hash>:: Described below.
285
+ #
286
+ # ==== Options
287
+ # :desc - Description for the argument.
288
+ # :required - If the argument is required or not.
289
+ # :optional - If the argument is optional or not.
290
+ # :type - The type of the argument, can be :string, :hash, :array,
291
+ # :numeric.
292
+ # :default - Default value for this argument. It cannot be required and
293
+ # have default values.
294
+ # :banner - String to show on usage notes.
295
+ #
296
+ # ==== Errors
297
+ # ArgumentError:: Raised if you supply a required argument after a non
298
+ # required one.
299
+ #
300
+ def argument(name, options = {})
301
+ is_thor_reserved_word?(name, :argument)
302
+ no_commands { attr_accessor name }
303
+
304
+ required = if options.key?(:optional)
305
+ !options[:optional]
306
+ elsif options.key?(:required)
307
+ options[:required]
308
+ else
309
+ options[:default].nil?
310
+ end
311
+
312
+ remove_argument name
313
+
314
+ if required
315
+ arguments.each do |argument|
316
+ next if argument.required?
317
+ raise ArgumentError,
318
+ "You cannot have #{name.to_s.inspect} as required argument " \
319
+ "after the non-required argument #{argument.human_name.inspect}."
320
+ end
321
+ end
322
+
323
+ options[:required] = required
324
+
325
+ arguments << Thor::Argument.new(name, options)
326
+ end
327
+
328
+ # Returns this class arguments, looking up in the ancestors chain.
329
+ #
330
+ # ==== Returns
331
+ # Array[Thor::Argument]
332
+ #
333
+ def arguments
334
+ @arguments ||= from_superclass(:arguments, [])
335
+ end
336
+
337
+ # Adds a bunch of options to the set of class options.
338
+ #
339
+ # class_options :foo => false, :bar => :required, :baz => :string
340
+ #
341
+ # If you prefer more detailed declaration, check class_option.
342
+ #
343
+ # ==== Parameters
344
+ # Hash[Symbol => Object]
345
+ #
346
+ def class_options(options = nil)
347
+ @class_options ||= from_superclass(:class_options, {})
348
+ build_options(options, @class_options) if options
349
+ @class_options
350
+ end
351
+
352
+ # Adds an option to the set of class options
353
+ #
354
+ # ==== Parameters
355
+ # name<Symbol>:: The name of the argument.
356
+ # options<Hash>:: Described below.
357
+ #
358
+ # ==== Options
359
+ # :desc:: -- Description for the argument.
360
+ # :required:: -- If the argument is required or not.
361
+ # :default:: -- Default value for this argument.
362
+ # :group:: -- The group for this options. Use by class options to
363
+ # output options in different levels.
364
+ # :aliases:: -- Aliases for this option. <b>Note:</b> Thor follows a
365
+ # convention of one-dash-one-letter options. Thus
366
+ # aliases like "-something" wouldn't be parsed; use
367
+ # either "\--something" or "-s" instead.
368
+ # :type:: -- The type of the argument, can be :string, :hash, :array,
369
+ # :numeric or :boolean.
370
+ # :banner:: -- String to show on usage notes.
371
+ # :hide:: -- If you want to hide this option from the help.
372
+ #
373
+ def class_option(name, options = {})
374
+ build_option(name, options, class_options)
375
+ end
376
+
377
+ # Removes a previous defined argument. If :undefine is given, undefine
378
+ # accessors as well.
379
+ #
380
+ # ==== Parameters
381
+ # names<Array>:: Arguments to be removed
382
+ #
383
+ # ==== Examples
384
+ #
385
+ # remove_argument :foo
386
+ # remove_argument :foo, :bar, :baz, :undefine => true
387
+ #
388
+ def remove_argument(*names)
389
+ options = names.last.is_a?(Hash) ? names.pop : {}
390
+
391
+ names.each do |name|
392
+ arguments.delete_if { |a| a.name == name.to_s }
393
+ undef_method name, "#{name}=" if options[:undefine]
394
+ end
395
+ end
396
+
397
+ # Removes a previous defined class option.
398
+ #
399
+ # ==== Parameters
400
+ # names<Array>:: Class options to be removed
401
+ #
402
+ # ==== Examples
403
+ #
404
+ # remove_class_option :foo
405
+ # remove_class_option :foo, :bar, :baz
406
+ #
407
+ def remove_class_option(*names)
408
+ names.each do |name|
409
+ class_options.delete(name)
410
+ end
411
+ end
412
+
413
+ # Defines the group. This is used when thor list is invoked so you can
414
+ # specify that only commands from a pre-defined group will be shown.
415
+ # Defaults to standard.
416
+ #
417
+ # ==== Parameters
418
+ # name<String|Symbol>
419
+ #
420
+ def group(name = nil)
421
+ if name
422
+ @group = name.to_s
423
+ else
424
+ @group ||= from_superclass(:group, "standard")
425
+ end
426
+ end
427
+
428
+ # Returns the commands for this Thor class.
429
+ #
430
+ # ==== Returns
431
+ # OrderedHash:: An ordered hash with commands names as keys and
432
+ # Thor::Command objects as values.
433
+ #
434
+ def commands
435
+ @commands ||= Thor::CoreExt::OrderedHash.new
436
+ end
437
+ alias_method :tasks, :commands
438
+
439
+ # Returns the commands for this Thor class and all subclasses.
440
+ #
441
+ # ==== Returns
442
+ # OrderedHash:: An ordered hash with commands names as keys and
443
+ # Thor::Command objects as values.
444
+ #
445
+ def all_commands
446
+ @all_commands ||= from_superclass( :all_commands,
447
+ Thor::CoreExt::OrderedHash.new )
448
+ @all_commands.merge!(commands)
449
+ end
450
+ alias_method :all_tasks, :all_commands
451
+
452
+ # Removes a given command from this Thor class. This is usually done if you
453
+ # are inheriting from another class and don't want it to be available
454
+ # anymore.
455
+ #
456
+ # By default it only remove the mapping to the command. But you can supply
457
+ # :undefine => true to undefine the method from the class as well.
458
+ #
459
+ # ==== Parameters
460
+ # name<Symbol|String>:: The name of the command to be removed
461
+ # options<Hash>:: You can give :undefine => true if you want commands the
462
+ # method to be undefined from the class as well.
463
+ #
464
+ def remove_command(*names)
465
+ options = names.last.is_a?(Hash) ? names.pop : {}
466
+
467
+ names.each do |name|
468
+ commands.delete(name.to_s)
469
+ all_commands.delete(name.to_s)
470
+ undef_method name if options[:undefine]
471
+ end
472
+ end
473
+ alias_method :remove_task, :remove_command
474
+
475
+ # All methods defined inside the given block are not added as commands.
476
+ #
477
+ # So you can do:
478
+ #
479
+ # class MyScript < Thor
480
+ # no_commands do
481
+ # def this_is_not_a_command
482
+ # end
483
+ # end
484
+ # end
485
+ #
486
+ # You can also add the method and remove it from the command list:
487
+ #
488
+ # class MyScript < Thor
489
+ # def this_is_not_a_command
490
+ # end
491
+ # remove_command :this_is_not_a_command
492
+ # end
493
+ #
494
+ def no_commands
495
+ @no_commands = true
496
+ yield
497
+ ensure
498
+ @no_commands = false
499
+ end
500
+ alias_method :no_tasks, :no_commands
501
+
502
+ # Sets the namespace for the Thor or Thor::Group class. By default the
503
+ # namespace is retrieved from the class name. If your Thor class is named
504
+ # Scripts::MyScript, the help method, for example, will be called as:
505
+ #
506
+ # thor scripts:my_script -h
507
+ #
508
+ # If you change the namespace:
509
+ #
510
+ # namespace :my_scripts
511
+ #
512
+ # You change how your commands are invoked:
513
+ #
514
+ # thor my_scripts -h
515
+ #
516
+ # Finally, if you change your namespace to default:
517
+ #
518
+ # namespace :default
519
+ #
520
+ # Your commands can be invoked with a shortcut. Instead of:
521
+ #
522
+ # thor :my_command
523
+ #
524
+ def namespace(name = nil)
525
+ if name
526
+ @namespace = name.to_s
527
+ else
528
+ @namespace ||= Thor::Util.namespace_from_thor_class(self)
529
+ end
530
+ end
531
+
532
+ # Parses the command and options from the given args, instantiate the class
533
+ # and invoke the command. This method is used when the arguments must be parsed
534
+ # from an array. If you are inside Ruby and want to use a Thor class, you
535
+ # can simply initialize it:
536
+ #
537
+ # script = MyScript.new(args, options, config)
538
+ # script.invoke(:command, first_arg, second_arg, third_arg)
539
+ #
540
+ def start(given_args = ARGV, config = {})
541
+ config[:shell] ||= Thor::Base.shell.new
542
+ dispatch(nil, given_args.dup, nil, config)
543
+ rescue Thor::Error => e
544
+ config[:debug] || (
545
+ ENV["THOR_DEBUG"] == "1" ?
546
+ (raise e) :
547
+ config[:shell].error(e.message)
548
+ )
549
+ exit(1) if exit_on_failure?
550
+ rescue Errno::EPIPE
551
+ # This happens if a thor command is piped to something like `head`,
552
+ # which closes the pipe when it's done reading. This will also
553
+ # mean that if the pipe is closed, further unnecessary
554
+ # computation will not occur.
555
+ exit(0)
556
+ end
557
+
558
+ # Allows to use private methods from parent in child classes as commands.
559
+ #
560
+ # ==== Parameters
561
+ # names<Array>:: Method names to be used as commands
562
+ #
563
+ # ==== Examples
564
+ #
565
+ # public_command :foo
566
+ # public_command :foo, :bar, :baz
567
+ #
568
+ def public_command(*names)
569
+ names.each do |name|
570
+ class_eval "def #{name}(*); super end"
571
+ end
572
+ end
573
+ alias_method :public_task, :public_command
574
+
575
+ def handle_no_command_error(command, has_namespace = $thor_runner) #:nodoc:
576
+ if has_namespace
577
+ raise UndefinedCommandError,
578
+ "Could not find command #{command.inspect} in " \
579
+ "#{namespace.inspect} namespace."
580
+ end
581
+ raise UndefinedCommandError,
582
+ "Could not find command #{command.inspect}."
583
+ end
584
+ alias_method :handle_no_task_error, :handle_no_command_error
585
+
586
+ def handle_argument_error(command, error, args, arity) #:nodoc:
587
+ name = [command.ancestor_name, command.name].compact.join(" ")
588
+ msg = "ERROR: \"#{basename} #{name}\" was called with ".dup
589
+ msg << "no arguments" if args.empty?
590
+ msg << "arguments " << args.inspect unless args.empty?
591
+ msg << "\nUsage: #{banner(command).inspect}"
592
+ raise InvocationError, msg
593
+ end
594
+
595
+ protected
596
+
597
+ # Prints the class options per group. If an option does not belong to
598
+ # any group, it's printed as Class option.
599
+ #
600
+ def class_options_help(shell, groups = {}) #:nodoc:
601
+ # Group options by group
602
+ class_options.each do |_, value|
603
+ groups[value.group] ||= []
604
+ groups[value.group] << value
605
+ end
606
+
607
+ # Deal with default group
608
+ global_options = groups.delete(nil) || []
609
+ print_options(shell, global_options)
610
+
611
+ # Print all others
612
+ groups.each do |group_name, options|
613
+ print_options(shell, options, group_name)
614
+ end
615
+ end
616
+
617
+ # Receives a set of options and print them.
618
+ def print_options(shell, options, group_name = nil)
619
+ return if options.empty?
620
+
621
+ list = []
622
+ padding = options.map { |o| o.aliases.size }.max.to_i * 4
623
+
624
+ options.each do |option|
625
+ next if option.hide
626
+ item = [option.usage(padding)]
627
+ item.push(option.description ? "# #{option.description}" : "")
628
+
629
+ list << item
630
+ list << ["", "# Default: #{option.default}"] if option.show_default?
631
+ if option.enum
632
+ list << ["", "# Possible values: #{option.enum.join(', ')}"]
633
+ end
634
+ end
635
+
636
+ shell.say(group_name ? "#{group_name} options:" : "Options:")
637
+ shell.print_table(list, :indent => 2)
638
+ shell.say ""
639
+ end
640
+
641
+ # Raises an error if the word given is a Thor reserved word.
642
+ def is_thor_reserved_word?(word, type) #:nodoc:
643
+ return false unless THOR_RESERVED_WORDS.include?(word.to_s)
644
+ raise "#{word.inspect} is a Thor reserved word and cannot be " \
645
+ "defined as #{type}"
646
+ end
647
+
648
+ # Build an option and adds it to the given scope.
649
+ #
650
+ # ==== Parameters
651
+ # name<Symbol>:: The name of the argument.
652
+ # options<Hash>:: Described in both class_option and method_option.
653
+ # scope<Hash>:: Options hash that is being built up
654
+ def build_option(name, options, scope) #:nodoc:
655
+ scope[name] = Thor::Option.new(
656
+ name,
657
+ options.merge(:check_default_type => check_default_type?)
658
+ )
659
+ end
660
+
661
+ # Receives a hash of options, parse them and add to the scope. This is a
662
+ # fast way to set a bunch of options:
663
+ #
664
+ # build_options :foo => true, :bar => :required, :baz => :string
665
+ #
666
+ # ==== Parameters
667
+ # Hash[Symbol => Object]
668
+ def build_options(options, scope) #:nodoc:
669
+ options.each do |key, value|
670
+ scope[key] = Thor::Option.parse(key, value)
671
+ end
672
+ end
673
+
674
+ # Finds a command with the given name. If the command belongs to the current
675
+ # class, just return it, otherwise dup it and add the fresh copy to the
676
+ # current command hash.
677
+ def find_and_refresh_command(name) #:nodoc:
678
+ if commands[name.to_s]
679
+ commands[name.to_s]
680
+ elsif command = all_commands[name.to_s] # rubocop:disable AssignmentInCondition
681
+ commands[name.to_s] = command.clone
682
+ else
683
+ raise ArgumentError,
684
+ "You supplied :for => #{name.inspect}, but the command " \
685
+ "#{name.inspect} could not be found."
686
+ end
687
+ end
688
+ alias_method :find_and_refresh_task, :find_and_refresh_command
689
+
690
+ # Everytime someone inherits from a Thor class, register the klass
691
+ # and file into baseclass.
692
+ def inherited(klass)
693
+ Thor::Base.register_klass_file(klass)
694
+ klass.instance_variable_set(:@no_commands, false)
695
+ end
696
+
697
+ # Fire this callback whenever a method is added. Added methods are
698
+ # tracked as commands by invoking the create_command method.
699
+ def method_added(meth)
700
+ meth = meth.to_s
701
+
702
+ if meth == "initialize"
703
+ initialize_added
704
+ return
705
+ end
706
+
707
+ # Return if it's not a public instance method
708
+ return unless public_method_defined?(meth.to_sym)
709
+
710
+ @no_commands ||= false
711
+ return if @no_commands || !create_command(meth)
712
+
713
+ is_thor_reserved_word?(meth, :command)
714
+ Thor::Base.register_klass_file(self)
715
+ end
716
+
717
+ # Retrieves a value from superclass. If it reaches the baseclass,
718
+ # returns default.
719
+ def from_superclass(method, default = nil)
720
+ if self == baseclass || !superclass.respond_to?(method, true)
721
+ default
722
+ else
723
+ value = superclass.send(method)
724
+
725
+ # Ruby implements `dup` on Object, but raises a `TypeError`
726
+ # if the method is called on immediates. As a result, we
727
+ # don't have a good way to check whether dup will succeed
728
+ # without calling it and rescuing the TypeError.
729
+ begin
730
+ value.dup
731
+ rescue TypeError
732
+ value
733
+ end
734
+
735
+ end
736
+ end
737
+
738
+ # A flag that makes the process exit with status 1 if any error happens.
739
+ def exit_on_failure?
740
+ false
741
+ end
742
+
743
+ #
744
+ # The basename of the program invoking the thor class.
745
+ #
746
+ def basename
747
+ File.basename($PROGRAM_NAME).split(" ").first
748
+ end
749
+
750
+ # SIGNATURE: Sets the baseclass. This is where the superclass lookup
751
+ # finishes.
752
+ def baseclass #:nodoc:
753
+ end
754
+
755
+ # SIGNATURE: Creates a new command if valid_command? is true. This method
756
+ # is called when a new method is added to the class.
757
+ def create_command(meth) #:nodoc:
758
+ end
759
+ alias_method :create_task, :create_command
760
+
761
+ # SIGNATURE: Defines behavior when the initialize method is added to the
762
+ # class.
763
+ def initialize_added #:nodoc:
764
+ end
765
+
766
+ # SIGNATURE: The hook invoked by start.
767
+ def dispatch(command, given_args, given_opts, config) #:nodoc:
768
+ raise NotImplementedError
769
+ end
770
+
771
+ end # self << class
772
+ end # module Base
773
+ end # class Thor