Code introspection

Hi,

Is there a solution to do code introspection rather than introspection
at the level of the Module/Object ? I can't figure out if I'm looking
for something that isn't there in Ruby since I've been reading C# code
or if I'm missing something. Let me explain my problem.
I would like to load ruby source files and dynamically create objects
from classes located inside these sources. It's one class/object per
source file and I can make it so that a string from a configuration
file holds the name of the class. However I can't figure out a good
way to use that string to get to the class in the source file.

I could use eval but am really uncomfortable with that for obvious
reasons (people keep saying it's evil so I don't play with him). The
other solution I came up with was to add a method in the same source
but outside the class to return the desired object:

--------------source file---------
class MyNewClass
....
end

def create_loaded_class()
  return MyNewClass.new()
end

···

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

Then as I require the source files I can successively invoke the
create_loaded_class method. This works but can get quite messy as
there are a lot of things to consider if you need to reload sources
and create new objects etc.
Please tell me I'm missing something obvious :).

thanks,
Daniel

I would like to load ruby source files and dynamically create objects
from classes located inside these sources. It's one class/object per
source file and I can make it so that a string from a configuration
file holds the name of the class. However I can't figure out a good
way to use that string to get to the class in the source file.

something like:

'Myclass' in 'Myclass.rb' or the file names are not related

I could use eval but am really uncomfortable with that for obvious
reasons (people keep saying it's evil so I don't play with him). The
other solution I came up with was to add a method in the same source
but outside the class to return the desired object:

you may use const_get:

class Foo
end

=> nil

Object::const_get('Foo').new

=> #<Foo:0x28b3eb8>

···

il 26 Jul 2004 03:19:36 -0700, daniel@danielcremer.com (Daniel Cremer) ha scritto::

How about Object.const_get("MyClass").new ?
Sam

Hello Daniel,

Is there a solution to do code introspection rather than introspection
at the level of the Module/Object ? I can't figure out if I'm looking

Yes but only from the C level. You need to create the "Node" tree and
travere it. Unfortuneatly there seems to be no visitor pattern class to do
this and you must expect that the Node structure can change even
between minor updates a lot. So read the code in eval.c and parse.c.

Look at the ExErb project for an application that does this.

Programming is easy if you are an experienced programmer but
maintainance is quite some work.

···

--
Best regards, emailto: scholz at scriptolutions dot com
Lothar Scholz http://www.ruby-ide.com
CTO Scriptolutions Ruby, PHP, Python IDE 's

~ > cat c.rb
   files = %w(a.rb b.rb)

   files.each do |file|
     load file
     name, klass = Common.introspect file
     p name
     p klass
     klass.new
   end

   ~ > ruby c.rb
   "Foo"
   Common::Foo
   "foo"
   "Bar"
   Common::Bar
   "bar"

   ~ > cat a.rb
   module Common
     class Foo
       def initialize
         p 'foo'
       end
     end

     class << self
       def introspect path
         @introspect[File.basename(path)]
       end
     end
     @introspect = {} unless defined? @introspect
     @introspect[File.basename(__FILE__)] = 'Foo', Foo
   end

   ~ > cat b.rb
   module Common
     class Bar
       def initialize
         p 'bar'
       end
     end

     class << self
       def introspect path
         @introspect[File.basename(path)]
       end
     end
     @introspect = {} unless defined? @introspect
     @introspect[File.basename(__FILE__)] = 'Bar', Bar
   end

you could obviously do this without the 'Common' module - but i think it's
cleaner/safer this way - provided you can manage to wrap all your modules this
way...

kind regards.

-a

···

On Mon, 26 Jul 2004, Daniel Cremer wrote:

Hi,

Is there a solution to do code introspection rather than introspection
at the level of the Module/Object ? I can't figure out if I'm looking
for something that isn't there in Ruby since I've been reading C# code
or if I'm missing something. Let me explain my problem.
I would like to load ruby source files and dynamically create objects
from classes located inside these sources. It's one class/object per
source file and I can make it so that a string from a configuration
file holds the name of the class. However I can't figure out a good
way to use that string to get to the class in the source file.

I could use eval but am really uncomfortable with that for obvious
reasons (people keep saying it's evil so I don't play with him). The
other solution I came up with was to add a method in the same source
but outside the class to return the desired object:

--------------source file---------
class MyNewClass
...
end

def create_loaded_class()
return MyNewClass.new()
end
---------------------------------

Then as I require the source files I can successively invoke the
create_loaded_class method. This works but can get quite messy as
there are a lot of things to consider if you need to reload sources
and create new objects etc.
Please tell me I'm missing something obvious :).

thanks,
Daniel

--

EMAIL :: Ara [dot] T [dot] Howard [at] noaa [dot] gov
PHONE :: 303.497.6469
A flower falls, even though we love it;
and a weed grows, even though we do not love it. --Dogen

===============================================================================

"Lothar Scholz" <mailinglists@scriptolutions.com> schrieb im Newsbeitrag
news:178359988968.20040726131022@scriptolutions.com...

Hello Daniel,

> Is there a solution to do code introspection rather than

introspection

> at the level of the Module/Object ? I can't figure out if I'm

looking

Yes but only from the C level. You need to create the "Node" tree and
travere it. Unfortuneatly there seems to be no visitor pattern class to

do

this and you must expect that the Node structure can change even
between minor updates a lot. So read the code in eval.c and parse.c.

Err, I think this is overkill. Daniel just wants to create an instance of
a class whose name he knows. So the already suggested Object.const_get(
class_name ) or ObjectSpace.const_get( class_name ) suffices.

If you don't know class names beforehand, you can work with an anonymous
module as wrapper like this:

mod = Module.new
mod.class_eval( File.read( "foo.rb" ) )
new_classes = mod.constants.inject() {|ar,c| cl=mod.const_get(c); ar <<
cl if Class === cl; ar}
p new_classes
include mod
p Foo

Or even do this

mod = Object
mod.class_eval( File.read( "foo.rb" ) )
classes = mod.constants.inject() {|ar,c| cl=mod.const_get(c); ar << cl
if Class === cl; ar}
p classes
p Foo

Regards

    robert

Using module_eval in the context of a wrapper module is one way to go. (I know you said no eval, but load/require end up eval-ing, anyway. I guess you could do something with $SAFE if you are worried about mischief.)

This has gotten so common for me that I put it in a small lib: http://redshift.sourceforge.net/script\. Here's a way of using it for what you want to do:

--- program.rb ---

require 'script'

files = [ "dog.rb", "cat.rb" ]

files.each do |file|
   animal_wrapper = Script.load(file)
     # animal_wrapper is a Module in whose context the top-level
     # constants and methods are defined.

   p animal_wrapper.main_file # should be same as the file local var
   p animal_wrapper::CLASS # top-level constant in the script
   p animal_wrapper.make_animal # top-level meth in the script
end

--- dog.rb ---

class Dog
   def initialize name
     @name = name
   end
end

CLASS = Dog

def make_animal
   Dog.new "Rover"
end

--- cat.rb ---

class Dog
   def initialize name
     @name = name
   end
end

CLASS = Dog

def make_animal
   Dog.new "Rover"
end

--- output of program.rb ---

"/home/vjoel/tmp/script-example/dog.rb"
#<Script:0x401c4e3c>::Dog
#<#<Script:0x401c4e3c>::dog:0x401c4964 @name="Rover">
"/home/vjoel/tmp/script-example/cat.rb"
#<Script:0x401c4928>::Cat
#<#<Script:0x401c4928>::cat:0x401c4450 @name="Fuzzy">

···

On Mon, 26 Jul 2004, Daniel Cremer wrote:

Hi,

Is there a solution to do code introspection rather than introspection
at the level of the Module/Object ? I can't figure out if I'm looking
for something that isn't there in Ruby since I've been reading C# code
or if I'm missing something. Let me explain my problem.
I would like to load ruby source files and dynamically create objects
from classes located inside these sources. It's one class/object per
source file and I can make it so that a string from a configuration
file holds the name of the class. However I can't figure out a good
way to use that string to get to the class in the source file.

I could use eval but am really uncomfortable with that for obvious
reasons (people keep saying it's evil so I don't play with him). The
other solution I came up with was to add a method in the same source
but outside the class to return the desired object:

--------------source file---------
class MyNewClass
...
end

def create_loaded_class()
return MyNewClass.new()
end
---------------------------------

Then as I require the source files I can successively invoke the
create_loaded_class method. This works but can get quite messy as
there are a lot of things to consider if you need to reload sources
and create new objects etc.
Please tell me I'm missing something obvious :).

--------

(You might want Dog and Cat to inherit from some base class that has a self.to_s method to avoid the hex junk.)

You could simplify the picture by defining the _same_ class in each script file:

class Animal
   def initialize;...;end
end

and then you can reference it by

   animal_wrapper::Animal.new

in your main program. The fact that dog.rb and cat.rb are loaded into different wrapper contexts keeps these classes distinct, even though their names appear the same.

ahh... it looks like creating an anonymous module is the piece that is
really missing for me at the moment...
thanks for the ideas, I'm not sure how far I'll have to carry this so
they're all appreciated

  -Daniel

···

On Mon, 2004-07-26 at 14:16, Robert Klemme wrote:

"Lothar Scholz" <mailinglists@scriptolutions.com> schrieb im Newsbeitrag
news:178359988968.20040726131022@scriptolutions.com...
> Hello Daniel,
>
> > Is there a solution to do code introspection rather than
introspection
> > at the level of the Module/Object ? I can't figure out if I'm
looking
>
> Yes but only from the C level. You need to create the "Node" tree and
> travere it. Unfortuneatly there seems to be no visitor pattern class to
do
> this and you must expect that the Node structure can change even
> between minor updates a lot. So read the code in eval.c and parse.c.

Err, I think this is overkill. Daniel just wants to create an instance of
a class whose name he knows. So the already suggested Object.const_get(
class_name ) or ObjectSpace.const_get( class_name ) suffices.

If you don't know class names beforehand, you can work with an anonymous
module as wrapper like this:

mod = Module.new
mod.class_eval( File.read( "foo.rb" ) )
new_classes = mod.constants.inject() {|ar,c| cl=mod.const_get(c); ar <<
cl if Class === cl; ar}
p new_classes
include mod
p Foo

(...)

Joel VanderWerf wrote:

--- cat.rb ---

class Dog
  def initialize name
    @name = name
  end
end

CLASS = Dog

def make_animal
  Dog.new "Rover"
end

Ooops. Copy/paste error.

--- cat.rb ---

class Cat
   def initialize name
     @name = name
   end
end

CLASS = Cat

def make_animal
   Cat.new "Fuzzy"
end

well, than take a look at the simple load:

    load(filename, wrap=false) => true

···

il Mon, 26 Jul 2004 23:56:40 +0900, Daniel Cremer <daniel@danielcremer.com> ha scritto::

If you don't know class names beforehand, you can work with an anonymous
module as wrapper like this:

mod = Module.new
mod.class_eval( File.read( "foo.rb" ) )
new_classes = mod.constants.inject() {|ar,c| cl=mod.const_get(c); ar <<
cl if Class === cl; ar}
p new_classes
include mod
p Foo

(...)

ahh... it looks like creating an anonymous module is the piece that is
really missing for me at the moment...
thanks for the ideas, I'm not sure how far I'll have to carry this so
they're all appreciated

-----------------------------------------------------------------------
    Loads and executes the Ruby program in the file _filename_. If the
    filename does not resolve to an absolute path, the file is
searched
    for in the library directories listed in +$:+. If the optional
    _wrap_ parameter is +true+, the loaded script will be executed
    under an anonymous module, protecting the calling program's global
    namespace. In no circumstance will any local variables in the
    loaded file be propagated to the loading environment.

"gabriele renzi" <surrender_it@remove.yahoo.it> schrieb im Newsbeitrag
news:rkjcg0tf6o9g63lnb6lo9gk5bsjvg82kcd@4ax.com...

>> If you don't know class names beforehand, you can work with an

anonymous

>> module as wrapper like this:
>>
>> mod = Module.new
>> mod.class_eval( File.read( "foo.rb" ) )
>> new_classes = mod.constants.inject() {|ar,c| cl=mod.const_get(c);

ar <<

>> cl if Class === cl; ar}
>> p new_classes
>> include mod
>> p Foo
>>
>> (...)
>
>ahh... it looks like creating an anonymous module is the piece that is
>really missing for me at the moment...
>thanks for the ideas, I'm not sure how far I'll have to carry this so
>they're all appreciated
>

well, than take a look at the simple load:

    load(filename, wrap=false) => true
-----------------------------------------------------------------------
    Loads and executes the Ruby program in the file _filename_. If the
    filename does not resolve to an absolute path, the file is
searched
    for in the library directories listed in +$:+. If the optional
    _wrap_ parameter is +true+, the loaded script will be executed
    under an anonymous module, protecting the calling program's global
    namespace. In no circumstance will any local variables in the
    loaded file be propagated to the loading environment.

Yes, but that doesn't help here since there's no way to find this
anonymous module in order to find all classes defined in the file. I
tried that initially, too, but the return value of load is not the module
but 'true' or 'false'. Or am I missing somethig?

Regards

    robert

···

il Mon, 26 Jul 2004 23:56:40 +0900, Daniel Cremer > <daniel@danielcremer.com> ha scritto::

Robert Klemme wrote:

"gabriele renzi" <surrender_it@remove.yahoo.it> schrieb im Newsbeitrag
news:rkjcg0tf6o9g63lnb6lo9gk5bsjvg82kcd@4ax.com...

...

   load(filename, wrap=false) => true
-----------------------------------------------------------------------
   Loads and executes the Ruby program in the file _filename_. If the
   filename does not resolve to an absolute path, the file is
searched
   for in the library directories listed in +$:+. If the optional
   _wrap_ parameter is +true+, the loaded script will be executed
   under an anonymous module, protecting the calling program's global
   namespace. In no circumstance will any local variables in the
   loaded file be propagated to the loading environment.

Yes, but that doesn't help here since there's no way to find this
anonymous module in order to find all classes defined in the file. I
tried that initially, too, but the return value of load is not the module
but 'true' or 'false'. Or am I missing somethig?

The anonymous module is not even reachable, so it will get GC-ed, as in this example:

irb(main):002:0> def foo; load "f.rb", true; end
=> nil
irb(main):003:0> ObjectSpace.each_object(Module){}
=> 372
irb(main):004:0> foo
=> true
irb(main):005:0> ObjectSpace.each_object(Module){}
=> 373
irb(main):006:0> foo
=> true
irb(main):007:0> ObjectSpace.each_object(Module){}
=> 374
irb(main):008:0> GC.start
=> nil
irb(main):009:0> ObjectSpace.each_object(Module){}
=> 372

Getting one's hands on the wrapper module is the raison d'etre of my little "script" lib (http://redshift.sourceforge.net/script\). More simply, you can just read the file and use module_eval (but script adds some features).