quality_extensions 0.1.1

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