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

-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-

The three rules of Ruby Quiz 2:

1. Please do not post any solutions or spoiler discussion for this
quiz until 48 hours have passed from the time on this message.

2. Support Ruby Quiz 2 by submitting ideas as often as you can! (A
permanent, new website is in the works for Ruby Quiz 2. Until then,
please visit the temporary website at

    <http://splatbang.com/rubyquiz/&gt;\.

3. Enjoy!

Suggestion: A [QUIZ] in the subject of emails about the problem
helps everyone on Ruby Talk follow the discussion. Please reply to
the original quiz message, if you can.

-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-

## Where the Required Things Are (#175)

Occasionally, I've taken a look at the source for some Ruby module,
often because there is no manual or man page, or what documentation is
available is outdated or incomplete. Or sometimes I just want to see
how some Ruby stuff is implemented.

One such example was from the previous quiz: I want to learn more
about the Sys::Uptime module. I have it installed, and the call to
`require 'sys/uptime'` works, but I don't know how to use it. But,
alas, I also don't know where the installed files are located. The
shell command `which` doesn't help here, since the module is unlikely
to be in the shell's executable path.

What I would like is a script that works like `which` but for Ruby
modules. Examples:

ruby modwhich.rb "sys/uptime"

   require 'sys/uptime' =>
/opt/local/lib/ruby/vendor_ruby/1.8/i686-darwin8.11.1/sys/uptime.bundle

ruby modwhich.rb date

   require 'date' => /opt/local/lib/ruby/1.8/date.rb

For extra credit, preserve this behavior when modwhich.rb is the main
program, but slightly different behavior is modwhich.rb is required by
another script:

ruby -r modwhich upsince.rb

   require 'sys/uptime' =>
/opt/local/lib/ruby/vendor_ruby/1.8/i686-darwin8.11.1/sys/uptime.bundle
   require 'date' => /opt/local/lib/ruby/1.8/date.rb
   Last reboot: 2008 Aug 22 at 18:49

Note that we allow upsince.rb to run as normal; the output of
modwhich.rb is mixed into stdout.

Lots of fun with this quiz :smiley:
so far, I came up with something that does like this:

rolando@dev02:~$ ruby quiz175.rb "date"
   require 'date' => /usr/local/lib/ruby/1.8/date.rb
rolando@dev02:~$ ruby quiz175.rb "rubygems"
   require 'rubygems' => /usr/local/lib/ruby/site_ruby/1.8/rubygems.rb
rolando@dev02:~$ ruby quiz175.rb "rubygems" "sequel"
   require 'rubygems' => /usr/local/lib/ruby/site_ruby/1.8/rubygems.rb
   require 'sequel' => /usr/local/lib/ruby/gems/1.8/gems/sequel-2.4.0/lib/sequel.rb
rolando@dev02:~$ ruby -r quiz175 test_quiz175.rb
   require 'date' => /usr/local/lib/ruby/1.8/date.rb
   require 'rubygems' => /usr/local/lib/ruby/site_ruby/1.8/rubygems.rb
   require 'sequel' => /usr/local/lib/ruby/gems/1.8/gems/sequel-2.4.0/lib/sequel.rb
   require 'sequel_model' => /usr/local/lib/ruby/gems/1.8/gems/sequel-2.4.0/lib/sequel_model.rb
i'm cool piece of ruby code
rolando@dev02:~$ cat test_quiz175.rb
require 'date'
require 'rubygems'
#puts "requiring sequel"
require 'sequel'
require 'sequel_model'

puts "i'm cool piece of ruby code"
rolando@dev02:~$ REQUIRE_SHOW_ALL=1 ruby quiz175.rb "date"
   require 'date' => /usr/local/lib/ruby/1.8/date.rb
   require 'rational' => /usr/local/lib/ruby/1.8/rational.rb
   require 'date/format' => /usr/local/lib/ruby/1.8/date/format.rb
rolando@dev02:~$ REQUIRE_SHOW_ALL=1 ruby quiz175.rb "rubygems"
   require 'rubygems' => /usr/local/lib/ruby/site_ruby/1.8/rubygems.rb
   require 'rubygems/rubygems_version' => /usr/local/lib/ruby/site_ruby/1.8/rubygems/rubygems_version.rb
   require 'rubygems/defaults' => /usr/local/lib/ruby/site_ruby/1.8/rubygems/defaults.rb
   require 'thread' => /usr/local/lib/ruby/1.8/thread.rb
   require 'thread.so' => /usr/local/lib/ruby/1.8/i686-linux/thread.so
   require 'rbconfig' => /usr/local/lib/ruby/1.8/i686-linux/rbconfig.rb
   require 'rubygems/exceptions' => /usr/local/lib/ruby/site_ruby/1.8/rubygems/exceptions.rb
   require 'rubygems/version' => /usr/local/lib/ruby/site_ruby/1.8/rubygems/version.rb
   require 'rubygems/requirement' => /usr/local/lib/ruby/site_ruby/1.8/rubygems/requirement.rb
   require 'rubygems/dependency' => /usr/local/lib/ruby/site_ruby/1.8/rubygems/dependency.rb
   require 'rubygems/gem_path_searcher' => /usr/local/lib/ruby/site_ruby/1.8/rubygems/gem_path_searcher.rb
   require 'rubygems/source_index' => /usr/local/lib/ruby/site_ruby/1.8/rubygems/source_index.rb
   require 'rubygems/user_interaction' => /usr/local/lib/ruby/site_ruby/1.8/rubygems/user_interaction.rb
   require 'rubygems/specification' => /usr/local/lib/ruby/site_ruby/1.8/rubygems/specification.rb
   require 'rubygems/platform' => /usr/local/lib/ruby/site_ruby/1.8/rubygems/platform.rb
   require 'rubygems/spec_fetcher' => /usr/local/lib/ruby/site_ruby/1.8/rubygems/spec_fetcher.rb
   require 'zlib' => /usr/local/lib/ruby/1.8/i686-linux/zlib.so
   require 'rubygems/remote_fetcher' => /usr/local/lib/ruby/site_ruby/1.8/rubygems/remote_fetcher.rb
   require 'net/http' => /usr/local/lib/ruby/1.8/net/http.rb
   require 'net/protocol' => /usr/local/lib/ruby/1.8/net/protocol.rb
   require 'socket' => /usr/local/lib/ruby/1.8/i686-linux/socket.so
   require 'timeout' => /usr/local/lib/ruby/1.8/timeout.rb
   require 'uri' => /usr/local/lib/ruby/1.8/uri.rb
   require 'uri/common' => /usr/local/lib/ruby/1.8/uri/common.rb
   require 'uri/generic' => /usr/local/lib/ruby/1.8/uri/generic.rb
   require 'uri/ftp' => /usr/local/lib/ruby/1.8/uri/ftp.rb
   require 'uri/http' => /usr/local/lib/ruby/1.8/uri/http.rb
   require 'uri/https' => /usr/local/lib/ruby/1.8/uri/https.rb
   require 'uri/ldap' => /usr/local/lib/ruby/1.8/uri/ldap.rb
   require 'uri/mailto' => /usr/local/lib/ruby/1.8/uri/mailto.rb
   require 'stringio' => /usr/local/lib/ruby/1.8/i686-linux/stringio.so
   require 'rubygems/builder' => /usr/local/lib/ruby/site_ruby/1.8/rubygems/builder.rb
   require 'rubygems/custom_require' => /usr/local/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb

(Matthew: I sent you my answer to the quiz in private, since I'll be away for the next week)

--
Matthew Moss <matthew.moss@gmail.com>

thanks Matthew for the fun :smiley:
regards,

···

On 29-08-2008, at 10:02, Matthew Moss wrote:
--
Rolando Abarca M.

Matthew Moss wrote:

What I would like is a script that works like `which` but for Ruby
modules. Examples:

Here's my solution:

[68] erikh@islay ~% ruby quiz.rb date
/usr/lib/ruby/1.8/date.rb
[69] erikh@islay ~% ruby quiz.rb date digest/sha1
/usr/lib/ruby/1.8/date.rb
/usr/lib/ruby/1.8/x86_64-linux/digest/sha1.so
[70] erikh@islay ~% irb -r quiz.rb
irb(main):001:0> require 'digest/sha1'
/usr/lib/ruby/1.8/x86_64-linux/digest/sha1.so
=> true
irb(main):002:0> require 'date'
/usr/lib/ruby/1.8/date.rb
/usr/lib/ruby/1.8/rational.rb
/usr/lib/ruby/1.8/date/format.rb
/usr/lib/ruby/1.8/rational.rb
=> true

Code emailed to Matthew. Again, thanks for the fun. :slight_smile:

-Erik

···

--
Posted via http://www.ruby-forum.com/\.

My solution (pastied too: http://pastie.org/263518):

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

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

···

--
Jesse Merriman

Matthew Moss wrote:

-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-

The three rules of Ruby Quiz 2:

1. Please do not post any solutions or spoiler discussion for this
quiz until 48 hours have passed from the time on this message.

2. Support Ruby Quiz 2 by submitting ideas as often as you can! (A
permanent, new website is in the works for Ruby Quiz 2. Until then,
please visit the temporary website at

     <http://splatbang.com/rubyquiz/&gt;\.

3. Enjoy!

Suggestion: A [QUIZ] in the subject of emails about the problem
helps everyone on Ruby Talk follow the discussion. Please reply to
the original quiz message, if you can.

-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-

## Where the Required Things Are (#175)

Occasionally, I've taken a look at the source for some Ruby module,
often because there is no manual or man page, or what documentation is
available is outdated or incomplete. Or sometimes I just want to see
how some Ruby stuff is implemented.

One such example was from the previous quiz: I want to learn more
about the Sys::Uptime module. I have it installed, and the call to
`require 'sys/uptime'` works, but I don't know how to use it. But,
alas, I also don't know where the installed files are located. The
shell command `which` doesn't help here, since the module is unlikely
to be in the shell's executable path.

What I would like is a script that works like `which` but for Ruby
modules. Examples:

    > ruby modwhich.rb "sys/uptime"
    require 'sys/uptime' =>
/opt/local/lib/ruby/vendor_ruby/1.8/i686-darwin8.11.1/sys/uptime.bundle

    > ruby modwhich.rb date
    require 'date' => /opt/local/lib/ruby/1.8/date.rb

For extra credit, preserve this behavior when modwhich.rb is the main
program, but slightly different behavior is modwhich.rb is required by
another script:

    > ruby -r modwhich upsince.rb

    require 'sys/uptime' =>
/opt/local/lib/ruby/vendor_ruby/1.8/i686-darwin8.11.1/sys/uptime.bundle
    require 'date' => /opt/local/lib/ruby/1.8/date.rb
    Last reboot: 2008 Aug 22 at 18:49

Note that we allow upsince.rb to run as normal; the output of
modwhich.rb is mixed into stdout.

Problem: Something like require 'rubygems' sets off a while chains of require statements with circular requires. It ends up printing a lot of the entries more than once. Also, none of the modules are added to $LOADED_FEATURES until after the original require statement succeeds, that can't be used to check for duplicates. Keep track of which modules were printed with $MODWHICH_SEEN, but be sure to store the full path. Else if you require 'some-mod', then change $LOAD_PATH or require 'rubygems' or overload the require method again, 'some-mod' could mean something completely different. Also, if 'foo' and 'foo.rb' are both required, storing the complete path prevents multiples from being printed.

I think require tries other extensions other than .rb, it could be extended to support that. I didn't feel like mucking around in eval.c though :stuck_out_tongue:

module Kernel
   alias modwhich_original_require require
   $MODWHICH_SEEN =

   def require(path)
     p =
       path +
       (path =~ %r{\.[^/]+$} ? '' : '.rb')

     dir = $LOAD_PATH.find do|d|
       File.exists? "#{d}/#{p}"
     end

     return if $MODWHICH_SEEN.include? "#{dir}/#{p}"
     $MODWHICH_SEEN << "#{dir}/#{p}"

     puts "require: #{path} => #{dir}/#{p}" unless dir.nil?
     modwhich_original_require path
   end
end

···

--
Michael Morin
Guide to Ruby

Become an About.com Guide: beaguide.about.com
About.com is part of the New York Times Company

hi!

here's my solution (first submission ever ;-).

first the cheating one (no extra credit):

  ARGV.each { |mod|
    puts "require '#{mod}' => #{%x{gem which #{mod}}.split(/\n/).last}"
  }

now on to the real one. it uses ruby's "magic constant" SCRIPT_LINES__ and (as
fallback) rubygem's 'which' command (which does not only find gems, actually;
see above).

contrary to the quiz "rules", information is output to STDERR instead of STDOUT.
the script takes a list of library names as arguments and a few options.

examples:

  ww@blackwinter:~/devel/scratch> ./modwhich.rb set date nuggets
  require 'set' => /usr/lib/ruby/1.8/set.rb
  require 'date' => /usr/lib/ruby/1.8/date.rb
  require 'nuggets' =>
/usr/lib/ruby/gems/1.8/gems/ruby-nuggets-0.3.1.277/lib/nuggets.rb

  ww@blackwinter:~/devel/scratch> ./modwhich.rb -r set date nuggets
  require 'set' => /usr/lib/ruby/1.8/set.rb
  require 'date' => /usr/lib/ruby/1.8/date.rb
  require 'rational' => /usr/lib/ruby/1.8/rational.rb
  require 'date/format' => /usr/lib/ruby/1.8/date/format.rb
  require 'nuggets' =>
/usr/lib/ruby/gems/1.8/gems/ruby-nuggets-0.3.1.277/lib/nuggets.rb
  require 'fileutils' => /usr/lib/ruby/1.8/fileutils.rb
  require 'etc' => /usr/lib/ruby/1.8/i486-linux/etc.so

  ww@blackwinter:~/devel/scratch> ./modwhich.rb -v set date nuggets
  /usr/lib/ruby/1.8/set.rb
  /usr/lib/ruby/1.8/rational.rb
  /usr/lib/ruby/1.8/date/format.rb
  /usr/lib/ruby/1.8/date.rb
  /usr/lib/ruby/1.8/i486-linux/etc.so
  /usr/lib/ruby/1.8/fileutils.rb
  /usr/lib/ruby/gems/1.8/gems/ruby-nuggets-0.3.1.277/lib/nuggets.rb

  ww@blackwinter:~/devel/scratch> ruby -rmodwhich test_modwhich.rb
  #<Set: {}>
  require 'set' => /usr/lib/ruby/1.8/set.rb
  require 'date' => /usr/lib/ruby/1.8/date.rb
  require 'rational' => /usr/lib/ruby/1.8/rational.rb
  require 'date/format' => /usr/lib/ruby/1.8/date/format.rb

  ww@blackwinter:~/devel/scratch> cat test_modwhich.rb
  require 'set'
  require 'date'
  p Set.new

  ww@blackwinter:~/devel/scratch> irb -r modwhich.rb
    require 'set'
  0.0096 => true
    require 'date', true
  /usr/lib/ruby/1.8/date.rb
  0.0084 => false
    require 'hen', true
  /usr/lib/ruby/gems/1.8/gems/hen-0.1.2.273/lib/hen.rb
  0.1735 => true
    ModWhich.to_h
  0.0003 => ...snip... (see for yourself :wink:
    x
  require 'set' => /usr/lib/ruby/1.8/set.rb
  require 'date' => /usr/lib/ruby/1.8/date.rb
  require 'hen' => /usr/lib/ruby/gems/1.8/gems/hen-0.1.2.273/lib/hen.rb
  require 'yaml' => /usr/lib/ruby/1.8/yaml.rb
  require 'forwardable' => /usr/lib/ruby/1.8/forwardable.rb
  require 'rubygems' => /usr/local/lib/site_ruby/1.8/rubygems.rb
  require 'rake' => /usr/lib/ruby/1.8/rake.rb
  require 'rbconfig' => /usr/lib/ruby/1.8/i486-linux/rbconfig.rb
  require 'ftools' => /usr/lib/ruby/1.8/ftools.rb
  require 'getoptlong' => /usr/lib/ruby/1.8/getoptlong.rb
  require 'fileutils' => /usr/lib/ruby/1.8/fileutils.rb
  require 'singleton' => /usr/lib/ruby/1.8/singleton.rb
  require 'thread' => /usr/lib/ruby/1.8/thread.rb
  require 'ostruct' => /usr/lib/ruby/1.8/ostruct.rb
  [...]

you can also find it on github:
<http://github.com/blackwinter/scratch/tree/master/modwhich.rb>.

cheers
jens

----[ modwhich.rb ]----

#! /usr/bin/env ruby

···

#--
###############################################################################
# #
# modwhich -- Find the location of a library. Solution for Ruby Quiz #
# "Where the Required Things Are" (#175) by Matthew Moss, 2008/08/29. #
# #
# Copyright (C) 2008 Jens Wille <jens.wille@uni-koeln.de> #
# #
# modwhich is free software; you can redistribute it and/or modify it under #
# the terms of the GNU General Public License as published by the Free #
# Software Foundation; either version 3 of the License, or (at your option) #
# any later version. #
# #
# modwhich is distributed in the hope that it will be useful, but WITHOUT #
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
# more details. #
# #
# You should have received a copy of the GNU General Public License along #
# with modwhich. If not, see <http://www.gnu.org/licenses/>. #
# #
###############################################################################
#++

begin
  require 'rubygems'
  require 'rubygems/commands/which_command'
rescue LoadError
end

class ModWhich

  @verbose, @recursive = false, false

  class << self

    include Enumerable

    attr_writer :verbose, :recursive

    def init(args = nil, recursive = recursive? || args.nil?)
      @args, @recursive = args, recursive

      @which, @load_order, @added_paths = {}, [], []

      unless Object.const_defined?(:SCRIPT_LINES__)
        Object.const_set(:SCRIPT_LINES__, {})
      end

      unless Object.ancestors.include?(Require)
        Object.send(:include, Require)
      end
    end

    def which(mod)
      self.require(mod)
      @which[mod] if @which
    end

    def require(mod, verbose = verbose?)
      if @which && !required?(mod)
        @load_order << mod
        current_paths = loaded_paths

        ret = _modwhich_original_require(mod)

        @added_paths.concat(loaded_paths - current_paths)
        @which[mod] = @added_paths.pop || gemwhich(mod)

        warn @which[mod] if verbose

        ret
      end
    end

    def required?(mod)
      @which && @which.has_key?(mod)
    end

    def verbose?
      @verbose
    end

    def recursive?
      @recursive
    end

    def include?(mod)
      !to_a.assoc(mod).nil?
    end

    def each
      if @load_order
        if @args
          @args.each { |mod| self.require(mod) }
          @load_order &= @args unless recursive?
          @args = nil
        end

        @load_order.each { |mod| yield mod, which(mod) }
      end
    end

    def to_h
      inject({}) { |h, (mod, path)| h.update(mod => path) }
    end

    private

    # basically equivalent to: <tt>%x{gem which #{mod}}.split(/\n/).last</tt>
    def gemwhich(mod)
      if defined?(Gem::Commands::WhichCommand)
        @gemwhich ||= Gem::Commands::WhichCommand.new
        @searcher ||= Gem::GemPathSearcher.new

        dirs = $LOAD_PATH

        if spec = @searcher.find(mod)
          dirs += @gemwhich.gem_paths(spec)
        end

        # return the last (only?) one
        @gemwhich.find_paths(mod, dirs).last
      end
    end

    def loaded_paths
      SCRIPT_LINES__.keys - (@which ? @which.values : [])
    end

  end

  module Require
    unless respond_to?(:_modwhich_original_require)
      alias_method :_modwhich_original_require, :require
    end

    def require(*args) ModWhich.require(*args) end
  end

end

if $0 == __FILE__
  progname = File.basename($0)

  usage = <<-EOT.gsub(/^\s+/, '')
    #{progname} [-v|--verbose] [-r|--recursive] <mod> ...
    #{progname} [-h|--help]
  EOT

  help = ARGV.delete('-h') || ARGV.delete('--help')
  verbose = ARGV.delete('-v') || ARGV.delete('--verbose')
  recursive = ARGV.delete('-r') || ARGV.delete('--recursive')

  abort usage if help || ARGV.empty?

  ModWhich.verbose = verbose
  ModWhich.init(ARGV, recursive)

  ARGV.each { |mod| require mod }
else
  ModWhich.init
end

at_exit {
  ModWhich.each { |mod, path|
    warn "require '#{mod}' => #{path || 'NOT FOUND'}"
  } unless ModWhich.verbose?
}

-----------------------

--
Jens Wille, Dipl.-Bibl. (FH)
prometheus - Das verteilte digitale Bildarchiv für Forschung & Lehre
Kunsthistorisches Institut der Universität zu Köln
Albertus-Magnus-Platz, D-50923 Köln
Tel.: +49 (0)221 470-6668, E-Mail: jens.wille@uni-koeln.de
http://www.prometheus-bildarchiv.de/

This is what came to mind. Interesting idea. I may just keep this around.

% ruby which.rb rubygems rake fake/thing date/format
"rubygems" found in /Library/Ruby/Site/1.8/rubygems.rb
"rake" found in /Library/Ruby/Site/1.8/rake.rb
Could not find "fake/thing" anywhere
"date/format" found in
/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/1.8/date/format.rb

% cat which.rb
alias untracked_require require
$req = Hash.new
def require(path)
  old_features = $LOADED_FEATURES.dup
  untracked_require(path)
  $req[path] ||= ($LOADED_FEATURES - old_features).last
end

ARGV.each do |path|
  begin
    require(path)
  rescue LoadError
    $stderr.puts "Could not find #{path.inspect} anywhere"
  else
    found = $LOAD_PATH.find { |dir| File.exist? "#{dir}/#{$req[path]}" }
    $stderr.puts "#{path.inspect} found in #{found}/#{$req[path]}"
  end
end

···

On 8/29/08, Matthew Moss <matthew.moss@gmail.com> wrote:

-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-

The three rules of Ruby Quiz 2:

1. Please do not post any solutions or spoiler discussion for this
quiz until 48 hours have passed from the time on this message.

What I would like is a script that works like `which` but for Ruby
modules. Examples:

    > ruby modwhich.rb "sys/uptime"
    require 'sys/uptime' =>
/opt/local/lib/ruby/vendor_ruby/1.8/i686-darwin8.11.1/sys/uptime.bundle

    > ruby modwhich.rb date
    require 'date' => /opt/local/lib/ruby/1.8/date.rb

Mine:

def locate(file)
   extensions = [".rb",".so",".o",".dll"]
   $:.each do |dir|
     path = dir + "/" + file
     if File.exist? path
       puts "require '#{file}' => #{path}"
       break
     else
       extensions.each do |ext|
         if File.exist? path + ext
           puts "require '#{file}' => #{path + ext}"
           break
         end
       end
     end
   end
end

module Kernel
   alias old_require require
   def require(string)
     locate string
     old_require string
   end
end

ARGV.each do |file|
   locate file if file
end

···

--
Fred O. Phillips
http://fophillips.org
BBC7 7572 755F 83E0 3209 504A E4F7 874F 1545 9D41

Hello, here's my solution for this quiz.

I have never put together a piece of metaprogramming that I would use
with confidence until this quiz :wink:

What I would like is a script that works like `which` but for Ruby
modules. Examples:

    > ruby modwhich.rb "sys/uptime"
    require 'sys/uptime' =>
/opt/local/lib/ruby/vendor_ruby/1.8/i686-darwin8.11.1/sys/uptime.bundle

This should work although the solution is not tested on a Mac.

    > ruby modwhich.rb date
    require 'date' => /opt/local/lib/ruby/1.8/date.rb

This works.

For extra credit, preserve this behavior when modwhich.rb is the main
program, but slightly different behavior is modwhich.rb is required by
another script:

    > ruby -r modwhich upsince.rb

    require 'sys/uptime' =>
/opt/local/lib/ruby/vendor_ruby/1.8/i686-darwin8.11.1/sys/uptime.bundle
    require 'date' => /opt/local/lib/ruby/1.8/date.rb
    Last reboot: 2008 Aug 22 at 18:49

This should also work within reason - modules loaded from files
located in rubylibdir are not printed.

Thanks

Michal

q175.rb (1.83 KB)

···

On 29/08/2008, Matthew Moss <matthew.moss@gmail.com> wrote:

This is what my solution does:

···

---
~$ ruby modwhich.rb date set bigdecimal
require "date" => /usr/lib/ruby/1.8/date.rb
require "set" => /usr/lib/ruby/1.8/set.rb
require "bigdecimal" => /usr/lib/ruby/1.8/i486-linux/bigdecimal.so
~$ ruby modwhich.rb /usr/lib/ruby/1.8/date
require "/usr/lib/ruby/1.8/date" => /usr/lib/ruby/1.8/date.rb
~$ cat modwhich_test.rb
require 'date'
require 'set'
require 'bigdecimal'
~$ ruby -rmodwhich modwhich_test.rb
require "date" => /usr/lib/ruby/1.8/date.rb
require "set" => /usr/lib/ruby/1.8/set.rb
require "bigdecimal" => /usr/lib/ruby/1.8/i486-linux/bigdecimal.so
~$ MODWHICH_VERBOSE=1 ruby -rmodwhich modwhich_test.rb
(modwhich_test.rb:1) require "date" => /usr/lib/ruby/1.8/date.rb
(/usr/lib/ruby/1.8/date.rb:196) require "rational" =>
/usr/lib/ruby/1.8/rational.rb
(/usr/lib/ruby/1.8/date.rb:197) require "date/format" =>
/usr/lib/ruby/1.8/date/format.rb
(/usr/lib/ruby/1.8/date/format.rb:4) require "rational" =>
/usr/lib/ruby/1.8/rational.rb
(modwhich_test.rb:2) require "set" => /usr/lib/ruby/1.8/set.rb
(modwhich_test.rb:3) require "bigdecimal" =>
/usr/lib/ruby/1.8/i486-linux/bigdecimal.so
---

In code it looks like this:

---
require 'pathname'

module ModWhich
  SUFFIXES = [''] + %w{ .rb .rbw .o .so .bundle .dll .sl .jar }

  def self.which(mod)
    if Pathname.new(mod).absolute?
      paths = File.dirname(mod).to_a
      mod = File.basename(mod)
    else
      paths = $LOAD_PATH
    end

    file_names = SUFFIXES.map { |suff| mod + suff }

    paths.each do |path|
      file_names.each do |file|
        full_path = File.expand_path(File.join(path, file))
        return full_path if File.file?(full_path)
      end
    end

    nil
  end

  def self.report(mod)
    puts "require %s => %s" % [mod.inspect, which(mod) || "(no such
file)"]
  end

  def self.verbose?
    ENV['MODWHICH_VERBOSE']
  end

  def self.trap_require
    Kernel.module_eval do
      alias :modwhich_original_require :require

      def require(arg)
        if ModWhich::verbose? || caller.first.split(':')[0] == $0
          if ModWhich::verbose?
            print "(#{caller.first}) "
          end
          ModWhich::report(arg)
        end
        modwhich_original_require(arg)
      end
    end
  end
end

if __FILE__ == $0
  while mod = ARGV.shift
    ModWhich::report(mod)
  end
else
  ModWhich::trap_require
end
---

As a pastie: http://pastie.org/264164

I think I'm gonna keep that around, looks useful to me.

Regards,
Matthias
--
Posted via http://www.ruby-forum.com/.

Here's my solution. I used the mod_req_level counter to restrict the
output to the files required at the top level. Without it, requiring
rubygems prints the rubygems location about 9 times.
-Adam

alias :original_require :require

$mod_req_level=0
$mod_req_exts = %w{rb rbw o so dll bundle sl jar}.map{|ex| Regexp.new
"\.#{ex}$"}

def require lib
    $:.each{|d| files=Dir.glob(d+"/#{lib}*")
      f=files.find{|f|$mod_req_exts.find{|re|re=~f}}
      (puts "require #{lib} => #{f}";break) if f
    } if 1==($mod_req_level+=1)
  begin
    original_require lib
  ensure
    $mod_req_level-=1
  end
end

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

···

On 8/29/08, Matthew Moss <matthew.moss@gmail.com> wrote:

-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-

The three rules of Ruby Quiz 2:

What I would like is a script that works like `which` but for Ruby
modules. Examples:

I've had a go at a solution, but I don't think what I did works properly.
Feel free to comment on what's wrong with it.

RUBYGEMS = 'rubygems.rb'

# Create a copy of the load path ($:)
@load_path = Array.new($:)

# Replace '.' in @load_path with full directory path
@load_path[@load_path.index('.')] = Dir.pwd

# Enforcing the require of rubygems before aliasing Kernel#require makes
things
# less troublesome.
begin
    # If rubygems has been loaded, append the its path(s)
    @load_path.concat(Gem.path) if require RUBYGEMS
rescue LoadError
    # A LoadError will occur if rubygems isn't installed, but it should be
safe
    # to ignore.
end

module Kernel
    alias :old_require :require

    # Make our own require method that uses '@load_path' variable to find
the path
    # of the file given by 'string'
    def require(string)
        old_require string

        # If 'string' doesn't have an extension
        if File.extname(string).empty?
            exts = ['.rb', '.so', '.o', '.dll']
            until exts.empty?
                fname = string + exts.shift
                path = @load_path.find { |i| File.exists? File.join(i,
fname) }
                return File.join(path, fname) unless path.nil?
            end
        else
            return @load_path.find { |i| File.exists? File.join(i, string) }
        end
        raise "If you can read this, Kernel#require was able to find a
library " +
                "named #{string} but #{__FILE__} was not able to. Either
there is" +
                "bug in #{__FILE__} and/or Kernel#require's search method is
" +
                "different to #{__FILE__}'s"
    end
end

if $0 == __FILE__
    ARGV.each { |a| puts require(a) }
end

···

2008/8/29 Matthew Moss <matthew.moss@gmail.com>

-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-

The three rules of Ruby Quiz 2:

1. Please do not post any solutions or spoiler discussion for this
quiz until 48 hours have passed from the time on this message.

2. Support Ruby Quiz 2 by submitting ideas as often as you can! (A
permanent, new website is in the works for Ruby Quiz 2. Until then,
please visit the temporary website at

    <http://splatbang.com/rubyquiz/&gt;\.

3. Enjoy!

Suggestion: A [QUIZ] in the subject of emails about the problem
helps everyone on Ruby Talk follow the discussion. Please reply to
the original quiz message, if you can.

-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-

## Where the Required Things Are (#175)

Occasionally, I've taken a look at the source for some Ruby module,
often because there is no manual or man page, or what documentation is
available is outdated or incomplete. Or sometimes I just want to see
how some Ruby stuff is implemented.

One such example was from the previous quiz: I want to learn more
about the Sys::Uptime module. I have it installed, and the call to
`require 'sys/uptime'` works, but I don't know how to use it. But,
alas, I also don't know where the installed files are located. The
shell command `which` doesn't help here, since the module is unlikely
to be in the shell's executable path.

What I would like is a script that works like `which` but for Ruby
modules. Examples:

   > ruby modwhich.rb "sys/uptime"
   require 'sys/uptime' =>
/opt/local/lib/ruby/vendor_ruby/1.8/i686-darwin8.11.1/sys/uptime.bundle

   > ruby modwhich.rb date
   require 'date' => /opt/local/lib/ruby/1.8/date.rb

For extra credit, preserve this behavior when modwhich.rb is the main
program, but slightly different behavior is modwhich.rb is required by
another script:

   > ruby -r modwhich upsince.rb

   require 'sys/uptime' =>
/opt/local/lib/ruby/vendor_ruby/1.8/i686-darwin8.11.1/sys/uptime.bundle
   require 'date' => /opt/local/lib/ruby/1.8/date.rb
   Last reboot: 2008 Aug 22 at 18:49

Note that we allow upsince.rb to run as normal; the output of
modwhich.rb is mixed into stdout.

--
Matthew Moss <matthew.moss@gmail.com>

Michael Morin wrote:

module Kernel
  alias modwhich_original_require require
  $MODWHICH_SEEN =

  def require(path)
    p =
      path +
      (path =~ %r{\.[^/]+$} ? '' : '.rb')

    dir = $LOAD_PATH.find do|d|
      File.exists? "#{d}/#{p}"
    end

    return if $MODWHICH_SEEN.include? "#{dir}/#{p}"
    $MODWHICH_SEEN << "#{dir}/#{p}"

    puts "require: #{path} => #{dir}/#{p}" unless dir.nil?
    modwhich_original_require path
  end
end

Small bug fix to account of files that aren't found.

module Kernel
   alias modwhich_original_require require
   $MODWHICH_SEEN =

   def require(path)
     p =
       path +
       (path =~ %r{\.[^/]+$} ? '' : '.rb')

     dir = $LOAD_PATH.find do|d|
       File.exists? "#{d}/#{p}"
     end

     # Let the original require raise an exception if
     # the file is not found.
     return modwhich_original_require(path) if dir.nil?

     return if $MODWHICH_SEEN.include? "#{dir}/#{p}"
     $MODWHICH_SEEN << "#{dir}/#{p}"

     puts "require: #{path} => #{dir}/#{p}" unless dir.nil?
     modwhich_original_require path
   end
end

···

--
Michael Morin
Guide to Ruby

Become an About.com Guide: beaguide.about.com
About.com is part of the New York Times Company

Erik Hollensbe wrote:

Matthew Moss wrote:

What I would like is a script that works like `which` but for Ruby
modules. Examples:

Here's my solution:

[68] erikh@islay ~% ruby quiz.rb date
/usr/lib/ruby/1.8/date.rb
[69] erikh@islay ~% ruby quiz.rb date digest/sha1
/usr/lib/ruby/1.8/date.rb
/usr/lib/ruby/1.8/x86_64-linux/digest/sha1.so
[70] erikh@islay ~% irb -r quiz.rb
irb(main):001:0> require 'digest/sha1'
/usr/lib/ruby/1.8/x86_64-linux/digest/sha1.so
=> true
irb(main):002:0> require 'date'
/usr/lib/ruby/1.8/date.rb
/usr/lib/ruby/1.8/rational.rb
/usr/lib/ruby/1.8/date/format.rb
/usr/lib/ruby/1.8/rational.rb
=> true

Here's the code:

#!/usr/bin/env ruby

module Kernel
    def require_which(filespec)
        $:.each do |path|
            spec_parts = filespec.split(/\//)

            files = Dir[File.join(path, *spec_parts) + ".*"]
            unless files.empty?
                return files
            end
        end

        
    end
end

if __FILE__ == $0
    ARGV.each do |arg|
        require_which(arg).each { |f| puts f }
    end
else
    module Kernel
        @@old_require = Kernel.method(:require)
        def require(*args)
            require_which(args[0]).each { |f| puts f }
            @@old_require.call(*args)
        end
    end
end

Benefits:

1) Should properly work on windows (or anything with a weird pathspec)
in any situation
2) catches .so/.bundle/.whatever
3) terminates early to avoid catching things that could be loaded if the
load path were different, but aren't actually (which is actually against
how 'which' generally works, but meh)

Drawbacks:
Suffers from the same require-itis mentioned earlier: change def require
to:

def require(*args)
  res = @@old_require.call(*args)
  if res
    require_which(args[0]).each { |f| puts f }
  end
  return res
end

Which should solve that problem.

···

--
Posted via http://www.ruby-forum.com/\.

Here is Rolando's solution:

module Kernel
  alias kernel_original_require require

  FILE_EXTENSIONS = [".so", ".bundle", ".dll"]

  def check_require2(lib, path)
    $_req_disp_table ||= {}
    if File.file?(path)
      if $_req_disp_table[lib].nil?
        puts " require '#{lib}' => #{path}"
        $_req_disp_table[lib] = 1
      end
      return true
    else
      return false
    end
  end

  def check_require(lib, path)
    if (ext = File.extname(path)).empty? || ext == ".rb"
      # puts ">>> #{path} does not have an extension"
      ext = ".rb"
      [".rb"].concat FILE_EXTENSIONS
    else
      # puts ">>> #{path} has extension"
      FILE_EXTENSIONS
    end.each { |_ext|
      res = check_require2(lib, File.join(File.dirname(path),
File.basename(path, ext) + _ext))
      return res if res
    }
    return false
  end

  def require(lib)
    # puts ">>> requiring #{lib} (#{$_req_top})
(#{$_req_table.inspect})"
    $_req_top ||= lib
    if (ENV['REQUIRE_SHOW_ALL'].nil? && lib == $_req_top) ||
ENV['REQUIRE_SHOW_ALL']
      got_it = false
      $:.each { |d|
        fpath = File.join(d, lib)
        got_it = check_require(lib, fpath)
        break if got_it
      }
    end
    if (res = kernel_original_require(lib)) && got_it
      $_req_top = nil
    end
    res
  end
end

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

just fixed verbosity setting when used in conjunction with recursion:

<http://github.com/blackwinter/scratch/commit/60eb300>

cheers
jens

I think I'm gonna keep that around, looks useful to me.

You're not the first person to say that.

It would probably be useful, all around, to squish a couple of these
solutions together into a nice feature set and make a gem or rubyforge
pkg out of it.

Yes, rubygems are quite common so the solution should probably should
include support for them, at least if I wanted to really use it in the
future.

Here is one that only shows dependencies between gems (and stdlib),
not the requires inside gems.

It generates somewhat excessive output when gems are involved as their
paths tend to be long.

Thanks

Michal

$ cat q175t.rb

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

$ ruby -r q175.rb q175t.rb
require: q175t.rb:1: date => /usr/lib/ruby/1.8/date.rb
require: q175t.rb:2: readline => /usr/lib/ruby/1.8/x86_64-linux/readline.so
require: q175t.rb:3: matrix => /usr/lib/ruby/1.8/matrix.rb
require: q175t.rb:4: rubygems => /usr/lib/ruby/1.8/rubygems.rb
require: q175t.rb:5: time => /usr/lib/ruby/1.8/time.rb
require: /var/lib/gems/1.8/gems/hpricot-0.6.161/lib/hpricot/traverse.rb:2:
uri => /usr/lib/ruby/1.8/uri.rb
require: /var/lib/gems/1.8/gems/hpricot-0.6.161/lib/hpricot/inspect.rb:1:
pp => /usr/lib/ruby/1.8/pp.rb
require: /var/lib/gems/1.8/gems/hpricot-0.6.161/lib/hpricot/builder.rb:2:
fast_xs => /var/lib/gems/1.8/gems/hpricot-0.6.161/lib/universal-darwin9.0/fast_xs.so
require: q175t.rb:6: hpricot =>
/var/lib/gems/1.8/gems/hpricot-0.6.161/lib/hpricot.rb
require: /var/lib/gems/1.8/gems/rubyforge-1.0.0/lib/rubyforge.rb:5:
yaml => /usr/lib/ruby/1.8/yaml.rb
require: /var/lib/gems/1.8/gems/rubyforge-1.0.0/lib/rubyforge.rb:6:
open-uri => /usr/lib/ruby/1.8/open-uri.rb
require: /var/lib/gems/1.8/gems/rubyforge-1.0.0/lib/rubyforge/client.rb:1:
webrick/cookie => /usr/lib/ruby/1.8/webrick/cookie.rb
require: /var/lib/gems/1.8/gems/rubyforge-1.0.0/lib/rubyforge/client.rb:2:
net/http => /usr/lib/ruby/1.8/net/http.rb
require: /var/lib/gems/1.8/gems/rubyforge-1.0.0/lib/rubyforge/client.rb:3:
net/https => /usr/lib/ruby/1.8/net/https.rb
require: q175t.rb:7: rubyforge =>
/var/lib/gems/1.8/gems/rubyforge-1.0.0/lib/rubyforge.rb
require: /var/lib/gems/1.8/gems/rake-0.8.1/lib/rake.rb:35: getoptlong
=> /usr/lib/ruby/1.8/getoptlong.rb
require: /var/lib/gems/1.8/gems/rake-0.8.1/lib/rake.rb:37: singleton
=> /usr/lib/ruby/1.8/singleton.rb
require: /var/lib/gems/1.8/gems/rake-0.8.1/lib/rake.rb:39: ostruct =>
/usr/lib/ruby/1.8/ostruct.rb
require: /var/lib/gems/1.8/gems/hoe-1.7.0/lib/hoe.rb:4: rake =>
/var/lib/gems/1.8/gems/rake-0.8.1/lib/rake.rb
require: /var/lib/gems/1.8/gems/hoe-1.7.0/lib/hoe.rb:5:
rake/gempackagetask =>
/var/lib/gems/1.8/gems/rake-0.8.1/lib/rake/gempackagetask.rb
require: /var/lib/gems/1.8/gems/hoe-1.7.0/lib/hoe.rb:6: rake/rdoctask
=> /var/lib/gems/1.8/gems/rake-0.8.1/lib/rake/rdoctask.rb
require: /var/lib/gems/1.8/gems/hoe-1.7.0/lib/hoe.rb:7: rake/testtask
=> /var/lib/gems/1.8/gems/rake-0.8.1/lib/rake/testtask.rb
require: q175t.rb:8: hoe => /var/lib/gems/1.8/gems/hoe-1.7.0/lib/hoe.rb
require: q175t.rb:9: curses => /usr/lib/ruby/1.8/x86_64-linux/curses.so

q175.rb (2.45 KB)

Erik Hollensbe wrote:

Drawbacks:

It also doesn't catch absolute paths, which I completely forgot about.
:slight_smile:

-Erik

···

--
Posted via http://www.ruby-forum.com/\.

Matthew Moss wrote:

I think I'm gonna keep that around, looks useful to me.

You're not the first person to say that.

It would probably be useful, all around, to squish a couple of these
solutions together into a nice feature set and make a gem or rubyforge
pkg out of it.

A require wrapper would be pretty slick:

module Kernel
  def wrap_require(method=nil)
     if block_given?
         @@require_wrapper = block.to_proc
     else
         @@require_wrapper = method
     end
  end

  @@old_require = method(:require)

  def require(*args)
      res = @@old_require.call(*args)
      @@require_wrapper.call(*args) if (@@require_wrapper and res)
      return res
  end
end

Dunno if it really warrants a gem though. Maybe a snippet?

-Erik

···

--
Posted via http://www.ruby-forum.com/\.