drydock 0.3.3 → 0.4.0

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