Yesterday I found myself writing a long-running migration rake task.

It was supposed to walk through one big database table and update records. One by one. There was no way to write a clever SQL query: 3rd party service was called for each record and the updated column value depended on the result of the request. I do not like starring at the stale console screen since 90s, when I had a 486 machine with 2MB of RAM. There is no typo above: my working computer, I used heavily to earn money as freelancer had 2MB of ROM. 20 years ago. Anyway, there is no room for nostalgia now and I feel no nostalgia at all for 14Kb modem connection, ROM counted in megabytes and processors, doing less flops than my cat chasing a fly.

I decided I need a progress bar for this rake task to show—well—progress. I went to DuckDuckGo (which is by all means better than Google for developers) and asked for a ruby gem providing console status bar functionality. There are many. With all respect to their authors, they must serve coffee and massage your back for this amount of code they contain.

Damn it, I decided. It would be faster to write the progress bar on my own, than to download one of these gems and digdoc there interfaces. After all, everything I need from a progress bar is to draw—well—progress bar.

Below is the source code of it. I put this file into my lib/tasks directory and I require_relative it from tasks where I need to draw a meter. That simple.

class ProgressBar
  RUNNING = %W{👆 👇 👈 👉 👊}.freeze

  attr_reader :count, :position

  def initialize caption, count, running = RUNNING
    @count = count
    @caption = " #{caption} "
    @running = running.cycle
    @position = 0
  end

  def tick
    @now = Time.now if @position.zero?

    @position += 1

    closing
    opening
    meter
  end

  ##########################################################

  def self.test caption = 'Hello', count = 1024
    ProgressBar.new(caption, count).tap do |pb|
      pb.count.times do
        sleep 0.01
        pb.tick
      end
    end
  end

  ##########################################################

  private

  def opening(sym = '|')
    print "\e[1G#{@caption}#{sym}"
  end

  def closing(sym = '|')
    print "\e[#{$stdin.winsize.first}G#{sym}"
  end

  def meter(sym = '=')
    space = $stdin.winsize.first - @caption.length - 2
    print sym * (@position.to_f * space / @count)
    if @position == @count
      puts
      # rubocop:disable Style/FormatString
      puts "It took %s min %s sec" % (Time.now - @now).ceil.divmod(60)
      # rubocop:enable Style/FormatString
    else
      print @running.next
    end
  end
end

This code is provided as is, take it, play with it, but don’t write me back saying it could’ve been done better. It could have. But I needed to have it up and running in half of an hour: now it’s running and I am writing this post, keeping one of my eyes on fancy progress meter.