drydock 0.4.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (6) hide show
  1. data/CHANGES.txt +29 -1
  2. data/README.rdoc +35 -35
  3. data/bin/example +33 -18
  4. data/drydock.gemspec +11 -6
  5. data/lib/drydock.rb +208 -70
  6. metadata +4 -4
@@ -1,8 +1,36 @@
1
1
  DRYDOCK, CHANGES
2
2
 
3
+ #### TODO ###############################
4
+
5
+ * Support putting descriptions into resource file (or __END__)
6
+ * Define aliases with "command [:name, :alias]"
7
+ * Add Drydock::Console, Drydock::Window, Drydock::Cursor
8
+ * Generate scripts in the form: script-action
9
+ * globals can be configured with env vars.
10
+ * Motivation to stick to a single environment (just stage)
11
+ * Add convenience methods for system calls: sh, write, read
12
+
13
+
14
+ #### 0.5 (2009-03-11) ###############################
15
+
16
+ * NEW: Checks that the command class is a subclass of Drydock::Command
17
+ * CHANGE: Cleaned up show-commands screen
18
+ * FIXED: Help didn't work when using command alias
19
+ * NEW: Named argv values.
20
+ * CHANGE: argv are now part of the Command class (not passed to command blocks)
21
+ * CHANGE: "project" now automatically requires the lowercase name of the project
22
+ and gracefully continues if the require failed.
23
+ * CHANGE: Drydock will look for different validation method, based on the method
24
+ being executed. If a validation method is found it's executed and
25
+ must return a true valid (it can also raise its own exceptions).
26
+ * NEW: command actions. These are boolean switches with a twist. Drydock looks
27
+ for command_action or action_command methods. Saves checking the switches
28
+ and sending to other methods manually.
29
+
30
+
3
31
  #### 0.4 (2009-02-28) ###############################
4
32
 
5
- * FIX: Bug, "interning empty string" error when bare "option" is used
33
+ * FIXED: "interning empty string" error when bare "option" is used
6
34
  * NEW: Calls valid? method (if present) before calling command block.
7
35
  * NEW: "capture" method. Auto capture STDOUT to obj.stdout etc...
8
36
  * NEW: Automatically calls init and print_header methods before the command
@@ -1,10 +1,10 @@
1
- = Drydock - v0.4
1
+ = Drydock - v0.5
2
2
 
3
3
  Inspired by github-gem and bmizerany-frylock.
4
4
 
5
5
  == Overview
6
6
 
7
- Drydock is a seaworthy DSL for command line apps. It is contained in a single .rb which can be copied directly into your project.
7
+ Drydock is a seaworthy DSL for command line apps. It's contained in a single .rb file so it's easy to copy directly into your project.
8
8
 
9
9
  == Install
10
10
 
@@ -32,41 +32,41 @@ See bin/example for more.
32
32
  end
33
33
 
34
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
-
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
41
54
 
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
-
55
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
- desc "Do something with John West's Smoked Oysters"
63
- command :oysters => JohnWestSmokedOysters do |obj|
64
- p obj # => #<JohnWestSmokedOysters:0x42179c ... >
65
- end
66
-
67
- desc "My way of saying hello!"
68
- command :ahoy! => JohnWestSmokedOysters
69
- # If you don't provide a block, Drydock will call JohnWestSmokedOysters#ahoy!
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
+ desc "Do something with John West's Smoked Oysters"
63
+ command :oysters => JohnWestSmokedOysters do |obj|
64
+ p obj # => #<JohnWestSmokedOysters:0x42179c ... >
65
+ end
66
+
67
+ desc "My way of saying hello!"
68
+ command :ahoy! => JohnWestSmokedOysters
69
+ # If you don't provide a block, Drydock will call JohnWestSmokedOysters#ahoy!
70
70
 
71
71
 
72
72
  == More Information
@@ -4,11 +4,17 @@
4
4
  #
5
5
  # This is a functioning script so you can copy it, run it,
6
6
  # and just generally be a longshoreman about things. This is
7
- # a Drydock after all.
7
+ # a drydock after all.
8
8
  #
9
- # If you're reading this via the Rdocs you won't see the code. Try:
9
+ # If you're reading this via the Rdocs you won't see the code. See:
10
+ #
11
+ # http://github.com/delano/drydock/blob/drydock-0.5.0/bin/example
12
+ #
13
+ # For an example of a complex command-line application using
14
+ # Drydock, see:
15
+ #
16
+ # http://github.com/solutious/rudy
10
17
  #
11
- # http://github.com/delano/drydock/blob/master/bin/example
12
18
 
13
19
  $:.unshift File.expand_path(File.join(File.dirname(__FILE__), '..')), 'lib'
14
20
 
@@ -33,9 +39,8 @@ end
33
39
 
34
40
  desc "A friendly welcome to the Drydock"
35
41
  command :welcome do
36
- puts "Welcome to Drydock."
37
- puts "For available commands:"
38
- puts "#{$0} show-commands"
42
+ puts "Welcome to Drydock.", $/
43
+ puts "For available commands: #{$0} show-commands"
39
44
  end
40
45
 
41
46
  usage "USAGE: #{$0} laugh [-f]"
@@ -63,8 +68,7 @@ end
63
68
 
64
69
  usage "#{$0} [-s] [-vv] date"
65
70
  desc "Display the current date"
66
- command :date do |obj, argv|
67
- # +argv+ is an array containing the unnamed arguments
71
+ command :date do |obj|
68
72
  require 'time'
69
73
  now = Time.now
70
74
  puts "(Not verbose enough. Try adding a -v.)" if (obj.global.verbose || 0) == 1
@@ -75,14 +79,15 @@ end
75
79
 
76
80
  ignore :options
77
81
  desc "This command ignores options"
78
- command :rogue do |obj, argv|
82
+ command :rogue do |obj|
79
83
  # You can use ignore :options to tell Drydock to not process the
80
84
  # command-specific options.
81
- if argv.empty?
85
+ # Unnamed arguments are available from obj.argv
86
+ if obj.argv.empty?
82
87
  puts "Had you supplied some arguments, I would have ignored them."
83
88
  else
84
89
  puts "Hi! You supplied some arguments but I ignored them."
85
- puts "They're all still here in this array: %s" % argv.join(', ')
90
+ puts "They're all still here in this array: %s" % obj.argv.join(', ')
86
91
  end
87
92
  end
88
93
 
@@ -102,9 +107,10 @@ command :ahoy! => JohnWestSmokedOysters
102
107
  # If you don't provide a block, Drydock will call JohnWestSmokedOysters#ahoy!
103
108
 
104
109
 
110
+ require 'yaml'
105
111
 
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'
112
+ usage 'ruby bin/example uri -c -d " " -t 15 http://solutious.com/'
113
+ usage 'echo "http://solutious.com/" | ruby bin/example uri -c -d " " -t 15'
108
114
  desc "Check for broken URIs"
109
115
  option :c, :check, "Check response codes for each URI"
110
116
  option :d, :delim, String, "Output delimiter"
@@ -114,18 +120,23 @@ option :t, :timeout, Float, "Timeout value for HTTP request" do |v|
114
120
  v = 10 if (v > 10)
115
121
  v
116
122
  end
117
- command :uri do |obj, argv, stdin|
118
- # +cmd+ is the string used to evoke this command. Useful with alias_command (see below).
119
- # +stdin+ is either an IO object or a custom object defined with a stdin block (see below)
123
+ argv :uris
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.
120
128
 
121
129
  require 'net/http'
122
130
  require 'uri'
123
131
  require 'timeout'
124
132
 
125
- uris = [stdin, argv].flatten # Combine the argv and stdin arrays
133
+ uris = []
134
+ uris += obj.stdin if obj.stdin
135
+ uris += obj.argv.uris if obj.argv.uris
136
+
126
137
  delim = obj.option.delim || ','
127
138
  timeout = obj.option.timeout || 5
128
- code = :notchecked # The default code when :check is false
139
+ code = :notchecked # The default code when :check is false
129
140
 
130
141
  if uris.empty?
131
142
  puts "Frylock: You didn't provide any URIs. "
@@ -137,6 +148,10 @@ command :uri do |obj, argv, stdin|
137
148
  code = response_code(uri, timeout) if (obj.option.check)
138
149
  puts [index+1, uri, code].join(delim)
139
150
  end
151
+
152
+ # NOTE: The alias used to evoke this command is available
153
+ # via obj.alias
154
+
140
155
  end
141
156
  alias_command :checkuri, :uri
142
157
 
@@ -1,13 +1,17 @@
1
1
  @spec = Gem::Specification.new do |s|
2
2
  s.name = %q{drydock}
3
- s.version = "0.4.0"
3
+ s.version = "0.5.0"
4
+ s.date = %q{2009-03-11}
4
5
  s.specification_version = 1 if s.respond_to? :specification_version=
5
6
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
6
7
 
7
8
  s.authors = ["Delano Mandelbaum"]
8
- s.date = %q{2008-08-17}
9
- s.description = %q{A seaworthy DSL for writing command line apps inspired by Blake Mizerany's Frylock}
9
+ s.description = %q{A seaworthy DSL for writing command line apps}
10
+ s.summary = s.description
10
11
  s.email = %q{delano@solutious.com}
12
+
13
+ # = MANIFEST =
14
+ # git ls-files
11
15
  s.files = %w(
12
16
  CHANGES.txt
13
17
  LICENSE.txt
@@ -17,13 +21,14 @@
17
21
  drydock.gemspec
18
22
  lib/drydock.rb
19
23
  )
24
+
25
+ # s.add_dependency ''
26
+
20
27
  s.has_rdoc = true
21
28
  s.homepage = %q{http://github.com/delano/drydock}
22
29
  s.extra_rdoc_files = %w[README.rdoc LICENSE.txt CHANGES.txt]
23
- s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Drydock: a seaworthy DSL for command-line apps", "--main", "README.rdoc"]
30
+ s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Drydock: #{s.description}", "--main", "README.rdoc"]
24
31
  s.require_paths = ["lib"]
25
32
  s.rubygems_version = %q{1.1.1}
26
- s.summary = %q{A seaworthy DSL for writing command line apps}
27
-
28
33
  s.rubyforge_project = "drydock"
29
34
  end
@@ -3,34 +3,64 @@ require 'ostruct'
3
3
  require 'stringio'
4
4
 
5
5
  module Drydock
6
+ class FancyArray < Array #:nodoc:
7
+ attr_reader :fields
8
+ def add_field(n)
9
+ @fields ||= []
10
+ field_name = n
11
+ eval <<-RUBY, binding, '(Drydock::FancyArray)', 1
12
+ def #{n}
13
+ if self.size > @fields.size && '#{n}'.to_sym == @fields.last
14
+ self[#{@fields.size}..-1]
15
+ else
16
+ self[#{@fields.size}]
17
+ end
18
+ end
19
+ RUBY
20
+ @fields << n
21
+ n
22
+ end
23
+ def fields=(*args)
24
+ args.flatten.each do |field|
25
+ add_field(field)
26
+ end
27
+ end
28
+ end
29
+
30
+
6
31
  # The base class for all command objects. There is an instance of this class
7
32
  # for every command defined. Global and command-specific options are added
8
33
  # as attributes to this class dynamically.
9
34
  #
10
- # i.e. "example -v date -f yaml"
35
+ # i.e. "example -v select --location kumamoto"
11
36
  #
12
37
  # global :v, :verbose, "I want mooooore!"
13
- # option :f, :format, String, "Long date format"
14
- # command :date do |obj|
15
- # puts obj.global.verbose #=> true
16
- # puts obj.option.format #=> "yaml"
38
+ # option :l, :location, String, "Source location"
39
+ # command :select do |obj|
40
+ # puts obj.global.verbose #=> true
41
+ # puts obj.option.location #=> "kumamoto"
42
+ # end
43
+ #
44
+ # You can sub-class it to create your own:
45
+ #
46
+ # class Malpeque < Drydock::Command
47
+ # # ... sea to it
17
48
  # end
18
49
  #
19
- # You can inherit from this class to create your own: EatFood < Drydock::Command.
20
- # And then specific your class in the command definition:
50
+ # And then specify your class in the command definition:
21
51
  #
22
- # command :eat => EatFood do |obj|; ...; end
52
+ # command :eat => Malpeque do |obj|
53
+ # # ... do stuff with your obj
54
+ # end
23
55
  #
24
56
  class Command
25
- VERSION = 0.4
57
+ VERSION = 0.5
26
58
  # The canonical name of the command (the one used in the command definition). If you
27
59
  # inherit from this class and add a method named +cmd+, you can leave omit the block
28
60
  # in the command definition. That method will be called instead. See bin/examples.
29
61
  attr_reader :cmd
30
62
  # The name used to evoke this command (it's either the canonical name or the alias used).
31
63
  attr_reader :alias
32
- # A friendly description of the command.
33
- attr_accessor :desc
34
64
  # The block that will be executed when this command is evoked. If the block is nil
35
65
  # it will check if there is a method named +cmd+. If so, that will be executed.
36
66
  attr_reader :b
@@ -38,6 +68,15 @@ module Drydock
38
68
  attr_reader :option
39
69
  # An OpenStruct object containing the global options specified at run-time.
40
70
  attr_reader :global
71
+ # A friendly description of the command.
72
+ attr_accessor :desc
73
+ # An array of action names specified in the command definition
74
+ attr_accessor :actions
75
+ # An instance of Drydock::FancyArray. Acts like an array of unnamed arguments
76
+ # but also allows field names if supplied.
77
+ attr_accessor :argv
78
+ # Either an IO handle to STDIN or the output of the Drydock#stdin handler.
79
+ attr_reader :stdin
41
80
 
42
81
  # The default constructor sets the short name of the command
43
82
  # and stores a reference to the block (if supplied).
@@ -49,6 +88,9 @@ module Drydock
49
88
  def initialize(cmd, &b)
50
89
  @cmd = (cmd.kind_of?(Symbol)) ? cmd : cmd.to_sym
51
90
  @b = b
91
+ @actions = []
92
+ @argv = Drydock::FancyArray.new # an array with field names
93
+ @stdin = STDIN
52
94
  @option = OpenStruct.new
53
95
  @global = OpenStruct.new
54
96
 
@@ -65,13 +107,13 @@ module Drydock
65
107
  #
66
108
  # Calls self.init before calling the block. Implement this method when
67
109
  #
68
- # +cmd_str+ is the short name used to evoke this command. It will equal @cmd
69
- # unless an alias was used used to evoke this command.
70
- # +argv+ an array of unnamed arguments. If ignore :options was declared this
71
- # will contain the arguments exactly as they were defined on the command-line.
72
- # +stdin+ contains the output of stdin do; ...; end otherwise it's a STDIN IO handle.
73
- # +global_options+ a hash of the global options specified on the command-line
74
- # +options+ a hash of the command-specific options specific on the command-line.
110
+ # <li>+cmd_str+ is the short name used to evoke this command. It will equal @cmd
111
+ # unless an alias was used used to evoke this command.</li>
112
+ # <li>+argv+ an array of unnamed arguments. If ignore :options was declared this</li>
113
+ # will contain the arguments exactly as they were defined on the command-line.</li>
114
+ # <li>+stdin+ contains the output of stdin do; ...; end otherwise it's a STDIN IO handle.</li>
115
+ # <li>+global_options+ a hash of the global options specified on the command-line</li>
116
+ # <li>+options+ a hash of the command-specific options specific on the command-line.</li>
75
117
  def call(cmd_str=nil, argv=[], stdin=[], global_options={}, options={})
76
118
  @alias = cmd_str.nil? ? @cmd : cmd_str
77
119
 
@@ -83,15 +125,35 @@ module Drydock
83
125
  self.option.send("#{n}=", v) # ... and also the command options
84
126
  end
85
127
 
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?'
89
128
 
90
- block_args = [self, argv, stdin]
129
+ @argv << argv # TODO: Using += returns an Array instead of FancyArray
130
+ @argv.flatten! # NOTE: << creates @argv[[]]
131
+ @stdin = stdin
132
+
133
+ self.init if self.respond_to? :init # Must be called first!
134
+ self.print_header if self.respond_to? :print_header
91
135
 
92
136
  if @b
93
- @b.call(*block_args[0..(@b.arity-1)]) # send only as many args as defined
137
+ run_validation
138
+ @b.call(self)
139
+
140
+ elsif !(chosen = find_action(options)).empty?
141
+ raise "Only one action at a time please! I can't #{chosen.join(' AND ')}." if chosen.size > 1
142
+ criteria = [[@cmd, chosen.first], [chosen.first, @cmd]]
143
+ meth = name = nil
144
+ # Try command_action, then action_command
145
+ criteria.each do |tuple|
146
+ name = tuple.join('_')
147
+ meth = name if self.respond_to?(name)
148
+ end
149
+
150
+ raise "#{self.class} needs a #{name} method!" unless meth
151
+
152
+ run_validation(meth)
153
+ self.send(meth)
154
+
94
155
  elsif self.respond_to? @cmd.to_sym
156
+ run_validation(@cmd)
95
157
  self.send(@cmd)
96
158
  else
97
159
  raise "The command #{@alias} has no block and #{self.class} has no #{@cmd} method!"
@@ -101,6 +163,40 @@ module Drydock
101
163
 
102
164
  end
103
165
 
166
+ # <li>+meth+ The method name used to determine the name of the validation method.
167
+ # If not supplied, the validation method is "valid?" otherwise it's "meth_valid?"</li>
168
+ # If the command class doesn't have the given validation method, we'll just continue
169
+ # on our way.
170
+ #
171
+ # Recognized validation methods are:
172
+ #
173
+ # def valid? # if we're executing a command block
174
+ # def command_valid? # if we're executing an object method
175
+ # def command_action_valid? # if the main meth is command_action
176
+ # def action_command_valid? # if the main meth is action_command
177
+ #
178
+ # This method raises a generic exception when the validation method returns false.
179
+ # However, <strong>it's more appropriate for the validation methods to raise
180
+ # detailed exceptions</strong>.
181
+ #
182
+ def run_validation(meth=nil)
183
+ vmeth = meth ? [meth, 'valid?'].join('_') : 'valid?'
184
+ is_valid = self.respond_to?(vmeth) ? self.send(vmeth) : true
185
+ raise "Your request is not valid. See #{$0} #{@cmd} -h" unless is_valid
186
+ end
187
+ private :run_validation
188
+
189
+ # Compares the list of known actions to the list of boolean switches supplied
190
+ # on the command line (if any).
191
+ # <li>+options+ is a hash of the named command line arguments (created by
192
+ # OptionParser#getopts)</li>
193
+ # Returns an array of action names (empty if no action was supplied)
194
+ def find_action(options)
195
+ boolkeys = options.keys.select { |n| options[n] == true } || []
196
+ (@actions || []) & boolkeys # an array of requested actions (or empty)
197
+ end
198
+ private :find_action
199
+
104
200
  # Print the list of available commands to STDOUT. This is used as the
105
201
  # "default" command unless another default commands is supplied. You
106
202
  # can also write your own Drydock::Command#show_commands to override
@@ -108,17 +204,25 @@ module Drydock
108
204
  def show_commands
109
205
  project = " for #{Drydock.project}" if Drydock.project?
110
206
  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
-
207
+ cmds = {}
208
+ Drydock.commands.keys.each do |cmd|
209
+ pretty = Drydock.decanonize(cmd)
114
210
  # Out to sea
211
+ cmds[Drydock.commands[cmd].cmd] ||= {}
115
212
  unless cmd === Drydock.commands[cmd].cmd
116
- msg = "See: #{Drydock.decanonize(Drydock.commands[cmd].cmd)} (this is an alias)"
213
+ (cmds[Drydock.commands[cmd].cmd][:aliases] ||= []) << pretty
214
+ next
117
215
  end
118
-
119
- puts " %16s: %s" % [Drydock.decanonize(cmd), msg]
216
+ cmds[cmd][:desc] = Drydock.commands[cmd].desc
217
+ cmds[cmd][:pretty] = cmd
120
218
  end
121
-
219
+
220
+ cmds.keys.sort{ |a,b| a.to_s <=> b.to_s }.each do |cmd|
221
+ p = cmds[cmd]
222
+ puts " %16s: %s" % [p[:pretty], p[:desc]]
223
+ puts " %17s (%s: %s)" % ['', "aliases", cmds[cmd][:aliases].join(', ')] if cmds[cmd][:aliases]
224
+ end
225
+
122
226
  puts
123
227
  puts "%6s: %s" % ["Try", "#{$0} -h"]
124
228
  puts "%6s %s" % ["", "#{$0} COMMAND -h"]
@@ -170,23 +274,6 @@ module Drydock
170
274
 
171
275
  VERSION = 0.4
172
276
 
173
- private
174
- # Disabled. We're going basic, using a module and include/extend.
175
- # Stolen from Sinatra!
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
189
-
190
277
  @@project = nil
191
278
 
192
279
  @@debug = false
@@ -198,6 +285,7 @@ module Drydock
198
285
 
199
286
  @@command_opts_parser = []
200
287
  @@command_option_names = []
288
+ @@command_actions = []
201
289
 
202
290
  @@default_command = nil
203
291
 
@@ -205,6 +293,7 @@ module Drydock
205
293
  @@command_descriptions = []
206
294
  @@command_index = 0
207
295
  @@command_index_map = {}
296
+ @@command_argv_names = [] # an array of names for values of argv
208
297
 
209
298
  @@capture = nil # contains one of :stdout, :stderr
210
299
  @@captured = nil
@@ -231,11 +320,31 @@ module Drydock
231
320
  @@debug
232
321
  end
233
322
 
323
+ # Provide names for CLI arguments, in the order they appear.
324
+ #
325
+ # $ yourscript sample malpeque zinqy
326
+ # argv :name, :flavour
327
+ # command :sample do |obj|
328
+ # obj.argv.name # => malpeque
329
+ # obj.argv.flavour # => zinqy
330
+ # end
331
+ #
332
+ def argv(*args)
333
+ @@command_argv_names[@@command_index] ||= []
334
+ @@command_argv_names[@@command_index] += args.flatten
335
+ end
336
+
234
337
  # The project of the script. This is currently only used when printing
235
338
  # list of commands (see: Drydock::Command#show_commands). It may be
236
339
  # used elsewhere in the future.
237
340
  def project(txt=nil)
341
+
238
342
  return @@project unless txt
343
+
344
+ begin
345
+ require txt.downcase
346
+ rescue LoadError
347
+ end
239
348
  @@project = txt
240
349
  end
241
350
 
@@ -255,7 +364,7 @@ module Drydock
255
364
  # # ...
256
365
  # end
257
366
  #
258
- # default :hullinspector do # This one will b named "hullinspector"
367
+ # default :hullinspector do # This one will be named "hullinspector"
259
368
  # # ...
260
369
  # end
261
370
  #
@@ -266,9 +375,11 @@ module Drydock
266
375
  end
267
376
 
268
377
  # Define a block for processing STDIN before the command is called.
269
- # The command block receives the return value of this block in a named argument:
378
+ # The command block receives the return value of this block as obj.stdin:
270
379
  #
271
- # command :task do |obj, argv, stdin|; ...; end
380
+ # command :task do |obj|;
381
+ # obj.stdin # => ...
382
+ # end
272
383
  #
273
384
  # If a stdin block isn't defined, +stdin+ above will be the STDIN IO handle.
274
385
  def stdin(&b)
@@ -358,9 +469,23 @@ module Drydock
358
469
  current_command_option_names << option_parser(args, &b)
359
470
  end
360
471
 
361
-
362
-
363
-
472
+ # Define an command-specific action.
473
+ #
474
+ # This is functionality very similar to option, but with an exciting and buoyant twist:
475
+ # Drydock keeps track of actions for each command (in addition to treating it like an option).
476
+ # When an action is specifiec on the command line Drydock looks for command_action or
477
+ # action_command methods in the command class.
478
+ #
479
+ # action :E, :eat, "Eat something"
480
+ # command :oysters => Fresh::Oysters
481
+ #
482
+ # # Drydock will look for Fresh::Oysters#eat_oysters and Fresh::Oysters#oysters_eat.
483
+ #
484
+ def action(*args, &b)
485
+ ret = option(*args, &b) # returns an array of all the current option names
486
+ current_command_action << ret.last # the most recent is last
487
+ end
488
+
364
489
  # Define a command.
365
490
  #
366
491
  # command :task do
@@ -376,15 +501,24 @@ module Drydock
376
501
  #
377
502
  def command(*cmds, &b)
378
503
  cmd = cmds.first
504
+
379
505
  if cmd.is_a? Hash
380
- c = cmd.values.first.new(cmd.keys.first, &b)
506
+ raise "#{cmd.values.first} is not a subclass of Drydock::Command" unless cmd.values.first.ancestors.member?(Drydock::Command)
507
+ c = cmd.values.first.new(cmd.keys.first, &b) # A custom class was specified
508
+ # TODO: handle command [:task, :alias] => Class
509
+ #elsif cmd.is_a? Array
510
+ # p cmd
381
511
  else
382
512
  c = Drydock::Command.new(cmd, &b)
383
513
  end
384
514
 
385
515
  @@command_descriptions[@@command_index] ||= ""
516
+ @@command_actions[@@command_index] ||= []
517
+ @@command_argv_names[@@command_index] ||= []
386
518
 
387
519
  c.desc = @@command_descriptions[@@command_index]
520
+ c.actions = @@command_actions[@@command_index]
521
+ c.argv.fields = @@command_argv_names[@@command_index]
388
522
 
389
523
  # Default Usage Banner.
390
524
  # Without this, there's no help displayed for the command.
@@ -406,8 +540,8 @@ module Drydock
406
540
  #
407
541
  # Either name can be used on the command-line:
408
542
  #
409
- # $ script task [options]
410
- # $ script pointer [options]
543
+ # $ yourscript task [options]
544
+ # $ yourscript pointer [options]
411
545
  #
412
546
  # Inside of the command definition, you have access to the
413
547
  # command name that was used via obj.alias.
@@ -499,20 +633,6 @@ module Drydock
499
633
  !@@capture.nil?
500
634
  end
501
635
 
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
515
-
516
636
  # Returns true if a command with the name +cmd+ has been defined.
517
637
  def command?(cmd)
518
638
  name = canonize(cmd)
@@ -649,10 +769,28 @@ module Drydock
649
769
  (@@command_option_names[@@command_index] ||= [])
650
770
  end
651
771
 
772
+ def current_command_action
773
+ (@@command_actions[@@command_index] ||= [])
774
+ end
775
+
652
776
  def get_command_index(cmd)
653
777
  @@command_index_map[canonize(cmd)] || -1
654
778
  end
655
779
 
780
+ # Grab the options parser for the current command or create it if it doesn't exist.
781
+ # Returns an instance of OptionParser.
782
+ def get_current_option_parser
783
+ (@@command_opts_parser[@@command_index] ||= OptionParser.new)
784
+ end
785
+
786
+ # Grabs the options parser for the given command.
787
+ # +arg+ can be an index or command name.
788
+ # Returns an instance of OptionParser.
789
+ def get_option_parser(arg)
790
+ index = arg.is_a?(String) ? get_command_index(arg) : arg
791
+ (@@command_opts_parser[index] ||= OptionParser.new)
792
+ end
793
+
656
794
  #
657
795
  # These are the "reel" defaults
658
796
  #
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: drydock
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Delano Mandelbaum
@@ -9,11 +9,11 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2008-08-17 00:00:00 -04:00
12
+ date: 2009-03-11 00:00:00 -04:00
13
13
  default_executable:
14
14
  dependencies: []
15
15
 
16
- description: A seaworthy DSL for writing command line apps inspired by Blake Mizerany's Frylock
16
+ description: A seaworthy DSL for writing command line apps
17
17
  email: delano@solutious.com
18
18
  executables: []
19
19
 
@@ -38,7 +38,7 @@ rdoc_options:
38
38
  - --line-numbers
39
39
  - --inline-source
40
40
  - --title
41
- - "Drydock: a seaworthy DSL for command-line apps"
41
+ - "Drydock: A seaworthy DSL for writing command line apps"
42
42
  - --main
43
43
  - README.rdoc
44
44
  require_paths: