A Ruby method for resolving relative paths while keeping absolute paths? - ruby

I am working on writing a rake build scrip which will work cross platform ( Mac OSX, Linux , Windows ). The build script will be consumed by a CI server.
I want the logic of my script to be as follows:
If the path is determined to be relative, make it absolute by making output_path = FOO_HOME + user_supplied_relative_path
If the path is determined to be absolute, take it as-is
I'm currently using Pathname.new(location).absolute? but it's not working correctly on windows.
What approach would you suggest for this?

require 'pathname'
(Pathname.new "/foo").absolute? # => true
(Pathname.new "foo").absolute? # => false

The method you're looking for is realpath.
Essentially you do this:
absolute_path = Pathname.new(path).realpath
N.B.: The Pathname module states that usage is experimental on machines that do not have unix like pathnames. So it's implementation dependent. Looks like JRuby should work on Windows.

There is a built-in function that covers both cases and does exactly what you want:
output_path = File.absolute_path(user_supplied_path, FOO_HOME)
The trick is supplying a second argument. It servers as a base directory if (and only if) the first argument is a relative path.

Pathname can do all that for you
require "pathname"
home= Pathname.new("/home/foo")
home + Pathname.new("/bin") # => #<Pathname:/bin>
home + Pathname.new("documents") # => #<Pathname:/home/foo/documents>
I am not sure about this on windows though.

You could also use File.expand_path if the relative directory is relative to the current working directory.
I checked on Linux and windows and didn't have any issues.
Assuming FOO_HOME is the working directory, the code would be:
output_path = File.expand_path user_supplied_relative_path

Related

Confusing behavior of File.dirname

I have written a couple of small Ruby scripts for system administration using Ruby 1.9.3. In one script I use:
File.dirname(__FILE__)
to get the directory of the script file. This returns a relative path, however when I call the script from a second script File.dirname returns an absolute path.
Ruby Doc lists an absolute return path in its example whereas I found a discussion on Ruby Forum where a user says dirname should only return a relative path.
I am using the suggested solution from Ruby Forums to use File.expand_path to always get the absolute path like this:
File.expand_path(File.dirname(__FILE__))
but is there a way to make the behaviour of dirname consistent?
UPDATE:
To expand on Janathan Cairs answer, I made two scripts:
s1.rb:
puts "External script __FILE__: #{File.dirname(__FILE__)}"
s0.rb:
puts "Local script __FILE__: #{File.dirname(__FILE__)}"
require './s1.rb'
Running ./s0.rb gives the following output:
Local script __FILE__: .
External script __FILE__: /home/kenneth/Pictures/wp/rip_vault
File.dirname should return an absolute path if given an absolute path, and a relative path if given a relative path:
File.dirname('/home/jon/test.rb') # => '/home/jon'
File.dirname('test.rb') # => '.'
__FILE__ returns the name of the current script, which is therefore a relative path from the current directory. That means you should always use expand_path if you want to get the absolute path with File.dirname(__FILE__).
NB Ruby 2.0.0 introduces the __dir__ constant
If you already upgraded to Ruby 2.0, you can use the new constant
__dir__
otherwise you can use
File.expand_path('..', __FILE__)
which is shorter than
File.expand_path(File.dirname(__FILE__))
File.expand_path documentation

How do I find the location of the gem?

I'm developing a library that provides access to gem metadata, including it's location on the file system. The idea was to let gem authors set it to a relative path from any script:
# $root/example.gemspec
Example::Gem.root '.' # => $root/
# $root/lib/example/gem.rb
Example::Gem.root '../..' # => $root/
Then, the path of the current script would be used to compute the absolute path. My implementation is currently as follows:
def root(relative_to = nil, file = __FILE__)
unless relative_to.nil?
#root = File.expand_path relative_to, File.dirname(file)
end
#root
end
I thought __FILE__ would return the path to the caller's script, but that assumption is wrong.
It worked within the library itself, but broke down when I tried to integrate it with one of my other gems; the generated path was always relative to the support library itself.
How can I implement this without having to pass the current __FILE__ on every call? Otherwise, there isn't much value to be gained; writing root('../..', __FILE__) is almost the same as writing an actual method to do the same thing.
If it's possible to figure out the path without having to specify anything, that would be even better, but I couldn't think of anything. How does Rails do it?
By the way, I'm aware of Gem::Specification#gem_dir, but it always returns paths relative to the installation directory, even if the gem is not actually there, which makes it useless in a development environment.
You can always make use of the backtrace facility provided:
caller.first
It produces an amalgam of file and line but is usually separated by :. I'd be careful to allow for filenames or paths that may contain colon for whatever reason by ignoring the line information but preserving the rest. In other words, do not split but sub:
caller.first.sub(/:\d+:in .*$/, '')

Relative File Path in RSpec

I have a RSpec test for a class in /lib/classes which needs access to a zip file (no upload). The file is stored in /spec/fixtures/files/test.zip. How do I input the correct path so it's environment agnostic, i.e. without absolute path?
Rails.root will give you the app root, so
Rails.root.join "spec/fixtures/files/test.zip"
will give you the absolute path of your file, agnostic of the location of the app on your hard drive.
I've tackled this issue recently and here's what I came up with:
Define a constant in your spec_helper.rb (or equivalent) pointing to RSpec root:
RSPEC_ROOT = File.dirname __FILE__
Use this variable in your *_spec.rb (or equivalent) test files:
require 'spec_helper'
File.open("#{RSPEC_ROOT}/resources/example_data.json")
Why this solution?
It doesn't make use of the location of the test file which may be
subject to change (spec_helper is likely not)
It doesn't require any additions to test files other than the already existing
require 'spec_helper'
It doesn't depend on Rails (unlike Rails.root)
Here's a Gist: https://gist.github.com/thisismydesign/9dc142f89b82a07e413a45a5d2983b07
As far as I know (from looking every month or two) there is no better way that building something in spec_helper that uses the __FILE__ value to grab the path to a know bit of content, then build your own helpers on top of that.
You can obviously use a path relative to __FILE__ in the individual *_spec.rb file as well.

Why isn't this glob working for a Rake FileList for my server?

How come I get an empty filelist from:
files = FileList.new("#{DEPLOYMENT_PATH}\**\*")
Where DEPLOYMENT_PATH is \\myserver\anndsomepath
How to get a filelist from a server like this? Is this an issue of Ruby/Rake?
UPDATE:
I tried:
files = FileList.new("#{DEPLOYMENT_PATH}\\**\\*")
files = Dir.glob("#{DEPLOYMENT_PATH}\\**\\*")
files = Dir.glob("#{DEPLOYMENT_PATH}\**\*")
UPDATE AGAIN: It works if I put server as:
//myserver/andsomepath
and get files like this:
files = FileList.new("#{DEPLOYMENT_PATH}/**/*")
Ruby' File.join is designed to be your helper when dealing with file paths, by building them in a system-independent way:
File.join('a','b','c')
=> "a/b/c"
So:
DEPLOYMENT_PATH = File.join('', 'myserver', 'andsomepath')
=> "/myserver/andsomepath"
Ruby determines the file path separator by sensing the OS, and is supposed to automatically supply the right value. On Windows XP, Linux and Mac OS it is:
File::SEPARATOR
=> "/"
File.join(DEPLOYMENT_PATH, '**', '*')
=> "/myserver/andsomepath/**/*"
While you can ignore the helper, it is there to make your life easier. Because you are working against a server, you might want to look into File::ALT_SEPARATOR, or just reassigning to SEPARATOR and ignore the warning, letting Ruby do the rest.
What happens if you do
Dir.glob("#{DEPLOYMENT_PATH}\**\*")
Edit: I think Ruby prefers you doing Unix-style slashes, even when you're on Windows. I assume the rationale is that it's better for the same code to work on both Unix and Windows, even if it looks weird on Windows.
tl;dr: If it works with / but not with \, then use what works.
Because:
> "\*" == "*"
=> true
Use "\\**\\*" instead.

slash and backslash in Ruby

I want to write a application that works in windows and linux. but I have a path problem because windows use "\" and Linux use "/" .how can I solve this problem.
thanks
In Ruby, there is no difference between the paths in Linux or Windows. The path should be using / regardless of environment. So, for using any path in Windows, replace all \ with /. File#join will work for both Windows and Linux. For example, in Windows:
Dir.pwd
=> "C/Documents and Settings/Users/prince"
File.open(Dir.pwd + "/Desktop/file.txt", "r")
=> #<File...>
File.open(File.join(Dir.pwd, "Desktop", "file.txt"), "r")
=> #<File...>
File.join(Dir.pwd, "Desktop", "file.txt")
=> "C/Documents and Settings/Users/prince/Desktop/file.txt"
As long as Ruby is doing the work, / in path names is ok on Windows
Once you have to send a path for some other program to use, especially in a command line or something like a file upload in a browser, you have to convert the slashes to backslashes when running in Windows.
C:/projects/a_project/some_file.rb'.gsub('/', '\\') works a charm. (That is supposed to be a double backslash - this editor sees it as an escape even in single quotes.)
Use something like this just before sending the string for the path name out of Ruby's control.
You will have to make sure your program knows what OS it is running in so it can decide when this is needed. One way is to set a constant at the beginning of the program run, something like this
::USING_WINDOWS = !!((RUBY_PLATFORM =~ /(win|w)(32|64)$/) || (RUBY_PLATFORM=~ /mswin|mingw/))
(I know this works but I didn't write it so I don't understand the double bang...)
Take a look at File.join: http://www.ruby-doc.org/core/classes/File.html#M000031
Use the Pathname class to generate paths which then will be correct on your system:
a_path = Pathname.new("a_path_goes_here")
The benefit of this is that it will allow you to chain directories by using the + operator:
a_path + "another_path" + "and another"
Calling a_path.to_s will then generate the correct path for the system that you are on.
Yes, it's annoying as a windows users to keep replacing those backslashes to slashes and vice-versa if you need the path to copy it to your filemanager, so i do it like his.
It does no harm if you are on Linux or Mac and saves a lot of nuisance in windows.
path = 'I:\ebooks\dutch\_verdelen\Komma'.gsub(/\\/,'/')
Dir.glob("#{path}/**/*.epub").each do |file|
puts file
end

Resources