Ruby: Writing to file in subdirectory - ruby

I am trying to write to a file inside a subdirectory. This file is created by the code but, once the file is created, it is empty after the script finishes its execution. What am I doing wrong?
# Creating output files
print "Creating output files and filling root menu..."
FileUtils.cd(outdir) do
file = File.new("directory.xml", "w")
file.puts "<?php header(\"Content-type: text/xml\"); ?>"
file.puts "<CiscoIPPhoneMenu>"
file.puts "<Title>Telefonbuch</Title>"
file.puts "<Prompt>Dir External</Prompt>"
letters_used.each do |letter|
filename = "contacts_" + letter + ".xml"
FileUtils.touch(filename)
file.puts "<MenuItem>"
file.puts "<Name>" + letter.upcase + "</Name>"
file.puts "<URL>http://" + HOSTNAME + WEBSERV_DIR + "/" + filename + "</URL>"
file.puts "</MenuItem>"
end
file.puts "</CiscoIPPhoneMenu>"
file.rewind
end
print "Done\n"
"directory.xml" should link to each "contacts_letter.xml" file, which is created by the script too, however directory.xml is empty. Why?

Idiomatic Ruby would write to the file using a block:
File.new("directory.xml", "w") do |fo|
fo.puts "<?php header(\"Content-type: text/xml\"); ?>"
fo.puts "<CiscoIPPhoneMenu>"
fo.puts "<Title>Telefonbuch</Title>"
fo.puts "<Prompt>Dir External</Prompt>"
letters_used.each do |letter|
filename = "contacts_" + letter + ".xml"
FileUtils.touch(filename)
fo.puts "<MenuItem>"
fo.puts "<Name>" + letter.upcase + "</Name>"
fo.puts "<URL>http://" + HOSTNAME + WEBSERV_DIR + "/" + filename + "</URL>"
fo.puts "</MenuItem>"
end
fo.puts "</CiscoIPPhoneMenu>"
end
This closes the file automatically at the end of the block.

Related

Remove concrete string from CSV in Ruby

I got a ruby script which take XML files and create from them CSV. This CSV use semicolons as delimiter -> but, content of XML contains these tags:
- &
- <
- >
And this of course break the structure of CSV file. I need clean it up. This cleaner must be writen in Ruby. I try next code, but this complete destroy the file.
#Clean up CSV file
#Remove: & \< >
file_names = ['terms.csv']
file_names.each do |file_name|
text = File.read(file_name)
new_contents = text.gsub(/&/, " and ")
# To merely print the contents of the file, use:
puts new_contents
# To write changes to the file, use:
File.open(file_name, "w") {|file| file.puts new_contents }
end
file_names.each do |file_name|
text = File.read(file_name)
new_contents = text.gsub(/</, " < ")
puts new_contents
File.open(file_name, "w") {|file| file.puts new_contents }
end
file_names.each do |file_name|
text = File.read(file_name)
new_contents = text.gsub(/>/, " > ")
puts new_contents
File.open(file_name, "w") {|file| file.puts new_contents }
end
I never use Ruby - this is my first contact. Is there better way how to do this?
I solved it... I change CSV delimiter from ";" to "#" in FOR cycle which create a CSV file. It is not ideal solution, but it works.

Ruby mulitple conditional statment write to same file twice?

I am trying to create a find and replace script in ruby. But I cannot figure out how to write to the same file twice when there are two conditions matched (2 different regex patterns are found and need to be replaced in the same file) I can get it to provide 2 copies of the file concatonated with only changes made from one condition in each.
Here is my code (Specifically pattern3 and pattern4):
print "What extension do you want to modify? "
ext = gets.chomp
if ext == "py"
print("Enter password: " )
pass = gets.chomp
elsif ext == "bat"
print "Enter drive letter: "
drive = gets.chomp
print "Enter IP address and Port: "
ipport = gets.chomp
end
pattern1 = /'Admin', '.+'/
pattern2 = /password='.+'/
pattern3 = /[a-zA-Z]:\\(?i:dir1\\dir2)/
pattern4 = /http:\/\/.+:\d\d\d\d\//
Dir.glob("**/*."+ext).each do |file|
data = File.read(file)
File.open(file, "w") do |f|
if data.match(pattern1)
match = data.match(pattern1)
replace = data.gsub(pattern1, '\''+pass+'\'')
f.write(replace)
puts "File " + file + " modified " + match.to_s
elsif data.match(pattern2)
match = data.match(pattern2)
replace = data.gsub(pattern2, 'password=\''+pass+'\'')
f.write(replace)
puts "File " + file + " modified " + match.to_s
end
if data.match(pattern3)
match = data.match(pattern3)
replace = data.gsub(pattern3, drive+':\dir1\dir2')
f.write(replace)
puts "File " + file + " modified " + match.to_s
if data.match(pattern4)
match = data.match(pattern4)
replace = data.gsub(pattern4, 'http://' + ipport + '/')
f.write(replace)
puts "File " + file + " modified " + match.to_s
end
end
end
end
f.truncate(0) makes things better but truncates the first line since it concatonates from the end of the 1st modified portion of the file.
Try writing file only once after all substitutions:
print "What extension do you want to modify? "
ext = gets.chomp
if ext == "py"
print("Enter password: " )
pass = gets.chomp
elsif ext == "bat"
print "Enter drive letter: "
drive = gets.chomp
print "Enter IP address and Port: "
ipport = gets.chomp
end
pattern1 = /'Admin', '.+'/
pattern2 = /password='.+'/
pattern3 = /[a-zA-Z]:\\(?i:dir1\\dir2)/
pattern4 = /http:\/\/.+:\d\d\d\d\//
Dir.glob("**/*.#{ext}").each do |file|
data = File.read(file)
data.gsub!(pattern1, "'#{pass}'")
data.gsub!(pattern2, "password='#{pass}'")
data.gsub!(pattern3, "#{drive}:\\dir1\\dir2")
data.gsub!(pattern4, "http://#{ipport}/")
File.open(file, 'w') {|f| f.write(data)}
end

Ruby write to file exception

I'm having some troubles with a Ruby script I wrote to unzip an archive and then make files.
script.rb:66: syntax error, unexpected unary+, expecting '}'
...images/pages/"+ filename.to_s +".jpg', alt=''"
#fileList.each do |filename|
File.open('templates/layouts/_partials/page-' + filename.to_s + '.jade', 'w') { |file|
file.puts ".c-page\n"
file.puts " img(src='images/pages/"+ filename.to_s +".jpg', alt=''"
}
end
If I do this:
File.open('templates/layouts/_partials/page-' + filename.to_s + '.jade', 'w') { |file|
file.puts ".c-page\n"
file.puts " img(src='images/pages/"
file.puts filename.to_s
file.puts ".jpg', alt=''"
}
I don't have the result I want, because it prints a new line every time.
Try this
#fileList.each do |filename|
File.open("templates/layouts/_partials/page-#{filename.to_s}.jade", 'w') do |file|
file.puts ".c-page"
file.puts " img(src='images/pages/#{filename.to_s}.jpg', alt=''"
end
end
puts adds a new line character so I see no need for the first one and string interpolation will work better than appending with the + sign. Another note that I try and hold to is using {} syntax for single line and do..end for multi-line blocks. It makes it cleaner and easier to read.
You need space between a string and a plus sign:
pry(main)> " img(src='images/pages/"+ filename.to_s +".jpg', alt=''"
SyntaxError: unexpected unary+, expecting end-of-input
" img(src='images/pages/"+ filename.to_s +".jpg', alt=''"
and this works:
pry(main)> " img(src='images/pages/" + filename.to_s + ".jpg', alt=''"
=> " img(src='images/pages/whatever.jpg', alt=''"
Or just use string interpolation, as engineersmnky suggested in the comment.
"string"+ is treaten as running unary operator + on the string object.

Split a text file in ruby

I have a text file with several different sections. Each section has a header followed by the actual data. For example:
Header1
x,y,z
x,y,z
x,y,z
Header2
a,b,c
a,b,c
a,b,c
I want to read through the file in one pass and do different things with the data present under each section. I know how to parse the data, but I'm having trouble figuring out how to code the logic for "Do this until hitting Header2, then do something else until Header3, etc."
I'm using ruby, and I haven't really come across any examples of doing this. Any suggestions?
At the simplest you could do something like this:
# Process lines for header1
def do_header1(line)
puts line.split(/,/).join("|")
end
# Process lines for header2
def do_header2(line)
puts line.split(/,/).map{ |e| e.upcase}.join(",")
end
header1 = false
header2 = false
# Main loop
File.open("file.txt").each_line do |line|
if line.chomp == 'Header1' # or whatever match for header1
header1 = true
header2 = false
next
end
if line.chomp == 'Header2' # or whatever match for header2
header1 = false
header2 = true
next
end
do_header1(line) && next if header1
do_header2(line) && next if header2
end
If the number of headers becomes too high, you can start tracking headers with an integer:
header = -1
# Main loop
File.open("file.txt").each_line do |line|
if line.chomp == 'Header1' # or whatever match for header1
header = 1
next
end
if line.chomp == 'Header2' # or whatever match for header2
header = 2
next
end
do_header1(line) && next if header == 1
do_header2(line) && next if header == 2
end
A solution using objects. For each line you ask each parser if a new section has started that the parser can parse.
class Section1Parser
def section? potential_header
potential_header.chomp == 'Header1'
end
def parse line
puts "Section 1: #{line.split(/,/).join("|")}"
end
end
class Section2Parser
def section? potential_header
potential_header.chomp == 'Header2'
end
def parse line
puts "Section 2: #{line.split(/,/).join("|")}"
end
end
parsers = [Section1Parser.new, Section2Parser.new]
selected_parser = nil
File.open("c:\\temp\\file.txt").each_line do |line|
if new_parser_detected = parsers.detect {|p| p.section? line }
selected_parser = new_parser_detected
next # skip header
end
selected_parser.parse line if selected_parser
end
Would something like this work?
File.open('datafile').each_line do |s|
if s =~ /^headerpattern$/
#Start a new parsing block
...
else
#Parse data
...
end
end
In my case 'Header' was in form of following string OBJECT ObjectType ObjectNumber ObjectName
if File.exist?("all.txt") then
object_file = File
File.open("all.txt").each_line do |line|
file_name = case
when line.match('^OBJECT Table.*')
"TAB" + line.split[2] + ".TXT"
when line.match('^OBJECT Form.*')
"FOR" + line.split[2] + ".TXT"
when line.match('^OBJECT Report.*')
"REP" + line.split[2] + ".TXT"
when line.match('^OBJECT Dataport.*')
"DAT" + line.split[2] + ".TXT"
when line.match('^OBJECT XMLPort.*')
"XML" + line.split[2] + ".TXT"
when line.match('^OBJECT Codeunit.*')
"COD" + line.split[2] + ".TXT"
when line.match("^OBJECT MenuSuite.*")
"MEN" + line.split[2] + ".TXT"
when line.match('^OBJECT Page.*')
"PAG" + line.split[2] + ".TXT"
when line.match('^OBJECT Query.*')
"QUE" + line.split[2] + ".TXT"
end
unless file_name.nil?
File.exist?(file_name) { File.delete(file_name) }
object_file = File.open(file_name,"w")
end
object_file.write(line)
end
end
But there are some prerequisites: I'm always sure that first line of the file will contain a header. I'm also not closing file (this will definitely draw my karma to the zero one day).

Too much nesting in ruby?

Surely there must be a better way of doing this:
File.open('Data/Networks/to_process.txt', 'w') do |out|
Dir['Data/Networks/*'].each do |f|
if File.directory?(f)
File.open("#{f}/list.txt").each do |line|
out.puts File.basename(f) + "/" + line.split(" ")[0]
end
end
end
end
Cheers!
You can rid of 1 level of nesting by utilizing Guard Clause pattern:
File.open('Data/Networks/to_process.txt', 'w') do |out|
Dir['Data/Networks/*'].each do |f|
next unless File.directory?(f)
File.open("#{f}/list.txt").each do |line|
out.puts File.basename(f) + "/" + line.split(" ")[0]
end
end
end
See Jeff Atwood's article on this approach.
IMHO there's nothing wrong with your code, but you could do the directory globbing and the check from the if in one statement, saving one level of nesting:
Dir.glob('Data/Networks/*').select { |fn| File.directory?(fn) }.each do |f|
...
end
Since you're looking for a particular file in each of the directories, just let Dir#[] find them for you, completely eliminating the need to check for a directory. In addition, IO#puts will accept an array, putting each element on a new line. This will get rid of another level of nesting.
File.open('Data/Networks/to_process.txt', 'w') do |out|
Dir['Data/Networks/*/list.txt'] do |file|
dir = File.basename(File.dirname(file))
out.puts File.readlines(file).map { |l| "#{dir}/#{l.split.first}" }
end
end
Reducing the nesting a bit by separating the input from the output:
directories = Dir['Data/Networks/*'].find_all{|f| File.directory?(f)}
output_lines = directories.flat_map do |f|
output_lines_for_directory = File.open("#{f}/list.txt").map do |line|
File.basename(f) + "/" + line.split(" ")[0]
end
end
File.open('Data/Networks/to_process.txt', 'w') do |out|
out.puts output_lines.join("\n")
end

Resources