clamp 0.0.7 → 0.0.9

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.
@@ -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