clamp 0.0.7 → 0.0.9

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,63 @@
1
+ module Clamp
2
+
3
+ class Parameter
4
+
5
+ def initialize(name, description, options = {})
6
+ @name = name
7
+ @description = description
8
+ infer_attribute_name_and_multiplicity
9
+ if options.has_key?(:attribute_name)
10
+ @attribute_name = options[:attribute_name].to_s
11
+ end
12
+ end
13
+
14
+ attr_reader :name, :description, :attribute_name
15
+
16
+ def help
17
+ [name, description]
18
+ end
19
+
20
+ def consume(arguments)
21
+ if required? && arguments.empty?
22
+ raise ArgumentError, "no value provided"
23
+ end
24
+ if multivalued?
25
+ arguments.shift(arguments.length)
26
+ else
27
+ arguments.shift
28
+ end
29
+ end
30
+
31
+ private
32
+
33
+ def infer_attribute_name_and_multiplicity
34
+ case @name
35
+ when /^\[(\S+)\]$/
36
+ @attribute_name = $1
37
+ when /^\[(\S+)\] ...$/
38
+ @attribute_name = "#{$1}_list"
39
+ @multivalued = true
40
+ when /^(\S+) ...$/
41
+ @attribute_name = "#{$1}_list"
42
+ @multivalued = true
43
+ @required = true
44
+ when /^\S+$/
45
+ @attribute_name = @name
46
+ @required = true
47
+ else
48
+ raise "invalid parameter name: '#{name}'"
49
+ end
50
+ @attribute_name = @attribute_name.downcase.tr('-', '_')
51
+ end
52
+
53
+ def multivalued?
54
+ @multivalued
55
+ end
56
+
57
+ def required?
58
+ @required
59
+ end
60
+
61
+ end
62
+
63
+ end
@@ -0,0 +1,24 @@
1
+ require 'clamp/attribute_declaration'
2
+ require 'clamp/parameter'
3
+
4
+ module Clamp
5
+ class Parameter
6
+
7
+ module Declaration
8
+
9
+ include AttributeDeclaration
10
+
11
+ def parameters
12
+ @parameters ||= []
13
+ end
14
+
15
+ def parameter(name, description, options = {}, &block)
16
+ parameter = Parameter.new(name, description, options)
17
+ parameters << parameter
18
+ define_accessors_for(parameter, &block)
19
+ end
20
+
21
+ end
22
+
23
+ end
24
+ end
@@ -0,0 +1,30 @@
1
+ module Clamp
2
+ class Parameter
3
+
4
+ module Parsing
5
+
6
+ protected
7
+
8
+ def parse_parameters
9
+
10
+ return false if self.class.parameters.empty?
11
+
12
+ self.class.parameters.each do |parameter|
13
+ begin
14
+ value = parameter.consume(arguments)
15
+ send("#{parameter.attribute_name}=", value)
16
+ rescue ArgumentError => e
17
+ signal_usage_error "parameter '#{parameter.name}': #{e.message}"
18
+ end
19
+ end
20
+
21
+ unless arguments.empty?
22
+ signal_usage_error "too many arguments"
23
+ end
24
+
25
+ end
26
+
27
+ end
28
+
29
+ end
30
+ end
@@ -0,0 +1,35 @@
1
+ module Clamp
2
+
3
+ class Subcommand < Struct.new(:name, :description, :subcommand_class)
4
+
5
+ def help
6
+ [name, description]
7
+ end
8
+
9
+ module Declaration
10
+
11
+ def recognised_subcommands
12
+ @recognised_subcommands ||= []
13
+ end
14
+
15
+ def subcommand(name, description, subcommand_class = self, &block)
16
+ if block
17
+ # generate a anonymous sub-class
18
+ subcommand_class = Class.new(subcommand_class, &block)
19
+ end
20
+ recognised_subcommands << Subcommand.new(name, description, subcommand_class)
21
+ end
22
+
23
+ def has_subcommands?
24
+ !recognised_subcommands.empty?
25
+ end
26
+
27
+ def find_subcommand(name)
28
+ recognised_subcommands.find { |sc| sc.name == name }
29
+ end
30
+
31
+ end
32
+
33
+ end
34
+
35
+ end
@@ -0,0 +1,32 @@
1
+ module Clamp
2
+ class Subcommand
3
+
4
+ module Execution
5
+
6
+ protected
7
+
8
+ def execute_subcommand
9
+ signal_usage_error "no subcommand specified" if arguments.empty?
10
+ subcommand_name = arguments.shift
11
+ subcommand_class = find_subcommand_class(subcommand_name)
12
+ subcommand = subcommand_class.new("#{name} #{subcommand_name}", context)
13
+ subcommand.parent_command = self
14
+ subcommand.run(arguments)
15
+ end
16
+
17
+ private
18
+
19
+ def find_subcommand(name)
20
+ self.class.find_subcommand(name) ||
21
+ signal_usage_error("No such sub-command '#{name}'")
22
+ end
23
+
24
+ def find_subcommand_class(name)
25
+ subcommand = find_subcommand(name)
26
+ subcommand.subcommand_class if subcommand
27
+ end
28
+
29
+ end
30
+
31
+ end
32
+ end
data/lib/clamp/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Clamp
2
- VERSION = "0.0.7".freeze
2
+ VERSION = "0.0.9".freeze
3
3
  end
@@ -83,6 +83,11 @@ describe Clamp::Command do
83
83
  stdout.should =~ /walking north/
84
84
  end
85
85
 
86
+ it "accepts parents options (specified before the subcommand)" do
87
+ @command.run(["--direction", "north", "walk"])
88
+ stdout.should =~ /walking north/
89
+ end
90
+
86
91
  it "has access to command context" do
87
92
  @command.run(["walk"])
88
93
  stdout.should =~ /wandering south by default/
@@ -27,25 +27,6 @@ describe Clamp::Command do
27
27
 
28
28
  end
29
29
 
30
- describe "#parse" do
31
-
32
- it "sets arguments" do
33
- @command.parse(%w(a b c))
34
- @command.arguments.should == %w(a b c)
35
- end
36
-
37
- describe "with an unrecognised option" do
38
-
39
- it "raises a UsageError" do
40
- lambda do
41
- @command.parse(%w(--foo bar))
42
- end.should raise_error(Clamp::UsageError)
43
- end
44
-
45
- end
46
-
47
- end
48
-
49
30
  describe "#run" do
50
31
 
51
32
  before do
@@ -72,6 +53,19 @@ describe Clamp::Command do
72
53
  @command.flavour.should == "chocolate"
73
54
  end
74
55
 
56
+ describe "with type :flag" do
57
+
58
+ before do
59
+ @command.class.option "--verbose", :flag, "Be heartier"
60
+ end
61
+
62
+ it "declares a predicate-style reader" do
63
+ @command.should respond_to(:verbose?)
64
+ @command.should_not respond_to(:verbose)
65
+ end
66
+
67
+ end
68
+
75
69
  describe "with explicit :attribute_name" do
76
70
 
77
71
  before do
@@ -102,26 +96,56 @@ describe Clamp::Command do
102
96
 
103
97
  end
104
98
 
105
- end
99
+ describe "with a block" do
106
100
 
101
+ before do
102
+ @command.class.option "--port", "PORT", "Port to listen on" do |port|
103
+ Integer(port)
104
+ end
105
+ end
106
+
107
+ it "uses the block to validate and convert the option argument" do
108
+ lambda do
109
+ @command.port = "blah"
110
+ end.should raise_error(ArgumentError)
111
+ @command.port = "1234"
112
+ @command.port.should == 1234
113
+ end
114
+
115
+ end
116
+
117
+ end
118
+
107
119
  describe "with options declared" do
108
120
 
109
121
  before do
110
122
  @command.class.option "--flavour", "FLAVOUR", "Flavour of the month"
111
123
  @command.class.option "--color", "COLOR", "Preferred hue"
124
+ @command.class.option "--[no-]nuts", :flag, "Nuts (or not)"
112
125
  end
113
126
 
114
127
  describe "#parse" do
115
128
 
129
+ describe "with an unrecognised option" do
130
+
131
+ it "raises a UsageError" do
132
+ lambda do
133
+ @command.parse(%w(--foo bar))
134
+ end.should raise_error(Clamp::UsageError)
135
+ end
136
+
137
+ end
138
+
116
139
  describe "with options" do
117
140
 
118
141
  before do
119
- @command.parse(%w(--flavour strawberry --color blue a b c))
142
+ @command.parse(%w(--flavour strawberry --nuts --color blue a b c))
120
143
  end
121
144
 
122
- it "extracts the option values" do
145
+ it "maps the option values onto the command object" do
123
146
  @command.flavour.should == "strawberry"
124
147
  @command.color.should == "blue"
148
+ @command.nuts?.should == true
125
149
  end
126
150
 
127
151
  it "retains unconsumed arguments" do
@@ -148,6 +172,53 @@ describe Clamp::Command do
148
172
 
149
173
  end
150
174
 
175
+ describe "with --flag" do
176
+
177
+ before do
178
+ @command.parse(%w(--nuts))
179
+ end
180
+
181
+ it "sets the flag" do
182
+ @command.nuts?.should be_true
183
+ end
184
+
185
+ end
186
+
187
+ describe "with --no-flag" do
188
+
189
+ before do
190
+ @command.nuts = true
191
+ @command.parse(%w(--no-nuts))
192
+ end
193
+
194
+ it "clears the flag" do
195
+ @command.nuts?.should be_false
196
+ end
197
+
198
+ end
199
+
200
+ describe "when option-writer raises an ArgumentError" do
201
+
202
+ before do
203
+ @command.class.class_eval do
204
+
205
+ def color=(c)
206
+ unless c == "black"
207
+ raise ArgumentError, "sorry, we're out of #{c}"
208
+ end
209
+ end
210
+
211
+ end
212
+ end
213
+
214
+ it "re-raises it as a UsageError" do
215
+ lambda do
216
+ @command.parse(%w(--color red))
217
+ end.should raise_error(Clamp::UsageError, /^option '--color': sorry, we're out of red/)
218
+ end
219
+
220
+ end
221
+
151
222
  end
152
223
 
153
224
  describe "#help" do
@@ -165,117 +236,124 @@ describe Clamp::Command do
165
236
 
166
237
  end
167
238
 
168
- describe "with a flag option declared" do
239
+ describe ".parameter" do
169
240
 
170
- before do
171
- @command.class.option "--verbose", :flag, "Be heartier"
241
+ it "declares option argument accessors" do
242
+ @command.class.parameter "FLAVOUR", "flavour of the month"
243
+ @command.flavour.should == nil
244
+ @command.flavour = "chocolate"
245
+ @command.flavour.should == "chocolate"
172
246
  end
173
247
 
174
- it "declares a predicate-style reader" do
175
- @command.should respond_to(:verbose?)
176
- @command.should_not respond_to(:verbose)
177
- end
248
+ describe "with explicit :attribute_name" do
178
249
 
179
- describe "#parse" do
250
+ before do
251
+ @command.class.parameter "FOO", "a foo", :attribute_name => :bar
252
+ end
180
253
 
181
- describe "with option" do
254
+ it "uses the specified attribute_name name to name accessors" do
255
+ @command.bar = "chocolate"
256
+ @command.bar.should == "chocolate"
257
+ end
182
258
 
183
- before do
184
- @command.parse(%w(--verbose foo))
185
- end
259
+ end
186
260
 
187
- it "sets the flag" do
188
- @command.should be_verbose
189
- end
261
+ describe "with a block" do
190
262
 
191
- it "does not consume an argument" do
192
- @command.arguments.should == %w(foo)
263
+ before do
264
+ @command.class.parameter "PORT", "port to listen on" do |port|
265
+ Integer(port)
193
266
  end
267
+ end
194
268
 
269
+ it "uses the block to validate and convert the argument" do
270
+ lambda do
271
+ @command.port = "blah"
272
+ end.should raise_error(ArgumentError)
273
+ @command.port = "1234"
274
+ @command.port.should == 1234
195
275
  end
196
276
 
197
277
  end
198
278
 
199
279
  end
200
280
 
201
- describe "with a negatable flag option declared" do
281
+ describe "with no parameters declared" do
202
282
 
283
+ describe "#parse" do
284
+
285
+ it "retains arguments for handling by #execute" do
286
+ @command.parse(["crash", "bang", "wallop"])
287
+ @command.arguments.should == ["crash", "bang", "wallop"]
288
+ end
289
+
290
+ end
291
+
292
+ end
293
+
294
+ describe "with parameters declared" do
295
+
203
296
  before do
204
- @command.class.option "--[no-]sync", :flag, "Synchronise"
297
+ @command.class.parameter "X", "x"
298
+ @command.class.parameter "Y", "y"
299
+ @command.class.parameter "[Z]", "z"
205
300
  end
206
301
 
207
302
  describe "#parse" do
208
-
209
- describe "with --flag" do
210
-
303
+
304
+ describe "with arguments for all parameters" do
305
+
211
306
  before do
212
- @command.parse(%w(--sync))
307
+ @command.parse(["crash", "bang", "wallop"])
213
308
  end
214
309
 
215
- it "sets the flag" do
216
- @command.sync?.should be_true
310
+ it "maps arguments onto the command object" do
311
+ @command.x.should == "crash"
312
+ @command.y.should == "bang"
313
+ @command.z.should == "wallop"
217
314
  end
218
315
 
219
- end
220
-
221
- describe "with --no-flag" do
222
-
223
- before do
224
- @command.sync = true
225
- @command.parse(%w(--no-sync))
226
- end
227
-
228
- it "clears the flag" do
229
- @command.sync?.should be_false
316
+ it "consumes all the arguments" do
317
+ @command.arguments.should == []
230
318
  end
231
-
319
+
232
320
  end
233
321
 
234
- end
235
-
236
- end
237
-
238
- describe ".option, with a block" do
239
-
240
- before do
241
- @command.class.option "--port", "PORT", "Port to listen on" do |port|
242
- Integer(port)
322
+ describe "with insufficient arguments" do
323
+
324
+ it "raises a UsageError" do
325
+ lambda do
326
+ @command.parse(["crash"])
327
+ end.should raise_error(Clamp::UsageError, "parameter 'Y': no value provided")
328
+ end
329
+
243
330
  end
244
- end
245
-
246
- it "uses the block to validate and convert the option argument" do
247
- lambda do
248
- @command.port = "blah"
249
- end.should raise_error(ArgumentError)
250
- @command.port = "1234"
251
- @command.port.should == 1234
252
- end
253
-
254
- describe "#parse" do
255
331
 
256
- describe "with a valid option argument" do
332
+ describe "with optional argument omitted" do
257
333
 
258
- it "stores the converted value" do
259
- @command.parse(%w(--port 4321))
260
- @command.port.should == 4321
334
+ it "defaults the optional argument to nil" do
335
+ @command.parse(["crash", "bang"])
336
+ @command.x.should == "crash"
337
+ @command.y.should == "bang"
338
+ @command.z.should == nil
261
339
  end
262
-
340
+
263
341
  end
264
342
 
265
- describe "with an invalid option argument" do
266
-
343
+ describe "with too many arguments" do
344
+
267
345
  it "raises a UsageError" do
268
346
  lambda do
269
- @command.parse(%w(--port blah))
270
- end.should raise_error(Clamp::UsageError, /^option '--port': invalid value/)
347
+ @command.parse(["crash", "bang", "wallop", "kapow"])
348
+ end.should raise_error(Clamp::UsageError, "too many arguments")
271
349
  end
272
-
350
+
273
351
  end
274
-
352
+
275
353
  end
276
-
354
+
277
355
  end
278
-
356
+
279
357
  describe "with explicit usage" do
280
358
 
281
359
  given_command("blah") do