J
Jamis Buck
--------------080608050401040503050008
Content-Type: text/plain; charset=ISO-8859-1; format=flowed
Content-Transfer-Encoding: 7bit
Well, I was kind of waiting to see what other people came up with, but
since the list seems quiet on this topic, I guess I'll go ahead and post
first.
This is a VERY rough implementation. It uses ruby-gtk2, and is one of my
first projects using that interface, so I've doubtless done all kinds of
things wrong. But it works.
By default, it displays the "main" object. You can see the class,
superclass, instance/class variables, public/private/protected methods,
and constants (where any of them apply and are non-empty).
I wanted to add the ability to modify values, but didn't quite have time
to get that far.
This was a great quiz, though. I'd love to see a more sophisticated
version of this. I can use mine, for instance, to do a kind of
breakpoint in my code:
ObjectBrowser.browse( @foo )
And the program will stop, display the window, and wait for the window
to close before proceeding.
Anyway. Comments?
- Jamis
--
Jamis Buck
(e-mail address removed)
http://www.jamisbuck.org/jamis
--------------080608050401040503050008
Content-Type: text/plain;
name="object-browser.rb"
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
filename="object-browser.rb"
require 'gtk2'
DEFAULT_OBJECTBROWSER_ROOT = self
class Object
alias re_objbrowser_inspect :inspect
def inspect
result = pre_objbrowser_inspect
result = $1 + " ...>" if result =~ /^(#<.*?:0x\w+) /
result
end
end
module ObjectBrowser
def browse( root = DEFAULT_OBJECTBROWSER_ROOT )
Interface.new( root ).display_and_wait
end
module_function :browse
class Interface
def initialize( root = DEFAULT_OBJECTBROWSER_ROOT )
@root = root
Gtk.init
end
def display
window = Window.new( @root )
window.show_all
end
def display_and_wait
display
wait
end
def wait
Gtk.main
end
end
class Window < Gtk::Window
OBJECT = 1
CLASS = 2
INSTANCE_VARS = 3
PUBLIC_METHODS = 4
PROTECTED_METHODS = 5
PRIVATE_METHODS = 6
CLASS_VARS = 7
CONSTANTS = 8
SUPERCLASS = 9
STRING = 10
INSTANCE_METHODS = 11
LABEL = 0
TYPE = 1
REF = 2
def initialize( root )
super( Gtk::Window::TOPLEVEL )
signal_connect "delete_event", &method( n_delete )
signal_connect "destroy", &method( n_destroy )
vbox = Gtk::VBox.new
add(vbox)
pane = Gtk::VPaned.new
vbox.add pane
sw = Gtk::ScrolledWindow.new
sw.set_policy *[Gtk:OLICY_AUTOMATIC]*2
sw.shadow_type = Gtk::SHADOW_IN
pane.add sw
@model = Gtk::TreeStore.new( String, Integer, Integer )
add_node( nil, root )
@tree = Gtk::TreeView.new( @model )
@tree.set_size_request -1, 400
renderer = Gtk::CellRendererText.new
col = Gtk::TreeViewColumn.new( "Data", renderer )
col.set_cell_data_func renderer, &method( n_cell_render )
@tree.append_column col
@tree.expand_row Gtk::TreePath.new( "0" ), false
@tree.signal_connect "row_expanded", &method( n_row_expanded )
sw.add @tree
sw = Gtk::ScrolledWindow.new
sw.set_policy *[Gtk:OLICY_AUTOMATIC]*2
sw.shadow_type = Gtk::SHADOW_IN
pane.add sw
@text = Gtk::TextView.new
sw.add @text
set_default_size 650, 500
end
def on_delete( widget, event )
false
end
def on_destroy( widget )
Gtk.main_quit
end
def on_cell_render( c, r, m, i )
case i[TYPE]
when OBJECT
obj = ObjectSpace._id2ref( i[REF].to_i )
r.text = "#{i[LABEL]}#{obj.inspect}"
when CLASS, SUPERCLASS
obj = ObjectSpace._id2ref( i[REF].to_i )
r.text = "#{i[LABEL]} #{obj.name}"
else
r.text = i[LABEL]
end
end
def on_row_expanded( widget, iter, path )
unless iter.first_child[LABEL]
case iter[1]
when OBJECT, CLASS, SUPERCLASS then
obj = ObjectSpace._id2ref( iter[REF].to_i )
add_node iter, obj, iter.first_child
when INSTANCE_VARS then
obj = ObjectSpace._id2ref( iter.parent[REF].to_i )
initialize_vars_list( obj, iter, obj.instance_variables.sort,
:instance_variable_get )
when PUBLIC_METHODS then
obj = ObjectSpace._id2ref( iter.parent[REF].to_i )
initialize_methods_list( obj, iter, obj.public_methods(false).sort )
when PROTECTED_METHODS then
obj = ObjectSpace._id2ref( iter.parent[REF].to_i )
initialize_methods_list( obj, iter,
obj.protected_methods(false).sort )
when PRIVATE_METHODS then
obj = ObjectSpace._id2ref( iter.parent[REF].to_i )
initialize_methods_list( obj, iter,
obj.private_methods(false).sort )
when INSTANCE_METHODS then
obj = ObjectSpace._id2ref( iter.parent[REF].to_i )
initialize_methods_list( obj, iter,
obj.instance_methods(false).sort, true )
when CLASS_VARS then
obj = ObjectSpace._id2ref( iter.parent[REF].to_i )
initialize_vars_list( obj, iter,
obj.class_variables.sort, :class_eval )
when CONSTANTS then
obj = ObjectSpace._id2ref( iter.parent[REF].to_i )
constants = obj.constants
if obj.respond_to?superclass) && obj.superclass
constants = constants - obj.superclass.constants
end
initialize_vars_list( obj, iter, constants.sort, :const_get )
else
raise "don't know what to do with row of type #{iter[TYPE]}"
end
end
path_str = iter.path.to_s + ":" + ( iter.n_children - 1 ).to_s
path = Gtk::TreePath.new( path_str )
@tree.scroll_to_cell( path, nil, true, 1.0, 0 )
end
def add_node( parent, object, node=nil )
unless node
node = add_row( parent, "", object, OBJECT, false )
add_row( node, "class", object.class, CLASS )
else
add_row( parent, "class", object.class, CLASS, true, node )
node = parent
end
if object.is_a?( Module )
if object.respond_to?superclass) && object.superclass
add_row( node, "extends", object.superclass, SUPERCLASS )
end
add_row_unless_empty(
object.class_variables, node, "Class Variables", CLASS_VARS )
constants = object.constants
if object.respond_to?superclass) && object.superclass
constants = constants - object.superclass.constants
end
add_row_unless_empty( constants, node, "Constants", CONSTANTS )
add_row_unless_empty( object.instance_methods(false), node,
"Instance Methods", INSTANCE_METHODS )
end
add_row_unless_empty( object.instance_variables, node,
"Instance Variables", INSTANCE_VARS )
add_row_unless_empty( object.public_methods(false), node,
"Public Methods", PUBLIC_METHODS )
add_row_unless_empty( object.protected_methods(false), node,
"Protected Methods", PROTECTED_METHODS )
add_row_unless_empty( object.private_methods(false), node,
"Private Methods", PRIVATE_METHODS )
node
end
def add_row_unless_empty( list, node, name, type, add_empty=true )
unless list.empty?
summary = list.sort.join( "," )
summary = summary[0,60] + "..." if summary.length > 63
add_row( node, "#{name} (#{summary})", nil, type )
end
end
def add_row( parent, label, value, type, add_empty=true, node=nil )
node = @model.append( parent ) unless node
node[ LABEL ] = label
node[ TYPE ] = type
node[ REF ] = value.object_id
@model.append( node ) if add_empty
node
end
def initialize_methods_list( obj, iter, list, instance=false )
node = iter.first_child
list.each do |item|
if instance
method = obj.instance_method( item.to_sym )
else
method = obj.method( item.to_sym )
end
add_row iter, item + "(#{method.arity})", obj, STRING, false, node
node = nil
end
end
def initialize_vars_list( obj, iter, list, message )
node = iter.first_child
list.each do |item|
value = obj.__send__( message, item )
add_row iter, "#{item}=", value, OBJECT, true, node
node = nil
end
end
end
end
if __FILE__ == $0
@obj = ObjectBrowser::Interface.new
@obj.display_and_wait
end
--------------080608050401040503050008--
Content-Type: text/plain; charset=ISO-8859-1; format=flowed
Content-Transfer-Encoding: 7bit
Well, I was kind of waiting to see what other people came up with, but
since the list seems quiet on this topic, I guess I'll go ahead and post
first.
This is a VERY rough implementation. It uses ruby-gtk2, and is one of my
first projects using that interface, so I've doubtless done all kinds of
things wrong. But it works.
By default, it displays the "main" object. You can see the class,
superclass, instance/class variables, public/private/protected methods,
and constants (where any of them apply and are non-empty).
I wanted to add the ability to modify values, but didn't quite have time
to get that far.
This was a great quiz, though. I'd love to see a more sophisticated
version of this. I can use mine, for instance, to do a kind of
breakpoint in my code:
ObjectBrowser.browse( @foo )
And the program will stop, display the window, and wait for the window
to close before proceeding.
Anyway. Comments?
- Jamis
--
Jamis Buck
(e-mail address removed)
http://www.jamisbuck.org/jamis
--------------080608050401040503050008
Content-Type: text/plain;
name="object-browser.rb"
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
filename="object-browser.rb"
require 'gtk2'
DEFAULT_OBJECTBROWSER_ROOT = self
class Object
alias re_objbrowser_inspect :inspect
def inspect
result = pre_objbrowser_inspect
result = $1 + " ...>" if result =~ /^(#<.*?:0x\w+) /
result
end
end
module ObjectBrowser
def browse( root = DEFAULT_OBJECTBROWSER_ROOT )
Interface.new( root ).display_and_wait
end
module_function :browse
class Interface
def initialize( root = DEFAULT_OBJECTBROWSER_ROOT )
@root = root
Gtk.init
end
def display
window = Window.new( @root )
window.show_all
end
def display_and_wait
display
wait
end
def wait
Gtk.main
end
end
class Window < Gtk::Window
OBJECT = 1
CLASS = 2
INSTANCE_VARS = 3
PUBLIC_METHODS = 4
PROTECTED_METHODS = 5
PRIVATE_METHODS = 6
CLASS_VARS = 7
CONSTANTS = 8
SUPERCLASS = 9
STRING = 10
INSTANCE_METHODS = 11
LABEL = 0
TYPE = 1
REF = 2
def initialize( root )
super( Gtk::Window::TOPLEVEL )
signal_connect "delete_event", &method( n_delete )
signal_connect "destroy", &method( n_destroy )
vbox = Gtk::VBox.new
add(vbox)
pane = Gtk::VPaned.new
vbox.add pane
sw = Gtk::ScrolledWindow.new
sw.set_policy *[Gtk:OLICY_AUTOMATIC]*2
sw.shadow_type = Gtk::SHADOW_IN
pane.add sw
@model = Gtk::TreeStore.new( String, Integer, Integer )
add_node( nil, root )
@tree = Gtk::TreeView.new( @model )
@tree.set_size_request -1, 400
renderer = Gtk::CellRendererText.new
col = Gtk::TreeViewColumn.new( "Data", renderer )
col.set_cell_data_func renderer, &method( n_cell_render )
@tree.append_column col
@tree.expand_row Gtk::TreePath.new( "0" ), false
@tree.signal_connect "row_expanded", &method( n_row_expanded )
sw.add @tree
sw = Gtk::ScrolledWindow.new
sw.set_policy *[Gtk:OLICY_AUTOMATIC]*2
sw.shadow_type = Gtk::SHADOW_IN
pane.add sw
@text = Gtk::TextView.new
sw.add @text
set_default_size 650, 500
end
def on_delete( widget, event )
false
end
def on_destroy( widget )
Gtk.main_quit
end
def on_cell_render( c, r, m, i )
case i[TYPE]
when OBJECT
obj = ObjectSpace._id2ref( i[REF].to_i )
r.text = "#{i[LABEL]}#{obj.inspect}"
when CLASS, SUPERCLASS
obj = ObjectSpace._id2ref( i[REF].to_i )
r.text = "#{i[LABEL]} #{obj.name}"
else
r.text = i[LABEL]
end
end
def on_row_expanded( widget, iter, path )
unless iter.first_child[LABEL]
case iter[1]
when OBJECT, CLASS, SUPERCLASS then
obj = ObjectSpace._id2ref( iter[REF].to_i )
add_node iter, obj, iter.first_child
when INSTANCE_VARS then
obj = ObjectSpace._id2ref( iter.parent[REF].to_i )
initialize_vars_list( obj, iter, obj.instance_variables.sort,
:instance_variable_get )
when PUBLIC_METHODS then
obj = ObjectSpace._id2ref( iter.parent[REF].to_i )
initialize_methods_list( obj, iter, obj.public_methods(false).sort )
when PROTECTED_METHODS then
obj = ObjectSpace._id2ref( iter.parent[REF].to_i )
initialize_methods_list( obj, iter,
obj.protected_methods(false).sort )
when PRIVATE_METHODS then
obj = ObjectSpace._id2ref( iter.parent[REF].to_i )
initialize_methods_list( obj, iter,
obj.private_methods(false).sort )
when INSTANCE_METHODS then
obj = ObjectSpace._id2ref( iter.parent[REF].to_i )
initialize_methods_list( obj, iter,
obj.instance_methods(false).sort, true )
when CLASS_VARS then
obj = ObjectSpace._id2ref( iter.parent[REF].to_i )
initialize_vars_list( obj, iter,
obj.class_variables.sort, :class_eval )
when CONSTANTS then
obj = ObjectSpace._id2ref( iter.parent[REF].to_i )
constants = obj.constants
if obj.respond_to?superclass) && obj.superclass
constants = constants - obj.superclass.constants
end
initialize_vars_list( obj, iter, constants.sort, :const_get )
else
raise "don't know what to do with row of type #{iter[TYPE]}"
end
end
path_str = iter.path.to_s + ":" + ( iter.n_children - 1 ).to_s
path = Gtk::TreePath.new( path_str )
@tree.scroll_to_cell( path, nil, true, 1.0, 0 )
end
def add_node( parent, object, node=nil )
unless node
node = add_row( parent, "", object, OBJECT, false )
add_row( node, "class", object.class, CLASS )
else
add_row( parent, "class", object.class, CLASS, true, node )
node = parent
end
if object.is_a?( Module )
if object.respond_to?superclass) && object.superclass
add_row( node, "extends", object.superclass, SUPERCLASS )
end
add_row_unless_empty(
object.class_variables, node, "Class Variables", CLASS_VARS )
constants = object.constants
if object.respond_to?superclass) && object.superclass
constants = constants - object.superclass.constants
end
add_row_unless_empty( constants, node, "Constants", CONSTANTS )
add_row_unless_empty( object.instance_methods(false), node,
"Instance Methods", INSTANCE_METHODS )
end
add_row_unless_empty( object.instance_variables, node,
"Instance Variables", INSTANCE_VARS )
add_row_unless_empty( object.public_methods(false), node,
"Public Methods", PUBLIC_METHODS )
add_row_unless_empty( object.protected_methods(false), node,
"Protected Methods", PROTECTED_METHODS )
add_row_unless_empty( object.private_methods(false), node,
"Private Methods", PRIVATE_METHODS )
node
end
def add_row_unless_empty( list, node, name, type, add_empty=true )
unless list.empty?
summary = list.sort.join( "," )
summary = summary[0,60] + "..." if summary.length > 63
add_row( node, "#{name} (#{summary})", nil, type )
end
end
def add_row( parent, label, value, type, add_empty=true, node=nil )
node = @model.append( parent ) unless node
node[ LABEL ] = label
node[ TYPE ] = type
node[ REF ] = value.object_id
@model.append( node ) if add_empty
node
end
def initialize_methods_list( obj, iter, list, instance=false )
node = iter.first_child
list.each do |item|
if instance
method = obj.instance_method( item.to_sym )
else
method = obj.method( item.to_sym )
end
add_row iter, item + "(#{method.arity})", obj, STRING, false, node
node = nil
end
end
def initialize_vars_list( obj, iter, list, message )
node = iter.first_child
list.each do |item|
value = obj.__send__( message, item )
add_row iter, "#{item}=", value, OBJECT, true, node
node = nil
end
end
end
end
if __FILE__ == $0
@obj = ObjectBrowser::Interface.new
@obj.display_and_wait
end
--------------080608050401040503050008--