views:

1905

answers:

2

I have a simple task that needs to wait for something to change on the filesystem (it's essentially a compiler for prototypes). So I've a simple infinite loop with a 5 second sleep after the check for changed files.

loop do
  # if files changed
  #   process files
  #   and puts result
  sleep 5
end

Instead of the Ctrl+C salute, I'd rather be able to test and see if a key has been pressed, without blocking the loop. Essentially I just need a way to tell if there are incoming key presses, then a way to grab them until a Q is met, then exit out of the program.

What I want is:

def wait_for_Q
  key_is_pressed && get_ch == 'Q'
end

loop do
  # if files changed
  #   process files
  #   and puts result
  wait_for_Q or sleep 5
end

Or, is this something Ruby just doesn't do (well)?

+3  A: 

Here's one way to do it, using IO#read_nonblock:

def quit?
  begin
    # See if a 'Q' has been typed yet
    while c = STDIN.read_nonblock(1)
      puts "I found a #{c}"
      return true if c == 'Q'
    end
    # No 'Q' found
    false
  rescue Errno::EINTR
    puts "Well, your device seems a little slow..."
    false
  rescue Errno::EAGAIN
    # nothing was ready to be read
    puts "Nothing to be read..."
    false
  rescue EOFError
    # quit on the end of the input stream
    # (user hit CTRL-D)
    puts "Who hit CTRL-D, really?"
    true
  end
end

loop do
  puts "I'm a loop!"
  puts "Checking to see if I should quit..."
  break if quit?
  puts "Nope, let's take a nap"
  sleep 5
  puts "Onto the next iteration!"
end

puts "Oh, I quit."

Bear in mind that even though this uses non-blocking IO, it's still buffered IO. That means that your users will have to hit Q then <Enter>. If you want to do unbuffered IO, I'd suggest checking out ruby's curses library.

rampion
Sadly I'm on windows, and this throws an Errno::EBADF, or bad-file error. I'll investigate my options.
The Wicked Flea
Try capturing the EBADF with the EINTR and the EAGAIN- it may just be a transitory error until you actually type some input (not sure, not on windows)
rampion
Can I do the same on C or PHP or Perl?? any code out there?
Enjoy coding
Yep. Ruby's just using read (see man 2 read) to do this, which is native C, and PHP or Perl no doubt wrap.
rampion
A: 

You may also want to investigate the 'io/wait' library for Ruby which provides the ready? method to all IO objects. I haven't tested your situation specifically, but am using it in a socket based library I'm working on. In your case, provided STDIN is just a standard IO object, you could probably quit the moment ready? returns a non-nil result, unless you're interested in finding out what key was actually pressed. This functionality can be had through require 'io/wait', which is part of the Ruby standard library. I am not certain that it works on all environments, but it's worth a try. Rdocs: http://ruby-doc.org/stdlib/libdoc/io/wait/rdoc/

Ian