Skip to content

Instantly share code, notes, and snippets.

@apeiros
Created January 28, 2010 19:28
Show Gist options
  • Save apeiros/289055 to your computer and use it in GitHub Desktop.
Save apeiros/289055 to your computer and use it in GitHub Desktop.
# This code is under BSD 3-clause license
# Author: Stefan Rusterholz <stefan.rusterholz@gmail.com> (aka apeiros)
module Kernel
ValidLocalVariableName = /\A[a-z_]\w*\z/
# Dynamically define a bunch of local variables from a hash.
# Overrides existing local variables.
#
# IMPORTANT: This is an anti-pattern. Think thrice before using this. It
# almost always means you're doing it wrong and should use a Hash instead.
#
# Arguments:
# binding: The binding the variables should appear in, usually this is
# the current binding which you get via Kernel#binding
# variables: A Hash containing the variable names and their values. The
# keys must be Symbols. The values can be any object.
# valid_names: Optional. An array with valid local variable names.
#
# Example:
# extract(binding, :foo => 1, :bar => 2)
# foo # => 1
# bar # => 2
# extract(binding, :foo => 3, :bar => 4)
# foo # => 3
# bar # => 4
#
# Also see Kernel#safe_extract
#
def extract(binding, variables, valid_names=nil)
# Verify against provided variable names
if valid_names && !(variables.keys-valid_names).empty? then
raise ArgumentError,
"Contains invalid names #{(variables.keys-valid_names).join(', ')}"
end
# Verify that all names are really names to prevent code injection
# only do it if the user didn't provide a list with valid names
unless valid_names || variables.keys.all? { |name| name.to_s =~ ValidLocalVariableName }
raise ArgumentError, "Contains invalid identifiers"
end
# Deposit the data somewhere threadsafe without corrupting the namespace
Thread.current[:__extract__] = variables
# Assign the lvars from our deposit
code = "#{variables.keys.join(', ')} = Thread.current[:__extract__]." \
"values_at(:#{variables.keys.join(', :')})"
eval(code, binding)
# Clean up
Thread.current[:__extract__] = nil # no `Thread#delete`? odd...
nil
end
module_function :extract
# Dynamically define a bunch of local variables from a hash.
# Will only set previously undefined local variables.
#
# IMPORTANT: This is an anti-pattern. Think thrice before using this. It
# almost always means you're doing it wrong and should use a Hash instead.
#
# Arguments:
# binding: The binding the variables should appear in, usually this is
# the current binding which you get via Kernel#binding
# variables: A Hash containing the variable names and their values. The
# keys must be Symbols. The values can be any object.
# valid_names: Optional. An array with valid local variable names.
#
# Example:
# safe_extract(binding, :foo => 1, :bar => 2)
# foo # => 1
# bar # => 2
# safe_extract(binding, :foo => 3, :bar => 4)
# foo # => 1
# bar # => 2
#
# Also see Kernel#extract
#
def safe_extract(binding, variables, valid_names=nil)
# Verify against provided variable names
if valid_names && !(variables.keys-valid_names).empty? then
raise ArgumentError,
"Contains invalid names #{(variables.keys-valid_names).join(', ')}"
end
# Verify that all names are really names to prevent code injection
# only do it if the user didn't provide a list with valid names
unless valid_names || variables.keys.all? { |name| name.to_s =~ ValidLocalVariableName }
raise ArgumentError, "Contains invalid identifiers"
end
# Deposit the data somewhere threadsafe without corrupting the namespace
Thread.current[:__extract__] = variables
# Assign the lvars from our deposit
variables.each_key do |name|
eval("unless defined?(#{name}) == 'local-variable'; #{name} = Thread.current[:__extract__][:#{name}]; end", binding)
end
# Clean up
Thread.current[:__extract__] = nil # no `Thread#delete`? odd...
nil
end
module_function :safe_extract
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment