Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Get directory of file that instantiated a class ruby

Tags:

file-io

ruby

I have a gem that has code like this inside:

def read(file)
    @file = File.new file, "r"
end

Now the problem is, say you have a directory structure like so:

app/main.rb
app/templates/example.txt

and main.rb has the following code:

require 'mygem'

example = MyGem.read('templates/example.txt')

It comes up with File Not Found: templates/example.txt. It would work if example.txt was in the same directory as main.rb but not if it's in a directory. To solve this problem I've added an optional argument called relative_to in read(). This takes an absolute path so the above could would need to be:

require 'mygem'

example = MyGem.read('templates/example.txt', File.dirname(__FILE__))

That works fine, but I think it's a bit ugly. Is there anyway to make it so the class knows what file read() is being called in and works out the path based on that?

like image 398
andy Avatar asked Feb 08 '13 12:02

andy


5 Answers

There is an interesting library - i told you it was private. One can protect their methods with it from being called from outside. The code finds the caller method's file and removes it. The offender is found using this line:

offender = caller[0].split(':')[0]

I guess you can use it in your MyGem.read code:

def read( file )
  fpath = Pathname.new(file)
  if fpath.relative?
    offender = caller[0].split(':')[0]
    fpath = File.join( File.dirname( offender ), file )
  end
  @file = File.new( fpath, "r" )
end

This way you can use paths, relative to your Mygem caller and not pwd. Exactly the way you tried in your app/main.rb

like image 182
Draco Ater Avatar answered Nov 07 '22 04:11

Draco Ater


Well, you can use caller, and a lot more reliably than what the other people said too.

In your gem file, outside of any class or module, put this:

c = caller
req_file = nil
c.each do |s|
  if(s =~ /(require|require_relative)/)
    req_file = File.dirname(File.expand_path(s.split(':')[0])) #Does not work for filepaths with colons!
    break
  end
end
REQUIRING_FILE_PATH = req_file

This will work 90% of the time, unless the requiring script executed a Dir.chdir. The File.expand_path depends on that. I'm afraid that unless your requirer passes their __FILE__, there's nothing you can do if they change the working dir.

like image 20
Linuxios Avatar answered Nov 07 '22 02:11

Linuxios


Also you may check for caller:

def read(file)
  if /^(?<file>.+?):.*?/ =~ caller(1).first
    caller_dir, caller_file = Pathname.new(Regexp.last_match[:file]).split
    file_with_path = File.join caller_dir, file
    @file = File.new "#{file_with_path}", "r"
  end
end

I would not suggest you to do so (the code above will break being called indirectly, because of caller(1), see reference to documentation on caller). Furthermore, the regex above should be tuned more accurately if the caller path is intended to contain colons.

like image 1
Aleksei Matiushkin Avatar answered Nov 07 '22 03:11

Aleksei Matiushkin


This should work for typical uses (I'm not sure how resistant it is to indirect use, as mentioned by madusobwa above):

def read_relative(file)
  @file = File.new File.join(File.dirname(caller.first), file)
end

On a side note, consider adding a block form of your method that closes the file after yielding. In the current form you're forcing clients to wrap their use of your gem with an ensure.

like image 1
exbinary Avatar answered Nov 07 '22 03:11

exbinary


Accept a file path String as an argument. Convert to a Pathname object. Check if the path is relative. If yes, then convert to absolute.

def read(file)
  fpath = Pathname.new(file)
  if fpath.relative?
    fpath = File.expand_path(File.join(File.dirname(__FILE__),file))
  end
  @file = File.new(fpath,"r")
end

You can make this code more succinct (less verbose).

like image 1
Martin Velez Avatar answered Nov 07 '22 02:11

Martin Velez