command_mapper-gen 0.1.0.pre1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (55) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/ruby.yml +27 -0
  3. data/.gitignore +10 -0
  4. data/.rspec +1 -0
  5. data/.yardopts +1 -0
  6. data/ChangeLog.md +20 -0
  7. data/Gemfile +17 -0
  8. data/LICENSE.txt +20 -0
  9. data/README.md +145 -0
  10. data/Rakefile +15 -0
  11. data/bin/command_mapper-gen +7 -0
  12. data/commnad_mapper-gen.gemspec +61 -0
  13. data/examples/grep.rb +63 -0
  14. data/gemspec.yml +26 -0
  15. data/lib/command_mapper/gen/arg.rb +43 -0
  16. data/lib/command_mapper/gen/argument.rb +53 -0
  17. data/lib/command_mapper/gen/cli.rb +233 -0
  18. data/lib/command_mapper/gen/command.rb +202 -0
  19. data/lib/command_mapper/gen/exceptions.rb +9 -0
  20. data/lib/command_mapper/gen/option.rb +66 -0
  21. data/lib/command_mapper/gen/option_value.rb +23 -0
  22. data/lib/command_mapper/gen/parsers/common.rb +49 -0
  23. data/lib/command_mapper/gen/parsers/help.rb +351 -0
  24. data/lib/command_mapper/gen/parsers/man.rb +80 -0
  25. data/lib/command_mapper/gen/parsers/options.rb +127 -0
  26. data/lib/command_mapper/gen/parsers/usage.rb +141 -0
  27. data/lib/command_mapper/gen/parsers.rb +2 -0
  28. data/lib/command_mapper/gen/task.rb +90 -0
  29. data/lib/command_mapper/gen/types/enum.rb +30 -0
  30. data/lib/command_mapper/gen/types/key_value.rb +34 -0
  31. data/lib/command_mapper/gen/types/list.rb +34 -0
  32. data/lib/command_mapper/gen/types/map.rb +36 -0
  33. data/lib/command_mapper/gen/types/num.rb +18 -0
  34. data/lib/command_mapper/gen/types/str.rb +48 -0
  35. data/lib/command_mapper/gen/types.rb +6 -0
  36. data/lib/command_mapper/gen/version.rb +6 -0
  37. data/lib/command_mapper/gen.rb +2 -0
  38. data/spec/argument_spec.rb +92 -0
  39. data/spec/cli_spec.rb +269 -0
  40. data/spec/command_spec.rb +316 -0
  41. data/spec/option_spec.rb +85 -0
  42. data/spec/option_value_spec.rb +20 -0
  43. data/spec/parsers/common_spec.rb +616 -0
  44. data/spec/parsers/help_spec.rb +612 -0
  45. data/spec/parsers/man_spec.rb +158 -0
  46. data/spec/parsers/options_spec.rb +802 -0
  47. data/spec/parsers/usage_spec.rb +1175 -0
  48. data/spec/spec_helper.rb +6 -0
  49. data/spec/task_spec.rb +69 -0
  50. data/spec/types/enum_spec.rb +45 -0
  51. data/spec/types/key_value_spec.rb +36 -0
  52. data/spec/types/list_spec.rb +36 -0
  53. data/spec/types/map_spec.rb +48 -0
  54. data/spec/types/str_spec.rb +70 -0
  55. metadata +133 -0
@@ -0,0 +1,612 @@
1
+ require 'spec_helper'
2
+ require 'command_mapper/gen/parsers/help'
3
+
4
+ describe CommandMapper::Gen::Parsers::Help do
5
+ let(:command_name) { 'yes' }
6
+ let(:command) { CommandMapper::Gen::Command.new(command_name) }
7
+
8
+ subject { described_class.new(command) }
9
+
10
+ describe "#initialize" do
11
+ it "must set #command" do
12
+ expect(subject.command).to be(command)
13
+ end
14
+
15
+ context "when a block is given" do
16
+ let(:block) do
17
+ proc { |line,error| }
18
+ end
19
+
20
+ subject { described_class.new(command,&block) }
21
+
22
+ it "must set #parser_error_callback" do
23
+ expect(subject.parser_error_callback).to eq(block)
24
+ end
25
+ end
26
+ end
27
+
28
+ describe "#parse_usage" do
29
+ let(:usage) { "#{command_name} ARG1 ARG2" }
30
+
31
+ before { subject.parse_usage(usage) }
32
+
33
+ it "must parse and add the argument to the command" do
34
+ expect(command.arguments.keys).to eq([:arg1, :arg2])
35
+ expect(command.arguments[:arg1].name).to eq(:arg1)
36
+ expect(command.arguments[:arg2].name).to eq(:arg2)
37
+ end
38
+
39
+ context "when the argument is optional" do
40
+ let(:usage) { "#{command_name} ARG1 [ARG2]" }
41
+
42
+ before { subject.parse_usage(usage) }
43
+
44
+ it "must set the Argument#required to false" do
45
+ expect(command.arguments[:arg2].required).to be(false)
46
+ end
47
+ end
48
+
49
+ context "when the argument repeats" do
50
+ let(:usage) { "#{command_name} ARG1 ARG2..." }
51
+
52
+ before { subject.parse_usage(usage) }
53
+
54
+ it "must set the Argument#required to false" do
55
+ expect(command.arguments[:arg2].repeats).to be(true)
56
+ end
57
+ end
58
+
59
+ context "when an optional argument group repeats" do
60
+ let(:usage) { "#{command_name} [ARG1 ARG2]..." }
61
+
62
+ before { subject.parse_usage(usage) }
63
+
64
+ it "must set the Argument#required to false" do
65
+ expect(command.arguments[:arg1].required).to be(false)
66
+ expect(command.arguments[:arg1].repeats).to be(true)
67
+
68
+ expect(command.arguments[:arg2].required).to be(false)
69
+ expect(command.arguments[:arg2].repeats).to be(true)
70
+ end
71
+ end
72
+
73
+ context "when the argument is named OPTS" do
74
+ let(:usage) { "#{command_name} [OPTS] ARG..." }
75
+
76
+ before { subject.parse_usage(usage) }
77
+
78
+ it "must ignore it" do
79
+ expect(command.arguments.keys).to eq([:arg])
80
+ end
81
+ end
82
+
83
+ context "when the argument is named OPTION" do
84
+ let(:usage) { "#{command_name} [OPTION] ARG..." }
85
+
86
+ before { subject.parse_usage(usage) }
87
+
88
+ it "must ignore it" do
89
+ expect(command.arguments.keys).to eq([:arg])
90
+ end
91
+ end
92
+
93
+ context "when the argument is named OPTIONS" do
94
+ let(:usage) { "#{command_name} [OPTIONS] ARG..." }
95
+
96
+ before { subject.parse_usage(usage) }
97
+
98
+ it "must ignore it" do
99
+ expect(command.arguments.keys).to eq([:arg])
100
+ end
101
+ end
102
+
103
+ context "when the usage cannot be parsed" do
104
+ let(:usage) { " " }
105
+
106
+ it "must return nil" do
107
+ expect(subject.parse_usage(usage)).to be(nil)
108
+ end
109
+
110
+ context "when a parser error callback is set" do
111
+ it "must pass the text and parser error to the callback" do
112
+ yielded_line = nil
113
+ yielded_parser_error = nil
114
+
115
+ subject = described_class.new(command) do |line,parser_error|
116
+ yielded_line = line
117
+ yielded_parser_error = parser_error
118
+ end
119
+
120
+ expect(subject.parse_usage(usage)).to be(nil)
121
+
122
+ expect(yielded_line).to eq(usage)
123
+ expect(yielded_parser_error).to be_kind_of(Parslet::ParseFailed)
124
+ end
125
+ end
126
+ end
127
+ end
128
+
129
+ describe "#parse_option_line" do
130
+ let(:long_flag) { "--option" }
131
+ let(:line) { " #{long_flag} Bla bla bla" }
132
+
133
+ before { subject.parse_option_line(line) }
134
+
135
+ it "must parse and add the option to the command" do
136
+ expect(command.options.keys).to eq([long_flag])
137
+ expect(command.options[long_flag].flag).to eq(long_flag)
138
+ end
139
+
140
+ context "when the option line also includes a short flag" do
141
+ let(:short_flag) { "-o" }
142
+ let(:line) { " #{short_flag}, #{long_flag} Bla bla bla" }
143
+
144
+ before { subject.parse_option_line(line) }
145
+
146
+ it "must parse and add the option to the command" do
147
+ expect(command.options.keys).to eq([long_flag])
148
+ end
149
+ end
150
+
151
+ context "when the option line includes a value" do
152
+ let(:line) { " #{long_flag} VALUE Bla bla bla" }
153
+
154
+ before { subject.parse_option_line(line) }
155
+
156
+ it "must set the Option#value" do
157
+ expect(command.options[long_flag].value).to_not be_nil
158
+ end
159
+
160
+ it "must set the OptionValue#required to true" do
161
+ expect(command.options[long_flag].value.required).to be(true)
162
+ end
163
+
164
+ context "when the option value is optional" do
165
+ let(:line) { " #{long_flag} [VALUE] Bla bla bla" }
166
+
167
+ before { subject.parse_option_line(line) }
168
+
169
+ it "must set the OptionValue#required to false" do
170
+ expect(command.options[long_flag].value.required).to be(false)
171
+ end
172
+ end
173
+
174
+ context "when the option flag and value are joined by a '='" do
175
+ let(:line) { " #{long_flag}=VALUE Bla bla bla" }
176
+
177
+ before { subject.parse_option_line(line) }
178
+
179
+ it "must set Option#equals to true" do
180
+ expect(command.options[long_flag].equals).to be(true)
181
+ end
182
+ end
183
+
184
+ context "when the option value is named NUM" do
185
+ let(:line) { " #{long_flag} NUM Bla bla bla" }
186
+
187
+ before { subject.parse_option_line(line) }
188
+
189
+ it "must set Option#value's type to a Num" do
190
+ expect(command.options[long_flag].value.type).to be_kind_of(CommandMapper::Gen::Types::Num)
191
+ end
192
+ end
193
+
194
+ context "and when the value contains literal values" do
195
+ let(:value1) { :foo }
196
+ let(:value2) { :bar }
197
+ let(:value3) { :baz }
198
+ let(:line) { " #{long_flag}=[#{value1}|#{value2}|#{value3}] Bla bla bla" }
199
+
200
+ let(:values) { [value1, value2, value3] }
201
+
202
+ before { subject.parse_option_line(line) }
203
+
204
+ it "must set Option#value's #type to an Enum of the values" do
205
+ expect(command.options[long_flag].value.type).to be_kind_of(CommandMapper::Gen::Types::Enum)
206
+ expect(command.options[long_flag].value.type.values).to eq(values)
207
+ end
208
+
209
+ context "when the literal values are 'YES' and 'NO'" do
210
+ let(:line) { " #{long_flag}=[YES|NO] Bla bla bla" }
211
+
212
+ it "must set Option#value's #type to a Map of {true=>'YES', false=>'NO'}" do
213
+ expect(command.options[long_flag].value.type).to be_kind_of(CommandMapper::Gen::Types::Map)
214
+ expect(command.options[long_flag].value.type.map).to eq(
215
+ {true => 'YES', false => 'NO'}
216
+ )
217
+ end
218
+ end
219
+
220
+ context "when the literal values are 'Yes' and 'No'" do
221
+ let(:line) { " #{long_flag}=[Yes|No] Bla bla bla" }
222
+
223
+ it "must set Option#value's #type to a Map of {true=>'Yes', false=>'No'}" do
224
+ expect(command.options[long_flag].value.type).to be_kind_of(CommandMapper::Gen::Types::Map)
225
+ expect(command.options[long_flag].value.type.map).to eq(
226
+ {true => 'Yes', false => 'No'}
227
+ )
228
+ end
229
+ end
230
+
231
+ context "when the literal values are 'yes' and 'no'" do
232
+ let(:line) { " #{long_flag}=[yes|no] Bla bla bla" }
233
+
234
+ it "must set Option#value's #type to a Map of {true=>'yes', false=>'no'}" do
235
+ expect(command.options[long_flag].value.type).to be_kind_of(CommandMapper::Gen::Types::Map)
236
+ expect(command.options[long_flag].value.type.map).to eq(
237
+ {true => 'yes', false => 'no'}
238
+ )
239
+ end
240
+ end
241
+
242
+ context "when the literal values are 'Y' and 'N'" do
243
+ let(:line) { " #{long_flag}=[Y|N] Bla bla bla" }
244
+
245
+ it "must set Option#value's #type to a Map of {true=>'Y', false=>'N'}" do
246
+ expect(command.options[long_flag].value.type).to be_kind_of(CommandMapper::Gen::Types::Map)
247
+ expect(command.options[long_flag].value.type.map).to eq(
248
+ {true => 'Y', false => 'N'}
249
+ )
250
+ end
251
+ end
252
+
253
+ context "when the literal values are 'y' and 'n'" do
254
+ let(:line) { " #{long_flag}=[y|n] Bla bla bla" }
255
+
256
+ it "must set Option#value's #type to a Map of {true=>'y', false=>'n'}" do
257
+ expect(command.options[long_flag].value.type).to be_kind_of(CommandMapper::Gen::Types::Map)
258
+ expect(command.options[long_flag].value.type.map).to eq(
259
+ {true => 'y', false => 'n'}
260
+ )
261
+ end
262
+ end
263
+
264
+ context "when the literal values are 'ENABLED' and 'DISABLED'" do
265
+ let(:line) { " #{long_flag}=[ENABLED|DISABLED] Bla bla bla" }
266
+
267
+ it "must set Option#value's #type to a Map of {true=>'ENABLED', false=>'DISABLED'}" do
268
+ expect(command.options[long_flag].value.type).to be_kind_of(CommandMapper::Gen::Types::Map)
269
+ expect(command.options[long_flag].value.type.map).to eq(
270
+ {true => 'ENABLED', false => 'DISABLED'}
271
+ )
272
+ end
273
+ end
274
+
275
+ context "when the literal values are 'Enabled' and 'Disabled'" do
276
+ let(:line) { " #{long_flag}=[Enabled|Disabled] Bla bla bla" }
277
+
278
+ it "must set Option#value's #type to a Map of {true=>'Enabled', false=>'Disabled'}" do
279
+ expect(command.options[long_flag].value.type).to be_kind_of(CommandMapper::Gen::Types::Map)
280
+ expect(command.options[long_flag].value.type.map).to eq(
281
+ {true => 'Enabled', false => 'Disabled'}
282
+ )
283
+ end
284
+ end
285
+
286
+ context "when the literal values are 'enabled' and 'disabled'" do
287
+ let(:line) { " #{long_flag}=[enabled|disabled] Bla bla bla" }
288
+
289
+ it "must set Option#value's #type to a Map of {true=>'enabled', false=>'disabled'}" do
290
+ expect(command.options[long_flag].value.type).to be_kind_of(CommandMapper::Gen::Types::Map)
291
+ expect(command.options[long_flag].value.type.map).to eq(
292
+ {true => 'enabled', false => 'disabled'}
293
+ )
294
+ end
295
+ end
296
+ end
297
+ end
298
+
299
+ context "when the option line cannot be parsed" do
300
+ let(:line) { " FOO BAR BAZ Bla bla bla" }
301
+
302
+ it "must return nil" do
303
+ expect(subject.parse_option_line(line)).to be(nil)
304
+ end
305
+
306
+ context "when an parser error callback is given" do
307
+ it "must pass the text and parser error to the callback" do
308
+ yielded_line = nil
309
+ yielded_parser_error = nil
310
+
311
+ subject = described_class.new(command) do |line,parser_error|
312
+ yielded_line = line
313
+ yielded_parser_error = parser_error
314
+ end
315
+
316
+ expect(subject.parse_option_line(line)).to be(nil)
317
+
318
+ expect(yielded_line).to eq(line)
319
+ expect(yielded_parser_error).to be_kind_of(Parslet::ParseFailed)
320
+ end
321
+ end
322
+ end
323
+ end
324
+
325
+ describe "#parse_subcommand" do
326
+ let(:subcommand) { 'bar' }
327
+ let(:line) { " #{subcommand} bla bla bla" }
328
+
329
+ context "when the line starts with a subcommand name" do
330
+ before { subject.parse_subcommand_line(line) }
331
+
332
+ it "must parse the subcommand and add it to the command" do
333
+ expect(command.subcommands.keys).to eq([subcommand])
334
+ expect(command.subcommands[subcommand].command_name).to eq(subcommand)
335
+ end
336
+ end
337
+
338
+ context "when the line cannot be parsed" do
339
+ let(:line) { " " }
340
+
341
+ it "must return nil" do
342
+ expect(subject.parse_subcommand_line(line)).to be(nil)
343
+ end
344
+ end
345
+ end
346
+
347
+ let(:output) do
348
+ <<OUTPUT
349
+ Usage: yes [STRING]...
350
+ or: yes OPTION
351
+ Repeatedly output a line with all specified STRING(s), or 'y'.
352
+
353
+ --help display this help and exit
354
+ --version output version information and exit
355
+
356
+ GNU coreutils online help: <https://www.gnu.org/software/coreutils/>
357
+ Full documentation <https://www.gnu.org/software/coreutils/yes>
358
+ or available locally via: info '(coreutils) yes invocation'
359
+ OUTPUT
360
+ end
361
+
362
+ let(:subcommands_output) do
363
+ <<OUTPUT
364
+ NAME:
365
+ runc - Open Container Initiative runtime
366
+
367
+ runc is a command line client for running applications packaged according to
368
+ the Open Container Initiative (OCI) format and is a compliant implementation of the
369
+ Open Container Initiative specification.
370
+
371
+ runc integrates well with existing process supervisors to provide a production
372
+ container runtime environment for applications. It can be used with your
373
+ existing process monitoring tools and the container will be spawned as a
374
+ direct child of the process supervisor.
375
+
376
+ Containers are configured using bundles. A bundle for a container is a directory
377
+ that includes a specification file named "config.json" and a root filesystem.
378
+ The root filesystem contains the contents of the container.
379
+
380
+ To start a new instance of a container:
381
+
382
+ # runc run [ -b bundle ] <container-id>
383
+
384
+ Where "<container-id>" is your name for the instance of the container that you
385
+ are starting. The name you provide for the container instance must be unique on
386
+ your host. Providing the bundle directory using "-b" is optional. The default
387
+ value for "bundle" is the current directory.
388
+
389
+ USAGE:
390
+ runc [global options] command [command options] [arguments...]
391
+
392
+ VERSION:
393
+ 1.0.2
394
+ commit: e15f155-dirty
395
+ spec: 1.0.2-dev
396
+ go: go1.16.6
397
+ libseccomp: 2.5.0
398
+
399
+ COMMANDS:
400
+ checkpoint checkpoint a running container
401
+ create create a container
402
+ delete delete any resources held by the container often used with detached container
403
+ events display container events such as OOM notifications, cpu, memory, and IO usage statistics
404
+ exec execute new process inside the container
405
+ init initialize the namespaces and launch the process (do not call it outside of runc)
406
+ kill kill sends the specified signal (default: SIGTERM) to the container's init process
407
+ list lists containers started by runc with the given root
408
+ pause pause suspends all processes inside the container
409
+ ps ps displays the processes running inside a container
410
+ restore restore a container from a previous checkpoint
411
+ resume resumes all processes that have been previously paused
412
+ run create and run a container
413
+ spec create a new specification file
414
+ start executes the user defined process in a created container
415
+ state output the state of a container
416
+ update update container resource constraints
417
+ help, h Shows a list of commands or help for one command
418
+
419
+ GLOBAL OPTIONS:
420
+ --debug enable debug output for logging
421
+ --log value set the log file path where internal debug information is written
422
+ --log-format value set the format used by logs ('text' (default), or 'json') (default: "text")
423
+ --root value root directory for storage of container state (this should be located in tmpfs) (default: "/run/user/1000/runc")
424
+ --criu value path to the criu binary used for checkpoint and restore (default: "criu")
425
+ --systemd-cgroup enable systemd cgroup support, expects cgroupsPath to be of form "slice:prefix:name" for e.g. "system.slice:runc:434234"
426
+ --rootless value ignore cgroup permission errors ('true', 'false', or 'auto') (default: "auto")
427
+ --help, -h show help
428
+ --version, -v print the version
429
+ OUTPUT
430
+ end
431
+
432
+ describe "#parse" do
433
+ before { subject.parse(output) }
434
+
435
+ it "must parse and populate options" do
436
+ expect(command.options.keys).to eq(%w[--help --version])
437
+ end
438
+
439
+ it "must parse and populate arguments" do
440
+ expect(command.arguments.keys).to eq([:string])
441
+ end
442
+
443
+ context "when the `--help` output includes subcommands" do
444
+ let(:command_name) { 'runc' }
445
+ let(:output) { subcommands_output }
446
+
447
+ it "must populate the command's subcommands" do
448
+ expect(command.subcommands.keys).to eq(
449
+ %w[
450
+ checkpoint create delete events exec init kill list pause ps restore
451
+ resume run spec start state update help
452
+ ]
453
+ )
454
+ end
455
+
456
+ it "must still parse and populate options" do
457
+ expect(command.options.keys).to eq(
458
+ %w[
459
+ --debug --log --log-format --root --criu --systemd-cgroup --rootless
460
+ --help --version
461
+ ]
462
+ )
463
+ end
464
+
465
+ it "must still parse and populate arguments" do
466
+ expect(command.arguments.keys).to eq([:command, :arguments])
467
+ end
468
+ end
469
+ end
470
+
471
+ describe "#print_parser_error" do
472
+ end
473
+
474
+ describe "#parse" do
475
+ before { subject.parse(output) }
476
+
477
+ it "must populate the command's options" do
478
+ expect(command.options.keys).to eq(%w[--help --version])
479
+ expect(command.options['--help']).to be_kind_of(CommandMapper::Gen::Option)
480
+ expect(command.options['--version']).to be_kind_of(CommandMapper::Gen::Option)
481
+ end
482
+
483
+ it "must populate the command's arguments" do
484
+ expect(command.arguments.keys).to eq([:string])
485
+ expect(command.arguments[:string]).to be_kind_of(CommandMapper::Gen::Argument)
486
+ end
487
+
488
+ context "when the output contains subcommands" do
489
+ let(:command_name) { 'runc' }
490
+
491
+ let(:output) { subcommands_output }
492
+
493
+ it "must populate the command's subcommands" do
494
+ expect(command.subcommands.keys).to eq(
495
+ %w[
496
+ checkpoint create delete events exec init kill list pause ps restore
497
+ resume run spec start state update help
498
+ ]
499
+ )
500
+ end
501
+ end
502
+
503
+ context "when the usage: command is on the following line" do
504
+ let(:command) do
505
+ CommandMapper::Gen::Command.new(
506
+ 'tag', CommandMapper::Gen::Command.new(
507
+ 'image', CommandMapper::Gen::Command.new(
508
+ 'podman'
509
+ )
510
+ )
511
+ )
512
+ end
513
+
514
+ let(:output) do
515
+ <<OUTPUT
516
+ Add an additional name to a local image
517
+
518
+ Description:
519
+ Adds one or more additional names to locally-stored image.
520
+
521
+ Usage:
522
+ podman image tag IMAGE TARGET_NAME [TARGET_NAME...]
523
+
524
+ Examples:
525
+ podman image tag 0e3bbc2 fedora:latest
526
+ podman image tag imageID:latest myNewImage:newTag
527
+ podman image tag httpd myregistryhost:5000/fedora/httpd:v2
528
+
529
+ OUTPUT
530
+ end
531
+
532
+ it "must parse the line after the usage:" do
533
+ expect(command.arguments.keys).to eq(
534
+ [
535
+ :image, :target_name
536
+ ]
537
+ )
538
+ end
539
+ end
540
+ end
541
+
542
+ describe ".parse" do
543
+ subject { described_class }
544
+
545
+ before { subject.parse(output,command) }
546
+
547
+ it "must parse the output and populate the command" do
548
+ expect(command.options.keys).to eq(%w[--help --version])
549
+ expect(command.options['--help']).to be_kind_of(CommandMapper::Gen::Option)
550
+ expect(command.options['--version']).to be_kind_of(CommandMapper::Gen::Option)
551
+
552
+ expect(command.arguments.keys).to eq([:string])
553
+ expect(command.arguments[:string]).to be_kind_of(CommandMapper::Gen::Argument)
554
+ end
555
+ end
556
+
557
+ describe ".run" do
558
+ subject { described_class }
559
+
560
+ it "must parse the output, populate the commamd, and return the command" do
561
+ parsed_command = subject.run(command)
562
+
563
+ expect(parsed_command).to be(command)
564
+
565
+ expect(parsed_command.options.keys).to eq(%w[--help --version])
566
+ expect(parsed_command.options['--help']).to be_kind_of(CommandMapper::Gen::Option)
567
+ expect(parsed_command.options['--version']).to be_kind_of(CommandMapper::Gen::Option)
568
+
569
+ expect(parsed_command.arguments.keys).to eq([:string])
570
+ expect(parsed_command.arguments[:string]).to be_kind_of(CommandMapper::Gen::Argument)
571
+ end
572
+
573
+ context "when the command is not installed" do
574
+ let(:command_name) { "foo" }
575
+
576
+ before do
577
+ allow(subject).to receive(:`).and_raise(Errno::ENOENT.new("foo"))
578
+ end
579
+
580
+ it do
581
+ expect {
582
+ subject.run(command)
583
+ }.to raise_error(CommandMapper::Gen::CommandNotInstalled,"command #{command_name.inspect} is not installed")
584
+ end
585
+ end
586
+
587
+ context "when there is no output from `--help`" do
588
+ let(:command_name) { "foo" }
589
+
590
+ before do
591
+ allow(subject).to receive(:`).with("#{command_name} --help 2>&1").and_return("")
592
+ end
593
+
594
+ it "must try executing -h" do
595
+ expect(subject).to receive(:`).with("#{command_name} -h 2>&1").and_return("")
596
+
597
+ subject.run(command)
598
+ end
599
+
600
+ context "but `-h` also produces no output" do
601
+ before do
602
+ allow(subject).to receive(:`).with("#{command_name} --help 2>&1").and_return("")
603
+ allow(subject).to receive(:`).with("#{command_name} -h 2>&1").and_return("")
604
+ end
605
+
606
+ it "must return nil" do
607
+ expect(subject.run(command)).to be(nil)
608
+ end
609
+ end
610
+ end
611
+ end
612
+ end