From: jweirich@... Date: 2000-11-11T08:44:19+09:00 Subject: [ruby-talk:6261] A Really Simple Class Browser 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