Ruby Dir.mkdir Usage - ruby

I am pretty new to ruby and have a very simple ruby script that has the following purpose:
Read lines of file
Access jira instance using jira4r gem
Query jira instance for issue(s)
Create a directory using the issue key and issue summary
I've come to the conclusion after some tinkering that the Dir.mkdir command does not accept the object I am passing it as argument.
Findings:
If Dir.mkdir is passed a line, #{chompline}, from my textfile directory creation execute properly.
If Dir.mkdir is passed a string consisting of issue.key and issue.summary it chokes with the following error:
./readFile.rb:29:in `mkdir': No such file or directory - (Errno::ENOENT)
from ./readFile.rb:29
Based on point #1 and #2, it must be something about the string I create from issue key and summary.
I have the following theories/questions:
Is "#{keyPlusSummary}"the correct object type to pass into mkdir as argument ?
I believe it to be string, but perhaps I am assuming incorrectly.
Source:
#!/usr/bin/env ruby
require 'rubygems'
require 'jira4r'
require 'FileUtils'
jira = Jira4R::JiraTool.new(2, "http://jira.somejirainstance.com")
baseurl = jira.getServerInfo().baseUrl
puts "Base URL: " + baseurl , "\n"
jira.login("someUser", "somePassword")
file = File.new("awkOutput.txt", "r")
while (line = file.gets)
chompline = "#{line}".chomp!
issue = jira.getIssue("#{chompline}")
keyPlusSummary = "#{issue.key}"+"#{issue.summary}"
puts keyPlusSummary
Dir.mkdir "#{keyPlusSummary}"
end
file.close

It's a string, but you don't tell us what's in it.
# More canonical, both in var naming, and there's
# no need for concatenation in this case.
dir_name = "#{issue.key}#{issue.summary}"
Are you making the string "directory-name friendly"?
I would not use a JIRA issue summary as a directory name; IMO just the project/issue # would be enough. If you do use the summary, make it something that's directory-friendly by stripping out anything non-alphanumeric, and replacing spaces with underscores.

keyPlusSummary is a string, so it is of the right type. What may be the problem is slashes in the string. Like mkdir in UNIX, Dir.mkdir will not create parent directories for you, it will only create a single directory. If the key + summary includes a '/', then it will read it as a multi-level directory. You need to either escape the '/', or (better), use FileUtils.mkdir_p, or (best) do cleanup to replace ' ' with '_', and remove special characters that make using the directory harder :)
As an aside, your code doesn't need to have the interpolations it does:
#!/usr/bin/env ruby
require 'rubygems'
require 'jira4r'
require 'FileUtils'
jira = Jira4R::JiraTool.new(2, "http://jira.somejirainstance.com")
baseurl = jira.getServerInfo().baseUrl
puts "Base URL: #{baseurl}\n" #use it here!
jira.login("someUser", "somePassword")
File.new("awkOutput.txt", "r") do |file| #using the block form to ensure you close the file
while (line = file.gets)
chompline = line.chomp! #line is already a string, no need to interpolate
issue = jira.getIssue(chompline) #line is already a string, no need
keyPlusSummary = "#{issue.key}#{issue.summary}" #already interpolating, no need to add
puts keyPlusSummary
Dir.mkdir keyPlusSummary #already a string
end
end

Related

Get directory of file that instantiated a class 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?
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
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.
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.
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.
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).

Reading file with Ruby returns strange output

I am trying to read in a JSON file with Ruby and the output is extremely strange. Here is the code that I am using:
require 'rubygems'
class ServiceCalls
def initialize ()
end
def getFile()
Dir.entries('./json').each do |mFile|
if mFile[0,1] != "."
self.sendServiceRequest(mFile)
end
end
end
def sendServiceRequest(mFile)
currentFile = File.new("./json/" + mFile, "r")
puts currentFile.read
currentFile.close
end
end
mServiceCalls = ServiceCalls.new
mServiceCalls.getFile
And here is the output:
Macintosh H??=A?v?P$66267945-2481-3907-B88A-1094AA9DAB6D??/??is32???????????????????????????????????vvz?????????????????????????????????????????????????????????????????????????????????????????????vvz?????????????????????????????????????????????????????????????????????????????????????????????vvz???????????????????????????????????????????????????????????s8m+88888888???????89????????99?????????9:??????????:;??????????;=??????????=>??????????>????????????#??????????#A??????????AC??????????CD??????????DE??????????EE??????????E6OXdknnkdXO6ic118?PNG
bookmark88?A[DT>??A?#
ApplicationsMAMPhtdocsServiceTestAutomationMDXservicecatalog-verizon.json$4T??
`?
U?????l??????
Macintosh H??=A?v?P$66267945-2481-3907-B88A-1094?is32???????????????????????????????????vvz?????????????????????????????????????????????????????????????????????????????????????????????vvz?????????????????????????????????????????????????????????????????????????????????????????????vvz???????????????????????????????????????????????????????????s8m+88888888???????89????????99?????????9:??????????:;??????????;=??????????=>??????????>????????????#??????????#A??????????AC??????????CD??????????DE??????????EE??????????E6OXdknnkdXO6ic118?PNG
UIEvolutions-MacBook-Pro-109:MDXServiceTesting Banderson$ ruby testmdxservices.rb
bookmark88?A?,P>??A?#
ApplicationsMAMPhtdocsServiceTestAutomationMDXservicecatalog-adaptation.json$4T??
`?
U?????l??????
Macintosh H??=A?v?P$66267945-2481-3907-B88A-1094AA9DAB6D??/?<icns<?TOC his32?s8mic118il32?l8mic1?ic07ic13#ic08#ic14^?ic09_ic1?is32???????????????????????????????????vvz?????????????????????????????????????????????????????????????????????????????????????????????vvz?????????????????????????????????????????????????????????????????????????????????????????????vvz???????????????????????????????????????????????????????????s8m+88888888???????89????????99?????????9:??????????:;??????????;=??????????=>??????????>????????????#??????????#A??????????AC??????????CD??????????DE??????????EE??????????E6OXdknnkdXO6ic118?PNG
IHDR szz?iCCPICC Profile(?T?k?P??e???:g >h?ndStC??kW??Z?6?!H??m\??$?~?ًo:?w?>?
كo{?a???"L?"???4M'S??????9'??^??qZ?/USO???????^C+?hM??J&G#Ӳy???lt?o߫?c՚?
? ??5?"?Y?i\?΁?'&??.?<?ER/?dE?oc?ግ#?f45#? ??B:K?#8?i??
??s??_???雭??m?N?|??9}p?????_?A??pX6?5~B?$?&???ti??e??Y)%$?bT?3li?
??????P???4?43Y???P??1???KF????ۑ??5>?)?#????r??y??????[?:V???ͦ#??wQ?HB??d(??B
a?cĪ?L"J??itTy?8?;(???Gx?_?^?[???????%׎??ŷ??Q???麲?ua??n?7???
Q???H^e?O?Q?u6?S??u
?2??%vX
???^?*l
O?????ޭˀq,>??S???%?L??d????B???1CZ??$M??9??P
'w????\/????]???.r#???E|!?3?>_?o?a?۾?d?1Z?ӑ???z???'?=??????~+??cjJ?tO%mN?????
|??-???bW?O+
o?
^?
I?H?.?;???S?]?i_s9?*p???.7U^??s.?3u?
Can someone please tell me what I am doing wrong? Do I need to specify what type of encoding I'm using? I have tried to read the file with gets, sysread, and another I can't remember.
I am not completely sure why but I believe it is the './json' path that is causing the issue. I tried the script on my Windows XP machine and got similar results.
However, when I rewrote the script to include File.dirname(__FILE__) instead of './' it worked. I also cleaned up some of the code.
class ServiceCalls
def get_file
dirname = File.join(File.dirname(__FILE__), 'json')
Dir.entries(dirname).each do |file|
unless file.start_with? '.'
File.open(File.join(dirname, file), 'r') {|f| puts f.read}
end
end
end
end
sc = ServiceCalls.new
sc.get_file
__FILE__ is the path of the current script. File.join uses system independent path separators. File.open, if you pass it a block, will actually close the file for you when it completes the block. String#start_with? is a cleaner way than using [0,1] to get the first element of a string.
try this:
Dir.entries('./json').each do |mFile|
next if ['.', '..'].include?(mFile)
self.sendServiceRequest(mFile)

How do I create directory if none exists using File class in Ruby?

I have this statement:
File.open(some_path, 'w+') { |f| f.write(builder.to_html) }
Where
some_path = "somedir/some_subdir/some-file.html"
What I want to happen is, if there is no directory called somedir or some_subdir or both in the path, I want it to automagically create it.
How can I do that?
You can use FileUtils to recursively create parent directories, if they are not already present:
require 'fileutils'
dirname = File.dirname(some_path)
unless File.directory?(dirname)
FileUtils.mkdir_p(dirname)
end
Edit: Here is a solution using the core libraries only (reimplementing the wheel, not recommended)
dirname = File.dirname(some_path)
tokens = dirname.split(/[\/\\]/) # don't forget the backslash for Windows! And to escape both "\" and "/"
1.upto(tokens.size) do |n|
dir = tokens[0...n]
Dir.mkdir(dir) unless Dir.exist?(dir)
end
For those looking for a way to create a directory if it doesn't exist, here's the simple solution:
require 'fileutils'
FileUtils.mkdir_p 'dir_name'
Based on Eureka's comment.
directory_name = "name"
Dir.mkdir(directory_name) unless File.exists?(directory_name)
How about using Pathname?
require 'pathname'
some_path = Pathname("somedir/some_subdir/some-file.html")
some_path.dirname.mkdir_p
some_path.write(builder.to_html)
Based on others answers, nothing happened (didn't work). There was no error, and no directory created.
Here's what I needed to do:
require 'fileutils'
response = FileUtils.mkdir_p('dir_name')
I needed to create a variable to catch the response that FileUtils.mkdir_p('dir_name') sends back... then everything worked like a charm!
Along similar lines (and depending on your structure), this is how we solved where to store screenshots:
In our env setup (env.rb)
screenshotfolder = "./screenshots/#{Time.new.strftime("%Y%m%d%H%M%S")}"
unless File.directory?(screenshotfolder)
FileUtils.mkdir_p(screenshotfolder)
end
Before do
#screenshotfolder = screenshotfolder
...
end
And in our hooks.rb
screenshotName = "#{#screenshotfolder}/failed-#{scenario_object.title.gsub(/\s+/,"_")}-#{Time.new.strftime("%Y%m%d%H%M%S")}_screenshot.png";
#browser.take_screenshot(screenshotName) if scenario.failed?
embed(screenshotName, "image/png", "SCREENSHOT") if scenario.failed?
The top answer's "core library" only solution was incomplete. If you want to only use core libraries, use the following:
target_dir = ""
Dir.glob("/#{File.join("**", "path/to/parent_of_some_dir")}") do |folder|
target_dir = "#{File.expand_path(folder)}/somedir/some_subdir/"
end
# Splits name into pieces
tokens = target_dir.split(/\//)
# Start at '/'
new_dir = '/'
# Iterate over array of directory names
1.upto(tokens.size - 1) do |n|
# Builds directory path one folder at a time from top to bottom
unless n == (tokens.size - 1)
new_dir << "#{tokens[n].to_s}/" # All folders except innermost folder
else
new_dir << "#{tokens[n].to_s}" # Innermost folder
end
# Creates directory as long as it doesn't already exist
Dir.mkdir(new_dir) unless Dir.exist?(new_dir)
end
I needed this solution because FileUtils' dependency gem rmagick prevented my Rails app from deploying on Amazon Web Services since rmagick depends on the package libmagickwand-dev (Ubuntu) / imagemagick (OSX) to work properly.

Find and replace in a file in Ruby

I have this little program I write in ruby. I found a nice piece of code here, on SO, to find and replace something in a file, but it doesn't seems to work.
Here's the code:
#!/usr/bin/env ruby
DOC = "test.txt"
FIND = /,,^M/
SEP = "\n"
#make substitution
File.read(DOC).gsub(FIND, SEP)
#Check if the line already exist
unique_lines = File.readlines(DOC).uniq
#Save the result in a new file
File.open('test2.txt', 'w') { |f| f.puts(unique_lines) }
Thanks everybody !
I skip the check you make to see if the line already exists and usually go with something like this (here I want to replace 'FOO' with 'BAR'):
full_path_to_read = File.expand_path('~/test1.txt')
full_path_to_write = File.expand_path('~/test2.txt')
File.open(full_path_to_read) do |source_file|
contents = source_file.read
contents.gsub!(/FOO/, 'BAR')
File.open(full_path_to_write, "w+") { |f| f.write(contents) }
end
The use of expand_path is also probably a bit pedantic here, but I like it just so that I don't accidentally clobber some file I didn't mean to.

Ruby unable to use require

This is a newbie question as I am attempting to learn Ruby by myself, so apologies if it sounds like a silly question!
I am reading through the examples of why's (poignant) guide to ruby and am in chapter 4. I typed the code_words Hash into a file called wordlist.rb
I opened another file and typed the first line as require 'wordlist.rb' and the rest of the code as below
#Get evil idea and swap in code
print "Enter your ideas "
idea = gets
code_words.each do |real, code|
idea.gsub!(real, code)
end
#Save the gibberish to a new file
print "File encoded, please enter a name to save the file"
ideas_name = gets.strip
File::open( 'idea-' + ideas_name + '.txt', 'w' ) do |f|
f << idea
end
When I execute this code, it fails with the following error message:
C:/MyCode/MyRubyCode/filecoder.rb:5: undefined local variable or method `code_words' for main:Object (NameError)
I use Windows XP and Ruby version ruby 1.8.6
I know I should be setting something like a ClassPath, but not sure where/how to do so!
Many thanks in advance!
While the top-level of all files are executed in the same context, each file has its own script context for local variables. In other words, each file has its own set of local variables that can be accessed throughout that file, but not in other files.
On the other hand, constants (CodeWords), globals ($code_words) and methods (def code_words) would be accessible across files.
Some solutions:
CodeWords = {:real => "code"}
$code_words = {:real => "code"}
def code_words
{:real => "code"}
end
An OO solution that is definitely too complex for this case:
# first file
class CodeWords
DEFAULT = {:real => "code"}
attr_reader :words
def initialize(words = nil)
#words = words || DEFAULT
end
end
# second file
print "Enter your ideas "
idea = gets
code_words = CodeWords.new
code_words.words.each do |real, code|
idea.gsub!(real, code)
end
#Save the gibberish to a new file
print "File encoded, please enter a name to save the file"
ideas_name = gets.strip
File::open( 'idea-' + ideas_name + '.txt', 'w' ) do |f|
f << idea
end
I think the problem might be that the require executes the code in another context, so the runtime variable is no longer available after the require.
What you could try is making it a constant:
CodeWords = { :real => 'code' }
That will be available everywhere.
Here is some background on variable scopes etc.
I was just looking at the same example and was having the same problem.
What I did was change the variable name in both files from code_words to $code_words .
This would make it a global variable and thus accesible by both files right?
My question is: wouldn't this be a simpler solution than making it a constant and having to write CodeWords = { :real => 'code' } or is there a reason not to do it ?
A simpler way would be to use the Marshal.dump feature to save the code words.
# Save to File
code_words = {
'starmonkeys' => 'Phil and Pete, those prickly chancellors of the New Reich',
'catapult' => 'chucky go-go', 'firebomb' => 'Heat-Assisted Living',
'Nigeria' => "Ny and Jerry's Dry Cleaning (with Donuts)",
'Put the kabosh on' => 'Put the cable box on'
}
# Serialize
f = File.open('codewords','w')
Marshal.dump(code_words, f)
f.close
Now at the beginning of your file you would put this:
# Load the Serialized Data
code_words = Marshal.load(File.open('codewords','r'))
Here's the easy way to make sure you can always include a file that's in the same directory as your app, put this before the require statement
$:.unshift File.dirname(__FILE__)
$: is the global variable representing the "CLASSPATH"

Resources