command_kit 0.2.0 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
data/spec/command_spec.rb CHANGED
@@ -26,14 +26,14 @@ describe CommandKit::Command do
26
26
  expect(described_class).to include(CommandKit::Usage)
27
27
  end
28
28
 
29
- it "must include CommandKit::Arguments" do
30
- expect(described_class).to include(CommandKit::Arguments)
31
- end
32
-
33
29
  it "must include CommandKit::Options" do
34
30
  expect(described_class).to include(CommandKit::Options)
35
31
  end
36
32
 
33
+ it "must include CommandKit::Arguments" do
34
+ expect(described_class).to include(CommandKit::Arguments)
35
+ end
36
+
37
37
  it "must include CommandKit::Examples" do
38
38
  expect(described_class).to include(CommandKit::Examples)
39
39
  end
@@ -49,4 +49,75 @@ describe CommandKit::Command do
49
49
  it "must include FileUtils" do
50
50
  expect(described_class).to include(FileUtils)
51
51
  end
52
+
53
+ module TestCommandClass
54
+ class TestCommand < CommandKit::Command
55
+
56
+ usage '[OPTIONS] ARG1 [ARG2]'
57
+
58
+ option :option1, short: '-a',
59
+ value: {
60
+ type: Integer,
61
+ default: 1
62
+ },
63
+ desc: "Option 1"
64
+
65
+ option :option2, short: '-b',
66
+ value: {
67
+ type: String,
68
+ usage: 'FILE'
69
+ },
70
+ desc: "Option 2"
71
+
72
+ argument :argument1, required: true,
73
+ usage: 'ARG1',
74
+ desc: "Argument 1"
75
+
76
+ argument :argument2, required: false,
77
+ usage: 'ARG2',
78
+ desc: "Argument 2"
79
+
80
+ examples [
81
+ '-a 42 foo/bar/baz',
82
+ '-a 42 -b bar.txt baz qux'
83
+ ]
84
+
85
+ description 'Example command'
86
+
87
+ end
88
+ end
89
+
90
+ let(:command_class) { TestCommandClass::TestCommand }
91
+ subject { command_class.new }
92
+
93
+ describe "#help" do
94
+ let(:option1) { command_class.options[:option1] }
95
+ let(:option2) { command_class.options[:option2] }
96
+ let(:argument1) { command_class.arguments[:argument1] }
97
+ let(:argument2) { command_class.arguments[:argument2] }
98
+
99
+ it "must print the usage, options, arguments, examples, and description" do
100
+ expect { subject.help }.to output(
101
+ [
102
+ "Usage: #{subject.usage}",
103
+ '',
104
+ 'Options:',
105
+ " #{option1.usage.join(', ').ljust(33 - 1)} #{option1.desc}",
106
+ " #{option2.usage.join(', ').ljust(33 - 1)} #{option2.desc}",
107
+ ' -h, --help Print help information',
108
+ '',
109
+ "Arguments:",
110
+ " #{argument1.usage.ljust(33)}#{argument1.desc}",
111
+ " #{argument2.usage.ljust(33)}#{argument2.desc}",
112
+ '',
113
+ "Examples:",
114
+ " #{subject.command_name} #{command_class.examples[0]}",
115
+ " #{subject.command_name} #{command_class.examples[1]}",
116
+ '',
117
+ command_class.description,
118
+ ''
119
+ ].join($/)
120
+ ).to_stdout
121
+ end
122
+ end
52
123
  end
@@ -54,16 +54,27 @@ describe CommandKit::Commands::AutoLoad do
54
54
  end
55
55
 
56
56
  module TestAutoLoad
57
- class CLI
57
+ class TestCommand
58
58
  include CommandKit::Commands
59
59
  include CommandKit::Commands::AutoLoad.new(
60
60
  dir: File.expand_path('../fixtures/test_auto_load/cli/commands',__FILE__),
61
61
  namespace: "#{self}::Commands"
62
62
  )
63
63
  end
64
+
65
+ class TestCommandWithBlock
66
+ include CommandKit::Commands
67
+ include(CommandKit::Commands::AutoLoad.new(
68
+ dir: File.expand_path('../fixtures/test_auto_load/cli/commands',__FILE__),
69
+ namespace: "#{self}::Commands"
70
+ ) { |autoload|
71
+ autoload.command 'test-1', 'Test1', 'test1.rb', aliases: %w[test_1]
72
+ autoload.command 'test-2', 'Test2', 'test2.rb', aliases: %w[test_2]
73
+ })
74
+ end
64
75
  end
65
76
 
66
- let(:command_class) { TestAutoLoad::CLI }
77
+ let(:command_class) { TestAutoLoad::TestCommand }
67
78
  let(:autoload_module) { command_class.included_modules.first }
68
79
 
69
80
  describe "#included" do
@@ -76,6 +87,26 @@ describe CommandKit::Commands::AutoLoad do
76
87
  it "must merge the module's #commands into the class'es #commands" do
77
88
  expect(command_class.commands).to include(autoload_module.commands)
78
89
  end
90
+
91
+ context "when CommandKit::Commands::AutoLoad has an explicit mapping" do
92
+ let(:command_class) { TestAutoLoad::TestCommandWithBlock }
93
+
94
+ it "must merge the module's #commands into the class'es #commands" do
95
+ expect(command_class.commands).to include(autoload_module.commands)
96
+ end
97
+
98
+ it "must also merge the any aliases into the class'es #command_aliases" do
99
+ expected_command_aliases = {}
100
+
101
+ autoload_module.commands.each do |name,subcommand|
102
+ subcommand.aliases.each do |alias_name|
103
+ expected_command_aliases[alias_name] = name
104
+ end
105
+ end
106
+
107
+ expect(command_class.command_aliases).to include(expected_command_aliases)
108
+ end
109
+ end
79
110
  end
80
111
 
81
112
  describe "#join" do
@@ -126,6 +126,33 @@ describe CommandKit::Commands do
126
126
  command Test2
127
127
 
128
128
  end
129
+
130
+ class TestCommandsWithCommandArguments
131
+
132
+ include CommandKit::Commands
133
+
134
+ class Test1 < CommandKit::Command
135
+ argument :arg1, required: false,
136
+ desc: 'Argument 1'
137
+
138
+ argument :arg2, required: false,
139
+ desc: 'Argument 2'
140
+ end
141
+
142
+ class Test2 < CommandKit::Command
143
+ argument :arg1, required: false,
144
+ desc: 'Argument 1'
145
+
146
+ argument :arg2, required: false,
147
+ desc: 'Argument 2'
148
+ end
149
+
150
+ p method(:command).source_location
151
+ command Test1
152
+ command Test2
153
+
154
+ end
155
+
129
156
  end
130
157
 
131
158
  let(:command_class) { TestCommands::TestCommands }
@@ -569,7 +596,7 @@ describe CommandKit::Commands do
569
596
  expect(subject).to receive(:exit).with(1)
570
597
 
571
598
  expect { subject.command_not_found(unknown_command) }.to output(
572
- "'#{unknown_command}' is not a #{subject.command_name} command. See `#{subject.command_name} help`" + $/
599
+ "#{subject.command_name}: '#{unknown_command}' is not a #{subject.command_name} command. See `#{subject.command_name} help`" + $/
573
600
  ).to_stderr
574
601
  end
575
602
  end
@@ -582,6 +609,44 @@ describe CommandKit::Commands do
582
609
  end
583
610
  end
584
611
 
612
+ describe "#option_parser" do
613
+ let(:command_name) { 'test1' }
614
+ let(:command_argv) { %w[foo bar baz] }
615
+ let(:argv) { [command_name, *command_argv] }
616
+
617
+ it "must stop before the first non-option argument" do
618
+ expect(subject.option_parser.parse(argv)).to eq(
619
+ [command_name, *command_argv]
620
+ )
621
+ end
622
+
623
+ context "when an unknown command name is given" do
624
+ let(:command_argv) { %w[bar baz] }
625
+ let(:argv) { ['foo', command_name, *command_argv] }
626
+
627
+ it "must stop before the first non-option argument" do
628
+ expect(subject.option_parser.parse(argv)).to eq(argv)
629
+ end
630
+ end
631
+
632
+ context "when additional global options are defined" do
633
+ let(:command_class) { TestCommands::TestCommandsWithGlobalOptions }
634
+ let(:bar) { '2' }
635
+ let(:argv) do
636
+ ['--foo', '--bar', bar.to_s, command_name, *command_argv]
637
+ end
638
+
639
+ it "must parse the global options, but stop before the first non-option associated argument" do
640
+ expect(subject.option_parser.parse(argv)).to eq(
641
+ [command_name, *command_argv]
642
+ )
643
+
644
+ expect(subject.options[:foo]).to be(true)
645
+ expect(subject.options[:bar]).to eq(bar)
646
+ end
647
+ end
648
+ end
649
+
585
650
  describe "#run" do
586
651
  context "when a command name is the first argument" do
587
652
  let(:command) { 'test1' }
@@ -607,48 +672,45 @@ describe CommandKit::Commands do
607
672
  end
608
673
 
609
674
  context "when given no arguments" do
610
- it "must default to calling #help" do
675
+ let(:exit_status) { 1 }
676
+
677
+ it "must default to calling #help and exit with 1" do
611
678
  expect(subject).to receive(:help)
679
+ expect(subject).to receive(:exit).with(exit_status)
612
680
 
613
681
  subject.run()
614
682
  end
615
683
  end
616
684
  end
617
685
 
618
- describe "#option_parser" do
619
- let(:command_name) { 'test1' }
620
- let(:command_argv) { %w[foo bar baz] }
621
- let(:argv) { [command_name, *command_argv] }
686
+ describe "#main" do
687
+ let(:command_class) { TestCommands::TestCommandsWithCommandArguments }
622
688
 
623
- it "must stop before the first non-option argument" do
624
- expect(subject.option_parser.parse(argv)).to eq(
625
- [command_name, *command_argv]
626
- )
689
+ context "when a command name is the first argument" do
690
+ let(:command) { 'test1' }
691
+ let(:argv) { [command] }
692
+
693
+ it "must return 0" do
694
+ expect(subject.main(argv)).to eq(0)
695
+ end
627
696
  end
628
697
 
629
- context "when an unknown command name is given" do
630
- let(:command_argv) { %w[bar baz] }
631
- let(:argv) { ['foo', command_name, *command_argv] }
698
+ context "when given additional command arguments" do
699
+ let(:argv) { %w[test1 arg1 arg2] }
632
700
 
633
- it "must stop before the first non-option argument" do
634
- expect(subject.option_parser.parse(argv)).to eq(argv)
701
+ it "must pass them to the command" do
702
+ expect(subject.main(argv)).to eq(0)
635
703
  end
636
704
  end
637
705
 
638
- context "when additional global options are defined" do
639
- let(:command_class) { TestCommands::TestCommandsWithGlobalOptions }
640
- let(:bar) { '2' }
641
- let(:argv) do
642
- ['--foo', '--bar', bar.to_s, command_name, *command_argv]
643
- end
706
+ context "when given no arguments" do
707
+ let(:exit_status) { 1 }
708
+ let(:argv) { %w[] }
644
709
 
645
- it "must parse the global options, but stop before the first non-option associated argument" do
646
- expect(subject.option_parser.parse(argv)).to eq(
647
- [command_name, *command_argv]
648
- )
710
+ it "must call #help and return 1" do
711
+ expect(subject).to receive(:help)
649
712
 
650
- expect(subject.options[:foo]).to be(true)
651
- expect(subject.options[:bar]).to eq(bar)
713
+ expect(subject.main(argv)).to eq(exit_status)
652
714
  end
653
715
  end
654
716
  end
@@ -711,6 +773,10 @@ describe CommandKit::Commands do
711
773
  "Options:",
712
774
  " -h, --help Print help information",
713
775
  "",
776
+ "Arguments:",
777
+ " [COMMAND] The command name to run",
778
+ " [ARGS ...] Additional arguments for the command",
779
+ "",
714
780
  "Commands:",
715
781
  " help",
716
782
  " test1",
@@ -731,6 +797,10 @@ describe CommandKit::Commands do
731
797
  "Options:",
732
798
  " -h, --help Print help information",
733
799
  "",
800
+ "Arguments:",
801
+ " [COMMAND] The command name to run",
802
+ " [ARGS ...] Additional arguments for the command",
803
+ "",
734
804
  "Commands:",
735
805
  " help",
736
806
  " test1, t1",
@@ -754,6 +824,10 @@ describe CommandKit::Commands do
754
824
  " -b, --bar BAR Global --bar option",
755
825
  " -h, --help Print help information",
756
826
  "",
827
+ "Arguments:",
828
+ " [COMMAND] The command name to run",
829
+ " [ARGS ...] Additional arguments for the command",
830
+ "",
757
831
  "Commands:",
758
832
  " help",
759
833
  " test1",
@@ -13,6 +13,12 @@ describe CommandKit::Env::Path do
13
13
  let(:command_class) { TestEnvPath::TestCommand }
14
14
  subject { command_class.new }
15
15
 
16
+ describe ".included" do
17
+ it "must include CommandKit::Env" do
18
+ expect(command_class).to be_include(CommandKit::Env)
19
+ end
20
+ end
21
+
16
22
  describe "#initialize" do
17
23
  it "must split ENV['PATH'] into an Array of directories" do
18
24
  expect(subject.path_dirs).to eq(ENV['PATH'].split(File::PATH_SEPARATOR))
@@ -16,7 +16,13 @@ describe CommandKit::Help::Man do
16
16
  man_page 'foo.1'
17
17
  end
18
18
 
19
- class EmptyCommand
19
+ class TestCommandWithRelativeManDir
20
+ include CommandKit::Help::Man
21
+
22
+ man_dir File.join(__dir__,'..','..','..','man')
23
+ end
24
+
25
+ class TestCommandWithManDirNotSet
20
26
  include CommandKit::Help::Man
21
27
  end
22
28
  end
@@ -41,7 +47,7 @@ describe CommandKit::Help::Man do
41
47
 
42
48
  describe ".man_dir" do
43
49
  context "when no man_dir have been set" do
44
- subject { TestHelpMan::EmptyCommand }
50
+ subject { TestHelpMan::TestCommandWithManDirNotSet }
45
51
 
46
52
  it "should default to nil" do
47
53
  expect(subject.man_dir).to be_nil
@@ -52,7 +58,17 @@ describe CommandKit::Help::Man do
52
58
  subject { TestHelpMan::TestCommand }
53
59
 
54
60
  it "must return the explicitly set man_dir" do
55
- expect(subject.man_dir).to eq(File.expand_path('../fixtures/man',__FILE__))
61
+ expect(subject.man_dir).to eq(File.join(__dir__,'fixtures','man'))
62
+ end
63
+
64
+ context "but it's a relative path" do
65
+ subject { TestHelpMan::TestCommandWithRelativeManDir }
66
+
67
+ it "must expand the given man_dir path" do
68
+ expect(subject.man_dir).to eq(
69
+ File.expand_path(File.join(__dir__,'..','..','..','man'))
70
+ )
71
+ end
56
72
  end
57
73
  end
58
74
 
@@ -96,7 +112,7 @@ describe CommandKit::Help::Man do
96
112
  end
97
113
 
98
114
  it "must not change the superclass'es man_dir" do
99
- expect(super_subject.man_dir).to eq('set/in/baseclass')
115
+ expect(super_subject.man_dir).to eq(File.expand_path('set/in/baseclass'))
100
116
  end
101
117
  end
102
118
 
@@ -115,7 +131,7 @@ describe CommandKit::Help::Man do
115
131
  subject { TestHelpMan::ExplicitInheritedCmd }
116
132
 
117
133
  it "must return the subclass'es man_dir" do
118
- expect(subject.man_dir).to eq('set/in/subclass')
134
+ expect(subject.man_dir).to eq(File.expand_path('set/in/subclass'))
119
135
  end
120
136
 
121
137
  it "must not change the superclass'es man_dir" do
@@ -134,11 +150,11 @@ describe CommandKit::Help::Man do
134
150
  subject { TestHelpMan::ExplicitOverridingInheritedCmd }
135
151
 
136
152
  it "must return the subclass'es man_dir" do
137
- expect(subject.man_dir).to eq('set/in/subclass')
153
+ expect(subject.man_dir).to eq(File.expand_path('set/in/subclass'))
138
154
  end
139
155
 
140
156
  it "must not change the superclass'es man_dir" do
141
- expect(super_subject.man_dir).to eq('set/in/baseclass')
157
+ expect(super_subject.man_dir).to eq(File.expand_path('set/in/baseclass'))
142
158
  end
143
159
  end
144
160
  end
@@ -235,15 +251,6 @@ describe CommandKit::Help::Man do
235
251
  subject { command_class.new }
236
252
 
237
253
  describe "#help_man" do
238
- context "when .man_dir is not set" do
239
- let(:command_class) { TestHelpMan::EmptyCommand }
240
- subject { command_class.new }
241
-
242
- it do
243
- expect { subject.help_man }.to raise_error(NotImplementedError)
244
- end
245
- end
246
-
247
254
  let(:man_page_path) do
248
255
  File.join(subject.class.man_dir,subject.class.man_page)
249
256
  end
@@ -286,21 +293,33 @@ describe CommandKit::Help::Man do
286
293
  expect(subject.stdout).to receive(:tty?).and_return(true)
287
294
  end
288
295
 
289
- it "must open the command's man-page" do
290
- expect(subject).to receive(:help_man).and_return(true)
296
+ context "and man_dir is set" do
297
+ it "must open the command's man-page" do
298
+ expect(subject).to receive(:help_man).and_return(true)
291
299
 
292
- subject.help
293
- end
300
+ subject.help
301
+ end
294
302
 
295
- context "but when the man command is not installed" do
296
- before do
297
- expect(subject).to receive(:help_man).and_return(nil)
303
+ context "but when the man command is not installed" do
304
+ before do
305
+ expect(subject).to receive(:help_man).and_return(nil)
306
+ end
307
+
308
+ it "must call the super help() method" do
309
+ subject.help
310
+
311
+ expect(subject.stdout.string).to eq(normal_help_output)
312
+ end
298
313
  end
314
+ end
315
+
316
+ context "but the man_dir is not set" do
317
+ let(:command_class) { TestHelpMan::TestCommandWithManDirNotSet }
299
318
 
300
- it "must call the super help() method" do
319
+ it "must call the super help() mehtod" do
301
320
  subject.help
302
321
 
303
- expect(subject.stdout.string).to eq(normal_help_output)
322
+ expect(stdout.string).to eq(normal_help_output)
304
323
  end
305
324
  end
306
325
  end
@@ -7,6 +7,61 @@ describe CommandKit::Options::OptionValue do
7
7
  let(:required) { true }
8
8
  let(:default) { 1 }
9
9
 
10
+ describe ".default_usage" do
11
+ subject { described_class }
12
+
13
+ context "when given a Hash" do
14
+ let(:type) do
15
+ {'foo' => :foo, "bar" => :bar}
16
+ end
17
+
18
+ it "must join the Hash's keys with a '|' character" do
19
+ expect(subject.default_usage(type)).to eq(type.keys.join('|'))
20
+ end
21
+ end
22
+
23
+ context "when given an Array" do
24
+ let(:type) do
25
+ ['foo', 'bar', 'baz']
26
+ end
27
+
28
+ it "must join the Array's elements with a '|' character" do
29
+ expect(subject.default_usage(type)).to eq(type.join('|'))
30
+ end
31
+ end
32
+
33
+ context "when given a Regexp" do
34
+ let(:type) { /[0-9a-f]+/ }
35
+
36
+ it "must return the Regexp's source" do
37
+ expect(subject.default_usage(type)).to eq(type.source)
38
+ end
39
+ end
40
+
41
+ context "when given a Class" do
42
+ module TestOptionValue
43
+ class FooBarBaz
44
+ end
45
+ end
46
+
47
+ let(:type) { TestOptionValue::FooBarBaz }
48
+
49
+ it "must return the uppercase and underscored version of it's name" do
50
+ expect(subject.default_usage(type)).to eq("FOO_BAR_BAZ")
51
+ end
52
+ end
53
+
54
+ context "when given another kind of Object" do
55
+ let(:type) { Object.new }
56
+
57
+ it do
58
+ expect {
59
+ subject.default_usage(type)
60
+ }.to raise_error(TypeError,"unsupported option type: #{type.inspect}")
61
+ end
62
+ end
63
+ end
64
+
10
65
  describe "#initialize" do
11
66
  context "when the type: keyword is given" do
12
67
  let(:type) { Integer }