ronin-core 0.1.0.beta1

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 (109) hide show
  1. checksums.yaml +7 -0
  2. data/.document +5 -0
  3. data/.github/workflows/ruby.yml +41 -0
  4. data/.gitignore +12 -0
  5. data/.rspec +1 -0
  6. data/.rubocop.yml +160 -0
  7. data/.ruby-version +1 -0
  8. data/.yardopts +1 -0
  9. data/COPYING.txt +165 -0
  10. data/ChangeLog.md +11 -0
  11. data/Gemfile +30 -0
  12. data/README.md +299 -0
  13. data/Rakefile +34 -0
  14. data/examples/ruby_shell.rb +11 -0
  15. data/gemspec.yml +28 -0
  16. data/lib/ronin/core/class_registry.rb +246 -0
  17. data/lib/ronin/core/cli/command.rb +87 -0
  18. data/lib/ronin/core/cli/command_shell/command.rb +110 -0
  19. data/lib/ronin/core/cli/command_shell.rb +345 -0
  20. data/lib/ronin/core/cli/generator/options/author.rb +106 -0
  21. data/lib/ronin/core/cli/generator/options/description.rb +54 -0
  22. data/lib/ronin/core/cli/generator/options/reference.rb +60 -0
  23. data/lib/ronin/core/cli/generator/options/summary.rb +54 -0
  24. data/lib/ronin/core/cli/generator.rb +238 -0
  25. data/lib/ronin/core/cli/logging.rb +59 -0
  26. data/lib/ronin/core/cli/options/param.rb +68 -0
  27. data/lib/ronin/core/cli/options/values/arches.rb +45 -0
  28. data/lib/ronin/core/cli/options/values/oses.rb +32 -0
  29. data/lib/ronin/core/cli/printing/arch.rb +71 -0
  30. data/lib/ronin/core/cli/printing/metadata.rb +113 -0
  31. data/lib/ronin/core/cli/printing/os.rb +54 -0
  32. data/lib/ronin/core/cli/printing/params.rb +69 -0
  33. data/lib/ronin/core/cli/ruby_shell.rb +131 -0
  34. data/lib/ronin/core/cli/shell.rb +186 -0
  35. data/lib/ronin/core/git.rb +73 -0
  36. data/lib/ronin/core/home.rb +86 -0
  37. data/lib/ronin/core/metadata/authors/author.rb +241 -0
  38. data/lib/ronin/core/metadata/authors.rb +120 -0
  39. data/lib/ronin/core/metadata/description.rb +100 -0
  40. data/lib/ronin/core/metadata/id.rb +88 -0
  41. data/lib/ronin/core/metadata/references.rb +87 -0
  42. data/lib/ronin/core/metadata/summary.rb +78 -0
  43. data/lib/ronin/core/metadata/version.rb +74 -0
  44. data/lib/ronin/core/params/exceptions.rb +38 -0
  45. data/lib/ronin/core/params/mixin.rb +317 -0
  46. data/lib/ronin/core/params/param.rb +137 -0
  47. data/lib/ronin/core/params/types/boolean.rb +64 -0
  48. data/lib/ronin/core/params/types/enum.rb +107 -0
  49. data/lib/ronin/core/params/types/float.rb +68 -0
  50. data/lib/ronin/core/params/types/integer.rb +100 -0
  51. data/lib/ronin/core/params/types/numeric.rb +106 -0
  52. data/lib/ronin/core/params/types/regexp.rb +67 -0
  53. data/lib/ronin/core/params/types/string.rb +118 -0
  54. data/lib/ronin/core/params/types/type.rb +54 -0
  55. data/lib/ronin/core/params/types/uri.rb +72 -0
  56. data/lib/ronin/core/params/types.rb +62 -0
  57. data/lib/ronin/core/params.rb +19 -0
  58. data/lib/ronin/core/version.rb +24 -0
  59. data/ronin-core.gemspec +59 -0
  60. data/spec/class_registry_spec.rb +224 -0
  61. data/spec/cli/command_shell/command_spec.rb +113 -0
  62. data/spec/cli/command_shell_spec.rb +1114 -0
  63. data/spec/cli/command_spec.rb +16 -0
  64. data/spec/cli/fixtures/irb_command +8 -0
  65. data/spec/cli/fixtures/template/dir/file1.txt +1 -0
  66. data/spec/cli/fixtures/template/dir/file2.txt +1 -0
  67. data/spec/cli/fixtures/template/file.erb +1 -0
  68. data/spec/cli/fixtures/template/file.txt +1 -0
  69. data/spec/cli/generator/options/author_spec.rb +121 -0
  70. data/spec/cli/generator/options/description_spec.rb +45 -0
  71. data/spec/cli/generator/options/reference_spec.rb +53 -0
  72. data/spec/cli/generator/options/summary_spec.rb +45 -0
  73. data/spec/cli/generator_spec.rb +244 -0
  74. data/spec/cli/logging_spec.rb +95 -0
  75. data/spec/cli/options/param_spec.rb +67 -0
  76. data/spec/cli/options/values/arches_spec.rb +62 -0
  77. data/spec/cli/printing/arch_spec.rb +130 -0
  78. data/spec/cli/printing/metadata_spec.rb +211 -0
  79. data/spec/cli/printing/os_spec.rb +64 -0
  80. data/spec/cli/printing/params_spec.rb +63 -0
  81. data/spec/cli/ruby_shell.rb +99 -0
  82. data/spec/cli/shell_spec.rb +211 -0
  83. data/spec/fixtures/example_class_registry/base_class.rb +9 -0
  84. data/spec/fixtures/example_class_registry/classes/loaded_class.rb +9 -0
  85. data/spec/fixtures/example_class_registry/classes/name_mismatch.rb +9 -0
  86. data/spec/fixtures/example_class_registry/classes/no_module.rb +4 -0
  87. data/spec/fixtures/example_class_registry.rb +8 -0
  88. data/spec/git_spec.rb +58 -0
  89. data/spec/home_spec.rb +64 -0
  90. data/spec/metadata/authors/author_spec.rb +335 -0
  91. data/spec/metadata/authors_spec.rb +126 -0
  92. data/spec/metadata/description_spec.rb +74 -0
  93. data/spec/metadata/id_spec.rb +92 -0
  94. data/spec/metadata/references_spec.rb +100 -0
  95. data/spec/metadata/summary_spec.rb +74 -0
  96. data/spec/metadata/version_spec.rb +72 -0
  97. data/spec/params/mixin_spec.rb +484 -0
  98. data/spec/params/param_spec.rb +164 -0
  99. data/spec/params/types/boolean_spec.rb +56 -0
  100. data/spec/params/types/enum_spec.rb +94 -0
  101. data/spec/params/types/float_spec.rb +107 -0
  102. data/spec/params/types/integer_spec.rb +155 -0
  103. data/spec/params/types/numeric_spec.rb +138 -0
  104. data/spec/params/types/regexp_spec.rb +64 -0
  105. data/spec/params/types/string_spec.rb +174 -0
  106. data/spec/params/types/type_spec.rb +14 -0
  107. data/spec/params/types/uri_spec.rb +62 -0
  108. data/spec/spec_helper.rb +11 -0
  109. metadata +252 -0
@@ -0,0 +1,1114 @@
1
+ require 'spec_helper'
2
+ require 'ronin/core/cli/command_shell'
3
+
4
+ describe Ronin::Core::CLI::CommandShell do
5
+ describe ".commands" do
6
+ subject { shell_class }
7
+
8
+ context "when no commands have been defined in the shell class" do
9
+ module TestCommandShell
10
+ class ShellWithNoCommands < Ronin::Core::CLI::CommandShell
11
+ shell_name 'test'
12
+ end
13
+ end
14
+
15
+ let(:shell_class) { TestCommandShell::ShellWithNoCommands }
16
+
17
+ it "must return a Hash only containing the help command" do
18
+ expect(subject.commands.keys).to eq(%w[help])
19
+ expect(subject.commands['help']).to be_kind_of(described_class::Command)
20
+ end
21
+ end
22
+
23
+ context "when commands have been defined in the shell class" do
24
+ module TestCommandShell
25
+ class ShellWithCommands < Ronin::Core::CLI::CommandShell
26
+ shell_name 'test'
27
+
28
+ command :foo, summary: 'Foo command'
29
+ def foo
30
+ end
31
+ end
32
+ end
33
+
34
+ let(:shell_class) { TestCommandShell::ShellWithCommands }
35
+
36
+ it "must return the Hash of command names and Command classes" do
37
+ expect(subject.commands['foo']).to be_kind_of(described_class::Command)
38
+ expect(subject.commands['foo'].name).to eq(:foo)
39
+ expect(subject.commands['foo'].summary).to eq('Foo command')
40
+ end
41
+ end
42
+
43
+ context "but when the commands are defined in the superclass" do
44
+ module TestCommandShell
45
+ class ShellWithInheritedCommands < ShellWithCommands
46
+ shell_name 'test'
47
+ end
48
+ end
49
+
50
+ let(:shell_superclass) do
51
+ TestCommandShell::ShellWithCommands
52
+ end
53
+
54
+ let(:shell_class) do
55
+ TestCommandShell::ShellWithInheritedCommands
56
+ end
57
+
58
+ it "must return the commands defined in the superclass" do
59
+ expect(subject.commands['foo']).to be(shell_superclass.commands['foo'])
60
+ end
61
+
62
+ context "but additional commands are defined in the sub-class" do
63
+ module TestCommandShell
64
+ class ShellWithInheritedCommandsAndItsOwnCommands < ShellWithCommands
65
+ shell_name 'test'
66
+ command :bar, summary: 'Bar command'
67
+ def bar
68
+ end
69
+ end
70
+ end
71
+
72
+ let(:shell_class) do
73
+ TestCommandShell::ShellWithInheritedCommandsAndItsOwnCommands
74
+ end
75
+
76
+ it "must contain the commands defined in the subclass" do
77
+ expect(subject.commands['bar']).to be_kind_of(described_class::Command)
78
+ expect(subject.commands['bar'].name).to eq(:bar)
79
+ expect(subject.commands['bar'].summary).to eq('Bar command')
80
+ end
81
+
82
+ it "must also contain the commands defined in the superclass" do
83
+ expect(subject.commands['foo']).to be(shell_superclass.commands['foo'])
84
+ end
85
+
86
+ it "must not modify the superclass'es .commands" do
87
+ expect(shell_superclass.commands['foo']).to be_kind_of(described_class::Command)
88
+ expect(shell_superclass.commands['foo'].name).to eq(:foo)
89
+ expect(shell_superclass.commands['foo'].summary).to eq('Foo command')
90
+
91
+ expect(shell_superclass.commands['bar']).to be(nil)
92
+ end
93
+ end
94
+
95
+ context "but the commands defined in the sub-class override those in the superclass" do
96
+ module TestCommandShell
97
+ class ShellThatOverridesInheritedCommands < ShellWithCommands
98
+ shell_name 'test'
99
+ command :foo, summary: 'Overrided foo command'
100
+ def foo
101
+ end
102
+ end
103
+ end
104
+
105
+ let(:shell_class) { TestCommandShell::ShellThatOverridesInheritedCommands }
106
+
107
+ it "must contain the commands overridden in the sub-class" do
108
+ expect(subject.commands['foo']).to_not be(shell_superclass.commands['foo'])
109
+ expect(subject.commands['foo'].summary).to eq('Overrided foo command')
110
+ end
111
+
112
+ it "must not modify the superclass'es .commands" do
113
+ expect(shell_superclass.commands['foo']).to be_kind_of(described_class::Command)
114
+ expect(shell_superclass.commands['foo'].name).to eq(:foo)
115
+ expect(shell_superclass.commands['foo'].summary).to eq('Foo command')
116
+ end
117
+ end
118
+ end
119
+ end
120
+
121
+ describe ".parse_command" do
122
+ subject { described_class }
123
+
124
+ let(:command_name) { 'foo' }
125
+
126
+ context "when given a single command name" do
127
+ let(:line) { command_name.to_s }
128
+
129
+ it "must return the command name" do
130
+ expect(subject.parse_command(line)).to eq(
131
+ [command_name]
132
+ )
133
+ end
134
+ end
135
+
136
+ context "when given a command name and additional arguments" do
137
+ let(:arg1) { "bar" }
138
+ let(:arg2) { "baz" }
139
+ let(:line) { "#{command_name} #{arg1} #{arg2}" }
140
+
141
+ it "must return the command name and an Array of arguments" do
142
+ expect(subject.parse_command(line)).to eq(
143
+ [command_name, arg1, arg2]
144
+ )
145
+ end
146
+
147
+ context "but the arguments are in quotes" do
148
+ let(:line) { "#{command_name} \"#{arg1} #{arg2}\"" }
149
+
150
+ it "must keep quoted arguments together" do
151
+ expect(subject.parse_command(line)).to eq(
152
+ [command_name, "#{arg1} #{arg2}"]
153
+ )
154
+ end
155
+ end
156
+ end
157
+ end
158
+
159
+ describe "#complete" do
160
+ module TestCommandShell
161
+ class ShellWithCompletions < Ronin::Core::CLI::CommandShell
162
+ shell_name 'test'
163
+
164
+ command :foo, summary: 'Foo command'
165
+ def foo
166
+ end
167
+
168
+ command :bar, completions: %w[arg1 arg2 foo], summary: 'Bar command'
169
+ def bar
170
+ end
171
+
172
+ command :baz, summary: 'Baz command'
173
+ def baz
174
+ end
175
+ end
176
+ end
177
+
178
+ let(:shell_class) { TestCommandShell::ShellWithCompletions }
179
+ subject { shell_class.new }
180
+
181
+ context "when the input is empty" do
182
+ let(:preposing) { '' }
183
+ let(:word) { '' }
184
+
185
+ it "must return all available command names" do
186
+ expect(subject.complete(word,preposing)).to eq(subject.class.commands.keys)
187
+ end
188
+ end
189
+
190
+ context "when the input does not contain a space" do
191
+ let(:preposing) { '' }
192
+ let(:word) { 'ba' }
193
+
194
+ it "must return the matching command names" do
195
+ expect(subject.complete(word,preposing)).to eq(%w[bar baz])
196
+ end
197
+ end
198
+
199
+ context "when the input does contain a space" do
200
+ context "and the input starts with a known command" do
201
+ let(:command) { 'bar' }
202
+ let(:arg) { "arg" }
203
+ let(:preposing) { "#{command} " }
204
+ let(:word) { arg }
205
+
206
+ context "and the command defines an Array of completion values" do
207
+ it "must return the command's argument values that match the end of the input" do
208
+ expect(subject.complete(word,preposing)).to eq(
209
+ subject.class.commands[command].completions.select { |value|
210
+ value.start_with?(word)
211
+ }
212
+ )
213
+ end
214
+
215
+ context "but the input contains multiple spaces" do
216
+ let(:preposing) { "#{command} bla bla " }
217
+
218
+ it "must still return the command's argument values that match the end of the input" do
219
+ expect(subject.complete(word,preposing)).to eq(
220
+ subject.class.commands[command].completions.select { |value|
221
+ value.start_with?(word)
222
+ }
223
+ )
224
+ end
225
+ end
226
+ end
227
+
228
+ context "and the command defines a completion method name" do
229
+ module TestCommandShell
230
+ class ShellWithCompletionMethod < Ronin::Core::CLI::CommandShell
231
+ shell_name 'test'
232
+
233
+ command :foo, summary: 'Foo command'
234
+ def foo
235
+ end
236
+
237
+ command :bar, summary: 'Bar command',
238
+ completions: :bar_completion
239
+ def bar
240
+ end
241
+
242
+ def bar_completion(arg,preposing)
243
+ %w[
244
+ foo
245
+ arg-A
246
+ arg-B
247
+ bar
248
+ ]
249
+ end
250
+
251
+ command :baz, summary: 'Baz command'
252
+ def baz
253
+ end
254
+ end
255
+ end
256
+
257
+ let(:shell_class) { TestCommandShell::ShellWithCompletionMethod }
258
+
259
+ it "must call the completion method with the argument and filter the results" do
260
+ completion_method = subject.class.commands[command].completions
261
+
262
+ expect(subject.complete(word,preposing)).to eq(
263
+ subject.send(completion_method,word,preposing).select { |value|
264
+ value.start_with?(word)
265
+ }
266
+ )
267
+ end
268
+
269
+ context "but the completion method was not defined" do
270
+ module TestCommandShell
271
+ class ShellWithMissingCompletionMethod < Ronin::Core::CLI::CommandShell
272
+ shell_name 'test'
273
+
274
+ command :foo, summary: 'Foo command'
275
+ def foo
276
+ end
277
+
278
+ command :bar, summary: 'Bar command',
279
+ completions: :bar_completion
280
+ def bar
281
+ end
282
+
283
+ command :baz, summary: 'Baz command'
284
+ def baz
285
+ end
286
+ end
287
+ end
288
+
289
+ let(:shell_class) do
290
+ TestCommandShell::ShellWithMissingCompletionMethod
291
+ end
292
+
293
+ it "must call the completion method with the argument" do
294
+ expect {
295
+ subject.complete(word,preposing)
296
+ }.to raise_error(NotImplementedError,"#{subject.class}#bar_completion was not defined")
297
+ end
298
+ end
299
+ end
300
+ end
301
+
302
+ context "but the input does not start with a known command" do
303
+ let(:command) { 'does_not_exist' }
304
+ let(:arg) { "arg" }
305
+ let(:preposing) { "#{command} " }
306
+ let(:word) { arg }
307
+
308
+ it "must return nil" do
309
+ expect(subject.complete(word,preposing)).to be(nil)
310
+ end
311
+ end
312
+ end
313
+
314
+ context "when the input ends with a space" do
315
+ context "and the input starts with a known command" do
316
+ let(:command) { 'bar' }
317
+
318
+ let(:preposing) { "#{command} " }
319
+ let(:word) { '' }
320
+
321
+ it "must return all possible completion values for the command" do
322
+ expect(subject.complete(word,preposing)).to eq(
323
+ subject.class.commands[command].completions
324
+ )
325
+ end
326
+ end
327
+
328
+ context "but the input does not start with a known command" do
329
+ let(:command) { 'does_not_exist' }
330
+
331
+ let(:preposing) { '' }
332
+ let(:word) { command }
333
+
334
+ it "must return nil" do
335
+ expect(subject.complete(word,preposing)).to eq([])
336
+ end
337
+ end
338
+ end
339
+ end
340
+
341
+ module TestCommandShell
342
+ class TestCommandShell < Ronin::Core::CLI::CommandShell
343
+ shell_name 'test'
344
+
345
+ command :foo, summary: 'Foo command'
346
+ def foo
347
+ end
348
+
349
+ command :bar, summary: 'Bar command'
350
+ def bar
351
+ end
352
+ end
353
+ end
354
+
355
+ let(:shell_class) { TestCommandShell::TestCommandShell }
356
+ subject { shell_class.new }
357
+
358
+ describe "#exec" do
359
+ it "must call the underlying command method" do
360
+ expect(subject).to receive(:foo)
361
+
362
+ subject.exec('foo')
363
+ end
364
+ end
365
+
366
+ describe "#call" do
367
+ context "when the command exists" do
368
+ context "but the command does not accept any arguments" do
369
+ module TestCommandShell
370
+ class ShellWithCommandWithNoArgs < Ronin::Core::CLI::CommandShell
371
+ shell_name 'test'
372
+
373
+ command :cmd, summary: 'Example command'
374
+ def cmd
375
+ puts "#{__method__} called"
376
+ end
377
+ end
378
+ end
379
+
380
+ let(:shell_class) do
381
+ TestCommandShell::ShellWithCommandWithNoArgs
382
+ end
383
+ let(:name) { 'cmd' }
384
+
385
+ context "and no arguments are given" do
386
+ let(:args) { [] }
387
+
388
+ it "must call the command method and return true" do
389
+ expect {
390
+ expect(subject.call(name,*args)).to be(true)
391
+ }.to output("#{name} called#{$/}").to_stdout
392
+ end
393
+ end
394
+
395
+ context "and one argument is given" do
396
+ let(:arg) { "foo" }
397
+ let(:args) { [arg] }
398
+
399
+ it "must print an error and return false" do
400
+ expect {
401
+ expect(subject.call(name,*args)).to be(false)
402
+ }.to output("#{name}: too many arguments given#{$/}").to_stderr
403
+ end
404
+ end
405
+ end
406
+
407
+ context "and the command accepts an argument" do
408
+ module TestCommandShell
409
+ class ShellWithCommandWithArg < Ronin::Core::CLI::CommandShell
410
+ shell_name 'test'
411
+
412
+ command :cmd_with_arg, usage: 'ARG',
413
+ summary: 'Example command with arg'
414
+ def cmd_with_arg(arg)
415
+ puts "#{__method__} called: arg=#{arg}"
416
+ end
417
+ end
418
+ end
419
+
420
+ let(:shell_class) do
421
+ TestCommandShell::ShellWithCommandWithArg
422
+ end
423
+ let(:name) { 'cmd_with_arg' }
424
+
425
+ context "and no arguments are given" do
426
+ let(:args) { [] }
427
+
428
+ it "must print an error and return false" do
429
+ expect {
430
+ expect(subject.call(name,*args)).to be(false)
431
+ }.to output("#{name}: too few arguments given#{$/}").to_stderr
432
+ end
433
+ end
434
+
435
+ context "and one argument is given" do
436
+ let(:arg) { "foo" }
437
+ let(:args) { [arg] }
438
+
439
+ it "must call the command mehtod with the argument and return true" do
440
+ expect {
441
+ expect(subject.call(name,*args)).to be(true)
442
+ }.to output("#{name} called: arg=#{arg}#{$/}").to_stdout
443
+ end
444
+ end
445
+
446
+ context "and more than one arguments is given" do
447
+ let(:arg1) { "foo" }
448
+ let(:arg2) { "bar" }
449
+ let(:args) { [arg1, arg2] }
450
+
451
+ it "must print an error and return false" do
452
+ expect {
453
+ expect(subject.call(name,*args)).to be(false)
454
+ }.to output("#{name}: too many arguments given#{$/}").to_stderr
455
+ end
456
+ end
457
+ end
458
+
459
+ context "and the command accepts multiple arguments" do
460
+ module TestCommandShell
461
+ class ShellWithCommandWithMultipleArgs < Ronin::Core::CLI::CommandShell
462
+ shell_name 'test'
463
+
464
+ command :cmd_with_args, usage: 'ARG1 ARG2',
465
+ summary: 'Example command with multiple arg'
466
+ def cmd_with_args(arg1,arg2)
467
+ puts "#{__method__} called: arg1=#{arg1} arg2=#{arg2}"
468
+ end
469
+ end
470
+ end
471
+
472
+ let(:shell_class) do
473
+ TestCommandShell::ShellWithCommandWithMultipleArgs
474
+ end
475
+ let(:name) { 'cmd_with_args' }
476
+
477
+ context "and no arguments are given" do
478
+ let(:args) { [] }
479
+
480
+ it "must print an error and return false" do
481
+ expect {
482
+ expect(subject.call(name,*args)).to be(false)
483
+ }.to output("#{name}: too few arguments given#{$/}").to_stderr
484
+ end
485
+ end
486
+
487
+ context "and one argument is given" do
488
+ let(:arg) { "foo" }
489
+ let(:args) { [arg] }
490
+
491
+ it "must print an error and return false" do
492
+ expect {
493
+ expect(subject.call(name,*args)).to be(false)
494
+ }.to output("#{name}: too few arguments given#{$/}").to_stderr
495
+ end
496
+ end
497
+
498
+ context "and two arguments are given" do
499
+ let(:arg1) { "foo" }
500
+ let(:arg2) { "bar" }
501
+ let(:args) { [arg1, arg2] }
502
+
503
+ it "must call the command method and return true" do
504
+ expect {
505
+ expect(subject.call(name,*args)).to be(true)
506
+ }.to output("#{name} called: arg1=#{arg1} arg2=#{arg2}#{$/}").to_stdout
507
+ end
508
+ end
509
+
510
+ context "and more than two arguments is given" do
511
+ let(:arg1) { "foo" }
512
+ let(:arg2) { "bar" }
513
+ let(:arg3) { "baz" }
514
+ let(:args) { [arg1, arg2, arg3] }
515
+
516
+ it "must print an error and return false" do
517
+ expect {
518
+ expect(subject.call(name,*args)).to be(false)
519
+ }.to output("#{name}: too many arguments given#{$/}").to_stderr
520
+ end
521
+ end
522
+ end
523
+
524
+ context "and the command accepts an optional argument" do
525
+ module TestCommandShell
526
+ class ShellWithCommandWithOptionalArg < Ronin::Core::CLI::CommandShell
527
+ shell_name 'test'
528
+
529
+ command :cmd_with_opt_arg, usage: '[ARG]',
530
+ summary: 'Example command with optional arg'
531
+ def cmd_with_opt_arg(arg=nil)
532
+ puts "#{__method__} called: arg=#{arg}"
533
+ end
534
+ end
535
+ end
536
+
537
+ let(:shell_class) do
538
+ TestCommandShell::ShellWithCommandWithOptionalArg
539
+ end
540
+ let(:name) { 'cmd_with_opt_arg' }
541
+
542
+ context "and no arguments are given" do
543
+ let(:args) { [] }
544
+
545
+ it "must call the command mehtod and return true" do
546
+ expect {
547
+ expect(subject.call(name,*args)).to be(true)
548
+ }.to output("#{name} called: arg=#{$/}").to_stdout
549
+ end
550
+ end
551
+
552
+ context "and one argument is given" do
553
+ let(:arg) { "foo" }
554
+ let(:args) { [arg] }
555
+
556
+ it "must call the command mehtod with the argument and return true" do
557
+ expect {
558
+ expect(subject.call(name,*args)).to be(true)
559
+ }.to output("#{name} called: arg=#{arg}#{$/}").to_stdout
560
+ end
561
+ end
562
+
563
+ context "and more than one argument is given" do
564
+ let(:arg1) { "foo" }
565
+ let(:arg2) { "bar" }
566
+ let(:args) { [arg1, arg2] }
567
+
568
+ it "must print an error and return false" do
569
+ expect {
570
+ expect(subject.call(name,*args)).to be(false)
571
+ }.to output("#{name}: too many arguments given#{$/}").to_stderr
572
+ end
573
+ end
574
+ end
575
+
576
+ context "and the command accepts an argument and an optional argument" do
577
+ module TestCommandShell
578
+ class ShellWithCommandWithArgAndOptionalArg < Ronin::Core::CLI::CommandShell
579
+ shell_name 'test'
580
+
581
+ command :cmd_with_arg_and_opt_arg, usage: 'ARG1 [ARG2]',
582
+ summary: 'Example command with arg and optional arg'
583
+ def cmd_with_arg_and_opt_arg(arg1,arg2=nil)
584
+ puts "#{__method__} called: arg1=#{arg1} arg2=#{arg2}"
585
+ end
586
+ end
587
+ end
588
+
589
+ let(:shell_class) do
590
+ TestCommandShell::ShellWithCommandWithArgAndOptionalArg
591
+ end
592
+ let(:name) { 'cmd_with_arg_and_opt_arg' }
593
+
594
+ context "and no arguments are given" do
595
+ let(:args) { [] }
596
+
597
+ it "must print an error and return false" do
598
+ expect {
599
+ expect(subject.call(name,*args)).to be(false)
600
+ }.to output("#{name}: too few arguments given#{$/}").to_stderr
601
+ end
602
+ end
603
+
604
+ context "and one argument is given" do
605
+ let(:arg) { "foo" }
606
+ let(:args) { [arg] }
607
+
608
+ it "must call the command mehtod with the argument and return true" do
609
+ expect {
610
+ expect(subject.call(name,*args)).to be(true)
611
+ }.to output("#{name} called: arg1=#{arg} arg2=#{$/}").to_stdout
612
+ end
613
+ end
614
+
615
+ context "and two arguments are given" do
616
+ let(:arg1) { "foo" }
617
+ let(:arg2) { "bar" }
618
+ let(:args) { [arg1, arg2] }
619
+
620
+ it "must call the command mehtod with the argument and return true" do
621
+ expect {
622
+ expect(subject.call(name,*args)).to be(true)
623
+ }.to output("#{name} called: arg1=#{arg1} arg2=#{arg2}#{$/}").to_stdout
624
+ end
625
+ end
626
+
627
+ context "and more than two arguments are given" do
628
+ let(:arg1) { "foo" }
629
+ let(:arg2) { "bar" }
630
+ let(:arg3) { "baz" }
631
+ let(:args) { [arg1, arg2, arg3] }
632
+
633
+ it "must print an error and return false" do
634
+ expect {
635
+ expect(subject.call(name,*args)).to be(false)
636
+ }.to output("#{name}: too many arguments given#{$/}").to_stderr
637
+ end
638
+ end
639
+ end
640
+
641
+ context "and the command accepts a splat of arguments" do
642
+ module TestCommandShell
643
+ class ShellWithCommandWithSplatArgs < Ronin::Core::CLI::CommandShell
644
+ shell_name 'test'
645
+
646
+ command :cmd_with_splat_args, usage: '[ARGS...]',
647
+ summary: 'Example command with splat args'
648
+ def cmd_with_splat_args(*args)
649
+ puts "#{__method__} called: args=#{args.join(' ')}"
650
+ end
651
+ end
652
+ end
653
+
654
+ let(:shell_class) do
655
+ TestCommandShell::ShellWithCommandWithSplatArgs
656
+ end
657
+ let(:name) { 'cmd_with_splat_args' }
658
+
659
+ context "and no arguments are given" do
660
+ let(:args) { [] }
661
+
662
+ it "must call the command mehtod with no arguments and return true" do
663
+ expect {
664
+ expect(subject.call(name,*args)).to be(true)
665
+ }.to output("#{name} called: args=#{$/}").to_stdout
666
+ end
667
+ end
668
+
669
+ context "and one argument is given" do
670
+ let(:arg) { "foo" }
671
+ let(:args) { [arg] }
672
+
673
+ it "must call the command mehtod with the argument and return true" do
674
+ expect {
675
+ expect(subject.call(name,*args)).to be(true)
676
+ }.to output("#{name} called: args=#{arg}#{$/}").to_stdout
677
+ end
678
+ end
679
+
680
+ context "and multiple arguments are given" do
681
+ let(:arg1) { "foo" }
682
+ let(:arg2) { "bar" }
683
+ let(:args) { [arg1, arg2] }
684
+
685
+ it "must call the command mehtod with the argument and return true" do
686
+ expect {
687
+ expect(subject.call(name,*args)).to be(true)
688
+ }.to output("#{name} called: args=#{arg1} #{arg2}#{$/}").to_stdout
689
+ end
690
+ end
691
+ end
692
+
693
+ context "and the command accepts an argument and splat arguments" do
694
+ module TestCommandShell
695
+ class ShellWithCommandWithArgAndSplatArgs < Ronin::Core::CLI::CommandShell
696
+ shell_name 'test'
697
+
698
+ command :cmd_with_arg_and_splat_args, usage: 'ARG [ARGS...]',
699
+ summary: 'Example command with arg and splat args'
700
+ def cmd_with_arg_and_splat_args(arg,*args)
701
+ puts "#{__method__} called: arg=#{arg} args=#{args.join(' ')}"
702
+ end
703
+ end
704
+ end
705
+
706
+ let(:shell_class) do
707
+ TestCommandShell::ShellWithCommandWithArgAndSplatArgs
708
+ end
709
+ let(:name) { 'cmd_with_arg_and_splat_args' }
710
+
711
+ context "and no arguments are given" do
712
+ let(:args) { [] }
713
+
714
+ it "must print an error and return false" do
715
+ expect {
716
+ expect(subject.call(name,*args)).to be(false)
717
+ }.to output("#{name}: too few arguments given#{$/}").to_stderr
718
+ end
719
+ end
720
+
721
+ context "and one argument is given" do
722
+ let(:arg) { "foo" }
723
+ let(:args) { [arg] }
724
+
725
+ it "must call the command mehtod with the argument and return true" do
726
+ expect {
727
+ expect(subject.call(name,*args)).to be(true)
728
+ }.to output("#{name} called: arg=#{arg} args=#{$/}").to_stdout
729
+ end
730
+ end
731
+
732
+ context "and two arguments are given" do
733
+ let(:arg1) { "foo" }
734
+ let(:arg2) { "bar" }
735
+ let(:args) { [arg1, arg2] }
736
+
737
+ it "must call the command mehtod with the argument and return true" do
738
+ expect {
739
+ expect(subject.call(name,*args)).to be(true)
740
+ }.to output("#{name} called: arg=#{arg1} args=#{arg2}#{$/}").to_stdout
741
+ end
742
+ end
743
+
744
+ context "and more than two arguments are given" do
745
+ let(:arg1) { "foo" }
746
+ let(:arg2) { "bar" }
747
+ let(:arg3) { "baz" }
748
+ let(:args) { [arg1, arg2, arg3] }
749
+
750
+ it "must call the command mehtod with the argument and return true" do
751
+ expect {
752
+ expect(subject.call(name,*args)).to be(true)
753
+ }.to output("#{name} called: arg=#{arg1} args=#{arg2} #{arg3}#{$/}").to_stdout
754
+ end
755
+ end
756
+ end
757
+
758
+ context "and the command accepts an optional argument and splat arguments" do
759
+ module TestCommandShell
760
+ class ShellWithCommandWithOptionalArgAndSplatArgs < Ronin::Core::CLI::CommandShell
761
+ shell_name 'test'
762
+
763
+ command :cmd_with_opt_arg_and_splat_args, usage: '[ARG] [ARGS...]',
764
+ summary: 'Example command with arg and splat args'
765
+ def cmd_with_opt_arg_and_splat_args(arg=nil,*args)
766
+ puts "#{__method__} called: arg=#{arg} args=#{args.join(' ')}"
767
+ end
768
+ end
769
+ end
770
+
771
+ let(:shell_class) do
772
+ TestCommandShell::ShellWithCommandWithOptionalArgAndSplatArgs
773
+ end
774
+ let(:name) { 'cmd_with_opt_arg_and_splat_args' }
775
+
776
+ context "and no arguments are given" do
777
+ let(:args) { [] }
778
+
779
+ it "must call the command method with no arguments and return true" do
780
+ expect {
781
+ expect(subject.call(name,*args)).to be(true)
782
+ }.to output("#{name} called: arg= args=#{$/}").to_stdout
783
+ end
784
+ end
785
+
786
+ context "and one argument is given" do
787
+ let(:arg) { "foo" }
788
+ let(:args) { [arg] }
789
+
790
+ it "must call the command mehtod with the argument and return true" do
791
+ expect {
792
+ expect(subject.call(name,*args)).to be(true)
793
+ }.to output("#{name} called: arg=#{arg} args=#{$/}").to_stdout
794
+ end
795
+ end
796
+
797
+ context "and two arguments are given" do
798
+ let(:arg1) { "foo" }
799
+ let(:arg2) { "bar" }
800
+ let(:args) { [arg1, arg2] }
801
+
802
+ it "must call the command mehtod with the argument and return true" do
803
+ expect {
804
+ expect(subject.call(name,*args)).to be(true)
805
+ }.to output("#{name} called: arg=#{arg1} args=#{arg2}#{$/}").to_stdout
806
+ end
807
+ end
808
+
809
+ context "and more than two arguments are given" do
810
+ let(:arg1) { "foo" }
811
+ let(:arg2) { "bar" }
812
+ let(:arg3) { "baz" }
813
+ let(:args) { [arg1, arg2, arg3] }
814
+
815
+ it "must call the command mehtod with the argument and return true" do
816
+ expect {
817
+ expect(subject.call(name,*args)).to be(true)
818
+ }.to output("#{name} called: arg=#{arg1} args=#{arg2} #{arg3}#{$/}").to_stdout
819
+ end
820
+ end
821
+ end
822
+
823
+ context "but the command method raises an exception" do
824
+ module TestCommandShell
825
+ class ShellWithCommandThatRaisesAnException < Ronin::Core::CLI::CommandShell
826
+
827
+ command 'cmd', summary: 'Test command'
828
+ def cmd
829
+ raise("error!")
830
+ end
831
+
832
+ end
833
+ end
834
+
835
+ let(:shell_class) do
836
+ TestCommandShell::ShellWithCommandThatRaisesAnException
837
+ end
838
+ let(:name) { 'cmd' }
839
+
840
+ it "must print an error message and return false" do
841
+ expect {
842
+ expect(subject.call(name)).to be(false)
843
+ }.to output(/an unhandled exception occurred in the #{name} command/).to_stderr
844
+ end
845
+ end
846
+ end
847
+
848
+ context "when the command does not exist" do
849
+ let(:name) { "does_not_exist" }
850
+
851
+ it "must print an error message and return false" do
852
+ expect {
853
+ expect(subject.call(name)).to be(false)
854
+ }.to output("unknown command: #{name}#{$/}").to_stderr
855
+ end
856
+ end
857
+
858
+ context "when the command was defined but no method is defined" do
859
+ module TestCommandShell
860
+ class ShellWithCommandButNoMethod < Ronin::Core::CLI::CommandShell
861
+
862
+ command 'cmd', summary: 'Test command'
863
+
864
+ end
865
+ end
866
+
867
+ let(:shell_class) do
868
+ TestCommandShell::ShellWithCommandButNoMethod
869
+ end
870
+ let(:name) { 'cmd' }
871
+
872
+ it "must raise NotImplementedError" do
873
+ expect {
874
+ subject.call(name)
875
+ }.to raise_error(NotImplementedError,"#{shell_class}##{name} was not defined for the #{name.inspect} command")
876
+ end
877
+ end
878
+ end
879
+
880
+ describe "#command_missing" do
881
+ let(:name) { "foo" }
882
+
883
+ it "must print an error message and return false" do
884
+ expect {
885
+ expect(subject.command_missing(name)).to be(false)
886
+ }.to output("unknown command: #{name}#{$/}").to_stderr
887
+ end
888
+ end
889
+
890
+ describe "#command_not_found" do
891
+ let(:name) { "foo" }
892
+
893
+ it "must print an error message and return false" do
894
+ expect {
895
+ subject.command_not_found(name)
896
+ }.to output("unknown command: #{name}#{$/}").to_stderr
897
+ end
898
+ end
899
+
900
+ describe "#help" do
901
+ module TestCommandShell
902
+ class ShellWithCommandsWithoutUsages < Ronin::Core::CLI::CommandShell
903
+ shell_name 'test'
904
+
905
+ command :foo, summary: 'Foo command'
906
+ def foo
907
+ end
908
+
909
+ command :bar, summary: 'Bar command'
910
+ def bar
911
+ end
912
+ end
913
+
914
+ class ShellWithCommandsWithCustomUsages < Ronin::Core::CLI::CommandShell
915
+ shell_name 'test'
916
+
917
+ command :foo, usage: 'ARG',
918
+ summary: 'Foo command'
919
+ def foo(arg)
920
+ end
921
+
922
+ command :bar, usage: 'ARG1 [ARG2]',
923
+ summary: 'Bar command'
924
+ def bar(arg1,arg2=nil)
925
+ end
926
+ end
927
+ end
928
+
929
+ context "when called with no arguments" do
930
+ context "but the shell has no commands" do
931
+ let(:shell_class) do
932
+ TestCommandShell::ShellWithNoCommands
933
+ end
934
+
935
+ it "must list the help command" do
936
+ expect {
937
+ subject.help
938
+ }.to output(
939
+ [
940
+ " help [COMMAND]\tPrints the list of commands or additional help",
941
+ ''
942
+ ].join($/)
943
+ ).to_stdout
944
+ end
945
+ end
946
+
947
+ context "but the commands do not have usage strings" do
948
+ let(:shell_class) do
949
+ TestCommandShell::ShellWithCommandsWithoutUsages
950
+ end
951
+
952
+ it "must print the command names and summaries in two columns" do
953
+ expect {
954
+ subject.help
955
+ }.to output(
956
+ [
957
+ " help [COMMAND]\tPrints the list of commands or additional help",
958
+ " foo \tFoo command",
959
+ " bar \tBar command",
960
+ ''
961
+ ].join($/)
962
+ ).to_stdout
963
+ end
964
+ end
965
+
966
+ context "when the commands defines custom usages" do
967
+ let(:shell_class) do
968
+ TestCommandShell::ShellWithCommandsWithCustomUsages
969
+ end
970
+
971
+ it "must print the custom usages next to the command names with extra right-padding" do
972
+ expect {
973
+ subject.help
974
+ }.to output(
975
+ [
976
+ " help [COMMAND] \tPrints the list of commands or additional help",
977
+ " foo ARG \tFoo command",
978
+ " bar ARG1 [ARG2]\tBar command",
979
+ ''
980
+ ].join($/)
981
+ ).to_stdout
982
+ end
983
+ end
984
+ end
985
+
986
+ context "when called with a command name" do
987
+ let(:command) { 'bar' }
988
+
989
+ context "but the command does not have a usage string" do
990
+ context "nor does the command have any additional help output" do
991
+ let(:shell_class) do
992
+ TestCommandShell::ShellWithCommandsWithoutUsages
993
+ end
994
+
995
+ it "must print the command's name and summary" do
996
+ expect {
997
+ subject.help(command)
998
+ }.to output(
999
+ [
1000
+ "usage: bar",
1001
+ '',
1002
+ 'Bar command',
1003
+ ''
1004
+ ].join($/)
1005
+ ).to_stdout
1006
+ end
1007
+ end
1008
+
1009
+ context "and the command has additional help output" do
1010
+ module TestCommandShell
1011
+ class ShellWithCommandsWithNoUsagesButWithHelp < Ronin::Core::CLI::CommandShell
1012
+ shell_name 'test'
1013
+
1014
+ command :foo, summary: 'Foo command',
1015
+ help: 'The foo command that does things.'
1016
+ def foo
1017
+ end
1018
+
1019
+ command :bar, summary: 'Bar command',
1020
+ help: 'The bar command that does things.'
1021
+ def bar
1022
+ end
1023
+ end
1024
+ end
1025
+
1026
+ let(:shell_class) do
1027
+ TestCommandShell::ShellWithCommandsWithNoUsagesButWithHelp
1028
+ end
1029
+
1030
+ it "must print the command name and additional help output" do
1031
+ expect {
1032
+ subject.help(command)
1033
+ }.to output(
1034
+ [
1035
+ "usage: bar",
1036
+ '',
1037
+ 'The bar command that does things.',
1038
+ ''
1039
+ ].join($/)
1040
+ ).to_stdout
1041
+ end
1042
+ end
1043
+ end
1044
+
1045
+ context "and the command does have a usage string" do
1046
+ context "but the command does not have any additional help output" do
1047
+ let(:shell_class) do
1048
+ TestCommandShell::ShellWithCommandsWithCustomUsages
1049
+ end
1050
+
1051
+ it "must print the command name, usage, and summary" do
1052
+ expect {
1053
+ subject.help(command)
1054
+ }.to output(
1055
+ [
1056
+ "usage: bar ARG1 [ARG2]",
1057
+ '',
1058
+ 'Bar command',
1059
+ ''
1060
+ ].join($/)
1061
+ ).to_stdout
1062
+ end
1063
+ end
1064
+
1065
+ context "and the command does have any additional help output" do
1066
+ module TestCommandShell
1067
+ class ShellWithCommandsWithUsagesButWithHelp < Ronin::Core::CLI::CommandShell
1068
+ shell_name 'test'
1069
+
1070
+ command :foo, usage: 'ARG',
1071
+ summary: 'Foo command',
1072
+ help: 'The foo command that does things.'
1073
+ def foo(arg)
1074
+ end
1075
+
1076
+ command :bar, usage: 'ARG1 [ARG2]',
1077
+ summary: 'Bar command',
1078
+ help: 'The bar command that does things.'
1079
+ def bar(arg1,arg2=nil)
1080
+ end
1081
+ end
1082
+ end
1083
+
1084
+ let(:shell_class) do
1085
+ TestCommandShell::ShellWithCommandsWithUsagesButWithHelp
1086
+ end
1087
+
1088
+ it "must print the command name, usage, and additional help output" do
1089
+ expect {
1090
+ subject.help(command)
1091
+ }.to output(
1092
+ [
1093
+ "usage: bar ARG1 [ARG2]",
1094
+ '',
1095
+ 'The bar command that does things.',
1096
+ ''
1097
+ ].join($/)
1098
+ ).to_stdout
1099
+ end
1100
+ end
1101
+ end
1102
+
1103
+ context "but the command name isn't a defined command" do
1104
+ let(:command) { 'does_not_exist' }
1105
+
1106
+ it "must print an 'unknown command' error" do
1107
+ expect {
1108
+ subject.help(command)
1109
+ }.to output("help: unknown command: #{command}#{$/}").to_stderr
1110
+ end
1111
+ end
1112
+ end
1113
+ end
1114
+ end