Well, I figured it out. Just in case anyone is interested, here is the
complete code I’m using. This executes the code in the code'' parameter (a string), supplying the strings in the
input’’ array as user input when
requested, and it returns the output in a string. (The variable ``@@INPUT’'
is just a string I am using to help me to distinguish output from echoed
input.)
The idea was to create a sufficiently robust execution environment for
running code samples and showing what those code samples do if run on the
command line. (Of course, “sufficiently” is a moving target… or maybe a
shrinking target.) I wanted as few side-effects as possible (since I’m
running this in mod_ruby). Aside from a tutorial, I think this might be
nice to incorporate into a ruby-based wiki… but I don’t know much about
$SAFE levels and all that.
It’s my dream that someday we’ll have a wiki-based documentation of the
built-in classes and standard libraries, along with live code examples, so
that we can all help to keep the docs up-to-date. (The Pickaxe reference
section, while wonderful, is slowly falling out of date… it would be nice
to have a more-or-less “definitive” place where we could keep up-to-date
docs.) Anyway, I need to finish this tutorial first…
So here’s the code. It handles errors (including parse errors), and
all-in-all seems to work pretty well. There are a few sticky areas when
running from mod_ruby… basically, this code acts like you are running it
in mod_ruby (go figure!) instead of on the command line.
def executeCode (code, input)
# Wrap code to catch errors and to stop SystemExit.
code = <<-END_CODE
begin
#{code}
rescue SystemExit
rescue Exception => error
puts error.inspect
end
END_CODE
strIO = StringIO.new
if !input.empty?
input = input.join("\n")+"\n"
input = StringIO.new (input, "r")
class << strIO; self; end.module_eval do
['gets', 'getc', 'read'].each do |meth|
define_method(meth) do |*params|
inStr = input.method(meth).call(*params)
puts @@INPUT+inStr.chomp+(@@INPUT.reverse) # Echo input.
inStr
end
end
end
end
# Pass these methods to strIO:
kernelMethods = ['puts', 'putc', 'gets']
# Swap out Kernel methods...
kernelMethods.each do |meth|
Kernel.module_eval "alias __temp__tutorial__#{meth}__ #{meth}"
Kernel.module_eval do
define_method(meth) do |*params|
strIO.method(meth).call(*params)
end
end
end
begin
strIO.instance_eval code
rescue Exception => error # Catch parse errors.
return error.inspect
end
# ...and swap them back in.
kernelMethods.each do |meth|
Kernel.module_eval "alias #{meth} __temp__tutorial__#{meth}__"
end
strIO.string
end
Thoughts? Suggestions? Errors?
Chris