class Rototiller::Task::Command

The Command class to implement rototiller command handling

via a RototillerTask's #add_command

@since v0.1.0 @attr [String] name The name of the command to run @attr_reader [Struct] result A structured command result

contains members: output, exit_code and pid

Constants

Result

Attributes

name[RW]

@return [String] the command to be used, could be considered a default

result[R]

@return [Struct] the command results, if run

Public Class Methods

new(args={}) { |self) : send_hash_keys_as_methods_to_self(args)| ... } click to toggle source

Creates a new instance of Command, holds information about desired state of a command @param [Hash,Array<Hash>] args hashes of information about the command for block { |b| … } @yield Command object with attributes matching method calls supported by Command @return Command object

# File lib/rototiller/task/params/command.rb, line 28
def initialize(args={}, &block)
  # the env_vars that override the command name
  @env_vars      = EnvCollection.new
  @switches      = SwitchCollection.new
  @options       = OptionCollection.new
  @arguments     = ArgumentCollection.new

  block_given? ? (yield self) : send_hash_keys_as_methods_to_self(args)
  # @name is the default unless @env_vars returns something truthy
  (@name = @env_vars.last) if @env_vars.last
end

Public Instance Methods

add_argument(*args, &block) click to toggle source

adds argument to append to command.

In the Command context this Argument is added to the end of the command string

@param [Hash] args hashes of information about the argument @option args [String] :name The value to be used as the argument @option args [String] :message A message describing the use of argument

for block {|a| … } @yield [a] Optional block syntax allows you to specify information about the option, available methods match hash keys

# File lib/rototiller/task/params/command.rb, line 128
def add_argument(*args, &block)
  raise ArgumentError.new("#{__method__} takes a block or a hash") if !args.empty? && block_given?
  if block_given?
    @arguments.push(Argument.new(&block))
  else
    args.each do |arg| # we can accept an array of hashes, each of which defines a param
      error_string = "#{__method__} takes an Array of Hashes. Received Array of: '#{arg.class}'"
      raise ArgumentError.new(error_string) unless arg.is_a?(Hash)
      @arguments.push(Argument.new(arg))
    end
  end
end
add_env(*args, &block) click to toggle source

adds environment variables to be tracked, messaged.

In the Command context this env_var overrides the command "name"

@param [Hash] args hashes of information about the environment variable @option args [String] :name The environment variable @option args [String] :default The default value for the environment variable

this is optional and defaults to the parent's `:name`

@option args [String] :message A message describing the use of this variable

for block {|a| … } @yield [a] Optional block syntax allows you to specify information about the environment variable, available methods match hash keys described above

# File lib/rototiller/task/params/command.rb, line 50
def add_env(*args, &block)
  raise ArgumentError.new("#{__method__} takes a block or a hash") if !args.empty? && block_given?
  # this is kinda annoying we have to do this for all params? (not DRY)
  #   have to do it this way so EnvVar doesn't become a collection
  #   but if this gets moved to a mixin, it might be more tolerable
  if block_given?
    # send in the name of this Param, so it can be used when no default is given to add_env
    @env_vars.push(EnvVar.new({:parent_name => @name},&block))
  else
    #TODO: test this with array and non-array single hash
    args.each do |arg| # we can accept an array of hashes, each of which defines a param
      error_string = "#{__method__} takes an Array of Hashes. Received Array of: '#{arg.class}'"
      raise ArgumentError.new(error_string) unless arg.is_a?(Hash)
      # send in the name of this Param, so it can be used when no default is given to add_env
      arg[:parent_name] = @name
      @env_vars.push(EnvVar.new(arg))
    end
  end
  @name = @env_vars.last if @env_vars.last
end
add_option(*args, &block) click to toggle source

adds option to append to command (a switch with an argument)

add_option creates an option object which has its own `#add_env`, `#add_argument` methods

@param [Hash] args hashes of information about the option @option args [String] :name The value to be used as the option @option args [String] :message A message describing the use of option

for block {|a| … } @yield [a] Optional block syntax allows you to specify information about the option, available methods match hash keys

# File lib/rototiller/task/params/command.rb, line 103
def add_option(*args, &block)
  raise ArgumentError.new("#{__method__} takes a block or a hash") if !args.empty? && block_given?
  # this is kinda annoying we have to do this for all params? (not DRY)
  #   have to do it this way so EnvVar doesn't become a collection
  #   but if this gets moved to a mixin, it might be more tolerable
  if block_given?
    @options.push(Option.new(&block))
  else
    #TODO: test this with array and non-array single hash
    args.each do |arg| # we can accept an array of hashes, each of which defines a param
      error_string = "#{__method__} takes an Array of Hashes. Received Array of: '#{arg.class}'"
      raise ArgumentError.new(error_string) unless arg.is_a?(Hash)
      @options.push(Option.new(arg))
    end
  end
end
add_switch(*args, &block) click to toggle source

adds switch(es) (binary option flags) to this Command instance with optional env_var overrides @param [Hash] args hashes of information about the switch @option args [String] :name The switch string, including any '-', '–', etc @option args [String] :message A message describing the use of this variable

for block {|a| … } @yield [a] Optional block syntax allows you to specify information about the environment variable, available methods match hash keys described above

# File lib/rototiller/task/params/command.rb, line 78
def add_switch(*args, &block)
  raise ArgumentError.new("#{__method__} takes a block or a hash") if !args.empty? && block_given?
  # this is kinda annoying we have to do this for all params? (not DRY)
  #   have to do it this way so EnvVar doesn't become a collection
  #   but if this gets moved to a mixin, it might be more tolerable
  if block_given?
    @switches.push(Switch.new(&block))
  else
    #TODO: test this with array and non-array single hash
    args.each do |arg| # we can accept an array of hashes, each of which defines a param
      error_string = "#{__method__} takes an Array of Hashes. Received Array of: '#{arg.class}'"
      raise ArgumentError.new(error_string) unless arg.is_a?(Hash)
      @switches.push(Switch.new(arg))
    end
  end
end
message() click to toggle source

@return [String] formatted messages from all of Command's pieces

itself, env_vars, switches, options, arguments

TODO make private method? so that it will throw an error if yielded to?

# File lib/rototiller/task/params/command.rb, line 217
def message
  return_message = @message
  return_message += "\n" if @message && @message != ''
  return [return_message,
          @env_vars.messages,
          @switches.messages,
          @options.messages,
          @arguments.messages,
  ].join('')
end
run() { |result| ... } click to toggle source

run Command locally, capture relevent result data @return [Struct<Result>] a Result Struct with stdout, stderr, exit_code members TODO make private method? so that it will throw an error if yielded to?

# File lib/rototiller/task/params/command.rb, line 158
def run
  # make this look a bit like beaker's result class
  #   we may have to convert this to a class if it gets complex
  @result = Result.new
  @result.output = ''

  read_pipe, write_pipe = IO.pipe
  begin
    @result.pid = Process.spawn(self.to_str, :out => write_pipe, :err => write_pipe)
  rescue Errno::ENOENT => e
    $stderr.puts e
    @result.output << e.to_s
    @result.exit_code = 127
    raise
  end
  #create a thread that monitors the process and tells us when it is done
  @exitstatus = :not_done
  Thread.new do
    Process.wait(@result.pid);
    @exitstatus       = $?.exitstatus
    @result.exit_code = @exitstatus
    write_pipe.close
  end

  #FIXME: monitor for deadlock?
  while @exitstatus == :not_done
    begin
      # readpartial will read UP to amount given
      # we should never really need 64k, but it makes the responsiveness
      #   of our output much better when something is really quickly spewing output
      sixty_four_k = 65536
      this_read = read_pipe.readpartial(sixty_four_k)
    rescue EOFError
      next
    end
    @result.output << this_read
    $stdout.sync = true # print stuff right away
    print this_read
    sleep 0.001
  end

  if block_given? # if block, send result to the block
    yield @result
  end
  @result
end
stop() click to toggle source

Does this param require the task to stop Determined by the interactions between @name, @env_vars, @options, @switches, @arguments @return [true|nil] if this param requires a stop TODO make private method? so that it will throw an error if yielded to?

# File lib/rototiller/task/params/command.rb, line 209
def stop
  return true if [@switches, @options, @arguments].any?{ |collection| collection.stop? }
  return true unless @name
end
to_s()
Alias for: to_str
to_str() click to toggle source

convert a Command object to a string (runable command string) @return [String] the current value of the command string as built from its params TODO make private method? so that it will throw an error if yielded to?

# File lib/rototiller/task/params/command.rb, line 144
def to_str
  delete_nil_empty_false([
    (name if name),
    @switches.to_s,
    @options.to_s,
    @arguments.to_s
  ]).join(' ').to_s
end
Also aliased as: to_s

Private Instance Methods

delete_nil_empty_false(arg) click to toggle source

@private

# File lib/rototiller/task/params/command.rb, line 230
def delete_nil_empty_false(arg)
  arg.delete_if{ |i| ([nil, '', false].include?(i)) }
end