Unique Object for Each source file

J

Joel VanderWerf

This is a silly little bit of code, but it's sort of an answer to
http://ruby-talk.org/99775, "RCR: Unique Object for Each source file".

Also, I had been thinking about proposing an RCR to let Kernel#load
return the last value in the file it loads, so it would be easy to
construct an object in a script file and hand it back to the main
program. But after reading back to the thread at
http://ruby-talk.org/62660, that didn't seem like the best way to go.
So, based on Nobu's suggestion in http://ruby-talk.org/62727, here is a
Script class which does the following:

- program.rb:

require 'script'
script = Script.load("script.rb")
p script::VALUE
script.run

- script.rb:

VALUE = [1,2,3]
def run
puts "#{self} running."
end

- output:

$ ruby program.rb
[1, 2, 3]
#<Script:/tmp/script.rb> running.

The value returned by Script.load is a module, and you can access its
constants and module methods, as expected. You can, of course, define
any number of constants or methods in the scope of the script file.
Defining things at the top level in your script file behaves as you
expect, like definitions at the top level of ruby itself, but in the
scope of the script. More precisely, every "def foo" results in
'module_function "foo"' getting invoked, so you can call foo on the
script object itself, back in the main program. This also means that
instance variables defined at the top level of your script are
accessible from the module functions that you define at the top level.

Script has implementations of #load and #require that, called from
within a "script", try to find files locally (relative to the script
file) and then fall back to the Kernel versions of #load and #require,
which look in $LOAD_PATH. If a local file is found, its definitions go
into the same scope as the original script. The intent is that if you
have main script file that loads some others, and everything works
nicely with "ruby script.rb", then it should also work with
Script.load("script.rb"), except that all of your definitions will be
wrapped in the module returned by Script.load. (And it will not break if
you Dir.chdir.)

Also, it is possible to pass "arguments" to a script by attaching a
block to Script.load and using it to define constants in the script's scope.

The implementation of Script is very simple and is included below. A
complete package with documentation and examples is at
http://redshift.sourceforge.net/script.

It's seems like too trivial a thing to put on RAA, especially with a
grand name like "script", so I'll just leave where it is for now, unless
someone has a better suggestion.

-- script.rb
class Script < Module
attr_reader :main_file
attr_reader :dir
attr_reader :loaded_features

class << self
alias load new
end

def initialize(main_file)
@main_file = File.expand_path(main_file)
@dir = File.dirname(@main_file)
@loaded_features = {}

yield self if block_given?
load_in_module(main_file)
end

def load(file, wrap = false)
load_in_module(File.join(@dir, file))
true
rescue MissingFile
super
end

def require(feature)
unless @loaded_features[feature]
@loaded_features[feature] = true
file = File.join(@dir, feature)
file += ".rb" unless /\.rb$/ =~ file
load_in_module(file)
end
rescue MissingFile
@loaded_features[feature] = false
super
end

class MissingFile < LoadError; end

def load_in_module(file)
module_eval(IO.read(file), file)
rescue Errno::ENOENT => e
if /#{file}$/ =~ e.message
raise MissingFile, e.message
else
raise
end
end

def method_added(name)
module_function(name)
end

def to_s
"#<#{self.class}:#{File.join(dir, File.basename(main_file))}>"
end
end
 

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,766
Messages
2,569,569
Members
45,042
Latest member
icassiem

Latest Threads

Top