def retry_until_true_or_timeout(
      sleep_interval, timeout_secs, output_stream = $stdout, predicate = nil)

    test_preconditions = -> do

      # Method signature has changed from:
      # (predicate, sleep_interval, timeout_secs, output_stream = $stdout)
      # to:
      # (sleep_interval, timeout_secs, output_stream = $stdout, predicate = nil)
      #
      # Test to see that when old signature is used, a descriptive error is raised.
      #
      # This test should be removed when we go to version 1.0.
      if sleep_interval.respond_to?(:call)
        raise ArgumentError.new('Sorry, method signature has changed to: ' \
              '(sleep_interval, timeout_secs, output_stream = $stdout, predicate = nil).' \
              '  Also a code block can now be provided instead of a lambda.')
      end

      if block_given? && predicate
        raise ArgumentError.new('Both a predicate lambda and a code block were specified.' \
            '  Please specify one or the other but not both.')
      end
    end

    test_preconditions.()

    success = false
    start_time = Time.now
    end_time = start_time + timeout_secs

    text_generator = ->() do
      now = Time.now
      elapsed = now - start_time
      to_go = end_time - now
      '%9.3f   %9.3f' % [elapsed, to_go]
    end
    status_updater = ::TrickBag::Io::TextModeStatusUpdater.new(text_generator, output_stream)

    loop do

      break if Time.now >= end_time

      success = !! (block_given? ? yield : predicate.())
      break if success

      status_updater.print
      sleep(sleep_interval)
    end
    output_stream.print "\n"
    success
  end
def retry_until_true_or_timeout(
      sleep_interval, timeout_secs, output_stream = $stdout, predicate = nil)

    test_preconditions = -> do

      # Method signature has changed from:
      # (predicate, sleep_interval, timeout_secs, output_stream = $stdout)
      # to:
      # (sleep_interval, timeout_secs, output_stream = $stdout, predicate = nil)
      #
      # Test to see that when old signature is used, a descriptive error is raised.
      #
      # This test should be removed when we go to version 1.0.
      if sleep_interval.respond_to?(:call)
        raise ArgumentError.new('Sorry, method signature has changed to: ' \
              '(sleep_interval, timeout_secs, output_stream = $stdout, predicate = nil).' \
              '  Also a code block can now be provided instead of a lambda.')
      end

      if block_given? && predicate
        raise ArgumentError.new('Both a predicate lambda and a code block were specified.' \
            '  Please specify one or the other but not both.')
      end
    end

    test_preconditions.()

    success = false
    start_time = Time.now
    end_time = start_time + timeout_secs

    text_generator = ->() do
      now = Time.now
      elapsed = now - start_time
      to_go = end_time - now
      '%9.3f   %9.3f' % [elapsed, to_go]
    end
    status_updater = ::TrickBag::Io::TextModeStatusUpdater.new(text_generator, output_stream)

    loop do

      break if Time.now >= end_time

      success = !! (block_given? ? yield : predicate.())
      break if success

      status_updater.print
      sleep(sleep_interval)
    end
    output_stream.print "\n"
    success
  end
Calls a predicate proc repeatedly, sleeping the specified interval
 between calls, returning if the predicate returns true,
 and giving up after the specified number of seconds.
 Displays elapsed and remaining times on the terminal.
 Outputs the time elapsed and the time to go.

 @param predicate something that can be called with .() or .call
   that returns a truthy value that indicates no further retries are necessary
 @param sleep_interval  number of seconds (fractions ok) to wait between tries
 @param timeout_secs maximum number of seconds (fractions ok) during which to retry
 @return true if/when the predicate returns true, false if it times out

 Ex: TrickBag::Timing.retry_until_true_or_timeout(->{false}, 1.0, 5)

 Example Code:

 require 'trick_bag'
 predicate = -> { false }
 print "Waiting 10 seconds for true to be returned (but it won't):\n"
 TrickBag::Timing.retry_until_true_or_timeout(predicate, 1, 10)