views:

471

answers:

3

I am writing a web-based dev-console for Rails development. In one of my controller actions, I am calling Rake, but I am unable to capture any of the output that Rake generates. For example, here is some sample code, from the controller:

require 'rake'
require 'rake/rdoctask'
require 'rake/testtask'
require 'tasks/rails'
require 'stringio'

...

def show_routes

  @results = capture_stdout { Rake.tasks['routes'].invoke }

  # @results is nil -- the capture_stdout doesn't catpure anything that Rake generates

end

def capture_stdout
  s = StringIO.new
  $stdout = s
  yield
  s.string
ensure
  $stdout = STDOUT
end

Does anybody know why I can't capture the Rake output? I've tried going through the Rake source, and I can't see where it fires a new process or anything, so I think I ought to be able to do this.

Many thanks! Adrian


I have since discovered the correct way to call Rake from inside Ruby that works much better:

Rake.application['db:migrate:redo'].reenable
Rake.application['db:migrate:redo'].invoke

Strangely, some rake tasks work perfectly now (routes), some capture the output the first time the run and after that are always blank (db:migrate:redo) and some don't seem to ever capture output (test). Odd.

A: 

Well, I know ZenTest is able to capture "puts"-statements through "until_capture" and I even found someone who wrote about a custom implementation by extending the Kernel module and redirecting $stdout to an instance of StringIO.

Hope that helps:
http://thinkingdigitally.com/archive/capturing-output-from-puts-in-ruby/

You may also want to check out OutputCatcher by Matthias Hennemeyer:
http://github.com/mhennemeyer/output%5Fcatcher

rubiii
Thanks for your suggestion. Unfortunately, capturing stdio in the kernel module seems to give exactly the same results as I've been getting with my local capture_output method.
+2  A: 

While this is some kind of a hack (since Rack is written in ruby), you could use open3's popen3 method and invoke the rake task like you would from the commandline.

In your case you would use it like that

require 'open3'

buffer = []
Open3::popen3("rake db:migrate:redo") do |stdin,stdout,stderr|
  begin
    while line = stdout.readline
      buffer << line
    end
  rescue
  end
end

So you would end up with all lines that rake returned to stdout in buffer. Maybe this works more reliable, though I would recommend investigating the source of the odd behaviour you describe.

Lennart
+1  A: 

Line 1174 of gems/rake-0.8.7/lib/rake.rb:

  # Send the message to the default rake output (which is $stderr).
  def rake_output_message(message)
    $stderr.puts(message)
  end
  private :rake_output_message

Perhaps try capturing STDERR as well as STDOUT?

I'm not saying that all output is done through $stderr, but rake itself seems to have set up that convention, perhaps somewhere along the line someone made certain rake tasks overwrite puts to direct to $stderr.

I haven't tested the idea.

Tim Snowhite