Loading a module without polluting my namespace

H

Hagbard Celine

Hey folks!

In my current project I try to load a module dynamically. Which is
basically no problem due to 'require` accepting filenames as well. My
problem is that I'd totally pollute my namespace. I thought of something
like the following:

def load_module(filename)
module NamespaceGuard # Just a random name
require filename
# Take a care of the loaded module
end
# My namespace is clean again
end

But Ruby's syntax apparently forbids module definitions in methods. I'd
be very glad if anyone could help me with this problem.
 
I

Intransition

Hey folks!

In my current project I try to load a module dynamically. Which is
basically no problem due to 'require` accepting filenames as well. My
problem is that I'd totally pollute my namespace. I thought of something
like the following:

def load_module(filename)
=A0 module NamespaceGuard =A0# Just a random name
=A0 =A0 require filename
=A0 =A0 # Take a care of the loaded module
=A0 end
=A0 # My namespace is clean again
end

But Ruby's syntax apparently forbids module definitions in methods. I'd
be very glad if anyone could help me with this problem.

Usually the file you are loading has the "protective" namespace. Eg.

# namespace_guard.rb
module NamespaceGuard
...
end

# main.rb
require 'namespace_guard'
 
J

Joel VanderWerf

Hagbard said:
Hey folks!

In my current project I try to load a module dynamically. Which is
basically no problem due to 'require` accepting filenames as well. My
problem is that I'd totally pollute my namespace. I thought of something
like the following:

def load_module(filename)
module NamespaceGuard # Just a random name
require filename
# Take a care of the loaded module
end
# My namespace is clean again
end

But Ruby's syntax apparently forbids module definitions in methods. I'd
be very glad if anyone could help me with this problem.

You certainly can define modules dynamically:

def make_mod
Module.new do
def self.foo; p "FOO"; end
def bar; p "BAR"; end
end
end

m = make_mod

p m.methods(false) # ==> ["foo"]
p m.instance_methods(false) # ==> ["bar"]

m.foo # ==> "FOO"
x=[]
x.extend m
x.bar # ==> "BAR"

Also,, the #load method takes an optional argument that causes it to
wrap the loaded definitions in

$ ri Kernel#load | cat
------------------------------------------------------------ Kernel#load
load(filename, wrap=false) => true
------------------------------------------------------------------------
Loads and executes the Ruby program in the file filename. If the
filename does not resolve to an absolute path, the file is
searched for in the library directories listed in $:. If the
optional wrap parameter is true, the loaded script will be
executed under an anonymous module, protecting the calling
program's global namespace. In no circumstance will any local
variables in the loaded file be propagated to the loading
environment.

You can use this like so:

$ cat b.rb
def foo
puts "foo in b"
end

$ cat a.rb
load "b.rb", true # try this without the true

begin
foo
rescue => e
puts e
end

def foo
puts "foo in a"
end

foo

$ ruby a.rb
undefined local variable or method `foo' for main:Object
foo in a


However, you don't get easy access to the anonymous module. If you want
that, I have a little library that may be helpful:

http://redshift.sourceforge.net/script/
 
B

Brian Candler

Hagbard said:
In my current project I try to load a module dynamically. Which is
basically no problem due to 'require` accepting filenames as well. My
problem is that I'd totally pollute my namespace.

Kernel.load(filename, true) might help. But AFAIK that doesn't prevent
the source code from doing

class ::Object
def override_something_important
..
end
end

If you need to protect against untrusted code, have a look at _why's
sandbox.
 
H

Hagbard Celine

Thanks for your reply but I'm afraid that doesn't solve my problem. I
stumbled upon the optional argument of `load' as well but it prevents me
from accessing the loaded module. I'm sorry if I wasn't clear enough. I
would need something like this:

# foo.rb
module Foo
def method_a
end

def method_b
end
end

# bar.rb

def load_module
load "foo.rb"
puts Foo.methods
end
# `Foo' isn't known anymore


If I use `load "foo.rb", true' I can't access the loaded module or am I
mistaken?


Security is a minor problem for the moment as it's just a hobby project.
But I take a look at _why's sandbox anyway. Thanks for the hint.
 
B

Brian Candler

Hagbard said:
Thanks for your reply but I'm afraid that doesn't solve my problem. I
stumbled upon the optional argument of `load' as well but it prevents me
from accessing the loaded module.

I wish 'load' would simply return the anonymous module it has created,
but there are nasty workarounds. For example:

$ cat foo.rb
module Foo
def bar
puts "hello"
end
module_function :bar
end
$res = Foo

$ irb --simple-prompthello
=> nil
 
J

Joel VanderWerf

Hagbard said:
Thanks for your reply but I'm afraid that doesn't solve my problem. I
stumbled upon the optional argument of `load' as well but it prevents me
from accessing the loaded module. I'm sorry if I wasn't clear enough. I
would need something like this: ...
If I use `load "foo.rb", true' I can't access the loaded module or am I
mistaken?

That's correct (you can't access it without playing tricks like global
vars or searching ObjectSpace).

If security is not your concern, take a look at the script lib I
mentioned before:

http://redshift.sourceforge.net/script

Unlike load(..., true) , Script.load returns the wrapper module.
 
J

Joel VanderWerf

Hagbard said:
Thanks for your reply but I'm afraid that doesn't solve my problem. I
stumbled upon the optional argument of `load' as well but it prevents me
from accessing the loaded module. I'm sorry if I wasn't clear enough. I
would need something like this:

# foo.rb
module Foo
def method_a
end

def method_b
end
end

# bar.rb

def load_module
load "foo.rb"
puts Foo.methods
end
# `Foo' isn't known anymore

Your example is not quite right.

$ cat foo.rb
module Foo
def method_a
end

def method_b
end
end

$ cat bar.rb
def load_module
load "foo.rb"
puts Foo.instance_methods
end

load_module

$ ruby bar.rb
method_a
method_b
 
H

Hagbard Celine

Brian said:
I wish 'load' would simply return the anonymous module it has created,
but there are nasty workarounds.

Exactly this behavior I would have loved and needed. What a bummer that
there are just workarounds which are indeed nasty. I guess I have to
live with it. Thanks for your help.
 
H

Hagbard Celine

Rein said:
Properly written Rubby libraries namespace their classes and modules.
If you own the code you're requiring, fix it. If not, find an
alternative to the code in question (which I find suspect based on this
lack of namespacing) or perhaps you may find some luck with Kernel#load.

I happen to be in control of the loaded modules but I think you didn't
understand my problem correctly. If I use namespace guards (what I
actually do at the moment) the loaded modules remain in existence and
pollute my namespace. So, namespace guards don't really help me.

@Joel:
The `Script' library seems to fit my needs quite perfectly. Thank you
for the hint.
 
D

Dreamcat Four

Joel said:
However, you don't get easy access to the anonymous module. If you want
that, I have a little library that may be helpful:

http://redshift.sourceforge.net/script/

This is great, but seems to blows up when requiring other files

http://github.com/dreamcat4/plist4r/blob/master/lib/plist4r/backend/c_f_property_list.rb#L8

http://gist.github.com/471434

I noticed that there is a redefinition of require in script.rb. Is that
method meant to be handling that?

http://redshift.sourceforge.net/script/doc/classes/Script.html#M000003


Best regards

dreamcat4
(e-mail address removed)
 
J

Joel VanderWerf

Dreamcat said:

Is it possible to simplify the example a bit? I can't tell where the
missing const is actually defined.
I noticed that there is a redefinition of require in script.rb. Is that
method meant to be handling that?

http://redshift.sourceforge.net/script/doc/classes/Script.html#M000003

There's an example of using require with script in examples/program2.rb.
Maybe that helps?

Sorry not to be more helpful...
 
D

Dreamcat Four

Thanks for replying,
Will try to come back soon with an example for reproducing this.
 
D

Dreamcat Four

Joel said:
Is it possible to simplify the example a bit? I can't tell where the

Here is the example2.rb, but modified to reproduce this scenario.

http://github.com/dreamcat4/script/commit/0a89728d

When we run the same ruby code directly (example2-no-wrapper.rb), there
is no error. So the script wrapper is doing something differently than
ruby would normally.

My question:
Can find a way to get around this without touching the loaded source
code?
 
J

Joel VanderWerf

This is great, but seems to blows up when requiring other files

http://github.com/dreamcat4/plist4r/blob/master/lib/plist4r/backend/c_f_property_list.rb#L8

http://gist.github.com/471434

I noticed that there is a redefinition of require in script.rb. Is that
method meant to be handling that?

http://redshift.sourceforge.net/script/doc/classes/Script.html#M000003

Yes. The problem is in the way your script references the other files.
You've got absolute paths (which makes sense in the no-wrapper case),
but the Script class is expecting relative. The problem goes away
(AFAICT) with this patch:

--- /home/vjoel/tmp/dc2/examples/scripts/script.rb 2010-07-13
17:13:47.673562875 -0700
+++ - 2010-07-13 17:15:18.375246675 -0700
@@ -1,6 +1,6 @@
puts "in #{__FILE__}, line #{__LINE__}"

-load File.dirname(__FILE__)+"/sub-script.rb"
+load "sub-script.rb"

OUTPUT = ["input was #{INPUT}"]

@@ -9,10 +9,10 @@
end
end

-require File.dirname(__FILE__)+'/lib/a-class'
+require 'lib/a-class'

-require File.dirname(__FILE__)+'/lib/x-accessor'
-require File.dirname(__FILE__)+'/lib/x-accessor' # only loaded once
+require 'lib/x-accessor'
+require 'lib/x-accessor' # only loaded once

# Falls back to Kernel.load, since "benchmark.rb" isn't in the current dir.
load "benchmark.rb" unless $LOADED_FEATURES.include?("benchmark.rb")
 
D

Dreamcat Four

Here is a fix to the require() and load() methods. Its easier to patch
it there and just not to assume those methods are being given relative
paths.

http://github.com/dreamcat4/script/commit/e7d585b5

Joel said:
http://gist.github.com/471434

I noticed that there is a redefinition of require in script.rb. Is that
method meant to be handling that?

http://redshift.sourceforge.net/script/doc/classes/Script.html#M000003

Yes. The problem is in the way your script references the other files.
You've got absolute paths (which makes sense in the no-wrapper case),
but the Script class is expecting relative. The problem goes away
(AFAICT) with this patch:

--- /home/vjoel/tmp/dc2/examples/scripts/script.rb 2010-07-13
17:13:47.673562875 -0700
+++ - 2010-07-13 17:15:18.375246675 -0700
@@ -1,6 +1,6 @@
puts "in #{__FILE__}, line #{__LINE__}"

-load File.dirname(__FILE__)+"/sub-script.rb"
+load "sub-script.rb"

OUTPUT = ["input was #{INPUT}"]

@@ -9,10 +9,10 @@
end
end

-require File.dirname(__FILE__)+'/lib/a-class'
+require 'lib/a-class'

-require File.dirname(__FILE__)+'/lib/x-accessor'
-require File.dirname(__FILE__)+'/lib/x-accessor' # only loaded once
+require 'lib/x-accessor'
+require 'lib/x-accessor' # only loaded once

# Falls back to Kernel.load, since "benchmark.rb" isn't in the current
dir.
load "benchmark.rb" unless $LOADED_FEATURES.include?("benchmark.rb")
 
D

Dreamcat Four

Dreamcat said:
Here is a fix to the require() and load() methods. Its easier to patch
it there and just not to assume those methods are being given relative
paths.

http://github.com/dreamcat4/script/commit/e7d585b5

--- a/lib/script.rb
+++ b/lib/script.rb
@@ -52,7 +52,11 @@ class Script < Module
# from those sub files.

def load(file, wrap = false)
- load_in_module(File.join(@__dir, file))
+ if file =~ /^\//
+ load_in_module(file)
+ else
+ load_in_module(File.join(@__dir, file))
+ end
true
rescue MissingFile
super
@@ -70,7 +74,11 @@ class Script < Module
def require(feature)
unless @__loaded_features[feature]
@__loaded_features[feature] = true
- file = File.join(@__dir, feature)
+ if feature =~ /^\//
+ file = feature
+ else
+ file = File.join(@__dir, feature)
+ end
file += ".rb" unless /\.rb$/ =~ file
load_in_module(file)
end
--
1.6.6.1

Joel said:
http://gist.github.com/471434

I noticed that there is a redefinition of require in script.rb. Is that
method meant to be handling that?

http://redshift.sourceforge.net/script/doc/classes/Script.html#M000003

Yes. The problem is in the way your script references the other files.
You've got absolute paths (which makes sense in the no-wrapper case),
but the Script class is expecting relative. The problem goes away
(AFAICT) with this patch:

--- /home/vjoel/tmp/dc2/examples/scripts/script.rb 2010-07-13
17:13:47.673562875 -0700
+++ - 2010-07-13 17:15:18.375246675 -0700
@@ -1,6 +1,6 @@
puts "in #{__FILE__}, line #{__LINE__}"

-load File.dirname(__FILE__)+"/sub-script.rb"
+load "sub-script.rb"

OUTPUT = ["input was #{INPUT}"]

@@ -9,10 +9,10 @@
end
end

-require File.dirname(__FILE__)+'/lib/a-class'
+require 'lib/a-class'

-require File.dirname(__FILE__)+'/lib/x-accessor'
-require File.dirname(__FILE__)+'/lib/x-accessor' # only loaded once
+require 'lib/x-accessor'
+require 'lib/x-accessor' # only loaded once

# Falls back to Kernel.load, since "benchmark.rb" isn't in the current
dir.
load "benchmark.rb" unless $LOADED_FEATURES.include?("benchmark.rb")
 
J

Joel VanderWerf

Dreamcat said:
Here is a fix to the require() and load() methods. Its easier to patch
it there and just not to assume those methods are being given relative
paths.

Sure, that makes sense. I'll merge that in the next release. Thanks!
 

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

No members online now.

Forum statistics

Threads
473,744
Messages
2,569,482
Members
44,900
Latest member
Nell636132

Latest Threads

Top