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.
- checksums.yaml +7 -0
- data/CHANGES.txt +26 -21
- data/LICENSE.txt +2 -2
- data/README.md +92 -0
- data/Rakefile +40 -58
- data/bin/example +92 -96
- data/drydock.gemspec +41 -37
- data/lib/drydock.rb +265 -281
- metadata +52 -48
- 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
|
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
|
-
|
52
|
-
def
|
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
|
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
|
-
|
85
|
-
|
86
|
-
|
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
|
-
|
102
|
+
# The name used to evoke this command (it's either the canonical name or the alias used).
|
90
103
|
attr_reader :alias
|
91
|
-
|
92
|
-
|
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
|
-
|
107
|
+
# An OpenStruct object containing the command options specified at run-time.
|
95
108
|
attr_reader :option
|
96
|
-
|
109
|
+
# An OpenStruct object containing the global options specified at run-time.
|
97
110
|
attr_reader :global
|
98
|
-
|
111
|
+
# A friendly description of the command.
|
99
112
|
attr_accessor :desc
|
100
|
-
|
113
|
+
# An array of action names specified in the command definition
|
101
114
|
attr_accessor :actions
|
102
|
-
|
103
|
-
|
104
|
-
attr_accessor :argv
|
105
|
-
|
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
|
-
|
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, &
|
118
|
-
@cmd =
|
119
|
-
@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 =
|
135
|
+
@stdin = $stdin
|
123
136
|
@option = OpenStruct.new
|
124
137
|
@global = OpenStruct.new
|
125
|
-
@executable = File.basename($
|
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
|
-
|
165
|
+
global.send("#{n}=", v) # Populate the object's globals
|
153
166
|
end
|
154
|
-
|
167
|
+
|
155
168
|
options.each_pair do |n,v|
|
156
|
-
|
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
|
-
|
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
|
-
|
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(
|
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
|
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
|
-
|
197
|
-
|
198
|
-
# No block and no action. We'll try for the method name in the Drydock::Command class.
|
199
|
-
elsif
|
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
|
-
|
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
|
-
|
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 =
|
230
|
-
raise "Your request is not valid. See #{$
|
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
|
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
|
289
|
-
puts
|
290
|
-
puts
|
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
|
294
|
-
if @global.verbose
|
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
|
-
|
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.
|
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 =
|
523
|
-
get_current_option_parser.banner
|
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
|
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
|
-
#
|
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: #{$
|
929
|
-
@@global_opts_parser.on "Usage: #{$
|
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
|
-
|
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
|