mysociety/alaveteli

View on GitHub
lib/alaveteli_external_command.rb

Summary

Maintainability
A
2 hrs
Test Coverage
# -*- encoding : utf-8 -*-
require 'external_command'

module AlaveteliExternalCommand
  class << self
    # Final argument can be a hash of options.
    # Valid options are:
    # :append_to - string to append the output of the process to
    # :append_errors_to - string to append the errors produced by the process to
    # :stdin_string - stdin string to pass to the process
    # :binary_output - boolean flag for treating the output as binary or text encoded with
    #                   the default external encoding (only significant in ruby 1.9 and above)
    # :binary_input - boolean flag for treating the input as binary or as text encoded with
    #                   the default external encoding (only significant in ruby 1.9 and above)
    # :memory_limit - maximum amount of memory (in bytes) available to the process
    # :timeout - maximum amount of time (in s) to allow the process to run for
    # :env - hash of environment variables to set for the process
    def run(program_name, *args)
      # Run an external program, and return its output.
      # Standard error is suppressed unless the program
      # fails (i.e. returns a non-zero exit status).
      # If the program fails, returns nil and writes any error to stderr.
      # TODO: calling code should be able to specify error stream - may want to log it or
      # otherwise act upon it.
      opts = {}
      if !args.empty? && args.last.is_a?(Hash)
        opts = args.last
      end

      program_path = find_program(program_name)
      xc = ExternalCommand.new(program_path, *args)
      begin
        xc.run
      rescue ExternalCommand::ChildUnterminated => e
        $stderr.puts(e.message)
        return nil
      end

      if !xc.exited
        # Crash or timeout
        if xc.timed_out
          $stderr.puts(%Q[External Command: "#{program_name} #{args.join(' ')}" timed out at #{opts[:timeout]}s])
        else
          $stderr.puts(%Q[External Command: "#{program_name} #{args.join(' ')}" exited abnormally])
        end
        $stderr.print(xc.err)
        return nil

      elsif xc.status != 0
        # Error
        $stderr.puts(%Q[External Command: Error from command "#{program_name} #{args.join(' ')}":])
        $stderr.print(xc.err)
        return nil
      else
        if opts.has_key? :append_to
          opts[:append_to] << "\n\n"
        else

          return xc.out
        end
      end
    end

    def find_program(program_name)
      if program_name =~ %r(^/)
        return program_name
      else
        search_path = AlaveteliConfiguration::utility_search_path
        search_path.each do |d|
          program_path = File.join(d, program_name)
          return program_name if File.file? program_path and File.executable? program_path
        end
        raise "Could not find #{program_name} in any of #{search_path.join(', ')}"
      end
    end
  end
end