Irb is a Text Adventure
December 16th, 2009
Ruby's flavor of classed OO offers a thoughtful balance of rigidity and flexibility along with plenty of introspection and reflection. Over time I have come to experience this model less as a framework for thought and more as a living thing with which to converse. Dave Thomas brought up the prospect of a Ruby object browser over nine years ago. That thread raised questions that still haven't been answered.
Unfortunately for a browser the rigidity offered by Ruby is optional. I personally feel that HTML with links is the only graphical form that comes close to capturing the needed range. I just described a blank slate for a digraph so feel free to think it's lame. The object browser is possible and desirable but after all these years Irb is still the Ruby browser of choice.
My take is that every object in Irb is like a noun in a text adventure, and likewise every object is different. There are common verbs and uncommon verbs, and not every noun supports every verb. The components of trial, error, failure, and reward are the same. Nothing ruins my suspension of disbelief like having to consult documentation, and I like to believe that I am programming for as long as possible. The notable missing link to the underground world of Zork is "look". Here is "look". It is nothing more than a mashup of introspection methods but I've found it indispensable.
# Examples:
# foo.look
# foo.class.look
# foo.method(:meth).look
# foo.method(:meth).source_context
# foo.instance_variable_hash
Object.class_eval do
def instance_variable_hash
ret = {}
instance_variables.each { |v|
ret[v] = instance_variable_get(v)
}
ret
end
def look
{
:class => self.class,
:interesting_methods => (methods - Object.new.methods).sort,
:instance_variables => instance_variables.sort,
}
end
end
Class.class_eval do
def look
{
:class => self.class,
:ancestors => ancestors,
:included_modules => included_modules,
:interesting_methods => methods - Class.new.methods,
:instance_methods => instance_methods(false),
:class_variables => class_variables,
:instance_variables => instance_variables,
:constants => constants,
:source_files => methods(false).collect { |m| method(m).source_location.try(:first) }.compact.sort.uniq,
}
end
end
Module.class_eval do
def look
{
:class => self.class,
:ancestors => ancestors,
:included_modules => included_modules,
:class_variables => class_variables,
:interesting_methods => methods - Class.new.methods,
:instance_methods => instance_methods(false),
:instance_variables => instance_variables,
:constants => constants,
}
end
end
Method.class_eval do
if RUBY_VERSION < "1.9"
def source_location
end
end
def look
{
:class => self.class,
:arity => arity,
:name => name,
:owner => owner,
:receiver => receiver,
:source_location => source_location,
}
end
def source_context
return nil unless source_location
line_number = source_location.second
file = File.open(source_location.first, "r")
lines = file.lines.drop(line_number - 1)
indent = lines.first.scan(/^ */).first.length
end_marker = Regexp.new("^" << " " * indent << "end")
interesting_lines = []
begin
line = lines.shift
interesting_lines << line
end until line =~ end_marker or interesting_lines.count > 20 or line.nil?
file.close
source_location << interesting_lines.join
end
end
Plays well with 1. Wirble, 2. pretty printing, 3. Ruby 1.8, 4. Ruby 1.9, and OF COURSE your .irbrc.
Your Response