command_mapper 0.1.0 → 0.2.0
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 +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}.
|