How to navigate two directories up ruby - ruby

In Ruby,I have global variable $file_folder that gives me the location of the current config file:
$file_folder = "#{File}"
$file_folder = /repos/.../test-folder/features/support
I need to access a file sitting in a different folder and this folder is two levels up and two levels down. Is there any way to navigate to this target path using the current location?
target path = /repos/..../test-folder/lib/resources

There are two ways to do this (well, there are several, but here are two good ones). First, using File.expand_path:
original_path = "/repos/something/test-folder/features/support"
target_path = File.expand_path("../../lib/resources", original_path)
p target_path
# => "/repos/something/test-folder/lib/resources"
Note that File.expand_path will always return an absolute path, so it's not appropriate if you want to get a relative path back. If you use it, either make sure that the second argument is an absolute path, or, if it's a relative path, that you know what it will expand to (which will depend on the current working directory).
Next, using Pathname#join (which is aliased as Pathname#+):
require "pathname"
original_path = Pathname("/repos/something/test-folder/features/support")
target_path = original_path + "../../lib/resources"
p target_path
# => #<Pathname:/repos/something/test-folder/lib/resources>
You can also use Pathname#parent, but I think it's kind of ugly:
p original_path.parent.parent
# => #<Pathname:/repos/something/test-folder>
p original_path.parent.parent + "lib/resources"
# => #<Pathname:/repos/something/test-folder/lib/resources>
I prefer Pathname because it makes working with paths very easy. In general you can pass the Pathname object to any method that takes a path, but once in awhile a method will balk at anything other than a String, in which case you'll need to cast it as a string first:
p target_path.to_s
# => "/repos/something/test-folder/lib/resources"
P.S. foo = "#{bar}" is an antipattern in Ruby. If bar is already a String you should just use it directly, i.e. foo = bar. If bar isn't already a String (or you're not sure), you should cast it explicitly with to_s, i.e. foo = bar.to_s.
P.P.S. Global variables are a nasty code smell in Ruby. There is almost always a better way to go than using a global variable.

Related

Dir.glob stuck and thread not moving

Following code is stuck and it is not moving further in a custom plugin (here temp_dir = /tmp)
METADATA_FILE_EXTENSION = '.metadata'
metadata_files = Dir.glob(File.join(temp_dir, "**" ,"*#{METADATA_FILE_EXTENSION}"))
Your example isn't valid Ruby, but assuming that's just a typo it looks like you need to use File.join first. Per the Dir.glob docs, glob needs multiple parts of the path to be File.join'd first. Something closer to this:
metadata_files_path = File.join(temp_dir, "**", "*#{METADATA_FILE_EXTENSION}")
metadata_files = Dir.glob(meta_data_files_path)

Ruby mkdir returns 0

I have a Ruby script which sets up a directory which I need some other methods to use to store files. So, I need to be able to pass the directory as a string to some other methods:
To create the directory
results_dir = Dir.mkdir("/results/#{Time.now.strftime('%m-%d-%Y_%H:%M:%S')}")
The problem is that results_dir returns 0, not the string I would expect for the directory that was made: "/results/01-18-2016_14:58:38"
So, when I try to pass this to another method (i.e. my_method(var1, var2, results_dir), it's reading it as:
0/the_file_i_create
How can I pass the actual directory path to my methods?
It's not clear why you expect Dir.mkdir to return the directory name, since the docs explicitly say that Dir.mkdir returns 0:
mkdir( string [, integer] ) → 0
If you need the name of the directory you're creating, put it in a variable before you call Dir.mkdir:
results_dir = "/results/#{Time.now.strftime('%m-%d-%Y_%H:%M:%S')}"
Dir.mkdir(results_dir)
puts results_dir # => /results/01-18-2016_14:58:38
P.S. Avoid using colons (:) in file and directory names. It can cause issues on some systems, including OS X and Windows.

Is there a way to combine multiple regular expressions for a substring command?

Is there a way to combine these two regular expressions I am using to convert multi-platform file paths to a URL?
#image_file = "#{request.protocol}#{request.host}/#{#image_file.path.sub(/^([a-z]):\//,"")}".sub(/^\//,"")
This handles both my Windows and *IX platforms for file path conversion to a URL. For example, both of the following file path strings are handled properly:
- "c:\users\docs\pictures\image.jpg" goes to "http://localhost/users/docs/pictures/image.jpg"
- "\home\usr_name\pictures\image.jpg" goes to "http://localhost/usr_name/pictures/image.jpg"
I would prefer not to have to use two sub calls on a string if there is a way to combine them properly.
Suggestions and feedback from the community welcome!
The regex you are looking for is /^([a-z]:)?\//:
"c:/users/docs/pictures/image.jpg".sub(/^([a-z]:)?\//, '')
=> "users/docs/pictures/image.jpg"
"/home/usr_name/pictures/image.jpg".sub(/^([a-z]:)?\//, '')
=> "home/usr_name/pictures/image.jpg"
As some background on working with filenames and URLs...
First, Ruby doesn't require you to use reversed-slashes in Windows filenames, so if you're generating them don't bother. Instead, rely on the fact that the IO class knows what OS you're on and will auto-sense the path separator and convert things for you on the fly. This is from the IO documentation:
Ruby will convert pathnames between different operating system conventions if possible. For instance, on a Windows system the filename "/gumby/ruby/test.rb" will be opened as "\gumby\ruby\test.rb". When specifying a Windows-style filename in a Ruby string, remember to escape the backslashes:
"c:\\gumby\\ruby\\test.rb"
Our examples here will use the Unix-style forward slashes; File::ALT_SEPARATOR can be used to get the platform-specific separator character.
If you're receiving the paths from another source, this makes it easy to normalize them into something Ruby likes:
path = "c:\\users\\docs\\pictures\\image.jpg" # => "c:\\users\\docs\\pictures\\image.jpg"
puts path
# >> c:\users\docs\pictures\image.jpg
path.gsub!(/\\/, '/') if path['\\']
path # => "c:/users/docs/pictures/image.jpg"
puts path
# >> c:/users/docs/pictures/image.jpg
For convenience, write a little helper method:
def normalize_path(p)
p.gsub(/\\/, '/')
end
normalize_path("c:\\users\\docs\\pictures\\image.jpg") # => "c:/users/docs/pictures/image.jpg"
normalize_path("/users/docs/pictures/image.jpg") # => "/users/docs/pictures/image.jpg"
Ruby's File and Pathname classes are very helpful when dealing with paths:
foo = normalize_path(path) # => "c:/users/docs/pictures/image.jpg"
File.dirname(foo) # => "c:/users/docs/pictures"
File.basename(foo) # => "image.jpg"
and:
File.split(foo) # => ["c:/users/docs/pictures", "image.jpg"]
path_to_file, filename = File.split(foo)
path_to_file # => "c:/users/docs/pictures"
filename # => "image.jpg"
Alternately there's the Pathname class:
require 'pathname'
bar = Pathname.new(foo)
bar.dirname # => #<Pathname:c:/users/docs/pictures>
bar.basename # => #<Pathname:image.jpg>
Pathname is an experimental class in Ruby's standard library that wraps up all the convenience methods from File, FileUtils and Dir into one umbrella class. It's worth getting to know:
The goal of this class is to manipulate file path information in a neater way than standard Ruby provides. The examples below demonstrate the difference.
All functionality from File, FileTest, and some from Dir and FileUtils is included, in an unsurprising way. It is essentially a facade for all of these, and more.
Back to your question...
Ruby's standard library also contains the URI class. It's well tested and is a better way to build URLs than simple string concatenation due to idiosyncrasies that can occur when characters need to be encoded.
require 'uri'
url = URI::HTTP.build({:host => 'www.foo.com', :path => foo[/^(?:[a-z]:)?(.+)/, 1]})
url # => #<URI::HTTP:0x007fe91117a438 URL:http://www.foo.com/users/docs/pictures/image.jpg>
The build method applies syntax rules to make sure the URL is valid.
If you need it, at this point you can tack on to_s to get the stringified version:
url.to_s # => "http://www.foo.com/users/docs/pictures/image.jpg"

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 .*$/, '')

How do I temporarily change the require path in Ruby ($:)?

I'm doing some trickery with a bunch of Rake tasks for a complex project, gradually refactoring away some of the complexity in chunks at a time. This has exposed the bizarre web of dependencies left behind by the previous project maintainer.
What I'd like to be able to do is to add a specific path in the project to require's list of paths to be searched, aka $:. However, I only want that path to be searched in the context of one particular method. Right now I'm doing something like this:
def foo()
# Look up old paths, add new special path.
paths = $:
$: << special_path
# Do work ...
bar()
baz()
quux()
# Reset.
$:.clear
$: << paths
end
def bar()
require '...' # If called from within foo(), will also search special_path.
...
end
This is clearly a monstrous hack. Is there a better way?
Since $: is an Array, you have to be careful about what you are doing. You need to take a copy (via dup) and replace it later. It' simpler to simply remove what you have added, though:
def foo
$: << special_path
# Do work ...
bar()
ensure
# Reset.
$:.delete(special_path)
end
Without more info, it's difficult to know if there is a better way.
require is actually a method, it's Kernel#require (which calls rb_require_safe) so you could at least perform your hackery in a monkey-patched version. If you like that kind of thing.
Alias the orignal require out of the way
If passed an absolute path, call the original require method
Else iterate over load path by creating an absolute path and calling the original require method.
Just for fun I had a quick bash at that, prototype is below. This isn't fully tested, I haven't checked the semantics of rb_require_safe, and you probably would also need to look at #load and #include for completeness -- and this remains a monkey-patch of the Kernel module. It's perhaps not entirely monstrous, but it's certainly a hack. Your call if it's better or worse than messing with the global $: variable.
module Kernel
alias original_require require
# Just like standard require but takes an
# optional second argument (a string or an
# array of strings) for additional directories
# to search.
def require(file, more_dirs=[])
if file =~ /^\// # absolute path
original_require(file)
else
($: + [ more_dirs ].flatten).each do |dir|
path = File.join(dir, file)
begin
return original_require(path)
rescue LoadError
end
end
raise LoadError,
"no such file to load -- #{file}"
end
end
end
Examples:
require 'mymod'
require 'mymod', '/home/me/lib'
require 'mymod', [ '/home/me/lib', '/home/you/lib' ]

Resources