command_kit 0.1.0.pre1

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.
Files changed (98) hide show
  1. checksums.yaml +7 -0
  2. data/.document +3 -0
  3. data/.github/workflows/ruby.yml +29 -0
  4. data/.gitignore +7 -0
  5. data/.rspec +1 -0
  6. data/.yardopts +1 -0
  7. data/ChangeLog.md +29 -0
  8. data/Gemfile +14 -0
  9. data/LICENSE.txt +20 -0
  10. data/README.md +283 -0
  11. data/Rakefile +23 -0
  12. data/command_kit.gemspec +60 -0
  13. data/gemspec.yml +14 -0
  14. data/lib/command_kit.rb +1 -0
  15. data/lib/command_kit/arguments.rb +161 -0
  16. data/lib/command_kit/arguments/argument.rb +111 -0
  17. data/lib/command_kit/arguments/argument_value.rb +81 -0
  18. data/lib/command_kit/arguments/usage.rb +6 -0
  19. data/lib/command_kit/colors.rb +355 -0
  20. data/lib/command_kit/command.rb +42 -0
  21. data/lib/command_kit/command_name.rb +95 -0
  22. data/lib/command_kit/commands.rb +299 -0
  23. data/lib/command_kit/commands/auto_load.rb +153 -0
  24. data/lib/command_kit/commands/auto_load/subcommand.rb +90 -0
  25. data/lib/command_kit/commands/auto_require.rb +138 -0
  26. data/lib/command_kit/commands/command.rb +12 -0
  27. data/lib/command_kit/commands/help.rb +43 -0
  28. data/lib/command_kit/commands/parent_command.rb +21 -0
  29. data/lib/command_kit/commands/subcommand.rb +51 -0
  30. data/lib/command_kit/console.rb +141 -0
  31. data/lib/command_kit/description.rb +89 -0
  32. data/lib/command_kit/env.rb +43 -0
  33. data/lib/command_kit/env/home.rb +71 -0
  34. data/lib/command_kit/env/path.rb +71 -0
  35. data/lib/command_kit/examples.rb +99 -0
  36. data/lib/command_kit/exception_handler.rb +55 -0
  37. data/lib/command_kit/help.rb +62 -0
  38. data/lib/command_kit/help/man.rb +125 -0
  39. data/lib/command_kit/inflector.rb +84 -0
  40. data/lib/command_kit/main.rb +103 -0
  41. data/lib/command_kit/options.rb +179 -0
  42. data/lib/command_kit/options/option.rb +171 -0
  43. data/lib/command_kit/options/option_value.rb +90 -0
  44. data/lib/command_kit/options/parser.rb +227 -0
  45. data/lib/command_kit/options/quiet.rb +53 -0
  46. data/lib/command_kit/options/usage.rb +6 -0
  47. data/lib/command_kit/options/verbose.rb +55 -0
  48. data/lib/command_kit/options/version.rb +62 -0
  49. data/lib/command_kit/os.rb +47 -0
  50. data/lib/command_kit/pager.rb +115 -0
  51. data/lib/command_kit/printing.rb +32 -0
  52. data/lib/command_kit/printing/indent.rb +78 -0
  53. data/lib/command_kit/program_name.rb +57 -0
  54. data/lib/command_kit/stdio.rb +138 -0
  55. data/lib/command_kit/usage.rb +102 -0
  56. data/lib/command_kit/version.rb +4 -0
  57. data/lib/command_kit/xdg.rb +138 -0
  58. data/spec/arguments/argument_spec.rb +169 -0
  59. data/spec/arguments/argument_value_spec.rb +126 -0
  60. data/spec/arguments_spec.rb +213 -0
  61. data/spec/colors_spec.rb +470 -0
  62. data/spec/command_kit_spec.rb +8 -0
  63. data/spec/command_name_spec.rb +130 -0
  64. data/spec/command_spec.rb +49 -0
  65. data/spec/commands/auto_load/subcommand_spec.rb +82 -0
  66. data/spec/commands/auto_load_spec.rb +128 -0
  67. data/spec/commands/auto_require_spec.rb +142 -0
  68. data/spec/commands/fixtures/test_auto_load/cli/commands/test1.rb +10 -0
  69. data/spec/commands/fixtures/test_auto_load/cli/commands/test2.rb +10 -0
  70. data/spec/commands/fixtures/test_auto_require/lib/test_auto_require/cli/commands/test1.rb +10 -0
  71. data/spec/commands/help_spec.rb +66 -0
  72. data/spec/commands/parent_command_spec.rb +40 -0
  73. data/spec/commands/subcommand_spec.rb +99 -0
  74. data/spec/commands_spec.rb +767 -0
  75. data/spec/console_spec.rb +201 -0
  76. data/spec/description_spec.rb +203 -0
  77. data/spec/env/home_spec.rb +46 -0
  78. data/spec/env/path_spec.rb +78 -0
  79. data/spec/env_spec.rb +123 -0
  80. data/spec/examples_spec.rb +235 -0
  81. data/spec/exception_handler_spec.rb +103 -0
  82. data/spec/help_spec.rb +119 -0
  83. data/spec/inflector_spec.rb +104 -0
  84. data/spec/main_spec.rb +179 -0
  85. data/spec/options/option_spec.rb +258 -0
  86. data/spec/options/option_value_spec.rb +67 -0
  87. data/spec/options/parser_spec.rb +265 -0
  88. data/spec/options_spec.rb +137 -0
  89. data/spec/os_spec.rb +46 -0
  90. data/spec/pager_spec.rb +154 -0
  91. data/spec/printing/indent_spec.rb +130 -0
  92. data/spec/printing_spec.rb +76 -0
  93. data/spec/program_name_spec.rb +62 -0
  94. data/spec/spec_helper.rb +6 -0
  95. data/spec/stdio_spec.rb +264 -0
  96. data/spec/usage_spec.rb +237 -0
  97. data/spec/xdg_spec.rb +191 -0
  98. metadata +156 -0
@@ -0,0 +1,78 @@
1
+ require 'spec_helper'
2
+ require 'command_kit/env/path'
3
+
4
+ describe Env::Path do
5
+ module TestEnvPath
6
+ class TestCommand
7
+
8
+ include CommandKit::Env::Path
9
+
10
+ end
11
+ end
12
+
13
+ let(:command_class) { TestEnvPath::TestCommand }
14
+ subject { command_class.new }
15
+
16
+ describe "#initialize" do
17
+ it "must split ENV['PATH'] into an Array of directories" do
18
+ expect(subject.path_dirs).to eq(ENV['PATH'].split(File::PATH_SEPARATOR))
19
+ end
20
+
21
+ context "when given a custom 'PATH variable" do
22
+ let(:path_dirs) { %w[/bin /usr/bin] }
23
+ let(:path) { path_dirs.join(File::PATH_SEPARATOR) }
24
+
25
+ subject { command_class.new(env: {'PATH' => path}) }
26
+
27
+ it "must split env['PATH'] into an Array of directories" do
28
+ expect(subject.path_dirs).to eq(path_dirs)
29
+ end
30
+ end
31
+
32
+ context "when given an empty 'PATH' env variable" do
33
+ subject { command_class.new(env: {'PATH' => ''}) }
34
+
35
+ it "must set #path_dirs to []" do
36
+ expect(subject.path_dirs).to eq([])
37
+ end
38
+ end
39
+ end
40
+
41
+ describe "#find_command" do
42
+ context "when given a valid command name" do
43
+ it "must search #path_dirs for the command" do
44
+ command = subject.find_command('ruby')
45
+
46
+ expect(subject.path_dirs).to include(File.dirname(command))
47
+ end
48
+
49
+ it "must return the path to the command" do
50
+ command = subject.find_command('ruby')
51
+
52
+ expect(command).to_not be(nil)
53
+ expect(File.file?(command)).to be(true)
54
+ expect(File.executable?(command)).to be(true)
55
+ end
56
+ end
57
+
58
+ context "when given an unknown command name" do
59
+ it "must return nil" do
60
+ expect(subject.find_command('does_not_exist')).to be(nil)
61
+ end
62
+ end
63
+ end
64
+
65
+ describe "#command_installed?" do
66
+ context "when given a valid command name" do
67
+ it do
68
+ expect(subject.command_installed?('ruby')).to be(true)
69
+ end
70
+ end
71
+
72
+ context "when given an unknown command name" do
73
+ it "must return nil" do
74
+ expect(subject.command_installed?('does_not_exist')).to be(false)
75
+ end
76
+ end
77
+ end
78
+ end
data/spec/env_spec.rb ADDED
@@ -0,0 +1,123 @@
1
+ require 'spec_helper'
2
+ require 'command_kit/env'
3
+
4
+ describe CommandKit::Env do
5
+ module TestEnv
6
+ class TestCommand
7
+ include CommandKit::Env
8
+ end
9
+ end
10
+
11
+ let(:command_class) { TestEnv::TestCommand }
12
+
13
+ describe "#initialize" do
14
+ module TestEnv
15
+ class TestCommandWithInitialize
16
+ include CommandKit::Env
17
+
18
+ attr_reader :var
19
+
20
+ def initialize(**kwargs)
21
+ super(**kwargs)
22
+
23
+ @var = 'foo'
24
+ end
25
+ end
26
+
27
+ class TestCommandWithInitializeAndKeywordArgs
28
+ include CommandKit::Env
29
+
30
+ attr_reader :var
31
+
32
+ def initialize(var: "foo", **kwargs)
33
+ super(**kwargs)
34
+
35
+ @var = var
36
+ end
37
+ end
38
+ end
39
+
40
+ context "when given no arguments" do
41
+ subject { command_class.new() }
42
+
43
+ it "must initialize #env to ENV" do
44
+ expect(subject.env).to be(ENV)
45
+ end
46
+
47
+ context "and the including class defines it's own #initialize method" do
48
+ let(:command_class) { TestEnv::TestCommandWithInitialize }
49
+
50
+ it "must initialize #env to ENV" do
51
+ expect(subject.env).to be(ENV)
52
+ end
53
+
54
+ it "must also call the class'es #initialize method" do
55
+ expect(subject.var).to eq('foo')
56
+ end
57
+
58
+ context "and it accepts keyword arguments" do
59
+ let(:command_class) { TestEnv::TestCommandWithInitializeAndKeywordArgs }
60
+ let(:var) { 'custom value' }
61
+
62
+ subject { command_class.new(var: var) }
63
+
64
+ it "must initialize #env to ENV" do
65
+ expect(subject.env).to be(ENV)
66
+ end
67
+
68
+ it "must also call the class'es #initialize method with any additional keyword arguments" do
69
+ expect(subject.var).to eq(var)
70
+ end
71
+ end
72
+ end
73
+ end
74
+
75
+ context "when given a custom env: value" do
76
+ let(:custom_env) { {'FOO' => 'bar'} }
77
+
78
+ subject { command_class.new(env: custom_env) }
79
+
80
+ it "must initialize #env to the env: value" do
81
+ expect(subject.env).to eq(custom_env)
82
+ end
83
+
84
+ context "and the including class defines it's own #initialize method" do
85
+ let(:command_class) { TestEnv::TestCommandWithInitialize }
86
+
87
+ it "must initialize #env to the env: value" do
88
+ expect(subject.env).to eq(custom_env)
89
+ end
90
+
91
+ it "must also call the class'es #initialize method" do
92
+ expect(subject.var).to eq('foo')
93
+ end
94
+
95
+ context "and it accepts keyword arguments" do
96
+ let(:command_class) { TestEnv::TestCommandWithInitializeAndKeywordArgs }
97
+ let(:var) { 'custom value' }
98
+
99
+ subject { command_class.new(env: custom_env, var: var) }
100
+
101
+ it "must initialize #env to the env: value" do
102
+ expect(subject.env).to eq(custom_env)
103
+ end
104
+
105
+ it "must also call the class'es #initialize method with any additional keyword arguments" do
106
+ expect(subject.var).to eq(var)
107
+ end
108
+ end
109
+ end
110
+ end
111
+ end
112
+
113
+ describe "#env" do
114
+ let(:command_class) { TestEnv::TestCommand }
115
+ let(:env) { {"FOO" => "bar" } }
116
+
117
+ subject { command_class.new(env: env) }
118
+
119
+ it "must return the initialized env: value" do
120
+ expect(subject.env).to eq(env)
121
+ end
122
+ end
123
+ end
@@ -0,0 +1,235 @@
1
+ require 'spec_helper'
2
+ require 'command_kit/examples'
3
+
4
+ describe Examples do
5
+ module TestExamples
6
+ class ImplicitCmd
7
+ include CommandKit::Examples
8
+ end
9
+ end
10
+
11
+ let(:command_class) { TestExamples::ImplicitCmd }
12
+
13
+ describe ".examples" do
14
+ subject { TestExamples::ImplicitCmd }
15
+
16
+ context "when no examples have been set" do
17
+ it "should default to nil" do
18
+ expect(subject.examples).to be_nil
19
+ end
20
+ end
21
+
22
+ context "when a examples is explicitly set" do
23
+ module TestExamples
24
+ class ExplicitCmd
25
+ include CommandKit::Examples
26
+ examples [
27
+ '--example 1',
28
+ '--example 2'
29
+ ]
30
+ end
31
+ end
32
+
33
+ subject { TestExamples::ExplicitCmd }
34
+
35
+ it "must return the explicitly set examples" do
36
+ expect(subject.examples).to eq([
37
+ '--example 1',
38
+ '--example 2'
39
+ ])
40
+ end
41
+ end
42
+
43
+ context "when the command class inherites from another class" do
44
+ context "but no examples is set" do
45
+ module TestExamples
46
+ class BaseCmd
47
+ include CommandKit::Examples
48
+ end
49
+
50
+ class InheritedCmd < BaseCmd
51
+ end
52
+ end
53
+
54
+ subject { TestExamples::InheritedCmd }
55
+
56
+ it "must search each class then return nil "do
57
+ expect(subject.examples).to be_nil
58
+ end
59
+ end
60
+
61
+ module TestExamples
62
+ class ExplicitBaseCmd
63
+ include CommandKit::Examples
64
+ examples [
65
+ '--example 1',
66
+ '--example 2'
67
+ ]
68
+ end
69
+ end
70
+
71
+ context "when the superclass defines an explicit examples" do
72
+ module TestExamples
73
+ class ImplicitInheritedCmd < ExplicitBaseCmd
74
+ end
75
+ end
76
+
77
+ let(:super_subject) { TestExamples::ExplicitBaseCmd }
78
+ subject { TestExamples::ImplicitInheritedCmd }
79
+
80
+ it "must inherit the superclass'es examples" do
81
+ expect(subject.examples).to eq(super_subject.examples)
82
+ end
83
+
84
+ it "must not change the superclass'es examples" do
85
+ expect(super_subject.examples).to eq([
86
+ '--example 1',
87
+ '--example 2'
88
+ ])
89
+ end
90
+ end
91
+
92
+ context "when the subclass defines an explicit examples" do
93
+ module TestExamples
94
+ class ImplicitBaseCmd
95
+ include CommandKit::Examples
96
+ end
97
+
98
+ class ExplicitInheritedCmd < ImplicitBaseCmd
99
+ examples [
100
+ '--example 1',
101
+ '--example 2'
102
+ ]
103
+ end
104
+ end
105
+
106
+ let(:super_subject) { TestExamples::ImplicitBaseCmd }
107
+ subject { TestExamples::ExplicitInheritedCmd }
108
+
109
+ it "must return the subclass'es examples" do
110
+ expect(subject.examples).to eq([
111
+ '--example 1',
112
+ '--example 2'
113
+ ])
114
+ end
115
+
116
+ it "must not change the superclass'es examples" do
117
+ expect(super_subject.examples).to be_nil
118
+ end
119
+ end
120
+
121
+ context "when both the subclass overrides the superclass's exampless" do
122
+ module TestExamples
123
+ class ExplicitOverridingInheritedCmd < ExplicitBaseCmd
124
+ examples [
125
+ '--example override'
126
+ ]
127
+ end
128
+ end
129
+
130
+ let(:super_subject) { TestExamples::ExplicitBaseCmd }
131
+ subject { TestExamples::ExplicitOverridingInheritedCmd }
132
+
133
+ it "must return the subclass'es examples" do
134
+ expect(subject.examples).to eq([
135
+ '--example override'
136
+ ])
137
+ end
138
+
139
+ it "must not change the superclass'es examples" do
140
+ expect(super_subject.examples).to eq([
141
+ '--example 1',
142
+ '--example 2'
143
+ ])
144
+ end
145
+ end
146
+ end
147
+ end
148
+
149
+ subject { command_class.new }
150
+
151
+ describe "#examples" do
152
+ it "must be the same as .examples" do
153
+ expect(subject.examples).to eq(command_class.examples)
154
+ end
155
+ end
156
+
157
+ describe "#help_examples" do
158
+ context "when #examples returns nil" do
159
+ module TestExamples
160
+ class NoExamples
161
+ include CommandKit::Examples
162
+ end
163
+ end
164
+
165
+ let(:command_class) { TestExamples::NoExamples }
166
+ subject { command_class.new }
167
+
168
+ it "must print out the examples" do
169
+ expect { subject.help_examples }.to_not output.to_stdout
170
+ end
171
+ end
172
+
173
+ context "when #examples returns an Array" do
174
+ module TestExamples
175
+ class MultipleExamples
176
+ include CommandKit::Examples
177
+
178
+ examples [
179
+ '--example 1',
180
+ '--example 2',
181
+ '--example 3'
182
+ ]
183
+ end
184
+ end
185
+
186
+ let(:command_class) { TestExamples::MultipleExamples }
187
+ subject { command_class.new }
188
+
189
+ it "must print out the 'Examples:' section header and the examples" do
190
+ expect { subject.help_examples }.to output(
191
+ [
192
+ '',
193
+ "Examples:",
194
+ " #{subject.command_name} #{subject.examples[0]}",
195
+ " #{subject.command_name} #{subject.examples[1]}",
196
+ " #{subject.command_name} #{subject.examples[2]}",
197
+ ''
198
+ ].join($/)
199
+ ).to_stdout
200
+ end
201
+ end
202
+ end
203
+
204
+ describe "#help" do
205
+ it "must call #help_examples" do
206
+ expect(subject).to receive(:help_examples)
207
+
208
+ subject.help
209
+ end
210
+
211
+ context "when the superclass defines it's own #help method" do
212
+ module TestDescription
213
+ class SuperclassHelpMethod
214
+ def help
215
+ puts 'superclass'
216
+ end
217
+ end
218
+
219
+ class InheritedHelpMethod < SuperclassHelpMethod
220
+ include CommandKit::Examples
221
+ end
222
+ end
223
+
224
+ let(:super_command_class) { TestDescription::SuperclassHelpMethod }
225
+ let(:command_class) { TestDescription::InheritedHelpMethod }
226
+
227
+ it "must call the superclass'es #help method first" do
228
+ expect_any_instance_of(super_command_class).to receive(:help)
229
+ expect(subject).to receive(:help_examples)
230
+
231
+ subject.help
232
+ end
233
+ end
234
+ end
235
+ end
@@ -0,0 +1,103 @@
1
+ require 'spec_helper'
2
+ require 'command_kit/exception_handler'
3
+
4
+ describe CommandKit::ExceptionHandler do
5
+ module TestExceptionHandler
6
+ class TestIncludedCmd
7
+
8
+ include CommandKit::Main
9
+ include CommandKit::ExceptionHandler
10
+
11
+ end
12
+ end
13
+
14
+ let(:command_class) { TestExceptionHandler::TestIncludedCmd }
15
+ subject { command_class.new }
16
+
17
+ describe ".included" do
18
+ it "must include CommandKit::Printing" do
19
+ expect(command_class).to be_include(CommandKit::Printing)
20
+ end
21
+ end
22
+
23
+ describe "#main" do
24
+ context "when the #main method rescues an Interrupt" do
25
+ module TestExceptionHandler
26
+ class RaisesInterruptCmd
27
+
28
+ include CommandKit::Main
29
+ include CommandKit::ExceptionHandler
30
+
31
+ def run
32
+ raise(Interrupt.new)
33
+ end
34
+
35
+ end
36
+ end
37
+
38
+ let(:command_class) { TestExceptionHandler::RaisesInterruptCmd }
39
+ subject { command_class.new }
40
+
41
+ it "must re-raise the Interrupt exception" do
42
+ expect { subject.main }.to raise_error(Interrupt)
43
+ end
44
+ end
45
+
46
+ context "when the #main method raises an Errno::EPIPE" do
47
+ module TestExceptionHandler
48
+ class RaisesErrnoEPIPECmd
49
+
50
+ include CommandKit::Main
51
+ include CommandKit::ExceptionHandler
52
+
53
+ def run
54
+ raise(Errno::EPIPE.new)
55
+ end
56
+
57
+ end
58
+ end
59
+
60
+ let(:command_class) { TestExceptionHandler::RaisesErrnoEPIPECmd }
61
+ subject { command_class.new }
62
+
63
+ it "must re-raise the Errno::EPIPE exception" do
64
+ expect { subject.main }.to raise_error(Errno::EPIPE)
65
+ end
66
+ end
67
+
68
+ context "when the #main method raises another Exception" do
69
+ module TestExceptionHandler
70
+ class RaisesExceptionCmd
71
+
72
+ include CommandKit::Main
73
+ include CommandKit::ExceptionHandler
74
+
75
+ def run
76
+ raise(RuntimeError.new("error"))
77
+ end
78
+
79
+ end
80
+ end
81
+
82
+ let(:command_class) { TestExceptionHandler::RaisesExceptionCmd }
83
+ subject { command_class.new }
84
+
85
+ it "must rescue the exception and call #on_exception" do
86
+ expect(subject).to receive(:on_exception).with(RuntimeError)
87
+
88
+ subject.main
89
+ end
90
+ end
91
+ end
92
+
93
+ describe "#on_exception" do
94
+ let(:error) { RuntimeError.new('error') }
95
+
96
+ it "must call #print_exception and exit with 1" do
97
+ expect(subject).to receive(:print_exception).with(error)
98
+ expect(subject).to receive(:exit).with(1)
99
+
100
+ subject.on_exception(error)
101
+ end
102
+ end
103
+ end