thor_dleavitt 0.18.1

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