command_mapper 0.1.1 → 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.document +3 -0
- data/.github/workflows/ruby.yml +2 -1
- data/ChangeLog.md +32 -0
- data/Gemfile +2 -0
- data/LICENSE.txt +1 -1
- data/README.md +41 -8
- data/examples/grep.rb +62 -0
- data/lib/command_mapper/arg.rb +5 -0
- data/lib/command_mapper/argument.rb +6 -0
- data/lib/command_mapper/command.rb +209 -56
- data/lib/command_mapper/option.rb +50 -13
- data/lib/command_mapper/option_value.rb +22 -0
- data/lib/command_mapper/types/enum.rb +8 -0
- data/lib/command_mapper/types/hex.rb +16 -2
- data/lib/command_mapper/types/input_dir.rb +2 -0
- data/lib/command_mapper/types/input_file.rb +2 -0
- data/lib/command_mapper/types/input_path.rb +2 -0
- data/lib/command_mapper/types/key_value.rb +10 -0
- data/lib/command_mapper/types/key_value_list.rb +2 -0
- data/lib/command_mapper/types/list.rb +10 -0
- data/lib/command_mapper/types/map.rb +12 -1
- data/lib/command_mapper/types/num.rb +28 -1
- data/lib/command_mapper/types/str.rb +10 -1
- data/lib/command_mapper/types/type.rb +4 -0
- data/lib/command_mapper/version.rb +1 -1
- data/spec/commnad_spec.rb +345 -74
- data/spec/option_spec.rb +252 -1
- data/spec/option_value_spec.rb +28 -0
- data/spec/types/hex_spec.rb +59 -1
- data/spec/types/map_spec.rb +2 -2
- data/spec/types/num_spec.rb +93 -3
- metadata +4 -2
@@ -5,6 +5,11 @@ require 'command_mapper/option'
|
|
5
5
|
require 'shellwords'
|
6
6
|
|
7
7
|
module CommandMapper
|
8
|
+
#
|
9
|
+
# Base class for all mapped commands.
|
10
|
+
#
|
11
|
+
# @api public
|
12
|
+
#
|
8
13
|
class Command
|
9
14
|
|
10
15
|
include Types
|
@@ -24,16 +29,6 @@ module CommandMapper
|
|
24
29
|
# @return [Hash{String => String}]
|
25
30
|
attr_reader :command_env
|
26
31
|
|
27
|
-
# The option values to execute the command with.
|
28
|
-
#
|
29
|
-
# @return [Hash{String => Object}]
|
30
|
-
attr_reader :command_options
|
31
|
-
|
32
|
-
# The argument values to execute the command with.
|
33
|
-
#
|
34
|
-
# @return [Hash{String => Object}]
|
35
|
-
attr_reader :command_arguments
|
36
|
-
|
37
32
|
# The subcommand's options and arguments.
|
38
33
|
#
|
39
34
|
# @return [Command, nil]
|
@@ -51,7 +46,7 @@ module CommandMapper
|
|
51
46
|
# @param [String, nil] command_path
|
52
47
|
# Overrides the command with a custom path to the command.
|
53
48
|
#
|
54
|
-
# @param [Hash{String => String}]
|
49
|
+
# @param [Hash{String => String}] command_env
|
55
50
|
# Custom environment variables to pass to the command.
|
56
51
|
#
|
57
52
|
# @param [Hash{Symbol => Object}] kwargs
|
@@ -94,12 +89,16 @@ module CommandMapper
|
|
94
89
|
# Initializes and runs the command.
|
95
90
|
#
|
96
91
|
# @param [Hash{Symbol => Object}] params
|
97
|
-
# The option values.
|
92
|
+
# The option and argument values.
|
98
93
|
#
|
99
|
-
# @
|
94
|
+
# @param [Hash{Symbol => Object}] kwargs
|
95
|
+
# Additional keywords arguments. These will be used to populate
|
96
|
+
# {#options} and {#arguments}, along with `params`.
|
97
|
+
#
|
98
|
+
# @yield [command]
|
100
99
|
# The newly initialized command.
|
101
100
|
#
|
102
|
-
# @yieldparam [Command]
|
101
|
+
# @yieldparam [Command] command
|
103
102
|
#
|
104
103
|
# @return [Boolean, nil]
|
105
104
|
#
|
@@ -109,15 +108,49 @@ module CommandMapper
|
|
109
108
|
end
|
110
109
|
|
111
110
|
#
|
112
|
-
#
|
111
|
+
# Initializes and spawns the command as a separate process, returning the
|
112
|
+
# PID of the process.
|
113
113
|
#
|
114
114
|
# @param [Hash{Symbol => Object}] params
|
115
|
-
# The option values.
|
115
|
+
# The option and argument values.
|
116
116
|
#
|
117
|
-
# @
|
117
|
+
# @param [Hash{Symbol => Object}] kwargs
|
118
|
+
# Additional keywords arguments. These will be used to populate
|
119
|
+
# {#options} and {#arguments}, along with `params`.
|
120
|
+
#
|
121
|
+
# @yield [command]
|
118
122
|
# The newly initialized command.
|
119
123
|
#
|
120
|
-
# @yieldparam [Command]
|
124
|
+
# @yieldparam [Command] command
|
125
|
+
#
|
126
|
+
# @return [Integer]
|
127
|
+
# The PID of the new command process.
|
128
|
+
#
|
129
|
+
# @raise [Errno::ENOENT]
|
130
|
+
# The command could not be found.
|
131
|
+
#
|
132
|
+
# @since 0.2.0
|
133
|
+
#
|
134
|
+
def self.spawn(params={},**kwargs,&block)
|
135
|
+
command = new(params,**kwargs,&block)
|
136
|
+
command.spawn_command
|
137
|
+
end
|
138
|
+
|
139
|
+
#
|
140
|
+
# Initializes and runs the command in a shell and captures all stdout
|
141
|
+
# output.
|
142
|
+
#
|
143
|
+
# @param [Hash{Symbol => Object}] params
|
144
|
+
# The option and argument values.
|
145
|
+
#
|
146
|
+
# @param [Hash{Symbol => Object}] kwargs
|
147
|
+
# Additional keywords arguments. These will be used to populate
|
148
|
+
# {#options} and {#arguments}, along with `params`.
|
149
|
+
#
|
150
|
+
# @yield [command]
|
151
|
+
# The newly initialized command.
|
152
|
+
#
|
153
|
+
# @yieldparam [Command] command
|
121
154
|
#
|
122
155
|
# @return [String]
|
123
156
|
# The stdout output of the command.
|
@@ -128,15 +161,22 @@ module CommandMapper
|
|
128
161
|
end
|
129
162
|
|
130
163
|
#
|
131
|
-
#
|
164
|
+
# Initializes and executes the command and returns an IO object to it.
|
132
165
|
#
|
133
166
|
# @param [Hash{Symbol => Object}] params
|
134
|
-
# The option values.
|
167
|
+
# The option and argument values.
|
135
168
|
#
|
136
|
-
# @
|
169
|
+
# @param [String] mode
|
170
|
+
# The IO "mode" to open the IO pipe in.
|
171
|
+
#
|
172
|
+
# @param [Hash{Symbol => Object}] kwargs
|
173
|
+
# Additional keywords arguments. These will be used to populate
|
174
|
+
# {#options} and {#arguments}, along with `params`.
|
175
|
+
#
|
176
|
+
# @yield [command]
|
137
177
|
# The newly initialized command.
|
138
178
|
#
|
139
|
-
# @yieldparam [Command]
|
179
|
+
# @yieldparam [Command] command
|
140
180
|
#
|
141
181
|
# @return [IO]
|
142
182
|
#
|
@@ -146,18 +186,18 @@ module CommandMapper
|
|
146
186
|
end
|
147
187
|
|
148
188
|
#
|
149
|
-
# Initializes and runs the command through sudo
|
189
|
+
# Initializes and runs the command through `sudo`.
|
150
190
|
#
|
151
191
|
# @param [Hash{Symbol => Object}] params
|
152
|
-
# The option values.
|
192
|
+
# The option and argument values.
|
153
193
|
#
|
154
194
|
# @param [Hash{Symbol => Object}] kwargs
|
155
195
|
# Additional keyword arguments for {#initialize}.
|
156
196
|
#
|
157
|
-
# @yield [
|
197
|
+
# @yield [command]
|
158
198
|
# The newly initialized command.
|
159
199
|
#
|
160
|
-
# @yieldparam [Command]
|
200
|
+
# @yieldparam [Command] command
|
161
201
|
#
|
162
202
|
# @return [Boolean, nil]
|
163
203
|
#
|
@@ -181,7 +221,11 @@ module CommandMapper
|
|
181
221
|
# @api semipublic
|
182
222
|
#
|
183
223
|
def self.command_name
|
184
|
-
@command_name ||
|
224
|
+
@command_name || if superclass < Command
|
225
|
+
superclass.command_name
|
226
|
+
else
|
227
|
+
raise(NotImplementedError,"#{self} did not call command(...)")
|
228
|
+
end
|
185
229
|
end
|
186
230
|
|
187
231
|
#
|
@@ -221,6 +265,23 @@ module CommandMapper
|
|
221
265
|
end
|
222
266
|
end
|
223
267
|
|
268
|
+
#
|
269
|
+
# Determines if an option with the given name has been defined.
|
270
|
+
#
|
271
|
+
# @param [Symbol] name
|
272
|
+
# The given name.
|
273
|
+
#
|
274
|
+
# @return [Boolean]
|
275
|
+
# Specifies whether an option with the given name has been defined.
|
276
|
+
#
|
277
|
+
# @api semipublic
|
278
|
+
#
|
279
|
+
# @since 0.2.0
|
280
|
+
#
|
281
|
+
def self.has_option?(name)
|
282
|
+
options.has_key?(name)
|
283
|
+
end
|
284
|
+
|
224
285
|
#
|
225
286
|
# Defines an option for the command.
|
226
287
|
#
|
@@ -230,10 +291,6 @@ module CommandMapper
|
|
230
291
|
# @param [Symbol, nil] name
|
231
292
|
# The option's name.
|
232
293
|
#
|
233
|
-
# @param [Boolean] equals
|
234
|
-
# Specifies whether the option's flag and value should be separated with a
|
235
|
-
# `=` character.
|
236
|
-
#
|
237
294
|
# @param [Hash, nil] value
|
238
295
|
# The option's value.
|
239
296
|
#
|
@@ -246,6 +303,14 @@ module CommandMapper
|
|
246
303
|
# @param [Boolean] repeats
|
247
304
|
# Specifies whether the option can be given multiple times.
|
248
305
|
#
|
306
|
+
# @param [Boolean] equals
|
307
|
+
# Specifies whether the option's flag and value should be separated with a
|
308
|
+
# `=` character.
|
309
|
+
#
|
310
|
+
# @param [Boolean] value_in_flag
|
311
|
+
# Specifies that the value should be appended to the option's flag
|
312
|
+
# (ex: `-Fvalue`).
|
313
|
+
#
|
249
314
|
# @api public
|
250
315
|
#
|
251
316
|
# @example Defining an option:
|
@@ -260,6 +325,9 @@ module CommandMapper
|
|
260
325
|
# @example Defining an option who's value is optional:
|
261
326
|
# option '--file', value: {required: false}
|
262
327
|
#
|
328
|
+
# @example Defining an `-Fvalue` option:
|
329
|
+
# option '--foo', value: true, value_in_flag: true
|
330
|
+
#
|
263
331
|
# @example Defining an `--opt=value` option:
|
264
332
|
# option '--foo', equals: true, value: true
|
265
333
|
#
|
@@ -270,25 +338,35 @@ module CommandMapper
|
|
270
338
|
# option '--list', value: List.new
|
271
339
|
#
|
272
340
|
# @raise [ArgumentError]
|
273
|
-
# The option flag conflicts with a pre-existing internal method
|
274
|
-
#
|
275
|
-
|
341
|
+
# The option flag conflicts with a pre-existing internal method, or
|
342
|
+
# another argument or subcommand.
|
343
|
+
#
|
344
|
+
def self.option(flag, name: nil, value: nil, repeats: false,
|
345
|
+
# formatting options
|
346
|
+
equals: nil,
|
347
|
+
value_in_flag: nil,
|
348
|
+
&block)
|
276
349
|
option = Option.new(flag, name: name,
|
277
|
-
equals: equals,
|
278
350
|
value: value,
|
279
351
|
repeats: repeats,
|
352
|
+
# formatting options
|
353
|
+
equals: equals,
|
354
|
+
value_in_flag: value_in_flag,
|
280
355
|
&block)
|
281
356
|
|
282
|
-
self.options[option.name] = option
|
283
|
-
|
284
357
|
if is_internal_method?(option.name)
|
285
358
|
if name
|
286
359
|
raise(ArgumentError,"option #{flag.inspect} with name #{name.inspect} cannot override the internal method with same name: ##{option.name}")
|
287
360
|
else
|
288
361
|
raise(ArgumentError,"option #{flag.inspect} maps to method name ##{option.name} and cannot override the internal method with same name: ##{option.name}")
|
289
362
|
end
|
363
|
+
elsif has_argument?(option.name)
|
364
|
+
raise(ArgumentError,"option #{flag.inspect} with name #{option.name.inspect} conflicts with another argument with the same name")
|
365
|
+
elsif has_subcommand?(option.name)
|
366
|
+
raise(ArgumentError,"option #{flag.inspect} with name #{option.name.inspect} conflicts with another subcommand with the same name")
|
290
367
|
end
|
291
368
|
|
369
|
+
self.options[option.name] = option
|
292
370
|
attr_accessor option.name
|
293
371
|
end
|
294
372
|
|
@@ -296,6 +374,7 @@ module CommandMapper
|
|
296
374
|
# All defined options.
|
297
375
|
#
|
298
376
|
# @return [Hash{Symbol => Argument}]
|
377
|
+
# The mapping of argument names and {Argument} objects.
|
299
378
|
#
|
300
379
|
# @api semipublic
|
301
380
|
#
|
@@ -307,6 +386,23 @@ module CommandMapper
|
|
307
386
|
end
|
308
387
|
end
|
309
388
|
|
389
|
+
#
|
390
|
+
# Determines if an argument with the given name has been defined.
|
391
|
+
#
|
392
|
+
# @param [Symbol] name
|
393
|
+
# The given name.
|
394
|
+
#
|
395
|
+
# @return [Boolean]
|
396
|
+
# Specifies whether an argument with the given name has been defined.
|
397
|
+
#
|
398
|
+
# @api semipublic
|
399
|
+
#
|
400
|
+
# @since 0.2.0
|
401
|
+
#
|
402
|
+
def self.has_argument?(name)
|
403
|
+
arguments.has_key?(name)
|
404
|
+
end
|
405
|
+
|
310
406
|
#
|
311
407
|
# Defines an option for the command.
|
312
408
|
#
|
@@ -333,7 +429,8 @@ module CommandMapper
|
|
333
429
|
# argument :file, required: false
|
334
430
|
#
|
335
431
|
# @raise [ArgumentError]
|
336
|
-
# The argument name conflicts with a pre-existing internal method
|
432
|
+
# The argument name conflicts with a pre-existing internal method, or
|
433
|
+
# another option or subcommand.
|
337
434
|
#
|
338
435
|
def self.argument(name, required: true, type: Str.new, repeats: false)
|
339
436
|
name = name.to_sym
|
@@ -341,19 +438,23 @@ module CommandMapper
|
|
341
438
|
type: type,
|
342
439
|
repeats: repeats)
|
343
440
|
|
344
|
-
self.arguments[argument.name] = argument
|
345
|
-
|
346
441
|
if is_internal_method?(argument.name)
|
347
442
|
raise(ArgumentError,"argument #{name.inspect} cannot override internal method with same name: ##{argument.name}")
|
443
|
+
elsif has_option?(argument.name)
|
444
|
+
raise(ArgumentError,"argument #{name.inspect} conflicts with another option with the same name")
|
445
|
+
elsif has_subcommand?(argument.name)
|
446
|
+
raise(ArgumentError,"argument #{name.inspect} conflicts with another subcommand with the same name")
|
348
447
|
end
|
349
448
|
|
449
|
+
self.arguments[argument.name] = argument
|
350
450
|
attr_accessor name
|
351
451
|
end
|
352
452
|
|
353
453
|
#
|
354
454
|
# All defined subcommands.
|
355
455
|
#
|
356
|
-
# @return [Hash{Symbol => Command}]
|
456
|
+
# @return [Hash{Symbol => Command.class}]
|
457
|
+
# The mapping of subcommand names and subcommand classes.
|
357
458
|
#
|
358
459
|
# @api semipublic
|
359
460
|
#
|
@@ -365,6 +466,23 @@ module CommandMapper
|
|
365
466
|
end
|
366
467
|
end
|
367
468
|
|
469
|
+
#
|
470
|
+
# Determines if a subcommand with the given name has been defined.
|
471
|
+
#
|
472
|
+
# @param [Symbol] name
|
473
|
+
# The given name.
|
474
|
+
#
|
475
|
+
# @return [Boolean]
|
476
|
+
# Specifies whether a subcommand with the given name has been defined.
|
477
|
+
#
|
478
|
+
# @api semipublic
|
479
|
+
#
|
480
|
+
# @since 0.2.0
|
481
|
+
#
|
482
|
+
def self.has_subcommand?(name)
|
483
|
+
subcommands.has_key?(name)
|
484
|
+
end
|
485
|
+
|
368
486
|
#
|
369
487
|
# Defines a subcommand.
|
370
488
|
#
|
@@ -392,25 +510,30 @@ module CommandMapper
|
|
392
510
|
# end
|
393
511
|
#
|
394
512
|
# @raise [ArgumentError]
|
395
|
-
# The subcommand name conflicts with a pre-existing internal method
|
513
|
+
# The subcommand name conflicts with a pre-existing internal method, or
|
514
|
+
# another option or argument.
|
396
515
|
#
|
397
516
|
def self.subcommand(name,&block)
|
398
|
-
name
|
517
|
+
name = name.to_s
|
518
|
+
method_name = name.tr('-','_')
|
519
|
+
class_name = name.split(/[_-]+/).map(&:capitalize).join
|
520
|
+
subcommand_name = method_name.to_sym
|
521
|
+
|
522
|
+
if is_internal_method?(method_name)
|
523
|
+
raise(ArgumentError,"subcommand #{name.inspect} maps to method name ##{method_name} and cannot override the internal method with same name: ##{method_name}")
|
524
|
+
elsif has_option?(subcommand_name)
|
525
|
+
raise(ArgumentError,"subcommand #{name.inspect} conflicts with another option with the same name")
|
526
|
+
elsif has_argument?(subcommand_name)
|
527
|
+
raise(ArgumentError,"subcommand #{name.inspect} conflicts with another argument with the same name")
|
528
|
+
end
|
399
529
|
|
400
530
|
subcommand_class = Class.new(Command)
|
401
531
|
subcommand_class.command(name)
|
402
532
|
subcommand_class.class_eval(&block)
|
403
533
|
|
404
|
-
|
405
|
-
class_name = name.split(/[_-]+/).map(&:capitalize).join
|
406
|
-
|
407
|
-
self.subcommands[method_name.to_sym] = subcommand_class
|
534
|
+
self.subcommands[subcommand_name] = subcommand_class
|
408
535
|
const_set(class_name,subcommand_class)
|
409
536
|
|
410
|
-
if is_internal_method?(method_name)
|
411
|
-
raise(ArgumentError,"subcommand #{name.inspect} maps to method name ##{method_name} and cannot override the internal method with same name: ##{method_name}")
|
412
|
-
end
|
413
|
-
|
414
537
|
define_method(method_name) do |&block|
|
415
538
|
if block then @command_subcommand = subcommand_class.new(&block)
|
416
539
|
else @command_subcommand
|
@@ -428,8 +551,10 @@ module CommandMapper
|
|
428
551
|
# Gets the value of an option or an argument.
|
429
552
|
#
|
430
553
|
# @param [Symbol] name
|
554
|
+
# The name of the option, argument, or subcommand.
|
431
555
|
#
|
432
556
|
# @return [Object]
|
557
|
+
# The value of the option, argument, or subcommand.
|
433
558
|
#
|
434
559
|
# @raise [ArgumentError]
|
435
560
|
# The given name was not match any option or argument.
|
@@ -448,10 +573,13 @@ module CommandMapper
|
|
448
573
|
# Sets an option or an argument with the given name.
|
449
574
|
#
|
450
575
|
# @param [Symbol] name
|
576
|
+
# The name of the option, argument, or subcommand.
|
451
577
|
#
|
452
578
|
# @param [Object] value
|
579
|
+
# The new value for the option, argument, or subcommand.
|
453
580
|
#
|
454
581
|
# @return [Object]
|
582
|
+
# The new value for the option, argument, or subcommand.
|
455
583
|
#
|
456
584
|
# @raise [ArgumentError]
|
457
585
|
# The given name was not match any option or argument.
|
@@ -468,6 +596,7 @@ module CommandMapper
|
|
468
596
|
# Returns an Array of command-line arguments for the command.
|
469
597
|
#
|
470
598
|
# @return [Array<String>]
|
599
|
+
# The formatted command-line arguments.
|
471
600
|
#
|
472
601
|
# @raise [ArgumentReqired]
|
473
602
|
# A required argument was not set.
|
@@ -490,8 +619,10 @@ module CommandMapper
|
|
490
619
|
self.class.arguments.each do |name,argument|
|
491
620
|
value = self[name]
|
492
621
|
|
493
|
-
if value.nil?
|
494
|
-
|
622
|
+
if value.nil?
|
623
|
+
if argument.required?
|
624
|
+
raise(ArgumentRequired,"argument #{name} is required")
|
625
|
+
end
|
495
626
|
else
|
496
627
|
argument.argv(additional_args,value)
|
497
628
|
end
|
@@ -529,12 +660,30 @@ module CommandMapper
|
|
529
660
|
end
|
530
661
|
|
531
662
|
#
|
532
|
-
#
|
663
|
+
# Runs the command.
|
533
664
|
#
|
534
665
|
# @return [Boolean, nil]
|
666
|
+
# Indicates whether the command exited successfully or not.
|
667
|
+
# `nil` indicates the command could not be found.
|
535
668
|
#
|
536
669
|
def run_command
|
537
|
-
system(@command_env,*command_argv)
|
670
|
+
Kernel.system(@command_env,*command_argv)
|
671
|
+
end
|
672
|
+
|
673
|
+
#
|
674
|
+
# Spawns the command as a separate process, returning the PID of the
|
675
|
+
# process.
|
676
|
+
#
|
677
|
+
# @return [Integer]
|
678
|
+
# The PID of the new command process.
|
679
|
+
#
|
680
|
+
# @raise [Errno::ENOENT]
|
681
|
+
# The command could not be found.
|
682
|
+
#
|
683
|
+
# @since 0.2.0
|
684
|
+
#
|
685
|
+
def spawn_command
|
686
|
+
Process.spawn(@command_env,*command_argv)
|
538
687
|
end
|
539
688
|
|
540
689
|
#
|
@@ -551,6 +700,7 @@ module CommandMapper
|
|
551
700
|
# Executes the command and returns an IO object to it.
|
552
701
|
#
|
553
702
|
# @return [IO]
|
703
|
+
# The IO object for the command's `STDIN`.
|
554
704
|
#
|
555
705
|
def popen_command(mode=nil)
|
556
706
|
if mode then IO.popen(@command_env,command_argv,mode)
|
@@ -559,12 +709,14 @@ module CommandMapper
|
|
559
709
|
end
|
560
710
|
|
561
711
|
#
|
562
|
-
#
|
712
|
+
# Runs the command through `sudo`.
|
563
713
|
#
|
564
714
|
# @param [Hash{Symbol => Object}] sudo_params
|
565
715
|
# Additional keyword arguments for {Sudo#initialize}.
|
566
716
|
#
|
567
717
|
# @return [Boolean, nil]
|
718
|
+
# Indicates whether the command exited successfully or not.
|
719
|
+
# `nil` indicates the command could not be found.
|
568
720
|
#
|
569
721
|
def sudo_command(**sudo_kwargs,&block)
|
570
722
|
sudo_params = sudo_kwargs.merge(command: command_argv)
|
@@ -595,6 +747,7 @@ module CommandMapper
|
|
595
747
|
# The method name.
|
596
748
|
#
|
597
749
|
# @return [Boolean]
|
750
|
+
# Indicates that the method name is also an intenral method name.
|
598
751
|
#
|
599
752
|
def self.is_internal_method?(name)
|
600
753
|
Command.instance_methods(false).include?(name.to_sym)
|
@@ -7,12 +7,18 @@ module CommandMapper
|
|
7
7
|
#
|
8
8
|
class Option
|
9
9
|
|
10
|
+
# The option's flag (ex: `-o` or `--output`).
|
11
|
+
#
|
10
12
|
# @return [String]
|
11
13
|
attr_reader :flag
|
12
14
|
|
15
|
+
# The option's name.
|
16
|
+
#
|
13
17
|
# @return [Symbol]
|
14
18
|
attr_reader :name
|
15
19
|
|
20
|
+
# Describes the option's value.
|
21
|
+
#
|
16
22
|
# @return [OptionValue, nil]
|
17
23
|
attr_reader :value
|
18
24
|
|
@@ -25,10 +31,6 @@ module CommandMapper
|
|
25
31
|
# @param [Symbol, nil] name
|
26
32
|
# The option's name.
|
27
33
|
#
|
28
|
-
# @param [Boolean] equals
|
29
|
-
# Specifies whether the option's flag and value should be separated with a
|
30
|
-
# `=` character.
|
31
|
-
#
|
32
34
|
# @param [Hash, nil] value
|
33
35
|
# The option's value.
|
34
36
|
#
|
@@ -41,15 +43,31 @@ module CommandMapper
|
|
41
43
|
# @param [Boolean] repeats
|
42
44
|
# Specifies whether the option can be given multiple times.
|
43
45
|
#
|
44
|
-
|
46
|
+
# @param [Boolean] equals
|
47
|
+
# Specifies whether the option's flag and value should be separated with a
|
48
|
+
# `=` character.
|
49
|
+
#
|
50
|
+
# @param [Boolean] value_in_flag
|
51
|
+
# Specifies that the value should be appended to the option's flag
|
52
|
+
# (ex: `-Fvalue`).
|
53
|
+
#
|
54
|
+
# @api private
|
55
|
+
#
|
56
|
+
def initialize(flag, name: nil, value: nil, repeats: false,
|
57
|
+
# formatting options
|
58
|
+
equals: nil,
|
59
|
+
value_in_flag: nil)
|
45
60
|
@flag = flag
|
46
61
|
@name = name || self.class.infer_name_from_flag(flag)
|
47
|
-
@equals = equals
|
48
62
|
@value = case value
|
49
63
|
when Hash then OptionValue.new(**value)
|
50
64
|
when true then OptionValue.new
|
51
65
|
end
|
52
66
|
@repeats = repeats
|
67
|
+
|
68
|
+
# formatting options
|
69
|
+
@equals = equals
|
70
|
+
@value_in_flag = value_in_flag
|
53
71
|
end
|
54
72
|
|
55
73
|
#
|
@@ -65,6 +83,8 @@ module CommandMapper
|
|
65
83
|
# Could not infer the name from the given option flag or was not given a
|
66
84
|
# valid option flag.
|
67
85
|
#
|
86
|
+
# @api private
|
87
|
+
#
|
68
88
|
def self.infer_name_from_flag(flag)
|
69
89
|
if flag.start_with?('--')
|
70
90
|
name = flag[2..-1]
|
@@ -90,6 +110,15 @@ module CommandMapper
|
|
90
110
|
!@value.nil?
|
91
111
|
end
|
92
112
|
|
113
|
+
#
|
114
|
+
# Determines whether the option can be given multiple times.
|
115
|
+
#
|
116
|
+
# @return [Boolean]
|
117
|
+
#
|
118
|
+
def repeats?
|
119
|
+
@repeats
|
120
|
+
end
|
121
|
+
|
93
122
|
#
|
94
123
|
# Indicates whether the option flag and value should be separated with a
|
95
124
|
# `=` character.
|
@@ -101,12 +130,14 @@ module CommandMapper
|
|
101
130
|
end
|
102
131
|
|
103
132
|
#
|
104
|
-
#
|
133
|
+
# Indicates whether the value will be appended to the option's flag.
|
105
134
|
#
|
106
135
|
# @return [Boolean]
|
107
136
|
#
|
108
|
-
|
109
|
-
|
137
|
+
# @since 0.2.0
|
138
|
+
#
|
139
|
+
def value_in_flag?
|
140
|
+
@value_in_flag
|
110
141
|
end
|
111
142
|
|
112
143
|
#
|
@@ -119,6 +150,8 @@ module CommandMapper
|
|
119
150
|
# Returns true if the value is valid, or `false` and a validation error
|
120
151
|
# message if the value is not compatible.
|
121
152
|
#
|
153
|
+
# @api semipublic
|
154
|
+
#
|
122
155
|
def validate(value)
|
123
156
|
if accepts_value?
|
124
157
|
if repeats?
|
@@ -146,6 +179,8 @@ module CommandMapper
|
|
146
179
|
# @raise [ArgumentError]
|
147
180
|
# The given value was incompatible with the option.
|
148
181
|
#
|
182
|
+
# @api semipublic
|
183
|
+
#
|
149
184
|
def argv(argv=[],value)
|
150
185
|
valid, message = validate(value)
|
151
186
|
|
@@ -269,13 +304,15 @@ module CommandMapper
|
|
269
304
|
else
|
270
305
|
string = @value.format(value)
|
271
306
|
|
272
|
-
if string.start_with?('-')
|
273
|
-
raise(ValidationError,"option #{@name} formatted value (#{string.inspect}) cannot start with a '-'")
|
274
|
-
end
|
275
|
-
|
276
307
|
if equals?
|
277
308
|
argv << "#{@flag}=#{string}"
|
309
|
+
elsif value_in_flag?
|
310
|
+
argv << "#{@flag}#{string}"
|
278
311
|
else
|
312
|
+
if string.start_with?('-')
|
313
|
+
raise(ValidationError,"option #{@name} formatted value (#{string.inspect}) cannot start with a '-'")
|
314
|
+
end
|
315
|
+
|
279
316
|
argv << @flag << string
|
280
317
|
end
|
281
318
|
end
|
@@ -6,6 +6,26 @@ module CommandMapper
|
|
6
6
|
#
|
7
7
|
class OptionValue < Arg
|
8
8
|
|
9
|
+
#
|
10
|
+
# Validates whether a given value is compatible with the option {#type}.
|
11
|
+
#
|
12
|
+
# @param [Object] value
|
13
|
+
# The given value to validate.
|
14
|
+
#
|
15
|
+
# @return [true, (false, String)]
|
16
|
+
# Returns true if the value is valid, or `false` and a validation error
|
17
|
+
# message if the value is not compatible.
|
18
|
+
#
|
19
|
+
# @api semipublic
|
20
|
+
#
|
21
|
+
def validate(value)
|
22
|
+
if !required? && value == true
|
23
|
+
return true
|
24
|
+
else
|
25
|
+
super(value)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
9
29
|
#
|
10
30
|
# Formats a value using the options {#type}.
|
11
31
|
#
|
@@ -15,6 +35,8 @@ module CommandMapper
|
|
15
35
|
# @return [String]
|
16
36
|
# The formatted value.
|
17
37
|
#
|
38
|
+
# @api semipublic
|
39
|
+
#
|
18
40
|
def format(value)
|
19
41
|
@type.format(value)
|
20
42
|
end
|
@@ -2,9 +2,17 @@ require 'command_mapper/types/map'
|
|
2
2
|
|
3
3
|
module CommandMapper
|
4
4
|
module Types
|
5
|
+
#
|
6
|
+
# Represents a mapping of Ruby values to their String equivalents.
|
7
|
+
#
|
5
8
|
class Enum < Map
|
6
9
|
|
10
|
+
# The values of the enum.
|
11
|
+
#
|
7
12
|
# @return [Array<Object>]
|
13
|
+
#
|
14
|
+
# @api semipublic
|
15
|
+
#
|
8
16
|
attr_reader :values
|
9
17
|
|
10
18
|
#
|