clamp 1.0.1 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +60 -0
- data/.travis.yml +3 -5
- data/CHANGES.md +6 -0
- data/Gemfile +3 -2
- data/Guardfile +1 -1
- data/README.md +14 -1
- data/Rakefile +2 -2
- data/clamp.gemspec +3 -4
- data/examples/defaulted +1 -1
- data/examples/scoop +2 -2
- data/examples/speak +2 -4
- data/examples/subcommand_missing +18 -0
- data/lib/clamp.rb +2 -2
- data/lib/clamp/attribute/declaration.rb +3 -1
- data/lib/clamp/attribute/definition.rb +6 -12
- data/lib/clamp/attribute/instance.rb +1 -1
- data/lib/clamp/command.rb +33 -35
- data/lib/clamp/help.rb +11 -12
- data/lib/clamp/messages.rb +5 -28
- data/lib/clamp/option/declaration.rb +20 -19
- data/lib/clamp/option/definition.rb +11 -18
- data/lib/clamp/option/parsing.rb +27 -18
- data/lib/clamp/parameter/declaration.rb +16 -3
- data/lib/clamp/parameter/definition.rb +7 -2
- data/lib/clamp/parameter/parsing.rb +8 -1
- data/lib/clamp/subcommand/declaration.rb +28 -25
- data/lib/clamp/subcommand/definition.rb +1 -1
- data/lib/clamp/subcommand/execution.rb +7 -7
- data/lib/clamp/subcommand/parsing.rb +2 -2
- data/lib/clamp/truthy.rb +1 -1
- data/lib/clamp/version.rb +1 -1
- data/spec/clamp/command_group_spec.rb +89 -23
- data/spec/clamp/command_spec.rb +20 -21
- data/spec/clamp/messages_spec.rb +1 -1
- data/spec/clamp/option/definition_spec.rb +5 -5
- data/spec/clamp/option_module_spec.rb +1 -1
- data/spec/clamp/parameter/definition_spec.rb +1 -1
- data/spec/spec_helper.rb +1 -3
- metadata +5 -3
@@ -15,18 +15,18 @@ module Clamp
|
|
15
15
|
|
16
16
|
def instantiate_subcommand(name)
|
17
17
|
subcommand_class = find_subcommand_class(name)
|
18
|
-
|
18
|
+
subcommand = subcommand_class.new("#{invocation_path} #{name}", context)
|
19
19
|
self.class.inheritable_attributes.each do |attribute|
|
20
|
-
|
21
|
-
|
22
|
-
end
|
20
|
+
next unless attribute.of(self).defined?
|
21
|
+
attribute.of(subcommand).set(attribute.of(self).get)
|
23
22
|
end
|
24
|
-
|
23
|
+
subcommand
|
25
24
|
end
|
26
25
|
|
27
26
|
def find_subcommand_class(name)
|
28
|
-
subcommand_def = self.class.find_subcommand(name)
|
29
|
-
subcommand_def.subcommand_class
|
27
|
+
subcommand_def = self.class.find_subcommand(name)
|
28
|
+
return subcommand_def.subcommand_class if subcommand_def
|
29
|
+
subcommand_missing(name)
|
30
30
|
end
|
31
31
|
|
32
32
|
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
require
|
1
|
+
require "clamp/subcommand/execution"
|
2
2
|
|
3
3
|
module Clamp
|
4
4
|
module Subcommand
|
@@ -9,7 +9,7 @@ module Clamp
|
|
9
9
|
|
10
10
|
def parse_subcommand
|
11
11
|
return false unless self.class.has_subcommands?
|
12
|
-
|
12
|
+
extend(Subcommand::Execution)
|
13
13
|
end
|
14
14
|
|
15
15
|
private
|
data/lib/clamp/truthy.rb
CHANGED
data/lib/clamp/version.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
require
|
1
|
+
require "spec_helper"
|
2
2
|
|
3
3
|
describe Clamp::Command do
|
4
4
|
|
@@ -30,10 +30,10 @@ describe Clamp::Command do
|
|
30
30
|
it "delegates to sub-commands" do
|
31
31
|
|
32
32
|
command.run(["flip"])
|
33
|
-
expect(stdout).to match
|
33
|
+
expect(stdout).to match(/FLIPPED/)
|
34
34
|
|
35
35
|
command.run(["flop"])
|
36
|
-
expect(stdout).to match
|
36
|
+
expect(stdout).to match(/FLOPPED/)
|
37
37
|
|
38
38
|
end
|
39
39
|
|
@@ -55,13 +55,13 @@ describe Clamp::Command do
|
|
55
55
|
|
56
56
|
it "lists subcommands" do
|
57
57
|
help = command.help
|
58
|
-
expect(help).to match
|
59
|
-
expect(help).to match
|
60
|
-
expect(help).to match
|
58
|
+
expect(help).to match(/Subcommands:/)
|
59
|
+
expect(help).to match(/flip +flip it/)
|
60
|
+
expect(help).to match(/flop +flop it/)
|
61
61
|
end
|
62
62
|
|
63
63
|
it "handles new lines in subcommand descriptions" do
|
64
|
-
expect(command.help).to match
|
64
|
+
expect(command.help).to match(/flop +flop it\n +for extra flop/)
|
65
65
|
end
|
66
66
|
|
67
67
|
end
|
@@ -96,10 +96,10 @@ describe Clamp::Command do
|
|
96
96
|
it "responds to both aliases" do
|
97
97
|
|
98
98
|
command.run(["say", "boo"])
|
99
|
-
expect(stdout).to match
|
99
|
+
expect(stdout).to match(/boo/)
|
100
100
|
|
101
101
|
command.run(["talk", "jive"])
|
102
|
-
expect(stdout).to match
|
102
|
+
expect(stdout).to match(/jive/)
|
103
103
|
|
104
104
|
end
|
105
105
|
|
@@ -107,7 +107,7 @@ describe Clamp::Command do
|
|
107
107
|
|
108
108
|
it "lists all aliases" do
|
109
109
|
help = command.help
|
110
|
-
expect(help).to match
|
110
|
+
expect(help).to match(/say, talk .* Say something/)
|
111
111
|
end
|
112
112
|
|
113
113
|
end
|
@@ -137,7 +137,7 @@ describe Clamp::Command do
|
|
137
137
|
|
138
138
|
it "delegates multiple levels" do
|
139
139
|
command.run(["foo", "bar"])
|
140
|
-
expect(stdout).to match
|
140
|
+
expect(stdout).to match(/FUBAR/)
|
141
141
|
end
|
142
142
|
|
143
143
|
describe ".find_subcommand_class" do
|
@@ -170,7 +170,7 @@ describe Clamp::Command do
|
|
170
170
|
|
171
171
|
it "invokes the default subcommand" do
|
172
172
|
command.run([])
|
173
|
-
expect(stdout).to match
|
173
|
+
expect(stdout).to match(/All good/)
|
174
174
|
end
|
175
175
|
|
176
176
|
end
|
@@ -195,7 +195,7 @@ describe Clamp::Command do
|
|
195
195
|
|
196
196
|
it "invokes the default subcommand" do
|
197
197
|
command.run([])
|
198
|
-
expect(stdout).to match
|
198
|
+
expect(stdout).to match(/All good/)
|
199
199
|
end
|
200
200
|
|
201
201
|
end
|
@@ -238,13 +238,24 @@ describe Clamp::Command do
|
|
238
238
|
end
|
239
239
|
end
|
240
240
|
|
241
|
+
subcommand "say", "say it" do
|
242
|
+
subcommand "loud", "yell it" do
|
243
|
+
def execute
|
244
|
+
puts thing.upcase
|
245
|
+
end
|
246
|
+
end
|
247
|
+
end
|
248
|
+
|
241
249
|
end
|
242
250
|
|
243
251
|
it "allows the parameter to be specified first" do
|
244
|
-
|
245
252
|
command.run(["dummy", "spit"])
|
246
253
|
expect(stdout.strip).to eql "spat the dummy"
|
254
|
+
end
|
247
255
|
|
256
|
+
it "passes the parameter down the stack" do
|
257
|
+
command.run(["money", "say", "loud"])
|
258
|
+
expect(stdout.strip).to eql "MONEY"
|
248
259
|
end
|
249
260
|
|
250
261
|
end
|
@@ -282,46 +293,101 @@ describe Clamp::Command do
|
|
282
293
|
|
283
294
|
it "accepts options defined in superclass (specified after the subcommand)" do
|
284
295
|
command.run(["move", "--direction", "north"])
|
285
|
-
expect(stdout).to match
|
296
|
+
expect(stdout).to match(/walking north/)
|
286
297
|
end
|
287
298
|
|
288
299
|
it "accepts options defined in superclass (specified before the subcommand)" do
|
289
300
|
command.run(["--direction", "north", "move"])
|
290
|
-
expect(stdout).to match
|
301
|
+
expect(stdout).to match(/walking north/)
|
291
302
|
end
|
292
303
|
|
293
304
|
it "accepts options defined in included modules" do
|
294
305
|
command.run(["move", "--speed", "very quickly"])
|
295
|
-
expect(stdout).to match
|
306
|
+
expect(stdout).to match(/walking home very quickly/)
|
296
307
|
end
|
297
308
|
|
298
309
|
it "has access to command context" do
|
299
310
|
command = command_class.new("go", :motion => "wandering")
|
300
311
|
command.run(["move"])
|
301
|
-
expect(stdout).to match
|
312
|
+
expect(stdout).to match(/wandering home/)
|
302
313
|
end
|
303
314
|
|
304
315
|
end
|
305
316
|
|
306
317
|
context "with a subcommand, with options" do
|
307
318
|
|
308
|
-
given_command
|
309
|
-
option
|
319
|
+
given_command "weeheehee" do
|
320
|
+
option "--json", "JSON", "a json blob" do |option|
|
310
321
|
print "parsing!"
|
311
322
|
option
|
312
323
|
end
|
313
324
|
|
314
|
-
subcommand
|
325
|
+
subcommand "woohoohoo", "like weeheehee but with more o" do
|
315
326
|
def execute
|
316
327
|
end
|
317
328
|
end
|
318
329
|
end
|
319
330
|
|
320
331
|
it "only parses options once" do
|
321
|
-
command.run([
|
322
|
-
expect(stdout).to eql
|
332
|
+
command.run(["--json", '{"a":"b"}', "woohoohoo"])
|
333
|
+
expect(stdout).to eql "parsing!"
|
334
|
+
end
|
335
|
+
|
336
|
+
end
|
337
|
+
|
338
|
+
context "with an unknown subcommand" do
|
339
|
+
|
340
|
+
let(:subcommand_missing) do
|
341
|
+
Module.new do
|
342
|
+
def subcommand_missing(_name)
|
343
|
+
abort "there is no such thing"
|
344
|
+
end
|
345
|
+
end
|
346
|
+
end
|
347
|
+
|
348
|
+
let(:subcommand_missing_with_return) do
|
349
|
+
Module.new do
|
350
|
+
def subcommand_missing(_name)
|
351
|
+
self.class.recognised_subcommands.first.subcommand_class
|
352
|
+
end
|
353
|
+
end
|
323
354
|
end
|
324
355
|
|
356
|
+
let(:command_class) do
|
357
|
+
|
358
|
+
Class.new(Clamp::Command) do
|
359
|
+
subcommand "test", "test subcommand" do
|
360
|
+
def execute
|
361
|
+
puts "known subcommand"
|
362
|
+
end
|
363
|
+
end
|
364
|
+
|
365
|
+
def execute
|
366
|
+
end
|
367
|
+
end
|
368
|
+
end
|
369
|
+
|
370
|
+
let(:command) do
|
371
|
+
command_class.new("foo")
|
372
|
+
end
|
373
|
+
|
374
|
+
it "should signal no such subcommand usage error" do
|
375
|
+
expect { command.run(["foo"]) }.to raise_error(Clamp::UsageError) do |exception|
|
376
|
+
expect(exception.message).to eq "No such sub-command 'foo'"
|
377
|
+
end
|
378
|
+
end
|
379
|
+
|
380
|
+
it "should execute the subcommand missing method" do
|
381
|
+
command.extend subcommand_missing
|
382
|
+
expect { command.run(["foo"]) }.to raise_error(SystemExit)
|
383
|
+
expect(stderr).to match(/there is no such thing/)
|
384
|
+
end
|
385
|
+
|
386
|
+
it "should use the subcommand class returned from subcommand_missing" do
|
387
|
+
command.extend subcommand_missing_with_return
|
388
|
+
command.run(["foo"])
|
389
|
+
expect(stdout).to match(/known subcommand/)
|
390
|
+
end
|
325
391
|
end
|
326
392
|
|
327
393
|
end
|
data/spec/clamp/command_spec.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
|
2
|
-
require
|
2
|
+
require "spec_helper"
|
3
3
|
|
4
4
|
describe Clamp::Command do
|
5
5
|
|
@@ -18,7 +18,7 @@ describe Clamp::Command do
|
|
18
18
|
describe "#help" do
|
19
19
|
|
20
20
|
it "describes usage" do
|
21
|
-
expect(command.help).to match
|
21
|
+
expect(command.help).to match(/^Usage:\n cmd.*\n/)
|
22
22
|
end
|
23
23
|
|
24
24
|
end
|
@@ -147,9 +147,10 @@ describe Clamp::Command do
|
|
147
147
|
let(:args) { [] }
|
148
148
|
|
149
149
|
before do
|
150
|
-
command.class.option "--port", "PORT", "port to listen on",
|
151
|
-
|
152
|
-
|
150
|
+
command.class.option "--port", "PORT", "port to listen on",
|
151
|
+
:default => 4321,
|
152
|
+
:environment_variable => "PORT",
|
153
|
+
&:to_i
|
153
154
|
set_env("PORT", environment_value)
|
154
155
|
command.parse(args)
|
155
156
|
end
|
@@ -167,7 +168,7 @@ describe Clamp::Command do
|
|
167
168
|
let(:environment_value) { "12345" }
|
168
169
|
|
169
170
|
it "uses the environment variable" do
|
170
|
-
expect(command.port).to eql
|
171
|
+
expect(command.port).to eql 12_345
|
171
172
|
end
|
172
173
|
|
173
174
|
context "and a value is specified on the command-line" do
|
@@ -294,8 +295,8 @@ describe Clamp::Command do
|
|
294
295
|
command.class.option ["-f", "--flavour"], "FLAVOUR", "Flavour of the month"
|
295
296
|
command.class.option ["-c", "--color"], "COLOR", "Preferred hue"
|
296
297
|
command.class.option ["--scoops"], "N", "Number of scoops",
|
297
|
-
|
298
|
-
|
298
|
+
:default => 1,
|
299
|
+
:environment_variable => "DEFAULT_SCOOPS" do |arg|
|
299
300
|
Integer(arg)
|
300
301
|
end
|
301
302
|
command.class.option ["-n", "--[no-]nuts"], :flag, "Nuts (or not)\nMay include nuts"
|
@@ -486,12 +487,12 @@ describe Clamp::Command do
|
|
486
487
|
end
|
487
488
|
|
488
489
|
it "includes option details" do
|
489
|
-
expect(command.help).to match
|
490
|
-
expect(command.help).to match
|
490
|
+
expect(command.help).to match(/--flavour FLAVOUR +Flavour of the month/)
|
491
|
+
expect(command.help).to match(/--color COLOR +Preferred hue/)
|
491
492
|
end
|
492
493
|
|
493
494
|
it "handles new lines in option descriptions" do
|
494
|
-
expect(command.help).to match
|
495
|
+
expect(command.help).to match(/--\[no-\]nuts +Nuts \(or not\)\n +May include nuts/)
|
495
496
|
end
|
496
497
|
|
497
498
|
end
|
@@ -528,7 +529,7 @@ describe Clamp::Command do
|
|
528
529
|
end
|
529
530
|
|
530
531
|
it "does not map -h to help" do
|
531
|
-
expect(command.help).to_not match
|
532
|
+
expect(command.help).to_not match(/-h[, ].*help/)
|
532
533
|
end
|
533
534
|
|
534
535
|
it "still recognises --help" do
|
@@ -636,13 +637,12 @@ describe Clamp::Command do
|
|
636
637
|
|
637
638
|
before do
|
638
639
|
command.class.parameter "[FILE]", "a file", :environment_variable => "FILE",
|
639
|
-
|
640
|
+
:default => "/dev/null"
|
640
641
|
end
|
641
642
|
|
642
643
|
let(:args) { [] }
|
643
644
|
let(:environment_value) { nil }
|
644
645
|
|
645
|
-
|
646
646
|
before do
|
647
647
|
set_env("FILE", environment_value)
|
648
648
|
command.parse(args)
|
@@ -785,18 +785,17 @@ describe Clamp::Command do
|
|
785
785
|
end
|
786
786
|
|
787
787
|
it "includes parameter details" do
|
788
|
-
expect(command.help).to match
|
789
|
-
expect(command.help).to match
|
790
|
-
expect(command.help).to match
|
788
|
+
expect(command.help).to match(/X +x/)
|
789
|
+
expect(command.help).to match(/Y +y/)
|
790
|
+
expect(command.help).to match(/\[Z\] +z \(default: "ZZZ"\)/)
|
791
791
|
end
|
792
792
|
|
793
793
|
it "handles new lines in option descriptions" do
|
794
|
-
expect(command.help).to match
|
794
|
+
expect(command.help).to match(/X +x\n +xx/)
|
795
795
|
end
|
796
796
|
|
797
797
|
end
|
798
798
|
|
799
|
-
|
800
799
|
end
|
801
800
|
|
802
801
|
context "with explicit usage" do
|
@@ -853,8 +852,8 @@ describe Clamp::Command do
|
|
853
852
|
describe "#help" do
|
854
853
|
|
855
854
|
it "includes the banner" do
|
856
|
-
expect(command.help).to match
|
857
|
-
expect(command.help).to match
|
855
|
+
expect(command.help).to match(/^ Punt is an example command/)
|
856
|
+
expect(command.help).to match(/^ The prefix/)
|
858
857
|
end
|
859
858
|
|
860
859
|
end
|
data/spec/clamp/messages_spec.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
require
|
1
|
+
require "spec_helper"
|
2
2
|
|
3
3
|
describe Clamp::Option::Definition do
|
4
4
|
|
@@ -195,8 +195,8 @@ describe Clamp::Option::Definition do
|
|
195
195
|
end
|
196
196
|
|
197
197
|
it "can be overridden" do
|
198
|
-
option = described_class.new("-H", "HEADER", "extra header", :multivalued => true, :default => [1,2,3])
|
199
|
-
expect(option.default_value).to eql [1,2,3]
|
198
|
+
option = described_class.new("-H", "HEADER", "extra header", :multivalued => true, :default => [1, 2, 3])
|
199
|
+
expect(option.default_value).to eql [1, 2, 3]
|
200
200
|
end
|
201
201
|
|
202
202
|
end
|
@@ -234,7 +234,7 @@ describe Clamp::Option::Definition do
|
|
234
234
|
describe "Command#help" do
|
235
235
|
|
236
236
|
it "includes help for each option exactly once" do
|
237
|
-
subcommand = command_class.send(:find_subcommand,
|
237
|
+
subcommand = command_class.send(:find_subcommand, "foo")
|
238
238
|
subcommand_help = subcommand.subcommand_class.help("")
|
239
239
|
expect(subcommand_help.lines.grep(/--bar BAR/).count).to eql 1
|
240
240
|
end
|
@@ -247,7 +247,7 @@ describe Clamp::Option::Definition do
|
|
247
247
|
it "rejects :default" do
|
248
248
|
expect do
|
249
249
|
described_class.new("--key-file", "FILE", "SSH identity",
|
250
|
-
|
250
|
+
:required => true, :default => "hello")
|
251
251
|
end.to raise_error(ArgumentError)
|
252
252
|
end
|
253
253
|
|