command_mapper 0.1.1 → 0.2.1
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 +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
|
#
|