How can I convert an IO object to a string in Ruby? - 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

Related

Why does open('uri').read delete the response data?

If I open a URI and read a the response as follows:
response = open("https://www.example.com")
result = response.read
That works fine, but if I then call response.read again an empty string is returned. This seems like odd behavior. Why is this the case?
It's because OpenURI is returning a Tempfile object, which is a special implementation of the File class:
A Tempfile objects behaves just like a File object, and you can perform all the usual file operations on it: reading data, writing data, changing its permissions, etc. So although this class does not explicitly document all instance methods supported by File, you can in fact call any File instance method on a Tempfile object.
And a File class' parent is an IO object. Which means when you call read you're calling an IO implementation of the method.
What all of this means is that you're reading a file when doing response.read and you're reading until end of file. Which is why you're getting an empty string when you do a second read, because you're trying to read from the end of file, which has nothing.
Here's one way to examine this and see what's going on:
require 'open-uri'
response = open('http://google.com')
puts response.class # => Tempfile
puts response.read # => <!doctype html><html ...
puts response.pos # => 10941
puts response.read # => ""
response.rewind
puts response.pos # => 0
puts response.read # => <!doctype html><html ...

Ruby: how to parse e-mail mime from the stdin

Anyone know how I can parse the E-mail MIME from Ruby when the input is from STDIN? I have found a gem called "mail" which looks really nice but so far as I can see it's able to read a mail only from the file:
require 'mail'
mail = Mail.read('/path/to/message.eml')
This is how I'm testing it:
# First one is from stdin:
require 'mail'
mail = Mail.new($stdin.readlines)
p mail.from
p mail.to
returns:
nil
nil
# This one is from the file:
mail2 = Mail.new("my_email.txt")
p mail2.from
p mail2.to
returns:
nil
nil
# The same file but I'm using Mail.read:
mail3 = Mail.read("my_email.txt")
p mail3.from
p mail3.to
returns:
["test#gmail.com"]
["test2#gmail.com"]
STDIN I'm testing like:
# cat my_email.txt | ruby code.rb
So the method Mail.read works fine with my e-mail, but when I want to put my stdin to the Mail.read I have:
mail/mail.rb:176:in `initialize': no implicit conversion of Array into String (TypeError)
I know that I can save that file from stdin to the temp folder and then open it by Mail.read but I don't want to do this in that way since it will be slow.
The full url to the gem is: https://github.com/mikel/mail
readlines returns an array... of lines! You might want to try read on ARGF which is the input provided by $stdin instead.
mail = Mail.new ARGF.read
See the relevant documentation.
IO#readlines produces an array. You want a string there:
# readmail.rb
require 'mail'
mail = Mail.new(STDIN.read)
p mail.from
p mail.to
# mail.eml
From: bob#test.lindsaar.net
To: mikel#test.lindsaar.net
Subject: This is an email
This is the body
# command line
$ ruby readmail.rb < mail.eml
# => ["bob#test.lindsaar.net"]
# => ["mikel#test.lindsaar.net"]

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.

Read file while preserving format and type

How do I preserve the format and type of the original data stored in a file?
stored_response = File.open('spec/data/response.txt', 'rb') { |f| f.read }
item.get_location.should == stored_response
#get_location being an api call
response.txt contains this but returns this once read
It doesn't escape the characters in the string itself, it just escapes them when you inspect the string (which is what irb does when it prints the result).
Try using puts to inspect the data.
irb:001:0> contents = File.read("data.json")
=> "[990]\n"
irb:002:0> puts contents
[990]
=> nil
If you want to work with json, why not open it as json?
JSON.parse(IO.read("data.json"))
You won't see any serialization artifacts of that JSON File then.

How to create a IO object from a file?

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.

Resources