Module: CommandLine

Defined in:
lib/dtr/command_line.rb

Defined Under Namespace

Classes: ExecutionError, OptionError

Constant Summary collapse

QUOTE_REPLACEMENT =
(Platform.family == "mswin32") ? "\"" : "\\\""
LESS_THAN_REPLACEMENT =
(Platform.family == "mswin32") ? "<" : "\\<"

Class Method Summary collapse

Class Method Details

.e(cmd, options, &proc) ⇒ Object



144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
# File 'lib/dtr/command_line.rb', line 144

def e(cmd, options, &proc)
  full_cmd = full_cmd(cmd, options, &proc)

  options[:env].each{|k,v| ENV[k]=v}
  begin
    STDOUT.puts "#{Platform.prompt} #{cmd}" if options[:stdout].nil?
    IO.popen(full_cmd, options[:mode]) do |io|
      if(block_given?)
        proc.call(io)
      else
        io.each_line do |line|
          STDOUT.puts line if options[:stdout].nil?
        end
      end
    end
  rescue Errno::ENOENT => e
    unless options[:stderr].nil?
      File.open(options[:stderr], "a") {|io| io.write(e.message)}
    else
      STDERR.puts e.message
      STDERR.puts e.backtrace.join("\n")
    end
    raise ExecutionError.new(cmd, full_cmd, options[:dir] || Dir.pwd.untaint, nil, e.message)
  ensure
    verify_exit_code(cmd, full_cmd, options)
  end
end

.execute(cmd, options = {}, &proc) ⇒ Object

Executes cmd. If the :stdout and :stderr options are specified, a line consisting of a prompt (including cmd) will be appended to the respective output streams will be appended to those files, followed by the output itself. Example:

CommandLine.execute("echo hello world", {:stdout => "stdout.log", :stderr => "stderr.log"})

will result in the following being written to stdout.log:

/Users/aslakhellesoy/scm/buildpatterns/repos/damagecontrol/trunk aslakhellesoy$ echo hello world
hello world

-and to stderr.log:

/Users/aslakhellesoy/scm/buildpatterns/repos/damagecontrol/trunk aslakhellesoy$ echo hello world

If a block is passed, the stdout io will be yielded to it (as with IO.popen). In this case the output will not be written to the stdout file (even if it’s specified):

/Users/aslakhellesoy/scm/buildpatterns/repos/damagecontrol/trunk aslakhellesoy$ echo hello world
[output captured and therefore not logged]

If the exitstatus of the command is different from the value specified by the :exitstatus option (which defaults to 0) then an ExecutionError is raised, its message containing the last 400 bytes of stderr (provided :stderr was specified)

You can also specify the :dir option, which will cause the command to be executed in that directory (default is current directory).

You can also specify a hash of environment variables in :env, which will add additional environment variables to the default environment.

Finally, you can specify several commands within one by separating them with ‘&&’ (as you would in a shell). This will result in several lines to be appended to the log (as if you had executed the commands separately).

See the unit test for more examples.



83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
# File 'lib/dtr/command_line.rb', line 83

def execute(cmd, options={}, &proc)
  raise "Can't have newline in cmd" if cmd =~ /\n/
  options = {
    :dir => Dir.pwd.untaint,
    :env => {},
    :mode => 'r',
    :exitstatus => 0
  }.merge(options)

  options[:stdout] = %{"#{File.expand_path(options[:stdout])}"}.untaint if options[:stdout]
  options[:stderr] = %{"#{File.expand_path(options[:stderr])}"}.untaint if options[:stderr]


  if options[:dir].nil?
    e(cmd, options, &proc)
  else
    Dir.chdir(options[:dir]) do
      e(cmd, options, &proc)
    end
  end
end

.full_cmd(cmd, options, &proc) ⇒ Object



108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
# File 'lib/dtr/command_line.rb', line 108

def full_cmd(cmd, options, &proc)
  commands = cmd.split("&&").collect{|c| c.strip}
  stdout_opt = options[:stdout] ? ">> #{options[:stdout]}" : ""
  stderr_opt = options[:stderr] ? "2>> #{options[:stderr]}" : ""
  capture_info_command = (block_given? && options[:stdout])? "echo [output captured and therefore not logged] >> #{options[:stdout]} && " : ""

  full_cmd = commands.collect do |c|
    escaped_command = c.gsub(/"/, QUOTE_REPLACEMENT).gsub(/</, LESS_THAN_REPLACEMENT)
    stdout_prompt_command = options[:stdout] ? "echo #{Platform.prompt} #{escaped_command} >> #{options[:stdout]} && " : ""
    stderr_prompt_command = options[:stderr] ? "echo #{Platform.prompt} #{escaped_command} >> #{options[:stderr]} && " : ""
    redirected_command = block_given? ? "#{c} #{stderr_opt}" : "#{c} #{stdout_opt} #{stderr_opt}"

    stdout_prompt_command + capture_info_command + stderr_prompt_command + redirected_command
  end.join(" && ")
  full_cmd.untaint
end

.verify_exit_code(cmd, full_cmd, options) ⇒ Object



126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
# File 'lib/dtr/command_line.rb', line 126

def verify_exit_code(cmd, full_cmd, options)
  if($?.exitstatus != options[:exitstatus])
    error_message = "#{options[:stderr]} doesn't exist"
    if options[:stderr] && File.exist?(options[:stderr])
      File.open(options[:stderr]) do |errio|
        begin
          errio.seek(-1200, IO::SEEK_END)
        rescue Errno::EINVAL
          # ignore - it just means we didn't have 400 bytes.
        end
        error_message = errio.read
      end
    end
    raise ExecutionError.new(cmd, full_cmd, options[:dir] || Dir.pwd, $?.exitstatus, error_message)
  end
end