clamp 1.4.0 → 1.5.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.
@@ -1,1280 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "spec_helper"
4
-
5
- describe Clamp::Command do
6
-
7
- extend CommandFactory
8
- include OutputCapture
9
- include SetEnv
10
-
11
- given_command("cmd") do
12
-
13
- def execute
14
- puts "Hello, world"
15
- end
16
-
17
- end
18
-
19
- describe "#help" do
20
-
21
- it "describes usage" do
22
- expect(command.help).to match(/^Usage:\n cmd.*\n/)
23
- end
24
-
25
- end
26
-
27
- describe "#run" do
28
-
29
- before do
30
- command.run([])
31
- end
32
-
33
- it "executes the #execute method" do
34
- expect(stdout).not_to be_empty
35
- end
36
-
37
- end
38
-
39
- describe ".option" do
40
-
41
- context "when regular" do
42
-
43
- before do
44
- command.class.option "--flavour", "FLAVOUR", "Flavour of the month"
45
- end
46
-
47
- context "when not set" do
48
- it "equals nil" do
49
- expect(command.flavour).to be_nil
50
- end
51
- end
52
-
53
- context "when set" do
54
- before do
55
- command.flavour = "chocolate"
56
- end
57
-
58
- it "equals the value" do
59
- expect(command.flavour).to eq "chocolate"
60
- end
61
- end
62
-
63
- end
64
-
65
- context "with type :flag" do
66
-
67
- before do
68
- command.class.option "--verbose", :flag, "Be heartier"
69
- end
70
-
71
- describe "predicate-style reader" do
72
-
73
- it "exists" do
74
- expect(command).to respond_to(:verbose?)
75
- end
76
-
77
- end
78
-
79
- describe "regular reader" do
80
-
81
- it "does not exist" do
82
- expect(command).not_to respond_to(:verbose)
83
- end
84
-
85
- end
86
-
87
- end
88
-
89
- context "with explicit :attribute_name" do
90
-
91
- before do
92
- command.class.option "--foo", "FOO", "A foo", attribute_name: :bar
93
- end
94
-
95
- it "uses the specified attribute_name name to name accessors" do
96
- command.bar = "chocolate"
97
- expect(command.bar).to eq "chocolate"
98
- end
99
-
100
- describe "default reader" do
101
- it "does not exist" do
102
- expect(command).not_to respond_to(:foo)
103
- end
104
- end
105
-
106
- describe "default writer" do
107
- it "does not exist" do
108
- expect(command).not_to respond_to(:foo=)
109
- end
110
- end
111
-
112
- end
113
-
114
- context "with default method" do
115
-
116
- before do
117
- command.class.option "--port", "PORT", "port"
118
- command.class.class_eval do
119
- def default_port
120
- 4321
121
- end
122
- end
123
- end
124
-
125
- it "sets the specified default value" do
126
- expect(command.port).to eq 4321
127
- end
128
-
129
- end
130
-
131
- context "with :default value" do
132
-
133
- before do
134
- command.class.option "--port", "PORT", "port to listen on", default: 4321
135
- end
136
-
137
- it "declares default method" do
138
- expect(command.default_port).to eq 4321
139
- end
140
-
141
- describe "#help" do
142
-
143
- it "describes the default value" do
144
- expect(command.help).to include("port to listen on (default: 4321)")
145
- end
146
-
147
- end
148
-
149
- end
150
-
151
- context "without :default value" do
152
-
153
- before do
154
- command.class.option "--port", "PORT", "port to listen on"
155
- end
156
-
157
- it "does not declare default method" do
158
- expect(command).not_to respond_to(:default_port)
159
- end
160
-
161
- end
162
-
163
- context "with :multivalued" do
164
-
165
- before do
166
- command.class.option "--flavour", "FLAVOUR", "flavour(s)", multivalued: true, attribute_name: :flavours
167
- end
168
-
169
- it "defaults to empty array" do
170
- expect(command.flavours).to be_empty
171
- end
172
-
173
- it "supports multiple values" do
174
- command.parse(%w[--flavour chocolate --flavour vanilla])
175
- expect(command.flavours).to eq %w[chocolate vanilla]
176
- end
177
-
178
- it "generates a single-value appender method" do
179
- command.append_to_flavours("mud")
180
- command.append_to_flavours("pie")
181
- expect(command.flavours).to eq %w[mud pie]
182
- end
183
-
184
- it "generates a multi-value setter method" do
185
- command.append_to_flavours("replaceme")
186
- command.flavours = %w[mud pie]
187
- expect(command.flavours).to eq %w[mud pie]
188
- end
189
-
190
- it "does not require a value" do
191
- expect do
192
- command.parse([])
193
- end.not_to raise_error
194
- end
195
-
196
- end
197
-
198
- context "with :environment_variable" do
199
-
200
- let(:environment_value) { nil }
201
- let(:args) { [] }
202
-
203
- before do
204
- command.class.option "--port", "PORT", "port to listen on",
205
- default: 4321,
206
- environment_variable: "PORT",
207
- &:to_i
208
- set_env("PORT", environment_value)
209
- command.parse(args)
210
- end
211
-
212
- context "when no environment variable is present" do
213
-
214
- it "uses the default" do
215
- expect(command.port).to eq 4321
216
- end
217
-
218
- end
219
-
220
- context "when environment variable is present" do
221
-
222
- let(:environment_value) { "12345" }
223
-
224
- it "uses the environment variable" do
225
- expect(command.port).to eq 12_345
226
- end
227
-
228
- context "when a value is specified on the command-line" do
229
-
230
- let(:args) { %w[--port 1500] }
231
-
232
- it "uses command-line value" do
233
- expect(command.port).to eq 1500
234
- end
235
-
236
- end
237
-
238
- end
239
-
240
- describe "#help" do
241
-
242
- it "describes the default value and env usage" do
243
- expect(command.help).to include("port to listen on (default: $PORT, or 4321)")
244
- end
245
-
246
- end
247
-
248
- end
249
-
250
- context "with :environment_variable and type :flag" do
251
-
252
- let(:environment_value) { nil }
253
-
254
- before do
255
- command.class.option "--[no-]enable", :flag, "enable?", default: false, environment_variable: "ENABLE"
256
- set_env("ENABLE", environment_value)
257
- command.parse([])
258
- end
259
-
260
- context "when no environment variable is present" do
261
-
262
- it "uses the default" do
263
- expect(command.enable?).to be false
264
- end
265
-
266
- end
267
-
268
- %w[1 yes enable on true].each do |truthy_value|
269
-
270
- context "when environment variable is #{truthy_value.inspect}" do
271
-
272
- let(:environment_value) { truthy_value }
273
-
274
- it "sets the flag" do
275
- expect(command.enable?).to be true
276
- end
277
-
278
- end
279
-
280
- end
281
-
282
- %w[0 no disable off false].each do |falsey_value|
283
-
284
- context "when environment variable is #{falsey_value.inspect}" do
285
-
286
- let(:environment_value) { falsey_value }
287
-
288
- it "clears the flag" do
289
- expect(command.enable?).to be false
290
- end
291
-
292
- end
293
-
294
- end
295
-
296
- end
297
-
298
- context "with :required" do
299
-
300
- before do
301
- command.class.option "--port", "PORT", "port to listen on", required: true
302
- end
303
-
304
- describe "#help" do
305
-
306
- it "marks it as required" do
307
- expect(command.help).to include("port to listen on (required)")
308
- end
309
-
310
- end
311
-
312
- context "when no value is provided" do
313
-
314
- it "raises a UsageError" do
315
- expect do
316
- command.parse([])
317
- end.to raise_error(Clamp::UsageError)
318
- end
319
-
320
- end
321
-
322
- context "when a value is provided" do
323
-
324
- it "does not raise an error" do
325
- expect do
326
- command.parse(["--port", "12345"])
327
- end.not_to raise_error
328
- end
329
-
330
- end
331
-
332
- end
333
-
334
- context "with :required and :multivalued" do
335
-
336
- before do
337
- command.class.option "--port", "PORT", "port to listen on", required: true, multivalued: true
338
- end
339
-
340
- context "when no value is provided" do
341
-
342
- it "raises a UsageError" do
343
- expect do
344
- command.parse([])
345
- end.to raise_error(Clamp::UsageError)
346
- end
347
-
348
- end
349
-
350
- context "when a value is provided" do
351
-
352
- it "does not raise an error" do
353
- expect do
354
- command.parse(["--port", "12345"])
355
- end.not_to raise_error
356
- end
357
-
358
- end
359
-
360
- end
361
-
362
- context "with a block" do
363
-
364
- before do
365
- command.class.option "--port", "PORT", "Port to listen on" do |port|
366
- Integer(port)
367
- end
368
- end
369
-
370
- context "when value is incorrect" do
371
-
372
- it "raises an error" do
373
- expect do
374
- command.port = "blah"
375
- end.to raise_error(ArgumentError)
376
- end
377
-
378
- end
379
-
380
- context "when value is convertible" do
381
-
382
- it "uses the block to validate and convert the option argument" do
383
- command.port = "1234"
384
- expect(command.port).to eq 1234
385
- end
386
-
387
- end
388
-
389
- end
390
-
391
- end
392
-
393
- context "with options declared" do
394
-
395
- before do
396
- command.class.option ["-f", "--flavour"], "FLAVOUR", "Flavour of the month"
397
- command.class.option ["-c", "--color"], "COLOR", "Preferred hue"
398
- command.class.option ["--scoops"], "N", "Number of scoops",
399
- default: 1,
400
- environment_variable: "DEFAULT_SCOOPS" do |arg|
401
- Integer(arg)
402
- end
403
- command.class.option ["-n", "--[no-]nuts"], :flag, "Nuts (or not)\nMay include nuts"
404
- command.class.parameter "[ARG] ...", "extra arguments", attribute_name: :arguments
405
- end
406
-
407
- describe "#parse" do
408
-
409
- context "with an unrecognised option" do
410
-
411
- it "raises a UsageError" do
412
- expect do
413
- command.parse(%w[--foo bar])
414
- end.to raise_error(Clamp::UsageError)
415
- end
416
-
417
- end
418
-
419
- context "with options" do
420
-
421
- before do
422
- command.parse(%w[--flavour strawberry --nuts --color blue])
423
- end
424
-
425
- describe "flavour" do
426
- it "maps the option value onto the command object" do
427
- expect(command.flavour).to eq "strawberry"
428
- end
429
- end
430
-
431
- describe "color" do
432
- it "maps the option value onto the command object" do
433
- expect(command.color).to eq "blue"
434
- end
435
- end
436
-
437
- describe "nuts?" do
438
- it "maps the option value onto the command object" do
439
- expect(command.nuts?).to be true
440
- end
441
- end
442
-
443
- end
444
-
445
- context "with short options" do
446
-
447
- before do
448
- command.parse(%w[-f strawberry -c blue])
449
- end
450
-
451
- describe "flavour" do
452
- it "recognizes short options as aliases" do
453
- expect(command.flavour).to eq "strawberry"
454
- end
455
- end
456
-
457
- describe "color" do
458
- it "recognizes short options as aliases" do
459
- expect(command.color).to eq "blue"
460
- end
461
- end
462
-
463
- end
464
-
465
- context "with a value appended to a short option" do
466
-
467
- before do
468
- command.parse(%w[-fstrawberry])
469
- end
470
-
471
- it "works as though the value were separated" do
472
- expect(command.flavour).to eq "strawberry"
473
- end
474
-
475
- end
476
-
477
- context "with combined short options" do
478
-
479
- before do
480
- command.parse(%w[-nf strawberry])
481
- end
482
-
483
- describe "flavour" do
484
- it "works as though the options were separate" do
485
- expect(command.flavour).to eq "strawberry"
486
- end
487
- end
488
-
489
- describe "nuts?" do
490
- it "works as though the options were separate" do
491
- expect(command.nuts?).to be true
492
- end
493
- end
494
-
495
- end
496
-
497
- context "with option arguments attached using equals sign" do
498
-
499
- before do
500
- command.parse(%w[--flavour=strawberry --color=blue])
501
- end
502
-
503
- describe "flavour" do
504
- it "maps the option value onto the command object" do
505
- expect(command.flavour).to eq "strawberry"
506
- end
507
- end
508
-
509
- describe "color" do
510
- it "maps the option value onto the command object" do
511
- expect(command.color).to eq "blue"
512
- end
513
- end
514
-
515
- end
516
-
517
- context "with option arguments that look like options" do
518
-
519
- before do
520
- command.parse(%w[--flavour=-dashing- --scoops -1])
521
- end
522
-
523
- describe "flavour" do
524
- it "sets the options" do
525
- expect(command.flavour).to eq("-dashing-")
526
- end
527
- end
528
-
529
- describe "scoops" do
530
- it "sets the options" do
531
- expect(command.scoops).to eq(-1)
532
- end
533
- end
534
-
535
- end
536
-
537
- context "with option-like things beyond the arguments" do
538
-
539
- it "treats them as positional arguments" do
540
- command.parse(%w[a b c --flavour strawberry])
541
- expect(command.arguments).to eq %w[a b c --flavour strawberry]
542
- end
543
-
544
- end
545
-
546
- context "with multi-line arguments that look like options" do
547
-
548
- before do
549
- command.parse(["foo\n--flavour=strawberry", "bar\n-cblue"])
550
- end
551
-
552
- describe "arguments" do
553
- it "treats them as positional arguments" do
554
- expect(command.arguments).to eq ["foo\n--flavour=strawberry", "bar\n-cblue"]
555
- end
556
- end
557
-
558
- describe "flavour" do
559
- it "treats them as positional arguments" do
560
- expect(command.flavour).to be_nil
561
- end
562
- end
563
-
564
- describe "color" do
565
- it "treats them as positional arguments" do
566
- expect(command.color).to be_nil
567
- end
568
- end
569
-
570
- end
571
-
572
- context "with an option terminator" do
573
-
574
- it "considers everything after the terminator to be an argument" do
575
- command.parse(%w[--color blue -- --flavour strawberry])
576
- expect(command.arguments).to eq %w[--flavour strawberry]
577
- end
578
-
579
- end
580
-
581
- context "with --flag" do
582
-
583
- before do
584
- command.parse(%w[--nuts])
585
- end
586
-
587
- it "sets the flag" do
588
- expect(command.nuts?).to be true
589
- end
590
-
591
- end
592
-
593
- context "with --no-flag" do
594
-
595
- before do
596
- command.nuts = true
597
- command.parse(%w[--no-nuts])
598
- end
599
-
600
- it "clears the flag" do
601
- expect(command.nuts?).to be false
602
- end
603
-
604
- end
605
-
606
- context "with --help" do
607
-
608
- it "requests help" do
609
- expect do
610
- command.parse(%w[--help])
611
- end.to raise_error(Clamp::HelpWanted)
612
- end
613
-
614
- end
615
-
616
- context "with -h" do
617
-
618
- it "requests help" do
619
- expect do
620
- command.parse(%w[-h])
621
- end.to raise_error(Clamp::HelpWanted)
622
- end
623
-
624
- end
625
-
626
- context "when no option value is provided" do
627
-
628
- it "signals a UsageError" do
629
- expect do
630
- command.parse(%w[--flavour])
631
- end.to raise_error(Clamp::UsageError, /^option '--flavour': no value provided/)
632
- end
633
-
634
- end
635
-
636
- context "when a bad option value is specified on the command-line" do
637
-
638
- it "signals a UsageError" do
639
- expect do
640
- command.parse(%w[--scoops reginald])
641
- end.to raise_error(Clamp::UsageError, /^option '--scoops': invalid value for Integer/)
642
- end
643
-
644
- end
645
-
646
- context "when a bad option value is specified in the environment" do
647
-
648
- it "signals a UsageError" do
649
- ENV["DEFAULT_SCOOPS"] = "marjorie"
650
- expect do
651
- command.parse([])
652
- end.to raise_error(Clamp::UsageError, /^\$DEFAULT_SCOOPS: invalid value for Integer/)
653
- end
654
-
655
- end
656
-
657
- end
658
-
659
- describe "#help" do
660
-
661
- it "indicates that there are options" do
662
- expect(command.help).to include("cmd [OPTIONS]")
663
- end
664
-
665
- it "includes option details" do
666
- flavour_help = "-f, --flavour FLAVOUR +Flavour of the month"
667
- color_help = "-c, --color COLOR +Preferred hue"
668
-
669
- expect(command.help).to match(/#{flavour_help}\n +#{color_help}/)
670
- end
671
-
672
- it "handles new lines in option descriptions" do
673
- expect(command.help).to match(/--\[no-\]nuts +Nuts \(or not\)\n +May include nuts/)
674
- end
675
-
676
- end
677
-
678
- end
679
-
680
- context "with an explicit --help option declared" do
681
-
682
- before do
683
- command.class.option ["--help"], :flag, "help wanted"
684
- end
685
-
686
- describe "parsing" do
687
- it "does not generate implicit help option" do
688
- expect do
689
- command.parse(%w[--help])
690
- end.not_to raise_error
691
- end
692
- end
693
-
694
- describe "help?" do
695
- before do
696
- command.parse(%w[--help])
697
- end
698
-
699
- it "generates custom help option" do
700
- expect(command.help?).to be true
701
- end
702
- end
703
-
704
- it "does not recognise -h" do
705
- expect do
706
- command.parse(%w[-h])
707
- end.to raise_error(Clamp::UsageError)
708
- end
709
-
710
- end
711
-
712
- context "with an explicit -h option declared" do
713
-
714
- before do
715
- command.class.option ["-h", "--humidity"], "PERCENT", "relative humidity" do |n|
716
- Integer(n)
717
- end
718
- end
719
-
720
- it "does not map -h to help" do
721
- expect(command.help).not_to match(/-h[, ].*help/)
722
- end
723
-
724
- it "still recognizes --help" do
725
- expect do
726
- command.parse(%w[--help])
727
- end.to raise_error(Clamp::HelpWanted)
728
- end
729
-
730
- end
731
-
732
- describe ".parameter" do
733
-
734
- context "when regular" do
735
-
736
- before do
737
- command.class.parameter "FLAVOUR", "flavour of the month"
738
- end
739
-
740
- context "when not set" do
741
- it "equals nil" do
742
- expect(command.flavour).to be_nil
743
- end
744
- end
745
-
746
- context "when set" do
747
- before do
748
- command.flavour = "chocolate"
749
- end
750
-
751
- it "equals the value" do
752
- expect(command.flavour).to eq "chocolate"
753
- end
754
- end
755
-
756
- end
757
-
758
- context "with explicit :attribute_name" do
759
-
760
- before do
761
- command.class.parameter "FOO", "a foo", attribute_name: :bar
762
- end
763
-
764
- it "uses the specified attribute_name name to name accessors" do
765
- command.bar = "chocolate"
766
- expect(command.bar).to eq "chocolate"
767
- end
768
-
769
- end
770
-
771
- context "with :default value" do
772
-
773
- before do
774
- command.class.parameter "[ORIENTATION]", "direction", default: "west"
775
- end
776
-
777
- it "sets the specified default value" do
778
- expect(command.orientation).to eq "west"
779
- end
780
-
781
- describe "#help" do
782
-
783
- it "describes the default value" do
784
- expect(command.help).to include("direction (default: \"west\")")
785
- end
786
-
787
- end
788
-
789
- end
790
-
791
- context "with a block" do
792
-
793
- before do
794
- command.class.parameter "PORT", "port to listen on" do |port|
795
- Integer(port)
796
- end
797
- end
798
-
799
- context "when value is incorrect" do
800
-
801
- it "raises an error" do
802
- expect do
803
- command.port = "blah"
804
- end.to raise_error(ArgumentError)
805
- end
806
-
807
- end
808
-
809
- context "when value is convertible" do
810
-
811
- it "uses the block to validate and convert the argument" do
812
- command.port = "1234"
813
- expect(command.port).to eq 1234
814
- end
815
-
816
- end
817
-
818
- end
819
-
820
- context "with ellipsis" do
821
-
822
- before do
823
- command.class.parameter "FILE ...", "files"
824
- end
825
-
826
- it "accepts multiple arguments" do
827
- command.parse(%w[X Y Z])
828
- expect(command.file_list).to eq %w[X Y Z]
829
- end
830
-
831
- end
832
-
833
- context "when optional, with ellipsis" do
834
-
835
- before do
836
- command.class.parameter "[FILE] ...", "files"
837
-
838
- command.parse([])
839
- end
840
-
841
- describe "default_file_list" do
842
-
843
- it "defaults to an empty list" do
844
- expect(command.default_file_list).to be_empty
845
- end
846
-
847
- end
848
-
849
- describe "file_list" do
850
-
851
- it "defaults to an empty list" do
852
- expect(command.file_list).to be_empty
853
- end
854
-
855
- end
856
-
857
- describe "mutation" do
858
-
859
- before do
860
- command.file_list << "treasure"
861
- end
862
-
863
- it "is mutable" do
864
- expect(command.file_list).to eq ["treasure"]
865
- end
866
-
867
- end
868
-
869
- end
870
-
871
- context "with :environment_variable" do
872
-
873
- before do
874
- command.class.parameter "[FILE]", "a file", environment_variable: "FILE",
875
- default: "/dev/null"
876
-
877
- set_env("FILE", environment_value)
878
- command.parse(args)
879
- end
880
-
881
- let(:args) { [] }
882
- let(:environment_value) { nil }
883
-
884
- context "when neither argument nor environment variable are present" do
885
-
886
- it "uses the default" do
887
- expect(command.file).to eq File::NULL
888
- end
889
-
890
- end
891
-
892
- context "when environment variable is present" do
893
-
894
- let(:environment_value) { "/etc/motd" }
895
-
896
- describe "and no argument is provided" do
897
-
898
- it "uses the environment variable" do
899
- expect(command.file).to eq "/etc/motd"
900
- end
901
-
902
- end
903
-
904
- describe "and an argument is provided" do
905
-
906
- let(:args) { ["/dev/null"] }
907
-
908
- it "uses the argument" do
909
- expect(command.file).to eq File::NULL
910
- end
911
-
912
- end
913
-
914
- end
915
-
916
- describe "#help" do
917
-
918
- it "describes the default value and env usage" do
919
- expect(command.help).to include(%{ (default: $FILE, or "/dev/null")})
920
- end
921
-
922
- end
923
-
924
- end
925
-
926
- end
927
-
928
- context "with no parameters declared" do
929
-
930
- describe "#parse" do
931
-
932
- context "with arguments" do
933
-
934
- it "raises a UsageError" do
935
- expect do
936
- command.parse(["crash"])
937
- end.to raise_error(Clamp::UsageError, "too many arguments")
938
- end
939
-
940
- end
941
-
942
- end
943
-
944
- end
945
-
946
- context "with parameters declared" do
947
-
948
- before do
949
- command.class.parameter "X", "x\nxx"
950
- command.class.parameter "Y", "y"
951
- command.class.parameter "[Z]", "z", default: "ZZZ"
952
- end
953
-
954
- describe "#parse" do
955
-
956
- context "with arguments for all parameters" do
957
-
958
- before do
959
- command.parse(["crash", "bang", "wallop"])
960
- end
961
-
962
- describe "x" do
963
- it "maps arguments onto the command object" do
964
- expect(command.x).to eq "crash"
965
- end
966
- end
967
-
968
- describe "y" do
969
- it "maps arguments onto the command object" do
970
- expect(command.y).to eq "bang"
971
- end
972
- end
973
-
974
- describe "z" do
975
- it "maps arguments onto the command object" do
976
- expect(command.z).to eq "wallop"
977
- end
978
- end
979
-
980
- end
981
-
982
- context "with insufficient arguments" do
983
-
984
- it "raises a UsageError" do
985
- expect do
986
- command.parse(["crash"])
987
- end.to raise_error(Clamp::UsageError, "parameter 'Y': no value provided")
988
- end
989
-
990
- end
991
-
992
- context "with optional argument omitted" do
993
-
994
- before do
995
- command.parse(["crash", "bang"])
996
- end
997
-
998
- describe "x" do
999
- it "defaults the optional argument" do
1000
- expect(command.x).to eq "crash"
1001
- end
1002
- end
1003
-
1004
- describe "y" do
1005
- it "defaults the optional argument" do
1006
- expect(command.y).to eq "bang"
1007
- end
1008
- end
1009
-
1010
- describe "z" do
1011
- it "defaults the optional argument" do
1012
- expect(command.z).to eq "ZZZ"
1013
- end
1014
- end
1015
-
1016
- end
1017
-
1018
- context "with multi-line arguments" do
1019
-
1020
- before do
1021
- command.parse(["foo\nhi", "bar", "baz"])
1022
- end
1023
-
1024
- describe "x" do
1025
- it "parses them correctly" do
1026
- expect(command.x).to eq "foo\nhi"
1027
- end
1028
- end
1029
-
1030
- describe "y" do
1031
- it "parses them correctly" do
1032
- expect(command.y).to eq "bar"
1033
- end
1034
- end
1035
-
1036
- describe "z" do
1037
- it "parses them correctly" do
1038
- expect(command.z).to eq "baz"
1039
- end
1040
- end
1041
-
1042
- end
1043
-
1044
- context "with too many arguments" do
1045
-
1046
- it "raises a UsageError" do
1047
- expect do
1048
- command.parse(["crash", "bang", "wallop", "kapow"])
1049
- end.to raise_error(Clamp::UsageError, "too many arguments")
1050
- end
1051
-
1052
- end
1053
-
1054
- end
1055
-
1056
- describe "#help" do
1057
-
1058
- it "indicates that there are parameters" do
1059
- expect(command.help).to include("cmd [OPTIONS] X Y [Z]")
1060
- end
1061
-
1062
- it "includes parameter details" do
1063
- expect(command.help).to match(/X +x\n[\s\S]* +Y +y\n[\s\S]* +\[Z\] +z \(default: "ZZZ"\)/)
1064
- end
1065
-
1066
- it "handles new lines in option descriptions" do
1067
- expect(command.help).to match(/X +x\n +xx/)
1068
- end
1069
-
1070
- end
1071
-
1072
- end
1073
-
1074
- describe ".execute" do
1075
-
1076
- before do
1077
-
1078
- command_class.class_eval do
1079
- execute do
1080
- puts "using execute DSL"
1081
- end
1082
- end
1083
-
1084
- command.run([])
1085
-
1086
- end
1087
-
1088
- it "provides an alternative way to declare execute method" do
1089
- expect(stdout).to eq("using execute DSL\n")
1090
- end
1091
-
1092
- end
1093
-
1094
- context "with explicit usage" do
1095
-
1096
- given_command("blah") do
1097
-
1098
- usage "FOO BAR ..."
1099
-
1100
- end
1101
-
1102
- describe "#help" do
1103
-
1104
- it "includes the explicit usage" do
1105
- expect(command.help).to include("blah FOO BAR ...\n")
1106
- end
1107
-
1108
- end
1109
-
1110
- end
1111
-
1112
- context "with multiple usages" do
1113
-
1114
- given_command("put") do
1115
-
1116
- usage "THIS HERE"
1117
- usage "THAT THERE"
1118
-
1119
- end
1120
-
1121
- describe "#help" do
1122
-
1123
- it "includes both potential usages" do
1124
- expect(command.help).to match(/put THIS HERE\n +put THAT THERE/)
1125
- end
1126
-
1127
- end
1128
-
1129
- end
1130
-
1131
- context "with a banner" do
1132
-
1133
- given_command("punt") do
1134
-
1135
- banner <<-TEXT
1136
- Punt is an example command. It doesn't do much, really.
1137
-
1138
- The prefix at the beginning of this description should be normalised
1139
- to two spaces.
1140
- TEXT
1141
-
1142
- end
1143
-
1144
- describe "#help" do
1145
-
1146
- it "includes the banner" do
1147
- expect(command.help).to match(/^ Punt is an example command.+\n^ \n^ The prefix/)
1148
- end
1149
-
1150
- end
1151
-
1152
- end
1153
-
1154
- describe ".run" do
1155
-
1156
- context "when regular" do
1157
-
1158
- before do
1159
-
1160
- command.class.class_eval do
1161
- parameter "WORD ...", "words"
1162
- def execute
1163
- print word_list.inspect
1164
- end
1165
- end
1166
-
1167
- end
1168
-
1169
- it "creates a new Command instance and runs it" do
1170
- xyz = %w[x y z]
1171
- command.class.run("cmd", xyz)
1172
- expect(stdout).to eq xyz.inspect
1173
- end
1174
-
1175
- end
1176
-
1177
- context "when invoked with a context hash" do
1178
-
1179
- before do
1180
-
1181
- command.class.class_eval do
1182
- def execute
1183
- print context[:foo]
1184
- end
1185
- end
1186
-
1187
- end
1188
-
1189
- it "makes the context available within the command" do
1190
- command.class.run("xyz", [], foo: "bar")
1191
- expect(stdout).to eq "bar"
1192
- end
1193
-
1194
- end
1195
-
1196
- context "when there's a CommandError" do
1197
-
1198
- before do
1199
-
1200
- command.class.class_eval do
1201
- def execute
1202
- signal_error "Oh crap!", status: 456
1203
- end
1204
- end
1205
-
1206
- end
1207
-
1208
- it "outputs the error message and exits with the specified status" do # rubocop:disable RSpec/ExampleLength
1209
- expect { command.class.run("cmd", []) }
1210
- .to raise_error(
1211
- an_instance_of(SystemExit).and(having_attributes(status: 456))
1212
- ).and output(<<~TEXT).to_stderr
1213
- ERROR: Oh crap!
1214
- TEXT
1215
- end
1216
-
1217
- end
1218
-
1219
- context "when there's a UsageError" do
1220
-
1221
- before do
1222
-
1223
- command.class.class_eval do
1224
- def execute
1225
- signal_usage_error "bad dog!"
1226
- end
1227
- end
1228
-
1229
- begin
1230
- command.class.run("cmd", [])
1231
- rescue SystemExit => e
1232
- @system_exit = e
1233
- end
1234
-
1235
- end
1236
-
1237
- it "outputs the error message and help" do # rubocop:disable RSpec/ExampleLength
1238
- expect { command.class.run("cmd", []) }
1239
- .to raise_error(
1240
- an_instance_of(SystemExit).and(having_attributes(status: 1))
1241
- ).and output(<<~TEXT).to_stderr
1242
- ERROR: bad dog!
1243
-
1244
- See: 'cmd --help'
1245
- TEXT
1246
- end
1247
-
1248
- end
1249
-
1250
- context "when help is requested" do
1251
-
1252
- it "outputs help" do
1253
- command.class.run("cmd", ["--help"])
1254
- expect(stdout).to include "Usage:"
1255
- end
1256
-
1257
- end
1258
-
1259
- end
1260
-
1261
- describe "subclass" do
1262
-
1263
- let(:command) do
1264
- parent_command_class = Class.new(Clamp::Command) do
1265
- option "--verbose", :flag, "be louder"
1266
- end
1267
- derived_command_class = Class.new(parent_command_class) do
1268
- option "--iterations", "N", "number of times to go around"
1269
- end
1270
- derived_command_class.new("cmd")
1271
- end
1272
-
1273
- it "inherits options from it's superclass" do
1274
- command.parse(["--verbose"])
1275
- expect(command).to be_verbose
1276
- end
1277
-
1278
- end
1279
-
1280
- end