Good morning.
I need a hint on reading from STDIN and the IOCTL options. This may or may not be Ruby-specific. Sorry for the long text. The whole code is available, selected code-sections are quoted below.
Context: I wrote a small Ruby-program to visualize multi-spreadsheet
workbooks in a terminal. Deriving the mime-type of the spreadsheet content
allows me to choose a wrapper-instance adapted for ODS, XLS or XLSX-files. The
external module (“Ruby-Gem” Roo) which does most of the work, however
insists on the file-extension to correspond to the type of the chosen
wrapper.., meaning: I know from the mime-type, that I need an ODS-wrapper,
but when my file does not have an *.ods extension, an exception is raised and
that's it.
This is normally not a problem. Calls like the following
Code:
user@machine:~/[path]/viewworkbook_trunk/test$ ../bin/viewworkbook.rb ./some_spreadsheet_file.xls
produce output like in the screen shot, below.
Edit: The issue with the file-extension is just an inconvenience
and *
not* the core problem that I seek help for in this discussion. It
does, though, impose code which interferes with the function that pipes-in
data from STDIN.
In the screen shot, PSE note the line of options,
below the table. The program is
interactive, in that it lets the user move
through the sheets in the workbook, by name or number, adapt the width of the
table-columns or save a single table or the whole workbook to a new file.
Reproducing the error: This program shall be used in a macro to quickly open
email-attachments from Mutt, my email-client. For the macro, I must be able to
pipe-in the file-content to the Ruby-program. As the wrapper class only works
with files of the right filename-extension, I must first redirect the
spreadsheet content to a temporary file, give it the right extension, then
open it just like any other spreadsheet-file.
Here my program crashes after the generation of the table. in the terminal, I
see the complete, first spreadsheet from the workbook and the user options
(like in the screen shot). The following exception is raised when the program
starts to wait for user-input:
Code:
/[path]/viewworkbook_trunk/lib/user_input.rb:30:in `raw': Inappropriate ioctl for device (Errno::ENOTTY)
from /[path]/viewworkbook_trunk/lib/user_input.rb:30:in `wait_for_user'
from /[path]/viewworkbook_trunk/lib/menu.rb:80:in `show'
[...]
I show you some of what I consider relevant code because it reads from STDIN.
As the difference in the working and the failing calls to my program is in the
way, that spreadsheet content is read, there is clearly something missing in
my handling the standard input device, and probably something which is
important in any such routine, independent of the programming language used.
Code
Here is the code from the main class, which “pipes in” spreadsheet content:
Code:
# verify that the programm argument is '-'
if args.length == 1 && args[0] == '-'
# not all the code is really needed for
# the current function to work. But I leave it here
# for authenicity.
args.compact!
args.shift
# First, I verified, that STDIN can be read.
# @log.debug('file piped-in: ' << $<.read)
tfile = nil
# Verify that there is data on STDIN
if(!$<.eof?)
@log.debug('reading from STDIN')
# ... provide a temporary file in that case,
tfile = Tempfile.new('spreadsheet_temp')
File.open(tfile, 'w+') {|of| of << $<.read}
@log.debug("Temporary file %s is written." %File.path(tfile))
wb = tfile
end
end
After this, the workbook is entirely stored in the file ‘wb’ which still has
no file-extension, but is named something like
“/tmp/spreadsheet_temp20160827-5802-1s6jlg1”.
For the recognition of spreadsheet types, I defined the Mime-types for use
with the magic library, via the Ruby Gem “FileMagic”.
Code:
ODS_MIME = "application/vnd.oasis.opendocument.spreadsheet"
XLS_MIME = "application/vnd.ms-excel"
XLSX_MIME = "application/zip"
CSV_MIME = "text/plain"
The wrapper is generated and the file-extension is added to the temporary
file, if needed in a (“static”) factory class.
Code:
def self::workbook(file)
@@file = file
if !@@workbook
begin
@@workbook = case mime_type
when ODS_MIME
@@log.debug('ODS')
unless File.extname(@@file) == '.ods'
odsfile = @@file.dup << '.ods'
File.rename(@@file, odsfile)
@@file = odsfile
end
# create the OpenOffice instance
Roo::OpenOffice.new(File.path(@@file)
when XLS_MIME
(...)
when XLSX_MIME
(...)
Then, the menu is created below the table, and we wait for user input, when
the program crashes:
Code:
require 'io/wait'
require 'io/console'
def wait_for_user()
@log.debug('wait_for_user() ')
char = STDIN.raw(&:getc)
# Here is another way to code the non-blocking
# read from STDIN which usually works, but fails in
# the exact same way after a spreadsheet has been
# piped-in
=begin
char = nil
STDIN.raw do
STDIN.noecho do
until (STDIN.ready?)
sleep(0.1)
end
char = (STDIN.read_nonblock(1).ord rescue nil)
end
end
=end
return char
end
The whole program in its current development-version 0.0.5 is available as a Ruby-gem,
containing all the code, from rubygem.org, either from the site:
https://rubygems.org/gems/viewworkbook
or by installing it with the gem-tool:
Code:
gem install viewworkbook
This is enough to see the complete code. To run the program, you also need the
Roo and the FileMagic gems.