From: Joel VanderWerf Date: 2004-05-16T17:13:30+09:00 Subject: Unique Object for Each source file 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] # 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