Relative #require

G

Gavin Kistner

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
 
T

trans. (T. Onoma)

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.


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.
 
B

Brian Candler

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| $: = Mydir if $: == '.'}

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.
 
C

Charles Hixson

trans. (T. Onoma) said:
...
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.)
 
N

nobu.nokada

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.
 
G

Gavin Kistner

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).
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.
 
G

Gavin Kistner

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

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
 
P

Paul Brannan

A simple solution might be something like this:

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

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
 
G

Gavin Kistner

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

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 http://phrogz.net/RubyLibs/rdoc/classes/Kernel.html)


~/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!
 
T

Tobias Peters

Gavin said:
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
 
G

Gavin Kistner

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! :) (Or,
inside the block, change it to caller[2])
 
P

Paul Brannan

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.

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
 

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,982
Messages
2,570,186
Members
46,744
Latest member
CortneyMcK

Latest Threads

Top