bone 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,92 @@
1
+ = Drydock - v0.6
2
+
3
+ <b>Build seaworthy command-line apps like a Captain with a powerful Ruby DSL.</b>
4
+
5
+ == Overview
6
+
7
+ Drydock is a seaworthy DSL for building really powerful command line applications. The core class is contained in a single .rb file so it's easy to copy directly into your project. See below for examples.
8
+
9
+ == Install
10
+
11
+ One of:
12
+
13
+ * gem install drydock
14
+ * copy lib/drydock.rb into your lib directory.
15
+
16
+ Or for GitHub fans:
17
+
18
+ * git clone git://github.com/delano/drydock.git
19
+ * gem install delano-drydock
20
+
21
+ == Examples
22
+
23
+ See bin/example for more.
24
+
25
+ require 'drydock'
26
+ extend Drydock
27
+
28
+ default :welcome
29
+
30
+ before do
31
+ # You can execute a block before the requests command is executed. Instance
32
+ # variables defined here will be available to all commands.
33
+ end
34
+
35
+ about "A friendly welcome to the Drydock"
36
+ command :welcome do
37
+ puts "Welcome to Drydock."
38
+ puts "For available commands:"
39
+ puts "#{$0} show-commands"
40
+ end
41
+
42
+ usage "USAGE: #{$0} laugh [-f]"
43
+ about "The captain commands his crew to laugh"
44
+ option :f, :faster, "A boolean value. Go even faster!"
45
+ command :laugh do |obj|
46
+ # +obj+ is an instance of Drydock::Command. The options you define are available
47
+ # via obj.option.name
48
+
49
+ answer = !obj.option.faster ? "Sort of" : "Yes! I'm literally laughing as fast as possible."
50
+
51
+ puts "Captain Stubing: Are you laughing?"
52
+ puts "Dr. Bricker: " << answer
53
+ end
54
+
55
+
56
+ class JohnWestSmokedOysters < Drydock::Command
57
+ # You can write your own command classes by inheriting from Drydock::Command
58
+ # and referencing it in the command definition.
59
+ def ahoy!; p "matey"; end
60
+ end
61
+
62
+ about "Do something with John West's Smoked Oysters"
63
+ command :oysters => JohnWestSmokedOysters do |obj|
64
+ p obj # => #<JohnWestSmokedOysters:0x42179c ... >
65
+ end
66
+
67
+ about "My way of saying hello!"
68
+ command :ahoy! => JohnWestSmokedOysters
69
+ # If you don't provide a block, Drydock will call JohnWestSmokedOysters#ahoy!
70
+
71
+ Drydock.run!
72
+
73
+
74
+ == More Information
75
+
76
+ * GitHub[http://github.com/delano/drydock]
77
+ * RDocs[http://drydock.rubyforge.org/]
78
+ * Inspiration[http://www.youtube.com/watch?v=m_wFEB4Oxlo]
79
+
80
+ == Thanks
81
+
82
+ * Solutious Inc for putting up with my endless references to the sea! (http://solutious.com)
83
+ * Blake Mizerany for the inspiration via bmizerany-frylock[http://github.com/bmizerany/frylock]
84
+
85
+ == Credits
86
+
87
+ * Delano Mandelbaum (delano@solutious.com)
88
+ * Bernie Kopell (bernie@solutious.com)
89
+
90
+ == License
91
+
92
+ See LICENSE.txt
@@ -0,0 +1,74 @@
1
+ require 'rubygems'
2
+ require 'rake/clean'
3
+ require 'rake/gempackagetask'
4
+ require 'hanna/rdoctask'
5
+ require 'fileutils'
6
+ include FileUtils
7
+
8
+ task :default => :test
9
+
10
+ # SPECS ===============================================================
11
+
12
+ desc 'Run specs with unit test style output'
13
+ task :test do |t|
14
+ sh "ruby test/*_test.rb"
15
+ end
16
+
17
+ desc 'Run bin/example and tryouts'
18
+ task :tryouts do |t|
19
+ sh "ruby bin/example"
20
+ end
21
+
22
+ # PACKAGE =============================================================
23
+
24
+ name = "drydock"
25
+ load "#{name}.gemspec"
26
+
27
+ version = @spec.version
28
+
29
+ Rake::GemPackageTask.new(@spec) do |p|
30
+ p.need_tar = true if RUBY_PLATFORM !~ /mswin/
31
+ end
32
+
33
+ task :release => [ :rdoc, :package ]
34
+
35
+ task :install => [ :rdoc, :package ] do
36
+ sh %{sudo gem install pkg/#{name}-#{version}.gem}
37
+ end
38
+
39
+ task :uninstall => [ :clean ] do
40
+ sh %{sudo gem uninstall #{name}}
41
+ end
42
+
43
+
44
+ # Rubyforge Release / Publish Tasks ==================================
45
+
46
+ desc 'Publish website to rubyforge'
47
+ task 'publish:rdoc' => 'doc/index.html' do
48
+ sh "scp -rp doc/* rubyforge.org:/var/www/gforge-projects/#{name}/"
49
+ end
50
+
51
+ task 'publish:gem' => [:package] do |t|
52
+ sh <<-end
53
+ rubyforge add_release -o Any -a CHANGES.txt -f -n README.rdoc #{name} #{name} #{@spec.version} pkg/#{name}-#{@spec.version}.gem &&
54
+ rubyforge add_file -o Any -a CHANGES.txt -f -n README.rdoc #{name} #{name} #{@spec.version} pkg/#{name}-#{@spec.version}.tgz
55
+ end
56
+ end
57
+
58
+
59
+ Rake::RDocTask.new do |t|
60
+ t.rdoc_dir = 'doc'
61
+ t.title = @spec.summary
62
+ t.options << '--line-numbers' << '-A cattr_accessor=object'
63
+ t.options << '--charset' << 'utf-8'
64
+ t.rdoc_files.include('LICENSE.txt')
65
+ t.rdoc_files.include('README.rdoc')
66
+ t.rdoc_files.include('CHANGES.txt')
67
+ t.rdoc_files.include('bin/*')
68
+ t.rdoc_files.include('lib/*.rb')
69
+ end
70
+
71
+ CLEAN.include [ 'pkg', '*.gem', '.config', 'doc' ]
72
+
73
+
74
+
@@ -0,0 +1,210 @@
1
+ #!/usr/bin/ruby
2
+
3
+ # Seafaring Drydock Examples
4
+ #
5
+ # This is a functioning script so you can copy it, run it,
6
+ # and just generally be a longshoreman about things. This is
7
+ # a drydock after all.
8
+ #
9
+ # If you're reading this via the Rdocs you won't see the code. See:
10
+ #
11
+ # http://github.com/delano/drydock/blob/master/bin/example
12
+ #
13
+ # For an example of a complex command-line application using
14
+ # Drydock, see:
15
+ #
16
+ # http://github.com/solutious/rudy/blob/master/bin/rudy
17
+ #
18
+
19
+ $:.unshift File.expand_path(File.join(File.dirname(__FILE__), '..')), 'lib'
20
+
21
+ require 'drydock'
22
+
23
+ module Example
24
+ extend Drydock # Tell Drydock you want its methods!
25
+
26
+ default :welcome # The welcome command will be run if no command is given
27
+ capture :stderr # Drydock will capture STDERR and keep it in the hold.
28
+ # You can use this to suppress errors.
29
+
30
+ about "A friendly welcome to the Drydock"
31
+ command :welcome do
32
+ puts "Welcome to Drydock.", $/
33
+ puts "For available commands: #{$0} show-commands"
34
+ end
35
+
36
+ usage "USAGE: #{$0} laugh [-f]"
37
+ about "The captain commands his crew to laugh"
38
+ option :f, :faster, "A boolean value. Go even faster!"
39
+ command :laugh do |obj|
40
+ # +obj+ is an instance of Drydock::Command. The options you define are available
41
+ # via obj.option.name
42
+
43
+ answer = !obj.option.faster ? "Sort of" : "Yes! I'm literally laughing as fast as possible."
44
+
45
+ puts "Captain Stubing: Are you laughing?"
46
+ puts "Dr. Bricker: " << answer
47
+ end
48
+
49
+ global_usage "USAGE: #{File.basename($0)} [global options] command [command options]"
50
+ global :s, :seconds, "Display values in seconds"
51
+ global :v, :verbose, "Verbosity level (i.e. -vvv is greater than -v)" do |v|
52
+ # Use instance variables to maintain values between option blocks.
53
+ # This will increment for every -v found (i.e. -vvv)
54
+ @val ||= 0
55
+ @val += 1
56
+ end
57
+
58
+ before do |obj|
59
+ # You can execute a block before the requests command is executed. Instance
60
+ # variables defined here will be available to all commands.
61
+ # +obj+ is a reference to the command object, just like in command blocks.
62
+ end
63
+
64
+ after do |obj|
65
+ # And this will be called after the command.
66
+ end
67
+
68
+ usage "#{$0} [-s] [-vv] date"
69
+ about "Display the current date"
70
+ command :date do |obj|
71
+ require 'time'
72
+ now = Time.now
73
+ puts "(Not verbose enough. Try adding a -v.)" if (obj.global.verbose || 0) == 1
74
+ puts "More verbosely, the date is now: " if (obj.global.verbose || 0) >= 2
75
+ puts (obj.global.seconds) ? now.to_i : now.to_s
76
+ end
77
+
78
+
79
+ ignore :options
80
+ about "This command ignores options"
81
+ command :rogue do |obj|
82
+ # You can use ignore :options to tell Drydock to not process the
83
+ # command-specific options.
84
+ # Unnamed arguments are available from obj.argv
85
+ if obj.argv.empty?
86
+ puts "Had you supplied some arguments, I would have ignored them."
87
+ else
88
+ puts "Hi! You supplied some arguments but I ignored them."
89
+ puts "They're all still here in this array: %s" % obj.argv.join(', ')
90
+ end
91
+ end
92
+
93
+ class JohnWestSmokedOysters < Drydock::Command
94
+ # You can write your own command classes by inheriting from Drydock::Command
95
+ # and referencing it in the command definition.
96
+ def ahoy!; p "matey"; end
97
+ end
98
+
99
+ about "Do something with John West's Smoked Oysters"
100
+ command :oysters => JohnWestSmokedOysters do |obj|
101
+ p obj # => #<JohnWestSmokedOysters:0x42179c ... >
102
+ end
103
+
104
+ about "My way of saying hello!"
105
+ command [:ahoy!, :hello!] => JohnWestSmokedOysters
106
+ # If you don't provide a block, Drydock will call JohnWestSmokedOysters#ahoy!
107
+
108
+
109
+ require 'yaml'
110
+
111
+ usage 'ruby bin/example uri -c -d " " -t 15 http://solutious.com/'
112
+ usage 'echo "http://solutious.com/" | ruby bin/example uri -c -d " " -t 15'
113
+ about "Check for broken URIs"
114
+ option :c, :check, "Check response codes for each URI"
115
+ option :d, :delim, String, "Output delimiter"
116
+ option :t, :timeout, Float, "Timeout value for HTTP request" do |v|
117
+ # You can provide an block to process the option value.
118
+ # This block must return the final value.
119
+ v = 10 if (v > 10)
120
+ v
121
+ end
122
+ argv :uris
123
+
124
+ command :uri do |obj|
125
+ # This command processes the output of the stdin block (below this definition).
126
+ # The output of that block is available as obj.stdin. If there is no stdin block
127
+ # obj.stdin will be STDIN's IO object.
128
+
129
+ require 'net/http'
130
+ require 'uri'
131
+ require 'timeout'
132
+
133
+ uris = []
134
+ uris += obj.stdin if obj.stdin
135
+ uris += obj.argv.uris if obj.argv.uris
136
+
137
+ delim = obj.option.delim || ','
138
+ timeout = obj.option.timeout || 5
139
+ code = :notchecked # The default code when :check is false
140
+
141
+ if uris.empty?
142
+ puts "Frylock: You didn't provide any URIs. "
143
+ puts "Master Shake: Ya, see #{$0} #{obj.alias} -h"
144
+ exit 0
145
+ end
146
+
147
+ uris.each_with_index do |uri, index|
148
+ code = response_code(uri, timeout) if (obj.option.check)
149
+ puts [index+1, uri, code].join(delim)
150
+ end
151
+
152
+ end
153
+
154
+ about "Prints the alias used to access the command"
155
+ # We can define command aliases by providing a list of command
156
+ # names. The first name is still consider to be the main name.
157
+ command :printalias, :reveal do |obj|
158
+ puts "This is printalias!"
159
+ if (obj.alias == obj.cmd)
160
+ puts "You did not use an alias"
161
+ else
162
+ puts "You used the alias " << obj.alias
163
+ end
164
+ end
165
+
166
+ stdin do |stdin, output|
167
+ # Pre-process STDIN for all commands. This example returns an array of lines.
168
+ # The command processuris uses this array.
169
+
170
+ # We only want piped data. If this is not included
171
+ # execution will wait for input from the user.
172
+ unless stdin.tty?
173
+
174
+ while !stdin.eof? do
175
+ line = stdin.readline
176
+ line.chomp!
177
+ (output ||= []) << line
178
+ end
179
+
180
+ end
181
+ output
182
+ end
183
+
184
+
185
+ # And one final feature for the intrepid swabbies like myself.
186
+ # Drydock can handle unknown commands by catching them with a
187
+ # trawler. It's like the captain of all aliases. Just specify
188
+ # the command name to direct all unknown commands to. Simple!
189
+ trawler :printalias
190
+
191
+
192
+ # Return the HTTP response code for the given URI. Used by
193
+ # uri command.
194
+ #
195
+ # +uri+ A valid HTTP URI
196
+ # +duration+ The timeout threshold (in seconds) for the request.
197
+ def response_code(uri_str, duration=5) #:nodoc:
198
+ response = :unavailable
199
+ begin
200
+ uri = (uri_str.kind_of? URI::HTTP) ? uri_str : URI.parse(uri_str)
201
+ timeout(duration) do
202
+ response = Net::HTTP.get_response(uri).code
203
+ end
204
+ rescue Exception => ex
205
+ end
206
+ response
207
+ end
208
+ end
209
+
210
+ Drydock.run!
@@ -0,0 +1,38 @@
1
+ @spec = Gem::Specification.new do |s|
2
+ s.name = %q{drydock}
3
+ s.version = "0.6.8"
4
+ s.specification_version = 1 if s.respond_to? :specification_version=
5
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
6
+
7
+ s.authors = ["Delano Mandelbaum"]
8
+ s.description = %q{Build seaworthy command-line apps like a Captain with a powerful Ruby DSL.}
9
+ s.summary = s.description
10
+ s.email = %q{delano@solutious.com}
11
+
12
+ # = MANIFEST =
13
+ # git ls-files
14
+ s.files = %w(
15
+ CHANGES.txt
16
+ LICENSE.txt
17
+ README.rdoc
18
+ Rakefile
19
+ bin/example
20
+ drydock.gemspec
21
+ lib/drydock.rb
22
+ lib/drydock/console.rb
23
+ lib/drydock/mixins.rb
24
+ lib/drydock/mixins/object.rb
25
+ lib/drydock/mixins/string.rb
26
+ lib/drydock/screen.rb
27
+ )
28
+
29
+ # s.add_dependency ''
30
+
31
+ s.has_rdoc = true
32
+ s.homepage = %q{http://github.com/delano/drydock}
33
+ s.extra_rdoc_files = %w[README.rdoc LICENSE.txt CHANGES.txt]
34
+ s.rdoc_options = ["--line-numbers", "--title", "Drydock: #{s.description}", "--main", "README.rdoc"]
35
+ s.require_paths = ["lib"]
36
+ s.rubygems_version = %q{1.1.1}
37
+ s.rubyforge_project = "drydock"
38
+ end
@@ -0,0 +1,961 @@
1
+ require 'optparse'
2
+ require 'ostruct'
3
+
4
+ require 'stringio'
5
+
6
+ module Drydock
7
+ require 'drydock/mixins'
8
+
9
+ autoload :Screen, 'drydock/screen'
10
+ autoload :Console, 'drydock/console'
11
+
12
+ class FancyArray < Array #:nodoc:
13
+ attr_reader :fields
14
+ def add_field(n)
15
+ @fields ||= []
16
+ field_name = n
17
+ eval <<-RUBY, binding, '(Drydock::FancyArray)', 1
18
+ def #{n}
19
+ if self.size > @fields.size && '#{n}'.to_sym == @fields.last
20
+ self[#{@fields.size}..-1]
21
+ else
22
+ self[#{@fields.size}]
23
+ end
24
+ end
25
+ def #{n}=(val)
26
+ if self.size > @fields.size && '#{n}'.to_sym == @fields.last
27
+ self[#{@fields.size}..-1] = val
28
+ else
29
+ self[#{@fields.size}] = val
30
+ end
31
+ end
32
+ RUBY
33
+ @fields << n
34
+ n
35
+ end
36
+ def fields=(*args)
37
+ args.flatten.each do |field|
38
+ add_field(field)
39
+ end
40
+ end
41
+ end
42
+
43
+ class ArgError < RuntimeError
44
+ attr_reader :arg, :cmd, :msg
45
+ def initialize(*args)
46
+ @msg = args.shift if args.size == 1
47
+ @arg, @cmd, @msg = *args
48
+ @cmd ||= 'COMMAND'
49
+ @msg = nil if @msg && @msg.empty?
50
+ end
51
+ def message; @msg || "Error: No #{@arg} provided"; end
52
+ def usage; "See: #{$0} #{@cmd} -h"; end
53
+ end
54
+ class OptError < ArgError
55
+ def message; @msg || "Error: No #{@arg} provided"; end
56
+ end
57
+
58
+ # The base class for all command objects. There is an instance of this class
59
+ # for every command defined. Global and command-specific options are added
60
+ # as attributes to this class dynamically.
61
+ #
62
+ # i.e. "example -v select --location kumamoto"
63
+ #
64
+ # global :v, :verbose, "I want mooooore!"
65
+ # option :l, :location, String, "Source location"
66
+ # command :select do |obj|
67
+ # puts obj.global.verbose #=> true
68
+ # puts obj.option.location #=> "kumamoto"
69
+ # end
70
+ #
71
+ # You can sub-class it to create your own:
72
+ #
73
+ # class Malpeque < Drydock::Command
74
+ # # ... sea to it
75
+ # end
76
+ #
77
+ # And then specify your class in the command definition:
78
+ #
79
+ # command :eat => Malpeque do |obj|
80
+ # # ... do stuff with your obj
81
+ # end
82
+ #
83
+ class Command
84
+ VERSION = "0.6.8"
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.
88
+ attr_reader :cmd
89
+ # The name used to evoke this command (it's either the canonical name or the alias used).
90
+ 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.
93
+ attr_reader :b
94
+ # An OpenStruct object containing the command options specified at run-time.
95
+ attr_reader :option
96
+ # An OpenStruct object containing the global options specified at run-time.
97
+ attr_reader :global
98
+ # A friendly description of the command.
99
+ attr_accessor :desc
100
+ # An array of action names specified in the command definition
101
+ 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.
106
+ attr_reader :stdin
107
+ # The basename of the executable or script: File.basename($0)
108
+ attr_reader :executable
109
+
110
+ # The default constructor sets the short name of the command
111
+ # and stores a reference to the block (if supplied).
112
+ # You don't need to override this method to add functionality
113
+ # to your custom Command classes. Define an +init+ method instead.
114
+ # It will be called just before the block is executed.
115
+ # +cmd+ is the short name of this command.
116
+ # +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
120
+ @actions = []
121
+ @argv = Drydock::FancyArray.new # an array with field names
122
+ @stdin = STDIN
123
+ @option = OpenStruct.new
124
+ @global = OpenStruct.new
125
+ @executable = File.basename($0)
126
+ @global.verbose = 0
127
+ @global.quiet = false
128
+ end
129
+
130
+ # Returns the command name (not the alias)
131
+ def name
132
+ @cmd
133
+ end
134
+
135
+ # Prepare this command object to be called.
136
+ #
137
+ # Calls self.init after setting attributes (if the method exists). You can
138
+ # implement an init method in your subclasses of Drydock::Command to handle
139
+ # your own initialization stuff.
140
+ #
141
+ # <li>+cmd_str+ is the short name used to evoke this command. It will equal @cmd
142
+ # unless an alias was used used to evoke this command.</li>
143
+ # <li>+argv+ an array of unnamed arguments. If ignore :options was declared this</li>
144
+ # will contain the arguments exactly as they were defined on the command-line.</li>
145
+ # <li>+stdin+ contains the output of stdin do; ...; end otherwise it's a STDIN IO handle.</li>
146
+ # <li>+global_options+ a hash of the global options specified on the command-line</li>
147
+ # <li>+options+ a hash of the command-specific options specific on the command-line.</li>
148
+ def prepare(cmd_str=nil, argv=[], stdin=[], global_options={}, options={})
149
+ @alias = cmd_str.nil? ? @cmd : cmd_str
150
+
151
+ global_options.each_pair do |n,v|
152
+ self.global.send("#{n}=", v) # Populate the object's globals
153
+ end
154
+
155
+ options.each_pair do |n,v|
156
+ self.option.send("#{n}=", v) # ... and also the command options
157
+ end
158
+
159
+ @argv << argv # TODO: Using += returns an Array instead of FancyArray
160
+ @argv.flatten! # NOTE: << creates @argv[[]]
161
+ @stdin = stdin
162
+
163
+ self.init if self.respond_to? :init # Must be called first!
164
+
165
+ end
166
+
167
+ # Calls the command in the following order:
168
+ #
169
+ # * print_header
170
+ # * validation (if methodname_valid? exists)
171
+ # * command block (@b)
172
+ # * print_footer
173
+ #
174
+ def call
175
+ self.print_header if self.respond_to? :print_header
176
+
177
+ # Execute the command block if it exists
178
+ if @b
179
+ run_validation
180
+ @b.call(self)
181
+
182
+ # Otherwise check to see if an action was specified
183
+ elsif !(chosen = find_action(self.option)).empty?
184
+ raise "Only one action at a time please! I can't #{chosen.join(' AND ')}." if chosen.size > 1
185
+ criteria = [[@cmd, chosen.first], [chosen.first, @cmd]]
186
+ meth = name = nil
187
+ # Try command_action, then action_command
188
+ criteria.each do |tuple|
189
+ name = tuple.join('_')
190
+ meth = name if self.respond_to?(name)
191
+ end
192
+
193
+ raise "#{self.class} needs a #{name} method!" unless meth
194
+
195
+ 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
200
+ run_validation(@cmd)
201
+ self.send(@cmd)
202
+
203
+ # Well, then I have no idea what you want me to do!
204
+ else
205
+ raise "The command #{@alias} has no block and #{self.class} has no #{@cmd} method!"
206
+ end
207
+
208
+ self.print_footer if respond_to? :print_footer
209
+ end
210
+
211
+ # <li>+meth+ The method name used to determine the name of the validation method.
212
+ # If not supplied, the validation method is "valid?" otherwise it's "meth_valid?"</li>
213
+ # If the command class doesn't have the given validation method, we'll just continue
214
+ # on our way.
215
+ #
216
+ # Recognized validation methods are:
217
+ #
218
+ # def valid? # if we're executing a command block
219
+ # def command_valid? # if we're executing an object method
220
+ # def command_action_valid? # if the main meth is command_action
221
+ # def action_command_valid? # if the main meth is action_command
222
+ #
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>.
226
+ #
227
+ def run_validation(meth=nil)
228
+ 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
231
+ end
232
+ private :run_validation
233
+
234
+ # 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
237
+ # OptionParser#getopts)</li>
238
+ # Returns an array of action names (empty if no action was supplied)
239
+ def find_action(options)
240
+ options = options.marshal_dump if options.is_a?(OpenStruct)
241
+ boolkeys = options.keys.select { |n| options[n] == true } || []
242
+ boolkeys = boolkeys.collect { |n| n.to_s } # @agents contains Strings.
243
+ # Returns the elements in @actions that are also found in boolkeys
244
+ (@actions || []) & boolkeys
245
+ end
246
+ 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
250
+ # can also write your own Drydock::Command#show_commands to override
251
+ # this default behaviour.
252
+ #
253
+ # The output was worked on here:
254
+ # http://etherpad.com/SXjqQGRr8M
255
+ #
256
+ def show_commands
257
+ project = " for #{Drydock.project}" if Drydock.project?
258
+ cmds = {}
259
+ Drydock.commands.keys.each do |cmd|
260
+ next if cmd == :show_commands
261
+ pretty = Drydock.decanonize(cmd)
262
+ # Out to sea
263
+ cmds[Drydock.commands[cmd].cmd] ||= {}
264
+ unless cmd === Drydock.commands[cmd].cmd
265
+ (cmds[Drydock.commands[cmd].cmd][:aliases] ||= []) << pretty
266
+ next
267
+ end
268
+ cmds[cmd][:desc] = Drydock.commands[cmd].desc
269
+ cmds[cmd][:desc] = nil if cmds[cmd][:desc] && cmds[cmd][:desc].empty?
270
+ cmds[cmd][:pretty] = pretty
271
+ end
272
+
273
+ cmd_names_sorted = cmds.keys.sort{ |a,b| a.to_s <=> b.to_s }
274
+
275
+ if @global.quiet
276
+ puts "Commands: "
277
+ line = []
278
+ cmd_names_sorted.each_with_index do |cmd,i|
279
+ line << cmd
280
+ if (line.size % 4 == 0) || i == (cmd_names_sorted.size - 1)
281
+ puts " %s" % line.join(', ')
282
+ line.clear
283
+ end
284
+ end
285
+ return
286
+ 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"]
291
+ puts
292
+
293
+ puts "Commands: "
294
+ if @global.verbose > 0
295
+ puts # empty line
296
+ cmd_names_sorted.each do |cmd|
297
+ puts "$ %s" % [@executable] if Drydock.default?(cmd)
298
+ puts "$ %s %s" % [@executable, cmds[cmd][:pretty]]
299
+ puts "%10s: %s" % ["About", cmds[cmd][:desc]] if cmds[cmd][:desc]
300
+ if cmds[cmd][:aliases]
301
+ cmds[cmd][:aliases].sort!{ |a,b| a.size <=> b.size }
302
+ puts "%10s: %s" % ["Aliases", cmds[cmd][:aliases].join(', ')]
303
+ end
304
+ puts
305
+ end
306
+
307
+ else
308
+ cmd_names_sorted.each do |cmd|
309
+ aliases = cmds[cmd][:aliases] || []
310
+ aliases.sort!{ |a,b| a.size <=> b.size }
311
+ aliases = aliases.empty? ? '' : "(aliases: #{aliases.join(', ')})"
312
+ pattern = Drydock.default?(cmd) ? "* %-16s %s" : " %-16s %s"
313
+ puts pattern % [cmds[cmd][:pretty], aliases]
314
+ end
315
+ end
316
+ end
317
+
318
+ # The name of the command
319
+ def to_s
320
+ @cmd.to_s
321
+ end
322
+ end
323
+ end
324
+
325
+ module Drydock
326
+ class UnknownCommand < RuntimeError
327
+ attr_reader :name
328
+ def initialize(name)
329
+ @name = name || :unknown
330
+ end
331
+ def message
332
+ "Unknown command: #{@name}"
333
+ end
334
+ end
335
+ class NoCommandsDefined < RuntimeError
336
+ def message
337
+ "No commands defined"
338
+ end
339
+ end
340
+ class InvalidArgument < RuntimeError
341
+ attr_accessor :args
342
+ def initialize(args)
343
+ @args = args || []
344
+ end
345
+ def message
346
+ "Unknown option: #{@args.join(", ")}"
347
+ end
348
+ end
349
+ class MissingArgument < InvalidArgument
350
+ def message
351
+ "Option requires a value: #{@args.join(", ")}"
352
+ end
353
+ end
354
+ end
355
+
356
+ # Drydock is a DSL for command-line apps.
357
+ # See bin/example for usage examples.
358
+ module Drydock
359
+ extend self
360
+
361
+ VERSION = 0.6
362
+
363
+ @@project = nil
364
+
365
+ @@debug = false
366
+ @@has_run = false
367
+ @@run = true
368
+
369
+ @@global_opts_parser = OptionParser.new
370
+ @@global_option_names = []
371
+
372
+ @@command_opts_parser = []
373
+ @@command_option_names = []
374
+ @@command_actions = []
375
+
376
+ @@default_command = nil
377
+ @@default_command_with_args = false
378
+
379
+ @@commands = {}
380
+ @@command_descriptions = []
381
+ @@command_index = 0
382
+ @@command_index_map = {}
383
+ @@command_argv_names = [] # an array of names for values of argv
384
+
385
+ @@capture = nil # contains one of :stdout, :stderr
386
+ @@captured = nil
387
+
388
+ @@trawler = nil
389
+
390
+ public
391
+ # Enable or disable debug output.
392
+ #
393
+ # debug :on
394
+ # debug :off
395
+ #
396
+ # Calling without :on or :off will toggle the value.
397
+ #
398
+ def debug(toggle=false)
399
+ if toggle.is_a? Symbol
400
+ @@debug = true if toggle == :on
401
+ @@debug = false if toggle == :off
402
+ else
403
+ @@debug = (!@@debug)
404
+ end
405
+ end
406
+
407
+ # Returns true if debug output is enabled.
408
+ def debug?
409
+ @@debug
410
+ end
411
+
412
+ # Provide names for CLI arguments, in the order they appear.
413
+ #
414
+ # $ yourscript sample malpeque zinqy
415
+ # argv :name, :flavour
416
+ # command :sample do |obj|
417
+ # obj.argv.name # => malpeque
418
+ # obj.argv.flavour # => zinqy
419
+ # end
420
+ #
421
+ def argv(*args)
422
+ @@command_argv_names[@@command_index] ||= []
423
+ @@command_argv_names[@@command_index] += args.flatten
424
+ end
425
+
426
+ # 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.
429
+ def project(txt=nil)
430
+
431
+ 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
439
+ @@project = txt
440
+ end
441
+
442
+ # Has the project been set?
443
+ def project?
444
+ (defined?(@@project) && !@@project.nil?)
445
+ end
446
+
447
+ # Define a default command. You can specify a command name that has
448
+ # been or will be defined in your script:
449
+ #
450
+ # default :task
451
+ #
452
+ # Or you can supply a block which will be used as the default command:
453
+ #
454
+ # default do |obj| # This command will be named "default"
455
+ # # ...
456
+ # end
457
+ #
458
+ # default :hullinspector do # This one will be named "hullinspector"
459
+ # # ...
460
+ # end
461
+ #
462
+ # If +with_args+ is specified, the default command will receive all unknown
463
+ # values as arguments. This is necessary to define explicitly because drydock
464
+ # parses arguments expecting a command name. If the default command accepts
465
+ # arguments and with_args is not specified, drydock will raise an unknown
466
+ # command exception for the first argument.
467
+ #
468
+ def default(cmd=nil, with_args=false, &b)
469
+ raise "Calling default requires a command name or a block" unless cmd || b
470
+ # Creates the command and returns the name or just stores given name
471
+ @@default_command = (b) ? command(cmd || :default, &b).cmd : canonize(cmd)
472
+ # IDEA: refactor out the argument parser to support different types of CLI
473
+ @@default_command_with_args = with_args ? true : false
474
+ @@default_command
475
+ end
476
+
477
+ # Is +cmd+ the default command?
478
+ def default?(cmd)
479
+ return false if @@default_command.nil?
480
+ (@@default_command == canonize(cmd))
481
+ end
482
+
483
+ #
484
+ def default_with_args?; @@default_command_with_args; end
485
+
486
+
487
+ # Define a block for processing STDIN before the command is called.
488
+ # The command block receives the return value of this block as obj.stdin:
489
+ #
490
+ # command :task do |obj|;
491
+ # obj.stdin # => ...
492
+ # end
493
+ #
494
+ # If a stdin block isn't defined, +stdin+ above will be the STDIN IO handle.
495
+ def stdin(&b)
496
+ @@stdin_block = b
497
+ end
498
+
499
+ # Define a block to be called before the command.
500
+ # This is useful for opening database connections, etc...
501
+ def before(&b)
502
+ @@before_block = b
503
+ 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.
507
+ def after(&b)
508
+ @@after_block = b
509
+ end
510
+
511
+ # Define the default global usage banner. This is displayed
512
+ # with "script -h".
513
+ def global_usage(msg)
514
+ @@global_opts_parser.banner = "USAGE: #{msg}"
515
+ end
516
+
517
+ # Define a command-specific usage banner. This is displayed
518
+ # with "script command -h"
519
+ def usage(msg)
520
+ # 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}" << $/
524
+ 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",
528
+ # otherwise it will ignore you!
529
+ #
530
+ # +what+ the thing to ignore. When it equals :options Drydock will not parse
531
+ # the command-specific arguments. It will pass the arguments directly to the
532
+ # 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.
534
+ def ignore(what=:nothing)
535
+ @@command_opts_parser[@@command_index] = :ignore if what == :options || what == :all
536
+ end
537
+
538
+ # Define a global option. See +option+ for more info.
539
+ def global_option(*args, &b)
540
+ args.unshift(@@global_opts_parser)
541
+ @@global_option_names << option_parser(args, &b)
542
+ end
543
+ alias :global :global_option
544
+
545
+ # Define a command-specific option.
546
+ #
547
+ # +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.
550
+ # Some examples:
551
+ #
552
+ # option :h, :help, "Displays this message"
553
+ # option '-l x,y,z', '--lang=x,y,z', Array, "Requested languages"
554
+ #
555
+ # You can also supply a block to fiddle with the values. The final
556
+ # value becomes the option's value:
557
+ #
558
+ # option :m, :max, Integer, "Maximum threshold" do |v|
559
+ # v = 100 if v > 100
560
+ # v
561
+ # end
562
+ #
563
+ # All calls to +option+ must come before the command they're associated
564
+ # to. Example:
565
+ #
566
+ # option :t, :tasty, "A boolean switch"
567
+ # option :reason, String, "Requires a parameter"
568
+ # command :task do |obj|;
569
+ # obj.options.tasty # => true
570
+ # obj.options.reason # => I made the sandwich!
571
+ # end
572
+ #
573
+ # When calling your script with a specific command-line option, the value
574
+ # is available via obj.longname inside the command block.
575
+ #
576
+ def option(*args, &b)
577
+ args.unshift(get_current_option_parser)
578
+ current_command_option_names << option_parser(args, &b)
579
+ end
580
+
581
+ # Define a command-specific action.
582
+ #
583
+ # This is functionally very similar to option, but with an exciting and buoyant twist:
584
+ # 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.
587
+ #
588
+ # action :E, :eat, "Eat something"
589
+ # command :oysters => Fresh::Oysters
590
+ #
591
+ # # Drydock will look for Fresh::Oysters#eat_oysters and Fresh::Oysters#oysters_eat.
592
+ #
593
+ def action(*args, &b)
594
+ ret = option(*args, &b) # returns an array of all the current option names
595
+ current_command_action << ret.last # the most recent is last
596
+ end
597
+
598
+ # Define a command.
599
+ #
600
+ # command :task do
601
+ # ...
602
+ # end
603
+ #
604
+ # A custom command class can be specified using Hash syntax. The class
605
+ # must inherit from Drydock::Command (class CustomeClass < Drydock::Command)
606
+ #
607
+ # command :task => CustomCommand do
608
+ # ...
609
+ # end
610
+ #
611
+ def command(*cmds, &b)
612
+ cmd = cmds.shift # Should we accept aliases here?
613
+
614
+ if cmd.is_a? Hash
615
+ klass = cmd.values.first
616
+ names = cmd.keys.first
617
+ if names.is_a? Array
618
+ cmd, cmds = names.shift, [names].flatten.compact
619
+ else
620
+ cmd = names
621
+ end
622
+ raise "#{klass} is not a subclass of Drydock::Command" unless klass.ancestors.member?(Drydock::Command)
623
+ c = klass.new(cmd, &b) # A custom class was specified
624
+ else
625
+ c = Drydock::Command.new(cmd, &b)
626
+ end
627
+
628
+ @@command_descriptions[@@command_index] ||= ""
629
+ @@command_actions[@@command_index] ||= []
630
+ @@command_argv_names[@@command_index] ||= []
631
+
632
+ c.desc = @@command_descriptions[@@command_index]
633
+ c.actions = @@command_actions[@@command_index]
634
+ c.argv.fields = @@command_argv_names[@@command_index]
635
+
636
+ # Default Usage Banner.
637
+ # Without this, there's no help displayed for the command.
638
+ option_parser = get_option_parser(@@command_index)
639
+ if option_parser.is_a?(OptionParser) && option_parser.banner !~ /^USAGE/
640
+ usage "#{c.executable} #{c.cmd}"
641
+ end
642
+
643
+ @@commands[c.cmd] = c
644
+ @@command_index_map[c.cmd] = @@command_index
645
+ @@command_index += 1 # This will point to the next command
646
+
647
+ # Created aliases to the command using any additional command names
648
+ # i.e. command :something, :sumpin => Something
649
+ cmds.each { |aliaz| command_alias(cmd, aliaz); } unless cmds.empty?
650
+
651
+ c # Return the Command object
652
+ end
653
+
654
+ # Used to create an alias to a defined command.
655
+ # Here's an example:
656
+ #
657
+ # command :task do; ...; end
658
+ # alias_command :pointer, :task
659
+ #
660
+ # Either name can be used on the command-line:
661
+ #
662
+ # $ yourscript task [options]
663
+ # $ yourscript pointer [options]
664
+ #
665
+ # Inside of the command definition, you have access to the
666
+ # command name that was used via obj.alias.
667
+ def alias_command(aliaz, cmd)
668
+ return unless commands.has_key? cmd
669
+ commands[canonize(aliaz)] = commands[cmd]
670
+ 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.
675
+ def command_alias(cmd, aliaz)
676
+ return unless commands.has_key? cmd
677
+ commands[canonize(aliaz)] = commands[cmd]
678
+ end
679
+
680
+ # A hash of the currently defined Drydock::Command objects
681
+ def commands
682
+ @@commands
683
+ end
684
+
685
+ # An array of the currently defined commands names
686
+ def command_names
687
+ @@commands.keys.collect { |cmd| decanonize(cmd); }
688
+ end
689
+
690
+ # 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.
693
+ #
694
+ # trawler :command_name
695
+ #
696
+ def trawler(cmd)
697
+ @@trawler = cmd
698
+ end
699
+
700
+ # Has the trawler been set?
701
+ def trawler?
702
+ !@@trawler.nil? && !@@trawler.to_s.empty?
703
+ end
704
+
705
+ # Provide a description for a command
706
+ def about(txt)
707
+ @@command_descriptions += [txt]
708
+ return if get_current_option_parser.is_a?(Symbol)
709
+ get_current_option_parser.on "ABOUT: #{txt}"
710
+ end
711
+ # Deprecated. Use about.
712
+ def desc(txt)
713
+ STDERR.puts "'desc' is deprecated. Please use 'about' instead."
714
+ about(txt)
715
+ end
716
+
717
+ # Returns true if automatic execution is enabled.
718
+ def run?
719
+ @@run && has_run? == false
720
+ end
721
+
722
+ # Disable automatic execution (enabled by default)
723
+ #
724
+ # Drydock.run = false
725
+ def run=(v)
726
+ @@run = (v.is_a?(TrueClass)) ? true : false
727
+ end
728
+
729
+ # Return true if a command has been executed.
730
+ def has_run?
731
+ @@has_run
732
+ end
733
+
734
+ # Execute the given command.
735
+ # By default, Drydock automatically executes itself and provides handlers for known errors.
736
+ # You can override this functionality by calling +Drydock.run!+ yourself. Drydock
737
+ # will only call +run!+ once.
738
+ def run!(argv=[], stdin=STDIN)
739
+ return if has_run?
740
+ @@has_run = true
741
+ raise NoCommandsDefined.new if commands.empty?
742
+
743
+ global_options, cmd_name, command_options, argv = process_arguments(argv)
744
+ stdin = (defined? @@stdin_block) ? @@stdin_block.call(stdin, []) : stdin
745
+
746
+ command_obj = get_command(cmd_name)
747
+ command_obj.prepare(cmd_name, argv, stdin, global_options, command_options)
748
+
749
+ # Execute before block
750
+ @@before_block.call(command_obj) if defined? @@before_block
751
+
752
+ # Execute the requested command. We'll capture STDERR or STDOUT if desired.
753
+ @@captured = capture? ? capture_io(@@capture) { command_obj.call } : command_obj.call
754
+
755
+ # Execute after block
756
+ @@after_block.call(command_obj) if defined? @@after_block
757
+
758
+ rescue OptionParser::InvalidOption => ex
759
+ raise Drydock::InvalidArgument.new(ex.args)
760
+ rescue OptionParser::MissingArgument => ex
761
+ raise Drydock::MissingArgument.new(ex.args)
762
+ end
763
+
764
+ def capture(io)
765
+ @@capture = io
766
+ end
767
+
768
+ def captured
769
+ @@captured
770
+ end
771
+
772
+ def capture?
773
+ !@@capture.nil?
774
+ end
775
+
776
+ # Returns true if a command with the name +cmd+ has been defined.
777
+ def command?(cmd)
778
+ name = canonize(cmd)
779
+ @@commands.has_key? name
780
+ end
781
+
782
+ # Canonizes a string (+cmd+) to the symbol for command names
783
+ # '-' is replaced with '_'
784
+ def canonize(cmd)
785
+ return unless cmd
786
+ return cmd if cmd.kind_of?(Symbol)
787
+ cmd.to_s.tr('-', '_').to_sym
788
+ end
789
+
790
+ # Returns a string version of +cmd+, decanonized.
791
+ # Lowercase, '_' is replaced with '-'
792
+ def decanonize(cmd)
793
+ return unless cmd
794
+ cmd.to_s.tr('_', '-')
795
+ end
796
+
797
+ # Capture STDOUT or STDERR to prevent it from being printed.
798
+ #
799
+ # capture(:stdout) do
800
+ # ...
801
+ # end
802
+ #
803
+ def capture_io(stream, &block)
804
+ raise "We can only capture STDOUT or STDERR" unless stream == :stdout || stream == :stderr
805
+ begin
806
+ eval "$#{stream} = StringIO.new"
807
+ block.call
808
+ eval("$#{stream}").rewind # Otherwise we'll get nil
809
+ result = eval("$#{stream}").read
810
+ ensure
811
+ eval "$#{stream} = #{stream.to_s.upcase}" # Put it back!
812
+ end
813
+ end
814
+
815
+ private
816
+
817
+ # Returns the Drydock::Command object with the name +cmd+
818
+ def get_command(cmd)
819
+ return unless command?(cmd)
820
+ @@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').
825
+ def option_parser(args=[], &b)
826
+ return if args.empty?
827
+ opts_parser = args.shift
828
+
829
+ arg_name = ''
830
+ symbol_switches = []
831
+ args.each_with_index do |arg, index|
832
+ if arg.is_a? Symbol
833
+ arg_name = arg.to_s if arg.to_s.size > arg_name.size
834
+ args[index] = (arg.to_s.length == 1) ? "-#{arg.to_s}" : "--#{arg.to_s}"
835
+ symbol_switches << args[index]
836
+ elsif arg.kind_of?(Class)
837
+ symbol_switches.each do |arg|
838
+ arg << "=S"
839
+ end
840
+ end
841
+ end
842
+
843
+ if args.size == 1
844
+ opts_parser.on(args.shift)
845
+ else
846
+ opts_parser.on(*args) do |v|
847
+ block_args = [v, opts_parser]
848
+ result = (b.nil?) ? v : b.call(*block_args[0..(b.arity-1)])
849
+ end
850
+ end
851
+
852
+ arg_name
853
+ end
854
+
855
+
856
+ # Split the +argv+ array into global args and command args and
857
+ # find the command name.
858
+ # i.e. ./script -H push -f (-H is a global arg, push is the command, -f is a command arg)
859
+ # returns [global_options, cmd, command_options, argv]
860
+ def process_arguments(argv=[])
861
+ global_options = command_options = {}
862
+ cmd = nil
863
+
864
+ argv_copy = argv.clone # See: @@default_command_with_args below
865
+
866
+ global_options = @@global_opts_parser.getopts(argv)
867
+ cmd_name = (argv.empty?) ? @@default_command : argv.shift
868
+
869
+ unless command?(cmd_name)
870
+ # If requested, send all unknown arguments to the default command
871
+ if @@default_command_with_args
872
+ cmd_name = @@default_command
873
+ argv = argv_copy
874
+ else
875
+ raise UnknownCommand.new(cmd_name) unless trawler?
876
+ raise UnknownCommand.new(@@trawler) unless command?(@@trawler)
877
+ command_alias(@@trawler, cmd_name)
878
+ end
879
+ end
880
+
881
+ cmd = get_command(cmd_name)
882
+
883
+ command_parser = @@command_opts_parser[get_command_index(cmd.cmd)]
884
+ command_options = {}
885
+
886
+ # 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.
889
+ if !argv.empty? && command_parser && command_parser != :ignore
890
+ command_options = command_parser.getopts(argv)
891
+ end
892
+
893
+ [global_options, cmd_name, command_options, argv]
894
+ end
895
+
896
+
897
+ # Grab the current list of command-specific option names. This is a list of the
898
+ # long names.
899
+ def current_command_option_names
900
+ (@@command_option_names[@@command_index] ||= [])
901
+ end
902
+
903
+ def current_command_action
904
+ (@@command_actions[@@command_index] ||= [])
905
+ end
906
+
907
+ def get_command_index(cmd)
908
+ @@command_index_map[canonize(cmd)] || -1
909
+ end
910
+
911
+ # Grab the options parser for the current command or create it if it doesn't exist.
912
+ # Returns an instance of OptionParser.
913
+ def get_current_option_parser
914
+ (@@command_opts_parser[@@command_index] ||= OptionParser.new)
915
+ end
916
+
917
+ # Grabs the options parser for the given command.
918
+ # +arg+ can be an index or command name.
919
+ # Returns an instance of OptionParser.
920
+ def get_option_parser(arg)
921
+ index = arg.is_a?(String) ? get_command_index(arg) : arg
922
+ (@@command_opts_parser[index] ||= OptionParser.new)
923
+ end
924
+
925
+ #
926
+ # These are the "reel" defaults
927
+ #
928
+ @@global_opts_parser.banner = " Try: #{$0} show-commands"
929
+ @@global_opts_parser.on "Usage: #{$0} [global options] COMMAND [command options] #{$/}"
930
+ @@command_descriptions = ["Display available commands with descriptions"]
931
+ @@default_command = Drydock.command(:show_commands).cmd
932
+
933
+ end
934
+
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
+