Relative #require

I want relative require paths, in addition to 'absolute':

app/run.rb
app/code/app.rb
app/code/k1.rb
app/code/k2.rb
app/code/k3/k3.rb
app/code/k3/k4.rb
app/code/lib/foo.rb

% cat run.rb
require 'code/app'
$app = App.instance
$app.run

% cat code/app.rb
require 'k1'; require 'k2'; require 'k3/k3' #RELATIVE

% cat code/k1.rb
require 'lib/foo.rb' #RELATIVE

% cat code/k3/k3.rb
require 'k4' #RELATIVE

% cat code/k3/k4.rb
require 'code/lib/foo.rb' #~ABSOLUTE

I see that the (legacy) RCR 170[1] and [ruby-dev:22788] [2] (although I can't read Japanese) have discussed this issue. Is there a better way to handle this than working the hack mentioned in RCR170:

dir = Pathname.new(File.expand_path(__FILE__)).realpath
require File.join(dir, 'utils' ) # UGLY

into a new version of Kernel#require, which prepends the path if an optional second parameter is true or something? Something like (untested):

class Kernel
   alias_method :gk_old_require, :require
     def require( path, local=nil )
       dir = local != :local ? '' : Pathname.new(File.expand_path(__FILE__)).realpath
       gk_old_require File.join( dir, path )
     end
end

The key part (for me) is that files inside of code/k3/ be able to refer to each other without having to know where they might be stored or have been included from.

[1] http://rcrchive.net/rcr/RCR/RCR170
[2] http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-dev/22788

···

--
(-, /\ \/ / /\/

Not sure if this is what you want, but a long time ago someone gave me this.

  module Kernel
    # Require files from same dir as running script.
    def import(*args)
      fd = File.dirname(caller[0])
      args.each do |file_name|
        require File.join(fd, file_name)
      end
    end
  end

Perhaps your hack is better?

Nonetheless, the point being that it is preferable to have this as a separate
method rather then integrated into #require. I recall that my experiments
(also long ago) bore this out.

T.

···

On Monday 18 October 2004 09:51 am, Gavin Kistner wrote:

I see that the (legacy) RCR 170[1] and [ruby-dev:22788] [2] (although I
can't read Japanese) have discussed this issue. Is there a better way
to handle this than working the hack mentioned in RCR170:

dir = Pathname.new(File.expand_path(__FILE__)).realpath
require File.join(dir, 'utils' ) # UGLY

into a new version of Kernel#require, which prepends the path if an
optional second parameter is true or something? Something like
(untested):

class Kernel
   alias_method :gk_old_require, :require
     def require( path, local=nil )
       dir = local != :local ? '' :
Pathname.new(File.expand_path(__FILE__)).realpath
       gk_old_require File.join( dir, path )
     end
end

The key part (for me) is that files inside of code/k3/ be able to refer
to each other without having to know where they might be stored or have
been included from.

I sympathise greatly with this problem. Essentially it's that

  require 'foo/bar'

is tested against each of the directories in $:, the last of which is
usually "." - but actually what you want is it relative to the directory
which the file is in, not the directory which the user happened to be in
when they ran your application!

A simple solution might be something like this:

  $:.push File.dirname(File.expand_path(__FILE__))

Or perhaps replace the '.' in $: with an object which dynamically returns
this:

  class Mydir
    def self.+(rest)
      self.to_str + rest
    end
    def self.to_str
      File.dirname(File.expand_path(__FILE__))
    end
  end

  $:.each_index {|i| $:[i] = Mydir if $:[i] == '.'}

Proviso: that seems to work, but is not heavily tested!

However I cannot see any real use for require '../foo/bar' meaning 'relative
to the directory in which the user happened to be when they ran your
program', rather than 'relative to the directory in which the script which
contains this require statement is located'. So perhaps this ought to be
the default for non-absolute paths.

Regards,

Brian.

Hi,

At Mon, 18 Oct 2004 22:51:06 +0900,
Gavin Kistner wrote in [ruby-talk:116961]:

I want relative require paths, in addition to 'absolute':

app/run.rb
app/code/app.rb
app/code/k1.rb
app/code/k2.rb
app/code/k3/k3.rb
app/code/k3/k4.rb
app/code/lib/foo.rb

% cat run.rb
require 'code/app'
$app = App.instance
$app.run

% cat code/app.rb
require 'k1'; require 'k2'; require 'k3/k3' #RELATIVE

Not 'code/k1' and so on?

% cat code/k1.rb
require 'lib/foo.rb' #RELATIVE

% cat code/k3/k3.rb
require 'k4' #RELATIVE

% cat code/k3/k4.rb
require 'code/lib/foo.rb' #~ABSOLUTE

It's not called absolute, I guess.

···

--
Nobu Nakada

trans. (T. Onoma) wrote:

...
module Kernel
   # Require files from same dir as running script.
   def import(*args)
     fd = File.dirname(caller[0])
     args.each do |file_name|
       require File.join(fd, file_name)
     end
   end
end

Perhaps your hack is better?
Nonetheless, the point being that it is preferable to have this as a separate method rather then integrated into #require. I recall that my experiments (also long ago) bore this out.
T.

Thanks for the nice snippet. And I agree that it may well be best to have separate words for basing a path around an absolute path and around a relative path. (OTOneH, it seems to me that I use a relative path more frequently, so that should be more fundamental. OTOH, an absolute path is easier to base things on. The gripping hand is that require is already defined, and any change to it risks breaking code.)

app/run.rb
app/code/app.rb
app/code/k1.rb
app/code/k2.rb
app/code/k3/k3.rb
app/code/k3/k4.rb
app/code/lib/foo.rb

% cat run.rb
require 'code/app'

% cat code/app.rb
require 'k1'; require 'k2'; require 'k3/k3' #RELATIVE

Not 'code/k1' and so on?

Correct. While for some reason it's less important to me to have this, it's rather important that I be able to have k3.rb simply do require 'k4' (or "import", or "load_relative", or whatever).

require 'code/lib/foo.rb' #~ABSOLUTE

It's not called absolute, I guess.

Yeah, more like "Root Relative", where the root changes magically based upon which file happened to start the train of requires.

···

On Oct 18, 2004, at 6:34 PM, nobu.nokada@softhome.net wrote:

Thanks for passing this along, although it doesn't work. caller[0] is neither a path, nor the path to the 'current' file. (The one I supplied is even worse, as __FILE__ doesn't change at all.) From my test (below), caller[2] seems like it would be usable (with some regexp massaging) but that feels like a hack which will break under some odd circumstance. Perhaps someone more clever than I can figure out a clean, robust way to do this.

~/Desktop/tmp.rb
~/Desktop/libs/lib1.rb
~/Desktop/libs/lib2.rb
~/Desktop/libs/sublib/lib3.rb

% cat tmp.rb
module Kernel
  require 'pathname'
  def import( *paths )
    paths.each{ |path|
      puts path,caller.join("\n"),' '
    }
  end
end

import 'Go find "./libs/lib1.rb" given "libs/lib1.rb"'
require 'libs/lib1.rb'

% cat libs/lib1.rb
1.times{
  import 'Go find "./libs/lib2.rb" given "lib2.rb"'
  require 'libs/lib2.rb'
}

% cat libs/lib2.rb
class Bar
  class Jim
    import 'Go find "./libs/sublib/lib3.rb" given "sublib/lib3.rb"'
    require 'libs/sublib/lib3.rb'
  end
end

% cat libs/sublibs/libs3.rb
import 'Go find "./libs/sublib/lib4.rb given "lib4.rb"'

% ruby tmp.rb
Go find "./libs/lib1.rb" given "libs/lib1.rb"
/Users/gavinkistner/Desktop/tmp.rb:6:in `each'
/Users/gavinkistner/Desktop/tmp.rb:6:in `import'
/Users/gavinkistner/Desktop/tmp.rb:12

Go find "./libs/lib2.rb" given "lib2.rb"
/Users/gavinkistner/Desktop/tmp.rb:6:in `each'
/Users/gavinkistner/Desktop/tmp.rb:6:in `import'
./libs/lib1.rb:4
./libs/lib1.rb:3:in `times'
./libs/lib1.rb:3
/Users/gavinkistner/Desktop/tmp.rb:13:in `require'
/Users/gavinkistner/Desktop/tmp.rb:13

Go find "./libs/sublib/lib3.rb" given "sublib/lib3.rb"
/Users/gavinkistner/Desktop/tmp.rb:6:in `each'
/Users/gavinkistner/Desktop/tmp.rb:6:in `import'
./libs/lib2.rb:3
./libs/lib1.rb:5:in `require'
./libs/lib1.rb:5
./libs/lib1.rb:3:in `times'
./libs/lib1.rb:3
/Users/gavinkistner/Desktop/tmp.rb:13:in `require'
/Users/gavinkistner/Desktop/tmp.rb:13

Go find "./libs/sublib/lib4.rb given "lib4.rb"
/Users/gavinkistner/Desktop/tmp.rb:6:in `each'
/Users/gavinkistner/Desktop/tmp.rb:6:in `import'
./libs/sublib/lib3.rb:1
./libs/lib2.rb:4:in `require'
./libs/lib2.rb:4
./libs/lib1.rb:5:in `require'
./libs/lib1.rb:5
./libs/lib1.rb:3:in `times'
./libs/lib1.rb:3
/Users/gavinkistner/Desktop/tmp.rb:13:in `require'
/Users/gavinkistner/Desktop/tmp.rb:13

···

On Oct 18, 2004, at 8:32 AM, trans. (T. Onoma) wrote:

  module Kernel
    # Require files from same dir as running script.
    def import(*args)
      fd = File.dirname(caller[0])
      args.each do |file_name|
        require File.join(fd, file_name)
      end
    end
  end

This solution is too naive, unfortunately. Consider this program:

main.rb:

$:.push File.dirname(File.expand_path(__FILE__))
require 'helper/foo'
rqeuire 'helper/bar'

helper/bar.rb:

$:.push File.dirname(File.expand_path(__FILE__))
puts "bar.rb"
require 'foo'

helper/foo.rb:

$:.push File.dirname(File.expand_path(__FILE__))
puts "foo.rb"

[pbrannan@zaphod tmp]$ ruby main.rb
foo.rb
bar.rb
foo.rb

There are three workarounds I know of:
1. foo.rb and bar.rb must know about the directory structure they are
   in, and bar.rb must require 'helper/foo'
2. main.rb can add the 'helper' directory to $:, and require foo.rb and
   bar.rb as if they were standalone libraries
3. The filenames can be normalized before storing them in $" (see
   RCR#211). This can be combined with the 'requirelocal' method which
   requires a file from the same directory as it is located (search the
   mailing list archives for more details).

Paul

···

On Tue, Oct 19, 2004 at 01:25:41AM +0900, Brian Candler wrote:

A simple solution might be something like this:

  $:.push File.dirname(File.expand_path(__FILE__))

The following seems to work for me (on MacOS X), tested in the presence of both 'root relative' and root-level paths along the way as part of real 'require' calls. (I've also added this to my 'basiclibrary', documented at Module: Kernel)

~/rr_test/start1.rb
~/rr_test/foo/bar/relative_require.rb
~/rr_test/libs/lib1.rb
~/rr_test/libs/lib2.rb
~/rr_test/libs/sublib/lib3.rb
~/rr_test/libs/sublib/lib4.rb

% cat start1.rb
require 'foo/bar/relative_require.rb'
#require 'libs/lib1.rb'
require_relative 'libs/lib1.rb'

% cat foo/bar/relative_require.rb
module Kernel
   def require_relative( *paths )
     path_match = Regexp.new( "^.+#{File::SEPARATOR}" )
     paths.each{ |path|
       file_path = caller[2] && caller[2].match( path_match )
       full_path = file_path && file_path[0] || ''
       puts "require '#{full_path + path}'" if $DEBUG
       require full_path + path
     }
   end
end

% cat libs/lib1.rb
1.times{
         #require '/Users/gavinkistner/rr_test/libs/lib2.rb'
         require_relative 'lib2.rb'
}

% cat libs/lib2.rb
class Bar
         class Jim
                 #require 'libs/sublib/lib3.rb'
                 require_relative 'sublib/lib3.rb'
         end
end

% cat libs/sublib/lib3.rb
#require 'libs/sublib/lib4.rb'
require_relative 'lib4.rb'

% cat libs/sublib/lib4.rb
puts "Congratulations, you made it to lib4!"

% ruby -d start1.rb
require 'libs/lib1.rb'
require './libs/lib2.rb'
require './libs/sublib/lib3.rb'
require './libs/sublib/lib4.rb'
Congratulations, you made it to lib4!

% cd libs && ruby -d start2.rb
require 'lib1.rb'
require './lib2.rb'
require './sublib/lib3.rb'
require './sublib/lib4.rb'
Congratulations, you made it to lib4!

···

On Oct 18, 2004, at 8:32 PM, Gavin Kistner wrote:

On Oct 18, 2004, at 8:32 AM, trans. (T. Onoma) wrote:

  module Kernel
    # Require files from same dir as running script.
    def import(*args)
      fd = File.dirname(caller[0])
      args.each do |file_name|
        require File.join(fd, file_name)
      end
    end
  end

Gavin Kistner wrote:

Thanks for passing this along, although it doesn't work.

Hi Gavin. You modified the source and moved the "caller" call into the
block. That's what broke it.

I agree it does not feel all that robust. I suppose it will break on a
system using ":" as path separator. For all I know, Macs may or may
not use ":" as path separator. (Perhaps a Macintosh user could clarify.)

Using ruby only on windows and unix so far, import is robust enough
for me. See here for the version that I suggested "a long time ago":
http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/61367

    Tobias

Gavin Kistner wrote:

Thanks for passing this along, although it doesn't work.

Hi Gavin. You modified the source and moved the "caller" call into the
block. That's what broke it.

I appreciate you pointing out the working code, though for the record, it wasn't I who moved anything anywhere...I was just responding to what trans passed along.

I agree it does not feel all that robust. I suppose it will break on a
system using ":" as path separator. For all I know, Macs may or may
not use ":" as path separator. (Perhaps a Macintosh user could clarify.)

MacOS X, as a *nix, uses '/' just fine.
And in any case, File.join uses File::SEPARATOR, so it should theoretically work even if MacOS used a different separator.

But I just tested yours, and as long as caller[0] isn't inside an each, it's doing the 'right' thing and works like a charm. Thanks! :slight_smile: (Or, inside the block, change it to caller[2])

···

On Oct 24, 2004, at 2:54 PM, Tobias Peters wrote:

Try it, and you'll see that the problem isn't with File::join but that
caller() returns a string that separates the filename from the line
number using ':'. Your code includes the line number as part of the
filename if you are on a system that has File::SEPARATOR set to ':'.

I know of no such system, though.

Paul

···

On Tue, Oct 26, 2004 at 10:55:46PM +0900, Gavin Kistner wrote:

MacOS X, as a *nix, uses '/' just fine.
And in any case, File.join uses File::SEPARATOR, so it should
theoretically work even if MacOS used a different separator.