How to take the result from another method - ruby

I have a directory structure with sub-directories:
../../../../../MY_PROJECT/TEST_A/cats/
../../../../../MY_PROJECT/TEST_B/dogs/
../../../../../MY_PROJECT/TEST_A/tigers/
../../../../../MY_PROJECT/TEST_A/elephants/
each of which has a file that ends with ".sln":
../../../../../MY_PROJECT/TEST_A/cats/cats.sln
../../../../../MY_PROJECT/TEST_B/dogs/dogs.sln
...
These files contain information specific to their directory. I would like to do the following:
Create a file "myfile.txt" within each sub-directory, and write some strings to them:
../../../../../MY_PROJECT/TEST_A/cats/myfile.txt
../../../../../MY_PROJECT/TEST_B/dogs/myfile.txt
../../../../../MY_PROJECT/TEST_A/tigers/myfile.txt
../../../../../MY_PROJECT/TEST_A/elephants/myfile.txt
Copy a specific string in the ".sln" files to the myfile.txt of certain directories using the following method:
def parse_sln_files
sln_files = Dir["../../../../../MY_PROJECT/TEST_*/**/*.sln"]
sln_files.each do |file_name|
File.open(file_name) do |f|
f.each_line { |line|
if line =~ /C Source files ="..\\/ #"
path = line.scan(/".*.c"/)
puts path
end
}
end
end
end
I would like to do something like this:
def create_myfile
Dir['../../../../../MY_PROJECT/TEST_*/*/'].each do |dir|
File.new File.join(dir, 'myfile.txt'), 'w+'
Dir['../../../../../TEST/TEST_*/*/myfile.txt'].each do |path|
File.open(path,'w+') do |f|
f.puts "some text...."
f.puts "some text..."
f.puts # here I would like to return the result of parse_sln_files
end
end
end
end
Any suggestions on how to express this?

It seems like you want to read list of C file names from a Visual C++ Solution file, and store in a separate file in the same directory. You may have to merge the two loops that you have shown in your code, and do something like this:
def parse_sln_and_store_source_files
sln_files = Dir["../../../../../MY_PROJECT/TEST_*/**/*.sln"]
sln_files.each do |file_name|
#### Lets collect source file names in this array
source_file_names = []
File.open(file_name) do |f|
f.each_line { |line|
if line =~ /C Source files ="..\\/ #"
path = line.scan(/".*.c"/)
############ Add path to array ############
source_file_names << path
end
}
end
#### lets create `myfile.txt` in same dir as that of .sln
test_file = File.expand_path(File.dirname(file_name)) + "/myfile.txt"
File.open(test_file,'w+') do |f|
f.puts "some text...."
f.puts "some text..."
##### Iterate over source file names & write to file
source_file_names.each { |n| f.puts n }
end
end
end
This can be done bit more elegantly with few more refactoring. Also note that this is not tested code, hopefully, you get the gist of what I am suggesting.

Related

output lines to separate line in ruby

I have a file where I search for specific lines, like this:
<ClCompile Include="..\..\..\Source\fileA.c" />
<ClCompile Include="..\..\..\Tests\fileB.c" />
In my script I can find this lines and extract only the path string between the double qoutes . When I find them, I save it to an array (which I use later in my code). It looks like this:
source_path_array = []
File.open(file_name) do |f|
f.each_line {|line|
if line =~ /<ClCompile Include="..\\/
source_path = line.scan(/".*.c"/)
###### Add path to array ######
source_path_array << source_path
end
}
end
So far, everything OK. Later in my script I output the array within an other file to a line "Source Files":
f.puts "Source Files= #{source_path_array.flatten.join(" ")}"
The result is than like this:
Source Files= "..\..\..\Source\fileA.c" "..\..\..\Tests\fileB.c"
I would like to have the output in this form:
Source Files=..\..\..\Source\fileA.c
Source Files=..\..\..\Tests\fileB.c
As you can see, each path in an separate line with the string "Source Files" before and also without double quotes. Any idea? Maybe my concept with the array is also not the best.
Don't use #join, then. Use #each or #map. Also, you can use #gsub to remove the quotes:
source_path_array.flatten.each do |path|
f.puts "Source Files=#{path.gsub(/(^"|")$/, '')}"
end
or
f.puts source_path_array.flatten.map do |path|
"Source Files=#{path.gsub(/(^"|")$/, '')}"
end.join("\n")
The second version is probably more I/O efficient.
For this to work (and as an answer to the second part of your question), the source_path_array should contain strings. Here's a way to obtain this:
regex = /<ClCompile Include="(\.\.\\[^"]+)/
File.open(file_name) do |f|
f.each_line do |line|
regex.match(line) do |matches|
source_path_array << matches[1]
end
end
end
If you don't mind reading the entire file in memory at once, this is slightly shorter:
regex = /<ClCompile Include="(\.\.\\[^"]+)/
File.read(file_name).split(/(\r?\n)+/).each do |line|
regex.match(line) do |matches|
source_path_array << matches[1]
end
end
Finally, here's an example using Nokogiri:
require 'nokogiri'
source_path_array = File.open(file_name) do |f|
Nokogiri::XML(f)
end.css('ClCompile[Include^=..\\]').map{|el| el['Include']}
All of these parse out the quotes, so you can remove the #gsub from the first portion.
All together now:
require 'nokogiri'
f.puts File.open(file_name) do |source|
Nokogiri::XML(source)
end.css('ClCompile[Include^=..\\]').map do |el|
"Source Files=#{el['Include']}"
end.join("\n")
and let's not loop twice (#map then #join) when once (a single #reduce) is doable:
require 'nokogiri'
f.puts File.open(file_name) do |source|
Nokogiri::XML(source)
end.css('ClCompile[Include^=..\\]').reduce('') do |memo, el|
memo += "Source Files=#{el['Include']}\n"
end.chomp
Thanks to #FĂ©lix Saparelli:
The following worked for me:
source_path_array.flatten.each do |path|
f.puts "Source Files=#{path.delete('"')}"
end

Find a specific text in a file, cut it from the current file and then paste it another file

I am trying find a certain piece of code in a .rb file, once found I want to cut it from the current file and then paste it into another existing file. So an an example:
file1.rb has the following:
RSpec.describe 'Get Test Data' do
it "should get test data for build" do |example|
log_start_test("#{example.description}")
get_test_data
log_complete_test("#{example.description}")
end
end
I want to find it "should get test data for build" do |example| and then cut this piece of code:
it "should get test data for build" do |example|
log_start_test("#{example.description}")
get_test_data
log_complete_test("#{example.description}")
end
and paste it another file.
So far I have been able to find the desired string using something like this:
File.open("#{Dir.pwd}/spec/api/test_data_search_spec.rb") do |f|
f.each_line do |line|
if line =~ /do |example|/
puts "Found root #{line}"
end
end
end
Just not able to figure out the exact regular expression to find the required block and then how do i do a cut from a file and paste into another one? Any ideas would be great.
regular expressions are not suited to parse code.
you could use method_source as an existing solution to the problem.
Thanks #phoet. That would have worked for specific methods but I was looking more for moving rspec example code block around. But here is what I ended up using as an example:
def move_example_block(example_description, source_file,destination_file)
lines = File.readlines("#{Dir.pwd}/spec/api/#{source_file}_spec.rb")
desired_block = lines.join[/it "should get test data for build" do(.*)end/m]
temp = desired_block.freeze
puts temp
filename = "#{Dir.pwd}/spec/api/#{source_file}_spec.rb"
text = File.read(filename)
puts = text.gsub(/it "#{example_description}" do(.*)end/m, "end")
File.open(filename, "w") { |file| file << puts }
filename = "#{Dir.pwd}/spec/api/#{destination_file}_spec.rb"
text = File.read(filename)
puts = text.gsub(/end/, temp)
File.open(filename, "w") { |file| file << puts }
end

Line replacement in directories clears the whole files?

I am trying to recursively replace a whole line from index.html files into a directory with sub-directories.
The code above puts the right lines I'm searching with the var "pattern", but when I run it, it removes everything form my index.html files.
pattern = "Keyword"
replacement = "<td width=\"30\"><img src=\"styles/img/trans.gif\" width=\"30\"></td>"
Dir.glob('/Users/root/Desktop/directory/test/**/index.html') do |item|
next unless File.file?(item)
File.open(item, "w+:ASCII-8BIT") do |f|
f.each_line do |line|
if line.match(pattern)
my_line = line
line.sub(my_line, replacement)
end
end
end
end
What am I doing wrong ?
You need to read the file first, build the expected output, and then write it:
Dir.glob('/Users/root/Desktop/directory/test/**/index.html') do |item|
next unless File.file?(item)
output = IO.readlines(item).map do |line|
if line.match(pattern)
replacement
else
line
end
end
File.open(item, "w+:ASCII-8BIT") do |f|
f.write output.join
end
end
end
You use File.open with open mode w+ which, according to Ruby documentation, is:
"w+" Read-write, truncates existing file to zero length or creates a new file for reading and writing.
To read the file and put some lines use r:
File.open(item, "r:ASCII-8BIT")

Script to append files

I am trying to write a script to do the following:
There are two directories A and B. In directory A, there are files called "today" and "today1". In directory B, there are three files called "today", "today1" and "otherfile".
I want to loop over the files in directory A and append the files that have similar names in directory B to the files in Directory A.
I wrote the method below to handle this but I am not sure if this is on track or if there is a more straightforward way to handle such a case?
Please note I am running the script from directory B.
def append_data_to_daily_files
directory = "B"
Dir.entries('B').each do |file|
fileName = file
next if file == '.' or file == '..'
File.open(File.join(directory, file), 'a') {|file|
Dir.entries('.').each do |item|
next if !(item.match(/fileName/))
File.open(item, "r")
file<<item
item.close
end
#file.puts "hello"
file.close
}
end
end
In my opinion, your append_data_to_daily_files() method is trying to do too many things -- which makes it difficult to reason about. Break down the logic into very small steps, and write a simple method for each step. Here's a start along that path.
require 'set'
def dir_entries(dir)
Dir.chdir(dir) {
return Dir.glob('*').to_set
}
end
def append_file_content(target, source)
File.open(target, 'a') { |fh|
fh.write(IO.read(source))
}
end
def append_common_files(target_dir, source_dir)
ts = dir_entries(target_dir)
ss = dir_entries(source_dir)
common_files = ts.intersection(ss)
common_files.each do |file_name|
t = File.join(target_dir, file_name)
s = File.join(source_dir, file_name)
append_file_content(t, s)
end
end
# Run script like this:
# ruby my_script.rb A B
append_common_files(*ARGV)
By using a Set, you can easily figure out the common files. By using glob you can avoid the hassle of filtering out the dot-directories. By designing the code to take its directory names from the command line (rather than hard-coding the names in the script), you end up with a potentially re-usable tool.
My solution....
def append_old_logs_to_daily_files
directory = "B"
#For each file in the folder "B"
Dir.entries('B').each do |file|
fileName = file
#skip dot directories
next if file == '.' or file == '..'
#Open each file
File.open(File.join(directory, file), 'a') {|file|
#Get each log file from the current directory in turn
Dir.entries('.').each do |item|
next if item == '.' or item == '..'
#that matches the day we are looking for
next if !(item.match(fileName))
#Read the log file
logFilesToBeCopied = File.open(item, "r")
contents = logFilesToBeCopied.read
file<<contents
end
file.close
}
end
end

In Ruby- Parsing Directory and reading first row of the file

Below is the piece of code that is supposed read the directory and for each file entry prints the first row of the file. The issue is x is not visible so file is not being parsed.
Dir.foreach("C:/fileload/src") do |file_name|
x = file_name
puts x
f = File.open("C:/fileload/src/" +x)
f.readlines[1..1].each do |line|
puts line
end
end
Why are you assigning x to file_name? You can use file_name directly. And if you are only reading the first line of the file, why not try this?
#!/usr/bin/ruby
dir = "C:/fileload/src"
Dir.foreach(dir) do |file_name|
full = File.join(dir, file_name)
if File.file?(full)
f = File.open(full)
puts f.first
f.close
end
end
You should use File.join to safely combine paths in Ruby. I also checked that you are opening a file using the File.file? method.
You have no visibility issue with x. You should be using File::join or Pathname#+ to build your file paths. You should exclude non-files from consideration. You're selecting the second line, not the first with [1..1]. Here's a cleaner correct replacement for your sample code.
dir = "C:/fileload/src"
Dir.foreach(dir).
map { |fn| File.join(dir,fn) }.
select { |fn| File.file?(fn) }.
each { |fn| puts File.readlines(fn).first }

Resources