bone 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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
+