drydock 0.4.0 → 0.5.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.
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: