LRTHW exercise 20 strange output - ruby

For this exercise I wrote all the code perfectly and even double checked by literally copying and pasting the code from the book (Learn Ruby the Hard Way). For some reason when I call the print_a_line() function, it won't print out the current_line argument I pass through until the 3rd call where it will print out 3 before the line. Is there some flushing of the IO stream I am missing out on here or some nuance with Powershell?
I am running Ruby 2.0.0p576 (x64) on Windows 8 64 bit machine.
Code:
input_file = ARGV.first
def print_all(f)
puts f.read
end
def rewind(f)
f.seek(0)
end
def print_a_line(line_count, f)
puts "#{line_count}, #{f.gets.chomp}"
end
current_file = open(input_file)
puts "First let's print the whole file:\n"
print_all(current_file)
puts "Now let's rewind, kind of like a tape."
rewind(current_file)
puts "Let's print three lines:"
current_line = 1
print_a_line(current_line, current_file)
current_line = current_line + 1
print_a_line(current_line, current_file)
current_line = current_line + 1
print_a_line(current_line, current_file)

I deleted the test.txt file and recreated it from scratch and this made the code work perfectly. I am not 100% as to why, but I have a hunch that from reusing that file in many different tests and playing around in Powershell I had inadvertently left some strange formatting in the file causing it to behave differently.

Related

Passing an gets.chomp as an argument

input_file = ARGV.first
def print_all(f)
puts f.read
end
def rewind(f)
f.seek(0)
end
def print_a_line(line_count, f)
puts "#{line_count}, #{f.gets.chomp}"
end
current_file = open(input_file)
puts "First let's print the whole file:¥n"
print_all(current_file)
puts "Let's rewind kind a like a tape"
rewind(current_file)
puts "Let's print three lines:"
current_line = 1
print_a_line(current_line, current_file)
current_line += 1
print_a_line(current_line, current_file)
I'm sure there is a kinda similar post to this, but my question is a bit different. As seen above, the print_a_line method got two params that are line_count and f.
1) As I understood, line_count argument only serves as a variable which is current_line and it is just an integer. How does it relate to the rewind(f) method because when I run the code, the method print_a_line shows this:
1, Hi
2, I'm a noob
where 1 is the first line and 2 is the second. line_count is just a number, how does ruby know that 1 is line 1 and 2 is line 2?
2) Why use gets.chomp in method print_a_line? If I pass just f like this
def print_a_line(line_count, f)
puts "#{line_count}, #{f}"
end
I'll get a crazy result which is
1, #<File:0x007fccef84c4c0>
2, #<File:0x007fccef84c4c0>
Because IO#gets reads next line from readable I/O stream(in this case it's a file) and returns a String object when reading successfully and not reached end of the file. And, chomp removes carriage return characters from String object.
So when you have a file with content such as:
This is file content.
This file has multiple lines.
The code will print:
1, This is file content.
2, This file has multiple lines.
In the second case, you're passing file object itself and not reading it. Hence you see those objects in output.

undefined method `chomp' for nil:NilClass (NoMethodError)

I have entered the following code from Zed Shaw's book on "Learning Ruby the Hard Way
input_file = ARGV.first #this takes the file test.txt
def print_all(f) #reading a line
puts f.read
end
def rewind(f)
f.seek(0)
end
def print_a_line(line_count, f)
current_line
puts "#{line_count}, #{f.gets.chomp}"
end
current_file = open(input_file)
puts "First let's print the whole file:\n"
print_all(current_file)
puts "Now let's rewind, kind of like a tape"
rewind(current_file)
puts "Let's print three line:"
current_line = 1
print_a_line(current_line, current_file)
current_line = current_line + 1
print_a_line(current_line, current_file)
current_line = current_line + 1
print_a_line(current_line, current_file)
The error I am getting is 'ex20.rb:12:in print_a_line': undefined method chomp' for nil:NilClass (NoMethodError)
from ex20.rb:31:in `'
Any help would be greatly appreciated. I have followed his example word by word.
You have to add a few more lines to the test.txt file (at least three lines of text for each of the method calls you do at the end).
I ran across the same issue because the lesson isn't exactly clear about it, but since the script prints out three lines in a row, you need 3 lines of text in the file for the script to work.
add more lines to your test.txt file

Printing out one line of a file in a Ruby script

I am going through "Learn Ruby the Hard Way" and I came across the method print_a_line in exercise 20.
input_file = ARGV.first
current_file = open(input_file)
def print_a_line(line_count, f)
puts "#{line_count}, #{f.gets.chomp}"
end
current_line = 1
print_a_line(current_line, current_file)
current_line = current_line + 1
print_a_line(current_line, current_file)
current_line = current_line + 1
print_a_line(current_line, current_file)
This method is about to take the current line count and output to the terminal only the contents of the file from that line. I don't understand how the method knows to print the line of the file that is associated with current_line. When I look at this I would think #{f.gets.chomp) would return the entire contents of they file. How does the method know to look at the current_line and print out the associated line of the file?
The gets method being called in print_a_line reads a single line from the file (not the entire contents). The File object referenced by current_file keeps track of the current position within the file, so each time gets is called, the next line is returned.
Nothing is looking at current_line to determine which line to read.

What does IO do?

What does the (0, IO::SEEK_SET) do in method rewind? I understand the rest of the code, but I am honestly stuck at the (0, IO::SEEK_SET).
input_file = ARGV[0]
def print_all(f)
puts f.read()
end
def rewind(f)
f.seek(0, IO::SEEK_SET)
end
def print_a_line(line_count, f)
puts "#{line_count} #{f.readline()}"
end
current_file = File.open(input_file)
puts "First let's print the whole file:"
puts # a blank line
print_all(current_file)
puts "Now let's rewind, kind of like a tape."
rewind(current_file)
puts "Let's print three lines:"
current_line = 1
print_a_line(current_line, current_file)
current_line = current_line + 1
print_a_line(current_line, current_file)
current_line = current_line + 1
print_a_line(current_line, current_file)
seek(amount, whence=IO::SEEK_SET) → 0
Seeks to a given offset amount in the stream according to the value of whence:
:CUR or IO::SEEK_CUR | Seeks to _amount_ plus current position
----------------------+--------------------------------------------------
:END or IO::SEEK_END | Seeks to _amount_ plus end of stream (you
| probably want a negative value for _amount_)
----------------------+--------------------------------------------------
:SET or IO::SEEK_SET | Seeks to the absolute location given by _amount_
http://www.ruby-doc.org/core-2.1.2/IO.html#method-i-seek
When you specify a position to seek to, there are typically three ways to do this
SEEK_SET: from the start of the file (ie: absolute)
SEEK_CUR: from the current position (ie: relative)
SEEK_END: from the end of the file
So in your case the rewind method simply seeks to the beginning of the file.

Printing an ASCII spinning "cursor" in the console

I have a Ruby script that does some long taking jobs. It is command-line only and I would like to show that the script is still running and not halted. I used to like the so called "spinning cursor" in the old days and I managed to reproduce it in Ruby under Windows.
Question: does this work in the other OS's? If not, is there an OS-independent way to accomplish this?
No IRB solutions please.
10.times {
print "/"
sleep(0.1)
print "\b"
print "-"
sleep(0.1)
print "\b"
print "\\"
sleep(0.1)
print "\b"
print "|"
sleep(0.1)
print "\b"
}
Yes, this works on Windows, OS X, and Linux. Improving on Niklas' suggestion, you can make this more general like so:
def show_wait_cursor(seconds,fps=10)
chars = %w[| / - \\]
delay = 1.0/fps
(seconds*fps).round.times{ |i|
print chars[i % chars.length]
sleep delay
print "\b"
}
end
show_wait_cursor(3)
If you don't know how long the process will take, you can do this in another thread:
def show_wait_spinner(fps=10)
chars = %w[| / - \\]
delay = 1.0/fps
iter = 0
spinner = Thread.new do
while iter do # Keep spinning until told otherwise
print chars[(iter+=1) % chars.length]
sleep delay
print "\b"
end
end
yield.tap{ # After yielding to the block, save the return value
iter = false # Tell the thread to exit, cleaning up after itself…
spinner.join # …and wait for it to do so.
} # Use the block's return value as the method's
end
print "Doing something tricky..."
show_wait_spinner{
sleep rand(4)+2 # Simulate a task taking an unknown amount of time
}
puts "Done!"
This one outputs:
Doing something tricky...|
Doing something tricky.../
Doing something tricky...-
Doing something tricky...\
(et cetera)
Doing something tricky...done!
# First define your chars
pinwheel = %w{| / - \\}
# Rotate and print as often as needed to "spin"
def spin_it
print "\b" + pinwheel.rotate!.first
end
EDIT from peter: here a working version
def spin_it(times)
pinwheel = %w{| / - \\}
times.times do
print "\b" + pinwheel.rotate!.first
sleep(0.1)
end
end
spin_it 10
I wrote a gem spin_to_win that displays a spinner while yielding a block. For example:
SpinToWin.with_spinner('Zzzz') do |spinner|
spinner.banner('sleepy')
sleep 1
end
Zzzz \ [sleepy]
It can also track work pending vs. work completed:
SpinToWin.with_spinner('Zzzz') do |spinner|
spinner.increment_todo!(3)
spinner.banner('snore')
sleep 1
spinner.increment_done!
spinner.banner('dream')
sleep 1
spinner.increment_done!
spinner.banner('wake up!')
sleep 1
spinner.increment_done!
end
Zzzz \ 3 of 3 [wake up!]
I use rainbow gem to print color-changing string to indicate the code is working.
require 'rainbow'
def self.print_with_random_color
content = "I am still working on it. Please wait..."
colors = ["aqua","chartreuse","crimson","fuchsia","gold","lawngreen","palegoldenrod","powderblue","sandybrown","deepskyblue"]
loop do
print Rainbow("[#{Time.now}] " + content).send(colors.sample)
(content.length + 1).times {print "\r"}
sleep 0.3
end
end

Resources