[QUIZ] Object Browser (#8)

R

Ruby Quiz

The three rules of Ruby Quiz:

1. Please do not post any solutions or spoiler discussion for this quiz until
48 hours have passed from the time on this message.

2. Support Ruby Quiz by submitting ideas as often as you can:

http://www.grayproductions.net/ruby_quiz/

3. Enjoy!

-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

by Jim Menard

Recently on ruby-talk, itsme123 asked if there was a generic object browser that
will "interactively browse a graph of connected objects by showing their
instance variables and letting me click through to browse".

The quiz challenge: write such a browser. It should be able to start at any
object or, if none is given to it, start at the main object ("self" at the top
level of any Ruby script).

The interface to the browser can be text-based or graphical.

I'm thinking of something like the Squeak Explorer (the new inspector). It's a
window that displays the object with an open/close triangle next to it. Click
the triangle, and the ivars are exposed.

V root: an OrderedCollection(a MyClass, a Number)
V 1: a MyClass
name: 'the name'
anotherIvar: 42
2: a Number

That's just one possible UI, of course.

Bonus points for allowing modification of instance variable values and for
allowing inspection of classes (remember, classes are objects, too!).
 
R

R. Mark Volkmann

I'm new to using gems. Can you tell me the command I need to run to get
ruby-gtk2 so I can run your code?

----- Original Message -----
From: "Jamis Buck" <[email protected]>
To: "ruby-talk ML" <[email protected]>
Sent: Sunday, November 21, 2004 6:48 PM
Subject: [SOLUTION] Object Browser (#8)

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


--------------------------------------------------------------------------------


require 'gtk2'

DEFAULT_OBJECTBROWSER_ROOT = self

class Object
alias :pre_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( :eek:n_delete )
signal_connect "destroy", &method( :eek:n_destroy )

vbox = Gtk::VBox.new
add(vbox)

pane = Gtk::VPaned.new
vbox.add pane

sw = Gtk::ScrolledWindow.new
sw.set_policy *[Gtk::pOLICY_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( :eek:n_cell_render )

@tree.append_column col
@tree.expand_row Gtk::TreePath.new( "0" ), false

@tree.signal_connect "row_expanded", &method( :eek:n_row_expanded )

sw.add @tree

sw = Gtk::ScrolledWindow.new
sw.set_policy *[Gtk::pOLICY_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
 
M

Masao Mutoh

Hi,

Unfortunately, ruby-gtk2 is not a gem, nor is it in rpa. So you have to
install it the "hard" way--from source. If you're on Windows, it's even
harder: you have to install GTK2 first.

GTK2: http://www.gtk.org
Ruby-GTK2 (part of Ruby-Gnome2): http://ruby-gnome2.sourceforge.jp/

Having never used GTK in Windows, I have no idea how easy/hard it is to
get ruby-gtk2 running under Windows. YMMV. YHBW.

See http://ruby-gnome2.sourceforge.jp/hiki.cgi?Install+Guide+for+Windows
You can install them just four steps, though it's not one click ;).
- Jamis

P.S.: A plea to the ruby-gnome2 folks: a gemmable version would be a
real boon. A similar plea to the RPA folks... ;)

Good idea. Are there anyone to work for them ?
 
J

James Edward Gray II

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.

[snip description]
Anyway. Comments?

Yes. Would you mind posting a few screenshots, for those of us having
trouble getting past the interface requirements?

James Edward Gray II
 
M

Masao Mutoh

Hi Lothar,

Hello Masao,

MM> Hi,

MM> On Mon, 22 Nov 2004 11:38:36 +0900


MM> See
MM> http://ruby-gnome2.sourceforge.jp/hiki.cgi?Install+Guide+for+Windows
MM> You can install them just four steps, though it's not one click ;).

And fails on step 2 with

Z:\work\arachno\make>ruby -e "require 'gtk2'"
c:/ruby/lib/ruby/site_ruby/1.8/i386-msvcrt/glib2.so: 126: The specified module could not be found.
- c:/ruby/lib/ruby/site_ruby/1.8/i386-msvcrt/glib2.so (LoadError)
from c:/ruby/lib/ruby/site_ruby/1.8/glib2.rb:64
from c:/ruby/lib/ruby/site_ruby/1.8/gtk2.rb:1:in `require'
from c:/ruby/lib/ruby/site_ruby/1.8/gtk2.rb:1
from -e:1:in `require'
from -e:1

The file exists all all DLL's also, so there is some other thing wrong here.
My System is WinXP SP2.


On step 2? Did you do step 3 and 4?

And are there c:/ruby/lib/ruby/site_ruby/1.8/i386-msvcrt/glib2.so ?
 
M

Masao Mutoh

Hi,

Hello Masao,


MM> On step 2? Did you do step 3 and 4?

Yes.

MM> And are there
MM> c:/ruby/lib/ruby/site_ruby/1.8/i386-msvcrt/glib2.so ?

Yes.

Hmm. What versions are the One click installer and GTK ?
Could you try the same versions of the information page?

In my environment(WinXP SP2), it works.

Are there anyone to test it?
 
M

Masao Mutoh

Hi,

I just tried it (thank-you, VMWare!), and it worked flawlessly. I'm also
using WinXP SP2, with Ruby 1.8.2. I grabbed the recommended packages
from each step.

Thanks.

Lothar, check your environment carefully again, please.
I recommand to uninstall ruby, ruby-gtk2 and gtk2 once, then re-install them
with latest versions of them.
 
B

Brian Schröder

Hello Group,

Thanks for the quiz. I always wanted to learn more about the reflection capabilites of ruby, and indeed there is quite a lot to learn. This quiz was not too complicated, but the design of a good gui takes a lot of time. (Especially if you're not accustomed to the toolkit).

I implemented a gnome2/gtk version. I did not use code from the other solution, but the next step will be to see what I can borrow.

I submit now, because I've already invested too much time. The code is not beautifull and could need a heavy facelift.

The difference to the other solution is, that I'm starting with a class-tree from which you can get to all the objects. I think the right pane in my solution is more or less what was specified in the quiz.

You can see the code and screenshots at:

http://ruby.brian-schroeder.de/quiz/object_browser/

Regards,

Brian
 
C

Charles Mills

Hello Group,

Thanks for the quiz. I always wanted to learn more about the
reflection capabilites of ruby, and indeed there is quite a lot to
learn. This quiz was not too complicated, but the design of a good gui
takes a lot of time. (Especially if you're not accustomed to the
toolkit).

I implemented a gnome2/gtk version. I did not use code from the other
solution, but the next step will be to see what I can borrow.

I submit now, because I've already invested too much time. The code is
not beautifull and could need a heavy facelift.

The difference to the other solution is, that I'm starting with a
class-tree from which you can get to all the objects. I think the
right pane in my solution is more or less what was specified in the
quiz.

You can see the code and screenshots at:

http://ruby.brian-schroeder.de/quiz/object_browser/
That is *really* nice. Very impressive. Kudos.

-Charlie
 
J

James Edward Gray II

Hello Group,

Thanks for the quiz. I always wanted to learn more about the
reflection capabilites of ruby, and indeed there is quite a lot to
learn. This quiz was not too complicated, but the design of a good gui
takes a lot of time. (Especially if you're not accustomed to the
toolkit).

I've been unfortunately, very busy this weekend and haven't had time to
play with the quiz. (I promise to get back in the game on the next
one!)

However, I have to take a moment to say... Wow. Very nice job Brian.
I'm impressed.

James Edward Gray II
 
Z

Zach Dennis

Brian, this is very cool....very very cool! Thanks for making this
viewable online w/screenshots as well.

Zach
 
J

jim

* Brian Schröder <[email protected]> [2004-11-23 09:26:11 +0900]:

I particularly liked the clean colorization of the ruby code. What did you use
(and what color style) to convert the Ruby code to HTML?
 
B

Brian Schröder

On Wed, 24 Nov 2004 04:26:17 +0900
* Brian Schröder <[email protected]> [2004-11-23 09:26:11 +0900]:

I particularly liked the clean colorization of the ruby code. What did you use
(and what color style) to convert the Ruby code to HTML?

I use xemacs' htmlize package to convert the ruby-code to html. Then I include a stylesheet. (You can copy that from my page if you want)

The important snippets of the Makefile I use:


htmlize: $(patsubst %.rb,browse/%-rb.html,$(wildcard *.rb))

browse/%-rb.html: %.rb
mkdir -p browse/unsuccessfull
xemacs -nw -eval '(htmlize-file "$<" "$@")' -kill && \
mv $@ [email protected] && \
cat [email protected] | ruby -e 'puts $$stdin.read.gsub(/<style type="text\/css">.*<\/style>/m, "<link id=\"css\" href=\"../ruby.css\" rel=\"stylesheet\">")' > $@ && \
rm [email protected]


Regards,

Brian
 
J

jim

* Brian Schröder said:
I use xemacs' htmlize package to convert the ruby-code to html. Then I include a stylesheet. (You can copy that from my page if you want)

htmlize: $(patsubst %.rb,browse/%-rb.html,$(wildcard *.rb))

browse/%-rb.html: %.rb
mkdir -p browse/unsuccessfull
xemacs -nw -eval '(htmlize-file "$<" "$@")' -kill && \
mv $@ [email protected] && \
cat [email protected] | ruby -e 'puts $$stdin.read.gsub(/<style type="text\/css">.*<\/style>/m, "<link id=\"css\" href=\"../ruby.css\" rel=\"stylesheet\">")' > $@ && \
rm [email protected]

Thanks. Uhh, sorry to be so dense, but (not being an emacs person) it
appears that htmlize-file.el is not part of the xemacs distro and I'm not
sure what version to get. Also, it would be great if you could provide a
command line example without the make cipher added. The obvious:

xemacs -nw -eval '(htmlize-file "myfile.rb")' -kill

doesn't seem to work.
 
B

Brian Schröder

I use xemacs' htmlize package to convert the ruby-code to html. Then I include a stylesheet. (You can copy that from my page if you want)

htmlize: $(patsubst %.rb,browse/%-rb.html,$(wildcard *.rb))

browse/%-rb.html: %.rb
mkdir -p browse/unsuccessfull
xemacs -nw -eval '(htmlize-file "$<" "$@")' -kill && \
mv $@ [email protected] && \
cat [email protected] | ruby -e 'puts $$stdin.read.gsub(/<style type="text\/css">.*<\/style>/m, "<link id=\"css\" href=\"../ruby.css\" rel=\"stylesheet\">")' > $@ && \
rm [email protected]

Thanks. Uhh, sorry to be so dense, but (not being an emacs person) it
appears that htmlize-file.el is not part of the xemacs distro and I'm not
sure what version to get. Also, it would be great if you could provide a
command line example without the make cipher added. The obvious:

xemacs -nw -eval '(htmlize-file "myfile.rb")' -kill

doesn't seem to work.
[/QUOTE]

Hello Jim,

I installed htmlize as part of the debian package

emacs-goodies-el - Miscellaneous add-ons for Emacs

In your example you forgot the output file. It should be

xemacs -nw -eval '(htmlize-file "myfile.rb" "myfile-rb.html")' -kill

Or you can try it interactively

open a ruby file in xemacs
C-x C-f myfile.rb

htmlize the buffer
M-x htmlize-buffer

save the file
C-x C-s myfile-rb.html

Make shure that you have turned font-lock on, such that the file is fontified in xemacs.

HTH,

Brian
 
B

Brian Schröder

So I took some time and refactored my solution. It now has a modular and extendible structure (at least I hope so). It should be possible to easily write non-gtk ui's and extend the reporting capabilities.

The code is at the same location as before
http://ruby.brian-schroeder.de/quiz/object_browser/

The screenshots are not updated.

It now is able to do more or less exactly the same as before, but the code has changed a lot. I could not let code as bad as the previous solution exist under my name ;).

Missing things:
- look at jamis solution and merge interesting parts.
- Polish the ui
- Inlcude Breakpoint support.

I hope that I can spare some hours this weekend to accomplish this.

I hope i do not "get charged per email" ;), and nobody will hate me because I put code into the public domain without the capabilities and spare time to support it ;).

Regards,

Brian Schröder
 
J

James Edward Gray II

So I took some time and refactored my solution. It now has a modular
and extendible structure (at least I hope so). It should be possible
to easily write non-gtk ui's and extend the reporting capabilities.

Brian, I'll kill ya! I just finished the summary about 15 minutes ago.
<laughs>

Seriously, thanks for the update. I'm looking through it and it looks
great. Just don't be too mad at me if the summary covers the original
solution. :D

Since we're talking...

I want to avoid embarrassing myself in the summary again by claiming to
know your code better than you, so I'll embarrass myself in this thread
with all of Ruby Talk looking on. See the question in the comment
below:

def object_browser(classtree = ClassTreeNode.new(Kernel))
ObjectSpace.each_object do | x |
classnode = classtree # <- This line truly isn't needed, right???
x.class.ancestors.reverse[1..-1].inject(classtree){ | classnode,
klass | classnode.add_class(klass) }.add_object(x)
end
classtree
end

Tell me I'm right this time, even if you need to lie to me. It's good
for my ego.
I hope i do not "get charged per email" ;), and nobody will hate me
because I put code into the public domain without the capabilities and
spare time to support it ;).

No, now you're in trouble for not properly respecting my schedule. You
just can't win, really. :D

James Edward Gray II
 
R

R. Mark Volkmann

I'm concerned about the state of GUI toolkits for Ruby when in comes to
installation requirements, particularly under Mac OS X. I'm think about the
case where we want to create a Ruby-based GUI application for non-developers to
use.

I recently went through the steps to get Tk working with Ruby under Mac OS X.
It was fairly complicated. After all that work I found that GUIs I had created
under Windows didn't look very good on Mac OS X. I then decided to try FOX.
That required downloading the X11 software from Apple. I installed that and
then found that I didn't automatically get the X11 library files that are
needed in order to build FOX. Maybe I'll get this figured out eventually.

The bottom line is that even if you take the time to write a great GUI app.
using Ruby, your audience of potential users will likely be quite small.
Installing Ruby is easy enough, but setting up a GUI toolkit is prohibitively
complex for a non-developer. Compare this to Java. Install the Java Runtime
Environment or use Java Web Start and Swing is available.

One answer is to create web apps instead. That's not appropriate for every
application though.

Do others think this is an issue?
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,755
Messages
2,569,536
Members
45,020
Latest member
GenesisGai

Latest Threads

Top