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 | 
             
                  #
         |