command_mapper 0.1.2 → 0.2.0
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 +15 -0
- data/Gemfile +2 -0
- data/LICENSE.txt +1 -1
- data/README.md +26 -5
- data/lib/command_mapper/arg.rb +1 -0
- data/lib/command_mapper/command.rb +148 -29
- data/lib/command_mapper/option.rb +42 -13
- data/lib/command_mapper/types/hex.rb +10 -2
- data/lib/command_mapper/types/map.rb +2 -0
- data/lib/command_mapper/types/num.rb +22 -1
- data/lib/command_mapper/version.rb +1 -1
- data/spec/commnad_spec.rb +314 -74
- data/spec/option_spec.rb +252 -1
- data/spec/types/hex_spec.rb +59 -1
- data/spec/types/num_spec.rb +93 -3
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7f40de593c4e0124a5c3075b9e89ec08e38fe7451af1ede8fc62030b89b694ec
|
4
|
+
data.tar.gz: '0392b215a5dff4c4b2bffd34ec7df72fece82adbe7d260a975bd909edb9f962d'
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f5c09b88caea6446ac20238bbba137accd62552c413e5fa67d9dce9a62f2e3f027624c6a951ce59fc6c720f2683e80dcbe5d3e8942d85f472e5d25e12f5e5a85
|
7
|
+
data.tar.gz: a8e1b267f3628fd23d2924f9d6f25534744af6c2af8a80d5cc3b3fa63bbdc1f35853dcda99871db67cbeea6f6824f3ed77a19b33bd8bd1bb1f5ca7750abf8a7b
|
data/.document
ADDED
data/.github/workflows/ruby.yml
CHANGED
data/ChangeLog.md
CHANGED
@@ -1,3 +1,18 @@
|
|
1
|
+
### 0.2.0 / 2022-04-18
|
2
|
+
|
3
|
+
* Added {CommandMapper::Command.spawn} and
|
4
|
+
{CommandMapper::Command#spawn_command}.
|
5
|
+
* Added checks to {CommandMapper::Command.option},
|
6
|
+
{CommandMapper::Command.argument}, and {CommandMapper::Command.subcommand} to
|
7
|
+
avoid overwriting an existing option/argument/subcommand with the same name.
|
8
|
+
* Added the `value_in_flag:` keyword argument to
|
9
|
+
{CommandMapper::Command.option} which indicates an option's value
|
10
|
+
should be appended to the flag (ex: `-Fvalue`).
|
11
|
+
* Added the `range:` keyword argument to {CommandMapper::Types::Num#initialize}
|
12
|
+
for specifying the acceptable range of numbers.
|
13
|
+
* Allow options with `equals: true` (aka `--opt=...`) or `value_in_flag: true`
|
14
|
+
(aka `-Fvalue`) to accept values that start with a `-` character.
|
15
|
+
|
1
16
|
### 0.1.2 / 2021-11-29
|
2
17
|
|
3
18
|
* Fixed a bug where {CommandMapper::Command.command_name} was not checking the
|
data/Gemfile
CHANGED
data/LICENSE.txt
CHANGED
data/README.md
CHANGED
@@ -43,7 +43,10 @@ allow safely and securely executing commands.
|
|
43
43
|
* Allows running commands with additional environment variables.
|
44
44
|
* Allows overriding the command name or path to the command.
|
45
45
|
* Allows running commands via `sudo`.
|
46
|
-
* Prevents command injection and option injection.
|
46
|
+
* Prevents [command injection] and [option injection].
|
47
|
+
|
48
|
+
[command injection]: https://owasp.org/www-community/attacks/Command_Injection
|
49
|
+
[option injection]: https://staaldraad.github.io/post/2019-11-24-argument-injection/
|
47
50
|
|
48
51
|
[CommandMapper::Types::Str]: https://rubydoc.info/gems/command_mapper/CommandMapper/Types/Str
|
49
52
|
[CommandMapper::Types::Num]: https://rubydoc.info/gems/command_mapper/CommandMapper/Types/Num
|
@@ -73,7 +76,7 @@ class Grep < CommandMapper::Command
|
|
73
76
|
option "--basic-regexp"
|
74
77
|
option "--perl-regexp"
|
75
78
|
option "--regexp", equals: true, value: true
|
76
|
-
option "--file", equals: true, value: true
|
79
|
+
option "--file", name: :patterns_file, equals: true, value: true
|
77
80
|
option "--ignore-case"
|
78
81
|
option "--no-ignore-case"
|
79
82
|
option "--word-regexp"
|
@@ -142,6 +145,18 @@ Defines an option with a required value:
|
|
142
145
|
option "--output", value: {required: true}
|
143
146
|
```
|
144
147
|
|
148
|
+
Defines an option that uses an equals sign (ex: `--output=value`):
|
149
|
+
|
150
|
+
```ruby
|
151
|
+
option "--output", equals: true, value: {required: true}
|
152
|
+
```
|
153
|
+
|
154
|
+
Defines an option where the value is embedded into the flag (ex: `-Ivalue`):
|
155
|
+
|
156
|
+
```ruby
|
157
|
+
option "-I", value: {required: true}, value_in_flag: true
|
158
|
+
```
|
159
|
+
|
145
160
|
Defines an option that can be specified multiple times:
|
146
161
|
|
147
162
|
```ruby
|
@@ -154,6 +169,12 @@ Defines an option that accepts a numeric value:
|
|
154
169
|
option "--count", value: {type: Num.new}
|
155
170
|
```
|
156
171
|
|
172
|
+
Define an option that only accepts a range of acceptable values:
|
173
|
+
|
174
|
+
```ruby
|
175
|
+
option "--count", value: {type: Num.new(range: 1..100)}
|
176
|
+
```
|
177
|
+
|
157
178
|
Defines an option that accepts a comma-separated list:
|
158
179
|
|
159
180
|
```ruby
|
@@ -368,18 +389,18 @@ $ gem install command_mapper
|
|
368
389
|
### Gemfile
|
369
390
|
|
370
391
|
```ruby
|
371
|
-
gem 'command_mapper', '~> 0.
|
392
|
+
gem 'command_mapper', '~> 0.2'
|
372
393
|
```
|
373
394
|
|
374
395
|
### gemspec
|
375
396
|
|
376
397
|
```ruby
|
377
|
-
gemspec.add_dependency 'command_mapper', '~> 0.
|
398
|
+
gemspec.add_dependency 'command_mapper', '~> 0.2'
|
378
399
|
```
|
379
400
|
|
380
401
|
## License
|
381
402
|
|
382
|
-
Copyright (c) 2021 Hal Brodigan
|
403
|
+
Copyright (c) 2021-2022 Hal Brodigan
|
383
404
|
|
384
405
|
See {file:LICENSE.txt} for license information.
|
385
406
|
|
data/lib/command_mapper/arg.rb
CHANGED
@@ -109,7 +109,33 @@ module CommandMapper
|
|
109
109
|
end
|
110
110
|
|
111
111
|
#
|
112
|
-
#
|
112
|
+
# Initializes and spawns the command as a separate process, returning the
|
113
|
+
# PID of the process.
|
114
|
+
#
|
115
|
+
# @param [Hash{Symbol => Object}] params
|
116
|
+
# The option values.
|
117
|
+
#
|
118
|
+
# @yield [self]
|
119
|
+
# The newly initialized command.
|
120
|
+
#
|
121
|
+
# @yieldparam [Command] self
|
122
|
+
#
|
123
|
+
# @return [Integer]
|
124
|
+
# The PID of the new command process.
|
125
|
+
#
|
126
|
+
# @raise [Errno::ENOENT]
|
127
|
+
# The command could not be found.
|
128
|
+
#
|
129
|
+
# @since 0.2.0
|
130
|
+
#
|
131
|
+
def self.spawn(params={},**kwargs,&block)
|
132
|
+
command = new(params,**kwargs,&block)
|
133
|
+
command.spawn_command
|
134
|
+
end
|
135
|
+
|
136
|
+
#
|
137
|
+
# Initializes and runs the command in a shell and captures all stdout
|
138
|
+
# output.
|
113
139
|
#
|
114
140
|
# @param [Hash{Symbol => Object}] params
|
115
141
|
# The option values.
|
@@ -128,7 +154,7 @@ module CommandMapper
|
|
128
154
|
end
|
129
155
|
|
130
156
|
#
|
131
|
-
#
|
157
|
+
# Initializes and executes the command and returns an IO object to it.
|
132
158
|
#
|
133
159
|
# @param [Hash{Symbol => Object}] params
|
134
160
|
# The option values.
|
@@ -146,7 +172,7 @@ module CommandMapper
|
|
146
172
|
end
|
147
173
|
|
148
174
|
#
|
149
|
-
# Initializes and runs the command through sudo
|
175
|
+
# Initializes and runs the command through `sudo`.
|
150
176
|
#
|
151
177
|
# @param [Hash{Symbol => Object}] params
|
152
178
|
# The option values.
|
@@ -225,6 +251,23 @@ module CommandMapper
|
|
225
251
|
end
|
226
252
|
end
|
227
253
|
|
254
|
+
#
|
255
|
+
# Determines if an option with the given name has been defined.
|
256
|
+
#
|
257
|
+
# @param [Symbol] name
|
258
|
+
# The given name.
|
259
|
+
#
|
260
|
+
# @return [Boolean]
|
261
|
+
# Specifies whether an option with the given name has been defined.
|
262
|
+
#
|
263
|
+
# @api semipublic
|
264
|
+
#
|
265
|
+
# @since 0.2.0
|
266
|
+
#
|
267
|
+
def self.has_option?(name)
|
268
|
+
options.has_key?(name)
|
269
|
+
end
|
270
|
+
|
228
271
|
#
|
229
272
|
# Defines an option for the command.
|
230
273
|
#
|
@@ -234,10 +277,6 @@ module CommandMapper
|
|
234
277
|
# @param [Symbol, nil] name
|
235
278
|
# The option's name.
|
236
279
|
#
|
237
|
-
# @param [Boolean] equals
|
238
|
-
# Specifies whether the option's flag and value should be separated with a
|
239
|
-
# `=` character.
|
240
|
-
#
|
241
280
|
# @param [Hash, nil] value
|
242
281
|
# The option's value.
|
243
282
|
#
|
@@ -250,6 +289,14 @@ module CommandMapper
|
|
250
289
|
# @param [Boolean] repeats
|
251
290
|
# Specifies whether the option can be given multiple times.
|
252
291
|
#
|
292
|
+
# @param [Boolean] equals
|
293
|
+
# Specifies whether the option's flag and value should be separated with a
|
294
|
+
# `=` character.
|
295
|
+
#
|
296
|
+
# @param [Boolean] value_in_flag
|
297
|
+
# Specifies that the value should be appended to the option's flag
|
298
|
+
# (ex: `-Fvalue`).
|
299
|
+
#
|
253
300
|
# @api public
|
254
301
|
#
|
255
302
|
# @example Defining an option:
|
@@ -264,6 +311,9 @@ module CommandMapper
|
|
264
311
|
# @example Defining an option who's value is optional:
|
265
312
|
# option '--file', value: {required: false}
|
266
313
|
#
|
314
|
+
# @example Defining an `-Fvalue` option:
|
315
|
+
# option '--foo', value: true, value_in_flag: true
|
316
|
+
#
|
267
317
|
# @example Defining an `--opt=value` option:
|
268
318
|
# option '--foo', equals: true, value: true
|
269
319
|
#
|
@@ -274,25 +324,35 @@ module CommandMapper
|
|
274
324
|
# option '--list', value: List.new
|
275
325
|
#
|
276
326
|
# @raise [ArgumentError]
|
277
|
-
# The option flag conflicts with a pre-existing internal method
|
278
|
-
#
|
279
|
-
|
327
|
+
# The option flag conflicts with a pre-existing internal method, or
|
328
|
+
# another argument or subcommand.
|
329
|
+
#
|
330
|
+
def self.option(flag, name: nil, value: nil, repeats: false,
|
331
|
+
# formatting options
|
332
|
+
equals: nil,
|
333
|
+
value_in_flag: nil,
|
334
|
+
&block)
|
280
335
|
option = Option.new(flag, name: name,
|
281
|
-
equals: equals,
|
282
336
|
value: value,
|
283
337
|
repeats: repeats,
|
338
|
+
# formatting options
|
339
|
+
equals: equals,
|
340
|
+
value_in_flag: value_in_flag,
|
284
341
|
&block)
|
285
342
|
|
286
|
-
self.options[option.name] = option
|
287
|
-
|
288
343
|
if is_internal_method?(option.name)
|
289
344
|
if name
|
290
345
|
raise(ArgumentError,"option #{flag.inspect} with name #{name.inspect} cannot override the internal method with same name: ##{option.name}")
|
291
346
|
else
|
292
347
|
raise(ArgumentError,"option #{flag.inspect} maps to method name ##{option.name} and cannot override the internal method with same name: ##{option.name}")
|
293
348
|
end
|
349
|
+
elsif has_argument?(option.name)
|
350
|
+
raise(ArgumentError,"option #{flag.inspect} with name #{option.name.inspect} conflicts with another argument with the same name")
|
351
|
+
elsif has_subcommand?(option.name)
|
352
|
+
raise(ArgumentError,"option #{flag.inspect} with name #{option.name.inspect} conflicts with another subcommand with the same name")
|
294
353
|
end
|
295
354
|
|
355
|
+
self.options[option.name] = option
|
296
356
|
attr_accessor option.name
|
297
357
|
end
|
298
358
|
|
@@ -311,6 +371,23 @@ module CommandMapper
|
|
311
371
|
end
|
312
372
|
end
|
313
373
|
|
374
|
+
#
|
375
|
+
# Determines if an argument with the given name has been defined.
|
376
|
+
#
|
377
|
+
# @param [Symbol] name
|
378
|
+
# The given name.
|
379
|
+
#
|
380
|
+
# @return [Boolean]
|
381
|
+
# Specifies whether an argument with the given name has been defined.
|
382
|
+
#
|
383
|
+
# @api semipublic
|
384
|
+
#
|
385
|
+
# @since 0.2.0
|
386
|
+
#
|
387
|
+
def self.has_argument?(name)
|
388
|
+
arguments.has_key?(name)
|
389
|
+
end
|
390
|
+
|
314
391
|
#
|
315
392
|
# Defines an option for the command.
|
316
393
|
#
|
@@ -337,7 +414,8 @@ module CommandMapper
|
|
337
414
|
# argument :file, required: false
|
338
415
|
#
|
339
416
|
# @raise [ArgumentError]
|
340
|
-
# The argument name conflicts with a pre-existing internal method
|
417
|
+
# The argument name conflicts with a pre-existing internal method, or
|
418
|
+
# another option or subcommand.
|
341
419
|
#
|
342
420
|
def self.argument(name, required: true, type: Str.new, repeats: false)
|
343
421
|
name = name.to_sym
|
@@ -345,12 +423,15 @@ module CommandMapper
|
|
345
423
|
type: type,
|
346
424
|
repeats: repeats)
|
347
425
|
|
348
|
-
self.arguments[argument.name] = argument
|
349
|
-
|
350
426
|
if is_internal_method?(argument.name)
|
351
427
|
raise(ArgumentError,"argument #{name.inspect} cannot override internal method with same name: ##{argument.name}")
|
428
|
+
elsif has_option?(argument.name)
|
429
|
+
raise(ArgumentError,"argument #{name.inspect} conflicts with another option with the same name")
|
430
|
+
elsif has_subcommand?(argument.name)
|
431
|
+
raise(ArgumentError,"argument #{name.inspect} conflicts with another subcommand with the same name")
|
352
432
|
end
|
353
433
|
|
434
|
+
self.arguments[argument.name] = argument
|
354
435
|
attr_accessor name
|
355
436
|
end
|
356
437
|
|
@@ -369,6 +450,23 @@ module CommandMapper
|
|
369
450
|
end
|
370
451
|
end
|
371
452
|
|
453
|
+
#
|
454
|
+
# Determines if a subcommand with the given name has been defined.
|
455
|
+
#
|
456
|
+
# @param [Symbol] name
|
457
|
+
# The given name.
|
458
|
+
#
|
459
|
+
# @return [Boolean]
|
460
|
+
# Specifies whether a subcommand with the given name has been defined.
|
461
|
+
#
|
462
|
+
# @api semipublic
|
463
|
+
#
|
464
|
+
# @since 0.2.0
|
465
|
+
#
|
466
|
+
def self.has_subcommand?(name)
|
467
|
+
subcommands.has_key?(name)
|
468
|
+
end
|
469
|
+
|
372
470
|
#
|
373
471
|
# Defines a subcommand.
|
374
472
|
#
|
@@ -396,25 +494,30 @@ module CommandMapper
|
|
396
494
|
# end
|
397
495
|
#
|
398
496
|
# @raise [ArgumentError]
|
399
|
-
# The subcommand name conflicts with a pre-existing internal method
|
497
|
+
# The subcommand name conflicts with a pre-existing internal method, or
|
498
|
+
# another option or argument.
|
400
499
|
#
|
401
500
|
def self.subcommand(name,&block)
|
402
|
-
name
|
501
|
+
name = name.to_s
|
502
|
+
method_name = name.tr('-','_')
|
503
|
+
class_name = name.split(/[_-]+/).map(&:capitalize).join
|
504
|
+
subcommand_name = method_name.to_sym
|
505
|
+
|
506
|
+
if is_internal_method?(method_name)
|
507
|
+
raise(ArgumentError,"subcommand #{name.inspect} maps to method name ##{method_name} and cannot override the internal method with same name: ##{method_name}")
|
508
|
+
elsif has_option?(subcommand_name)
|
509
|
+
raise(ArgumentError,"subcommand #{name.inspect} conflicts with another option with the same name")
|
510
|
+
elsif has_argument?(subcommand_name)
|
511
|
+
raise(ArgumentError,"subcommand #{name.inspect} conflicts with another argument with the same name")
|
512
|
+
end
|
403
513
|
|
404
514
|
subcommand_class = Class.new(Command)
|
405
515
|
subcommand_class.command(name)
|
406
516
|
subcommand_class.class_eval(&block)
|
407
517
|
|
408
|
-
|
409
|
-
class_name = name.split(/[_-]+/).map(&:capitalize).join
|
410
|
-
|
411
|
-
self.subcommands[method_name.to_sym] = subcommand_class
|
518
|
+
self.subcommands[subcommand_name] = subcommand_class
|
412
519
|
const_set(class_name,subcommand_class)
|
413
520
|
|
414
|
-
if is_internal_method?(method_name)
|
415
|
-
raise(ArgumentError,"subcommand #{name.inspect} maps to method name ##{method_name} and cannot override the internal method with same name: ##{method_name}")
|
416
|
-
end
|
417
|
-
|
418
521
|
define_method(method_name) do |&block|
|
419
522
|
if block then @command_subcommand = subcommand_class.new(&block)
|
420
523
|
else @command_subcommand
|
@@ -533,12 +636,28 @@ module CommandMapper
|
|
533
636
|
end
|
534
637
|
|
535
638
|
#
|
536
|
-
#
|
639
|
+
# Runs the command.
|
537
640
|
#
|
538
641
|
# @return [Boolean, nil]
|
539
642
|
#
|
540
643
|
def run_command
|
541
|
-
system(@command_env,*command_argv)
|
644
|
+
Kernel.system(@command_env,*command_argv)
|
645
|
+
end
|
646
|
+
|
647
|
+
#
|
648
|
+
# Spawns the command as a separate process, returning the PID of the
|
649
|
+
# process.
|
650
|
+
#
|
651
|
+
# @return [Integer]
|
652
|
+
# The PID of the new command process.
|
653
|
+
#
|
654
|
+
# @raise [Errno::ENOENT]
|
655
|
+
# The command could not be found.
|
656
|
+
#
|
657
|
+
# @since 0.2.0
|
658
|
+
#
|
659
|
+
def spawn_command
|
660
|
+
Process.spawn(@command_env,*command_argv)
|
542
661
|
end
|
543
662
|
|
544
663
|
#
|
@@ -563,7 +682,7 @@ module CommandMapper
|
|
563
682
|
end
|
564
683
|
|
565
684
|
#
|
566
|
-
#
|
685
|
+
# Runs the command through `sudo`.
|
567
686
|
#
|
568
687
|
# @param [Hash{Symbol => Object}] sudo_params
|
569
688
|
# Additional keyword arguments for {Sudo#initialize}.
|
@@ -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,29 @@ 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
|
+
def initialize(flag, name: nil, value: nil, repeats: false,
|
55
|
+
# formatting options
|
56
|
+
equals: nil,
|
57
|
+
value_in_flag: nil)
|
45
58
|
@flag = flag
|
46
59
|
@name = name || self.class.infer_name_from_flag(flag)
|
47
|
-
@equals = equals
|
48
60
|
@value = case value
|
49
61
|
when Hash then OptionValue.new(**value)
|
50
62
|
when true then OptionValue.new
|
51
63
|
end
|
52
64
|
@repeats = repeats
|
65
|
+
|
66
|
+
# formatting options
|
67
|
+
@equals = equals
|
68
|
+
@value_in_flag = value_in_flag
|
53
69
|
end
|
54
70
|
|
55
71
|
#
|
@@ -90,6 +106,15 @@ module CommandMapper
|
|
90
106
|
!@value.nil?
|
91
107
|
end
|
92
108
|
|
109
|
+
#
|
110
|
+
# Determines whether the option can be given multiple times.
|
111
|
+
#
|
112
|
+
# @return [Boolean]
|
113
|
+
#
|
114
|
+
def repeats?
|
115
|
+
@repeats
|
116
|
+
end
|
117
|
+
|
93
118
|
#
|
94
119
|
# Indicates whether the option flag and value should be separated with a
|
95
120
|
# `=` character.
|
@@ -101,12 +126,14 @@ module CommandMapper
|
|
101
126
|
end
|
102
127
|
|
103
128
|
#
|
104
|
-
#
|
129
|
+
# Indicates whether the value will be appended to the option's flag.
|
105
130
|
#
|
106
131
|
# @return [Boolean]
|
107
132
|
#
|
108
|
-
|
109
|
-
|
133
|
+
# @since 0.2.0
|
134
|
+
#
|
135
|
+
def value_in_flag?
|
136
|
+
@value_in_flag
|
110
137
|
end
|
111
138
|
|
112
139
|
#
|
@@ -269,13 +296,15 @@ module CommandMapper
|
|
269
296
|
else
|
270
297
|
string = @value.format(value)
|
271
298
|
|
272
|
-
if string.start_with?('-')
|
273
|
-
raise(ValidationError,"option #{@name} formatted value (#{string.inspect}) cannot start with a '-'")
|
274
|
-
end
|
275
|
-
|
276
299
|
if equals?
|
277
300
|
argv << "#{@flag}=#{string}"
|
301
|
+
elsif value_in_flag?
|
302
|
+
argv << "#{@flag}#{string}"
|
278
303
|
else
|
304
|
+
if string.start_with?('-')
|
305
|
+
raise(ValidationError,"option #{@name} formatted value (#{string.inspect}) cannot start with a '-'")
|
306
|
+
end
|
307
|
+
|
279
308
|
argv << @flag << string
|
280
309
|
end
|
281
310
|
end
|
@@ -14,9 +14,11 @@ module CommandMapper
|
|
14
14
|
# Specifies whether the hex value will start with `0x` or not.
|
15
15
|
#
|
16
16
|
# @param [Hash{Symbol => Object}] kwargs
|
17
|
-
# Additional keyword arguments for {
|
17
|
+
# Additional keyword arguments for {Num#initialize}.
|
18
18
|
#
|
19
|
-
def initialize(leading_zero: false)
|
19
|
+
def initialize(leading_zero: false, **kwargs)
|
20
|
+
super(**kwargs)
|
21
|
+
|
20
22
|
@leading_zero = leading_zero
|
21
23
|
end
|
22
24
|
|
@@ -46,6 +48,12 @@ module CommandMapper
|
|
46
48
|
return [false, "not in hex format (#{value.inspect})"]
|
47
49
|
end
|
48
50
|
|
51
|
+
if @range
|
52
|
+
unless @range.include?(value.to_i(16))
|
53
|
+
return [false, "unacceptable value (#{value.inspect})"]
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
49
57
|
return true
|
50
58
|
else
|
51
59
|
super(value)
|
@@ -7,6 +7,21 @@ module CommandMapper
|
|
7
7
|
#
|
8
8
|
class Num < Type
|
9
9
|
|
10
|
+
# The optional range of acceptable numbers.
|
11
|
+
#
|
12
|
+
# @return [Range, nil]
|
13
|
+
attr_reader :range
|
14
|
+
|
15
|
+
#
|
16
|
+
# Initializes the numeric value.
|
17
|
+
#
|
18
|
+
# @param [Range] range
|
19
|
+
# Specifies the range of acceptable numbers.
|
20
|
+
#
|
21
|
+
def initialize(range: nil)
|
22
|
+
@range = range
|
23
|
+
end
|
24
|
+
|
10
25
|
#
|
11
26
|
# Validates a value.
|
12
27
|
#
|
@@ -20,7 +35,7 @@ module CommandMapper
|
|
20
35
|
def validate(value)
|
21
36
|
case value
|
22
37
|
when Integer
|
23
|
-
|
38
|
+
# no-op
|
24
39
|
when String
|
25
40
|
unless value =~ /\A\d+\z/
|
26
41
|
return [false, "contains non-numeric characters (#{value.inspect})"]
|
@@ -31,6 +46,12 @@ module CommandMapper
|
|
31
46
|
end
|
32
47
|
end
|
33
48
|
|
49
|
+
if @range
|
50
|
+
unless @range.include?(value.to_i)
|
51
|
+
return [false, "unacceptable value (#{value.inspect})"]
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
34
55
|
return true
|
35
56
|
end
|
36
57
|
|