clamp 1.0.1 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,7 +1,7 @@
1
1
  module Clamp
2
2
  module Subcommand
3
3
 
4
- class Definition < Struct.new(:name, :description, :subcommand_class)
4
+ Definition = Struct.new(:name, :description, :subcommand_class) do
5
5
 
6
6
  def initialize(names, description, subcommand_class)
7
7
  @names = Array(names)
@@ -15,18 +15,18 @@ module Clamp
15
15
 
16
16
  def instantiate_subcommand(name)
17
17
  subcommand_class = find_subcommand_class(name)
18
- parent_attribute_values = {}
18
+ subcommand = subcommand_class.new("#{invocation_path} #{name}", context)
19
19
  self.class.inheritable_attributes.each do |attribute|
20
- if attribute.of(self).defined?
21
- parent_attribute_values[attribute] = attribute.of(self).get
22
- end
20
+ next unless attribute.of(self).defined?
21
+ attribute.of(subcommand).set(attribute.of(self).get)
23
22
  end
24
- subcommand_class.new("#{invocation_path} #{name}", context, parent_attribute_values)
23
+ subcommand
25
24
  end
26
25
 
27
26
  def find_subcommand_class(name)
28
- subcommand_def = self.class.find_subcommand(name) || signal_usage_error(Clamp.message(:no_such_subcommand, :name => 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 'clamp/subcommand/execution'
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
- self.extend(Subcommand::Execution)
12
+ extend(Subcommand::Execution)
13
13
  end
14
14
 
15
15
  private
data/lib/clamp/truthy.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  module Clamp
2
2
 
3
- TRUTHY_VALUES = %w(1 yes enable on true)
3
+ TRUTHY_VALUES = %w(1 yes enable on true).freeze
4
4
 
5
5
  def self.truthy?(arg)
6
6
  TRUTHY_VALUES.include?(arg.to_s.downcase)
data/lib/clamp/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Clamp
2
- VERSION = "1.0.1".freeze
2
+ VERSION = "1.1.0".freeze
3
3
  end
@@ -1,4 +1,4 @@
1
- require 'spec_helper'
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 /FLIPPED/
33
+ expect(stdout).to match(/FLIPPED/)
34
34
 
35
35
  command.run(["flop"])
36
- expect(stdout).to match /FLOPPED/
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 /Subcommands:/
59
- expect(help).to match /flip +flip it/
60
- expect(help).to match /flop +flop it/
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 /flop +flop it\n +for extra flop/
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 /boo/
99
+ expect(stdout).to match(/boo/)
100
100
 
101
101
  command.run(["talk", "jive"])
102
- expect(stdout).to match /jive/
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 /say, talk .* Say something/
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 /FUBAR/
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 /All good/
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 /All good/
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 /walking north/
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 /walking north/
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 /walking home very quickly/
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 /wandering home/
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 'weeheehee' do
309
- option '--json', 'JSON', 'a json blob' do |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 'woohoohoo', 'like weeheehee but with more o' do
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(['--json', '{"a":"b"}', 'woohoohoo'])
322
- expect(stdout).to eql 'parsing!'
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
@@ -1,5 +1,5 @@
1
1
 
2
- require 'spec_helper'
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 /^Usage:\n cmd.*\n/
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", :default => 4321, :environment_variable => "PORT" do |value|
151
- value.to_i
152
- end
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 12345
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
- :default => 1,
298
- :environment_variable => "DEFAULT_SCOOPS" do |arg|
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 %r(--flavour FLAVOUR +Flavour of the month)
490
- expect(command.help).to match %r(--color COLOR +Preferred hue)
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 %r(--\[no-\]nuts +Nuts \(or not\)\n +May include nuts)
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 %r( -h[, ].*help)
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
- :default => "/dev/null"
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 %r(X +x)
789
- expect(command.help).to match %r(Y +y)
790
- expect(command.help).to match %r(\[Z\] +z \(default: "ZZZ"\))
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 %r(X +x\n +xx)
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 /^ Punt is an example command/
857
- expect(command.help).to match /^ The prefix/
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
@@ -1,5 +1,5 @@
1
1
 
2
- require 'spec_helper'
2
+ require "spec_helper"
3
3
 
4
4
  describe Clamp::Messages do
5
5
 
@@ -1,4 +1,4 @@
1
- require 'spec_helper'
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, 'foo')
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
- :required => true, :default => "hello")
250
+ :required => true, :default => "hello")
251
251
  end.to raise_error(ArgumentError)
252
252
  end
253
253