drydock 0.6.9 → 1.0.0

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 (10) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGES.txt +26 -21
  3. data/LICENSE.txt +2 -2
  4. data/README.md +92 -0
  5. data/Rakefile +40 -58
  6. data/bin/example +92 -96
  7. data/drydock.gemspec +41 -37
  8. data/lib/drydock.rb +265 -281
  9. metadata +52 -48
  10. data/README.rdoc +0 -92
data/lib/drydock.rb CHANGED
@@ -1,3 +1,4 @@
1
+ require 'English'
1
2
  require 'optparse'
2
3
  require 'ostruct'
3
4
 
@@ -5,12 +6,13 @@ require 'stringio'
5
6
 
6
7
  module Drydock
7
8
  require 'drydock/mixins'
8
-
9
+
9
10
  autoload :Screen, 'drydock/screen'
10
11
  autoload :Console, 'drydock/console'
11
-
12
- class FancyArray < Array #:nodoc:
12
+
13
+ class FancyArray < Array # :nodoc:
13
14
  attr_reader :fields
15
+
14
16
  def add_field(n)
15
17
  @fields ||= []
16
18
  field_name = n
@@ -39,26 +41,38 @@ module Drydock
39
41
  end
40
42
  end
41
43
  end
42
-
44
+
43
45
  class ArgError < RuntimeError
44
46
  attr_reader :arg, :cmd, :msg
47
+
45
48
  def initialize(*args)
46
49
  @msg = args.shift if args.size == 1
47
50
  @arg, @cmd, @msg = *args
48
51
  @cmd ||= 'COMMAND'
49
52
  @msg = nil if @msg && @msg.empty?
53
+ super
50
54
  end
51
- def message; @msg || "Error: No #{@arg} provided"; end
52
- def usage; "See: #{$0} #{@cmd} -h"; end
55
+
56
+ def message
57
+ @msg || "Error: No #{@arg} provided"
58
+ end
59
+
60
+ def usage
61
+ "See: #{$0} #{@cmd} -h"
62
+ end
63
+
53
64
  end
65
+
54
66
  class OptError < ArgError
55
- def message; @msg || "Error: No #{@arg} provided"; end
67
+ def message
68
+ @msg || "Error: No #{@arg} provided"
69
+ end
56
70
  end
57
-
71
+
58
72
  # The base class for all command objects. There is an instance of this class
59
73
  # for every command defined. Global and command-specific options are added
60
- # as attributes to this class dynamically.
61
- #
74
+ # as attributes to this class dynamically.
75
+ #
62
76
  # i.e. "example -v select --location kumamoto"
63
77
  #
64
78
  # global :v, :verbose, "I want mooooore!"
@@ -68,7 +82,7 @@ module Drydock
68
82
  # puts obj.option.location #=> "kumamoto"
69
83
  # end
70
84
  #
71
- # You can sub-class it to create your own:
85
+ # You can sub-class it to create your own:
72
86
  #
73
87
  # class Malpeque < Drydock::Command
74
88
  # # ... sea to it
@@ -81,60 +95,59 @@ module Drydock
81
95
  # end
82
96
  #
83
97
  class Command
84
- VERSION = "0.6.9"
85
- # The canonical name of the command (the one used in the command definition). If you
86
- # inherit from this class and add a method named +cmd+, you can leave omit the block
87
- # in the command definition. That method will be called instead. See bin/examples.
98
+ # The canonical name of the command (the one used in the command definition). If you
99
+ # inherit from this class and add a method named +cmd+, you can leave omit the block
100
+ # in the command definition. That method will be called instead. See bin/examples.
88
101
  attr_reader :cmd
89
- # The name used to evoke this command (it's either the canonical name or the alias used).
102
+ # The name used to evoke this command (it's either the canonical name or the alias used).
90
103
  attr_reader :alias
91
- # The block that will be executed when this command is evoked. If the block is nil
92
- # it will check if there is a method named +cmd+. If so, that will be executed.
104
+ # The block that will be executed when this command is evoked. If the block is nil
105
+ # it will check if there is a method named +cmd+. If so, that will be executed.
93
106
  attr_reader :b
94
- # An OpenStruct object containing the command options specified at run-time.
107
+ # An OpenStruct object containing the command options specified at run-time.
95
108
  attr_reader :option
96
- # An OpenStruct object containing the global options specified at run-time.
109
+ # An OpenStruct object containing the global options specified at run-time.
97
110
  attr_reader :global
98
- # A friendly description of the command.
111
+ # A friendly description of the command.
99
112
  attr_accessor :desc
100
- # An array of action names specified in the command definition
113
+ # An array of action names specified in the command definition
101
114
  attr_accessor :actions
102
- # An instance of Drydock::FancyArray. Acts like an array of unnamed arguments
103
- # but also allows field names if supplied.
104
- attr_accessor :argv
105
- # Either an IO handle to STDIN or the output of the Drydock#stdin handler.
115
+ # An instance of Drydock::FancyArray. Acts like an array of unnamed arguments
116
+ # but also allows field names if supplied.
117
+ attr_accessor :argv
118
+ # Either an IO handle to STDIN or the output of the Drydock#stdin handler.
106
119
  attr_reader :stdin
107
- # The basename of the executable or script: File.basename($0)
120
+ # The basename of the executable or script: File.basename($0)
108
121
  attr_reader :executable
109
-
122
+
110
123
  # The default constructor sets the short name of the command
111
124
  # and stores a reference to the block (if supplied).
112
- # You don't need to override this method to add functionality
125
+ # You don't need to override this method to add functionality
113
126
  # to your custom Command classes. Define an +init+ method instead.
114
- # It will be called just before the block is executed.
127
+ # It will be called just before the block is executed.
115
128
  # +cmd+ is the short name of this command.
116
129
  # +b+ is the block associated to this command.
117
- def initialize(cmd, &b)
118
- @cmd = (cmd.kind_of?(Symbol)) ? cmd : cmd.to_sym
119
- @b = b
130
+ def initialize(cmd, &blk)
131
+ @cmd = cmd.is_a?(Symbol) ? cmd : cmd.to_sym
132
+ @b = blk
120
133
  @actions = []
121
134
  @argv = Drydock::FancyArray.new # an array with field names
122
- @stdin = STDIN
135
+ @stdin = $stdin
123
136
  @option = OpenStruct.new
124
137
  @global = OpenStruct.new
125
- @executable = File.basename($0)
138
+ @executable = File.basename($PROGRAM_NAME)
126
139
  @global.verbose = 0
127
140
  @global.quiet = false
128
141
  end
129
-
142
+
130
143
  # Returns the command name (not the alias)
131
144
  def name
132
145
  @cmd
133
146
  end
134
-
135
- # Prepare this command object to be called.
136
- #
137
- # Calls self.init after setting attributes (if the method exists). You can
147
+
148
+ # Prepare this command object to be called.
149
+ #
150
+ # Calls self.init after setting attributes (if the method exists). You can
138
151
  # implement an init method in your subclasses of Drydock::Command to handle
139
152
  # your own initialization stuff.
140
153
  #
@@ -149,65 +162,65 @@ module Drydock
149
162
  @alias = cmd_str.nil? ? @cmd : cmd_str
150
163
 
151
164
  global_options.each_pair do |n,v|
152
- self.global.send("#{n}=", v) # Populate the object's globals
165
+ global.send("#{n}=", v) # Populate the object's globals
153
166
  end
154
-
167
+
155
168
  options.each_pair do |n,v|
156
- self.option.send("#{n}=", v) # ... and also the command options
169
+ option.send("#{n}=", v) # ... and also the command options
157
170
  end
158
-
171
+
159
172
  @argv << argv # TODO: Using += returns an Array instead of FancyArray
160
173
  @argv.flatten! # NOTE: << creates @argv[[]]
161
174
  @stdin = stdin
162
-
163
- self.init if self.respond_to? :init # Must be called first!
164
-
175
+
176
+ init if respond_to? :init # Must be called first!
177
+
165
178
  end
166
-
179
+
167
180
  # Calls the command in the following order:
168
- #
181
+ #
169
182
  # * print_header
170
183
  # * validation (if methodname_valid? exists)
171
184
  # * command block (@b)
172
185
  # * print_footer
173
186
  #
174
- def call
175
- self.print_header if self.respond_to? :print_header
176
-
187
+ def call
188
+ print_header if respond_to? :print_header
189
+
177
190
  # Execute the command block if it exists
178
- if @b
191
+ if @b
179
192
  run_validation
180
- @b.call(self)
181
-
193
+ @b.call(self)
194
+
182
195
  # Otherwise check to see if an action was specified
183
- elsif !(chosen = find_action(self.option)).empty?
196
+ elsif !(chosen = find_action(option)).empty?
184
197
  raise "Only one action at a time please! I can't #{chosen.join(' AND ')}." if chosen.size > 1
185
198
  criteria = [[@cmd, chosen.first], [chosen.first, @cmd]]
186
199
  meth = name = nil
187
200
  # Try command_action, then action_command
188
201
  criteria.each do |tuple|
189
202
  name = tuple.join('_')
190
- meth = name if self.respond_to?(name)
203
+ meth = name if respond_to?(name)
191
204
  end
192
-
205
+
193
206
  raise "#{self.class} needs a #{name} method!" unless meth
194
-
207
+
195
208
  run_validation(meth)
196
- self.send(meth)
197
-
198
- # No block and no action. We'll try for the method name in the Drydock::Command class.
199
- elsif self.respond_to? @cmd.to_sym
209
+ send(meth)
210
+
211
+ # No block and no action. We'll try for the method name in the Drydock::Command class.
212
+ elsif respond_to? @cmd.to_sym
200
213
  run_validation(@cmd)
201
- self.send(@cmd)
202
-
214
+ send(@cmd)
215
+
203
216
  # Well, then I have no idea what you want me to do!
204
217
  else
205
218
  raise "The command #{@alias} has no block and #{self.class} has no #{@cmd} method!"
206
219
  end
207
-
208
- self.print_footer if respond_to? :print_footer
220
+
221
+ print_footer if respond_to? :print_footer
209
222
  end
210
-
223
+
211
224
  # <li>+meth+ The method name used to determine the name of the validation method.
212
225
  # If not supplied, the validation method is "valid?" otherwise it's "meth_valid?"</li>
213
226
  # If the command class doesn't have the given validation method, we'll just continue
@@ -220,35 +233,35 @@ module Drydock
220
233
  # def command_action_valid? # if the main meth is command_action
221
234
  # def action_command_valid? # if the main meth is action_command
222
235
  #
223
- # This method raises a generic exception when the validation method returns false.
224
- # However, <strong>it's more appropriate for the validation methods to raise
225
- # detailed exceptions</strong>.
236
+ # This method raises a generic exception when the validation method returns false.
237
+ # However, <strong>it's more appropriate for the validation methods to raise
238
+ # detailed exceptions</strong>.
226
239
  #
227
240
  def run_validation(meth=nil)
228
241
  vmeth = meth ? [meth, 'valid?'].join('_') : 'valid?'
229
- is_valid = self.respond_to?(vmeth) ? self.send(vmeth) : true
230
- raise "Your request is not valid. See #{$0} #{@cmd} -h" unless is_valid
242
+ is_valid = respond_to?(vmeth) ? send(vmeth) : true
243
+ raise "Your request is not valid. See #{$PROGRAM_NAME} #{@cmd} -h" unless is_valid
231
244
  end
232
245
  private :run_validation
233
-
246
+
234
247
  # Compares the list of known actions to the list of boolean switches supplied
235
- # on the command line (if any).
236
- # <li>+options+ is a hash of the named command line arguments (created by
248
+ # on the command line (if any).
249
+ # <li>+options+ is a hash of the named command line arguments (created by
237
250
  # OptionParser#getopts)</li>
238
251
  # Returns an array of action names (empty if no action was supplied)
239
252
  def find_action(options)
240
253
  options = options.marshal_dump if options.is_a?(OpenStruct)
241
254
  boolkeys = options.keys.select { |n| options[n] == true } || []
242
- boolkeys = boolkeys.collect { |n| n.to_s } # @agents contains Strings.
255
+ boolkeys = boolkeys.collect { |n| n.to_s } # @agents contains Strings.
243
256
  # Returns the elements in @actions that are also found in boolkeys
244
- (@actions || []) & boolkeys
257
+ (@actions || []) & boolkeys
245
258
  end
246
259
  private :find_action
247
-
248
- # Print the list of available commands to STDOUT. This is used as the
249
- # "default" command unless another default commands is supplied. You
260
+
261
+ # Print the list of available commands to STDOUT. This is used as the
262
+ # "default" command unless another default commands is supplied. You
250
263
  # can also write your own Drydock::Command#show_commands to override
251
- # this default behaviour.
264
+ # this default behaviour.
252
265
  #
253
266
  # The output was worked on here:
254
267
  # http://etherpad.com/SXjqQGRr8M
@@ -269,11 +282,11 @@ module Drydock
269
282
  cmds[cmd][:desc] = nil if cmds[cmd][:desc] && cmds[cmd][:desc].empty?
270
283
  cmds[cmd][:pretty] = pretty
271
284
  end
272
-
285
+
273
286
  cmd_names_sorted = cmds.keys.sort{ |a,b| a.to_s <=> b.to_s }
274
-
287
+
275
288
  if @global.quiet
276
- puts "Commands: "
289
+ puts 'Commands: '
277
290
  line = []
278
291
  cmd_names_sorted.each_with_index do |cmd,i|
279
292
  line << cmd
@@ -284,14 +297,14 @@ module Drydock
284
297
  end
285
298
  return
286
299
  end
287
-
288
- puts "%5s: %s" % ["Usage", "#{@executable} [global options] COMMAND [command options]"]
289
- puts "%5s: %s" % ["Try", "#{@executable} -h"]
290
- puts "%5s %s" % ["", "#{@executable} COMMAND -h"]
300
+
301
+ puts '%5s: %s' % ['Usage', "#{@executable} [global options] COMMAND [command options]"]
302
+ puts '%5s: %s' % ['Try', "#{@executable} -h"]
303
+ puts '%5s %s' % ['', "#{@executable} COMMAND -h"]
291
304
  puts
292
-
293
- puts "Commands: "
294
- if @global.verbose > 0
305
+
306
+ puts 'Commands: '
307
+ if @global.verbose.positive?
295
308
  puts # empty line
296
309
  cmd_names_sorted.each do |cmd|
297
310
  puts "$ %s" % [@executable] if Drydock.default?(cmd)
@@ -314,7 +327,7 @@ module Drydock
314
327
  end
315
328
  end
316
329
  end
317
-
330
+
318
331
  # The name of the command
319
332
  def to_s
320
333
  @cmd.to_s
@@ -325,27 +338,34 @@ end
325
338
  module Drydock
326
339
  class UnknownCommand < RuntimeError
327
340
  attr_reader :name
341
+
328
342
  def initialize(name)
329
343
  @name = name || :unknown
344
+ super
330
345
  end
346
+
331
347
  def message
332
348
  "Unknown command: #{@name}"
333
349
  end
334
350
  end
335
351
  class NoCommandsDefined < RuntimeError
336
352
  def message
337
- "No commands defined"
353
+ 'No commands defined'
338
354
  end
339
355
  end
340
356
  class InvalidArgument < RuntimeError
341
357
  attr_accessor :args
358
+
342
359
  def initialize(args)
343
360
  @args = args || []
361
+ super
344
362
  end
363
+
345
364
  def message
346
365
  "Unknown option: #{@args.join(", ")}"
347
366
  end
348
367
  end
368
+
349
369
  class MissingArgument < InvalidArgument
350
370
  def message
351
371
  "Option requires a value: #{@args.join(", ")}"
@@ -353,47 +373,47 @@ module Drydock
353
373
  end
354
374
  end
355
375
 
356
- # Drydock is a DSL for command-line apps.
357
- # See bin/example for usage examples.
376
+ # Drydock is a DSL for command-line apps.
377
+ # See bin/example for usage examples.
358
378
  module Drydock
359
379
  extend self
360
-
361
- VERSION = 0.6
362
-
380
+
381
+ VERSION = '1.0.0'.freeze
382
+
363
383
  @@project = nil
364
-
384
+
365
385
  @@debug = false
366
386
  @@has_run = false
367
387
  @@run = true
368
-
388
+
369
389
  @@global_opts_parser = OptionParser.new
370
390
  @@global_option_names = []
371
391
 
372
392
  @@command_opts_parser = []
373
393
  @@command_option_names = []
374
394
  @@command_actions = []
375
-
395
+
376
396
  @@default_command = nil
377
397
  @@default_command_with_args = false
378
-
398
+
379
399
  @@commands = {}
380
400
  @@command_descriptions = []
381
401
  @@command_index = 0
382
402
  @@command_index_map = {}
383
403
  @@command_argv_names = [] # an array of names for values of argv
384
-
404
+
385
405
  @@capture = nil # contains one of :stdout, :stderr
386
406
  @@captured = nil
387
-
407
+
388
408
  @@trawler = nil
389
-
409
+
390
410
  public
391
411
  # Enable or disable debug output.
392
412
  #
393
413
  # debug :on
394
414
  # debug :off
395
415
  #
396
- # Calling without :on or :off will toggle the value.
416
+ # Calling without :on or :off will toggle the value.
397
417
  #
398
418
  def debug(toggle=false)
399
419
  if toggle.is_a? Symbol
@@ -403,13 +423,13 @@ module Drydock
403
423
  @@debug = (!@@debug)
404
424
  end
405
425
  end
406
-
407
- # Returns true if debug output is enabled.
426
+
427
+ # Returns true if debug output is enabled.
408
428
  def debug?
409
429
  @@debug
410
430
  end
411
-
412
- # Provide names for CLI arguments, in the order they appear.
431
+
432
+ # Provide names for CLI arguments, in the order they appear.
413
433
  #
414
434
  # $ yourscript sample malpeque zinqy
415
435
  # argv :name, :flavour
@@ -422,29 +442,23 @@ module Drydock
422
442
  @@command_argv_names[@@command_index] ||= []
423
443
  @@command_argv_names[@@command_index] += args.flatten
424
444
  end
425
-
445
+
426
446
  # The project name. This is currently only used when printing
427
- # list of commands (see: Drydock::Command#show_commands). It may be
428
- # used elsewhere in the future.
447
+ # list of commands (see: Drydock::Command#show_commands). It may be
448
+ # used elsewhere in the future.
429
449
  def project(txt=nil)
430
-
450
+
431
451
  return @@project unless txt
432
-
433
- #begin
434
- # require txt.downcase
435
- #rescue LoadError => ex
436
- # Drydock.run = false # Prevent execution at_exit
437
- # abort "Problem during require: #{ex.message}"
438
- #end
452
+
439
453
  @@project = txt
440
454
  end
441
-
455
+
442
456
  # Has the project been set?
443
457
  def project?
444
458
  (defined?(@@project) && !@@project.nil?)
445
459
  end
446
-
447
- # Define a default command. You can specify a command name that has
460
+
461
+ # Define a default command. You can specify a command name that has
448
462
  # been or will be defined in your script:
449
463
  #
450
464
  # default :task
@@ -473,86 +487,86 @@ module Drydock
473
487
  @@default_command_with_args = with_args ? true : false
474
488
  @@default_command
475
489
  end
476
-
490
+
477
491
  # Is +cmd+ the default command?
478
492
  def default?(cmd)
479
493
  return false if @@default_command.nil?
480
494
  (@@default_command == canonize(cmd))
481
495
  end
482
-
483
- #
496
+
497
+ #
484
498
  def default_with_args?; @@default_command_with_args; end
485
-
486
-
487
- # Define a block for processing STDIN before the command is called.
499
+
500
+
501
+ # Define a block for processing STDIN before the command is called.
488
502
  # The command block receives the return value of this block as obj.stdin:
489
503
  #
490
- # command :task do |obj|;
504
+ # command :task do |obj|;
491
505
  # obj.stdin # => ...
492
506
  # end
493
507
  #
494
- # If a stdin block isn't defined, +stdin+ above will be the STDIN IO handle.
508
+ # If a stdin block isn't defined, +stdin+ above will be the STDIN IO handle.
495
509
  def stdin(&b)
496
510
  @@stdin_block = b
497
511
  end
498
-
499
- # Define a block to be called before the command.
512
+
513
+ # Define a block to be called before the command.
500
514
  # This is useful for opening database connections, etc...
501
515
  def before(&b)
502
516
  @@before_block = b
503
517
  end
504
-
505
- # Define a block to be called after the command.
506
- # This is useful for stopping, closing, etc... the stuff in the before block.
518
+
519
+ # Define a block to be called after the command.
520
+ # This is useful for stopping, closing, etc... the stuff in the before block.
507
521
  def after(&b)
508
522
  @@after_block = b
509
523
  end
510
-
524
+
511
525
  # Define the default global usage banner. This is displayed
512
- # with "script -h".
526
+ # with "script -h".
513
527
  def global_usage(msg)
514
528
  @@global_opts_parser.banner = "USAGE: #{msg}"
515
529
  end
516
-
530
+
517
531
  # Define a command-specific usage banner. This is displayed
518
532
  # with "script command -h"
519
533
  def usage(msg)
520
534
  # The default value given by OptionParser starts with "Usage". That's how
521
- # we know we can clear it.
522
- get_current_option_parser.banner = "" if get_current_option_parser.banner =~ /^Usage:/
523
- get_current_option_parser.banner << "USAGE: #{msg}" << $/
535
+ # we know we can clear it.
536
+ get_current_option_parser.banner = get_current_option_parser.banner.sub(/^Usage:.*/, '')
537
+ get_current_option_parser.banner = get_current_option_parser.banner + "USAGE: #{msg}" + $INPUT_RECORD_SEPARATOR
524
538
  end
525
-
526
- # Tell the Drydock parser to ignore something.
527
- # Drydock will currently only listen to you if you tell it to "ignore :options",
539
+
540
+ # Tell the Drydock parser to ignore something.
541
+ # Drydock will currently only listen to you if you tell it to "ignore :options",
528
542
  # otherwise it will ignore you!
529
- #
543
+ #
530
544
  # +what+ the thing to ignore. When it equals :options Drydock will not parse
531
545
  # the command-specific arguments. It will pass the arguments directly to the
532
546
  # Command object. This is useful when you want to parse the arguments in some a way
533
- # that's too crazy, dangerous for Drydock to handle automatically.
547
+ # that's too crazy, dangerous for Drydock to handle automatically.
534
548
  def ignore(what=:nothing)
535
549
  @@command_opts_parser[@@command_index] = :ignore if what == :options || what == :all
536
550
  end
537
-
538
- # Define a global option. See +option+ for more info.
551
+
552
+ # Define a global option. See +option+ for more info.
539
553
  def global_option(*args, &b)
540
554
  args.unshift(@@global_opts_parser)
541
555
  @@global_option_names << option_parser(args, &b)
542
556
  end
543
557
  alias :global :global_option
544
-
545
- # Define a command-specific option.
546
- #
558
+
559
+ # Define a command-specific option.
560
+ #
547
561
  # +args+ is passed directly to OptionParser.on so it can contain anything
548
- # that's valid to that method. If a class is included, it will tell
549
- # OptionParser to expect a value otherwise it assumes a boolean value.
562
+ # that's valid to that method. If a class is included, it will tell
563
+ # OptionParser to expect a value otherwise it assumes a boolean value.
550
564
  # Some examples:
551
565
  #
552
566
  # option :h, :help, "Displays this message"
553
567
  # option '-l x,y,z', '--lang=x,y,z', Array, "Requested languages"
554
568
  #
555
- # You can also supply a block to fiddle with the values. The final
569
+ # You can also supply a block to fiddle with the values. The final
556
570
  # value becomes the option's value:
557
571
  #
558
572
  # option :m, :max, Integer, "Maximum threshold" do |v|
@@ -562,28 +576,28 @@ module Drydock
562
576
  #
563
577
  # All calls to +option+ must come before the command they're associated
564
578
  # to. Example:
565
- #
579
+ #
566
580
  # option :t, :tasty, "A boolean switch"
567
581
  # option :reason, String, "Requires a parameter"
568
- # command :task do |obj|;
582
+ # command :task do |obj|;
569
583
  # obj.options.tasty # => true
570
584
  # obj.options.reason # => I made the sandwich!
571
585
  # end
572
586
  #
573
587
  # When calling your script with a specific command-line option, the value
574
- # is available via obj.longname inside the command block.
588
+ # is available via obj.longname inside the command block.
575
589
  #
576
590
  def option(*args, &b)
577
591
  args.unshift(get_current_option_parser)
578
592
  current_command_option_names << option_parser(args, &b)
579
593
  end
580
-
594
+
581
595
  # Define a command-specific action.
582
596
  #
583
597
  # This is functionally very similar to option, but with an exciting and buoyant twist:
584
598
  # Drydock keeps track of actions for each command (in addition to treating it like an option).
585
- # When an action is specified on the command line Drydock looks for command_action or
586
- # action_command methods in the command class.
599
+ # When an action is specified on the command line Drydock looks for command_action or
600
+ # action_command methods in the command class.
587
601
  #
588
602
  # action :E, :eat, "Eat something"
589
603
  # command :oysters => Fresh::Oysters
@@ -594,13 +608,13 @@ module Drydock
594
608
  ret = option(*args, &b) # returns an array of all the current option names
595
609
  current_command_action << ret.last # the most recent is last
596
610
  end
597
-
598
- # Define a command.
599
- #
611
+
612
+ # Define a command.
613
+ #
600
614
  # command :task do
601
615
  # ...
602
616
  # end
603
- #
617
+ #
604
618
  # A custom command class can be specified using Hash syntax. The class
605
619
  # must inherit from Drydock::Command (class CustomeClass < Drydock::Command)
606
620
  #
@@ -610,7 +624,7 @@ module Drydock
610
624
  #
611
625
  def command(*cmds, &b)
612
626
  cmd = cmds.shift # Should we accept aliases here?
613
-
627
+
614
628
  if cmd.is_a? Hash
615
629
  klass = cmd.values.first
616
630
  names = cmd.keys.first
@@ -624,34 +638,34 @@ module Drydock
624
638
  else
625
639
  c = Drydock::Command.new(cmd, &b)
626
640
  end
627
-
641
+
628
642
  @@command_descriptions[@@command_index] ||= ""
629
643
  @@command_actions[@@command_index] ||= []
630
644
  @@command_argv_names[@@command_index] ||= []
631
-
645
+
632
646
  c.desc = @@command_descriptions[@@command_index]
633
647
  c.actions = @@command_actions[@@command_index]
634
648
  c.argv.fields = @@command_argv_names[@@command_index]
635
-
636
- # Default Usage Banner.
637
- # Without this, there's no help displayed for the command.
649
+
650
+ # Default Usage Banner.
651
+ # Without this, there's no help displayed for the command.
638
652
  option_parser = get_option_parser(@@command_index)
639
653
  if option_parser.is_a?(OptionParser) && option_parser.banner !~ /^USAGE/
640
654
  usage "#{c.executable} #{c.cmd}"
641
655
  end
642
-
656
+
643
657
  @@commands[c.cmd] = c
644
658
  @@command_index_map[c.cmd] = @@command_index
645
659
  @@command_index += 1 # This will point to the next command
646
-
647
- # Created aliases to the command using any additional command names
660
+
661
+ # Created aliases to the command using any additional command names
648
662
  # i.e. command :something, :sumpin => Something
649
663
  cmds.each { |aliaz| command_alias(cmd, aliaz); } unless cmds.empty?
650
-
664
+
651
665
  c # Return the Command object
652
666
  end
653
-
654
- # Used to create an alias to a defined command.
667
+
668
+ # Used to create an alias to a defined command.
655
669
  # Here's an example:
656
670
  #
657
671
  # command :task do; ...; end
@@ -663,45 +677,45 @@ module Drydock
663
677
  # $ yourscript pointer [options]
664
678
  #
665
679
  # Inside of the command definition, you have access to the
666
- # command name that was used via obj.alias.
680
+ # command name that was used via obj.alias.
667
681
  def alias_command(aliaz, cmd)
668
682
  return unless commands.has_key? cmd
669
683
  commands[canonize(aliaz)] = commands[cmd]
670
684
  end
671
-
672
- # Identical to +alias_command+ with reversed arguments.
673
- # For whatever reason I forget the order so Drydock supports both.
674
- # Tip: the argument order matches the method name.
685
+
686
+ # Identical to +alias_command+ with reversed arguments.
687
+ # For whatever reason I forget the order so Drydock supports both.
688
+ # Tip: the argument order matches the method name.
675
689
  def command_alias(cmd, aliaz)
676
690
  return unless commands.has_key? cmd
677
691
  commands[canonize(aliaz)] = commands[cmd]
678
692
  end
679
-
693
+
680
694
  # A hash of the currently defined Drydock::Command objects
681
695
  def commands
682
696
  @@commands
683
697
  end
684
-
698
+
685
699
  # An array of the currently defined commands names
686
700
  def command_names
687
701
  @@commands.keys.collect { |cmd| decanonize(cmd); }
688
702
  end
689
-
703
+
690
704
  # The trawler catches any and all unknown commands that pass through
691
- # Drydock. It's like the captain of aliases.
692
- # +cmd+ is the name of the command to direct unknowns to.
705
+ # Drydock. It's like the captain of aliases.
706
+ # +cmd+ is the name of the command to direct unknowns to.
693
707
  #
694
708
  # trawler :command_name
695
709
  #
696
710
  def trawler(cmd)
697
711
  @@trawler = cmd
698
712
  end
699
-
713
+
700
714
  # Has the trawler been set?
701
715
  def trawler?
702
716
  !@@trawler.nil? && !@@trawler.to_s.empty?
703
717
  end
704
-
718
+
705
719
  # Provide a description for a command
706
720
  def about(txt)
707
721
  @@command_descriptions += [txt]
@@ -711,74 +725,74 @@ module Drydock
711
725
  # Deprecated. Use about.
712
726
  def desc(txt)
713
727
  STDERR.puts "'desc' is deprecated. Please use 'about' instead."
714
- about(txt)
728
+ about(txt)
715
729
  end
716
-
717
- # Returns true if automatic execution is enabled.
730
+
731
+ # Returns true if automatic execution is enabled.
718
732
  def run?
719
733
  @@run && has_run? == false
720
734
  end
721
-
735
+
722
736
  # Disable automatic execution (enabled by default)
723
737
  #
724
738
  # Drydock.run = false
725
739
  def run=(v)
726
- @@run = (v.is_a?(TrueClass)) ? true : false
740
+ @@run = (v.is_a?(TrueClass)) ? true : false
727
741
  end
728
-
742
+
729
743
  # Return true if a command has been executed.
730
744
  def has_run?
731
745
  @@has_run
732
746
  end
733
-
747
+
734
748
  # Execute the given command.
735
749
  # By default, Drydock automatically executes itself and provides handlers for known errors.
736
750
  # You can override this functionality by calling +Drydock.run!+ yourself. Drydock
737
- # will only call +run!+ once.
738
- def run!(argv=[], stdin=STDIN)
751
+ # will only call +run!+ once.
752
+ def run!(argv=[], stdin=$stdin)
739
753
  return if has_run?
740
754
  @@has_run = true
741
755
  raise NoCommandsDefined.new if commands.empty?
742
-
756
+
743
757
  global_options, cmd_name, command_options, argv = process_arguments(argv)
744
758
  stdin = (defined? @@stdin_block) ? @@stdin_block.call(stdin, []) : stdin
745
-
759
+
746
760
  command_obj = get_command(cmd_name)
747
761
  command_obj.prepare(cmd_name, argv, stdin, global_options, command_options)
748
-
762
+
749
763
  # Execute before block
750
764
  @@before_block.call(command_obj) if defined? @@before_block
751
-
752
- # Execute the requested command. We'll capture STDERR or STDOUT if desired.
765
+
766
+ # Execute the requested command. We'll capture STDERR or STDOUT if desired.
753
767
  @@captured = capture? ? capture_io(@@capture) { command_obj.call } : command_obj.call
754
-
768
+
755
769
  # Execute after block
756
770
  @@after_block.call(command_obj) if defined? @@after_block
757
-
771
+
758
772
  rescue OptionParser::InvalidOption => ex
759
773
  raise Drydock::InvalidArgument.new(ex.args)
760
774
  rescue OptionParser::MissingArgument => ex
761
775
  raise Drydock::MissingArgument.new(ex.args)
762
776
  end
763
-
777
+
764
778
  def capture(io)
765
779
  @@capture = io
766
780
  end
767
-
781
+
768
782
  def captured
769
783
  @@captured
770
784
  end
771
-
785
+
772
786
  def capture?
773
787
  !@@capture.nil?
774
788
  end
775
-
776
- # Returns true if a command with the name +cmd+ has been defined.
789
+
790
+ # Returns true if a command with the name +cmd+ has been defined.
777
791
  def command?(cmd)
778
792
  name = canonize(cmd)
779
793
  @@commands.has_key? name
780
794
  end
781
-
795
+
782
796
  # Canonizes a string (+cmd+) to the symbol for command names
783
797
  # '-' is replaced with '_'
784
798
  def canonize(cmd)
@@ -786,15 +800,15 @@ module Drydock
786
800
  return cmd if cmd.kind_of?(Symbol)
787
801
  cmd.to_s.tr('-', '_').to_sym
788
802
  end
789
-
803
+
790
804
  # Returns a string version of +cmd+, decanonized.
791
805
  # Lowercase, '_' is replaced with '-'
792
806
  def decanonize(cmd)
793
807
  return unless cmd
794
808
  cmd.to_s.tr('_', '-')
795
809
  end
796
-
797
- # Capture STDOUT or STDERR to prevent it from being printed.
810
+
811
+ # Capture STDOUT or STDERR to prevent it from being printed.
798
812
  #
799
813
  # capture(:stdout) do
800
814
  # ...
@@ -805,27 +819,27 @@ module Drydock
805
819
  begin
806
820
  eval "$#{stream} = StringIO.new"
807
821
  block.call
808
- eval("$#{stream}").rewind # Otherwise we'll get nil
822
+ eval("$#{stream}").rewind # Otherwise we'll get nil
809
823
  result = eval("$#{stream}").read
810
824
  ensure
811
825
  eval "$#{stream} = #{stream.to_s.upcase}" # Put it back!
812
826
  end
813
827
  end
814
-
815
- private
816
-
828
+
829
+ private
830
+
817
831
  # Returns the Drydock::Command object with the name +cmd+
818
832
  def get_command(cmd)
819
833
  return unless command?(cmd)
820
834
  @@commands[canonize(cmd)]
821
- end
822
-
823
- # Processes calls to option and global_option. Symbols are converted into
824
- # OptionParser style strings (:h and :help become '-h' and '--help').
835
+ end
836
+
837
+ # Processes calls to option and global_option. Symbols are converted into
838
+ # OptionParser style strings (:h and :help become '-h' and '--help').
825
839
  def option_parser(args=[], &b)
826
840
  return if args.empty?
827
841
  opts_parser = args.shift
828
-
842
+
829
843
  arg_name = ''
830
844
  symbol_switches = []
831
845
  args.each_with_index do |arg, index|
@@ -839,7 +853,7 @@ module Drydock
839
853
  end
840
854
  end
841
855
  end
842
-
856
+
843
857
  if args.size == 1
844
858
  opts_parser.on(args.shift)
845
859
  else
@@ -848,24 +862,23 @@ module Drydock
848
862
  result = (b.nil?) ? v : b.call(*block_args[0..(b.arity-1)])
849
863
  end
850
864
  end
851
-
865
+
852
866
  arg_name
853
867
  end
854
-
855
-
856
- # Split the +argv+ array into global args and command args and
857
- # find the command name.
868
+
869
+ # Split the +argv+ array into global args and command args and
870
+ # find the command name.
858
871
  # i.e. ./script -H push -f (-H is a global arg, push is the command, -f is a command arg)
859
872
  # returns [global_options, cmd, command_options, argv]
860
873
  def process_arguments(argv=[])
861
874
  global_options = command_options = {}
862
- cmd = nil
863
-
875
+ cmd = nil
876
+
864
877
  argv_copy = argv.clone # See: @@default_command_with_args below
865
-
878
+
866
879
  global_options = @@global_opts_parser.getopts(argv)
867
880
  cmd_name = (argv.empty?) ? @@default_command : argv.shift
868
-
881
+
869
882
  unless command?(cmd_name)
870
883
  # If requested, send all unknown arguments to the default command
871
884
  if @@default_command_with_args
@@ -877,85 +890,56 @@ module Drydock
877
890
  command_alias(@@trawler, cmd_name)
878
891
  end
879
892
  end
880
-
881
- cmd = get_command(cmd_name)
882
-
893
+
894
+ cmd = get_command(cmd_name)
895
+
883
896
  command_parser = @@command_opts_parser[get_command_index(cmd.cmd)]
884
897
  command_options = {}
885
-
898
+
886
899
  # We only need to parse the options out of the arguments when
887
- # there are args available, there is a valid parser, and
888
- # we weren't requested to ignore the options.
900
+ # there are args available, there is a valid parser, and
901
+ # we weren't requested to ignore the options.
889
902
  if !argv.empty? && command_parser && command_parser != :ignore
890
903
  command_options = command_parser.getopts(argv)
891
904
  end
892
-
905
+
893
906
  [global_options, cmd_name, command_options, argv]
894
907
  end
895
-
896
908
 
897
909
  # Grab the current list of command-specific option names. This is a list of the
898
- # long names.
910
+ # long names.
899
911
  def current_command_option_names
900
912
  (@@command_option_names[@@command_index] ||= [])
901
913
  end
902
-
914
+
903
915
  def current_command_action
904
916
  (@@command_actions[@@command_index] ||= [])
905
917
  end
906
-
918
+
907
919
  def get_command_index(cmd)
908
920
  @@command_index_map[canonize(cmd)] || -1
909
921
  end
910
-
922
+
911
923
  # Grab the options parser for the current command or create it if it doesn't exist.
912
924
  # Returns an instance of OptionParser.
913
925
  def get_current_option_parser
914
926
  (@@command_opts_parser[@@command_index] ||= OptionParser.new)
915
927
  end
916
-
917
- # Grabs the options parser for the given command.
928
+
929
+ # Grabs the options parser for the given command.
918
930
  # +arg+ can be an index or command name.
919
931
  # Returns an instance of OptionParser.
920
932
  def get_option_parser(arg)
921
933
  index = arg.is_a?(String) ? get_command_index(arg) : arg
922
934
  (@@command_opts_parser[index] ||= OptionParser.new)
923
935
  end
924
-
936
+
925
937
  #
926
938
  # These are the "reel" defaults
927
939
  #
928
- @@global_opts_parser.banner = " Try: #{$0} show-commands"
929
- @@global_opts_parser.on "Usage: #{$0} [global options] COMMAND [command options] #{$/}"
940
+ @@global_opts_parser.banner = " Try: #{$PROGRAM_NAME} show-commands"
941
+ @@global_opts_parser.on "Usage: #{$PROGRAM_NAME} [global options] COMMAND [command options] #{$INPUT_RECORD_SEPARATOR}"
930
942
  @@command_descriptions = ["Display available commands with descriptions"]
931
943
  @@default_command = Drydock.command(:show_commands).cmd
932
-
933
- end
934
944
 
935
- __END__
936
-
937
- at_exit {
938
- begin
939
- if $@
940
- puts $@ if Drydock.debug?
941
- exit 1
942
- end
943
- Drydock.run!(ARGV, STDIN) if Drydock.run? && !Drydock.has_run?
944
- rescue Drydock::ArgError, Drydock::OptError=> ex
945
- STDERR.puts ex.message
946
- STDERR.puts ex.usage
947
- rescue Drydock::UnknownCommand => ex
948
- STDERR.puts ex.message
949
- STDERR.puts ex.backtrace if Drydock.debug?
950
- rescue => ex
951
- STDERR.puts "ERROR (#{ex.class.to_s}): #{ex.message}"
952
- STDERR.puts ex.backtrace if Drydock.debug?
953
- rescue Interrupt
954
- puts "#{$/}Exiting... "
955
- exit 1
956
- rescue SystemExit
957
- # Don't balk
958
- end
959
- }
960
-
961
-
945
+ end