Make a Heroku style spinner in Ruby

If you've been using the Heroku command line toolbelt lately you would've noticed the cool progress spinner they have when you're running a task:

(It's the swirly thing on the right hand side - it actually looks better in real life, but I did a poor job of creating an animated gif).

They're doing this by using Braille characters - animating them to look like they're spinning.

I thought I'd like to use this in my own ruby command line apps, so here's how I did it:


First off, I wanted the spinner to run in its own thread, one that I can just shut off when the long running task is complete.

To do this I nicked some code for creating an 'interruptible sleep' method. This lets me send a kill command to the spinner process without getting an ugly error message:

# Shamelessly stolen from https://gist.github.com/ileitch/1459987

module InterruptibleSleep
  def interruptible_sleep(seconds)
    @sleep_check, @sleep_interrupt = IO.pipe
    IO.select([@sleep_check], nil, nil, seconds)
  end

  def interrupt_sleep
    @sleep_interrupt.close if @sleep_interrupt
  end
end

My code runs the spinner in its own thread, so it'll just keep spinning around and around until your long running command is complete.

Here's the crux of the code. The actual spinner class:

require 'interruptible_sleep'

class Spinner
  include InterruptibleSleep
  GLYPHS = %w(⣾ ⣷ ⣯ ⣟ ⡿ ⢿ ⣻ ⣽).reverse
  INTERVAL_DURATION = 0.07

  def initialize
    @current_glyph = 0
  end

  def progress
    loop do
      increment
      print "\r#{glyph} "
      interruptible_sleep(INTERVAL_DURATION)
    end
  end

  def stop
    print "\r"
    interrupt_sleep
  end

  def self.show_progress(&block)
    process = fork(&block)

    progress = fork do
      progress_indicator = Spinner.new
      progress_indicator.progress
    end

    Process.wait(process)
    Process.kill("HUP", progress)
    print "\r"
  end

  private

  def increment
    @current_glyph = @current_glyph + 1
    @current_glyph = 0 if @current_glyph >= GLYPHS.length
  end

  def glyph
    GLYPHS[@current_glyph]
  end
end

Basically it just runs in an infinite loop until the long running task has been completed.

Note the print("\r") - when you use \r with print it replaces the line with the new input. This lets you create an animation on the command line.


To use this code you use it like so:

Spinner.show_progress do
  # Some long running task
end

And boom! Nice fancy spinner!

Let me know if you use it in your own projects.



Subscribe via RSS

Back to all blog posts