How to create a IO object from a file? - ruby

Another seems stupid question, but haven't found a clear example.
I need a IO object as parameter for a function, actually its the new function in this class:
I used this way , but seems some problems when using a file descriptor as a IO after wrote something:
irb(main):001:0> f= File.open("result.txt","w")
=> #<File:result.txt>
irb(main):002:0> i=IO.new(f.to_i,"w")
=> #<IO:0x3b5cb90>
irb(main):003:0> i.write "hello the world"
=> 15
irb(main):004:0> i.close
=> nil
irb(main):005:0> f.close
Errno::EBADF: Bad file descriptor - result.txt
from (irb):5:in `close'
from (irb):5
from :0
So I only need to close either i or f once? or there is a standard way to do this?

FILE is a subclass of IO
irb(main):001:0> File.superclass
=> IO
In your case, i and f refer to the same object. Hence the observation. You can use the File object for TestRunner.

Couldn't you do something like this:
File.open("result.txt", 'w') do |f|
t = TestRunner.new(your_suite, NORMAL, f)
t.start
end
This will start the test running using the result.txt file io object. It will automatically close the file even if an exception occurs.

Related

The size of data written to stream obtained from IO#pipe

I'm using IO#pipe method and at some point I want to know how many bytes have already been written into the output stream. Using IO#tell causes error which isn't much helpful - Errno::EINVAL: Invalid argument - it doesn't say what is invalid where.
There is a trivial code snippet to demonstrate the issue:
read_io, write_io = IO.pipe
write_io.tell #=> #<Errno::EINVAL: Invalid argument>
Is there a way to write my own tell method? Or what am I doing wrong?
irb(main):007:0> read_io, write_io = IO.pipe
=> [#<IO:fd 9>, #<IO:fd 10>]
irb(main):008:0> read_io.tell
Errno::ESPIPE: Illegal seek
from (irb):8:in `tell'
from (irb):8
from /usr/bin/irb:11:in `<main>'
irb(main):009:0> write_io.tell
Errno::ESPIPE: Illegal seek
from (irb):9:in `tell'
from (irb):9
from /usr/bin/irb:11:in `<main>'
On my Ruby console, it returns ESPIPE and not EINVAL, which is expected because tell is implemented using lseek in Unix environments, which returns ESPIPE if the file is a pipe.
We can measure the amount of data written/read to the pipe by keeping an counter that accumulates results of read() and write() calls on the pipe.

What does TCPSocket#each iterate over in ruby?

I'm not too familiar with Ruby, so I wasn't able to find the documentation for this method.
When calling each on a TCPSocket object, like this
require "socket"
srv = TCPServer.new("localhost", 7887)
skt = srv.accept
skt.each {|arg| p arg}
Does the block get called once per tcp packet, once per line (after each '\n' char), once per string (after after each NUL/EOF), or something different entirely?
TL;DR TCPSocket.each will iterate for each newline delimited \n string it receives.
More details:
A TCPSocket is just a BasicSocket with some extra powder sugar on top. And a BasicSocket is a child of IO class. The IO class is just a stream of data; thus, it is iterable. And that is where you can find how each is defined for TCPSocket.
Fire up an irb console and enter your line of code with the $stdin socket to see how each behaves. They both inherit from IO. Here is an example of what happens:
irb(main):011:0> $stdin.each {|arg| p arg + "."}
hello
"hello\n."
But to directly answer the question, the block is called once per \n character. If your client is sending data 1 character at a time then the block is not going to be executed until it sees the \n.
Here is a quick sample client to show this:
irb(main):001:0> require 'socket'
=> true
irb(main):002:0> s = TCPSocket.open("localhost", 7887)
=> #<TCPSocket:fd 9>
irb(main):003:0> s.puts "hello"
=> nil
irb(main):007:0> s.write "hi"
=> 2
irb(main):008:0> s.write ", nice to meet you"
=> 18
irb(main):009:0> s.write "\n"
=> 1
And here is what the server printed out:
"hello\n"
"hi, nice to meet you\n" # note: this did not print until I sent "\n"

Retrieve a file in Ruby

So what I am trying to do is pass a file name into a method and and check if the file is closed. What I am struggling to do is getting a file object from the file name without actually opening the file.
def file_is_closed(file_name)
file = # The method I am looking for
file.closed?
end
I have to fill in the commented part. I tried using the load_file method from the YAML module but I think that gives the content of the file instead of the actual file.
I couldn't find a method in the File module to call. Is there a method maybe that I don't know?
File#closed? returns whether that particular File object is closed, so there is no method that is going to make your current attempted solution work:
f1 = File.new("test.file")
f2 = File.new("test.file")
f1.close
f1.closed? # => true # Even though f2 still has the same file open
It would be best to retain the File object that you're using in order to ask it if it is closed, if possible.
If you really want to know if your current Ruby process has any File objects open for a particular path, something like this feels hack-ish but should mostly work:
def file_is_closed?(file_name)
ObjectSpace.each_object(File) do |f|
if File.absolute_path(f) == File.absolute_path(file_name) && !f.closed?
return false
end
end
true
end
I don't stand by that handling corner cases well, but it seems to work for me in general:
f1 = File.new("test.file")
f2 = File.new("test.file")
file_is_closed?("test.file") # => false
f1.close
file_is_closed?("test.file") # => false
f2.close
file_is_closed?("test.file") # => true
If you want to know if any process has the file open, I think you'll need to resort to something external like lsof.
For those cases where you no longer have access to the original file objects in Ruby (after fork + exec, for instance), a list of open file descriptors is available in /proc/pid/fd. Each file there is named for the file descriptor number, and is a symlink to the opened file, pipe, or socket:
# Returns hash in form fd => filename
def open_file_descriptors
Hash[
Dir.glob( File.join( '/proc', Process.pid.to_s, 'fd', '*' ) ).
map { |fn| [File.basename(fn).to_i, File.readlink(fn)] rescue [nil, nil] }.
delete_if { |fd, fn| fd.nil? or fd < 3 }
]
end
# Return IO object for the named file, or nil if it's not open
def io_for_path(path)
fd, fn = open_file_descriptors.find {|k,v| path === v}
fd.nil? ? nil : IO.for_fd(fd)
end
# close an open file
file = io_for_path('/my/open/file')
file.close unless file.nil?
The open_file_descriptors method parses the fd directory and returns a hash like {3 => '/my/open/file'}. It is then a simple matter to get the file descriptor number for the desired file, and have Ruby produce an IO object for it with for_fd.
This assumes you are on Linux, of course.

How can I convert an IO object to a string in Ruby?

I'm working with an IO object (some STDOUT output text), and I'm trying to convert it to a string so that I can do some text processing. I would like to do something like this:
my_io_object = $stdout
#=> #<IO:<STDOUT>>
my_io_object.puts('hi') #note: I know how to make 'hi' into a string, but this is a simplified example
#=>hi
my_io_object.to_s
I have tried a few things and gotten a few errors:
my_io_object.read
#=> IOError: not opened for reading
my_io_object.open
#=> NoMethodError: private method `open' called for #<IO:<STDOUT>>
IO.read(my_io_object)
#=> TypeError: can't convert IO into String
I've read through the IO class methods, and I can't figure out how to manipulate the data in that object. Any suggestions?
I solved this by directing my output to a StringIO object instead of STDOUT:
> output = StringIO.new
#<StringIO:0x007fcb28629030>
> output.puts('hi')
nil
> output.string
"hi\n"
STDOUT accepts strings, it does not provide strings. You can write to it, but cannot read from it.
STDOUT.write("hello") # => hello
STDOUT.read # => IOError: not opened for reading

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