Created
December 8, 2010 05:29
-
-
Save cjheath/732930 to your computer and use it in GitHub Desktop.
ScopeVariables - set and get local variables in a method that passed you a block(!)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# | |
# ScopeVariables is a collection of utility methods to access variables | |
# relative to a specific Proc or Binding. It allows getting and assigning | |
# local variables in a method which passed us a block, for example: | |
# | |
# def monkey &block | |
# block.var_set("v1", "goodbye") | |
# end | |
# | |
# class Test | |
# def foo | |
# v1 = "Hello" | |
# monkey {} | |
# v1 | |
# end | |
# | |
# end | |
# | |
# Test.new.foo | |
# => "goodbye" | |
# | |
# ScopeVariables is a module which may be included anywhere. | |
# It contains the module Scoped, which is mixed in to Binding and Proc | |
# | |
module ScopeVariables | |
# If 'name' resolves to a Method in the given scope, return it | |
def method_in_scope name, scope | |
eval("method(:'#{name}') rescue nil", scope) | |
end | |
# Return true if the named variable can be read in the given scope. | |
# This includes methods which are callable without arguments. | |
def var_can_be_got_in_scope var_name, scope | |
if m = method_in_scope(var_name, scope) and m.arity != 0 and m.arity != -1 | |
nil | |
else | |
eval(var_name, scope) | |
end | |
end | |
# Get the value of the named variable in the given scope. | |
def var_get_in_scope var_name, scope | |
var_value = nil | |
begin | |
var_value = eval(var_name, scope) | |
rescue NameError => e | |
puts "Can't get #{var_name}: #{e.message}" | |
return nil | |
rescue ArgumentError => e | |
puts "Can't get variable #{var_name}, it appears to be a method needing arguments: #{e.message}" | |
return nil | |
end | |
var_value | |
end | |
# Return true if the named variable can be set in the given scope. | |
# Non-local variables can always be set, but locals must exist first. | |
def var_can_be_set_in_scope var_name, scope | |
case | |
when var_name =~ /^[@$]/ # Object, class or global variable | |
return true | |
when method_in_scope(var_name, scope) # A method, bail out | |
return false | |
else # A local variable, if it exists already | |
var_can_be_got_in_scope(var_name, scope) | |
end | |
end | |
# Assign a value to the named variable in this scope. | |
# This method does no checks. It will happily create an invisible local variable, for example: | |
def var_set_in_scope var_name, value, scope | |
eval("#{var_name} = #{value.inspect}", scope) | |
end | |
module Scoped | |
include ScopeVariables | |
def var_can_be_got var_name | |
var_can_be_got_in_scope(var_name, self) | |
end | |
def var_get var_name | |
var_get_in_scope var_name, self | |
end | |
def var_can_be_set var_name | |
var_can_be_set_in_scope(var_name, self) | |
end | |
def var_set var_name, value | |
var_set_in_scope(var_name, value, self) | |
end | |
end | |
end | |
class Binding | |
include ScopeVariables::Scoped | |
end | |
class Proc | |
include ScopeVariables::Scoped | |
end | |
if __FILE__ == $0 | |
require 'ruby-debug' | |
Debugger.start | |
class MonkeyExample | |
@@class1 = "was class1" | |
def initialize | |
@instance1 = "was instance1" | |
end | |
def play_up param1 | |
local1 = "was local1" | |
puts "----- Before monkeying -----" | |
puts "param1: #{param1.inspect}" rescue nil | |
puts "local1: #{local1.inspect}" rescue nil | |
puts "@instance1: #{@instance1.inspect}" rescue nil | |
puts "@@class1: #{@@class1.inspect}" rescue nil | |
# Pass the names of existing variables: | |
monkey_with {%{local1 param1 @instance1 @@class1 play_up foo}} | |
puts "param1: #{param1.inspect}" rescue nil | |
puts "param2: #{param2.inspect}" rescue nil | |
puts "local1: #{local1.inspect}" rescue nil | |
puts "foz: #{foz.inspect}" rescue nil | |
puts "local2: #{local2.inspect}" rescue nil | |
puts "@instance1: #{@instance1.inspect}" rescue nil | |
puts "@instance2: #{@instance2.inspect}" rescue nil | |
puts "@@class1: #{@@class1.inspect}" rescue nil | |
puts "@@class2: #{@@class2.inspect}" rescue nil | |
end | |
def foo | |
"bar" | |
end | |
end | |
def monkey_with &block | |
var_names = block.call.split | |
#context = Proc.new | |
#context = block.binding | |
context = block | |
var_names.each do |var_name| | |
monkey_name = var_name.sub(/1$/,'2') | |
# Get the value of the old variable, if possible: | |
var_value = context.var_get(var_name) | |
if var_name.nil? && !context.var_can_be_got(var_name) | |
puts "No value is available for #{var_name}" | |
end | |
# Change it, if possible: | |
if !context.var_can_be_set(var_name) | |
puts "#{var_name} is a variable that #{ | |
context.var_can_be_got(var_name) ? 'seems to exist but ' : '' | |
}cannot be set (trying anyway)" | |
end | |
context.var_set(var_name, "now #{var_value.to_s.sub(/^was /,'')}") | |
context.var_set(monkey_name, "monkeyed #{var_value.to_s.sub(/^was /,'')}") | |
end | |
puts "----- Finished monkeying -----" | |
end | |
MonkeyExample.new.play_up("was param1") | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment