quality_extensions 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (100) hide show
  1. data/Readme +54 -0
  2. data/lib/qualitysmith_extensions/all.rb +4 -0
  3. data/lib/qualitysmith_extensions/array/all.rb +2 -0
  4. data/lib/qualitysmith_extensions/array/average.rb +44 -0
  5. data/lib/qualitysmith_extensions/array/classify.rb +97 -0
  6. data/lib/qualitysmith_extensions/array/expand_ranges.rb +52 -0
  7. data/lib/qualitysmith_extensions/array/group_by.rb +134 -0
  8. data/lib/qualitysmith_extensions/array/sequence.rb +66 -0
  9. data/lib/qualitysmith_extensions/array/shell_escape.rb +36 -0
  10. data/lib/qualitysmith_extensions/array/to_a_recursive.rb +41 -0
  11. data/lib/qualitysmith_extensions/array/to_query_string.rb +96 -0
  12. data/lib/qualitysmith_extensions/collection_extensions_for_cgi.rb +2 -0
  13. data/lib/qualitysmith_extensions/colored/toggleability.rb +62 -0
  14. data/lib/qualitysmith_extensions/console/command.facets.1.8.51.rb +749 -0
  15. data/lib/qualitysmith_extensions/console/command.facets.1.8.54.rb +748 -0
  16. data/lib/qualitysmith_extensions/console/command.rb +944 -0
  17. data/lib/qualitysmith_extensions/date/all.rb +2 -0
  18. data/lib/qualitysmith_extensions/date/deprecated.rb +40 -0
  19. data/lib/qualitysmith_extensions/date/iso8601.rb +31 -0
  20. data/lib/qualitysmith_extensions/date/month_ranges.rb +122 -0
  21. data/lib/qualitysmith_extensions/dir/each_child.rb +58 -0
  22. data/lib/qualitysmith_extensions/enumerable/enum.rb +69 -0
  23. data/lib/qualitysmith_extensions/enumerable/select_until.rb +4 -0
  24. data/lib/qualitysmith_extensions/enumerable/select_while.rb +109 -0
  25. data/lib/qualitysmith_extensions/exception/inspect_with_backtrace.rb +65 -0
  26. data/lib/qualitysmith_extensions/file/exact_match_regexp.rb +34 -0
  27. data/lib/qualitysmith_extensions/file_test/binary_file.rb +110 -0
  28. data/lib/qualitysmith_extensions/find/select.rb +68 -0
  29. data/lib/qualitysmith_extensions/global_variable_set.rb +153 -0
  30. data/lib/qualitysmith_extensions/hash/all.rb +2 -0
  31. data/lib/qualitysmith_extensions/hash/to_date.rb +34 -0
  32. data/lib/qualitysmith_extensions/hash/to_query_string.rb +121 -0
  33. data/lib/qualitysmith_extensions/kernel/all.rb +2 -0
  34. data/lib/qualitysmith_extensions/kernel/autoreload.rb +128 -0
  35. data/lib/qualitysmith_extensions/kernel/backtrace.rb +71 -0
  36. data/lib/qualitysmith_extensions/kernel/capture_output.rb +115 -0
  37. data/lib/qualitysmith_extensions/kernel/die.rb +49 -0
  38. data/lib/qualitysmith_extensions/kernel/example_printer.rb +81 -0
  39. data/lib/qualitysmith_extensions/kernel/filter_output.rb +108 -0
  40. data/lib/qualitysmith_extensions/kernel/remove_const.rb +178 -0
  41. data/lib/qualitysmith_extensions/kernel/remove_module.rb +127 -0
  42. data/lib/qualitysmith_extensions/kernel/require_all.rb +186 -0
  43. data/lib/qualitysmith_extensions/kernel/require_local_all.rb +4 -0
  44. data/lib/qualitysmith_extensions/kernel/require_once.rb +18 -0
  45. data/lib/qualitysmith_extensions/kernel/simulate_input.rb +52 -0
  46. data/lib/qualitysmith_extensions/kernel/trap_chain.rb +61 -0
  47. data/lib/qualitysmith_extensions/kernel/windows_platform.rb +46 -0
  48. data/lib/qualitysmith_extensions/module/alias_method.rb +6 -0
  49. data/lib/qualitysmith_extensions/module/alias_method_chain.rb +165 -0
  50. data/lib/qualitysmith_extensions/module/ancestry_of_instance_method.rb +43 -0
  51. data/lib/qualitysmith_extensions/module/attribute_accessors.rb +49 -0
  52. data/lib/qualitysmith_extensions/module/basename.rb +76 -0
  53. data/lib/qualitysmith_extensions/module/bool_attr_accessor.rb +497 -0
  54. data/lib/qualitysmith_extensions/module/class_methods.rb +87 -0
  55. data/lib/qualitysmith_extensions/module/create.rb +315 -0
  56. data/lib/qualitysmith_extensions/module/create_setter.rb +9 -0
  57. data/lib/qualitysmith_extensions/module/dirname.rb +4 -0
  58. data/lib/qualitysmith_extensions/module/guard_method.rb +312 -0
  59. data/lib/qualitysmith_extensions/module/includable_once.rb +10 -0
  60. data/lib/qualitysmith_extensions/module/join.rb +66 -0
  61. data/lib/qualitysmith_extensions/module/malias_method_chain.rb +92 -0
  62. data/lib/qualitysmith_extensions/module/module_methods.rb +4 -0
  63. data/lib/qualitysmith_extensions/module/namespace.rb +112 -0
  64. data/lib/qualitysmith_extensions/module/parents.rb +61 -0
  65. data/lib/qualitysmith_extensions/module/remove_const.rb +117 -0
  66. data/lib/qualitysmith_extensions/module/split.rb +55 -0
  67. data/lib/qualitysmith_extensions/month.rb +66 -0
  68. data/lib/qualitysmith_extensions/mutex/if_available.rb +75 -0
  69. data/lib/qualitysmith_extensions/object/ancestry_of_method.rb +257 -0
  70. data/lib/qualitysmith_extensions/object/default.rb +69 -0
  71. data/lib/qualitysmith_extensions/object/if_else.rb +157 -0
  72. data/lib/qualitysmith_extensions/object/ignore_access.rb +84 -0
  73. data/lib/qualitysmith_extensions/object/mcall.rb +92 -0
  74. data/lib/qualitysmith_extensions/object/methods.rb +63 -0
  75. data/lib/qualitysmith_extensions/object/send_if.rb +151 -0
  76. data/lib/qualitysmith_extensions/object/send_if_not_nil.rb +35 -0
  77. data/lib/qualitysmith_extensions/object/singleton_send.rb +129 -0
  78. data/lib/qualitysmith_extensions/regexp/join.rb +111 -0
  79. data/lib/qualitysmith_extensions/string/all.rb +2 -0
  80. data/lib/qualitysmith_extensions/string/constantize.rb +4 -0
  81. data/lib/qualitysmith_extensions/string/digits_only.rb +27 -0
  82. data/lib/qualitysmith_extensions/string/each_char_with_index.rb +41 -0
  83. data/lib/qualitysmith_extensions/string/md5.rb +29 -0
  84. data/lib/qualitysmith_extensions/string/shell_escape.rb +43 -0
  85. data/lib/qualitysmith_extensions/string/to_underscored_label.rb +37 -0
  86. data/lib/qualitysmith_extensions/string/with_knowledge_of_color.rb +64 -0
  87. data/lib/qualitysmith_extensions/symbol/constantize.rb +69 -0
  88. data/lib/qualitysmith_extensions/symbol/match.rb +157 -0
  89. data/lib/qualitysmith_extensions/template.rb +33 -0
  90. data/lib/qualitysmith_extensions/test/all.rb +2 -0
  91. data/lib/qualitysmith_extensions/test/assert_anything.rb +93 -0
  92. data/lib/qualitysmith_extensions/test/assert_changed.rb +66 -0
  93. data/lib/qualitysmith_extensions/test/assert_exception.rb +66 -0
  94. data/lib/qualitysmith_extensions/test/assert_includes.rb +36 -0
  95. data/lib/qualitysmith_extensions/test/assert_user_error.rb +37 -0
  96. data/lib/qualitysmith_extensions/test/difference_highlighting.rb +323 -0
  97. data/lib/qualitysmith_extensions/time/all.rb +2 -0
  98. data/lib/qualitysmith_extensions/time/deprecated.rb +31 -0
  99. data/test/all.rb +16 -0
  100. metadata +148 -0
@@ -0,0 +1,944 @@
1
+ # = command.rb
2
+ #
3
+ # == Copyright (c) 2005 Thomas Sawyer
4
+ #
5
+ # Ruby License
6
+ #
7
+ # This module is free software. You may use, modify, and/or
8
+ # redistribute this software under the same terms as Ruby.
9
+ #
10
+ # This program is distributed in the hope that it will be
11
+ # useful, but WITHOUT ANY WARRANTY; without even the implied
12
+ # warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
13
+ # PURPOSE.
14
+ #
15
+ # == Author(s)
16
+ #
17
+ # CREDIT Thomas Sawyer
18
+ # CREDIT Tyler Rick
19
+ #
20
+ # == Developer Notes
21
+ #
22
+ # TODO Add help/documentation features.
23
+ #
24
+ # TODO Move to console/command.rb, but I'm not sure yet if
25
+ # adding subdirectories to more/ is a good idea.
26
+ #
27
+
28
+ # Author:: Thomas Sawyer, Tyler Rick
29
+ # Copyright:: Copyright (c) 2005-2007
30
+ # License:: Ruby License
31
+
32
+ require 'shellwords'
33
+ require 'rubygems'
34
+ require 'facet/string/modulize'
35
+ require 'escape' # http://www.a-k-r.org/escape/
36
+
37
+ # TODO Test
38
+ class String
39
+ def option_demethodize
40
+ self.sub('__','--').gsub('_','-')
41
+ end
42
+ def option_methodize
43
+ self.gsub('-','_')
44
+ end
45
+ end
46
+
47
+ # = Console
48
+ #
49
+ # Console namespace for use by tools specifically designed
50
+ # for command line interfaces.
51
+
52
+ module Console ; end
53
+
54
+ # = Console Command
55
+ #
56
+ # Console::Command provides a clean and easy way
57
+ # to create a command line interface for your program.
58
+ # The unique technique utlizes a Commandline to Object
59
+ # Mapping (COM) to make it quick and easy.
60
+ #
61
+ # == Synopsis
62
+ #
63
+ # Let's make an executable called 'mycmd'.
64
+ #
65
+ # #!/usr/bin/env ruby
66
+ #
67
+ # require 'facets'
68
+ # require 'command'
69
+ #
70
+ # MyCmd << Console::Command
71
+ #
72
+ # def _v
73
+ # $VERBOSE = true
74
+ # end
75
+ #
76
+ # def jump
77
+ # if $VERBOSE
78
+ # puts "JUMP! JUMP! JUMP!"
79
+ # else
80
+ # puts "Jump"
81
+ # end
82
+ # end
83
+ #
84
+ # end
85
+ #
86
+ # MyCmd.execute
87
+ #
88
+ # Then on the command line:
89
+ #
90
+ # % mycmd jump
91
+ # Jump
92
+ #
93
+ # % mycmd -v jump
94
+ # JUMP! JUMP! JUMP!
95
+ #
96
+ # == Subcommands
97
+ #
98
+ # Commands can take subcommand and suboptions. To do this
99
+ # simply add a module to your class with the same name
100
+ # as the subcommand, in which the suboption methods are defined.
101
+ #
102
+ # MyCmd << Console::Command
103
+ #
104
+ # def initialize
105
+ # @height = 1
106
+ # end
107
+ #
108
+ # def _v
109
+ # $VERBOSE = true
110
+ # end
111
+ #
112
+ # def jump
113
+ # if $VERBOSE
114
+ # puts "JUMP!" * @height
115
+ # else
116
+ # puts "Jump" * @height
117
+ # end
118
+ # end
119
+ #
120
+ # module Jump
121
+ # def __height(h)
122
+ # @height = h.to_i
123
+ # end
124
+ # end
125
+ #
126
+ # end
127
+ #
128
+ # MyCmd.start
129
+ #
130
+ # Then on the command line:
131
+ #
132
+ # % mycmd jump -h 2
133
+ # Jump Jump
134
+ #
135
+ # % mycmd -v jump -h 3
136
+ # JUMP! JUMP! JUMP!
137
+ #
138
+ # Another thing to notice about this example is that #start is an alias
139
+ # for #execute.
140
+ #
141
+ # == Missing Subcommands
142
+ #
143
+ # You can use #method_missing to catch missing subcommand calls.
144
+ #
145
+ # == Aliasing Subcommands
146
+ #
147
+ # You can use alias_subcommand to create an alias for a subcommand. For
148
+ # example:
149
+ #
150
+ # alias_subcommand :st, :status
151
+ #
152
+ # When the user attempts to call the 'st' subcommand, the Status subcommand
153
+ # module will be mixed in and the status subcommand method called.
154
+ #
155
+ # == Main and Default
156
+ #
157
+ # If your command does not take subcommands then simply define
158
+ # a #main method to dispatch action. All options will be treated globablly
159
+ # in this case and any remaining comman-line arguments will be passed
160
+ # to #main.
161
+ #
162
+ # If on the other hand your command does take subcommands but none is given,
163
+ # the #default method will be called, if defined. If not defined
164
+ # an error will be raised (but only reported if $DEBUG is true).
165
+ #
166
+ # == Global Options
167
+ #
168
+ # You can define <i>global options</i> which are options that will be
169
+ # processed no matter where they occur in the command line. In the above
170
+ # examples only the options occuring before the subcommand are processed
171
+ # globally. Anything occuring after the subcommand belonds strictly to
172
+ # the subcommand. For instance, if we had added the following to the above
173
+ # example:
174
+ #
175
+ # global_option :_v
176
+ #
177
+ # Then -v could appear anywhere in the command line, even on the end,
178
+ # and still work as expected.
179
+ #
180
+ # % mycmd jump -h 3 -v
181
+ #
182
+ # == Missing Options
183
+ #
184
+ # You can use #option_missing to catch any options that are not explicility
185
+ # defined.
186
+ #
187
+ # The method signature should look like:
188
+ #
189
+ # option_missing(option_name, args)
190
+ #
191
+ # Example:
192
+ # def option_missing(option_name, args)
193
+ # p args if $debug
194
+ # case option_name
195
+ # when 'p'
196
+ # @a = args[0].to_i
197
+ # @b = args[1].to_i
198
+ # 2
199
+ # else
200
+ # raise Console::Command::UnknownOptionError.new(option_name, args)
201
+ # end
202
+ # end
203
+ #
204
+ # Its return value should be the effective "arity" of that options -- that is,
205
+ # how many arguments it consumed ("-p a b", for example, would consume 2 args:
206
+ # "a" and "b"). An arity of 1 is assumed if nil or false is returned.
207
+ #
208
+ # Be aware that when using subcommand modules, the same option_missing
209
+ # method will catch missing options for global options and subcommand
210
+ # options too unless an option_missing method is also defined in the
211
+ # subcommand module.
212
+ #
213
+ #--
214
+ #
215
+ # == Help Documentation
216
+ #
217
+ # You can also add help information quite easily. If the following code
218
+ # is saved as 'foo' for instance.
219
+ #
220
+ # MyCmd << Console::Command
221
+ #
222
+ # help "Dispays the word JUMP!"
223
+ #
224
+ # def jump
225
+ # if $VERBOSE
226
+ # puts "JUMP! JUMP! JUMP!"
227
+ # else
228
+ # puts "Jump"
229
+ # end
230
+ # end
231
+ #
232
+ # end
233
+ #
234
+ # MyCmd.execute
235
+ #
236
+ # then by running 'foo help' on the command line, standard help information
237
+ # will be displayed.
238
+ #
239
+ # foo
240
+ #
241
+ # jump Displays the word JUMP!
242
+ #
243
+ #++
244
+
245
+ class Console::Command
246
+
247
+ class << self
248
+ # Starts the command execution.
249
+ def execute( *args )
250
+ cmd = new()
251
+ cmd.instance_variable_set("@global_options", global_options)
252
+ cmd.instance_variable_set("@subcommand_aliases", @subcommand_aliases || {})
253
+ cmd.execute( *args )
254
+ end
255
+
256
+ # Alias for #execute.
257
+ alias_method :start, :execute
258
+
259
+ # Change the option mode.
260
+ def global_option( *names )
261
+ names.each{ |name| global_options << name.to_sym }
262
+ end
263
+
264
+ def global_options
265
+ @global_options ||= []
266
+ end
267
+
268
+ # This is to be called from your subcommand module to specify which options should simply be "passed on" to some wrapped command that you will later call.
269
+ # Options that are collected by the option methods that this generates will be stored in @passthrough_options (so remember to append that array to your wrapped command!).
270
+ #
271
+ # module Status
272
+ # Console::Command.pass_through({
273
+ # [:_q, :__quiet] => 0,
274
+ # [:_N, :__non_recursive] => 0,
275
+ # [:__no_ignore] => 0,
276
+ # }, self)
277
+ # end
278
+ #
279
+ # Development notes:
280
+ # * Currently requires you to pass the subcommand module's "self" to this method. I didn't know of a better way to cause it to create the instance methods in *that* module rather than here in Console::Command.
281
+ # * Possible alternatives:
282
+ # * Binding.of_caller() (http://facets.rubyforge.org/src/doc/rdoc/core/classes/Binding.html) -- wary of using it if it depends on Continuations, which I understand are deprecated
283
+ # * copy the pass_through class method to each subcommand module so that calls will be in the module's context...
284
+ def pass_through(options, mod)
285
+ options.each do |method_names, arity|
286
+ method_names.each do |method_name|
287
+ if method_name == :_u
288
+ #puts "Defining method #{method_name}(with arity #{arity}) in #{mod.name}"
289
+ #puts "#{mod.name} has #{(mod.methods - Object.methods).inspect}"
290
+ end
291
+ option_name = method_name.to_s.option_demethodize
292
+ mod.send(:define_method, method_name.to_sym) do |*args|
293
+ @passthrough_options << option_name
294
+ args_for_current_option = Escape.shell_command(args.slice(0, arity))
295
+ @passthrough_options << args_for_current_option unless args_for_current_option == ''
296
+ #p args_for_current_option
297
+ #puts "in #{method_name}: Passing through #{arity} options: #{@passthrough_options.inspect}" #(why does @passthrough_options show up as nil? even when later on it's *not* nil...)
298
+ arity
299
+ end
300
+
301
+ # mod.instance_eval %Q{
302
+ # def #{method_name}(*args)
303
+ # @passthrough_options << '#{option_name}'
304
+ # args_for_current_option = Escape.shell_command(args.slice(0, #{arity}))
305
+ # @passthrough_options << args_for_current_option unless args_for_current_option == ''
306
+ # #p args_for_current_option
307
+ # #puts "in #{method_name}: Passing through #{arity} options: #{@passthrough_options.inspect}" #(why does @passthrough_options show up as nil? even when later on it's *not* nil...)
308
+ # #{arity}
309
+ # end
310
+ # }
311
+
312
+ end
313
+ end
314
+ end
315
+
316
+ def alias_subcommand(hash)
317
+ (@subcommand_aliases ||= {}).merge! hash
318
+ end
319
+
320
+ end # End of class methods
321
+
322
+
323
+ #-----------------------------------------------------------------------------------------------------------------------------
324
+
325
+ # Do not let this pass through to
326
+ # any included module.
327
+ # What do you mean? --Tyler
328
+
329
+ def initialize(global_options=[])
330
+ @global_options = global_options
331
+ end
332
+
333
+ # Execute the command.
334
+
335
+ def execute( line=nil )
336
+ begin
337
+ case line
338
+ when String
339
+ arguments = Shellwords.shellwords(line)
340
+ when Array
341
+ arguments = line
342
+ else
343
+ arguments = ARGV
344
+ end
345
+
346
+ # Duplicate arguments to work on them in-place.
347
+ argv = arguments.dup
348
+
349
+ # Split single letter option groupings into separate options.
350
+ # ie. -xyz => -x -y -z
351
+ argv = argv.collect { |arg|
352
+ if md = /^-(\w{2,})/.match( arg )
353
+ md[1].split(//).collect { |c| "-#{c}" }
354
+ else
355
+ arg
356
+ end
357
+ }.flatten
358
+
359
+ # Process global options
360
+ global_options.each do |name|
361
+ o = name.to_s.option_demethodize
362
+ m = method(name)
363
+ c = m.arity
364
+ while i = argv.index(o)
365
+ args = argv.slice!(i,c+1)
366
+ args.shift
367
+ m.call(*args)
368
+ end
369
+ end
370
+
371
+ # Does this command take subcommands?
372
+ takes_subcommands = !respond_to?(:main)
373
+
374
+ # Process primary options
375
+ argv = execute_options( argv, takes_subcommands )
376
+
377
+ # If this command doesn't take subcommands, then the remaining arguments are arguments for main().
378
+ return send(:main, *argv) unless takes_subcommands
379
+
380
+ # What to do if there is nothing else?
381
+ if argv.empty?
382
+ if respond_to?(:default)
383
+ return __send__(:default)
384
+ else
385
+ $stderr << "Nothing to do."
386
+ puts '' # :fix: This seems to be necessary or else I don't see the $stderr output at all! --Tyler
387
+ return
388
+ end
389
+ end
390
+
391
+ # Remaining arguments are subcommand and suboptions.
392
+
393
+ @subcommand = argv.shift.gsub('-','_')
394
+ @subcommand = (subcommand_aliases[@subcommand.to_sym] || @subcommand).to_s
395
+ puts "@subcommand = #{@subcommand}" if $debug
396
+
397
+ # Extend subcommand option module
398
+ #subconst = subcommand.gsub(/\W/,'_').capitalize
399
+ subconst = @subcommand.modulize
400
+ #p self.class.constants if $debug
401
+ if self.class.const_defined?(subconst)
402
+ puts "Extending self (#{self.class}) with subcommand module #{subconst}" if $debug
403
+ submod = self.class.const_get(subconst)
404
+ #puts "... which has these **module** methods (should be instance methods): #{(submod.methods - submod.instance_methods - Object.methods).sort.inspect}"
405
+ self.extend submod
406
+ #puts "... and now self has: #{(self.methods - Object.methods).sort.inspect}"
407
+ end
408
+
409
+ # Is the subcommand defined?
410
+ # This is a little tricky. The method has to be defined by a *subclass*.
411
+ @subcommand_is_defined = self.respond_to?( @subcommand ) and
412
+ !Console::Command.public_instance_methods.include?( @subcommand.to_s )
413
+
414
+ # The rest of the args will be interpreted as options for this particular subcommand options.
415
+ argv = execute_options( argv, false )
416
+
417
+ # Actually call the subcommand (or method_missing if the subcommand method isn't defined)
418
+ if @subcommand_is_defined
419
+ puts "Calling #{@subcommand}(#{argv.inspect})" if $debug
420
+ __send__(@subcommand, *argv)
421
+ else
422
+ #begin
423
+ puts "Calling method_missing with #{@subcommand}, #{argv.inspect}" if $debug
424
+ method_missing(@subcommand, *argv)
425
+ #rescue NoMethodError => e
426
+ #if self.private_methods.include?( "no_command_error" )
427
+ # no_command_error( *args )
428
+ #else
429
+ # $stderr << "Non-applicable command -- #{argv.join(' ')}\n"
430
+ # exit -1
431
+ #end
432
+ #end
433
+ end
434
+
435
+ rescue UnknownOptionError => exception
436
+ $stderr << exception.message << "\n"
437
+ exit -1
438
+ end
439
+
440
+ # rescue => err
441
+ # if $DEBUG
442
+ # raise err
443
+ # else
444
+ # msg = err.message.chomp('.') + '.'
445
+ # msg[0,1] = msg[0,1].capitalize
446
+ # msg << " (#{err.class})" if $VERBOSE
447
+ # $stderr << msg
448
+ # end
449
+ end # def execute
450
+
451
+ private
452
+
453
+ #
454
+
455
+ attr_accessor :global_options
456
+ attr_accessor :subcommand_aliases
457
+
458
+ def subcommand_aliases_list(main_subcommand_name)
459
+ # If subcommand_aliases returns {:edit_ext=>:edit_externals, :ee=>:edit_externals}, then
460
+ # subcommand_aliases_list(:edit_externals) ought to return [:edit_ext, :ee]
461
+ subcommand_aliases.select {|k, v| v == main_subcommand_name}.
462
+ map {|k, v| k if v == main_subcommand_name}
463
+ end
464
+
465
+ #
466
+
467
+ def execute_options( argv, break_when_hit_subcommand = false )
468
+ argv = argv.dup
469
+ args_to_return = []
470
+ until argv.empty?
471
+ arg = argv.first
472
+ if arg[0,1] == '-'
473
+ puts "'#{arg}' -- is an option" if $debug
474
+ method_name = arg.option_methodize
475
+ #puts "Methods: #{(methods - Object.methods).inspect}" if $debug
476
+ if respond_to?(method_name)
477
+ m = method(method_name)
478
+ puts "Method named #{method_name} exists and has an arity of #{m.arity}" if $debug
479
+ if m.arity == -1
480
+ # Implemented the same as for option_missing, except that we don't pass the *name* of the option
481
+ arity = m.call(*argv[1..-1]) || 1
482
+ puts "#{method_name} returned an arity of #{arity}" if $debug
483
+ if !arity.is_a?(Fixnum)
484
+ raise "Expected #{method_name} to return a valid arity, but it didn't"
485
+ end
486
+ #puts "argv before: #{argv.inspect}"
487
+ argv.shift # Get rid of the *name* of the option
488
+ argv.slice!(0, arity) # Then discard as many arguments as that option claimed it used up
489
+ #puts "argv after: #{argv.inspect}"
490
+ else
491
+ args_for_current_option = argv.slice!(0, m.arity+1) # The +1 is so that we also remove the option name from argv
492
+ args_for_current_option.shift # Remove the option name from args_for_current_option as well
493
+ m.call(*args_for_current_option)
494
+ end
495
+ elsif respond_to?(:option_missing)
496
+ puts "No method named #{method_name} exists -- calling option_missing(#{arg}, #{argv[1..-1].inspect})" if $debug
497
+ # Old: arity = option_missing(arg.gsub(/^[-]+/,''), argv[1..-1]) || 1
498
+ arity = option_missing(arg, argv[1..-1]) || 1
499
+ argv.shift # Get rid of the *name* of the option
500
+ argv.slice!(0, arity) # Then discard as many arguments as that option claimed it used up
501
+ else
502
+ raise UnknownOptionError.new(arg)
503
+ end
504
+ else
505
+ puts "'#{arg}' -- not an option. Adding to args_to_return..." if $debug
506
+ if break_when_hit_subcommand
507
+ # If we are parsing options for the *main* command and we are allowing subcommands, then we want to stop as soon as we
508
+ # get to the first non-option, because that non-option will be the name of our subcommand and all options that follow
509
+ # should be parsed later when we handle the subcommand (after we've extended the subcommand module, for instance).
510
+ args_to_return = argv
511
+ break
512
+ else
513
+ args_to_return << argv.shift
514
+ end
515
+ end
516
+ end
517
+ puts "Returning #{args_to_return.inspect}" if $debug
518
+ return args_to_return
519
+ end
520
+
521
+ public
522
+
523
+ =begin
524
+ # We include a module here so you can define your own help
525
+ # command and call #super to utilize this one.
526
+
527
+ module Help
528
+
529
+ def help
530
+ opts = help_options
531
+ s = ""
532
+ s << "#{File.basename($0)}\n\n"
533
+ unless opts.empty?
534
+ s << "OPTIONS\n"
535
+ s << help_options
536
+ s << "\n"
537
+ end
538
+ s << "COMMANDS\n"
539
+ s << help_commands
540
+ puts s
541
+ end
542
+
543
+ private
544
+
545
+ def help_commands
546
+ help = self.class.help
547
+ bufs = help.keys.collect{ |a| a.to_s.size }.max + 3
548
+ lines = []
549
+ help.each { |cmd, str|
550
+ cmd = cmd.to_s
551
+ if cmd !~ /^_/
552
+ lines << " " + cmd + (" " * (bufs - cmd.size)) + str
553
+ end
554
+ }
555
+ lines.join("\n")
556
+ end
557
+
558
+ def help_options
559
+ help = self.class.help
560
+ bufs = help.keys.collect{ |a| a.to_s.size }.max + 3
561
+ lines = []
562
+ help.each { |cmd, str|
563
+ cmd = cmd.to_s
564
+ if cmd =~ /^_/
565
+ lines << " " + cmd.gsub(/_/,'-') + (" " * (bufs - cmd.size)) + str
566
+ end
567
+ }
568
+ lines.join("\n")
569
+ end
570
+
571
+ module ClassMethods
572
+
573
+ def help( str=nil )
574
+ return (@help ||= {}) unless str
575
+ @current_help = str
576
+ end
577
+
578
+ def method_added( meth )
579
+ if @current_help
580
+ @help ||= {}
581
+ @help[meth] = @current_help
582
+ @current_help = nil
583
+ end
584
+ end
585
+
586
+ end
587
+
588
+ end
589
+
590
+ include Help
591
+ extend Help::ClassMethods
592
+ =end
593
+
594
+ class UnknownOptionError < StandardError
595
+ def initialize(option_name)
596
+ @option_name = option_name
597
+ end
598
+ def message
599
+ "Unknown option '#{@option_name}'."
600
+ end
601
+ end
602
+ end
603
+
604
+
605
+
606
+ # _____ _
607
+ # |_ _|__ ___| |_
608
+ # | |/ _ \/ __| __|
609
+ # | | __/\__ \ |_
610
+ # |_|\___||___/\__|
611
+ #
612
+
613
+ =begin test
614
+
615
+ require 'test/unit'
616
+ require 'stringio'
617
+ require 'qualitysmith_extensions/kernel/capture_output'
618
+
619
+ class TestCommand < Test::Unit::TestCase
620
+ def setup
621
+ $output = nil
622
+ end
623
+
624
+ #-----------------------------------------------------------------------------------------------------------------------------
625
+
626
+ class SimpleCommand < Console::Command
627
+ def __here ; @here = true ; end
628
+
629
+ def main(*args)
630
+ $output = [@here] | args
631
+ end
632
+ end
633
+
634
+ def test_SimpleCommand
635
+ SimpleCommand.execute( '--here file1 file2' )
636
+ assert_equal( [true, 'file1', 'file2'], $output )
637
+ end
638
+
639
+ #-----------------------------------------------------------------------------------------------------------------------------
640
+
641
+ class MethodMissingSubcommand < Console::Command
642
+ def __here ; @here = true ; end
643
+
644
+ def method_missing(subcommand, *args)
645
+ $output = [@here, subcommand] | args
646
+ end
647
+ end
648
+
649
+ def test_MethodMissingSubcommand
650
+ MethodMissingSubcommand.execute( '--here go file1' )
651
+ assert_equal( [true, 'go', 'file1'], $output )
652
+ end
653
+
654
+ #-----------------------------------------------------------------------------------------------------------------------------
655
+
656
+ class SimpleSubcommand < Console::Command
657
+ def __here ; @here = true ; end
658
+
659
+ # subcommand
660
+
661
+ module Go
662
+ def _p(n)
663
+ @p = n.to_i
664
+ end
665
+ end
666
+
667
+ def go ; $output = [@here, @p] ; end
668
+ end
669
+
670
+ def test_SimpleSubcommand
671
+ SimpleSubcommand.execute( '--here go -p 1' )
672
+ assert_equal( [true, 1], $output )
673
+ end
674
+
675
+ #-----------------------------------------------------------------------------------------------------------------------------
676
+
677
+ # Global options can be anywhere, right? Even after subcommands? Let's find out.
678
+ class GlobalOptionsAfterSubcommand < Console::Command
679
+ def _x ; @x = true ; end
680
+ global_option :_x
681
+
682
+ def go ; $output = [@x, @p] ; end
683
+
684
+ module Go
685
+ def _p(n)
686
+ @p = n.to_i
687
+ end
688
+ end
689
+ end
690
+
691
+ def test_GlobalOptionsAfterSubcommand
692
+ GlobalOptionsAfterSubcommand.execute( 'go -x -p 1' )
693
+ assert_equal( [true, 1], $output )
694
+
695
+ GlobalOptionsAfterSubcommand.execute( 'go -p 1 -x' )
696
+ assert_equal( [true, 1], $output )
697
+ end
698
+
699
+ #-----------------------------------------------------------------------------------------------------------------------------
700
+
701
+ class GivingUnrecognizedOptions < Console::Command
702
+ def _x ; @x = true ; end
703
+ def go ; $output = [@x, @p] ; end
704
+ end
705
+
706
+ def test_GivingUnrecognizedOptions
707
+ stderr = capture_output $stderr do
708
+ assert_raise(SystemExit) do
709
+ GivingUnrecognizedOptions.execute( '--an-option-that-wont-be-recognized -x go' )
710
+ end
711
+ end
712
+ assert_equal "Unknown option '--an-option-that-wont-be-recognized'.\n", stderr
713
+ assert_equal( nil, $output )
714
+ end
715
+
716
+ #-----------------------------------------------------------------------------------------------------------------------------
717
+
718
+ class PassingMultipleSingleCharOptionsAsOneOption < Console::Command
719
+ def _x ; @x = true ; end
720
+ def _y ; @y = true ; end
721
+ def _z(n) ; @z = n ; end
722
+
723
+ global_option :_x
724
+
725
+ def go ; $output = [@x, @y, @z, @p] ; end
726
+
727
+ module Go
728
+ def _p(n)
729
+ @p = n.to_i
730
+ end
731
+ end
732
+ end
733
+
734
+ def test_PassingMultipleSingleCharOptionsAsOneOption
735
+ PassingMultipleSingleCharOptionsAsOneOption.execute( '-xy -z HERE go -p 1' )
736
+ assert_equal( [true, true, 'HERE', 1], $output )
737
+ end
738
+
739
+ #-----------------------------------------------------------------------------------------------------------------------------
740
+
741
+ class OptionUsingEquals < Console::Command
742
+ module Go
743
+ def __mode(mode) ; @mode = mode ; end
744
+ end
745
+ def go ; $output = [@mode] ; end
746
+ end
747
+
748
+ def test_OptionUsingEquals
749
+ OptionUsingEquals.execute( 'go --mode smart' )
750
+ assert_equal( ['smart'], $output )
751
+
752
+ # I would expect this to work too, but currently it doesn't.
753
+ #assert_nothing_raised { OptionUsingEquals.execute( 'go --mode=smart' ) }
754
+ #assert_equal( ['smart'], $output )
755
+ end
756
+
757
+ #-----------------------------------------------------------------------------------------------------------------------------
758
+
759
+ class SubcommandThatTakesArgs < Console::Command
760
+ def go(arg1, *args) ; $output = [arg1] | args ; end
761
+ end
762
+
763
+ def test_SubcommandThatTakesArgs
764
+ SubcommandThatTakesArgs.execute( 'go file1 file2 file3' )
765
+ assert_equal( ['file1', 'file2', 'file3'], $output )
766
+ end
767
+
768
+ #-----------------------------------------------------------------------------------------------------------------------------
769
+
770
+ class With2OptionalArgs < Console::Command
771
+ module Go
772
+ def _p(n)
773
+ @p = n.to_i
774
+ end
775
+ end
776
+
777
+ def go(optional1 = nil, optional2 = nil) ; $output = [@p, optional1, optional2 ] ; end
778
+ end
779
+
780
+ def test_With2OptionalArgs
781
+ With2OptionalArgs.execute( 'go -p 1 to' )
782
+ assert_equal( [1, 'to', nil], $output )
783
+ end
784
+
785
+ #-----------------------------------------------------------------------------------------------------------------------------
786
+
787
+ class VariableArgs < Console::Command
788
+ module Go
789
+ def _p(n)
790
+ @p = n.to_i
791
+ end
792
+ end
793
+
794
+ def go(*args) ; $output = [@p] | args ; end
795
+ end
796
+
797
+ def test_VariableArgs
798
+ VariableArgs.execute( 'go -p 1 to bed' )
799
+ assert_equal( [1, 'to', 'bed'], $output )
800
+ end
801
+
802
+ #-----------------------------------------------------------------------------------------------------------------------------
803
+
804
+ class OptionMissing < Console::Command
805
+ module Go
806
+ def option_missing(option_name, args)
807
+ p args if $debug
808
+ case option_name
809
+ when '-p'
810
+ @p = args[0].to_i
811
+ 1
812
+ else
813
+ raise Console::Command::UnknownOptionError.new(option_name)
814
+ end
815
+ end
816
+ end
817
+
818
+ def go(*args) ; $output = [@p] | args ; end
819
+ end
820
+
821
+ def test_OptionMissing
822
+ OptionMissing.execute( 'go -p 1 to bed right now' )
823
+ assert_equal( [1, 'to', 'bed', 'right', 'now'], $output )
824
+ end
825
+
826
+ #-----------------------------------------------------------------------------------------------------------------------------
827
+
828
+ class OptionWith0Arity < Console::Command
829
+ module Go
830
+ def _p()
831
+ @p = 13
832
+ end
833
+ end
834
+
835
+ def go(arg1) ; $output = [@p, arg1] ; end
836
+ end
837
+
838
+ def test_OptionWith0Arity
839
+ OptionWith0Arity.execute( 'go -p away' )
840
+ assert_equal( [13, 'away'], $output )
841
+ end
842
+
843
+ #-----------------------------------------------------------------------------------------------------------------------------
844
+
845
+ class OptionWithVariableArity < Console::Command
846
+ module Go
847
+ def _p(*args)
848
+ #puts "_p received #{args.size} args: #{args.inspect}"
849
+ @p = args.reject {|arg| arg.to_i.to_s != arg } # TODO: this should be extracted to reusable String#is_numeric? if one doesn't exist
850
+ #puts "_p accepting #{@p.size} args: #{@p.inspect}"
851
+ @p.size
852
+ end
853
+ end
854
+
855
+ def go(arg1) ; $output = @p | [arg1] ; end
856
+ end
857
+
858
+ def test_OptionWithVariableArity
859
+ OptionWithVariableArity.execute( 'go -p 1 2 3 4 away' )
860
+ assert_equal( ['1', '2', '3', '4', 'away'], $output )
861
+ end
862
+
863
+ #-----------------------------------------------------------------------------------------------------------------------------
864
+
865
+ class OptionMissingArityOf2 < Console::Command
866
+ module Go
867
+ def option_missing(option_name, args)
868
+ case option_name
869
+ when '-p'
870
+ @p1 = args[0].to_i
871
+ @p2 = args[1].to_i
872
+ 2
873
+ when '-q'
874
+ @q = args[0].to_i
875
+ nil # Test default arity
876
+ else
877
+ raise Console::Command::UnknownOptionError.new.new(option_name, args)
878
+ end
879
+ end
880
+ end
881
+
882
+ def go(*args) ; $output = [@p1, @p2, @q] | args ; end
883
+ end
884
+
885
+ def test_OptionMissingArityOf2
886
+ OptionMissingArityOf2.execute( 'go -p 1 2 -q 3 to bed right now' )
887
+ assert_equal( [1, 2, 3, 'to', 'bed', 'right', 'now'], $output )
888
+ end
889
+
890
+ #-----------------------------------------------------------------------------------------------------------------------------
891
+
892
+ class OptionMissingReceivesShortAndLongOptionsDifferently < Console::Command
893
+ module Go
894
+ def option_missing(option_name, args)
895
+ case option_name
896
+ when '-s'
897
+ @s = "-s #{args[0]}"
898
+ 1
899
+ when '--long'
900
+ @long = "--long #{args[0]}"
901
+ 1
902
+ else
903
+ raise Console::Command::UnknownOptionError.new(option_name, args)
904
+ end
905
+ end
906
+ end
907
+
908
+ def go(*args) ; $output = [@s, @long] ; end
909
+ end
910
+
911
+ def test_OptionMissingReceivesShortAndLongOptionsDifferently
912
+ OptionMissingReceivesShortAndLongOptionsDifferently.execute( 'go -s 1 --long long' )
913
+ assert_equal( ['-s 1', '--long long'], $output )
914
+ end
915
+
916
+ #-----------------------------------------------------------------------------------------------------------------------------
917
+
918
+ class AliasSubcommand < Console::Command
919
+ alias_subcommand :g => :go
920
+ module Go
921
+ def option_missing(option_name, args)
922
+ case option_name
923
+ when '-s'
924
+ @s = "-s #{args[0]}"
925
+ 1
926
+ when '--long'
927
+ @long = "--long #{args[0]}"
928
+ 1
929
+ else
930
+ raise Console::Command::UnknownOptionError.new(option_name, args)
931
+ end
932
+ end
933
+ end
934
+
935
+ def go(*args) ; $output = [@s, @long] ; end
936
+ end
937
+
938
+ def test_AliasSubcommand
939
+ AliasSubcommand.execute( 'g -s 1 --long long' )
940
+ assert_equal( ['-s 1', '--long long'], $output )
941
+ end
942
+ end
943
+
944
+ =end