[SUMMARY] Where the Required Things Are (#175)

M

Matthew Moss

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 Merriman_ and
_Michal Suchanek_ (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 Suchanek'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.:) A 100% solid solution should account for all
these things.
 
J

Jens Wille

hi matthew!

thanks for the fun quiz :)

Matthew Moss [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:master@60eb300]> 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
 
R

Rolando Abarca

That leaves us with two "winners" for this week: _Jesse Merriman_ and
_Michal Suchanek_ (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? :p
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


regards,
 
M

Matthew Moss

hey, what about my solution? :p
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!
 
J

Jesse Merriman

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.:) A 100% solid solution should account for all
these things.

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

jesse@ricercar $ uname -a
Linux ricercar 2.6.25-gentoo-r7 #2 SMP PREEMPT Thu Aug 14 18:28:35 EDT 2008=
i686 Intel(R) Core(TM)2 CPU 6600 @ 2.40GHz GenuineIntel GNU/Linux
jesse@ricercar $ 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`:
=20
=A0 =A0 if __FILE__ =3D=3D $0
=A0 =A0 =A0 ARGV.each { |mod| require mod }
=A0 =A0 end

There is some danger though...

jesse@ricercar $ ./modwhich.rb modwhich.rb
=2E/modwhich.rb:8:in `require_orig': stack level too deep (SystemStackError)
from ./modwhich.rb:8:in `require_orig'
from ./modwhich.rb:8:in `require'
from ./modwhich.rb:25
from ./modwhich.rb:25:in `each'
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:in `=
require_orig'
from ./modwhich.rb:8:in `require'
from ./modwhich.rb:25
from ./modwhich.rb:25:in `each'
from ./modwhich.rb:25

=2D-
Jesse Merriman
 

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,769
Messages
2,569,577
Members
45,052
Latest member
LucyCarper

Latest Threads

Top