On 3/30/07, Kristoffer Lund˝¤ <kristoffer.lunden / gmail.com> wrote:
> On 3/31/07, Nasir Khan <rubylearner / gmail.com> wrote:
> > Anyway, there could be several uses for such a thing. Simplest use case
> > would be a RAILS like (but not RAILS) situation where a controller is
> > deployed on a running server which (controller) optionally comes as a string
> > enveloped in a protocol message, this string is evaled and the controller is
> > instatiated but the system also needs to maintain meta information including
> > the name of the controller just deployed, which defaults to the fully
> > qualified name of the class.
> > My use case is similar.
> >
>
> Here's one way to capture that info as it's evaled. Not thread-safe
> as-is. Not even sure if it's useful at all, but I was a bit curious.
> There's more ways to do this, of course, especially depending on when
> you want to capture this info.
>
> str = <<EOF
> module A
>   class B
>     def b
>       puts "hello"
>     end
>  end
> end
> EOF
>
> set_trace_func proc {|event, file, line, id, binding, classname|
>   return unless event == 'class'
>   name = eval('self.class == Class && Module.nesting[0]', binding)
>   if name
>     # Do something with name
>   end
> }
>
> eval(str)
>
> set_trace_func(nil)

I like his idea of evaluating the string in the context of a 'sandbox'
module, here's a refinement which does that.  It strips off the
throwaway module since I assume that he's doing this to pre-flight
check actually re-running the code again after scanning it.

I also check to see if the trace function is running on the
originating thread, there are still thread safety issues since another
thread might also be using set_trace_func.  Another way to approach
this would be to set and reset Thread.critical, but it doesn't solve
the second problem since there doesn't seem to be a way to stack trace
functions.

rick@bill:/public/rubyscripts$ cat find_classes.rb
def classes_defined_in(str)
  begin
    this_thread = Thread.current
    result = []
    set_trace_func(lambda do |event, file, line, id, binding, classname|
      return unless Thread.current == this_thread
      return unless event == 'class'
      name = eval('self.class == Class && Module.nesting[0]',binding)
      result << name.to_s.split(/::/,2)[1] if name
    end)
  ensure
    Module.new.module_eval(str)
    set_trace_func(nil)
  end
  result
end

str = <<EOF
module M1
   class C1
      def c
         puts "hello"
      end
   end

   module M2
     class C2
     end
   end
end
module M3
    class C3
    end
end
EOF

p classes_defined_in(str)
rick@bill:/public/rubyscripts$ ruby find_classes.rb
["M1::C1", "M1::M2::C2", "M3::C3"]

-- 
Rick DeNatale

My blog on Ruby
http://talklikeaduck.denhaven2.com/