command_mapper-gen 0.1.0.pre1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.github/workflows/ruby.yml +27 -0
- data/.gitignore +10 -0
- data/.rspec +1 -0
- data/.yardopts +1 -0
- data/ChangeLog.md +20 -0
- data/Gemfile +17 -0
- data/LICENSE.txt +20 -0
- data/README.md +145 -0
- data/Rakefile +15 -0
- data/bin/command_mapper-gen +7 -0
- data/commnad_mapper-gen.gemspec +61 -0
- data/examples/grep.rb +63 -0
- data/gemspec.yml +26 -0
- data/lib/command_mapper/gen/arg.rb +43 -0
- data/lib/command_mapper/gen/argument.rb +53 -0
- data/lib/command_mapper/gen/cli.rb +233 -0
- data/lib/command_mapper/gen/command.rb +202 -0
- data/lib/command_mapper/gen/exceptions.rb +9 -0
- data/lib/command_mapper/gen/option.rb +66 -0
- data/lib/command_mapper/gen/option_value.rb +23 -0
- data/lib/command_mapper/gen/parsers/common.rb +49 -0
- data/lib/command_mapper/gen/parsers/help.rb +351 -0
- data/lib/command_mapper/gen/parsers/man.rb +80 -0
- data/lib/command_mapper/gen/parsers/options.rb +127 -0
- data/lib/command_mapper/gen/parsers/usage.rb +141 -0
- data/lib/command_mapper/gen/parsers.rb +2 -0
- data/lib/command_mapper/gen/task.rb +90 -0
- data/lib/command_mapper/gen/types/enum.rb +30 -0
- data/lib/command_mapper/gen/types/key_value.rb +34 -0
- data/lib/command_mapper/gen/types/list.rb +34 -0
- data/lib/command_mapper/gen/types/map.rb +36 -0
- data/lib/command_mapper/gen/types/num.rb +18 -0
- data/lib/command_mapper/gen/types/str.rb +48 -0
- data/lib/command_mapper/gen/types.rb +6 -0
- data/lib/command_mapper/gen/version.rb +6 -0
- data/lib/command_mapper/gen.rb +2 -0
- data/spec/argument_spec.rb +92 -0
- data/spec/cli_spec.rb +269 -0
- data/spec/command_spec.rb +316 -0
- data/spec/option_spec.rb +85 -0
- data/spec/option_value_spec.rb +20 -0
- data/spec/parsers/common_spec.rb +616 -0
- data/spec/parsers/help_spec.rb +612 -0
- data/spec/parsers/man_spec.rb +158 -0
- data/spec/parsers/options_spec.rb +802 -0
- data/spec/parsers/usage_spec.rb +1175 -0
- data/spec/spec_helper.rb +6 -0
- data/spec/task_spec.rb +69 -0
- data/spec/types/enum_spec.rb +45 -0
- data/spec/types/key_value_spec.rb +36 -0
- data/spec/types/list_spec.rb +36 -0
- data/spec/types/map_spec.rb +48 -0
- data/spec/types/str_spec.rb +70 -0
- 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
|