14

I have a Ruby script that does some long taking jobs. It is command-line only and I would like to show that the script is still running and not halted. I used to like the so called "spinning cursor" in the old days and I managed to reproduce it in Ruby under Windows.

Question: does this work in the other OS's? If not, is there an OS-independent way to accomplish this?

No IRB solutions please.

10.times {
  print "/"
  sleep(0.1)
  print "\b"
  print "-"
  sleep(0.1)
  print "\b"
  print "\\"
  sleep(0.1)
  print "\b"
  print "|"
  sleep(0.1)
  print "\b"
}
Phrogz
  • 296,393
  • 112
  • 651
  • 745
peter
  • 41,770
  • 5
  • 64
  • 108
  • 2
    It would work on all important OSs I know of, unless the printed characters happen to cause a line break. – Niklas B. Apr 21 '12 at 19:28
  • 1
    Works on Linux, Ruby 1.8.7... – Michael Berkowski Apr 21 '12 at 19:28
  • 8
    By the way, this is equivalent to `('/-\\|' * 10).each { |c| print c; sleep(0.1); print "\b" }` – Niklas B. Apr 21 '12 at 19:30
  • 3
    Hi Niklas, thanks. One error in your equivalent though, it must be each_char – peter Apr 21 '12 at 19:51
  • if no one reports a problem in a major os i would also accept it as an answer, but give it a few days to be sure. has any of you an idea how to print the old DOS chars 176 to 178 (filled cursor with gradients) in ruby ? might be a console progress-bar this way – peter Apr 21 '12 at 21:55
  • @peter That sounds like a great candidate for a separate question. (I'd recommend [these](http://www.fileformat.info/info/unicode/char/2591/index.htm) [Unicode](http://www.fileformat.info/info/unicode/char/2592/index.htm) [characters](http://www.fileformat.info/info/unicode/char/2593/index.htm), but you may have an issue with these on Windows. The code page of your console will be an issue.) – Phrogz Apr 21 '12 at 22:21
  • '@Phrogz: you'r right, i'll make another question, gives the guys here a jumpstart to answering that one first 8>) – peter Apr 21 '12 at 23:19

4 Answers4

37

Yes, this works on Windows, OS X, and Linux. Improving on Niklas' suggestion, you can make this more general like so:

def show_wait_cursor(seconds,fps=10)
  chars = %w[| / - \\]
  delay = 1.0/fps
  (seconds*fps).round.times{ |i|
    print chars[i % chars.length]
    sleep delay
    print "\b"
  }
end

show_wait_cursor(3)

If you don't know how long the process will take, you can do this in another thread:

def show_wait_spinner(fps=10)
  chars = %w[| / - \\]
  delay = 1.0/fps
  iter = 0
  spinner = Thread.new do
    while iter do  # Keep spinning until told otherwise
      print chars[(iter+=1) % chars.length]
      sleep delay
      print "\b"
    end
  end
  yield.tap{       # After yielding to the block, save the return value
    iter = false   # Tell the thread to exit, cleaning up after itself…
    spinner.join   # …and wait for it to do so.
  }                # Use the block's return value as the method's
end

print "Doing something tricky..."
show_wait_spinner{
  sleep rand(4)+2 # Simulate a task taking an unknown amount of time
}
puts "Done!"

This one outputs:

Doing something tricky...|
Doing something tricky.../
Doing something tricky...-
Doing something tricky...\ 
(et cetera)
Doing something tricky...done!
Phrogz
  • 296,393
  • 112
  • 651
  • 745
  • wow Phrogz, this is beautiful, solves allready most of the implementation. What about your statement it works on WOL, were you able to test it ? do you have virtual machines or how dit you test it ? – peter Apr 21 '12 at 22:10
  • @peter You tested on Windows; I tested on OS X and Ubuntu. – Phrogz Apr 21 '12 at 22:18
  • @peter Note that I've updated the threaded code to be far simpler and cleaner. – Phrogz Apr 22 '12 at 03:36
  • 1
    That `yield.tap` block should be the `ensure` part of the function. Also, you can use `"|/-\\".chars.cycle` to get the character stream, instead of your verbose modulo logic: `"|/-\\".chars.cycle.take(seconds.to_i*fps).each { |c| ... }` – Niklas B. Apr 22 '12 at 13:20
  • 2
    Cool combined with [this question on SO](http://stackoverflow.com/questions/2685435/cooler-ascii-spinners) to get spinner characters and [this one](http://stackoverflow.com/questions/17324656/how-do-i-type-any-unicode-character-into-sublime-text) to find Unicode characters. I know it is not ASCII but it is cool anyway as many have now Unicode terminals – Ludovic Kuty Jan 15 '14 at 05:14
6
# First define your chars
pinwheel = %w{| / - \\}

# Rotate and print as often as needed to "spin"
def spin_it
  print "\b" + pinwheel.rotate!.first
end

EDIT from peter: here a working version

def spin_it(times)
  pinwheel = %w{| / - \\}
  times.times do
    print "\b" + pinwheel.rotate!.first
    sleep(0.1)
  end
end

spin_it 10
peter
  • 41,770
  • 5
  • 64
  • 108
oweff
  • 63
  • 4
3

I wrote a gem spin_to_win that displays a spinner while yielding a block. For example:

SpinToWin.with_spinner('Zzzz') do |spinner| 
  spinner.banner('sleepy')
  sleep 1 
end
Zzzz \ [sleepy]

It can also track work pending vs. work completed:

SpinToWin.with_spinner('Zzzz') do |spinner|
    spinner.increment_todo!(3)

    spinner.banner('snore')
    sleep 1
    spinner.increment_done!

    spinner.banner('dream')
    sleep 1
    spinner.increment_done!

    spinner.banner('wake up!')
    sleep 1
    spinner.increment_done!
end
Zzzz \ 3 of 3 [wake up!]
Rory O'Kane
  • 29,210
  • 11
  • 96
  • 131
mguymon
  • 8,946
  • 2
  • 39
  • 61
  • Thnx, I tried your examples after installing your gem (no errors there) but get an errror on both (i'm on win7, MRI Ruby 1.9.3) C:/../console/spin_to_win1.rb:8: syntax error, unexpected $undefined, expecting $end Zzzz \ [sleepy] ^ – peter Mar 06 '16 at 22:16
  • Ah, should make clear the last part is the output of the spinner. – mguymon Mar 07 '16 at 03:08
0

I use rainbow gem to print color-changing string to indicate the code is working.

require 'rainbow'
def self.print_with_random_color
    content = "I am still working on it. Please wait..."
    colors = ["aqua","chartreuse","crimson","fuchsia","gold","lawngreen","palegoldenrod","powderblue","sandybrown","deepskyblue"]
    loop do
        print Rainbow("[#{Time.now}] " + content).send(colors.sample)
        (content.length + 1).times {print "\r"}
        sleep 0.3
    end
end
yang
  • 508
  • 7
  • 17