Ruby equivalent to source command

B

Brett Williams

Hello all,

I am trying to make a "source" command for Ruby (a la sh or tcl), but I
think lexical scoping is tripping me up. I've done a lot of reading
about bindings, and it seems I just can't do what I'm trying to do.

def source(filename, bind = TOPLEVEL_BINDING)
code = nil
File.open(filename) { |f| code = f.read }
eval(code, bind)
end

This works as I would like, with the exception of local variables
present in the sourced file. For example, if I had something like this
in the sourced file:

x = "value set in sourced file"

then I get an error : undefined local variable or method 'x'

unless I extend the scope of x by setting it before sourcing the file.

Is there any way to accomplish what I want? I could work around this by
sticking to instance variables or *gulp* global variables for this
application, but before I throw in the towel I wanted to throw this out
to people here.

Thanks,

Brett Williams



P.S.: Things have changed since I was a pretty regular poster back in
2002-2003... ;)
 
R

Robert Klemme

2008/2/12 said:
I am trying to make a "source" command for Ruby (a la sh or tcl), but I
think lexical scoping is tripping me up. I've done a lot of reading
about bindings, and it seems I just can't do what I'm trying to do.

def source(filename, bind = TOPLEVEL_BINDING)
code = nil
File.open(filename) { |f| code = f.read }
eval(code, bind)
end

This works as I would like, with the exception of local variables
present in the sourced file. For example, if I had something like this
in the sourced file:

x = "value set in sourced file"

then I get an error : undefined local variable or method 'x'

Can you show more code? For me it works:

17:05:12 ~
$ echo "x=123; puts x" | ruby -e 'eval(ARGF.read, TOPLEVEL_BINDING)'
123
17:05:18 ~
$ echo "x=123; puts x" | ruby -e 'eval(ARGF.read, binding)'
123
17:05:25 ~
$

Or are you seeing this:

17:05:25 ~
$ echo "x=123" | ruby -e 'eval(ARGF.read, binding); puts x'
-e:1: undefined local variable or method `x' for main:Object (NameError)
17:06:31 ~
$
unless I extend the scope of x by setting it before sourcing the file.

Like

17:06:31 ~
$ echo "x=123" | ruby -e 'x=1; eval(ARGF.read, binding); puts x'
123
17:07:32 ~
$

This is because local variables are detected at compile time. That's
why Ruby thinks "x" in my bit above is a method because there is no
assignment.
Is there any way to accomplish what I want? I could work around this by
sticking to instance variables or *gulp* global variables for this
application, but before I throw in the towel I wanted to throw this out
to people here.

A global is probably much more appropriate here. If the code you
source somehow generates configuration settings then maybe you can set
a global Hash that will receive values. It depends on the larger
context of what you want to achieve.
P.S.: Things have changed since I was a pretty regular poster back in
2002-2003... ;)

Does this have to do with your email address? ;-) If yes, congrats
and greetings to Becky!

Cheers

robert
 
B

Brett Williams

Robert said:
2008/2/12 said:
This works as I would like, with the exception of local variables
present in the sourced file. For example, if I had something like this
in the sourced file:

x = "value set in sourced file"

then I get an error : undefined local variable or method 'x'

[snip]

Or are you seeing this:

17:05:25 ~
$ echo "x=123" | ruby -e 'eval(ARGF.read, binding); puts x'
-e:1: undefined local variable or method `x' for main:Object (NameError)
17:06:31 ~
$
unless I extend the scope of x by setting it before sourcing the file.

Like

17:06:31 ~
$ echo "x=123" | ruby -e 'x=1; eval(ARGF.read, binding); puts x'
123
17:07:32 ~
$
Precisely.

This is because local variables are detected at compile time. That's
why Ruby thinks "x" in my bit above is a method because there is no
assignment.

Aye, that's the problem.
A global is probably much more appropriate here. If the code you
source somehow generates configuration settings then maybe you can set
a global Hash that will receive values. It depends on the larger
context of what you want to achieve.

I figured this was the case. I'll go with globals for my context.
Does this have to do with your email address? ;-) If yes, congrats
and greetings to Becky!

Indeed no. Becky predates even my Ruby (which I started using as my
primary language in 2001). I'm just very happy to see this list
mirrored on ruby-forum which makes it much more convenient for me
personally. The email address is one not used for much -- it functions
mainly as a spam magnet =)
 
Y

yermej

A global is probably much more appropriate here. If the code you
source somehow generates configuration settings then maybe you can set
a global Hash that will receive values. It depends on the larger
context of what you want to achieve.

In cases like this, I've found it helpful to define a method that
returns the configuration hash:

$ echo "def config; {:a => 2}; end" | ruby -e 'eval(ARGF.read,
binding); puts config[:a]'
 
R

Robert Dober

The horror, the horror. I agree, i agree ;)

Here's an alternative: http://redshift.sourceforge.net/script.
It does not seem to handle OP's problem though, which are locals or am I wrong?
My idea would be to use Smalltalk constants, does anybody save Rick
know what that is? ;)
But of course constants are not enough so I will define setters too :)

But I am not sure if that works <blush> let me see:
Yup seems to work

file: test1.rb
a="sourced a"
b="sourced b"

file: main.rb
a = "Main a"
eval File.open("test1.rb"){|f| f.read } << %{
local_variables.each do |lvar|
this = class << self; self end
this.send :define_method, lvar do eval lvar end
this.send :define_method, lvar + "=" do |new_val| lvar = new_val end
end
}
p [:now_a, a]
p [:now_b, b]
b = "changed"
p [:changed_b, b]

----------------------------------------------------------------
Now it depends very much on the use case if you can live with this.
Any pitfalls I have overseen?

HTH
Robert
 
J

Joel VanderWerf

Robert said:
It does not seem to handle OP's problem though, which are locals or am I wrong?

Sorry, I should have made that clear. It uses module_eval, so you don't
get locals, but using module constants and/or methods is better than
globals :)

I like your hack. One possible pitfall is if the file has __END__.
Another is that local vars can become methods that globally shadow
Kernel/Object methods. For example, add this line to test1.rb:

open = false

Still, I might add this to the script lib as an option to capture locals
in the module.
 
R

Robert Dober

Sorry, I should have made that clear. It uses module_eval, so you don't
get locals, but using module constants and/or methods is better than
globals :)

I like your hack. One possible pitfall is if the file has __END__.
Another is that local vars can become methods that globally shadow
Kernel/Object methods. For example, add this line to test1.rb:

open = false very good OP watch out

Still, I might add this to the script lib as an option to capture locals
in the module.
If you do so just substract the Kernel methods (and potentially
Objects instance methods) from the locals array to avoid shadowing.

I knew I had forgotten something important, thx Joel.
R.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
474,262
Messages
2,571,056
Members
48,769
Latest member
Clifft

Latest Threads

Top