command_mapper 0.1.0 → 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 +30 -0
- data/Gemfile +2 -0
- data/LICENSE.txt +1 -1
- data/README.md +57 -19
- data/lib/command_mapper/arg.rb +3 -0
- data/lib/command_mapper/argument.rb +1 -0
- data/lib/command_mapper/command.rb +153 -30
- data/lib/command_mapper/option.rb +45 -13
- data/lib/command_mapper/option_value.rb +2 -0
- data/lib/command_mapper/types/enum.rb +2 -0
- data/lib/command_mapper/types/hex.rb +15 -2
- data/lib/command_mapper/types/input_dir.rb +3 -0
- data/lib/command_mapper/types/input_file.rb +3 -0
- data/lib/command_mapper/types/input_path.rb +3 -0
- data/lib/command_mapper/types/key_value.rb +4 -1
- data/lib/command_mapper/types/key_value_list.rb +1 -1
- data/lib/command_mapper/types/list.rb +5 -0
- data/lib/command_mapper/types/map.rb +20 -2
- data/lib/command_mapper/types/num.rb +27 -1
- data/lib/command_mapper/types/type.rb +9 -1
- data/lib/command_mapper/types.rb +6 -0
- data/lib/command_mapper/version.rb +1 -1
- data/spec/commnad_spec.rb +329 -74
- data/spec/option_spec.rb +252 -1
- data/spec/types/hex_spec.rb +59 -1
- data/spec/types/map_spec.rb +20 -3
- data/spec/types/num_spec.rb +93 -3
- data/spec/types_spec.rb +48 -0
- metadata +4 -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,33 @@
|
|
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
|
+
|
16
|
+
### 0.1.2 / 2021-11-29
|
17
|
+
|
18
|
+
* Fixed a bug where {CommandMapper::Command.command_name} was not checking the
|
19
|
+
superclass for the {CommandMapper::Command.command_name command_name}, if no
|
20
|
+
`command "..."` was defined in the subclass.
|
21
|
+
|
22
|
+
### 0.1.1 / 2021-11-29
|
23
|
+
|
24
|
+
* Fixed a bug where {CommandMapper::Types::Num}, {CommandMapper::Types::Hex},
|
25
|
+
{CommandMapper::Types::Enum}, {CommandMapper::Types::InputPath},
|
26
|
+
{CommandMapper::Types::InputFile}, and {CommandMapper::Types::InputDir} were
|
27
|
+
not being required by default.
|
28
|
+
* Allow {CommandMapper::Types::Map} to accept values that have already been
|
29
|
+
mapped to a String.
|
30
|
+
|
1
31
|
### 0.1.0 / 2021-11-25
|
2
32
|
|
3
33
|
* Initial release:
|
data/Gemfile
CHANGED
data/LICENSE.txt
CHANGED
data/README.md
CHANGED
@@ -18,27 +18,47 @@ allow safely and securely executing commands.
|
|
18
18
|
* Supports defining commands as Ruby classes.
|
19
19
|
* Supports mapping in options and additional arguments.
|
20
20
|
* Supports common option types:
|
21
|
-
*
|
22
|
-
*
|
23
|
-
*
|
24
|
-
*
|
25
|
-
(aka `--opt=yes|no` or
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
*
|
30
|
-
(aka `--opt
|
31
|
-
*
|
21
|
+
* [Str][CommandMapper::Types::Str]: string values
|
22
|
+
* [Num][CommandMapper::Types::Num]: numeric values
|
23
|
+
* [Hex][CommandMapper::Types::Hex]: hexadecimal values
|
24
|
+
* [Map][CommandMapper::Types::Map]: maps `true`/`false` to `yes`/`no`, or
|
25
|
+
`enabled`/`disabled` (aka `--opt=yes|no` or
|
26
|
+
`--opt=enabled|disabled` values).
|
27
|
+
* [Enum][CommandMapper::Types::Enum]: maps a finite set of Symbols to a
|
28
|
+
finite set of Strings (aka `--opt={foo|bar|baz}` values).
|
29
|
+
* [List][CommandMapper::Types::List]: comma-separated list
|
30
|
+
(aka `--opt VALUE,...`).
|
31
|
+
* [KeyValue][CommandMapper::Types::KeyValue]: maps a Hash or Array to
|
32
|
+
key:value Strings (aka `--opt KEY:VALUE` or `--opt KEY=VALUE` values).
|
33
|
+
* [KeyValueList][CommandMapper::Types::KeyValueList]: a key-value list
|
32
34
|
(aka `--opt KEY:VALUE,...` or `--opt KEY=VALUE;...` values).
|
33
|
-
*
|
34
|
-
|
35
|
-
*
|
35
|
+
* [InputPath][CommandMapper::Types::InputPath]: a path to a pre-existing
|
36
|
+
file or directory
|
37
|
+
* [InputFile][CommandMapper::Types::InputFile]: a path to a pre-existing
|
38
|
+
file
|
39
|
+
* [InputDir][CommandMapper::Types::InputDir]: a path to a pre-existing
|
40
|
+
directory
|
36
41
|
* Supports mapping in sub-commands.
|
37
42
|
* Allows running the command via `IO.popen` to read the command's output.
|
38
43
|
* Allows running commands with additional environment variables.
|
39
44
|
* Allows overriding the command name or path to the command.
|
40
45
|
* Allows running commands via `sudo`.
|
41
|
-
* 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/
|
50
|
+
|
51
|
+
[CommandMapper::Types::Str]: https://rubydoc.info/gems/command_mapper/CommandMapper/Types/Str
|
52
|
+
[CommandMapper::Types::Num]: https://rubydoc.info/gems/command_mapper/CommandMapper/Types/Num
|
53
|
+
[CommandMapper::Types::Hex]: https://rubydoc.info/gems/command_mapper/CommandMapper/Types/Hex
|
54
|
+
[CommandMapper::Types::Map]: https://rubydoc.info/gems/command_mapper/CommandMapper/Types/Map
|
55
|
+
[CommandMapper::Types::Enum]: https://rubydoc.info/gems/command_mapper/CommandMapper/Types/Enum
|
56
|
+
[CommandMapper::Types::List]: https://rubydoc.info/gems/command_mapper/CommandMapper/Types/List
|
57
|
+
[CommandMapper::Types::KeyValue]: https://rubydoc.info/gems/command_mapper/CommandMapper/Types/KeyValue
|
58
|
+
[CommandMapper::Types::KeyValueList]: https://rubydoc.info/gems/command_mapper/CommandMapper/Types/KeyValueList
|
59
|
+
[CommandMapper::Types::InputPath]: https://rubydoc.info/gems/command_mapper/CommandMapper/Types/InputPath
|
60
|
+
[CommandMapper::Types::InputFile]: https://rubydoc.info/gems/command_mapper/CommandMapper/Types/InputFile
|
61
|
+
[CommandMapper::Types::InputDir]: https://rubydoc.info/gems/command_mapper/CommandMapper/Types/InputDir
|
42
62
|
|
43
63
|
## Examples
|
44
64
|
|
@@ -56,7 +76,7 @@ class Grep < CommandMapper::Command
|
|
56
76
|
option "--basic-regexp"
|
57
77
|
option "--perl-regexp"
|
58
78
|
option "--regexp", equals: true, value: true
|
59
|
-
option "--file", equals: true, value: true
|
79
|
+
option "--file", name: :patterns_file, equals: true, value: true
|
60
80
|
option "--ignore-case"
|
61
81
|
option "--no-ignore-case"
|
62
82
|
option "--word-regexp"
|
@@ -125,6 +145,18 @@ Defines an option with a required value:
|
|
125
145
|
option "--output", value: {required: true}
|
126
146
|
```
|
127
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
|
+
|
128
160
|
Defines an option that can be specified multiple times:
|
129
161
|
|
130
162
|
```ruby
|
@@ -137,6 +169,12 @@ Defines an option that accepts a numeric value:
|
|
137
169
|
option "--count", value: {type: Num.new}
|
138
170
|
```
|
139
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
|
+
|
140
178
|
Defines an option that accepts a comma-separated list:
|
141
179
|
|
142
180
|
```ruby
|
@@ -351,18 +389,18 @@ $ gem install command_mapper
|
|
351
389
|
### Gemfile
|
352
390
|
|
353
391
|
```ruby
|
354
|
-
gem 'command_mapper', '~> 0.
|
392
|
+
gem 'command_mapper', '~> 0.2'
|
355
393
|
```
|
356
394
|
|
357
395
|
### gemspec
|
358
396
|
|
359
397
|
```ruby
|
360
|
-
gemspec.add_dependency 'command_mapper', '~> 0.
|
398
|
+
gemspec.add_dependency 'command_mapper', '~> 0.2'
|
361
399
|
```
|
362
400
|
|
363
401
|
## License
|
364
402
|
|
365
|
-
Copyright (c) 2021 Hal Brodigan
|
403
|
+
Copyright (c) 2021-2022 Hal Brodigan
|
366
404
|
|
367
405
|
See {file:LICENSE.txt} for license information.
|
368
406
|
|
data/lib/command_mapper/arg.rb
CHANGED
@@ -6,6 +6,7 @@ module CommandMapper
|
|
6
6
|
# The base class for both {Option options} and {Argument arguments}.
|
7
7
|
#
|
8
8
|
class Arg
|
9
|
+
|
9
10
|
# The argument's arg's type.
|
10
11
|
#
|
11
12
|
# @return [Types::Type, nil]
|
@@ -18,6 +19,7 @@ module CommandMapper
|
|
18
19
|
# Specifies whether the argument is required or can be omitted.
|
19
20
|
#
|
20
21
|
# @param [Types::Type, Hash, nil] type
|
22
|
+
# The type of the arg's value.
|
21
23
|
#
|
22
24
|
# @raise [ArgumentError]
|
23
25
|
# The `type` keyword argument was given a `nil` value.
|
@@ -54,6 +56,7 @@ module CommandMapper
|
|
54
56
|
# Validates whether a given value is compatible with the arg.
|
55
57
|
#
|
56
58
|
# @param [Object] value
|
59
|
+
# The given value to validate.
|
57
60
|
#
|
58
61
|
# @return [true, (false, String)]
|
59
62
|
# Returns true if the value is valid, or `false` and a validation error
|
@@ -50,6 +50,7 @@ module CommandMapper
|
|
50
50
|
# Validates whether a given value is compatible with the arg.
|
51
51
|
#
|
52
52
|
# @param [Array<Object>, Object] value
|
53
|
+
# The given value to validate.
|
53
54
|
#
|
54
55
|
# @return [true, (false, String)]
|
55
56
|
# Returns true if the value is valid, or `false` and a validation error
|
@@ -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.
|
@@ -181,7 +207,11 @@ module CommandMapper
|
|
181
207
|
# @api semipublic
|
182
208
|
#
|
183
209
|
def self.command_name
|
184
|
-
@command_name ||
|
210
|
+
@command_name || if superclass < Command
|
211
|
+
superclass.command_name
|
212
|
+
else
|
213
|
+
raise(NotImplementedError,"#{self} did not call command(...)")
|
214
|
+
end
|
185
215
|
end
|
186
216
|
|
187
217
|
#
|
@@ -221,6 +251,23 @@ module CommandMapper
|
|
221
251
|
end
|
222
252
|
end
|
223
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
|
+
|
224
271
|
#
|
225
272
|
# Defines an option for the command.
|
226
273
|
#
|
@@ -230,10 +277,6 @@ module CommandMapper
|
|
230
277
|
# @param [Symbol, nil] name
|
231
278
|
# The option's name.
|
232
279
|
#
|
233
|
-
# @param [Boolean] equals
|
234
|
-
# Specifies whether the option's flag and value should be separated with a
|
235
|
-
# `=` character.
|
236
|
-
#
|
237
280
|
# @param [Hash, nil] value
|
238
281
|
# The option's value.
|
239
282
|
#
|
@@ -246,6 +289,14 @@ module CommandMapper
|
|
246
289
|
# @param [Boolean] repeats
|
247
290
|
# Specifies whether the option can be given multiple times.
|
248
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
|
+
#
|
249
300
|
# @api public
|
250
301
|
#
|
251
302
|
# @example Defining an option:
|
@@ -260,6 +311,9 @@ module CommandMapper
|
|
260
311
|
# @example Defining an option who's value is optional:
|
261
312
|
# option '--file', value: {required: false}
|
262
313
|
#
|
314
|
+
# @example Defining an `-Fvalue` option:
|
315
|
+
# option '--foo', value: true, value_in_flag: true
|
316
|
+
#
|
263
317
|
# @example Defining an `--opt=value` option:
|
264
318
|
# option '--foo', equals: true, value: true
|
265
319
|
#
|
@@ -270,25 +324,35 @@ module CommandMapper
|
|
270
324
|
# option '--list', value: List.new
|
271
325
|
#
|
272
326
|
# @raise [ArgumentError]
|
273
|
-
# The option flag conflicts with a pre-existing internal method
|
274
|
-
#
|
275
|
-
|
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)
|
276
335
|
option = Option.new(flag, name: name,
|
277
|
-
equals: equals,
|
278
336
|
value: value,
|
279
337
|
repeats: repeats,
|
338
|
+
# formatting options
|
339
|
+
equals: equals,
|
340
|
+
value_in_flag: value_in_flag,
|
280
341
|
&block)
|
281
342
|
|
282
|
-
self.options[option.name] = option
|
283
|
-
|
284
343
|
if is_internal_method?(option.name)
|
285
344
|
if name
|
286
345
|
raise(ArgumentError,"option #{flag.inspect} with name #{name.inspect} cannot override the internal method with same name: ##{option.name}")
|
287
346
|
else
|
288
347
|
raise(ArgumentError,"option #{flag.inspect} maps to method name ##{option.name} and cannot override the internal method with same name: ##{option.name}")
|
289
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")
|
290
353
|
end
|
291
354
|
|
355
|
+
self.options[option.name] = option
|
292
356
|
attr_accessor option.name
|
293
357
|
end
|
294
358
|
|
@@ -307,6 +371,23 @@ module CommandMapper
|
|
307
371
|
end
|
308
372
|
end
|
309
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
|
+
|
310
391
|
#
|
311
392
|
# Defines an option for the command.
|
312
393
|
#
|
@@ -333,7 +414,8 @@ module CommandMapper
|
|
333
414
|
# argument :file, required: false
|
334
415
|
#
|
335
416
|
# @raise [ArgumentError]
|
336
|
-
# 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.
|
337
419
|
#
|
338
420
|
def self.argument(name, required: true, type: Str.new, repeats: false)
|
339
421
|
name = name.to_sym
|
@@ -341,12 +423,15 @@ module CommandMapper
|
|
341
423
|
type: type,
|
342
424
|
repeats: repeats)
|
343
425
|
|
344
|
-
self.arguments[argument.name] = argument
|
345
|
-
|
346
426
|
if is_internal_method?(argument.name)
|
347
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")
|
348
432
|
end
|
349
433
|
|
434
|
+
self.arguments[argument.name] = argument
|
350
435
|
attr_accessor name
|
351
436
|
end
|
352
437
|
|
@@ -365,6 +450,23 @@ module CommandMapper
|
|
365
450
|
end
|
366
451
|
end
|
367
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
|
+
|
368
470
|
#
|
369
471
|
# Defines a subcommand.
|
370
472
|
#
|
@@ -392,25 +494,30 @@ module CommandMapper
|
|
392
494
|
# end
|
393
495
|
#
|
394
496
|
# @raise [ArgumentError]
|
395
|
-
# 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.
|
396
499
|
#
|
397
500
|
def self.subcommand(name,&block)
|
398
|
-
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
|
399
513
|
|
400
514
|
subcommand_class = Class.new(Command)
|
401
515
|
subcommand_class.command(name)
|
402
516
|
subcommand_class.class_eval(&block)
|
403
517
|
|
404
|
-
|
405
|
-
class_name = name.split(/[_-]+/).map(&:capitalize).join
|
406
|
-
|
407
|
-
self.subcommands[method_name.to_sym] = subcommand_class
|
518
|
+
self.subcommands[subcommand_name] = subcommand_class
|
408
519
|
const_set(class_name,subcommand_class)
|
409
520
|
|
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
521
|
define_method(method_name) do |&block|
|
415
522
|
if block then @command_subcommand = subcommand_class.new(&block)
|
416
523
|
else @command_subcommand
|
@@ -529,12 +636,28 @@ module CommandMapper
|
|
529
636
|
end
|
530
637
|
|
531
638
|
#
|
532
|
-
#
|
639
|
+
# Runs the command.
|
533
640
|
#
|
534
641
|
# @return [Boolean, nil]
|
535
642
|
#
|
536
643
|
def run_command
|
537
|
-
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)
|
538
661
|
end
|
539
662
|
|
540
663
|
#
|
@@ -559,7 +682,7 @@ module CommandMapper
|
|
559
682
|
end
|
560
683
|
|
561
684
|
#
|
562
|
-
#
|
685
|
+
# Runs the command through `sudo`.
|
563
686
|
#
|
564
687
|
# @param [Hash{Symbol => Object}] sudo_params
|
565
688
|
# Additional keyword arguments for {Sudo#initialize}.
|