ronin-core 0.1.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
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