In my efforts to write yet another template language {I know, I
know …grin}, I find myself wanting to provide simple named
parameters ala Mason. I’d like to insulate the user from the
mechanics of “dereferencing” the hashed parameters.
What I desire is an implementation for “parameters” such that:
def announce(args)
parameters(args,:age=>21)
"My name is #{first} #{last} and my age is #{age}"
end
announce(:first=>‘John’, :last=>‘Smith’) generates
"My name is John Smith and my age is 21"
I was thinking something like …
def parameters(args,defaults)
args=defaults.update(args)
args.each_pair do |key,value|
< generate a object with the name ‘key’ and value 'value’
in the scope of the calling routine >
end
end
But the <…> portion has eluded me. Any assistance would be
appreciated.
def announce(args)
parameters(args,:age=>21)
“My name is #{first} #{last} and my age is #{age}”
end
announce(:first=>‘John’, :last=>‘Smith’) generates
“My name is John Smith and my age is 21”
You can use:
args.each { |key,val| eval “#{key}=#{val}” }
However, this is extremely insecure if you allow joe user to create their own template. They could execute any code they want via it. (#{IO.readlines(‘/etc/passwd’).join}), for example. My suggestion is to write your own template style:
def announce(args,template)
template.gsub(/#((\w+))/) { args[$1] }
end
In my efforts to write yet another template language {I know, I
know …grin}, I find myself wanting to provide simple named
parameters ala Mason. I’d like to insulate the user from the
mechanics of “dereferencing” the hashed parameters.
What I desire is an implementation for “parameters” such that:
def announce(args)
parameters(args,:age=>21)
“My name is #{first} #{last} and my age is #{age}”
end
announce(:first=>‘John’, :last=>‘Smith’) generates
“My name is John Smith and my age is 21”
I was thinking something like …
def parameters(args,defaults)
args=defaults.update(args)
args.each_pair do |key,value|
< generate a object with the name ‘key’ and value ‘value’
in the scope of the calling routine >
end
end
But the <…> portion has eluded me. Any assistance would be
appreciated.
Binding and eval are your friends. But they do not fully solve your
problem, since local variables are checked syntactically for presence:
def test(hash)
value = nil
env = binding
hash.each do |key,value|
eval “#{key}=value”, env
end
puts “last value was #{value}”
error because “name” is syntactically checked for presence:
puts “name is #{name}”
hash.each do |key,value|
eval “printf "#{key}=%s\n", #{key}.to_s”, env
end
end
irb(main):219:0* test( { :foo => “bar”, :name => “Smith” } )
last value was Smith
foo=bar
name=Smith
In my efforts to write yet another template language {I know, I
know …grin}, I find myself wanting to provide simple named
parameters ala Mason. I’d like to insulate the user from the
mechanics of “dereferencing” the hashed parameters.
What I desire is an implementation for “parameters” such that:
def announce(args)
parameters(args,:age=>21)
“My name is #{first} #{last} and my age is #{age}”
end
announce(:first=>‘John’, :last=>‘Smith’) generates
“My name is John Smith and my age is 21”
If you’re willing to add “{” and “}” in your announce method you could do:
def parameters(args,defaults,&blk)
args=defaults.update(args)
o = Object.new
args.each do |key,value|
o.class.send :define_method, key, proc { value }
end
o.instance_eval &blk
end
def announce(args)
parameters(args,:age=>21) {
“My name is #{first} #{last} and my age is #{age}”
}
end
announce(:first=>‘John’, :last=>‘Smith’)
Note that in the block passed to the method parameters you can only
access the args (first, last, …), but not instance variables or
methods from the calling object.
Note that in the block passed to the method parameters you can only
access the args (first, last, …), but not instance variables or
methods from the calling object.
This wasn’t correct: you can even call methods, just not access instance
variables.
Received: Mon, 15 Dec 2003 11:38:30 +0900
And lo David wrote:
The variables you create that way will only be in scope in the
eval block itself:
eval “a = 1; puts a” # 1
puts a # error: a is undefined
David
Not quite; eval creates variables at current scope, But you are right about one thing - the .each |key,val| scope will keep all the variables.
str = “”
args.each do |key,val|
str << “#{key}=‘#{val}’\n”
end
eval str # Will place all the variables at the current scope.
I still recommend using the regexp search-and-replace instead of that, though. It also doesn’t require the string itself to be in the templating function. Not to mention all the ickiness involved with eval, etc.
Received: Mon, 15 Dec 2003 11:38:30 +0900
And lo David wrote:
The variables you create that way will only be in scope in the
eval block itself:
eval “a = 1; puts a” # 1
puts a # error: a is undefined
David
Not quite; eval creates variables at current scope, But you are
No it doesn’t. Well, strictly speaking it does, since “current
scope” means its own block, which it then exits causing the
variables to go out of scope. But if by “current scope” you mean
the scope from which eval was called, then, as my example shows,
the variables are not created in that scope.
Your example shows the same thing actually
$ cat ev.rb
args = { “a” => “hello” }
str = “”
args.each do |key,val|
str << “#{key}=‘#{val}’\n”
end
eval str
puts a
$ ruby ev.rb
ev.rb:8: undefined local variable or method `a’
for main:Object (NameError)
No it doesn't. Well, strictly speaking it does, since "current
scope" means its own block, which it then exits causing the
variables to go out of scope. But if by "current scope" you mean
the scope from which eval was called, then, as my example shows,
the variables are not created in that scope.
not understood, sorry
svg% cat b.rb
#!/usr/bin/ruby
args = { "a" => "hello" }
str = ""
args.each do |key,val|
str << "#{key}='#{val}'\n"
end
eval str
eval 'puts a'
svg%
No it doesn’t. Well, strictly speaking it does, since “current
scope” means its own block, which it then exits causing the
variables to go out of scope. But if by “current scope” you mean
the scope from which eval was called, then, as my example shows,
the variables are not created in that scope.
not understood, sorry
svg% cat b.rb
#!/usr/bin/ruby
args = { “a” => “hello” }
str = “”
args.each do |key,val|
str << “#{key}=‘#{val}’\n”
end
eval str
eval ‘puts a’
svg%
svg% b.rb
hello
I was saying that if you do
puts a
instead of
eval ‘puts a’
‘a’ will not be known. This was in response to Greg’s post.
Mind you, I forgot about the fact that you can re-eval ‘a’ and get
back the same ‘a’ – but here the goal was to create variables at the
scope surrounding the eval, not in the eval block itself.