root/branches/1-2-stable/activesupport/lib/active_support/binding_of_caller.rb
| Revision 784, 2.8 kB (checked in by david, 4 years ago) |
|---|
| Line | |
|---|---|
| 1 | begin |
| 2 | require 'simplecc' |
| 3 | rescue LoadError |
| 4 | class Continuation # :nodoc: # for RDoc |
| 5 | end |
| 6 | def Continuation.create(*args, &block) # :nodoc: |
| 7 | cc = nil; result = callcc {|c| cc = c; block.call(cc) if block and args.empty?} |
| 8 | result ||= args |
| 9 | return *[cc, *result] |
| 10 | end |
| 11 | end |
| 12 | |
| 13 | class Binding; end # for RDoc |
| 14 | # This method returns the binding of the method that called your |
| 15 | # method. It will raise an Exception when you're not inside a method. |
| 16 | # |
| 17 | # It's used like this: |
| 18 | # def inc_counter(amount = 1) |
| 19 | # Binding.of_caller do |binding| |
| 20 | # # Create a lambda that will increase the variable 'counter' |
| 21 | # # in the caller of this method when called. |
| 22 | # inc = eval("lambda { |arg| counter += arg }", binding) |
| 23 | # # We can refer to amount from inside this block safely. |
| 24 | # inc.call(amount) |
| 25 | # end |
| 26 | # # No other statements can go here. Put them inside the block. |
| 27 | # end |
| 28 | # counter = 0 |
| 29 | # 2.times { inc_counter } |
| 30 | # counter # => 2 |
| 31 | # |
| 32 | # Binding.of_caller must be the last statement in the method. |
| 33 | # This means that you will have to put everything you want to |
| 34 | # do after the call to Binding.of_caller into the block of it. |
| 35 | # This should be no problem however, because Ruby has closures. |
| 36 | # If you don't do this an Exception will be raised. Because of |
| 37 | # the way that Binding.of_caller is implemented it has to be |
| 38 | # done this way. |
| 39 | def Binding.of_caller(&block) |
| 40 | old_critical = Thread.critical |
| 41 | Thread.critical = true |
| 42 | count = 0 |
| 43 | cc, result, error, extra_data = Continuation.create(nil, nil) |
| 44 | error.call if error |
| 45 | |
| 46 | tracer = lambda do |*args| |
| 47 | type, context, extra_data = args[0], args[4], args |
| 48 | if type == "return" |
| 49 | count += 1 |
| 50 | # First this method and then calling one will return -- |
| 51 | # the trace event of the second event gets the context |
| 52 | # of the method which called the method that called this |
| 53 | # method. |
| 54 | if count == 2 |
| 55 | # It would be nice if we could restore the trace_func |
| 56 | # that was set before we swapped in our own one, but |
| 57 | # this is impossible without overloading set_trace_func |
| 58 | # in current Ruby. |
| 59 | set_trace_func(nil) |
| 60 | cc.call(eval("binding", context), nil, extra_data) |
| 61 | end |
| 62 | elsif type == "line" then |
| 63 | nil |
| 64 | elsif type == "c-return" and extra_data[3] == :set_trace_func then |
| 65 | nil |
| 66 | else |
| 67 | set_trace_func(nil) |
| 68 | error_msg = "Binding.of_caller used in non-method context or " + |
| 69 | "trailing statements of method using it aren't in the block." |
| 70 | cc.call(nil, lambda { raise(ArgumentError, error_msg) }, nil) |
| 71 | end |
| 72 | end |
| 73 | |
| 74 | unless result |
| 75 | set_trace_func(tracer) |
| 76 | return nil |
| 77 | else |
| 78 | Thread.critical = old_critical |
| 79 | case block.arity |
| 80 | when 1 then yield(result) |
| 81 | else yield(result, extra_data) |
| 82 | end |
| 83 | end |
| 84 | end |
Note: See TracBrowser for help on using the browser.