After all the talk about a class browser, I threw this together to try
out some ideas.  It is a simple browser that allows you to explore the
classes in a running Ruby system.  It uses reflection to find the
relationships between classes (and Modules), so there is no reference
back to source code.  In fact, in its current state, you can't even
load in other Ruby modules (i.e. it only looks at basic Ruby, the Tk
classes and its own classes).

Despite its rather stripped down functionality, it might be useful as
a starting point to see what is possible and useful.  If someone wants
to run with this, feel free to use this as a starting point.

-- 
-- Jim Weirich     jweirich / one.net    http://w3.one.net/~jweirich
---------------------------------------------------------------------
"Beware of bugs in the above code; I have only proved it correct, 
not tried it." -- Donald Knuth (in a memo to Peter van Emde Boas)

--><--snip--><--------------------------------------------------------
#!/usr/bin/env ruby
# -*- ruby -*-

# Copyright 2000 by Jim Weirich (jweirich / one.net).  All rights reserved.
# Permission is granted for use, modification and distribution as
# long as the above copyright notice is included.

require 'tk'

REVISION ="$Revision: 1.3 $"

Class_reject_filter = /^(Errno::.*|fatal)$/

# ====================================================================
# (Additions)
class Object
  def if_absent
    self
  end
end


# ====================================================================
# (Additions)
class NilClass
  def if_absent
    yield
  end
end


# ====================================================================
class BasicListing

  attr_reader :list, :selection
  attr_writer :select_action

  @@packing = {'fill'=>'both', 'expand'=>true, 'side'=>'left'}

  def initialize (parent, title, container=nil)
    p = @@packing
    @widget = (container ? container : TkFrame.new(parent) { pack p } )
    TkLabel.new(@widget) {
      text title
      pack ( 'fill'=>'none', 'expand'=>false, 'side'=>'top', 'anchor'=>'w' )
    }
    @list = TkListbox.new(@widget) { height 6; width 15; pack p }
    sb = TkScrollbar.new (@widget)
    sb.command (proc { |*args| @list.yview *args })
    sb.pack ('fill'=>'y', 'side'=>'right')
    @list.yscrollcommand (proc { |a,b| sb.set(a,b) })
    @select_action = proc { puts "Do Nothing" }
    @list.bind ("ButtonRelease-1") { 
      index = @list.curselection[0]
      @selection = @list.get(index) if index.is_a?(Integer)
      select (@selection)
    }
  end

  def select (aString)
  end

  def fill (key)
  end

  def highlight (index)
    list.selection_clear (0, 'end')
    list.selection_set (index)
    list.see (index)
  end

  private

  def class_for_name (class_name)
    begin
      result = eval(class_name)
    rescue
      result = nil
    end
    result
  end

  def filtered_classes (class_list)
    class_list.find_all { |c| c.name !~ Class_reject_filter }
  end
end


# ====================================================================
class ClassListing < BasicListing
  
  attr_accessor :class_label, :parent_label

  def initialize (parent, title)
    super (parent, title)
    @target_list = []
  end

  def highlight_name (class_name)
    index = @classes.index(class_name).
      if_absent { @classes.index(class_name + " (M)") }
    highlight (index) if (index && index.is_a?(Integer))
  end

  def select (aString)
    return unless (String === aString) and (aString != "")
    aString =~ /^(\S+)/
    class_name = $1
    highlight_name(class_name)
    update_labels(class_name)
    update_targets(class_name)
  end

  def fill (key)
    @list.delete (0, "end")
    @classes = []
    ObjectSpace.each_object(Module) { |n|
      if n.name !~ Class_reject_filter then
	@classes << (n.name + ((Class === n) ? "" : " (M)"))
      end
    }
    @classes.sort!
    @classes.each {|n| @list.insert ("end", n) }
  end

  def add_target (target)
    @target_list << target
  end

  private

  def update_labels (class_name)
    update_class_label (class_name)
    update_parent_label (class_name)
  end

  def update_class_label (class_name)
    if @class_label then
      @class_label.text (class_name)
    end
  end

  def update_parent_label (class_name)
    if @parent_label then
      @parent_label.text (parent_of(class_name))
    end
  end

  def update_targets (class_name)
    @target_list.each { |t| t.fill(class_name) }
  end

  def parent_of (class_name)
    class_obj = class_for_name(class_name)
    if (not (Class === class_obj)) or (class_obj == Object) then
      parent_name = ""
    else
      parent_name = class_obj.superclass.name
    end
  end

end


# ====================================================================
class NameListing < BasicListing
  
  attr_accessor :target, :selection
  
  def initialize (parent, title, container=nil)
    super (parent, title, container)
    @names = []
    @target = nil
    @selection = nil
  end

  def highlight_name (name)
    index = @names.index(name)
    highlight (index) if index.is_a?(Integer)
  end

  def select (name)
    @selection = name
    highlight_name (name)
    @target.select(@selection) if @target
  end

  def fill (name)
    @names = find_names_for (name)
    @list.delete (0, "end")
    @names.each { |n| @list.insert ("end", n) }
  end
end


# ====================================================================
class ModuleListing < NameListing
  private

  def find_names_for (class_name)
    class_obj = class_for_name(class_name)
    filtered_classes (class_obj.included_modules.sort)
  end
end


# ====================================================================
class ChildListing < NameListing
  private

  def find_names_for (class_name)
    class_obj = class_for_name(class_name)
    list = []
    ObjectSpace.each_object(Class) { |c|
      list << c if c.superclass == class_obj
    }
    filtered_classes (list.sort)
  end
end


# ====================================================================
class MethodListing < BasicListing

  def initialize (parent, title)
    super(parent, title)
    @flat_view = false
    @show_class_methods = false
  end

  def select (class_name)
  end

  def toggle_flat_view
    @flat_view = (not @flat_view)
  end

  def toggle_class_methods
    @show_class_methods = (not @show_class_methods)
  end

  def fill (class_name)
    return if not class_name
    @list.delete (0, "end")
    methods = find_methods(class_name)
    methods.sort.each { |m|
      @list.insert ("end", m)
    }
  end

  def find_methods (class_name)
    result = []
    class_obj = class_for_name(class_name)
    return [] if class_obj == nil
    if @show_class_methods and @flat_view then
      result = find_flat_class_methods(class_obj)
    elsif @show_class_methods then
      result = find_class_methods(class_obj)
    elsif @flat_view then
      result = find_flat_instance_methods (class_obj)
    else
      result = find_instance_methods(class_obj)
    end
    result
  end    

  def find_instance_methods (class_obj)
    class_obj.instance_methods
  end

  def find_flat_instance_methods (class_obj)
    if not class_obj.is_a?(Class) then
      result = class_obj.instance_methods
    else
      result = []
      c = class_obj
      while c != nil
	result |= c.instance_methods
	c = c.superclass
      end
      class_obj.included_modules.each { |m|
	result |= m.instance_methods
      }
    end
    result
  end

  def find_flat_class_methods (class_obj)
    class_obj.methods
  end
    
  def find_class_methods (class_obj)
    result = class_obj.methods
    if class_obj.is_a?(Class) then
      result = result - class_obj.superclass.methods
    end
    result
  end
end


# Top Level Methods ==================================================

def create_gui
  root = TkRoot.new { title "Gem Hunter" }
  
  buttons = TkFrame.new {
    pack ( 'fill'=>'x', 'expand'=>false, 'side'=>'bottom' )
  }
  TkButton.new(buttons) {
    text 'Exit'
    command proc { exit }
    pack ( 'fill'=>'x', 'expand'=>false, 'side'=>'right')
  }
  
  flat_button = TkCheckButton.new (buttons) {
    text 'Flat View'
    pack ( 'fill'=>'x', 'expand'=>false, 'side'=>'left')
  }
  
  cm_button = TkCheckButton.new (buttons) {
    text 'Class Methods'
    variable TkVariable.new ($checked)
    pack ( 'fill'=>'x', 'expand'=>false, 'side'=>'left')
  }
  
  labels = TkFrame.new { pack ( 'fill'=>'x', 'expand'=>false ) }
  class_label = TkLabel.new(labels) {
    text "Class: "
    pack ( 'fill'=>'none',
	  'expand'=>false,
	  'side'=>'left',
	  'anchor'=>'w')
  }
  class_label = TkLabel.new (labels) {
    relief 'groove'
    width 15
    pack ( 'fill'=>'x',
	  'expand'=>true,
	  'side'=>'left',
	  'anchor'=>'w')
  }
  parent_label = TkButton.new (labels) {
    text ""
    width 15
    pack ( 'fill'=>'x',
	  'expand'=>true,
	  'side'=>'right',
	  'anchor'=>'w')
  }
  TkLabel.new(labels) {
    text "Parent: "
    pack ( 'fill'=>'none',
	  'expand'=>false,
	  'side'=>'right',
	  'anchor'=>'w')
  }

  windows = TkFrame.new { pack ('fill'=>'both', 'expand'=>true) }

  class_list  = ClassListing.new (windows, "Classes")
  center_frame = TkFrame.new(windows) {
    pack ('fill'=>'both', 'expand'=>true, 'side'=>'left')
  }
  center_top_frame = TkFrame.new (center_frame) {
    pack ('fill'=>'both', 'expand'=>true, 'side'=>'top')
  }
  center_bottom_frame = TkFrame.new (center_frame) {
    pack ('fill'=>'both', 'expand'=>true, 'side'=>'top')
  }
  module_list = ModuleListing.new (center_top_frame,
				   "Included Modules",
				   center_top_frame)
  child_list  = ChildListing.new (center_bottom_frame,
				  "Subclasses",
				   center_bottom_frame)
  method_list = MethodListing.new (windows, "Methods")
  parent_label.command proc {
    class_name = parent_label.text
    if class_name.is_a?(String) and (class_name!="") then
      class_list.select(parent_label.text)
    end
  }

  class_list.add_target (method_list)
  class_list.add_target (module_list)
  class_list.add_target (child_list)
  class_list.class_label = class_label
  class_list.parent_label = parent_label
  class_list.fill("")

  module_list.target = class_list
  child_list.target  = class_list

  cm_button.command proc {
    method_list.toggle_class_methods
    class_list.select(class_list.selection)
  }
  flat_button.command proc {
    method_list.toggle_flat_view
    class_list.select(class_list.selection)
  }
end


# Main ===============================================================

def main
  create_gui
  Tk.mainloop
end

if __FILE__ == $0 then
  main
end