jls-clamp 0.3.1

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,766 @@
1
+ require 'spec_helper'
2
+
3
+ describe Clamp::Command do
4
+
5
+ extend CommandFactory
6
+ include OutputCapture
7
+
8
+ given_command("cmd") do
9
+
10
+ def execute
11
+ puts "Hello, world"
12
+ end
13
+
14
+ end
15
+
16
+ describe "#help" do
17
+
18
+ it "describes usage" do
19
+ @command.help.should =~ /^Usage:\n cmd.*\n/
20
+ end
21
+
22
+ end
23
+
24
+ describe "#run" do
25
+
26
+ before do
27
+ @command.run([])
28
+ end
29
+
30
+ it "executes the #execute method" do
31
+ stdout.should_not be_empty
32
+ end
33
+
34
+ end
35
+
36
+ describe ".option" do
37
+
38
+ it "declares option argument accessors" do
39
+ @command.class.option "--flavour", "FLAVOUR", "Flavour of the month"
40
+ @command.flavour.should == nil
41
+ @command.flavour = "chocolate"
42
+ @command.flavour.should == "chocolate"
43
+ end
44
+
45
+ describe "with type :flag" do
46
+
47
+ before do
48
+ @command.class.option "--verbose", :flag, "Be heartier"
49
+ end
50
+
51
+ it "declares a predicate-style reader" do
52
+ @command.should respond_to(:verbose?)
53
+ @command.should_not respond_to(:verbose)
54
+ end
55
+
56
+ end
57
+
58
+ describe "with explicit :attribute_name" do
59
+
60
+ before do
61
+ @command.class.option "--foo", "FOO", "A foo", :attribute_name => :bar
62
+ end
63
+
64
+ it "uses the specified attribute_name name to name accessors" do
65
+ @command.bar = "chocolate"
66
+ @command.bar.should == "chocolate"
67
+ end
68
+
69
+ it "does not attempt to create the default accessors" do
70
+ @command.should_not respond_to(:foo)
71
+ @command.should_not respond_to(:foo=)
72
+ end
73
+
74
+ end
75
+
76
+ describe "with default method" do
77
+
78
+ before do
79
+ @command.class.option "--port", "PORT", "port"
80
+ @command.class.class_eval do
81
+ def default_port
82
+ 4321
83
+ end
84
+ end
85
+ end
86
+
87
+ it "sets the specified default value" do
88
+ @command.port.should == 4321
89
+ end
90
+
91
+ end
92
+
93
+ describe "with :default value" do
94
+
95
+ before do
96
+ @command.class.option "--port", "PORT", "port to listen on", :default => 4321
97
+ end
98
+
99
+ it "declares default method" do
100
+ @command.default_port.should == 4321
101
+ end
102
+
103
+ describe "#help" do
104
+
105
+ it "describes the default value" do
106
+ @command.help.should include("port to listen on (default: 4321)")
107
+ end
108
+
109
+ end
110
+
111
+ end
112
+
113
+ describe "with :env value" do
114
+
115
+ before do
116
+ @command.class.option "--port", "PORT", "port to listen on", :default => 4321, :env => "PORT" do |value|
117
+ value.to_i
118
+ end
119
+ end
120
+
121
+ it "should use the default if neither flag nor env var are present" do
122
+ @command.parse([])
123
+ @command.port.should == 4321
124
+ end
125
+
126
+ it "should use the env value if present (instead of default)" do
127
+ ENV["PORT"] = rand(10000).to_s
128
+ @command.parse([])
129
+ @command.port.should == ENV["PORT"].to_i
130
+ end
131
+
132
+ it "should use the the flag value if present (instead of env)" do
133
+ ENV["PORT"] = "12345"
134
+ @command.parse(%w(--port 1500))
135
+ @command.port.should == 1500
136
+ end
137
+
138
+ describe "#help" do
139
+
140
+ it "describes the default value and env usage" do
141
+ @command.help.should include("port to listen on (default: 4321) (env: \"PORT\")")
142
+ end
143
+
144
+ end
145
+
146
+ end
147
+
148
+ describe "with :env value on a :flag option" do
149
+
150
+ before do
151
+ @command.class.option "--[no-]enable", :flag, "enable?", :default => false, :env => "ENABLE"
152
+ end
153
+
154
+ it "should use the default if neither flag nor env var are present" do
155
+ @command.parse([])
156
+ @command.enable?.should == false
157
+ end
158
+
159
+ it "should use an env value of '1' to mean truth" do
160
+ ENV["ENABLE"] = "1"
161
+ @command.parse([])
162
+ @command.enable?.should == true
163
+ end
164
+
165
+ it "should use an env value other than '1' to mean false" do
166
+ ENV["ENABLE"] = "0"
167
+ @command.parse([])
168
+ @command.enable?.should == false
169
+ end
170
+
171
+ it "should use the the flag value if present (instead of env)" do
172
+ ENV["ENABLE"] = "1"
173
+ @command.parse(%w(--no-enable))
174
+ @command.enable?.should == false
175
+ end
176
+
177
+ describe "#help" do
178
+
179
+ it "describes the default value and env usage" do
180
+ @command.help.should include("enable? (default: false) (env: \"ENABLE\")")
181
+ end
182
+
183
+ end
184
+
185
+ end
186
+
187
+ describe "with a block" do
188
+
189
+ before do
190
+ @command.class.option "--port", "PORT", "Port to listen on" do |port|
191
+ Integer(port)
192
+ end
193
+ end
194
+
195
+ it "uses the block to validate and convert the option argument" do
196
+ lambda do
197
+ @command.port = "blah"
198
+ end.should raise_error(ArgumentError)
199
+ @command.port = "1234"
200
+ @command.port.should == 1234
201
+ end
202
+
203
+ end
204
+
205
+ end
206
+
207
+ describe "with options declared" do
208
+
209
+ before do
210
+ @command.class.option ["-f", "--flavour"], "FLAVOUR", "Flavour of the month"
211
+ @command.class.option ["-c", "--color"], "COLOR", "Preferred hue"
212
+ @command.class.option ["-n", "--[no-]nuts"], :flag, "Nuts (or not)\nMay include nuts"
213
+ @command.class.parameter "[ARG] ...", "extra arguments", :attribute_name => :arguments
214
+ end
215
+
216
+ describe "#parse" do
217
+
218
+ describe "with an unrecognised option" do
219
+
220
+ it "raises a UsageError" do
221
+ lambda do
222
+ @command.parse(%w(--foo bar))
223
+ end.should raise_error(Clamp::UsageError)
224
+ end
225
+
226
+ end
227
+
228
+ describe "with options" do
229
+
230
+ before do
231
+ @command.parse(%w(--flavour strawberry --nuts --color blue))
232
+ end
233
+
234
+ it "maps the option values onto the command object" do
235
+ @command.flavour.should == "strawberry"
236
+ @command.color.should == "blue"
237
+ @command.nuts?.should == true
238
+ end
239
+
240
+ end
241
+
242
+ describe "with short options" do
243
+
244
+ before do
245
+ @command.parse(%w(-f strawberry -c blue))
246
+ end
247
+
248
+ it "recognises short options as aliases" do
249
+ @command.flavour.should == "strawberry"
250
+ @command.color.should == "blue"
251
+ end
252
+
253
+ end
254
+
255
+ describe "with combined short options" do
256
+
257
+ before do
258
+ @command.parse(%w(-nf strawberry))
259
+ end
260
+
261
+ it "works as though the options were separate" do
262
+ @command.flavour.should == "strawberry"
263
+ @command.nuts?.should == true
264
+ end
265
+
266
+ end
267
+
268
+ describe "with option arguments attached using equals sign" do
269
+
270
+ before do
271
+ @command.parse(%w(--flavour=strawberry --color=blue))
272
+ end
273
+
274
+ it "works as though the option arguments were separate" do
275
+ @command.flavour.should == "strawberry"
276
+ @command.color.should == "blue"
277
+ end
278
+
279
+ end
280
+
281
+ describe "with option-like things beyond the arguments" do
282
+
283
+ it "treats them as positional arguments" do
284
+ @command.parse(%w(a b c --flavour strawberry))
285
+ @command.arguments.should == %w(a b c --flavour strawberry)
286
+ end
287
+
288
+ end
289
+
290
+ describe "with an option terminator" do
291
+
292
+ it "considers everything after the terminator to be an argument" do
293
+ @command.parse(%w(--color blue -- --flavour strawberry))
294
+ @command.arguments.should == %w(--flavour strawberry)
295
+ end
296
+
297
+ end
298
+
299
+ describe "with --flag" do
300
+
301
+ before do
302
+ @command.parse(%w(--nuts))
303
+ end
304
+
305
+ it "sets the flag" do
306
+ @command.nuts?.should be_true
307
+ end
308
+
309
+ end
310
+
311
+ describe "with --no-flag" do
312
+
313
+ before do
314
+ @command.nuts = true
315
+ @command.parse(%w(--no-nuts))
316
+ end
317
+
318
+ it "clears the flag" do
319
+ @command.nuts?.should be_false
320
+ end
321
+
322
+ end
323
+
324
+ describe "with --help" do
325
+
326
+ it "requests help" do
327
+ lambda do
328
+ @command.parse(%w(--help))
329
+ end.should raise_error(Clamp::HelpWanted)
330
+ end
331
+
332
+ end
333
+
334
+ describe "with -h" do
335
+
336
+ it "requests help" do
337
+ lambda do
338
+ @command.parse(%w(-h))
339
+ end.should raise_error(Clamp::HelpWanted)
340
+ end
341
+
342
+ end
343
+
344
+ describe "when option-writer raises an ArgumentError" do
345
+
346
+ before do
347
+ @command.class.class_eval do
348
+
349
+ def color=(c)
350
+ unless c == "black"
351
+ raise ArgumentError, "sorry, we're out of #{c}"
352
+ end
353
+ end
354
+
355
+ end
356
+ end
357
+
358
+ it "re-raises it as a UsageError" do
359
+ lambda do
360
+ @command.parse(%w(--color red))
361
+ end.should raise_error(Clamp::UsageError, /^option '--color': sorry, we're out of red/)
362
+ end
363
+
364
+ end
365
+
366
+ end
367
+
368
+ describe "#help" do
369
+
370
+ it "indicates that there are options" do
371
+ @command.help.should include("cmd [OPTIONS]")
372
+ end
373
+
374
+ it "includes option details" do
375
+ @command.help.should =~ %r(--flavour FLAVOUR +Flavour of the month)
376
+ @command.help.should =~ %r(--color COLOR +Preferred hue)
377
+ end
378
+
379
+ it "handles new lines in option descriptions" do
380
+ @command.help.should =~ %r(--\[no-\]nuts +Nuts \(or not\)\n +May include nuts)
381
+ end
382
+
383
+ end
384
+
385
+ end
386
+
387
+ describe "with an explicit --help option declared" do
388
+
389
+ before do
390
+ @command.class.option ["--help"], :flag, "help wanted"
391
+ end
392
+
393
+ it "does not generate implicit help option" do
394
+ lambda do
395
+ @command.parse(%w(--help))
396
+ end.should_not raise_error
397
+ @command.help.should be_true
398
+ end
399
+
400
+ it "does not recognise -h" do
401
+ lambda do
402
+ @command.parse(%w(-h))
403
+ end.should raise_error(Clamp::UsageError)
404
+ end
405
+
406
+ end
407
+
408
+ describe "with an explicit -h option declared" do
409
+
410
+ before do
411
+ @command.class.option ["-h", "--humidity"], "PERCENT", "relative humidity" do |n|
412
+ Integer(n)
413
+ end
414
+ end
415
+
416
+ it "does not map -h to help" do
417
+ @command.help.should_not =~ %r( -h[, ].*help)
418
+ end
419
+
420
+ it "still recognises --help" do
421
+ lambda do
422
+ @command.parse(%w(--help))
423
+ end.should raise_error(Clamp::HelpWanted)
424
+ end
425
+
426
+ end
427
+
428
+ describe ".parameter" do
429
+
430
+ it "declares option argument accessors" do
431
+ @command.class.parameter "FLAVOUR", "flavour of the month"
432
+ @command.flavour.should == nil
433
+ @command.flavour = "chocolate"
434
+ @command.flavour.should == "chocolate"
435
+ end
436
+
437
+ describe "with explicit :attribute_name" do
438
+
439
+ before do
440
+ @command.class.parameter "FOO", "a foo", :attribute_name => :bar
441
+ end
442
+
443
+ it "uses the specified attribute_name name to name accessors" do
444
+ @command.bar = "chocolate"
445
+ @command.bar.should == "chocolate"
446
+ end
447
+
448
+ end
449
+
450
+ describe "with :default value" do
451
+
452
+ before do
453
+ @command.class.parameter "[ORIENTATION]", "direction", :default => "west"
454
+ end
455
+
456
+ it "sets the specified default value" do
457
+ @command.orientation.should == "west"
458
+ end
459
+
460
+ describe "#help" do
461
+
462
+ it "describes the default value" do
463
+ @command.help.should include("direction (default: \"west\")")
464
+ end
465
+
466
+ end
467
+
468
+ end
469
+
470
+ describe "with a block" do
471
+
472
+ before do
473
+ @command.class.parameter "PORT", "port to listen on" do |port|
474
+ Integer(port)
475
+ end
476
+ end
477
+
478
+ it "uses the block to validate and convert the argument" do
479
+ lambda do
480
+ @command.port = "blah"
481
+ end.should raise_error(ArgumentError)
482
+ @command.port = "1234"
483
+ @command.port.should == 1234
484
+ end
485
+
486
+ end
487
+
488
+ describe "with ellipsis" do
489
+
490
+ before do
491
+ @command.class.parameter "FILE ...", "files"
492
+ end
493
+
494
+ it "accepts multiple arguments" do
495
+ @command.parse(%w(X Y Z))
496
+ @command.file_list.should == %w(X Y Z)
497
+ end
498
+
499
+ end
500
+
501
+ describe "optional, with ellipsis" do
502
+
503
+ before do
504
+ @command.class.parameter "[FILE] ...", "files"
505
+ end
506
+
507
+ it "default to an empty list" do
508
+ @command.parse([])
509
+ @command.default_file_list.should == []
510
+ @command.file_list.should == []
511
+ end
512
+
513
+ end
514
+
515
+ end
516
+
517
+ describe "with no parameters declared" do
518
+
519
+ describe "#parse" do
520
+
521
+ describe "with arguments" do
522
+
523
+ it "raises a UsageError" do
524
+ lambda do
525
+ @command.parse(["crash"])
526
+ end.should raise_error(Clamp::UsageError, "too many arguments")
527
+ end
528
+
529
+ end
530
+
531
+ end
532
+
533
+ end
534
+
535
+ describe "with parameters declared" do
536
+
537
+ before do
538
+ @command.class.parameter "X", "x\nxx"
539
+ @command.class.parameter "Y", "y"
540
+ @command.class.parameter "[Z]", "z", :default => "ZZZ"
541
+ end
542
+
543
+ describe "#parse" do
544
+
545
+ describe "with arguments for all parameters" do
546
+
547
+ before do
548
+ @command.parse(["crash", "bang", "wallop"])
549
+ end
550
+
551
+ it "maps arguments onto the command object" do
552
+ @command.x.should == "crash"
553
+ @command.y.should == "bang"
554
+ @command.z.should == "wallop"
555
+ end
556
+
557
+ end
558
+
559
+ describe "with insufficient arguments" do
560
+
561
+ it "raises a UsageError" do
562
+ lambda do
563
+ @command.parse(["crash"])
564
+ end.should raise_error(Clamp::UsageError, "parameter 'Y': no value provided")
565
+ end
566
+
567
+ end
568
+
569
+ describe "with optional argument omitted" do
570
+
571
+ it "defaults the optional argument" do
572
+ @command.parse(["crash", "bang"])
573
+ @command.x.should == "crash"
574
+ @command.y.should == "bang"
575
+ @command.z.should == "ZZZ"
576
+ end
577
+
578
+ end
579
+
580
+ describe "with too many arguments" do
581
+
582
+ it "raises a UsageError" do
583
+ lambda do
584
+ @command.parse(["crash", "bang", "wallop", "kapow"])
585
+ end.should raise_error(Clamp::UsageError, "too many arguments")
586
+ end
587
+
588
+ end
589
+
590
+ end
591
+
592
+ describe "#help" do
593
+
594
+ it "indicates that there are parameters" do
595
+ @command.help.should include("cmd [OPTIONS] X Y [Z]")
596
+ end
597
+
598
+ it "includes parameter details" do
599
+ @command.help.should =~ %r(X +x)
600
+ @command.help.should =~ %r(Y +y)
601
+ @command.help.should =~ %r(\[Z\] +z \(default: "ZZZ"\))
602
+ end
603
+
604
+ it "handles new lines in option descriptions" do
605
+ @command.help.should =~ %r(X +x\n +xx)
606
+ end
607
+
608
+ end
609
+
610
+
611
+ end
612
+
613
+ describe "with explicit usage" do
614
+
615
+ given_command("blah") do
616
+
617
+ usage "FOO BAR ..."
618
+
619
+ end
620
+
621
+ describe "#help" do
622
+
623
+ it "includes the explicit usage" do
624
+ @command.help.should include("blah FOO BAR ...\n")
625
+ end
626
+
627
+ end
628
+
629
+ end
630
+
631
+ describe "with multiple usages" do
632
+
633
+ given_command("put") do
634
+
635
+ usage "THIS HERE"
636
+ usage "THAT THERE"
637
+
638
+ end
639
+
640
+ describe "#help" do
641
+
642
+ it "includes both potential usages" do
643
+ @command.help.should include("put THIS HERE\n")
644
+ @command.help.should include("put THAT THERE\n")
645
+ end
646
+
647
+ end
648
+
649
+ end
650
+
651
+ describe "with a description" do
652
+
653
+ given_command("punt") do
654
+
655
+ self.description = <<-EOF
656
+ Punt is an example command. It doesn't do much, really.
657
+
658
+ The prefix at the beginning of this description should be normalised
659
+ to two spaces.
660
+ EOF
661
+
662
+ end
663
+
664
+ describe "#help" do
665
+
666
+ it "includes the description" do
667
+ @command.help.should =~ /^ Punt is an example command/
668
+ @command.help.should =~ /^ The prefix/
669
+ end
670
+
671
+ end
672
+
673
+ end
674
+
675
+ describe ".run" do
676
+
677
+ it "creates a new Command instance and runs it" do
678
+ @command.class.class_eval do
679
+ parameter "WORD ...", "words"
680
+ def execute
681
+ print word_list.inspect
682
+ end
683
+ end
684
+ @xyz = %w(x y z)
685
+ @command.class.run("cmd", @xyz)
686
+ stdout.should == @xyz.inspect
687
+ end
688
+
689
+ describe "invoked with a context hash" do
690
+
691
+ it "makes the context available within the command" do
692
+ @command.class.class_eval do
693
+ def execute
694
+ print context[:foo]
695
+ end
696
+ end
697
+ @command.class.run("xyz", [], :foo => "bar")
698
+ stdout.should == "bar"
699
+ end
700
+
701
+ end
702
+
703
+ describe "when there's a UsageError" do
704
+
705
+ before do
706
+
707
+ @command.class.class_eval do
708
+ def execute
709
+ signal_usage_error "bad dog!"
710
+ end
711
+ end
712
+
713
+ begin
714
+ @command.class.run("cmd", [])
715
+ rescue SystemExit => e
716
+ @system_exit = e
717
+ end
718
+
719
+ end
720
+
721
+ it "outputs the error message" do
722
+ stderr.should include "ERROR: bad dog!"
723
+ end
724
+
725
+ it "outputs help" do
726
+ stderr.should include "See: 'cmd --help'"
727
+ end
728
+
729
+ it "exits with a non-zero status" do
730
+ @system_exit.should_not be_nil
731
+ @system_exit.status.should == 1
732
+ end
733
+
734
+ end
735
+
736
+ describe "when help is requested" do
737
+
738
+ it "outputs help" do
739
+ @command.class.run("cmd", ["--help"])
740
+ stdout.should include "Usage:"
741
+ end
742
+
743
+ end
744
+
745
+ end
746
+
747
+ describe "subclass" do
748
+
749
+ before do
750
+ @parent_command_class = Class.new(Clamp::Command) do
751
+ option "--verbose", :flag, "be louder"
752
+ end
753
+ @derived_command_class = Class.new(@parent_command_class) do
754
+ option "--iterations", "N", "number of times to go around"
755
+ end
756
+ @command = @derived_command_class.new("cmd")
757
+ end
758
+
759
+ it "inherits options from it's superclass" do
760
+ @command.parse(["--verbose"])
761
+ @command.should be_verbose
762
+ end
763
+
764
+ end
765
+
766
+ end