Class: CommandMapper::Command

Inherits:
Object
  • Object
show all
Includes:
Types
Defined in:
lib/command_mapper/command.rb

Overview

Base class for all mapped commands.

Direct Known Subclasses

Sudo

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Types

Type

Constructor Details

#initialize(params = {}, command_name: self.class.command_name, command_path: nil, command_env: {}, **kwargs) {|self| ... } ⇒ Command

Initializes the command.

Examples:

with a symbol Hash

MyCommand.new({foo: 'bar', baz: 'qux'})

with a keyword arguments

MyCommand.new(foo: 'bar', baz: 'qux')

with a custom env Hash:

MyCommand.new({foo: 'bar', baz: 'qux'}, env: {'FOO' =>'bar'})
MyCommand.new(foo: 'bar', baz: 'qux', env: {'FOO' => 'bar'})

Parameters:

  • params (Hash{Symbol => Object}) (defaults to: {})

    The option and argument values.

  • command_name (String) (defaults to: self.class.command_name)

    Overrides the command with a custom command name.

  • command_path (String, nil) (defaults to: nil)

    Overrides the command with a custom path to the command.

  • command_env (Hash{String => String}) (defaults to: {})

    Custom environment variables to pass to the command.

  • kwargs (Hash{Symbol => Object})

    Additional keywords arguments. These will be used to populate #options and #arguments, along with params.

Yields:

  • (self)

    The newly initialized command.

Yield Parameters:


71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
# File 'lib/command_mapper/command.rb', line 71

def initialize(params={}, command_name: self.class.command_name,
                          command_path: nil,
                          command_env:  {},
                          **kwargs)
  @command_name = command_name
  @command_path = command_path
  @command_env  = command_env

  params = params.merge(kwargs)

  params.each do |name,value|
    self[name] = value
  end

  yield self if block_given?
end

Instance Attribute Details

#command_envHash{String => String} (readonly)

The environment variables to execute the command with.

Returns:

  • (Hash{String => String})

30
31
32
# File 'lib/command_mapper/command.rb', line 30

def command_env
  @command_env
end

#command_nameString (readonly)

The command name.

Returns:

  • (String)

20
21
22
# File 'lib/command_mapper/command.rb', line 20

def command_name
  @command_name
end

#command_pathString? (readonly)

The optional path to the command.

Returns:

  • (String, nil)

25
26
27
# File 'lib/command_mapper/command.rb', line 25

def command_path
  @command_path
end

#command_subcommandCommand? (readonly)

The subcommand's options and arguments.

Returns:


35
36
37
# File 'lib/command_mapper/command.rb', line 35

def command_subcommand
  @command_subcommand
end

Class Method Details

.argument(name, required: true, type: Str.new, repeats: false) ⇒ Object

Defines an option for the command.

Examples:

Define an argument:

argument :file

Define an argument that can be specified multiple times:

argument :files, repeats: true

Define an optional argument:

argument :file, required: false

Parameters:

  • name (Symbol)
  • required (Boolean) (defaults to: true)

    Specifies whether the argument is required or can be omitted.

  • type (Types::Type, Hash, nil) (defaults to: Str.new)

    The explicit type for the argument.

  • repeats (Boolean) (defaults to: false)

    Specifies whether the option can be repeated multiple times.

Raises:

  • (ArgumentError)

    The argument name conflicts with a pre-existing internal method, or another option or subcommand.


435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
# File 'lib/command_mapper/command.rb', line 435

def self.argument(name, required: true, type: Str.new, repeats: false)
  name     = name.to_sym
  argument = Argument.new(name, required: required,
                                type:     type,
                                repeats:  repeats)

  if is_internal_method?(argument.name)
    raise(ArgumentError,"argument #{name.inspect} cannot override internal method with same name: ##{argument.name}")
  elsif has_option?(argument.name)
    raise(ArgumentError,"argument #{name.inspect} conflicts with another option with the same name")
  elsif has_subcommand?(argument.name)
    raise(ArgumentError,"argument #{name.inspect} conflicts with another subcommand with the same name")
  end

  self.arguments[argument.name] = argument
  attr_accessor name
end

.argumentsHash{Symbol => Argument}

All defined options.

Returns:

  • (Hash{Symbol => Argument})

    The mapping of argument names and Argument objects.


381
382
383
384
385
386
387
# File 'lib/command_mapper/command.rb', line 381

def self.arguments
  @arguments ||= if superclass < Command
                   superclass.arguments.dup
                 else
                   {}
                 end
end

.capture(params = {}, **kwargs) {|command| ... } ⇒ String

Initializes and runs the command in a shell and captures all stdout output.

Parameters:

  • params (Hash{Symbol => Object}) (defaults to: {})

    The option and argument values.

  • kwargs (Hash{Symbol => Object})

    Additional keywords arguments. These will be used to populate #options and #arguments, along with params.

Yields:

  • (command)

    The newly initialized command.

Yield Parameters:

Returns:

  • (String)

    The stdout output of the command.


158
159
160
161
# File 'lib/command_mapper/command.rb', line 158

def self.capture(params={},**kwargs,&block)
  command = new(params,**kwargs,&block)
  command.capture_command
end

.command(new_command_name) {|self| ... } ⇒ Object

Examples:

command 'grep'
# ...
command 'grep' do
  option "--regexp", equals: true, value: true
  # ...
end

Parameters:

  • new_command_name (#to_s)

Yields:

  • (self)

248
249
250
251
# File 'lib/command_mapper/command.rb', line 248

def self.command(new_command_name,&block)
  @command_name = new_command_name.to_s.freeze
  yield self if block_given?
end

.command_nameString

Gets or sets the command name.

Parameters:

  • new_name (#to_s)

    The optional new command name.

Returns:

  • (String)

    The command name.

Raises:

  • (NotImplementedError)

    The command class did not call command.


223
224
225
226
227
228
229
# File 'lib/command_mapper/command.rb', line 223

def self.command_name
  @command_name || if superclass < Command
                     superclass.command_name
                   else
                     raise(NotImplementedError,"#{self} did not call command(...)")
                   end
end

.has_argument?(name) ⇒ Boolean

Determines if an argument with the given name has been defined.

Parameters:

  • name (Symbol)

    The given name.

Returns:

  • (Boolean)

    Specifies whether an argument with the given name has been defined.

Since:

  • 0.2.0


402
403
404
# File 'lib/command_mapper/command.rb', line 402

def self.has_argument?(name)
  arguments.has_key?(name)
end

.has_option?(name) ⇒ Boolean

Determines if an option with the given name has been defined.

Parameters:

  • name (Symbol)

    The given name.

Returns:

  • (Boolean)

    Specifies whether an option with the given name has been defined.

Since:

  • 0.2.0


281
282
283
# File 'lib/command_mapper/command.rb', line 281

def self.has_option?(name)
  options.has_key?(name)
end

.has_subcommand?(name) ⇒ Boolean

Determines if a subcommand with the given name has been defined.

Parameters:

  • name (Symbol)

    The given name.

Returns:

  • (Boolean)

    Specifies whether a subcommand with the given name has been defined.

Since:

  • 0.2.0


482
483
484
# File 'lib/command_mapper/command.rb', line 482

def self.has_subcommand?(name)
  subcommands.has_key?(name)
end

.option(flag, name: nil, value: nil, repeats: false, equals: nil, value_in_flag: nil, &block) ⇒ Object

Defines an option for the command.

Examples:

Defining an option:

option '--foo'

Defining an option with a custom name:

option '-F', name: :foo

Defining an option who's value is required:

option '--file', value: true

Defining an option who's value is optional:

option '--file', value: {required: false}

Defining an -Fvalue option:

option '--foo', value: true, value_in_flag: true

Defining an --opt=value option:

option '--foo', equals: true, value: true

Defining an option that can be repeated multiple times:

option '--foo', repeats: true

Defining an option that takes a comma-separated list:

option '--list', value: List.new

Parameters:

  • flag (String)

    The option's command-line flag.

  • name (Symbol, nil) (defaults to: nil)

    The option's name.

  • value (Hash, nil) (defaults to: nil)

    The option's value.

  • repeats (Boolean) (defaults to: false)

    Specifies whether the option can be given multiple times.

  • equals (Boolean) (defaults to: nil)

    Specifies whether the option's flag and value should be separated with a = character.

  • value_in_flag (Boolean) (defaults to: nil)

    Specifies that the value should be appended to the option's flag (ex: -Fvalue).

Options Hash (value:):

  • :required (Boolean)

    Specifies whether the option requires a value or not.

  • :type (Types:Type, Hash, nil)

    The explicit type for the option's value.

Raises:

  • (ArgumentError)

    The option flag conflicts with a pre-existing internal method, or another argument or subcommand.


344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
# File 'lib/command_mapper/command.rb', line 344

def self.option(flag, name: nil, value: nil, repeats: false,
                      # formatting options
                      equals:        nil,
                      value_in_flag: nil,
                      &block)
  option = Option.new(flag, name:    name,
                            value:   value,
                            repeats: repeats,
                            # formatting options
                            equals:        equals,
                            value_in_flag: value_in_flag,
                            &block)

  if is_internal_method?(option.name)
    if name
      raise(ArgumentError,"option #{flag.inspect} with name #{name.inspect} cannot override the internal method with same name: ##{option.name}")
    else
      raise(ArgumentError,"option #{flag.inspect} maps to method name ##{option.name} and cannot override the internal method with same name: ##{option.name}")
    end
  elsif has_argument?(option.name)
    raise(ArgumentError,"option #{flag.inspect} with name #{option.name.inspect} conflicts with another argument with the same name")
  elsif has_subcommand?(option.name)
    raise(ArgumentError,"option #{flag.inspect} with name #{option.name.inspect} conflicts with another subcommand with the same name")
  end

  self.options[option.name] = option
  attr_accessor option.name
end

.optionsHash{Symbol => Option}

All defined options.

Returns:


260
261
262
263
264
265
266
# File 'lib/command_mapper/command.rb', line 260

def self.options
  @options ||= if superclass < Command
                 superclass.options.dup
               else
                 {}
               end
end

.popen(params = {}, mode: 'r', **kwargs) {|command| ... } ⇒ IO

Initializes and executes the command and returns an IO object to it.

Parameters:

  • params (Hash{Symbol => Object}) (defaults to: {})

    The option and argument values.

  • mode (String) (defaults to: 'r')

    The IO "mode" to open the IO pipe in.

  • kwargs (Hash{Symbol => Object})

    Additional keywords arguments. These will be used to populate #options and #arguments, along with params.

Yields:

  • (command)

    The newly initialized command.

Yield Parameters:

Returns:

  • (IO)

183
184
185
186
# File 'lib/command_mapper/command.rb', line 183

def self.popen(params={}, mode: 'r', **kwargs,&block)
  command = new(params,**kwargs,&block)
  command.popen_command
end

.run(params = {}, **kwargs) {|command| ... } ⇒ Boolean?

Initializes and runs the command.

Parameters:

  • params (Hash{Symbol => Object}) (defaults to: {})

    The option and argument values.

  • kwargs (Hash{Symbol => Object})

    Additional keywords arguments. These will be used to populate #options and #arguments, along with params.

Yields:

  • (command)

    The newly initialized command.

Yield Parameters:

Returns:

  • (Boolean, nil)

105
106
107
108
# File 'lib/command_mapper/command.rb', line 105

def self.run(params={},**kwargs,&block)
  command = new(params,**kwargs,&block)
  command.run_command
end

.spawn(params = {}, **kwargs) {|command| ... } ⇒ Integer

Initializes and spawns the command as a separate process, returning the PID of the process.

Parameters:

  • params (Hash{Symbol => Object}) (defaults to: {})

    The option and argument values.

  • kwargs (Hash{Symbol => Object})

    Additional keywords arguments. These will be used to populate #options and #arguments, along with params.

Yields:

  • (command)

    The newly initialized command.

Yield Parameters:

Returns:

  • (Integer)

    The PID of the new command process.

Raises:

  • (Errno::ENOENT)

    The command could not be found.

Since:

  • 0.2.0


134
135
136
137
# File 'lib/command_mapper/command.rb', line 134

def self.spawn(params={},**kwargs,&block)
  command = new(params,**kwargs,&block)
  command.spawn_command
end

.subcommand(name) {|subcommand| ... } ⇒ Object

Note:

Also defines a class within the command class using the subcommand's name.

Defines a subcommand.

Examples:

Defining a sub-command:

class Git
  command 'git' do
    subcommand 'clone' do
      option '--bare'
      # ...
    end
  end
end

Parameters:

  • name (String)

    The name of the subcommand.

Yields:

  • (subcommand)

    The given block will be used to populate the subcommand's options.

Yield Parameters:

  • subcommand (Command)

    The newly created subcommand class.

Raises:

  • (ArgumentError)

    The subcommand name conflicts with a pre-existing internal method, or another option or argument.


516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
# File 'lib/command_mapper/command.rb', line 516

def self.subcommand(name,&block)
  name            = name.to_s
  method_name     = name.tr('-','_')
  class_name      = name.split(/[_-]+/).map(&:capitalize).join
  subcommand_name = method_name.to_sym

  if is_internal_method?(method_name)
    raise(ArgumentError,"subcommand #{name.inspect} maps to method name ##{method_name} and cannot override the internal method with same name: ##{method_name}")
  elsif has_option?(subcommand_name)
    raise(ArgumentError,"subcommand #{name.inspect} conflicts with another option with the same name")
  elsif has_argument?(subcommand_name)
    raise(ArgumentError,"subcommand #{name.inspect} conflicts with another argument with the same name")
  end

  subcommand_class = Class.new(Command)
  subcommand_class.command(name)
  subcommand_class.class_eval(&block)

  self.subcommands[subcommand_name] = subcommand_class
  const_set(class_name,subcommand_class)

  define_method(method_name) do |&block|
    if block then @command_subcommand = subcommand_class.new(&block)
    else          @command_subcommand
    end
  end

  define_method(:"#{method_name}=") do |options|
    @command_subcommand = if options
                            subcommand_class.new(options)
                          end
  end
end

.subcommandsHash{Symbol => Command.class}

All defined subcommands.

Returns:

  • (Hash{Symbol => Command.class})

    The mapping of subcommand names and subcommand classes.


461
462
463
464
465
466
467
# File 'lib/command_mapper/command.rb', line 461

def self.subcommands
  @subcommands ||= if superclass < Command
                     superclass.subcommands.dup
                   else
                     {}
                   end
end

.sudo(params = {}, sudo: {}, **kwargs) {|command| ... } ⇒ Boolean?

Initializes and runs the command through sudo.

Parameters:

  • params (Hash{Symbol => Object}) (defaults to: {})

    The option and argument values.

  • kwargs (Hash{Symbol => Object})

    Additional keyword arguments for #initialize.

Yields:

  • (command)

    The newly initialized command.

Yield Parameters:

Returns:

  • (Boolean, nil)

204
205
206
207
# File 'lib/command_mapper/command.rb', line 204

def self.sudo(params={}, sudo: {}, **kwargs,&block)
  command = new(params,**kwargs,&block)
  command.sudo_command(**sudo)
end

Instance Method Details

#[](name) ⇒ Object

Gets the value of an option or an argument.

Parameters:

  • name (Symbol)

    The name of the option, argument, or subcommand.

Returns:

  • (Object)

    The value of the option, argument, or subcommand.

Raises:

  • (ArgumentError)

    The given name was not match any option or argument.


562
563
564
565
566
567
568
569
570
# File 'lib/command_mapper/command.rb', line 562

def [](name)
  name = name.to_s

  if respond_to?(name)
    send(name)
  else
    raise(ArgumentError,"#{self.class} does not define ##{name}")
  end
end

#[]=(name, value) ⇒ Object

Sets an option or an argument with the given name.

Parameters:

  • name (Symbol)

    The name of the option, argument, or subcommand.

  • value (Object)

    The new value for the option, argument, or subcommand.

Returns:

  • (Object)

    The new value for the option, argument, or subcommand.

Raises:

  • (ArgumentError)

    The given name was not match any option or argument.


587
588
589
590
591
592
593
# File 'lib/command_mapper/command.rb', line 587

def []=(name,value)
  if respond_to?("#{name}=")
    send("#{name}=",value)
  else
    raise(ArgumentError,"#{self.class} does not define ##{name}=")
  end
end

#capture_commandString

Runs the command in a shell and captures all stdout output.

Returns:

  • (String)

    The stdout output of the command.


695
696
697
# File 'lib/command_mapper/command.rb', line 695

def capture_command
  `#{command_string}`
end

#command_argvArray<String>

Returns an Array of command-line arguments for the command.

Returns:

  • (Array<String>)

    The formatted command-line arguments.

Raises:

  • (ArgumentReqired)

    A required argument was not set.


604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
# File 'lib/command_mapper/command.rb', line 604

def command_argv
  argv = [@command_path || @command_name]

  self.class.options.each do |name,option|
    unless (value = self[name]).nil?
      option.argv(argv,value)
    end
  end

  if @command_subcommand
    # a subcommand takes precedence over any command arguments
    argv.concat(@command_subcommand.command_argv)
  else
    additional_args = []

    self.class.arguments.each do |name,argument|
      value = self[name]

      if value.nil?
        if argument.required?
          raise(ArgumentRequired,"argument #{name} is required")
        end
      else
        argument.argv(additional_args,value)
      end
    end

    if additional_args.any? { |arg| arg.start_with?('-') }
      # append a '--' separator if any of the arguments start with a '-'
      argv << '--'
    end

    argv.concat(additional_args)
  end

  return argv
end

#command_stringString

Escapes any shell control-characters so that it can be ran in a shell.

Returns:

  • (String)

    The shell-escaped command.


648
649
650
651
652
653
654
655
656
657
658
659
660
# File 'lib/command_mapper/command.rb', line 648

def command_string
  escaped_command = Shellwords.shelljoin(command_argv)

  unless @command_env.empty?
    escaped_env = @command_env.map { |name,value|
      "#{Shellwords.shellescape(name)}=#{Shellwords.shellescape(value)}"
    }.join(' ')

    escaped_command = "#{escaped_env} #{escaped_command}"
  end

  return escaped_command
end

#popen_command(mode = nil) ⇒ IO

Executes the command and returns an IO object to it.

Returns:

  • (IO)

    The IO object for the command's STDIN.


705
706
707
708
709
# File 'lib/command_mapper/command.rb', line 705

def popen_command(mode=nil)
  if mode then IO.popen(@command_env,command_argv,mode)
  else         IO.popen(@command_env,command_argv)
  end
end

#run_commandBoolean?

Runs the command.

Returns:

  • (Boolean, nil)

    Indicates whether the command exited successfully or not. nil indicates the command could not be found.


669
670
671
# File 'lib/command_mapper/command.rb', line 669

def run_command
  Kernel.system(@command_env,*command_argv)
end

#spawn_commandInteger

Spawns the command as a separate process, returning the PID of the process.

Returns:

  • (Integer)

    The PID of the new command process.

Raises:

  • (Errno::ENOENT)

    The command could not be found.

Since:

  • 0.2.0


685
686
687
# File 'lib/command_mapper/command.rb', line 685

def spawn_command
  Process.spawn(@command_env,*command_argv)
end

#sudo_command(**sudo_kwargs, &block) ⇒ Boolean?

Runs the command through sudo.

Parameters:

  • sudo_params (Hash{Symbol => Object})

    Additional keyword arguments for #initialize.

Returns:

  • (Boolean, nil)

    Indicates whether the command exited successfully or not. nil indicates the command could not be found.


721
722
723
724
725
# File 'lib/command_mapper/command.rb', line 721

def sudo_command(**sudo_kwargs,&block)
  sudo_params = sudo_kwargs.merge(command: command_argv)

  Sudo.run(sudo_params, command_env: @command_env, &block)
end

#to_aObject

See Also:

  • #argv

730
731
732
# File 'lib/command_mapper/command.rb', line 730

def to_a
  command_argv
end

#to_sObject

See Also:

  • #shellescape

737
738
739
# File 'lib/command_mapper/command.rb', line 737

def to_s
  command_string
end