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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6b0bd1d63ca9619bc3d0c0bd3729e1d5a3e0de9d7291f1e92a24f0c4d7fc6e26
4
- data.tar.gz: 3f5c08cc90eb514a75ab6333b2e76f7e9a6dd5f3ec33502e5b8c23e8d60c03ed
3
+ metadata.gz: 7f40de593c4e0124a5c3075b9e89ec08e38fe7451af1ede8fc62030b89b694ec
4
+ data.tar.gz: '0392b215a5dff4c4b2bffd34ec7df72fece82adbe7d260a975bd909edb9f962d'
5
5
  SHA512:
6
- metadata.gz: b42311e2b1453ce2c2295ad1cf3e893a205239287dff21566240a721ac45fd9dc16332c34eb8e9beca88053655d88f0d265e8c4a767a58f803f38e318d263e98
7
- data.tar.gz: 7981d9e48540297c7282cef7800015933d1fa5820ac0378bc71faa36adec6a4f321160beb8a10d5e7b9a5b860235a6d8407001f9bfc6935ac31d03802bf1f7d1
6
+ metadata.gz: f5c09b88caea6446ac20238bbba137accd62552c413e5fa67d9dce9a62f2e3f027624c6a951ce59fc6c720f2683e80dcbe5d3e8942d85f472e5d25e12f5e5a85
7
+ data.tar.gz: a8e1b267f3628fd23d2924f9d6f25534744af6c2af8a80d5cc3b3fa63bbdc1f35853dcda99871db67cbeea6f6824f3ed77a19b33bd8bd1bb1f5ca7750abf8a7b
data/.document ADDED
@@ -0,0 +1,3 @@
1
+ -
2
+ ChangeLog.*
3
+ LICENSE.txt
@@ -11,7 +11,8 @@ jobs:
11
11
  ruby:
12
12
  - 2.6
13
13
  - 2.7
14
- - 3.0
14
+ - '3.0'
15
+ - 3.1
15
16
  - jruby
16
17
  - truffleruby
17
18
  name: Ruby ${{ matrix.ruby }}
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
@@ -7,7 +7,9 @@ group :development do
7
7
  gem 'rubygems-tasks', '~> 0.2'
8
8
  gem 'rspec', '~> 3.0'
9
9
  gem 'simplecov', '~> 0.20', require: false
10
+
10
11
  gem 'kramdown'
12
+ gem 'redcarpet', platform: :mri
11
13
  gem 'yard', '~> 0.9'
12
14
  gem 'yard-spellcheck'
13
15
 
data/LICENSE.txt CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2021 Hal Brodigan
1
+ Copyright (c) 2021-2022 Hal Brodigan
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
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.1'
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.1'
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
 
@@ -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]
@@ -109,7 +109,33 @@ module CommandMapper
109
109
  end
110
110
 
111
111
  #
112
- # Runs the command in a shell and captures all stdout output.
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
- # Executes the command and returns an IO object to it.
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
- def self.option(flag, name: nil, equals: nil, value: nil, repeats: false, &block)
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 = name.to_s
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
- method_name = name.tr('-','_')
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
- # Initializes and runs the command.
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
- # Initializes and runs the command through sudo.
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
- def initialize(flag, name: nil, equals: nil, value: nil, repeats: false)
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
- # Determines whether the option can be given multiple times.
129
+ # Indicates whether the value will be appended to the option's flag.
105
130
  #
106
131
  # @return [Boolean]
107
132
  #
108
- def repeats?
109
- @repeats
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 {Type#initialize}.
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)
@@ -4,6 +4,8 @@ module CommandMapper
4
4
  module Types
5
5
  class Map < Type
6
6
 
7
+ # The map of values to Strings.
8
+ #
7
9
  # @return [Hash{Object => String}]
8
10
  attr_reader :map
9
11
 
@@ -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
- return true
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
 
@@ -1,4 +1,4 @@
1
1
  module CommandMapper
2
2
  # Version of command_mapper
3
- VERSION = '0.1.2'
3
+ VERSION = '0.2.0'
4
4
  end