Filesystem object symbology - ruby

What is the most standard Ruby symbology for naming variables containing file names, file names with path and file instances? Completely clear way of doing this would be:
file_name = "bar.txt"
file_name_with_path = "foo", file_name
file = File.open( file_name_with_path )
But it's too long. It is out of question to use :file_name_with_path in method definition:
def quux( file_name_with_path: "foo/bar.txt" )
# ...
end
Having encountered this for umpteenth time, I realized that shortening conventions are needed. I started making personal shortening conventions: :file_name => :fn, :file_name_with_path => :fnwp, :file always refers to a File instance, :fn never includes path, :fnwap means :file_name_with_absolute_path etc. But everyone must be facing this, so I am asking: Is there a public convention for this? More particularly, does Rails code have a convention for this?

But everyone must be facing this...
No, not really, because you're really over-thinking this.
Just use file:, or filename:. It doesn't matter whether your filename contains a relative or absolute path, or whether the path contains directories, and your code should reflect this. A path to a file is just a path to a file, and all paths should be treated identically by your code: It just opens the file, and raises an error if it can't.
You can use filesystem utilities to extract directories and base names from a path, and they'll work just fine on any path, regardless of the presence of directories, regardless of wether the path is absolute or relative. It just doesn't matter.

Related

How to get the name of an instance of Tempfile in Ruby?

When creating a Tempfile in ruby, it takes the basename you pass it, and then it appends a random string to the end.
From the docs: http://ruby-doc.org/stdlib-1.9.3/libdoc/tempfile/rdoc/Tempfile.html
file = Tempfile.new('hello')
file.path # => something like: "/tmp/hello2843-8392-92849382--0"
You can see it starts with hello and then adds 2843-8392-92849382--0. Though this ending will change every time you create an instance.
This makes it difficult (at least for me) to lookup in the directory its saved in.
Question:
Is there any method (like file.fullName) that could be run on the instance to just get the hello2843-8392-92849382--0, in order to look it up in the directory where its saved?
Thoughts:
You could take the path and parse it but that seems excessive.
Basically you're asking for:
File.basename(file.path)
There's rarely a reason to need that exposed as a method, but if you want you could subclass Tempfile to add it in:
class SuperTempfile < Tempfile
def basename
File.basename(path)
end
end

`File.exist?` won't find file that exists

I'm trying to use File.exist? to have a dynamic background. When I try to to point File.exist? to a specific file it doesn't seem to find it.
image_path = "../assets/" + city_code + ".jpg"
if
File.exist?('#{image_path}')
#image = image_path
else
#image = "../assets/coastbanner.jpg"
end
If I remove replace city_code with a path that I know exists, it still won't find it. I must bee missing something small.
I wanted to be a little more clear: If I remove the interpolation/variable and replace it with a path that I know works, '../assets/coastbanner.jpg' for example, it still will not work. In fact, no valid path seems to get it to yield a true response.
There are some issues with your code:
String interpolation works only with double quotes
There is no need for a string interpolation when the variable is a string already
the condition belongs in the same line than the if
Furthermore the folder in which the server is running might not be the same folder than the current webpage is served from. That said it might make sense to use absolute path for image links and path including Rails.root for the File.exist? test.
I am not sure about your application's folder structure, but something like the following should work:
image_path = "/assets/#{city_code}.jpg"
if File.exist?(Rails.root.join('app', image_path))
#image = image_path
else
#image = '/assets/coastbanner.jpg'
end
Tip: Test with the coastbanner.jpg to get the image apth correct and then with the File.exist? to get the path in the app.
You need to use double quotes for the string interpolation to work.
File.exist?("#{image_path}")
In this case, you could actually just use the variable since it already is the string you want.
File.exist?(image_path)
If you want to use the #{} feature, a good place to use it is when creating image_path.
image_path = "../assets/#{city_code}.jpg"
if
File.exist?( image_path )
Don't think you can have #{} in a string with single quotes. Try double-quoting it.
I assume you are using with in a Rails application, and if so, the most probable cause would be:
When Rails creates assets it creates a digest. Because of this your image name might have a digest string in the name. So I suggest to use image_path or asset_path
Ex: File.exist?(image_path("#{city_code}.jpg"))
But having said that, proper was to handle these kind of cases would be to have a flag in the database if you want to set the the custom image or not. Because it's much faster than accessing the file system.

Capturing parts of an absolute filepath in Ruby

I'm writing a class that parses a filename. I've got 3 questions:
The regex
Given hello/this/is/my/page.html I want to capture three parts:
The parent folders: hello/this/is/my
The filename itself: page
The extension: .html
This is the regex: /^((?:[^\/]+\/)*)(\w+)(\.\w+)$/
The problem is that when I tried this (using Rubular), when I use a relative pathfile such as page.html, it all gets captured into the first capturing group.
Can someone suggest a regex that works correctly for both relative and absolute filepaths?
The class
Would this class be ok?
class RegexFilenameHelper
filenameRegex = /^((?:[^\/]+\/)*)(\w+)(\.\w+)$/
def self.getParentFolders(filePath)
matchData = filenameRegex.match(filePath)
return matchData[1]
end
def self.getFileName(filePath)
# ...
end
def self.getFileExtension(filePath)
# ...
end
end
I understand that it's inefficient to call .match for every function, but I don't intend to use all three functions sequentially.
I also intend to call the class itself, and not instantiate an object.
An aside
Assuming this is important: would you rather capture .html or html, and why?
Using the standard library:
As Tim Pietzcker suggested, the functionality is already implemented in the Pathname and File classes.
filepath = "hello/this/is/my/page.html"
Getting the parents: File.dirname(filepath) => "hello/this/is/my"
Getting the name: File.basename(filepath) => "page.html"
without extension: File.basename(filepath, File.extname(filepath)) => "page"
Getting the extension: File.extname(filepath) => ".html"
We call class methods without having to instantiate any class, which is exactly what I wanted.
It's not necessary for the file or folders to actually exist in the file system!
Thanks to Tim Pietzcker for letting me know!
Using regex:
If I had wanted to do it with regex, the correct regex would be ((?:^.*\/)?)([^\/]+)(\..*$).
((?:^.*\/)?): Captures everything before the last /, or nothing (that's what the last ? is for). This is the parent path, which is optional.
([^\/]+): Gets everything that's not /, which is the filename.
(\..*$): Captures everything coming after the last ., including it.
I tried this in Rubular and it worked like a charm, but I'm still not sure if the second capturing group is too broad, so be careful if you use this!
Thanks to user230910 for helping me get there! :)

Has Directory Content Changed?

How can I check a directory to see if its contents has changed since a given point in time?
I don't need to be informed when it changes, or what has changed. I just need a way to check if it has changed.
Create a file at the point in time you wish to start monitoring, using any method you like, e.g.:
touch time_marker
Then, when you want to check if anything has been added, use "find" like this:
find . -newer time_marker
This will only tell you files that have been modified or added since time_marker was created - it won't tell you if anything has been deleted. If you want to look again at a future point, "touch" time_marker again to create a new reference point.
If you just need to know if names have changed or files have been added/removed, you can try this:
Dir.glob('some_directory/**/*').hash
Just store and compare the hash values. You can obviously go further by getting more information out of a call to ls, for example, or out of File objects that represent each of the files in your directory structure, and hashing that.
Dir.glob('some_directory/**/*').map { |name| [name, File.mtime(name)] }.hash
UM ACTUALLY I'm being dumb and hash is only consistent for any one runtime environment of ruby. Let's use the standard Zlib::crc32 instead, e.g.
Zlib::crc32(Dir.glob('some_directory/**/*').map { |name| [name, File.mtime(name)] }.to_s)
My concern is that this approach will be memory-hungry and slow if you're checking a very large filesystem. Perhaps globbing the entire structure and mapping it isn't the way--if you have a lot of subdirectories you could walk them recursively and calculate a checksum for each, then combine the checksums.
This might be better for larger directories:
Dir.glob('some_directory/**/*').map do |name|
s = [name, File.mtime(name)].to_s
[Zlib::crc32(s), s.length]
end.inject(Zlib::crc32('')) do |combined, x|
Zlib::crc32_combine(combined, x[0], x[1])
end
This would be less prone to collisions:
Dir.glob('some_directory/**/*').map do |name|
[name, File.mtime(name)].to_s
end.inject(Digest::SHA512.new) do |digest, x|
digest.update x
end.to_s
I've amended this to include timestamp and file size.
dir_checksum = Zlib::crc32(Dir.glob(
File.join(dispatch, '/**/*')).map { |path|
path.to_s + "_" + File.mtime(path).to_s + "_" + File.size(path).to_s
}.to_s)

How can I check if a string is a valid file name for windows using R?

I've been writing a program in R that outputs randomization schemes for a research project I'm working on with a few other people this summer, and I'm done with the majority of it, except for one feature. Part of what I've been doing is making it really user friendly, so that the program will prompt the user for certain pieces of information, and therefore know what needs to be randomized. I have it set up to check every piece of user input to make sure it's a valid input, and give an error message/prompt the user again if it's not. The only thing I can't quite figure out is how to get it to check whether or not the file name for the .csv output is valid. Does anyone know if there is a way to get R to check if a string makes a valid windows file name? Thanks!
These characters aren't allowed: /\:*?"<>|. So warn the user if it contains any of those.
Some other names are also disallowed: COM, AUX, NUL, COM1 to COM9, LPT1 to LPT9.
You probably want to check that the filename is valid using a regular expression. See this other answer for a Java example that should take minimal tweaking to work in R.
https://stackoverflow.com/a/6804755/134830
You may also want to check the filename length (260 characters for maximum portability, though longer names are allowed on some systems).
Finally, in R, if you try to create a file in a directory that doesn't exist, it will still fail, so you need to split the name up into the filename and directory name (using basename and dirname) and try to create the directory first, if necessary.
That said, David Heffernan gives good advice in his comment to let Windows do the wok in deciding whether or not it can create the file: you don't want to erroneously tell the user that a filename is invalid.
You want something a little like this:
nice_file_create <- function(filename)
{
directory_name <- dirname(filename)
if(!file.exists(directory_name))
{
ok <- dir.create(directory_name)
if(!ok)
{
warning("The directory of that path could not be created.")
return(invisible())
}
}
tryCatch(
file.create(filename),
error = function(e)
{
warning("The file could not be created.")
}
)
}
But test it thoroughly first! There are all sorts of edge cases where things can fall over: try UNC network path names, "~", and paths with "." and ".." in them.
I'd suggest that the easiest way to make sure a filename is valid is to use fs::path_sanitize().
It removes control characters, reserved characters, and Windows-reserved filenames, truncating the string at 255 bytes in length.

Resources