drydock 0.3.3 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGES.txt CHANGED
@@ -1,15 +1,30 @@
1
1
  DRYDOCK, CHANGES
2
2
 
3
- TODO:
4
- * Fix '-' '_' ambiguity for option names
5
- * Calls valid? method (if present) before calling command block.
3
+ #### 0.4 (2009-02-28) ###############################
4
+
5
+ * FIX: Bug, "interning empty string" error when bare "option" is used
6
+ * NEW: Calls valid? method (if present) before calling command block.
7
+ * NEW: "capture" method. Auto capture STDOUT to obj.stdout etc...
8
+ * NEW: Automatically calls init and print_header methods before the command
9
+ and print_footer after the command (if available)
10
+ * NEW: Tries to call obj.command if available when no block is supplied
11
+ * NEW: "show_commands" command built-in. Displays commands with descriptions
12
+ * NEW: A default usage help msg for every command: "#{$0} command-name"
13
+ * NEW: "usage" work multiple times for the same command.
14
+ * NEW: "desc" method for per command descriptions
15
+ * CHANGE: options are now stored as obj.option.name instead of obj.name
16
+ * CHANGE: global options are now stored as obj.globals.name
17
+ * CHANGE: removed auto importing methods
18
+ OLD: require 'drydock'
19
+ NEW: require 'drydock'
20
+ extend Drydock
6
21
 
7
22
 
8
23
  #### 0.3.3 (2009-02-14) ###############################
9
24
 
10
25
  * NEW: init method hook for subclasses of Drydock::Command
11
26
  * UPDATED: Rdocs
12
-
27
+ * CHANGE: added method command_aliaz to mirror aliaz_command
13
28
 
14
29
  #### 0.3 (2009-02-05) ###############################
15
30
 
data/README.rdoc CHANGED
@@ -1,4 +1,4 @@
1
- = Drydock - v0.3
1
+ = Drydock - v0.4
2
2
 
3
3
  Inspired by github-gem and bmizerany-frylock.
4
4
 
@@ -31,40 +31,49 @@ See bin/example for more.
31
31
  # variables defined here will be available to all commands.
32
32
  end
33
33
 
34
- command :welcome do
35
- # Example: ruby bin/example
36
-
37
- puts "Welcome to Drydock. You have the following commands:"
38
-
39
- # The commands method returns a hash of Drydock::Command objects
40
- puts commands.keys.inject([]) { |list, command| list << command.to_s }.sort.join(', ')
41
- end
34
+ desc "A friendly welcome to the Drydock"
35
+ command :welcome do
36
+ puts "Welcome to Drydock."
37
+ puts "For available commands:"
38
+ puts "#{$0} show-commands"
39
+ end
40
+
41
+
42
+ usage "USAGE: #{$0} laugh [-f]"
43
+ desc "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
+
42
55
 
43
- usage "Example: #{$0} laugh [-f]"
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 accessors in this object.
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
48
61
 
49
- answer = !obj.faster ? "Sort of" : "Yes! I'm literally laughing as fast as possible."
62
+ desc "Do something with John West's Smoked Oysters"
63
+ command :oysters => JohnWestSmokedOysters do |obj|
64
+ p obj # => #<JohnWestSmokedOysters:0x42179c ... >
65
+ end
50
66
 
51
- puts "Captain Stubing: Are you laughing?"
52
- puts "Dr. Bricker: " << answer
53
- end
54
-
55
- class JohnWestSmokedOysters < Drydock::Command; end;
56
- # You can write your own command classes by inheriting from Drydock::Command
57
- # and referencing it in the command definition.
67
+ desc "My way of saying hello!"
68
+ command :ahoy! => JohnWestSmokedOysters
69
+ # If you don't provide a block, Drydock will call JohnWestSmokedOysters#ahoy!
58
70
 
59
- command :oysters => JohnWestSmokedOysters do |obj|
60
- p obj # => #<JohnWestSmokedOysters:0x42179c ... >
61
- end
62
71
 
63
72
  == More Information
64
73
 
65
- * http://github.com/delano/drydock
66
- * http://drydock.rubyforge.org/ (rdocs)
67
- * http://www.youtube.com/watch?v=m_wFEB4Oxlo
74
+ * GitHub[http://github.com/delano/drydock]
75
+ * RDocs[http://drydock.rubyforge.org/]
76
+ * Inspiration[http://www.youtube.com/watch?v=m_wFEB4Oxlo]
68
77
 
69
78
  == Credits
70
79
 
data/bin/example CHANGED
@@ -13,8 +13,14 @@
13
13
  $:.unshift File.expand_path(File.join(File.dirname(__FILE__), '..')), 'lib'
14
14
 
15
15
  require 'drydock'
16
+ extend Drydock # Tell Drydock you want its methods!
16
17
 
17
- default :welcome
18
+
19
+ default :welcome # The welcome command will be run if no command is given
20
+ capture :stderr # Drydock will capture STDERR and keep it in the hold.
21
+ # You can use this to suppress errors.
22
+
23
+ project "Drydock Example" # An optional name
18
24
 
19
25
  before do
20
26
  # You can execute a block before the requests command is executed. Instance
@@ -25,30 +31,29 @@ after do
25
31
  # And this will be called after the command.
26
32
  end
27
33
 
34
+ desc "A friendly welcome to the Drydock"
28
35
  command :welcome do
29
- # Example: ruby bin/example
30
-
31
- puts "Welcome to Drydock. You have the following commands:"
32
-
33
- # The commands method returns a hash of Drydock::Command objects
34
- puts commands.keys.inject([]) { |list, command| list << command.to_s }.sort.join(', ')
36
+ puts "Welcome to Drydock."
37
+ puts "For available commands:"
38
+ puts "#{$0} show-commands"
35
39
  end
36
40
 
37
- usage "Example: #{$0} laugh [-f]"
41
+ usage "USAGE: #{$0} laugh [-f]"
42
+ desc "The captain commands his crew to laugh"
38
43
  option :f, :faster, "A boolean value. Go even faster!"
39
44
  command :laugh do |obj|
40
45
  # +obj+ is an instance of Drydock::Command. The options you define are available
41
- # via accessors in this object.
46
+ # via obj.option.name
42
47
 
43
- answer = !obj.faster ? "Sort of" : "Yes! I'm literally laughing as fast as possible."
48
+ answer = !obj.option.faster ? "Sort of" : "Yes! I'm literally laughing as fast as possible."
44
49
 
45
50
  puts "Captain Stubing: Are you laughing?"
46
51
  puts "Dr. Bricker: " << answer
47
52
  end
48
53
 
49
54
  global_usage "USAGE: #{File.basename($0)} [global options] command [command options]"
50
- global_option :s, :seconds, "Display values in seconds"
51
- global_option :v, :verbose, "Verbosity level (i.e. -vvv is greater than -v)" do |v|
55
+ global :s, :seconds, "Display values in seconds"
56
+ global :v, :verbose, "Verbosity level (i.e. -vvv is greater than -v)" do |v|
52
57
  # Use instance variables to maintain values between option blocks.
53
58
  # This will increment for every -v found (i.e. -vvv)
54
59
  @val ||= 0
@@ -57,17 +62,19 @@ end
57
62
 
58
63
 
59
64
  usage "#{$0} [-s] [-vv] date"
65
+ desc "Display the current date"
60
66
  command :date do |obj, argv|
61
67
  # +argv+ is an array containing the unnamed arguments
62
68
  require 'time'
63
69
  now = Time.now
64
- puts "(Not verbose enough. Try adding a -v.)" if (obj.verbose || 0) == 1
65
- puts "More verbosely, the date is now: " if (obj.verbose || 0) >= 2
66
- puts (obj.seconds) ? now.to_i : now.to_s
70
+ puts "(Not verbose enough. Try adding a -v.)" if (obj.global.verbose || 0) == 1
71
+ puts "More verbosely, the date is now: " if (obj.global.verbose || 0) >= 2
72
+ puts (obj.global.seconds) ? now.to_i : now.to_s
67
73
  end
68
74
 
69
- usage "#{$0} rogue"
75
+
70
76
  ignore :options
77
+ desc "This command ignores options"
71
78
  command :rogue do |obj, argv|
72
79
  # You can use ignore :options to tell Drydock to not process the
73
80
  # command-specific options.
@@ -79,14 +86,26 @@ command :rogue do |obj, argv|
79
86
  end
80
87
  end
81
88
 
82
- class JohnWestSmokedOysters < Drydock::Command; end;
83
- # You can write your own command classes by inheriting from Drydock::Command
84
- # and referencing it in the command definition.
89
+ class JohnWestSmokedOysters < Drydock::Command
90
+ # You can write your own command classes by inheriting from Drydock::Command
91
+ # and referencing it in the command definition.
92
+ def ahoy!; p "matey"; end
93
+ end
85
94
 
95
+ desc "Do something with John West's Smoked Oysters"
86
96
  command :oysters => JohnWestSmokedOysters do |obj|
87
97
  p obj # => #<JohnWestSmokedOysters:0x42179c ... >
88
98
  end
89
99
 
100
+ desc "My way of saying hello!"
101
+ command :ahoy! => JohnWestSmokedOysters
102
+ # If you don't provide a block, Drydock will call JohnWestSmokedOysters#ahoy!
103
+
104
+
105
+
106
+ usage 'ruby bin/example process -c -d " " -t 15 http://solutious.com/'
107
+ usage 'echo "http://solutious.com/" | ruby bin/example process -c -d " " -t 15'
108
+ desc "Check for broken URIs"
90
109
  option :c, :check, "Check response codes for each URI"
91
110
  option :d, :delim, String, "Output delimiter"
92
111
  option :t, :timeout, Float, "Timeout value for HTTP request" do |v|
@@ -95,9 +114,7 @@ option :t, :timeout, Float, "Timeout value for HTTP request" do |v|
95
114
  v = 10 if (v > 10)
96
115
  v
97
116
  end
98
-
99
- usage 'echo "http://github.com/" | ruby bin/example process -c -d " " -t 15 http://solutious.com/'
100
- command :processuri do |obj, argv, stdin|
117
+ command :uri do |obj, argv, stdin|
101
118
  # +cmd+ is the string used to evoke this command. Useful with alias_command (see below).
102
119
  # +stdin+ is either an IO object or a custom object defined with a stdin block (see below)
103
120
 
@@ -106,8 +123,8 @@ command :processuri do |obj, argv, stdin|
106
123
  require 'timeout'
107
124
 
108
125
  uris = [stdin, argv].flatten # Combine the argv and stdin arrays
109
- delim = obj.delim || ','
110
- timeout = obj.timeout || 5
126
+ delim = obj.option.delim || ','
127
+ timeout = obj.option.timeout || 5
111
128
  code = :notchecked # The default code when :check is false
112
129
 
113
130
  if uris.empty?
@@ -117,12 +134,11 @@ command :processuri do |obj, argv, stdin|
117
134
  end
118
135
 
119
136
  uris.each_with_index do |uri, index|
120
- code = response_code(uri, timeout) if (obj.check)
137
+ code = response_code(uri, timeout) if (obj.option.check)
121
138
  puts [index+1, uri, code].join(delim)
122
139
  end
123
140
  end
124
- alias_command :checkuri, :processuri
125
-
141
+ alias_command :checkuri, :uri
126
142
 
127
143
 
128
144
 
data/drydock.gemspec CHANGED
@@ -1,6 +1,6 @@
1
1
  @spec = Gem::Specification.new do |s|
2
2
  s.name = %q{drydock}
3
- s.version = "0.3.3"
3
+ s.version = "0.4.0"
4
4
  s.specification_version = 1 if s.respond_to? :specification_version=
5
5
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
6
6
 
@@ -16,30 +16,6 @@
16
16
  bin/example
17
17
  drydock.gemspec
18
18
  lib/drydock.rb
19
- test/command_test.rb
20
- doc
21
- doc/classes
22
- doc/classes/Drydock
23
- doc/classes/Drydock/Command.html
24
- doc/classes/Drydock/InvalidArgument.html
25
- doc/classes/Drydock/MissingArgument.html
26
- doc/classes/Drydock/NoCommandsDefined.html
27
- doc/classes/Drydock/UnknownCommand.html
28
- doc/classes/Drydock.html
29
- doc/created.rid
30
- doc/files
31
- doc/files/bin
32
- doc/files/bin/example.html
33
- doc/files/CHANGES_txt.html
34
- doc/files/lib
35
- doc/files/lib/drydock_rb.html
36
- doc/files/LICENSE_txt.html
37
- doc/files/README_rdoc.html
38
- doc/fr_class_index.html
39
- doc/fr_file_index.html
40
- doc/fr_method_index.html
41
- doc/index.html
42
- doc/rdoc-style.css
43
19
  )
44
20
  s.has_rdoc = true
45
21
  s.homepage = %q{http://github.com/delano/drydock}
data/lib/drydock.rb CHANGED
@@ -1,8 +1,7 @@
1
1
  require 'optparse'
2
2
  require 'ostruct'
3
+ require 'stringio'
3
4
 
4
- #
5
- #
6
5
  module Drydock
7
6
  # The base class for all command objects. There is an instance of this class
8
7
  # for every command defined. Global and command-specific options are added
@@ -10,11 +9,11 @@ module Drydock
10
9
  #
11
10
  # i.e. "example -v date -f yaml"
12
11
  #
13
- # global_option :v, :verbose, "I want mooooore!"
12
+ # global :v, :verbose, "I want mooooore!"
14
13
  # option :f, :format, String, "Long date format"
15
14
  # command :date do |obj|
16
- # puts obj.verbose #=> true
17
- # puts obj.format #=> "yaml"
15
+ # puts obj.global.verbose #=> true
16
+ # puts obj.option.format #=> "yaml"
18
17
  # end
19
18
  #
20
19
  # You can inherit from this class to create your own: EatFood < Drydock::Command.
@@ -23,7 +22,22 @@ module Drydock
23
22
  # command :eat => EatFood do |obj|; ...; end
24
23
  #
25
24
  class Command
26
- attr_reader :cmd, :alias
25
+ VERSION = 0.4
26
+ # The canonical name of the command (the one used in the command definition). If you
27
+ # inherit from this class and add a method named +cmd+, you can leave omit the block
28
+ # in the command definition. That method will be called instead. See bin/examples.
29
+ attr_reader :cmd
30
+ # The name used to evoke this command (it's either the canonical name or the alias used).
31
+ attr_reader :alias
32
+ # A friendly description of the command.
33
+ attr_accessor :desc
34
+ # The block that will be executed when this command is evoked. If the block is nil
35
+ # it will check if there is a method named +cmd+. If so, that will be executed.
36
+ attr_reader :b
37
+ # An OpenStruct object containing the command options specified at run-time.
38
+ attr_reader :option
39
+ # An OpenStruct object containing the global options specified at run-time.
40
+ attr_reader :global
27
41
 
28
42
  # The default constructor sets the short name of the command
29
43
  # and stores a reference to the block (if supplied).
@@ -35,6 +49,16 @@ module Drydock
35
49
  def initialize(cmd, &b)
36
50
  @cmd = (cmd.kind_of?(Symbol)) ? cmd : cmd.to_sym
37
51
  @b = b
52
+ @option = OpenStruct.new
53
+ @global = OpenStruct.new
54
+
55
+ @global.verbose = 0
56
+ @global.quiet = false
57
+ end
58
+
59
+ # Returns the command name (not the alias)
60
+ def name
61
+ @cmd
38
62
  end
39
63
 
40
64
  # Execute the block.
@@ -50,14 +74,55 @@ module Drydock
50
74
  # +options+ a hash of the command-specific options specific on the command-line.
51
75
  def call(cmd_str=nil, argv=[], stdin=[], global_options={}, options={})
52
76
  @alias = cmd_str.nil? ? @cmd : cmd_str
53
- global_options.merge(options).each_pair do |n,v|
54
- self.send("#{n}=", v)
77
+
78
+ global_options.each_pair do |n,v|
79
+ self.global.send("#{n}=", v) # Populate the object's globals
55
80
  end
56
81
 
57
- self.init if respond_to? :init
82
+ options.each_pair do |n,v|
83
+ self.option.send("#{n}=", v) # ... and also the command options
84
+ end
85
+
86
+ self.init if self.respond_to? :init # Must be called first!
87
+ self.print_header if respond_to? :print_header
88
+ self.valid? if respond_to? :'valid?'
58
89
 
59
- block_args = [self, argv, stdin] # TODO: review order
60
- @b.call(*block_args[0..(@b.arity-1)]) # send only as many args as defined
90
+ block_args = [self, argv, stdin]
91
+
92
+ if @b
93
+ @b.call(*block_args[0..(@b.arity-1)]) # send only as many args as defined
94
+ elsif self.respond_to? @cmd.to_sym
95
+ self.send(@cmd)
96
+ else
97
+ raise "The command #{@alias} has no block and #{self.class} has no #{@cmd} method!"
98
+ end
99
+
100
+ self.print_footer if respond_to? :print_footer
101
+
102
+ end
103
+
104
+ # Print the list of available commands to STDOUT. This is used as the
105
+ # "default" command unless another default commands is supplied. You
106
+ # can also write your own Drydock::Command#show_commands to override
107
+ # this default behaviour.
108
+ def show_commands
109
+ project = " for #{Drydock.project}" if Drydock.project?
110
+ puts "Available commands#{project}:", ""
111
+ Drydock.commands.keys.sort{ |a,b| a.to_s <=> b.to_s }.each do |cmd|
112
+ msg = Drydock.commands[cmd].desc
113
+
114
+ # Out to sea
115
+ unless cmd === Drydock.commands[cmd].cmd
116
+ msg = "See: #{Drydock.decanonize(Drydock.commands[cmd].cmd)} (this is an alias)"
117
+ end
118
+
119
+ puts " %16s: %s" % [Drydock.decanonize(cmd), msg]
120
+ end
121
+
122
+ puts
123
+ puts "%6s: %s" % ["Try", "#{$0} -h"]
124
+ puts "%6s %s" % ["", "#{$0} COMMAND -h"]
125
+ puts
61
126
  end
62
127
 
63
128
  # The name of the command
@@ -103,34 +168,52 @@ end
103
168
  module Drydock
104
169
  extend self
105
170
 
106
- VERSION = 0.3
171
+ VERSION = 0.4
107
172
 
108
173
  private
174
+ # Disabled. We're going basic, using a module and include/extend.
109
175
  # Stolen from Sinatra!
110
- def delegate(*args)
111
- args.each do |m|
112
- eval(<<-end_eval, binding, "(__Drydock__)", __LINE__)
113
- def #{m}(*args, &b)
114
- Drydock.#{m}(*args, &b)
115
- end
116
- end_eval
117
- end
118
- end
176
+ #def delegate(*args)
177
+ # args.each do |m|
178
+ # eval(<<-end_eval, binding, "(__Drydock__)", __LINE__)
179
+ # def #{m}(*args, &b)
180
+ # Drydock.#{m}(*args, &b)
181
+ # end
182
+ # end_eval
183
+ # end
184
+ #end
185
+ #
186
+ #delegate :before, :after, :alias_command, :desc
187
+ #delegate :global_option, :global_usage, :usage, :commands, :command
188
+ #delegate :debug, :option, :stdin, :default, :ignore, :command_alias
119
189
 
120
- delegate :before, :after, :alias_command, :commands
121
- delegate :global_option, :global_usage, :usage, :command
122
- delegate :debug, :option, :stdin, :default, :ignore, :command_alias
190
+ @@project = nil
123
191
 
124
192
  @@debug = false
125
193
  @@has_run = false
126
194
  @@run = true
195
+
196
+ @@global_opts_parser = OptionParser.new
197
+ @@global_option_names = []
198
+
199
+ @@command_opts_parser = []
200
+ @@command_option_names = []
201
+
127
202
  @@default_command = nil
128
203
 
204
+ @@commands = {}
205
+ @@command_descriptions = []
206
+ @@command_index = 0
207
+ @@command_index_map = {}
208
+
209
+ @@capture = nil # contains one of :stdout, :stderr
210
+ @@captured = nil
211
+
129
212
  public
130
213
  # Enable or disable debug output.
131
214
  #
132
- # debug :on
133
- # debug :off
215
+ # debug :on
216
+ # debug :off
134
217
  #
135
218
  # Calling without :on or :off will toggle the value.
136
219
  #
@@ -142,17 +225,44 @@ module Drydock
142
225
  @@debug = (!@@debug)
143
226
  end
144
227
  end
228
+
145
229
  # Returns true if debug output is enabled.
146
230
  def debug?
147
231
  @@debug
148
232
  end
149
233
 
150
- # Define a default command.
234
+ # The project of the script. This is currently only used when printing
235
+ # list of commands (see: Drydock::Command#show_commands). It may be
236
+ # used elsewhere in the future.
237
+ def project(txt=nil)
238
+ return @@project unless txt
239
+ @@project = txt
240
+ end
241
+
242
+ # Has the project been set?
243
+ def project?
244
+ (defined?(@@project) && !@@project.nil?)
245
+ end
246
+
247
+ # Define a default command. You can specify a command name that has
248
+ # been or will be defined in your script:
151
249
  #
152
250
  # default :task
153
251
  #
154
- def default(cmd)
155
- @@default_command = canonize(cmd)
252
+ # Or you can supply a block which will be used as the default command:
253
+ #
254
+ # default do |obj| # This command will be named "default"
255
+ # # ...
256
+ # end
257
+ #
258
+ # default :hullinspector do # This one will b named "hullinspector"
259
+ # # ...
260
+ # end
261
+ #
262
+ def default(cmd=nil, &b)
263
+ raise "Calling default requires a command name or a block" unless cmd || b
264
+ # Creates the command and returns the name or just stores given name
265
+ @@default_command = (b) ? command(cmd || :default, &b).cmd : canonize(cmd)
156
266
  end
157
267
 
158
268
  # Define a block for processing STDIN before the command is called.
@@ -180,30 +290,26 @@ module Drydock
180
290
  # Define the default global usage banner. This is displayed
181
291
  # with "script -h".
182
292
  def global_usage(msg)
183
- @@global_options ||= OpenStruct.new
184
- global_opts_parser.banner = "USAGE: #{msg}"
293
+ @@global_opts_parser.banner = "USAGE: #{msg}"
185
294
  end
186
295
 
187
296
  # Define a command-specific usage banner. This is displayed
188
297
  # with "script command -h"
189
298
  def usage(msg)
190
- get_current_option_parser.banner = "USAGE: #{msg}"
299
+ # The default value given by OptionParser starts with "Usage". That's how
300
+ # we know we can clear it.
301
+ get_current_option_parser.banner = "" if get_current_option_parser.banner =~ /^Usage:/
302
+ get_current_option_parser.banner << "USAGE: #{msg}" << $/
191
303
  end
192
304
 
193
- # Grab the options parser for the current command or create it if it doesn't exist.
194
- def get_current_option_parser
195
- @@command_opts_parser ||= []
196
- @@command_index ||= 0
197
- (@@command_opts_parser[@@command_index] ||= OptionParser.new)
198
- end
199
305
 
200
306
  # Tell the Drydock parser to ignore something.
201
307
  # Drydock will currently only listen to you if you tell it to "ignore :options",
202
308
  # otherwise it will ignore you!
203
309
  #
204
310
  # +what+ the thing to ignore. When it equals :options Drydock will not parse
205
- # the command-specific arguments. It will pass the Command object the list of
206
- # arguments. This is useful when you want to parse the arguments in some a way
311
+ # the command-specific arguments. It will pass the arguments directly to the
312
+ # Command object. This is useful when you want to parse the arguments in some a way
207
313
  # that's too crazy, dangerous for Drydock to handle automatically.
208
314
  def ignore(what=:nothing)
209
315
  @@command_opts_parser[@@command_index] = :ignore if what == :options || what == :all
@@ -211,25 +317,38 @@ module Drydock
211
317
 
212
318
  # Define a global option. See +option+ for more info.
213
319
  def global_option(*args, &b)
214
- args.unshift(global_opts_parser)
215
- global_option_names << option_parser(args, &b)
320
+ args.unshift(@@global_opts_parser)
321
+ @@global_option_names << option_parser(args, &b)
216
322
  end
323
+ alias :global :global_option
217
324
 
218
325
  # Define a command-specific option.
219
326
  #
220
327
  # +args+ is passed directly to OptionParser.on so it can contain anything
221
- # that's valid to that method. Some examples:
222
- # [:h, :help, "Displays this message"]
223
- # [:m, :max, Integer, "Maximum threshold"]
224
- # ['-l x,y,z', '--lang=x,y,z', Array, "Requested languages"]
225
- # If a class is included, it will tell OptionParser to expect a value
226
- # otherwise it assumes a boolean value.
328
+ # that's valid to that method. If a class is included, it will tell
329
+ # OptionParser to expect a value otherwise it assumes a boolean value.
330
+ # Some examples:
331
+ #
332
+ # option :h, :help, "Displays this message"
333
+ # option '-l x,y,z', '--lang=x,y,z', Array, "Requested languages"
334
+ #
335
+ # You can also supply a block to fiddle with the values. The final
336
+ # value becomes the option's value:
337
+ #
338
+ # option :m, :max, Integer, "Maximum threshold" do |v|
339
+ # v = 100 if v > 100
340
+ # v
341
+ # end
227
342
  #
228
343
  # All calls to +option+ must come before the command they're associated
229
344
  # to. Example:
230
345
  #
231
- # option :l, :longname, String, "Description" do; ...; end
232
- # command :task do |obj|; ...; end
346
+ # option :t, :tasty, "A boolean switch"
347
+ # option :reason, String, "Requires a parameter"
348
+ # command :task do |obj|;
349
+ # obj.options.tasty # => true
350
+ # obj.options.reason # => I made the sandwich!
351
+ # end
233
352
  #
234
353
  # When calling your script with a specific command-line option, the value
235
354
  # is available via obj.longname inside the command block.
@@ -239,6 +358,9 @@ module Drydock
239
358
  current_command_option_names << option_parser(args, &b)
240
359
  end
241
360
 
361
+
362
+
363
+
242
364
  # Define a command.
243
365
  #
244
366
  # command :task do
@@ -253,20 +375,27 @@ module Drydock
253
375
  # end
254
376
  #
255
377
  def command(*cmds, &b)
256
- @@command_index ||= 0
257
- @@command_opts_parser ||= []
258
- @@command_option_names ||= []
259
- cmds.each do |cmd|
260
- if cmd.is_a? Hash
261
- c = cmd.values.first.new(cmd.keys.first, &b)
262
- else
263
- c = Drydock::Command.new(cmd, &b)
264
- end
265
- commands[c.cmd] = c
266
- command_index_map[c.cmd] = @@command_index
267
- @@command_index += 1
378
+ cmd = cmds.first
379
+ if cmd.is_a? Hash
380
+ c = cmd.values.first.new(cmd.keys.first, &b)
381
+ else
382
+ c = Drydock::Command.new(cmd, &b)
268
383
  end
269
384
 
385
+ @@command_descriptions[@@command_index] ||= ""
386
+
387
+ c.desc = @@command_descriptions[@@command_index]
388
+
389
+ # Default Usage Banner.
390
+ # Without this, there's no help displayed for the command.
391
+ option_parser = get_option_parser(@@command_index)
392
+ usage "#{$0} #{c.cmd}" if option_parser.is_a?(OptionParser) && option_parser.banner !~ /^USAGE/
393
+
394
+ @@commands[c.cmd] = c
395
+ @@command_index_map[c.cmd] = @@command_index
396
+ @@command_index += 1 # This will point to the next command
397
+
398
+ c # Return the Command object
270
399
  end
271
400
 
272
401
  # Used to create an alias to a defined command.
@@ -284,13 +413,32 @@ module Drydock
284
413
  # command name that was used via obj.alias.
285
414
  def alias_command(aliaz, cmd)
286
415
  return unless commands.has_key? cmd
287
- @@commands[aliaz] = commands[cmd]
416
+ commands[canonize(aliaz)] = commands[cmd]
288
417
  end
289
- alias :command_alias :alias_command
290
418
 
291
- # An array of the currently defined Drydock::Command objects
419
+ # Identical to +alias_command+ with reversed arguments.
420
+ # For whatever reason I forget the order so Drydock supports both.
421
+ # Tip: the argument order matches the method name.
422
+ def command_alias(cmd, aliaz)
423
+ return unless commands.has_key? cmd
424
+ commands[canonize(aliaz)] = commands[cmd]
425
+ end
426
+
427
+ # A hash of the currently defined Drydock::Command objects
292
428
  def commands
293
- @@commands ||= {}
429
+ @@commands
430
+ end
431
+
432
+ # An array of the currently defined commands names
433
+ def command_names
434
+ @@commands.keys.collect { |cmd| decanonize(cmd); }
435
+ end
436
+
437
+ # Provide a description for a command
438
+ def desc(txt)
439
+ @@command_descriptions += [txt]
440
+ return if get_current_option_parser.is_a?(Symbol)
441
+ get_current_option_parser.on "ABOUT: #{txt}"
294
442
  end
295
443
 
296
444
  # Returns true if automatic execution is enabled.
@@ -318,7 +466,7 @@ module Drydock
318
466
  return if has_run?
319
467
  @@has_run = true
320
468
  raise NoCommandsDefined.new if commands.empty?
321
- @@global_options, cmd_name, @@command_options, argv = process_arguments(argv)
469
+ global_options, cmd_name, command_options, argv = process_arguments(argv)
322
470
 
323
471
  cmd_name ||= default_command
324
472
 
@@ -327,7 +475,9 @@ module Drydock
327
475
  stdin = (defined? @@stdin_block) ? @@stdin_block.call(stdin, []) : stdin
328
476
  @@before_block.call if defined? @@before_block
329
477
 
330
- call_command(cmd_name, argv, stdin)
478
+ command_portion = lambda { call_command(cmd_name, argv, stdin, global_options, command_options) }
479
+
480
+ capture? ? (@@captured = capture_io(@@capture, &command_portion)) : command_portion.call
331
481
 
332
482
  @@after_block.call if defined? @@after_block
333
483
 
@@ -337,33 +487,85 @@ module Drydock
337
487
  raise Drydock::MissingArgument.new(ex.args)
338
488
  end
339
489
 
340
- private
490
+ def capture(io)
491
+ @@capture = io
492
+ end
341
493
 
342
- # Executes the block associated to +cmd+
343
- def call_command(cmd, argv=[], stdin=nil)
344
- return unless command?(cmd)
345
- get_command(cmd).call(cmd, argv, stdin, @@global_options || {}, @@command_options || {})
494
+ def captured
495
+ @@captured
346
496
  end
347
497
 
348
- # Returns the Drydock::Command object with the name +cmd+
349
- def get_command(cmd)
350
- return unless command?(cmd)
351
- @@commands[canonize(cmd)]
352
- end
498
+ def capture?
499
+ !@@capture.nil?
500
+ end
501
+
502
+ # Grab the options parser for the current command or create it if it doesn't exist.
503
+ # Returns an instance of OptionParser.
504
+ def get_current_option_parser
505
+ (@@command_opts_parser[@@command_index] ||= OptionParser.new)
506
+ end
507
+
508
+ # Grabs the options parser for the given command.
509
+ # +arg+ can be an index or command name.
510
+ # Returns an instance of OptionParser.
511
+ def get_option_parser(arg)
512
+ index = arg.is_a?(String) ? get_command_index(arg) : arg
513
+ (@@command_opts_parser[index] ||= OptionParser.new)
514
+ end
353
515
 
354
516
  # Returns true if a command with the name +cmd+ has been defined.
355
517
  def command?(cmd)
356
518
  name = canonize(cmd)
357
- (@@commands || {}).has_key? name
519
+ @@commands.has_key? name
358
520
  end
359
521
 
360
- # Canonizes a string to the symbol format for command names
522
+ # Canonizes a string (+cmd+) to the symbol for command names
523
+ # '-' is replaced with '_'
361
524
  def canonize(cmd)
362
525
  return unless cmd
363
526
  return cmd if cmd.kind_of?(Symbol)
364
- cmd.tr('-', '_').to_sym
527
+ cmd.to_s.tr('-', '_').to_sym
528
+ end
529
+
530
+ # Returns a string version of +cmd+, decanonized.
531
+ # Lowercase, '_' is replaced with '-'
532
+ def decanonize(cmd)
533
+ return unless cmd
534
+ cmd.to_s.tr('_', '-')
535
+ end
536
+
537
+ # Capture STDOUT or STDERR to prevent it from being printed.
538
+ #
539
+ # capture(:stdout) do
540
+ # ...
541
+ # end
542
+ #
543
+ def capture_io(stream)
544
+ raise "We can only capture STDOUT or STDERR" unless stream == :stdout || stream == :stderr
545
+ begin
546
+ eval "$#{stream} = StringIO.new"
547
+ yield
548
+ eval("$#{stream}").rewind # Otherwise we'll get nil
549
+ result = eval("$#{stream}").read
550
+ ensure
551
+ eval "$#{stream} = #{stream.to_s.upcase}" # Put it back!
552
+ end
553
+ end
554
+
555
+ private
556
+
557
+ # Executes the block associated to +cmd+
558
+ def call_command(cmd, argv=[], stdin=nil, global_options={}, command_options={})
559
+ return unless command?(cmd)
560
+ get_command(cmd).call(cmd, argv, stdin, global_options || {}, command_options || {})
365
561
  end
366
562
 
563
+ # Returns the Drydock::Command object with the name +cmd+
564
+ def get_command(cmd)
565
+ return unless command?(cmd)
566
+ @@commands[canonize(cmd)]
567
+ end
568
+
367
569
  # Processes calls to option and global_option. Symbols are converted into
368
570
  # OptionParser style strings (:h and :help become '-h' and '--help').
369
571
  def option_parser(args=[], &b)
@@ -405,14 +607,13 @@ module Drydock
405
607
  global_options = command_options = {}
406
608
  cmd = nil
407
609
 
408
- global_options = global_opts_parser.getopts(argv)
409
-
610
+ global_options = @@global_opts_parser.getopts(argv)
410
611
  cmd_name = (argv.empty?) ? @@default_command : argv.shift
411
612
  raise UnknownCommand.new(cmd_name) unless command?(cmd_name)
412
613
 
413
614
  cmd = get_command(cmd_name)
414
615
 
415
- command_parser = @@command_opts_parser[get_command_index(cmd_name)]
616
+ command_parser = @@command_opts_parser[get_command_index(cmd.cmd)]
416
617
  command_options = {}
417
618
 
418
619
  # We only need to parse the options out of the arguments when
@@ -422,59 +623,47 @@ module Drydock
422
623
  command_options = command_parser.getopts(argv)
423
624
  end
424
625
 
626
+ # TODO: Remove this chunk for method creation. We now use OpenStruct.
425
627
  # Add accessors to the Drydock::Command object
426
628
  # for the global and command specific options
427
- [global_option_names, (command_option_names[get_command_index(cmd_name)] || [])].flatten.each do |n|
428
- unless cmd.respond_to?(n)
429
- cmd.class.send(:define_method, n) do
430
- instance_variable_get("@#{n}")
431
- end
432
- end
433
- unless cmd.respond_to?("#{n}=")
434
- cmd.class.send(:define_method, "#{n}=") do |val|
435
- instance_variable_set("@#{n}", val)
436
- end
437
- end
438
- end
629
+ #[global_option_names, (command_option_names[get_command_index(cmd_name)] || [])].flatten.each do |n|
630
+ # unless cmd.respond_to?(n)
631
+ # cmd.class.send(:define_method, n) do
632
+ # instance_variable_get("@#{n}")
633
+ # end
634
+ # end
635
+ # unless cmd.respond_to?("#{n}=")
636
+ # cmd.class.send(:define_method, "#{n}=") do |val|
637
+ # instance_variable_set("@#{n}", val)
638
+ # end
639
+ # end
640
+ #end
439
641
 
440
642
  [global_options, cmd_name, command_options, argv]
441
643
  end
442
644
 
443
- def global_option_names
444
- @@global_option_names ||= []
445
- end
446
-
645
+
447
646
  # Grab the current list of command-specific option names. This is a list of the
448
647
  # long names.
449
648
  def current_command_option_names
450
- @@command_option_names ||= []
451
- @@command_index ||= 0
452
649
  (@@command_option_names[@@command_index] ||= [])
453
650
  end
454
651
 
455
- def command_index_map
456
- @@command_index_map ||= {}
457
- end
458
-
459
652
  def get_command_index(cmd)
460
- command_index_map[canonize(cmd)] || -1
461
- end
462
-
463
- def command_option_names
464
- @@command_option_names ||= []
465
- end
466
-
467
- def global_opts_parser
468
- @@global_opts_parser ||= OptionParser.new
653
+ @@command_index_map[canonize(cmd)] || -1
469
654
  end
470
655
 
471
- def default_command
472
- @@default_command ||= nil
473
- end
656
+ #
657
+ # These are the "reel" defaults
658
+ #
659
+ @@global_opts_parser.banner = "USAGE: #{$0} [global options] COMMAND [command options]"
660
+ @@global_opts_parser.on " TRY: #{$0} show-commands #{$/}"
661
+ @@command_descriptions = ["Display available commands with descriptions"]
662
+ @@default_command = Drydock.command(:show_commands).cmd
474
663
 
475
664
  end
476
665
 
477
- include Drydock
666
+
478
667
 
479
668
  trap ("SIGINT") do
480
669
  puts "#{$/}Exiting..."