Skip to content

Instantly share code, notes, and snippets.

@cjheath
Created December 8, 2010 05:29
Show Gist options
  • Save cjheath/732930 to your computer and use it in GitHub Desktop.
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(!)
#
# 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