qualitysmith_extensions 0.0.3
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.
- data/Readme +72 -0
- data/lib/qualitysmith_extensions/all.rb +2 -0
- data/lib/qualitysmith_extensions/array/all.rb +2 -0
- data/lib/qualitysmith_extensions/array/average.rb +42 -0
- data/lib/qualitysmith_extensions/array/expand_ranges.rb +48 -0
- data/lib/qualitysmith_extensions/array/group_by.rb +112 -0
- data/lib/qualitysmith_extensions/array/sequence.rb +64 -0
- data/lib/qualitysmith_extensions/array/shell_escape.rb +34 -0
- data/lib/qualitysmith_extensions/array/to_a_recursive.rb +39 -0
- data/lib/qualitysmith_extensions/array/to_query_string.rb +94 -0
- data/lib/qualitysmith_extensions/collection_extensions_for_cgi.rb +2 -0
- data/lib/qualitysmith_extensions/console/command.facets.1.8.51.rb +749 -0
- data/lib/qualitysmith_extensions/console/command.rb +940 -0
- data/lib/qualitysmith_extensions/date/all.rb +2 -0
- data/lib/qualitysmith_extensions/date/deprecated.rb +38 -0
- data/lib/qualitysmith_extensions/date/iso8601.rb +29 -0
- data/lib/qualitysmith_extensions/date/month_ranges.rb +120 -0
- data/lib/qualitysmith_extensions/enumerable/enum.rb +72 -0
- data/lib/qualitysmith_extensions/file/exact_match_regexp.rb +32 -0
- data/lib/qualitysmith_extensions/file_test/binary_file.rb +108 -0
- data/lib/qualitysmith_extensions/filter_output.rb +107 -0
- data/lib/qualitysmith_extensions/global_variable_set.rb +151 -0
- data/lib/qualitysmith_extensions/hash/all.rb +2 -0
- data/lib/qualitysmith_extensions/hash/to_date.rb +32 -0
- data/lib/qualitysmith_extensions/hash/to_query_string.rb +119 -0
- data/lib/qualitysmith_extensions/kernel/all.rb +2 -0
- data/lib/qualitysmith_extensions/kernel/backtrace.rb +69 -0
- data/lib/qualitysmith_extensions/kernel/capture_output.rb +113 -0
- data/lib/qualitysmith_extensions/kernel/die.rb +34 -0
- data/lib/qualitysmith_extensions/kernel/require_all.rb +118 -0
- data/lib/qualitysmith_extensions/kernel/require_once.rb +16 -0
- data/lib/qualitysmith_extensions/month.rb +62 -0
- data/lib/qualitysmith_extensions/object/singleton.rb +95 -0
- data/lib/qualitysmith_extensions/simulate_input.rb +51 -0
- data/lib/qualitysmith_extensions/string/all.rb +2 -0
- data/lib/qualitysmith_extensions/string/digits_only.rb +25 -0
- data/lib/qualitysmith_extensions/string/md5.rb +27 -0
- data/lib/qualitysmith_extensions/string/shell_escape.rb +41 -0
- data/lib/qualitysmith_extensions/string/to_underscored_label.rb +35 -0
- data/lib/qualitysmith_extensions/test/assert_changed.rb +64 -0
- data/lib/qualitysmith_extensions/test/assert_exception.rb +63 -0
- data/lib/qualitysmith_extensions/test/assert_includes.rb +34 -0
- data/lib/qualitysmith_extensions/test/assert_user_error.rb +34 -0
- data/lib/qualitysmith_extensions/time/all.rb +2 -0
- data/lib/qualitysmith_extensions/time/deprecated.rb +29 -0
- data/test/all.rb +16 -0
- metadata +94 -0
@@ -0,0 +1,940 @@
|
|
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('__','--').sub('_','-')
|
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
|
+
new(global_options, @subcommand_aliases || {}).execute( *args )
|
251
|
+
end
|
252
|
+
|
253
|
+
# Alias for #execute.
|
254
|
+
alias_method :start, :execute
|
255
|
+
|
256
|
+
# Change the option mode.
|
257
|
+
def global_option( *names )
|
258
|
+
names.each{ |name| global_options << name.to_sym }
|
259
|
+
end
|
260
|
+
|
261
|
+
def global_options
|
262
|
+
@global_options ||= []
|
263
|
+
end
|
264
|
+
|
265
|
+
# 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.
|
266
|
+
# 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!).
|
267
|
+
#
|
268
|
+
# module Status
|
269
|
+
# Console::Command.pass_through({
|
270
|
+
# [:_q, :__quiet] => 0,
|
271
|
+
# [:_N, :__non_recursive] => 0,
|
272
|
+
# [:__no_ignore] => 0,
|
273
|
+
# }, self)
|
274
|
+
# end
|
275
|
+
#
|
276
|
+
# Development notes:
|
277
|
+
# * 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.
|
278
|
+
# * Possible alternatives:
|
279
|
+
# * 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
|
280
|
+
# * copy the pass_through class method to each subcommand module so that calls will be in the module's context...
|
281
|
+
def pass_through(options, mod)
|
282
|
+
options.each do |method_names, arity|
|
283
|
+
method_names.each do |method_name|
|
284
|
+
if method_name == :_u
|
285
|
+
#puts "Defining method #{method_name}(with arity #{arity}) in #{mod.name}"
|
286
|
+
#puts "#{mod.name} has #{(mod.methods - Object.methods).inspect}"
|
287
|
+
end
|
288
|
+
option_name = method_name.to_s.option_demethodize
|
289
|
+
mod.send(:define_method, method_name.to_sym) do |*args|
|
290
|
+
@passthrough_options << option_name
|
291
|
+
args_for_current_option = Escape.shell_command(args.slice(0, arity))
|
292
|
+
@passthrough_options << args_for_current_option unless args_for_current_option == ''
|
293
|
+
#p args_for_current_option
|
294
|
+
#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...)
|
295
|
+
arity
|
296
|
+
end
|
297
|
+
|
298
|
+
|
299
|
+
|
300
|
+
# mod.instance_eval %Q{
|
301
|
+
# def #{method_name}(*args)
|
302
|
+
# @passthrough_options << '#{option_name}'
|
303
|
+
# args_for_current_option = Escape.shell_command(args.slice(0, #{arity}))
|
304
|
+
# @passthrough_options << args_for_current_option unless args_for_current_option == ''
|
305
|
+
# #p args_for_current_option
|
306
|
+
# #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...)
|
307
|
+
# #{arity}
|
308
|
+
# end
|
309
|
+
# }
|
310
|
+
|
311
|
+
end
|
312
|
+
end
|
313
|
+
end
|
314
|
+
|
315
|
+
def alias_subcommand(hash)
|
316
|
+
(@subcommand_aliases ||= {}).merge! hash
|
317
|
+
end
|
318
|
+
|
319
|
+
end
|
320
|
+
|
321
|
+
# Do not let this pass through to
|
322
|
+
# any included module.
|
323
|
+
|
324
|
+
def initialize(global_options, subcommand_aliases)
|
325
|
+
@global_options, @subcommand_aliases =
|
326
|
+
global_options, subcommand_aliases
|
327
|
+
end
|
328
|
+
|
329
|
+
# Execute the command.
|
330
|
+
|
331
|
+
def execute( line=nil )
|
332
|
+
begin
|
333
|
+
case line
|
334
|
+
when String
|
335
|
+
arguments = Shellwords.shellwords(line)
|
336
|
+
when Array
|
337
|
+
arguments = line
|
338
|
+
else
|
339
|
+
arguments = ARGV
|
340
|
+
end
|
341
|
+
|
342
|
+
# Duplicate arguments to work on them in-place.
|
343
|
+
argv = arguments.dup
|
344
|
+
|
345
|
+
# Split single letter option groupings into separate options.
|
346
|
+
# ie. -xyz => -x -y -z
|
347
|
+
argv = argv.collect { |arg|
|
348
|
+
if md = /^-(\w{2,})/.match( arg )
|
349
|
+
md[1].split(//).collect { |c| "-#{c}" }
|
350
|
+
else
|
351
|
+
arg
|
352
|
+
end
|
353
|
+
}.flatten
|
354
|
+
|
355
|
+
# Process global options
|
356
|
+
global_options.each do |name|
|
357
|
+
o = name.to_s.option_demethodize
|
358
|
+
m = method(name)
|
359
|
+
c = m.arity
|
360
|
+
while i = argv.index(o)
|
361
|
+
args = argv.slice!(i,c+1)
|
362
|
+
args.shift
|
363
|
+
m.call(*args)
|
364
|
+
end
|
365
|
+
end
|
366
|
+
|
367
|
+
# Does this command take subcommands?
|
368
|
+
takes_subcommands = !respond_to?(:main)
|
369
|
+
|
370
|
+
# Process primary options
|
371
|
+
argv = execute_options( argv, takes_subcommands )
|
372
|
+
|
373
|
+
# If this command doesn't take subcommands, then the remaining arguments are arguments for main().
|
374
|
+
return send(:main, *argv) unless takes_subcommands
|
375
|
+
|
376
|
+
# What to do if there is nothing else?
|
377
|
+
if argv.empty?
|
378
|
+
if respond_to?(:default)
|
379
|
+
return __send__(:default)
|
380
|
+
else
|
381
|
+
$stderr << "Nothing to do."
|
382
|
+
puts '' # :fix: This seems to be necessary or else I don't see the $stderr output at all! --Tyler
|
383
|
+
return
|
384
|
+
end
|
385
|
+
end
|
386
|
+
|
387
|
+
# Remaining arguments are subcommand and suboptions.
|
388
|
+
|
389
|
+
@subcommand = argv.shift.gsub('-','_')
|
390
|
+
@subcommand = (subcommand_aliases[@subcommand.to_sym] || @subcommand).to_s
|
391
|
+
puts "@subcommand = #{@subcommand}" if $debug
|
392
|
+
|
393
|
+
# Extend subcommand option module
|
394
|
+
#subconst = subcommand.gsub(/\W/,'_').capitalize
|
395
|
+
subconst = @subcommand.modulize
|
396
|
+
#p self.class.constants if $debug
|
397
|
+
if self.class.const_defined?(subconst)
|
398
|
+
puts "Extending self (#{self.class}) with subcommand module #{subconst}" if $debug
|
399
|
+
submod = self.class.const_get(subconst)
|
400
|
+
#puts "... which has these **module** methods (should be instance methods): #{(submod.methods - submod.instance_methods - Object.methods).sort.inspect}"
|
401
|
+
self.extend submod
|
402
|
+
#puts "... and now self has: #{(self.methods - Object.methods).sort.inspect}"
|
403
|
+
end
|
404
|
+
|
405
|
+
# Is the subcommand defined?
|
406
|
+
# This is a little tricky. The method has to be defined by a *subclass*.
|
407
|
+
@subcommand_is_defined = self.respond_to?( @subcommand ) and
|
408
|
+
!Console::Command.public_instance_methods.include?( @subcommand.to_s )
|
409
|
+
|
410
|
+
# The rest of the args will be interpreted as options for this particular subcommand options.
|
411
|
+
argv = execute_options( argv, false )
|
412
|
+
|
413
|
+
# Actually call the subcommand (or method_missing if the subcommand method isn't defined)
|
414
|
+
if @subcommand_is_defined
|
415
|
+
puts "Calling #{@subcommand}(#{argv.inspect})" if $debug
|
416
|
+
__send__(@subcommand, *argv)
|
417
|
+
else
|
418
|
+
#begin
|
419
|
+
puts "Calling method_missing with #{@subcommand}, #{argv.inspect}" if $debug
|
420
|
+
method_missing(@subcommand, *argv)
|
421
|
+
#rescue NoMethodError => e
|
422
|
+
#if self.private_methods.include?( "no_command_error" )
|
423
|
+
# no_command_error( *args )
|
424
|
+
#else
|
425
|
+
# $stderr << "Non-applicable command -- #{argv.join(' ')}\n"
|
426
|
+
# exit -1
|
427
|
+
#end
|
428
|
+
#end
|
429
|
+
end
|
430
|
+
|
431
|
+
rescue UnknownOptionError => exception
|
432
|
+
$stderr << exception.message << "\n"
|
433
|
+
exit -1
|
434
|
+
end
|
435
|
+
|
436
|
+
# rescue => err
|
437
|
+
# if $DEBUG
|
438
|
+
# raise err
|
439
|
+
# else
|
440
|
+
# msg = err.message.chomp('.') + '.'
|
441
|
+
# msg[0,1] = msg[0,1].capitalize
|
442
|
+
# msg << " (#{err.class})" if $VERBOSE
|
443
|
+
# $stderr << msg
|
444
|
+
# end
|
445
|
+
end # def execute
|
446
|
+
|
447
|
+
private
|
448
|
+
|
449
|
+
#
|
450
|
+
|
451
|
+
attr_accessor :global_options
|
452
|
+
attr_accessor :subcommand_aliases
|
453
|
+
|
454
|
+
def subcommand_aliases_list(main_subcommand_name)
|
455
|
+
# If subcommand_aliases returns {:edit_ext=>:edit_externals, :ee=>:edit_externals}, then
|
456
|
+
# subcommand_aliases_list(:edit_externals) ought to return [:edit_ext, :ee]
|
457
|
+
subcommand_aliases.select {|k, v| v == main_subcommand_name}.
|
458
|
+
map {|k, v| k if v == main_subcommand_name}
|
459
|
+
end
|
460
|
+
|
461
|
+
#
|
462
|
+
|
463
|
+
def execute_options( argv, break_when_hit_subcommand = false )
|
464
|
+
argv = argv.dup
|
465
|
+
args_to_return = []
|
466
|
+
until argv.empty?
|
467
|
+
arg = argv.first
|
468
|
+
if arg[0,1] == '-'
|
469
|
+
puts "'#{arg}' -- is an option" if $debug
|
470
|
+
method_name = arg.option_methodize
|
471
|
+
#puts "Methods: #{(methods - Object.methods).inspect}" if $debug
|
472
|
+
if respond_to?(method_name)
|
473
|
+
m = method(method_name)
|
474
|
+
puts "Method named #{method_name} exists and has an arity of #{m.arity}" if $debug
|
475
|
+
if m.arity == -1
|
476
|
+
# Implemented the same as for option_missing, except that we don't pass the *name* of the option
|
477
|
+
arity = m.call(*argv[1..-1]) || 1
|
478
|
+
puts "#{method_name} returned an arity of #{arity}" if $debug
|
479
|
+
if !arity.is_a?(Fixnum)
|
480
|
+
raise "Expected #{method_name} to return a valid arity, but it didn't"
|
481
|
+
end
|
482
|
+
#puts "argv before: #{argv.inspect}"
|
483
|
+
argv.shift # Get rid of the *name* of the option
|
484
|
+
argv.slice!(0, arity) # Then discard as many arguments as that option claimed it used up
|
485
|
+
#puts "argv after: #{argv.inspect}"
|
486
|
+
else
|
487
|
+
args_for_current_option = argv.slice!(0, m.arity+1) # The +1 is so that we also remove the option name from argv
|
488
|
+
args_for_current_option.shift # Remove the option name from args_for_current_option as well
|
489
|
+
m.call(*args_for_current_option)
|
490
|
+
end
|
491
|
+
elsif respond_to?(:option_missing)
|
492
|
+
puts "No method named #{method_name} exists -- calling option_missing(#{arg}, #{argv[1..-1].inspect})" if $debug
|
493
|
+
# Old: arity = option_missing(arg.gsub(/^[-]+/,''), argv[1..-1]) || 1
|
494
|
+
arity = option_missing(arg, argv[1..-1]) || 1
|
495
|
+
argv.shift # Get rid of the *name* of the option
|
496
|
+
argv.slice!(0, arity) # Then discard as many arguments as that option claimed it used up
|
497
|
+
else
|
498
|
+
raise UnknownOptionError.new(arg)
|
499
|
+
end
|
500
|
+
else
|
501
|
+
puts "'#{arg}' -- not an option. Adding to args_to_return..." if $debug
|
502
|
+
if break_when_hit_subcommand
|
503
|
+
# If we are parsing options for the *main* command and we are allowing subcommands, then we want to stop as soon as we
|
504
|
+
# get to the first non-option, because that non-option will be the name of our subcommand and all options that follow
|
505
|
+
# should be parsed later when we handle the subcommand (after we've extended the subcommand module, for instance).
|
506
|
+
args_to_return = argv
|
507
|
+
break
|
508
|
+
else
|
509
|
+
args_to_return << argv.shift
|
510
|
+
end
|
511
|
+
end
|
512
|
+
end
|
513
|
+
puts "Returning #{args_to_return.inspect}" if $debug
|
514
|
+
return args_to_return
|
515
|
+
end
|
516
|
+
|
517
|
+
public
|
518
|
+
|
519
|
+
=begin
|
520
|
+
# We include a module here so you can define your own help
|
521
|
+
# command and call #super to utilize this one.
|
522
|
+
|
523
|
+
module Help
|
524
|
+
|
525
|
+
def help
|
526
|
+
opts = help_options
|
527
|
+
s = ""
|
528
|
+
s << "#{File.basename($0)}\n\n"
|
529
|
+
unless opts.empty?
|
530
|
+
s << "OPTIONS\n"
|
531
|
+
s << help_options
|
532
|
+
s << "\n"
|
533
|
+
end
|
534
|
+
s << "COMMANDS\n"
|
535
|
+
s << help_commands
|
536
|
+
puts s
|
537
|
+
end
|
538
|
+
|
539
|
+
private
|
540
|
+
|
541
|
+
def help_commands
|
542
|
+
help = self.class.help
|
543
|
+
bufs = help.keys.collect{ |a| a.to_s.size }.max + 3
|
544
|
+
lines = []
|
545
|
+
help.each { |cmd, str|
|
546
|
+
cmd = cmd.to_s
|
547
|
+
if cmd !~ /^_/
|
548
|
+
lines << " " + cmd + (" " * (bufs - cmd.size)) + str
|
549
|
+
end
|
550
|
+
}
|
551
|
+
lines.join("\n")
|
552
|
+
end
|
553
|
+
|
554
|
+
def help_options
|
555
|
+
help = self.class.help
|
556
|
+
bufs = help.keys.collect{ |a| a.to_s.size }.max + 3
|
557
|
+
lines = []
|
558
|
+
help.each { |cmd, str|
|
559
|
+
cmd = cmd.to_s
|
560
|
+
if cmd =~ /^_/
|
561
|
+
lines << " " + cmd.gsub(/_/,'-') + (" " * (bufs - cmd.size)) + str
|
562
|
+
end
|
563
|
+
}
|
564
|
+
lines.join("\n")
|
565
|
+
end
|
566
|
+
|
567
|
+
module ClassMethods
|
568
|
+
|
569
|
+
def help( str=nil )
|
570
|
+
return (@help ||= {}) unless str
|
571
|
+
@current_help = str
|
572
|
+
end
|
573
|
+
|
574
|
+
def method_added( meth )
|
575
|
+
if @current_help
|
576
|
+
@help ||= {}
|
577
|
+
@help[meth] = @current_help
|
578
|
+
@current_help = nil
|
579
|
+
end
|
580
|
+
end
|
581
|
+
|
582
|
+
end
|
583
|
+
|
584
|
+
end
|
585
|
+
|
586
|
+
include Help
|
587
|
+
extend Help::ClassMethods
|
588
|
+
=end
|
589
|
+
|
590
|
+
class UnknownOptionError < StandardError
|
591
|
+
def initialize(option_name)
|
592
|
+
@option_name = option_name
|
593
|
+
end
|
594
|
+
def message
|
595
|
+
"Unknown option '#{@option_name}'."
|
596
|
+
end
|
597
|
+
end
|
598
|
+
end
|
599
|
+
|
600
|
+
|
601
|
+
|
602
|
+
# _____ _
|
603
|
+
# |_ _|__ ___| |_
|
604
|
+
# | |/ _ \/ __| __|
|
605
|
+
# | | __/\__ \ |_
|
606
|
+
# |_|\___||___/\__|
|
607
|
+
#
|
608
|
+
|
609
|
+
=begin test
|
610
|
+
|
611
|
+
require 'test/unit'
|
612
|
+
require 'stringio'
|
613
|
+
require 'qualitysmith_extensions/kernel/capture_output'
|
614
|
+
|
615
|
+
class TestCommand < Test::Unit::TestCase
|
616
|
+
def setup
|
617
|
+
$output = nil
|
618
|
+
end
|
619
|
+
|
620
|
+
#-----------------------------------------------------------------------------------------------------------------------------
|
621
|
+
|
622
|
+
class SimpleCommand < Console::Command
|
623
|
+
def __here ; @here = true ; end
|
624
|
+
|
625
|
+
def main(*args)
|
626
|
+
$output = [@here] | args
|
627
|
+
end
|
628
|
+
end
|
629
|
+
|
630
|
+
def test_SimpleCommand
|
631
|
+
SimpleCommand.execute( '--here file1 file2' )
|
632
|
+
assert_equal( [true, 'file1', 'file2'], $output )
|
633
|
+
end
|
634
|
+
|
635
|
+
#-----------------------------------------------------------------------------------------------------------------------------
|
636
|
+
|
637
|
+
class MethodMissingSubcommand < Console::Command
|
638
|
+
def __here ; @here = true ; end
|
639
|
+
|
640
|
+
def method_missing(subcommand, *args)
|
641
|
+
$output = [@here, subcommand] | args
|
642
|
+
end
|
643
|
+
end
|
644
|
+
|
645
|
+
def test_MethodMissingSubcommand
|
646
|
+
MethodMissingSubcommand.execute( '--here go file1' )
|
647
|
+
assert_equal( [true, 'go', 'file1'], $output )
|
648
|
+
end
|
649
|
+
|
650
|
+
#-----------------------------------------------------------------------------------------------------------------------------
|
651
|
+
|
652
|
+
class SimpleSubcommand < Console::Command
|
653
|
+
def __here ; @here = true ; end
|
654
|
+
|
655
|
+
# subcommand
|
656
|
+
|
657
|
+
module Go
|
658
|
+
def _p(n)
|
659
|
+
@p = n.to_i
|
660
|
+
end
|
661
|
+
end
|
662
|
+
|
663
|
+
def go ; $output = [@here, @p] ; end
|
664
|
+
end
|
665
|
+
|
666
|
+
def test_SimpleSubcommand
|
667
|
+
SimpleSubcommand.execute( '--here go -p 1' )
|
668
|
+
assert_equal( [true, 1], $output )
|
669
|
+
end
|
670
|
+
|
671
|
+
#-----------------------------------------------------------------------------------------------------------------------------
|
672
|
+
|
673
|
+
# Global options can be anywhere, right? Even after subcommands? Let's find out.
|
674
|
+
class GlobalOptionsAfterSubcommand < Console::Command
|
675
|
+
def _x ; @x = true ; end
|
676
|
+
global_option :_x
|
677
|
+
|
678
|
+
def go ; $output = [@x, @p] ; end
|
679
|
+
|
680
|
+
module Go
|
681
|
+
def _p(n)
|
682
|
+
@p = n.to_i
|
683
|
+
end
|
684
|
+
end
|
685
|
+
end
|
686
|
+
|
687
|
+
def test_GlobalOptionsAfterSubcommand
|
688
|
+
GlobalOptionsAfterSubcommand.execute( 'go -x -p 1' )
|
689
|
+
assert_equal( [true, 1], $output )
|
690
|
+
|
691
|
+
GlobalOptionsAfterSubcommand.execute( 'go -p 1 -x' )
|
692
|
+
assert_equal( [true, 1], $output )
|
693
|
+
end
|
694
|
+
|
695
|
+
#-----------------------------------------------------------------------------------------------------------------------------
|
696
|
+
|
697
|
+
class GivingUnrecognizedOptions < Console::Command
|
698
|
+
def _x ; @x = true ; end
|
699
|
+
def go ; $output = [@x, @p] ; end
|
700
|
+
end
|
701
|
+
|
702
|
+
def test_GivingUnrecognizedOptions
|
703
|
+
stderr = capture_output $stderr do
|
704
|
+
assert_raise(SystemExit) do
|
705
|
+
GivingUnrecognizedOptions.execute( '--an-option-that-wont-be-recognized -x go' )
|
706
|
+
end
|
707
|
+
end
|
708
|
+
assert_equal "Unknown option '--an-option-that-wont-be-recognized'.\n", stderr
|
709
|
+
assert_equal( nil, $output )
|
710
|
+
end
|
711
|
+
|
712
|
+
#-----------------------------------------------------------------------------------------------------------------------------
|
713
|
+
|
714
|
+
class PassingMultipleSingleCharOptionsAsOneOption < Console::Command
|
715
|
+
def _x ; @x = true ; end
|
716
|
+
def _y ; @y = true ; end
|
717
|
+
def _z(n) ; @z = n ; end
|
718
|
+
|
719
|
+
global_option :_x
|
720
|
+
|
721
|
+
def go ; $output = [@x, @y, @z, @p] ; end
|
722
|
+
|
723
|
+
module Go
|
724
|
+
def _p(n)
|
725
|
+
@p = n.to_i
|
726
|
+
end
|
727
|
+
end
|
728
|
+
end
|
729
|
+
|
730
|
+
def test_PassingMultipleSingleCharOptionsAsOneOption
|
731
|
+
PassingMultipleSingleCharOptionsAsOneOption.execute( '-xy -z HERE go -p 1' )
|
732
|
+
assert_equal( [true, true, 'HERE', 1], $output )
|
733
|
+
end
|
734
|
+
|
735
|
+
#-----------------------------------------------------------------------------------------------------------------------------
|
736
|
+
|
737
|
+
class OptionUsingEquals < Console::Command
|
738
|
+
module Go
|
739
|
+
def __mode(mode) ; @mode = mode ; end
|
740
|
+
end
|
741
|
+
def go ; $output = [@mode] ; end
|
742
|
+
end
|
743
|
+
|
744
|
+
def test_OptionUsingEquals
|
745
|
+
OptionUsingEquals.execute( 'go --mode smart' )
|
746
|
+
assert_equal( ['smart'], $output )
|
747
|
+
|
748
|
+
# I would expect this to work too, but currently it doesn't.
|
749
|
+
#assert_nothing_raised { OptionUsingEquals.execute( 'go --mode=smart' ) }
|
750
|
+
#assert_equal( ['smart'], $output )
|
751
|
+
end
|
752
|
+
|
753
|
+
#-----------------------------------------------------------------------------------------------------------------------------
|
754
|
+
|
755
|
+
class SubcommandThatTakesArgs < Console::Command
|
756
|
+
def go(arg1, *args) ; $output = [arg1] | args ; end
|
757
|
+
end
|
758
|
+
|
759
|
+
def test_SubcommandThatTakesArgs
|
760
|
+
SubcommandThatTakesArgs.execute( 'go file1 file2 file3' )
|
761
|
+
assert_equal( ['file1', 'file2', 'file3'], $output )
|
762
|
+
end
|
763
|
+
|
764
|
+
#-----------------------------------------------------------------------------------------------------------------------------
|
765
|
+
|
766
|
+
class With2OptionalArgs < Console::Command
|
767
|
+
module Go
|
768
|
+
def _p(n)
|
769
|
+
@p = n.to_i
|
770
|
+
end
|
771
|
+
end
|
772
|
+
|
773
|
+
def go(optional1 = nil, optional2 = nil) ; $output = [@p, optional1, optional2 ] ; end
|
774
|
+
end
|
775
|
+
|
776
|
+
def test_With2OptionalArgs
|
777
|
+
With2OptionalArgs.execute( 'go -p 1 to' )
|
778
|
+
assert_equal( [1, 'to', nil], $output )
|
779
|
+
end
|
780
|
+
|
781
|
+
#-----------------------------------------------------------------------------------------------------------------------------
|
782
|
+
|
783
|
+
class VariableArgs < Console::Command
|
784
|
+
module Go
|
785
|
+
def _p(n)
|
786
|
+
@p = n.to_i
|
787
|
+
end
|
788
|
+
end
|
789
|
+
|
790
|
+
def go(*args) ; $output = [@p] | args ; end
|
791
|
+
end
|
792
|
+
|
793
|
+
def test_VariableArgs
|
794
|
+
VariableArgs.execute( 'go -p 1 to bed' )
|
795
|
+
assert_equal( [1, 'to', 'bed'], $output )
|
796
|
+
end
|
797
|
+
|
798
|
+
#-----------------------------------------------------------------------------------------------------------------------------
|
799
|
+
|
800
|
+
class OptionMissing < Console::Command
|
801
|
+
module Go
|
802
|
+
def option_missing(option_name, args)
|
803
|
+
p args if $debug
|
804
|
+
case option_name
|
805
|
+
when '-p'
|
806
|
+
@p = args[0].to_i
|
807
|
+
1
|
808
|
+
else
|
809
|
+
raise Console::Command::UnknownOptionError.new(option_name)
|
810
|
+
end
|
811
|
+
end
|
812
|
+
end
|
813
|
+
|
814
|
+
def go(*args) ; $output = [@p] | args ; end
|
815
|
+
end
|
816
|
+
|
817
|
+
def test_OptionMissing
|
818
|
+
OptionMissing.execute( 'go -p 1 to bed right now' )
|
819
|
+
assert_equal( [1, 'to', 'bed', 'right', 'now'], $output )
|
820
|
+
end
|
821
|
+
|
822
|
+
#-----------------------------------------------------------------------------------------------------------------------------
|
823
|
+
|
824
|
+
class OptionWith0Arity < Console::Command
|
825
|
+
module Go
|
826
|
+
def _p()
|
827
|
+
@p = 13
|
828
|
+
end
|
829
|
+
end
|
830
|
+
|
831
|
+
def go(arg1) ; $output = [@p, arg1] ; end
|
832
|
+
end
|
833
|
+
|
834
|
+
def test_OptionWith0Arity
|
835
|
+
OptionWith0Arity.execute( 'go -p away' )
|
836
|
+
assert_equal( [13, 'away'], $output )
|
837
|
+
end
|
838
|
+
|
839
|
+
#-----------------------------------------------------------------------------------------------------------------------------
|
840
|
+
|
841
|
+
class OptionWithVariableArity < Console::Command
|
842
|
+
module Go
|
843
|
+
def _p(*args)
|
844
|
+
#puts "_p received #{args.size} args: #{args.inspect}"
|
845
|
+
@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
|
846
|
+
#puts "_p accepting #{@p.size} args: #{@p.inspect}"
|
847
|
+
@p.size
|
848
|
+
end
|
849
|
+
end
|
850
|
+
|
851
|
+
def go(arg1) ; $output = @p | [arg1] ; end
|
852
|
+
end
|
853
|
+
|
854
|
+
def test_OptionWithVariableArity
|
855
|
+
OptionWithVariableArity.execute( 'go -p 1 2 3 4 away' )
|
856
|
+
assert_equal( ['1', '2', '3', '4', 'away'], $output )
|
857
|
+
end
|
858
|
+
|
859
|
+
#-----------------------------------------------------------------------------------------------------------------------------
|
860
|
+
|
861
|
+
class OptionMissingArityOf2 < Console::Command
|
862
|
+
module Go
|
863
|
+
def option_missing(option_name, args)
|
864
|
+
case option_name
|
865
|
+
when '-p'
|
866
|
+
@p1 = args[0].to_i
|
867
|
+
@p2 = args[1].to_i
|
868
|
+
2
|
869
|
+
when '-q'
|
870
|
+
@q = args[0].to_i
|
871
|
+
nil # Test default arity
|
872
|
+
else
|
873
|
+
raise Console::Command::UnknownOptionError.new.new(option_name, args)
|
874
|
+
end
|
875
|
+
end
|
876
|
+
end
|
877
|
+
|
878
|
+
def go(*args) ; $output = [@p1, @p2, @q] | args ; end
|
879
|
+
end
|
880
|
+
|
881
|
+
def test_OptionMissingArityOf2
|
882
|
+
OptionMissingArityOf2.execute( 'go -p 1 2 -q 3 to bed right now' )
|
883
|
+
assert_equal( [1, 2, 3, 'to', 'bed', 'right', 'now'], $output )
|
884
|
+
end
|
885
|
+
|
886
|
+
#-----------------------------------------------------------------------------------------------------------------------------
|
887
|
+
|
888
|
+
class OptionMissingReceivesShortAndLongOptionsDifferently < Console::Command
|
889
|
+
module Go
|
890
|
+
def option_missing(option_name, args)
|
891
|
+
case option_name
|
892
|
+
when '-s'
|
893
|
+
@s = "-s #{args[0]}"
|
894
|
+
1
|
895
|
+
when '--long'
|
896
|
+
@long = "--long #{args[0]}"
|
897
|
+
1
|
898
|
+
else
|
899
|
+
raise Console::Command::UnknownOptionError.new(option_name, args)
|
900
|
+
end
|
901
|
+
end
|
902
|
+
end
|
903
|
+
|
904
|
+
def go(*args) ; $output = [@s, @long] ; end
|
905
|
+
end
|
906
|
+
|
907
|
+
def test_OptionMissingReceivesShortAndLongOptionsDifferently
|
908
|
+
OptionMissingReceivesShortAndLongOptionsDifferently.execute( 'go -s 1 --long long' )
|
909
|
+
assert_equal( ['-s 1', '--long long'], $output )
|
910
|
+
end
|
911
|
+
|
912
|
+
#-----------------------------------------------------------------------------------------------------------------------------
|
913
|
+
|
914
|
+
class AliasSubcommand < Console::Command
|
915
|
+
alias_subcommand :g => :go
|
916
|
+
module Go
|
917
|
+
def option_missing(option_name, args)
|
918
|
+
case option_name
|
919
|
+
when '-s'
|
920
|
+
@s = "-s #{args[0]}"
|
921
|
+
1
|
922
|
+
when '--long'
|
923
|
+
@long = "--long #{args[0]}"
|
924
|
+
1
|
925
|
+
else
|
926
|
+
raise Console::Command::UnknownOptionError.new(option_name, args)
|
927
|
+
end
|
928
|
+
end
|
929
|
+
end
|
930
|
+
|
931
|
+
def go(*args) ; $output = [@s, @long] ; end
|
932
|
+
end
|
933
|
+
|
934
|
+
def test_AliasSubcommand
|
935
|
+
AliasSubcommand.execute( 'g -s 1 --long long' )
|
936
|
+
assert_equal( ['-s 1', '--long long'], $output )
|
937
|
+
end
|
938
|
+
end
|
939
|
+
|
940
|
+
=end
|