atli 0.1.2

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.
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