Kernel.load() behaviour

A

Andrew Walrond

I want to wrap a pile of classes, which are defined in their own files, inside
a wrapper module. Basically, I want this to work:

glibc.rb:
class Glibc
end

test.rb:
module Packages
load 'glibc.rb'
end

Packages::Glibc.new


But it doesn't do what I expected. Glibc gets added to the global namespace,
rather than the Packages namespace.

I.e. Glibc.new works; Packages::Glibc.new doesn't

Now, I see Kernel.load() is documented as

Kernel.load(filename,wrap=false)

If wrap==true, then "the loaded script will be executed under an anonymous
module, protecting the calling program's global namespace"

I guess I need the equivalent of
Kernel.load(filename,wrapper_module=nil)
So I can do
load('glibc.rb',Packages)

Or is there another way?

Suggestions appreciated

Andrew Walrond

PS Explicitly putting Packages:: on each class definition is possible, but
there are 000s of packages so I was looking for a better way....
 
R

Robert Klemme

Andrew Walrond said:
I want to wrap a pile of classes, which are defined in their own files, inside
a wrapper module. Basically, I want this to work:

glibc.rb:
class Glibc
end

test.rb:
module Packages
load 'glibc.rb'
end

Packages::Glibc.new


But it doesn't do what I expected. Glibc gets added to the global namespace,
rather than the Packages namespace.

I.e. Glibc.new works; Packages::Glibc.new doesn't

Now, I see Kernel.load() is documented as

Kernel.load(filename,wrap=false)

If wrap==true, then "the loaded script will be executed under an anonymous
module, protecting the calling program's global namespace"

I guess I need the equivalent of
Kernel.load(filename,wrapper_module=nil)
So I can do
load('glibc.rb',Packages)

Or is there another way?

This has come up recently. Unfortunately there's no chance of getting at
the anonymous module other than from within the loaded file. I'd wish it
was changed like this:

Kernel.load(file, mode=nil)

Where "mode" is interpreted like this:

- if it's nil or false, no wrapping takes place, return value is the
parameter

- if it's a module, that is used for wrapping, return value is the module

- if it's anything else (i.e. equivalent to true) an anonymous module is
created for wrapping, return value is the anonymous module

You might find more on this in the archives.
Suggestions appreciated

Andrew Walrond

PS Explicitly putting Packages:: on each class definition is possible, but
there are 000s of packages so I was looking for a better way....

You don't need that: simply wrap the file you want to load with

module Packages
# all 000s class definitions here
end

As easy as that. :)

Kind regards

robert
 
A

Andrew Walrond

This has come up recently. Unfortunately there's no chance of getting at
the anonymous module other than from within the loaded file. I'd wish it
was changed like this:

Kernel.load(file, mode=nil)

Where "mode" is interpreted like this:

- if it's nil or false, no wrapping takes place, return value is the
parameter

- if it's a module, that is used for wrapping, return value is the module

- if it's anything else (i.e. equivalent to true) an anonymous module is
created for wrapping, return value is the anonymous module

You might find more on this in the archives.

Thanks - I'll do a search.
You don't need that: simply wrap the file you want to load with

module Packages
# all 000s class definitions here
end

As easy as that. :)

Not quite; Each of the 000s of packages is in it's own file.... ;)

Andrew
 
R

Robert Klemme

Andrew Walrond said:
Thanks - I'll do a search.

Not quite; Each of the 000s of packages is in it's own file.... ;)

Then I probably misunderstood you. Care to reveal some more details of the
design and its rationale?

Kind regards

robert
 
B

Brian Mitchell

eval "module Packages\n" << `cat *.rb` << "\nend"

;-)

module Packages
end

Packages.module_eval( `cat glibc.rb`, 'glibc.rb`) # A little more elegant.

Brian.
 
A

Andrew Walrond

Then I probably misunderstood you. Care to reveal some more details of the
design and its rationale?

Essentially, I have lots of package files in a directory, each with a single
class derived from Package; eg class Glibc < Package

Now rather than polluting the global namespace (one of the packages is called
'File' which is an obvious clash) I just wanted to load them all into the
namespace of a module, without having to add <module Packages> ... <end>
around the class(es) in every one of the thousands of package files.

Very static and very boring ;)

Andrew
 
A

Andrew Walrond

module Packages
end

Packages.module_eval( `cat glibc.rb`, 'glibc.rb`) # A little more elegant.

Almost perfect, but this avoids the external 'cat' dependency:

Packages.module_eval( IO.readlines('glibc.rb').join, 'glibc.rb')

I learned some more ruby today. Thanks all!

Andrew
 
B

Brian Mitchell

Almost perfect, but this avoids the external 'cat' dependency:

Packages.module_eval( IO.readlines('glibc.rb').join, 'glibc.rb')

I learned some more ruby today. Thanks all!

One more idea that lets one use load and allows anonymous modules:

glibc.rb:
class Glibc
end

some_file.rb:
module Packages
end

class Package
def self.inherited(sub)
Packages.const_set(sub.name.split("::").last, sub)
end
end

load 'glibc.rb', true

# Packages.constants == ["Glibc"]

I also have an idea on how to do this with load my appending the
anonymous module BUT I couldn't get Module.nesting to work and it
seems ruby take some shortcuts in creation of the anon Module. If I
can fix this then we'll all be able to load into specific modules
which would be nice.

Brian.
 
S

Saynatkari

Con fecha 1/4/2005 said:
Almost perfect, but this avoids the external 'cat' dependency:

Packages.module_eval( IO.readlines('glibc.rb').join, 'glibc.rb')

I learned some more ruby today. Thanks all!

One more idea that lets one use load and allows anonymous modules:

glibc.rb:
class Glibc
end

some_file.rb:
module Packages
end

class Package
def self.inherited(sub)
Packages.const_set(sub.name.split("::").last, sub)
end
end

load 'glibc.rb', true

# Packages.constants == ["Glibc"]

This will still pollute the namespace, though, at
the initial loading. That seemed to be Andrew's
concern.
I also have an idea on how to do this with load my appending the
anonymous module BUT I couldn't get Module.nesting to work and it
seems ruby take some shortcuts in creation of the anon Module. If I
can fix this then we'll all be able to load into specific modules
which would be nice.

I submitted an RCR about this type of load behaviour (specifically
to enable the client programmer define a namespace for an imported
module rather than having the library author do it).

While the solutions here are likely suitable for this particular
situation, the more general problem is trying to load a file like
this:

class String
def my_string_extension()
# ...
end
end

module MyModule
class MyClass
# ...
end
end

The presumed extension of the standard String class will instead
create a new String class in the enveloping module.

E
 
A

Andrew Walrond

Con fecha 1/4/2005 said:
One more idea that lets one use load and allows anonymous modules:

glibc.rb:
class Glibc
end

some_file.rb:
module Packages
end

class Package
def self.inherited(sub)
Packages.const_set(sub.name.split("::").last, sub)
end
end

load 'glibc.rb', true

# Packages.constants == ["Glibc"]

This will still pollute the namespace, though, at
the initial loading. That seemed to be Andrew's
concern.

Not with
load 'glibc.rb',true
surely?

On a related note, if glibc.rb does require(filename) or load(filename,false),
this second level load breaks through and will pollute the top level global
namespace. I suppose this is the obvious, though unwanted behavior.

Andrew
 
S

Saynatkari

Con fecha 1/4/2005 said:
Con fecha 1/4/2005 said:
. ;)

eval "module Packages\n" << `cat *.rb` << "\nend"

;-)

module Packages
end

Packages.module_eval( `cat glibc.rb`, 'glibc.rb`) # A little more elegant.


Almost perfect, but this avoids the external 'cat' dependency:

Packages.module_eval( IO.readlines('glibc.rb').join, 'glibc.rb')

I learned some more ruby today. Thanks all!

One more idea that lets one use load and allows anonymous modules:

glibc.rb:
class Glibc
end

some_file.rb:
module Packages
end

class Package
def self.inherited(sub)
Packages.const_set(sub.name.split("::").last, sub)
end
end

load 'glibc.rb', true

# Packages.constants == ["Glibc"]

This will still pollute the namespace, though, at
the initial loading. That seemed to be Andrew's
concern.

Meh. Obviously ignore that.
I submitted an RCR about this type of load behaviour (specifically
to enable the client programmer define a namespace for an imported
module rather than having the library author do it).

While the solutions here are likely suitable for this particular
situation, the more general problem is trying to load a file like
this:

class String
def my_string_extension()
# ...
end
end

module MyModule
class MyClass
# ...
end
end

The presumed extension of the standard String class will instead
create a new String class in the enveloping module.

E
 
A

Andrew Walrond

On a related note, if glibc.rb does require(filename) or
load(filename,false), this second level load breaks through and will
pollute the top level global namespace. I suppose this is the obvious,
though unwanted behavior.

My final solution, taking the above into account:

c.rb
class Glibc
end

b.rb
inline 'c.rb'

class Gcc
end

a.rb
module Packages
def Packages.inline(filename)
eval(IO.readlines(filename).join,nil,filename)
end
inline('c.rb')
end

Packages::Gcc.new
Packages::Glibc.new


Nice and neat :)
 
A

Andrew Walrond

My final solution, taking the above into account:

c.rb
class Glibc
end

b.rb
inline 'c.rb'

class Gcc
end

a.rb
module Packages
def Packages.inline(filename)
eval(IO.readlines(filename).join,nil,filename)
end
inline('c.rb')
end

Packages::Gcc.new
Packages::Glibc.new

My final puzzle before sleep; How to throw an exception if any of the package
files try to use require() or load() instead of inline() ? (My package
developers might forget and pollute the global namespace)

Is there a neat way?

Andrew
 
B

Brian Mitchell

My final puzzle before sleep; How to throw an exception if any of the package
files try to use require() or load() instead of inline() ? (My package
developers might forget and pollute the global namespace)

Is there a neat way?

Andrew

This should give you the general idea if you want to prevent it.

module Kernel
alias _load load
def load(file, wrap)
raise "..." if check_if_f_is_a_package_file
_load(file, wrap)
end

alias _require require
def require(lib)
raise '...' if check_if_lib_plus_rb_is_a_package_file
_require(lib)
end
end

not sure if this covers everything you want. You may want to disable
require and load completely while you are in your eval:

# Not yet thread safe but can be made so.

module Packages
def Packages.disable_loading(&block)
Kernel.module_eval do
alias _load load
def load(*); raise '??' end
alias _require require
def require(*); raise '??' end
end
ret = yield
Kernel.module_eval do
alias load _load
alias require _require
end
ret
end

def Packages.inline(filename)
disable_loading {
eval(IO.readlines(filename).join, nil, filename)
}
end

inline('c.rb')
end

Brian.
 
R

Robert Klemme

You can shorten that to
eval( File.read(filename), nil, filename )
My final puzzle before sleep; How to throw an exception if any of the
package
files try to use require() or load() instead of inline() ? (My package
developers might forget and pollute the global namespace)

Is there a neat way?

I posted an approach for temporarily exchanging methods a while ago which
can be used for this under subject "Mocking new and other class methods":

module Kernel
private
def mock(obj, sym, mock)
cl = class <<obj; self; end
old = obj.method sym
cl.class_eval { define_method(sym, &mock) }

begin
yield
ensure
cl.class_eval { define_method(sym, &old) }
end
end
end

Something along these lines:

mock Kernel, :require, lambda {|f1| inline f} do
mock Kernel, :load, lambda {|f2,*| inline f2} do
require ...
end
end

Kind regards

robert
 
J

Jim Freeze

* Andrew Walrond said:
Almost perfect, but this avoids the external 'cat' dependency:

Packages.module_eval( IO.readlines('glibc.rb').join, 'glibc.rb')

Packages.module_eval( File.read('glibc.rb'), 'glibc.rb')
 
J

Joel VanderWerf

Andrew said:
My final puzzle before sleep; How to throw an exception if any of the package
files try to use require() or load() instead of inline() ? (My package
developers might forget and pollute the global namespace)

Is there a neat way?

If you can arrange that the context in which those nested require/load
calls occur is an object of your own choosing (rather than ruby's
top-level object), then you can make require and load do whatever you want.

Actually, this could be a way to unify your #inline with #load/#require
in such a way that it does the right thing, depending on where the
referenced file is located. That way, package developers are insulated
from the issue, and can still require standard libraries if they need
to. I can explain better with some code ...

In my "script" library (findable on RAA), there is a subclass of Module
called Script (probably too bland a name, but oh well). Script has a
module method Script.load that creates a new Script instance:

require 'script'
script = Script.load("scripts/simple-script.rb")

The script object returned from Script.load can be used to access all
the constants and top-level methods defined in the file. (A top level
method is one defined like "def foo ... end" without an explicit class
or module context.)

For example, if scripts/simple-script.rb has:

VALUE = [1,2,3]
def foo; "FOO"; end

Then the main program (with the Script.load line) can call

script.foo
script::VALUE

This is a convenient way to construct objects in a script and return
them to the program that loaded the script.

The underlying approach is to use module_eval, as others have suggested
on this thread, to load the definitions in the given file into a module
and return them, encapsulated into an instance of Script.

Here's the twist: instances of Script have their own #load and #require
implementation that augments Kernel's, and this affects nested loading.
Script#load behaves like Script.load: definitions go into the Script
instance (a module), rather than into the global namespace.
Script#require first tries the local dir (where the original script
file--in this case "scripts/simple-script.rb"--is located). Then it
falls back to Kernel#require. It maintains its own equivalent of
$LOADED_FEATURES so that you have the usual require semantics on the
local dir. So, as long as you doing it from the the top level, requiring
a local file puts the definitions into the script instance's scope, but
requiring a normal library file puts the definitions into the global
scope, as usual.

For example, if scripts/simple-script.rb might have this code:

require 'rbtree' # not found in scripts/, so look in usual
# places.
require 'other-script' # if found locally, load it in to the
# current Script context

That's really about all Script does, though there are some bells and
whistles: a way of passing in arguments to the loaded script, correct
reporting of file name and line number in exceptions generated by the
loaded files, a #to_s method, #autoscript (like #autoload).

If the script lib is not exactly what you want, you might still want to
take a look at the source (it's short) to see if it can be adapted. You
could change the require definition to raise an exception (note that
this only affects the top level, not require called dynamically from
within methods defined inside classes or modules other than the script
itself).

HTH.
 
A

Andrew Walrond

If the script lib is not exactly what you want, you might still want to
take a look at the source (it's short) to see if it can be adapted. You
could change the require definition to raise an exception (note that
this only affects the top level, not require called dynamically from
within methods defined inside classes or modules other than the script
itself).

This is useful. 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

Forum statistics

Threads
473,764
Messages
2,569,567
Members
45,041
Latest member
RomeoFarnh

Latest Threads

Top