Where the Required Things Are (#175)

Great job on this quiz, everyone who submitted. I know I’ll be adding
at least one of these submissions to my toolbox.

Ten solutions were provided this week. I downloaded them all and
tested each one in the manner requested in the extra credit. (For
anyone who did not attempt the extra credit, make sure to look at the
other solutions; adding this capability is only a small amount of
extra code.) To test, I used the sample file provided by Michal
Suchanek, shown here.

require 'date'
require 'readline'
require 'matrix'
require 'rubygems'
require 'time'
require 'hpricot'
require 'rubyforge'
require 'hoe'
require 'curses'

I made all modules were installed before testing. (Actually, some
tests were done with missing modules, with mostly expected results. In
the end, it was more interesting to see which solutions worked with a
set of requires that did exist.) A more extensive set of tests would
be appropriate to bullet-proof any solutions, but this is sufficient
for this quiz.

Ten solutions were provided from nine users (Suchanek providing two
solutions). Of those ten, three failed with errors (Shelly, Stevens,
Suchanek #2). Two solutions stumbled a bit when rubygems got involved.
The first (Bilkovich) failed to find any of the gems: hpricot,
rubyforge and hoe. The second (Reitinger) found rubygems but
failed on the next (non-gem) require, time; since rubygems does
its own thing to require, this is a possible source of problems to
be addressed.

It’s possible these problems might be caused by platform differences;
I am running ruby 1.8.6 (MacPorts install) on Mac OS X 10.4.11. I
didn’t have time to dig into these errors, but if you care to know the
specific error, let me know.

The next problem I found was that three solutions (Morin, Phillips,
Wille) did not report the locations of the readline and etc
modules. The likely cause here is, again, platform differences. These
two modules in Mac OS X are “.bundle” files. One solution (Wille)
reported the modules as not found, while the other two (Morin,
Phillips) simply reported nothing for those two modules.

Interestingly, the “cheat” provided by Wille, gem which, was also
unable to locate readline and etc.

That leaves us with two “winners” for this week: Jesse M. and
Michal S. (his first solution). Both solutions found all the
required modules, recursively, including the .bundle files, and didn’t
get confused by rubygems. I’m going to look here at Jesse’s
solution.

module Kernel
  alias :require_orig :require

  def require mod
    if success = require_orig(mod)
      file = $".last

      $:.each do |dir|
        path = "#{dir}/#{file}"
        if File.readable?(path)
          puts "require #{mod} => #{path}"
          break
        end
      end
    end

    success
  end
end

require is a Kernel method, so the first thing Jesse does is open
the Kernel module and keep track of the original require method
via an alias. Next, the new require method is defined, and the first
thing it does is to call the original method, which is why the alias
was needed.

There are three possible results. First, if require_orig throws an
exception… well, nothing gets done here. The exception throws out of
this method, reproducing the standard behavior. Second, if
require_orig returns false (and sets success accordingly), the the
body is skipped and false is returned. Again, standard behavior has
been maintained. The good stuff is what happens when require_orig
returns true.

Jesse makes use of two predefined global variables. $" is an array
containing module names loaded by require; calling last on that
array gets the most recently loaded module. $: is also an array, but
contains the search paths for modules.

So once a file is successfully loaded via a call to require_orig,
Jesse checks through the module search paths, and builds a full path
for the loaded module, checking that path against File.readable?. If
so, the information is printed to standard output and the loop ends.

Finally, most of you should recognize the standard technique for
knowing when a script is run rather than loaded via require:

if __FILE__ == $0
  ARGV.each { |mod| require mod }
end

Make sure to look at Michal S.'s solution as well. The use of
module rbconfig and method binding were new to me.

One final note: my machine is running Mac OS X, so of course, all my
testing has been using that. This has two implications: some programs
may not have worked (pehaps some that I mentioned above), and also
that the two “complete” solutions (Merriman, Suchanek) could quite
possibly be broken on other platforms. I don’t know. (I’m a bit
occupied this week.:slight_smile: A 100% solid solution should account for all
these things.

hi matthew!

thanks for the fun quiz :slight_smile:

Matthew M. [2008-09-04 15:45]:

Interestingly, the “cheat” provided by Wille, gem which, was
also unable to locate readline and etc.
well, it does on my machine. which is why i added it as a
“fallback”. the problem with my approach is that SCRIPT_LINES__
doesn’t include binary files (.so, .bundle, …).

~/devel/scratch [git:[email protected]]> ruby -rmodwhich.rb -e ‘require
“readline”; require “etc”’
require ‘readline’ => /usr/lib/ruby/1.8/x86_64-linux/readline.so
require ‘fileutils’ => /usr/lib/ruby/1.8/fileutils.rb
require ‘etc’ => /usr/lib/ruby/1.8/x86_64-linux/etc.so

cheers
jens

On 04-09-2008, at 9:45, Matthew M. wrote:

That leaves us with two “winners” for this week: Jesse M. and
Michal S. (his first solution). Both solutions found all the
required modules, recursively, including the .bundle files, and didn’t
get confused by rubygems.

hey, what about my solution? :stuck_out_tongue:
it also solves the test:

rolando:Desktop rolando$ cat test_quiz175.rb
require ‘date’
require ‘readline’
require ‘matrix’
require ‘rubygems’
require ‘time’
require ‘hpricot’
require ‘rubyforge’
require ‘hoe’
require ‘curses’

rolando:Desktop rolando$ ruby -r quiz175 test_quiz175.rb
require ‘date’ => /usr/local/lib/ruby/1.8/date.rb
require ‘readline’ => /usr/local/lib/ruby/1.8/i686-darwin9.2.2/
readline.bundle
require ‘matrix’ => /usr/local/lib/ruby/1.8/matrix.rb
require ‘rubygems’ => /usr/local/lib/ruby/site_ruby/1.8/rubygems.rb
require ‘time’ => /usr/local/lib/ruby/1.8/time.rb
require ‘hpricot’ => /usr/local/lib/ruby/gems/1.8/gems/hpricot-0.6/
lib/hpricot.rb
require ‘rubyforge’ => /usr/local/lib/ruby/gems/1.8/gems/
rubyforge-1.0.0/lib/rubyforge.rb
require ‘hoe’ => /usr/local/lib/ruby/gems/1.8/gems/hoe-1.7.0/lib/
hoe.rb
require ‘curses’ => /usr/local/lib/ruby/1.8/i686-darwin9.2.2/
curses.bundle


Matthew M. [email protected]

regards,

hey, what about my solution? :stuck_out_tongue:
it also solves the test:

Sorry, don’t know how I missed that…

Yup, passed test, all found, non-recursively. Very good.

Hmmm… methinks I may have messed up a couple of my tests with other
folks. I initially got errors from yours, Rolando, until I realized
that I had bad line breaks after copying from google groups. Apologies
to any folks whose solutions I called broken that actually aren’t!

the two “complete” solutions (Merriman, Suchanek) could quite
possibly be broken on other platforms. I don’t know. (I’m a bit
occupied this week.:slight_smile: A 100% solid solution should account for all
these things.

Both Michal’s and mine work on my Linux box:

[email protected] $ uname -a
Linux ricercar 2.6.25-gentoo-r7 #2 SMP PREEMPT Thu Aug 14 18:28:35 EDT
2008 i686 Intel® Core™2 CPU 6600 @ 2.40GHz GenuineIntel GNU/Linux
[email protected] $ ruby -v
ruby 1.8.6 (2008-03-03 patchlevel 114) [i686-linux]

Finally, most of you should recognize the standard technique for
knowing when a script is run rather than loaded via require:

if __FILE__ == $0
  ARGV.each { |mod| require mod }
end

There is some danger though…

[email protected] $ ./modwhich.rb modwhich.rb
./modwhich.rb:8:in require_orig': stack level too deep (SystemStackError) from ./modwhich.rb:8:inrequire_orig’
from ./modwhich.rb:8:in require' from ./modwhich.rb:25 from ./modwhich.rb:25:ineach’
from ./modwhich.rb:25
from
/usr/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in
gem_original_require' from /usr/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:inrequire_orig’
from ./modwhich.rb:8:in require' from ./modwhich.rb:25 from ./modwhich.rb:25:ineach’
from ./modwhich.rb:25

This forum is not affiliated to the Ruby language, Ruby on Rails framework, nor any Ruby applications discussed here.

| Privacy Policy | Terms of Service | Remote Ruby Jobs