Ruby error: undefined method `[]’ for nil:NilClass - ruby

I am learning Ruby and I have a bug I cannot understand. I have a method that takes an array of strings (lines) and removes all lines up to a certain line containing a pattern.
The method looks as follows:
def removeHeaderLines(lines)
pattern = "..." # Some pattern.
# If the pattern is not there, do not drop any lines.
prefix = lines.take_while {|line| (not line.match(pattern))}
if prefix.length == lines.length then
return prefix
end
# The pattern is there: remove all line preceding it, as well as the line
# containing it.
suffix = (lines.drop_while {|line| (not line.match(pattern))}).drop(1)
return suffix
end
This works fine and the resulting strings (lines) are displayed correctly on a web page
I am generating.
Additionally, I want to remove all non-empty lines following the pattern. I have modified the method as follows:
def removeHeaderLines(lines)
pattern = "..." # Some pattern.
# If the pattern is not there, do not drop any lines.
prefix = lines.take_while {|line| (not line.match(pattern))}
if prefix.length == lines.length then
return prefix
end
# The pattern is there: remove all line preceding it, as well as the line
# containing it.
suffix = (lines.drop_while {|line| (not line.match(pattern))}).drop(1)
# Remove leading non-empty lines.
# ADDING THIS INTRODUCES A BUG.
body = suffix.drop_while {|line| (line != "")}
return body
end
Very surprisingly (at least for me) this does not work. On the generated web page, instead of the content, I see the error message: Liquid error: undefined method `[]’ for nil:NilClass.
I cannot make much sense out of this message. As far as I understand, some code calling my code has tried to access a non-array object as if it were an array. But both versions
of my method return an array of strings (both variables suffix and body are set to an array of strings), so why should there be a difference?
So, unfortunately, also due to my scarce knowledge of Ruby, I have no clue as to how to debug this problem.
Does anybody see any mistake in the above code? Alternatively, does anybody have any hints as to what can cause the error "undefined method `[]’ for nil:NilClass"?
EDIT
Additional information. I am extending code that I have not written myself (it comes from
Octopress, file plugins/include_code.rb). The original
rendering code looks like this:
def render(context)
code_dir = (context.registers[:site].config['code_dir'].sub(/^\//,'') || 'downloads/code')
code_path = (Pathname.new(context.registers[:site].source) + code_dir).expand_path
file = code_path + #file
if File.symlink?(code_path)
return "Code directory '#{code_path}' cannot be a symlink"
end
unless file.file?
return "File #{file} could not be found"
end
Dir.chdir(code_path) do
##################################
# I have replaced the line below #
##################################
code = file.read
#filetype = file.extname.sub('.','') if #filetype.nil?
title = #title ? "#{#title} (#{file.basename})" : file.basename
url = "/#{code_dir}/#{#file}"
source = "<figure class='code'><figcaption><span>#{title}</span> <a href='#{url}'>download</a></figcaption>\n"
source += " #{highlight(code, #filetype)}</figure>"
safe_wrap(source)
end
end
I have replaced the line
code = file.read
with
code = linesToString(removeHeaderLines(stringToLines(file.read)))
where the two missing methods are:
def stringToLines(string)
ar = Array.new
string.each_line {|line| ar.push(line)}
return ar
end
def linesToString(lines)
s = ""
lines.each {|line| s.concat(line)}
return s
end
I hope this helps.
EDIT 2
Thanks to Hassan's hint (use the join method) I have found the problem!
Parallel to the join method there exists a split method. So
"A\nB\n".split(/\n/)
gives
["A", "B"]
Whereas by using each_line (as I did), one gets each line with the '\n' at the end.
As a consequence
suffix.drop_while {|line| (line != "")}
drops all lines. The result was an empty string that apparently crashes the library
I am using. Thanks to Hassan for indicating a more idiomatic solution. I have now the
following:
def removeHeaderLines(code)
lines = code.split(/\r?\n/)
pat = /.../ # Some pattern.
index = lines.index {|line| line =~ pat}
lines = lines.drop(index + 1).drop_while {|line| line != ""} unless index.nil?
lines.join "\n"
end
and it works fine.

That exception occurs when you attempt to use nil like an array (or hash):
irb(main):001:0> nil[0]
NoMethodError: undefined method `[]' for nil:NilClass
from (irb):1
from /home/mslade/rubygems1.9/bin/irb:12:in `<main>'
or use a variable as an array (or hash) when it has not been initialised:
irb(main):005:0> #b[0]
NoMethodError: undefined method `[]' for nil:NilClass
from (irb):5
from /home/mslade/rubygems1.9/bin/irb:12:in `<main>'
Look for where you have neglected to initalize the array, with something like #b = []
If you are not actually using an array, then it's possible that a funciton you are calling expects one. Scan through the stak dump given, starting at the top, until it mentions a line of your code. Then investigate that line to see what you might have missed.

I don't know what cause the exception, but your code could be like this:
def remove_header_lines(lines)
pattern = /some pat/
index = lines.index {|lines| lines =~ pattern}
lines = lines.drop(index+1).drop_while {|lines| line != ""} unless index.nil?
lines.join
end

Related

Ruby want to read file line by line and get the particular part out using gsub

I am writing a code to read a text file or csv file line by line which contain url and i want out the id of each url and print or store in text file but when i do i get an error when i use a loop before loop i am able to get it printed line by line. Can any one help me in this.
Below is the my code sample.
line_num=0
File.open('/Users/divyanshu/python_imdb/channels/url.txt').each do |line|
video_links = "#{line_num += 1} #{line}"
# puts video_links
for video_link in video_links
video_lin = video_link.gsub('https://www.youtube.com/watch?v=', '')
video_di = video_lin.gsub('?utm_source=komparify&utm_campaign=site&utm_medium=detailpage', '')
puts video_di
end
This the error I'm getting
Traceback (most recent call last):
2: from /Users/divyanshu/python_imdb/url_validation.rb:6:in `<main>'
1: from /Users/divyanshu/python_imdb/url_validation.rb:6:in `each'
/Users/divyanshu/python_imdb/url_validation.rb:10:in `block in <main>': undefined method `each' for #<String:0x00007fef3d0907c0> (NoMethodError)
and if I run only this part of code its working fine.
line_num=0
File.open('/Users/divyanshu/python_imdb/channels/url.txt').each do |line|
video_links = "#{line_num += 1} #{line}"
puts video_links
end
Strings Don't Normally #respond_to? :each
The stack trace tells you everything you need to know:
undefined method `each' for #String:0x00007fef3d0907c0 (NoMethodError)
Even assuming that /channels / with a trailing space is a valid portion of the file path, File#open returns a File object rather than a collection of lines. As written, video_links is a String, not a collection such as a Hash or Array, and there's no String#each method. Since for-in loops are syntatic sugar for #each, the object can't respond to the method.
Depending on whether you want to slurp the whole file into an array of lines, or operate linewise, you should use one of the following alternative methods:
File#each_line, inhereted from IO. For example:
File.open("path/to/file").each_line
File#readlines, also inherited from IO. For example:
File.readlines("path/to/file")
Adding on to the answer by #Todd. There is one more issue in your code snippet as I mentioned in my comment. the video_links variable is of class String.
It will contain only the last line of file
You can not iterate over it
For example, a file.txt as below:
one
two
three
four
And code:
File.open('file.txt').each do |line|
var = line
end
puts var
#=> "four"
So, working with your example, you should define your variable video_links as an (empty) Array and append the lines of file to iterate.
Example:
video_links = []
lines_in_file = File.readlines('/Users/divyanshu/python_imdb/channels/url.txt')
lines_in_file.each do |line|
video_links.append line
end
video_links.each do |video_link|
video_lin = video_link.gsub('https://www.youtube.com/watch?v=', '')
video_di = video_lin.gsub('?utm_source=komparify&utm_campaign=site&utm_medium=detailpage', '')
puts video_di
end

Get line number of beginning and end of Ruby method given a ruby file

How can I find the line of the beginning and end of a Ruby method given a ruby file?
Say for example:
1 class Home
2 def initialize(color)
3 #color = color
4 end
5 end
Given the file home.rb and the method name initialize I would like to receive (2,4) which are the beginning and end lines.
Finding the end is tricky. The best way I can think of is to use the parser gem. Basically you'll parse the Ruby code into an AST, then recursively traverse its nodes until you find a node with type :def whose first child is :initialize:
require "parser/current"
def recursive_find(node, &block)
return node if block.call(node)
return nil unless node.respond_to?(:children) && !node.children.empty?
node.children.each do |child_node|
found = recursive_find(child_node, &block)
return found if found
end
nil
end
src = <<END
class Home
def initialize(color)
#color = color
end
end
END
ast = Parser::CurrentRuby.parse(src)
found = recursive_find(ast) do |node|
node.respond_to?(:type) && node.type == :def && node.children[0] == :initialize
end
puts "Start: #{found.loc.first_line}"
puts "End: #{found.loc.last_line}"
# => Start: 2
# End: 4
P.S. I would have recommended the Ripper module from the standard library, but as far as I can tell there's no way to get the end line out of it.
Ruby has a source_location method which gives you the file and the beginning line:
class Home
def initialize(color)
#color = color
end
end
p Home.new(1).method(:initialize).source_location
# => ["test2.rb", 2]
To find the end, perhaps look for the next def or EOF.
Ruby source is nothing but a text file. You can use linux commands to find the method line number
grep -nrw 'def initialize' home.rb | grep -oE '[0-9]+'
I have assumed that the file contains the definition of at most one initialize method (though generalizing the method to search for others would not be difficult) and that the definition of that method contains no syntax errors. The latter assumption is probably required for any method to extract the correct line range.
The only tricky part is finding the line containing end that is the last line of the definition of the initialize method. I've used Kernel#eval to locate that line. Naturally caution must be exercised whenever that method is to be executed, though here eval is merely attempting to compile (not execute) a method.
Code
def get_start_end_offsets(fname)
start = nil
str = ''
File.foreach(fname).with_index do |line, i|
if start.nil?
next unless line.lstrip.start_with?('def initialize')
start = i
str << line.lstrip.insert(4,'_')
else
str << line
if line.strip == "end"
begin
rv = eval(str)
rescue SyntaxError
nil
end
return [start, i] unless rv.nil?
end
end
end
nil
end
Example
Suppose we are searching a file created as follows1.
str = <<-_
class C
def self.feline
"cat"
end
def initialize(arr)
#row_sums = arr.map do |row|
row.reduce do |t,x|
t+x
end
end
end
def speak(sound)
puts sound
end
end
_
FName = 'temp'
File.write(FName, str)
#=> 203
We first search for the line that begins (after stripping leading spaces) "def initialize". That is the line at index 4. The end that completes the definition of that method is at index 10. We therefore expect the method to return [4, 10].
Let's see if that's what we get.
p get_start_end_offsets(FName)
#=> [4, 10]
Explanation
The variable start equals the index of the line beginning def initialize (after removing leading whitespace). start is initially nil and remains nil until the "def initialize" line is found. start is then set to the index of that line.
We now look for a line line such that line.strip #=> "end". This may or may not be the end that terminates the method. To determine if it is we eval a string that contains all lines from the one that begins def initialize to the line equal to end just found. If eval raises a SyntaxError exception that end does not terminate the method. That exception is rescued and nil is returned. eval will return :_initialize (which is truthy) if that end terminates the method. In that case the method returns [start, i], where i is the index of that line. nil is returned if no initialize method is found in the file.
I've converted "initialize" to "_initialize" to suppress the warning (eval):1: warning: redefining Object#initialize may cause infinite loop)
See both answers to this SO question to understand why SyntaxError is being rescued.
Compare indentation
If it is known that "def initialize..." is always indented the same amount as the line "end" that terminates the method definition (and no other lines "end" between the two are indented the same), we can use that fact to obtain the beginning and ending lines. There are many ways to do that; I will use Ruby's somewhat obscure flip-flop operator. This approach will tolerate syntax errors.
def get_start_end_offsets(fname)
indent = -1
lines = File.foreach(fname).with_index.select do |line, i|
cond1 = line.lstrip.start_with?('def initialize')
indent = line.size - line.lstrip.size if cond1
cond2 = line.strip == "end" && line.size - line.lstrip.size == indent
cond1 .. cond2 ? true : false
end
return nil if lines.nil?
lines.map(&:last).minmax
end
get_start_end_offsets(FName)
#=> [4, 10]
1 The file need not contain only code.

Error using yield in Ruby

I am new to ruby and am trying to learn how yield works. I am using yield in the following way to read characters in a file with help of a function. The return value of this function is input to another function which extracts words based on spaces. However, I am getting the following error while execution:
in `block (2 levels) in getchars': no block given (yield) (LocalJumpError)
Here is the code snippet generating error:
def getchars(file)
IO.readlines(file).each {|line| line.each_char {|chrc| yield chrc }}
end
Can someone please help me understand what am I doing wrong? Thanks.
Addition:
This is how I make call:
def getwords(file)
#st_char = true
getchars(file).each {|c|
if #st_char == true
#word = ""
if c.match(/^[[:alnum:]]+$/)
#word = c
#st_char = false
end
else
if c.match(/^[[:alnum:]]+$/)
#word = #word + c
else
#st_char = true
yield #word
end
end
}
end
You need to pass a code block to getchars for it to yield to.
something like,
getchars("example.txt") {|char| puts char}
Then inside your get chars, it will yield each char one at a time to the supplied code block - which simply puts them out.
I think the error message is pretty clear: getchars tries to yield to a block, but whoever called getchars didnt' pass a block.
In line 2 of getwords, you call getchars without passing a block. You need to pass a block to getchars, so it has something to yield to.
file_as_string = File.read("greetings.txt") # => "hey hello hola vanakam"
array_of_words_after_splitting_by_spaces = file_as_string.split(" ") # => ["hey", "hello", "hola", "vanakam"]
Warning: Please don't use such variable names, just used it to explicitly see whats happening

read file into an array excluding the the commented out lines

I'm almost a Ruby-nOOb (have just the knowledge of Ruby to write some basic .erb template or Puppet custom-facts). Looks like my requirements fairly simple but can't get my head around it.
Trying to write a .erb template, where it reads a file (with space delimited lines) to an array and then handle each array element according to the requirements. This is what I got so far:
fname = "webURI.txt"
def myArray()
#if defined? $fname
if File.exist?($fname) and File.file?($fname)
IO.readlines($fname)
end
end
myArray.each_index do |i|
myLine = myArray[i].split(' ')
puts myLine[0] +"\t=> "+ myLine.last
end
Which works just fine, except (for obvious reason) for the line that is commented out or blank lines. I also want to make sure that when spitted (by space) up, the line shouldn't have more than two fields in it; a file like this:
# This is a COMMENT
#
# Puppet dashboard
puppet controller-all-local.example.co.uk:80
# Nagios monitoring
nagios controller-all-local.example.co.uk::80/nagios
tac talend-tac-local.example.co.uk:8080/org.talend.admin
mng console talend-mca-local.example.co.uk:8080/amc # Line with three fields
So, basically these two things I'd like to achieve:
Read the lines into array, stripping off everything after the first #
Split each element and print a message if the number id more than two
Any help would be greatly appreciated. Cheers!!
Update 25/02
Thanks guy for your help!!
The blankthing doesn't work for at all; throwing in this error; but I kinda failed to understand why:
undefined method `blank?' for "\n":String (NoMethodError)
The array: myArray, which I get is actually something like this (using p instead of puts:
["\n", "puppet controller-all-local.example.co.uk:80\n", "\n", "\n", "nagios controller-all-local.example.co.uk::80/nagios\n", ..... \n"]
Hence, I had to do this to get around this prob:
$fname = "webURI.txt"
def myArray()
if File.exist?($fname) and File.file?($fname)
IO.readlines($fname).map { |arr| arr.gsub(/#.*/,'') }
end
end
# remove blank lines
SSS = myArray.reject { |ln| ln.start_with?("\n") }
SSS.each_index do |i|
myLine = SSS[i].split(' ')
if myLine.length > 2
puts "Too many arguments!!!"
elsif myLine.length == 1
puts "page"+ i.to_s + "\t=> " + myLine[0]
else
puts myLine[0] +"\t=> "+ myLine.last
end
end
You are most welcome to improve the code. cheers!!
goodArray = myArray.reject do |line|
line.start_with?('#') || line.split(' ').length > 2
end
This would reject whatever that either starts with # or the split returns an array of more than two elements returning you an array of only good items.
Edit:
For your inline commenting you can then do
goodArray.map do |line|
line.gsub(/#.*/, '')
end

loop, array and file problem in ruby

I'm currently learning ruby and here what I'm trying to do:
A script which open a file, make a subsitution, then comparing every lines to each other to see if it exist many times.
So, I tried to work directly with the string, but I didn't find how to do it, so I put every line in an array, and comparing every row.
But I got a first problem.
Here is my code:
#!/usr/bin/env ruby
DOC = "test.txt"
FIND = /,,^M/
SEP = "\n"
#make substitution
puts File.read(DOC).gsub(FIND, SEP)
#open the file and put every line in an array
openFile = File.open(DOC, "r+")
fileArray = openFile.each { |line| line.split(SEP) }
#print fileArray #--> give the name of the object
#Cross the array to compare every items to every others
fileArray.each do |items|
items.chomp
fileArray.each do |items2|
items2.chomp
#Delete if the item already exist
if items = items2
fileArray.delete(items2)
end
end
end
#Save the result in a new file
File.open("test2.txt", "w") do |f|
f.puts fileArray
end
At the end, I only have the name of the array object "fileArray". I print the object after the split, and i've got the same, so I guess the problem is from here. Little help required (if you know how to do this without array, just with the line in the file, answer appreciate too).
Thanks !
EDIT:
So, here's my code now
#!/usr/bin/env ruby
DOC = "test.txt"
FIND = /,,^M/
SEP = "\n"
#make substitution
File.read(DOC).gsub(FIND, SEP)
unique_lines = File.readlines(DOC).uniq
#Save the result in a new file
File.open('test2.txt', 'w') { |f| f.puts(unique_lines) }
Can't figure out how to chomp this.
Deleting duplicate lines in a file:
no_duplicate_lines = File.readlines("filename").uniq
No need to write so much code :)
Modify your code like this:
f.puts fileArray.join("\n")
Alternate way:
unique_lines = File.readlines("filename").uniq
# puts(unique_lines.join("\n")) # Uncomment this line and see if the variable holds the result you want...
File.open('filename', 'w') {|f| f.puts(unique_lines.join("\n"))}
Just a couple of points about the original code:
fileArray = openFile.each { |line| line.split(SEP) }
sets fileArray to a File object, which I suspect wasn't your intention. File#each (the # notation is Ruby convention to describe a particular method on an object of the supplied class) executes your supplied block for each line (it's also available with a synonym: each_line), where a line is defined by default as your OS's end-line character(s).
If you were looking to build an array of lines, then you could just have written
fileArray = openFile.readlines
and if you wanted those lines to be chomped (often a good idea) then that could be achieved by something like
fileArray = openFile.readlines.collect { |line| line.chomp }
or even (since File mixes in Enumerable)
fileArray = openFile.collect { |line| line.chomp }
And one other tiny thing: Ruby tests for equality with ==, = is only for assignment, so
if items = items2
will set items to items2 (and will always evaluate as true)

Resources