stubborn program using readline

W

Wybo Dekker

Hi,

I've been on the list a few days ago with a readline-problem, for which
there doesn't seem to be a quick fix. I'm now trying a dirty fix... but
this seems to be a very stubborn problem.

Here is my original test program

# rltest - test for readline
# run me like this: (i.e. operating on a file)
#
# ruby rltest rltest
#
# and all is fine. But run me like so: (i.e. operating on stdin)
#
# ruby rltest < rltest
#
# and see that I successfully count my own lines,
# then also print the "type return to quit" prompt,
# but finally seem to be looping in the readline function, and
# can only be stopped with ctrl-C,
# ( not even with ctrl-D - I'm not looking at the terminal)

require 'readline'
include Readline

# count lines in stdin (the mail message):
n=0; while gets: n+=1 end
puts "#{n} lines"

readline("type return to quit: ")
puts "quitting..."

I tried to get around this problem by testing if input is from stdin,
and if so, making a temporary copy of stdin and re-running the program
on that copied file:

file = ARGV.shift || ''
if file == ''
require 'tempfile'
f = Tempfile.new('vpp')
name = f.path
puts "Making temporary file: #{name}"
n = 0
while gets
f.puts
n += 1
end
f.close
puts "Wrote #{n} lines"
system($0, name)
else
require 'readline'
include Readline
puts "reading #{file}"
# count lines in stdin (the mail message):
n = 0
open(file).each do
n+=1
end
puts "Saw #{n} lines"

readline("type return to quit: ")
puts "quitting..."
end

Running this with: ruby rltest rltest says:

reading rltest
Saw 30 lines
type return to quit:
quitting...

But to my astonishment, running with: ruby rltest < rltest stays in a
loop, like the original test program:

Making temporary file: /data/tmp/vpp19009.0
Wrote 30 lines
reading /data/tmp/vpp19009.0
Saw 30 lines
type return to quit:
./rltest:28:in `readline': Interrupt
from ./rltest:28

Can anybody explain this?
 
L

Logan Capaldo

Hi,
=20
I've been on the list a few days ago with a readline-problem, for which
there doesn't seem to be a quick fix. I'm now trying a dirty fix... but
this seems to be a very stubborn problem.
=20
Here is my original test program
=20
# rltest - test for readline
# run me like this: (i.e. operating on a file)
#
# ruby rltest rltest
#
# and all is fine. But run me like so: (i.e. operating on stdin)
#
# ruby rltest < rltest
#
# and see that I successfully count my own lines,
# then also print the "type return to quit" prompt,
# but finally seem to be looping in the readline function, and
# can only be stopped with ctrl-C,
# ( not even with ctrl-D - I'm not looking at the terminal)
=20
require 'readline'
include Readline
=20
# count lines in stdin (the mail message):
n=3D0; while gets: n+=3D1 end
puts "#{n} lines"
=20
readline("type return to quit: ")
puts "quitting..."
=20
I tried to get around this problem by testing if input is from stdin,
and if so, making a temporary copy of stdin and re-running the program
on that copied file:
=20
file =3D ARGV.shift || ''
if file =3D=3D ''
require 'tempfile'
f =3D Tempfile.new('vpp')
name =3D f.path
puts "Making temporary file: #{name}"
n =3D 0
while gets
f.puts
n +=3D 1
end
f.close
puts "Wrote #{n} lines"
system($0, name)
else
require 'readline'
include Readline
puts "reading #{file}"
# count lines in stdin (the mail message):
n =3D 0
open(file).each do
n+=3D1
end
puts "Saw #{n} lines"
=20
readline("type return to quit: ")
puts "quitting..."
end
=20
Running this with: ruby rltest rltest says:
=20
reading rltest
Saw 30 lines
type return to quit:
quitting...
=20
But to my astonishment, running with: ruby rltest < rltest stays in a
loop, like the original test program:
=20
Making temporary file: /data/tmp/vpp19009.0
Wrote 30 lines
reading /data/tmp/vpp19009.0
Saw 30 lines
type return to quit:
./rltest:28:in `readline': Interrupt
from ./rltest:28
=20
Can anybody explain this?
=20

Theory: readline reads from STDIN correct? using rltest < rltest means
that STDIN gets EOF, which means STDIN is closed which means readlien
can't read anything from it. I don't think you can have your cake and
eat it too in this case. If you want the interactive prompt your going
to have pass the file name on the command line instead of using IO
redirection. I'm a bit confused as to the purpose of this.
 
W

Wybo Dekker

Theory: readline reads from STDIN correct? using rltest < rltest means
that STDIN gets EOF, which means STDIN is closed which means readlien
can't read anything from it.

No, readline should not necessarily read from STDIN. It can read from the
console, /dev/tty or so.
I don't think you can have your cake and
eat it too in this case. If you want the interactive prompt your going
to have pass the file name on the command line instead of using IO
redirection. I'm a bit confused as to the purpose of this.

What I want is a program that reads data from either a file or STDIN and
then lets the user run various commands on those data.

To be more specific, it's a program (vpp, http://www.servalys.nl/scripts/vpp)
that I have written in Perl and it reads a PDF (or PostScript) file,
displays it, and after this offers the user a command prompt where she can
choose to print some pages, or all pages, in various formats (booklet,
one-sided, two-sided), either to a printer or to an other PDF file.

PDF (or PostScript) files can be offered to it:

vpp some_pdf_or_postscript_file

or piped to it:

vpp <some_pdf_or_postscript_file

The latter is particularly useful if, for example, you want to print
selected pages from a man page. For example:

man -t ruby |vpp

This worked fine in Perl, but can't be done in the Ruby version which
I'm trying to make...

I quess you cannot translate the following Perl script into Ruby

#!/usr/bin/perl -w

# showline - read lines from stdin,
# then let user interrogate lines by number

my @x;
while (<>) {
push(@x,$_);
}

use Term::ReadLine;
my $t=new Term::ReadLine 'vpp';

while ( (my $n = $t->readline("type line number (0 to stop): ")) > 0) {
if ( $n > @x) {
print "no such line\n";
} else {
print "line $n: $x[$n-1]";
}
}

and then run it like so:

$ showline < showline
type line number (0 to stop): 1
line 1: #!/usr/bin/perl -w
type line number (0 to stop): 3
line 3: # showline - read lines from stdin,
type line number (0 to stop): 20
line 20: }
type line number (0 to stop): 21
no such line
type line number (0 to stop): 0
$
 
W

Wybo Dekker

Since you always want to readline from the terminal, try that:

tty = open("/dev/tty","a+")
tty.readline

That's too simple: it does not use the readline library with its editing
and completion facilities.

What I need is a translation of this simple Perl example into Ruby -
I think Ruby can't do this:

#!/usr/bin/perl -w

# showline - read lines from stdin (either file or pipe),
# then let user interrogate lines by number

use Term::ReadLine;

push(@x,$_) while <>;
print scalar(@x)," lines\n";

$t=new Term::ReadLine 'vpp';

while (1) {
$_ = $t->readline("type line number (0 to stop): ");
PROMPT: {
/^\d+$/ or do { print "Not a positive number\n"; last PROMPT};
$_ > @x and do { print "No such line\n"; last PROMPT};
$_ and do { print "line $_: $x[$_-1]"; last PROMPT};
die "thanks!\n";
}
}

Typical run:

$ showline <showline
21 lines
type line number (0 to stop): 1
line 1: #!/usr/bin/perl -w
type line number (0 to stop): 3
line 3: # showline - read lines from stdin (either file or pipe),
type line number (0 to stop): 20
line 20: }
type line number (0 to stop): 30
No such line
type line number (0 to stop): 0
thanks!
$
 
J

James Edward Gray II

What I need is a translation of this simple Perl example into Ruby -
I think Ruby can't do this:

[snip code]

I think it can:

#!/usr/local/bin/ruby -w

require "readline"
include Readline

lines = ARGF.readlines
puts "#{lines.size} lines."

loop do
line = readline("Line number: ", true)

if line !~ /^\d+$/
puts "Please enter a positive integer."
else
number = line.to_i
case number
when 0
break
when 1..lines.size
puts "Line #{number}: #{lines[number - 1]}"
else
puts "No such line."
end
end
end

__END__

My run:

$ ruby showlines.rb showlines.rb
25 lines.
Line number: 1
Line 1: #!/usr/local/bin/ruby -w
Line number: 16
Line 16: case number
Line number: 17
Line 17: when 0
Line number: 18
Line 18: break
Line number: 1003
No such line.
Line number: darn
Please enter a positive integer.
Line number: 0

Hope that helps.

James Edward Gray II
 
W

Wybo Dekker

What I need is a translation of this simple Perl example into Ruby -
I think Ruby can't do this:

[snip code]

I think it can:

Sure, when you run it with a file argument.
But now try to run it on stdin.
For example:

$ showlines.rb < showlines.rb

It won't work. In Perl, it will.
#!/usr/local/bin/ruby -w

require "readline"
include Readline

lines = ARGF.readlines
puts "#{lines.size} lines."

loop do
line = readline("Line number: ", true)

if line !~ /^\d+$/
puts "Please enter a positive integer."
else
number = line.to_i
case number
when 0
break
when 1..lines.size
puts "Line #{number}: #{lines[number - 1]}"
else
puts "No such line."
end
end
end

__END__

My run:

$ ruby showlines.rb showlines.rb
25 lines.
Line number: 1
Line 1: #!/usr/local/bin/ruby -w
Line number: 16
Line 16: case number
Line number: 17
Line 17: when 0
Line number: 18
Line 18: break
Line number: 1003
No such line.
Line number: darn
Please enter a positive integer.
Line number: 0

Hope that helps.

James Edward Gray II
 
A

Ara.T.Howard

No, readline should not necessarily read from STDIN. It can read from the
console, /dev/tty or so.


What I want is a program that reads data from either a file or STDIN and
then lets the user run various commands on those data.

put another way - you want the program to ALWAYS read from EITHER a file or
stdin and you ALWAYS want to interactively (eg. from a tty) run commands after
reading the file - so why not just say that in the code?

#
# program which does the above
#
jib:~ > cat a.rb
require 'readline'
include Readline

inpath = ARGV.shift
infile = inpath ? open(inpath) : STDIN

# count lines in stdin (the mail message):
n=0; while infile.gets: n+=1 end
puts "#{n} lines"

# always get commands interactively
STDIN.reopen '/dev/tty' unless STDIN.tty?
readline("type return to quit: ")
puts "quitting..."

#
# reading interactively from stdin and typing ctrl-D
#
jib:~ > ruby a.rb
one
two
three
3 lines
type return to quit:
quitting...

#
# reading non-interactively from stdin
#
jib:~ > ruby a.rb < a.rb
13 lines
type return to quit:
quitting...

#
# reading from file
#
jib:~ > ruby a.rb a.rb
13 lines
type return to quit:
quitting...


you could take other approaches with dup'ing and forking but this might work
for you.

hth.

-a
--
===============================================================================
| email :: ara [dot] t [dot] howard [at] noaa [dot] gov
| phone :: 303.497.6469
| My religion is very simple. My religion is kindness.
| --Tenzin Gyatso
===============================================================================
 
W

Wybo Dekker

put another way - you want the program to ALWAYS read from EITHER a file or
stdin and you ALWAYS want to interactively (eg. from a tty) run commands after
reading the file - so why not just say that in the code?

#
# program which does the above
#
jib:~ > cat a.rb
require 'readline'
include Readline

inpath = ARGV.shift
infile = inpath ? open(inpath) : STDIN

# count lines in stdin (the mail message):
n=0; while infile.gets: n+=1 end
puts "#{n} lines"

# always get commands interactively
STDIN.reopen '/dev/tty' unless STDIN.tty?
readline("type return to quit: ")
puts "quitting..."

you could take other approaches with dup'ing and forking but this might work
for you.

This did it!!
The essential line is: STDIN.reopen '/dev/tty' unless STDIN.tty?
and I would say that this should actually be done in Readline at the first
call of readline.
I rewrote my Perl program showlines:

#!/usr/bin/env ruby

#
# showlines - read lines from stdin (either file or pipe),
# then let user interrogate lines by number

require 'readline'
include Readline

# store and count lines in stdin:
x = []
while gets: x.push($_) end
puts "#{x.size} lines"

# always get commands interactively
STDIN.reopen '/dev/tty' unless STDIN.tty?
loop do
line = readline("type line number (0 to stop): ",true)
if line !~ /^\d+$/
puts "Please enter a positive integer."
else
number = line.to_i
case number
when 0
puts "quitting..."
exit 0
when 1..x.size
puts "Line #{number}: #{x[number - 1]}"
else
puts "No such line."
end
end
end

and this now works as expected:

$ showlines <showlines
33 lines
type line number (0 to stop): 1
Line 1: #!/usr/bin/env ruby
type line number (0 to stop): 16
Line 16: STDIN.reopen '/dev/tty' unless STDIN.tty?
type line number (0 to stop): 33
Line 33: end
type line number (0 to stop): 133
No such line.
type line number (0 to stop): 0
quitting...
$

Thank you very much!
 
A

Ara.T.Howard

This did it!!
The essential line is: STDIN.reopen '/dev/tty' unless STDIN.tty?
and I would say that this should actually be done in Readline at the first
call of readline.
I rewrote my Perl program showlines:

i think it's a bit more complicated than that because then you could not do

readline_program.rb < commands.txt

which would be bad. i'd have to look into perl module to see what they do -
but i suspect it would also give some suprising behaviour under certain
conditions too... it's just too tough to imagine all the combinations of
tty/stdin one might want in a program... but maybe not.

cheers.

-a
--
===============================================================================
| email :: ara [dot] t [dot] howard [at] noaa [dot] gov
| phone :: 303.497.6469
| My religion is very simple. My religion is kindness.
| --Tenzin Gyatso
===============================================================================
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,769
Messages
2,569,581
Members
45,056
Latest member
GlycogenSupporthealth

Latest Threads

Top