Stubborn program using readline

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?

--
Wybo

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.

···

On 6/16/05, Wybo Dekker <wybo@servalys.nl> wrote:

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?

--
Wybo

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
   $

···

On Thu, 16 Jun 2005, Logan Capaldo wrote:

--
Wybo

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

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

···

On Fri, 17 Jun 2005 08:41:22 +0900, Wybo Dekker wrote:

On Thu, 16 Jun 2005, Logan Capaldo wrote:

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.

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?

···

On Fri, 17 Jun 2005, Wybo Dekker wrote:

On Thu, 16 Jun 2005, Logan Capaldo wrote:

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.

   #
   # 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

===============================================================================

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!
$

···

On Fri, 17 Jun 2005, Jonathan Paisley wrote:

On Fri, 17 Jun 2005 08:41:22 +0900, Wybo Dekker wrote:

> On Thu, 16 Jun 2005, Logan Capaldo wrote:
>
>> 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.

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

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

--
Wybo

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

···

On Sat, 18 Jun 2005, Ara.T.Howard wrote:

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.

#
# 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!

--
Wybo

[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

···

On Jun 17, 2005, at 9:28 AM, Wybo Dekker wrote:

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

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

···

On Sat, 18 Jun 2005, Wybo Dekker wrote:

On Sat, 18 Jun 2005, Ara.T.Howard wrote:

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:

--

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

===============================================================================

> 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.

···

On Sat, 18 Jun 2005, James Edward Gray II wrote:

On Jun 17, 2005, at 9:28 AM, Wybo Dekker wrote:

#!/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

--
Wybo