clamp 1.0.1 → 1.1.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.
- 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
|
|