module Whirly

Constants

CLI_COMMANDS
DEFAULT_OPTIONS
SOFT_DEFAULT_OPTIONS
VERSION

Attributes

options[R]
status[RW]

Public Class Methods

configure(**options) click to toggle source

save options and preprocess, set defaults if value is still unknown

# File lib/whirly.rb, line 114
def self.configure(**options)
  if !defined?(@configured) || !@configured || !defined?(@options) || !@options
    @options = DEFAULT_OPTIONS.dup
    @configured = true
  end

  @options.merge!(options)

  spinner = configure_spinner(@options[:spinner])
  spinner_overwrites = {}
  spinner_overwrites["mode"] = @options[:mode] if @options.key?(:mode)
  @frames   = configure_frames(spinner.merge(spinner_overwrites))

  @interval = (@options[:interval] || spinner["interval"] || SOFT_DEFAULT_OPTIONS[:interval]) * 0.001
  @stop     = @options[:stop] || spinner["stop"]
  @status   = @options[:status]
end
configure_frames(spinner) click to toggle source

frames can be generated from enumerables or procs

# File lib/whirly.rb, line 85
def self.configure_frames(spinner)
  if spinner["frames"]
    case spinner["mode"]
    when "swing"
      frames = (spinner["frames"].to_a + spinner["frames"].to_a[1..-2].reverse).cycle
    when "random"
      frame_pool = spinner["frames"].to_a
      frames = ->(){ frame_pool.sample }
    when "reverse"
      frames = spinner["frames"].to_a.reverse.cycle
    else
      frames = spinner["frames"].cycle
    end
  elsif spinner["proc"]
    frames = spinner["proc"].dup
  else
    raise(ArgumentError, "Whirly: Invalid spinner given")
  end

  if frames.is_a? Proc
    class << frames
      alias next call
    end
  end

  frames
end
configure_spinner(spinner_option) click to toggle source

set spinner directly or lookup

# File lib/whirly.rb, line 55
def self.configure_spinner(spinner_option)
  case spinner_option
  when Hash
    spinner = spinner_option.dup
  when Enumerable
    spinner = { "frames" => spinner_option.dup }
  when Proc
    spinner = { "proc" => spinner_option.dup }
  else
    spinner = nil
    catch(:found){
      @options[:spinner_packs].each{ |spinner_pack|
        spinners = Whirly::Spinners.const_get(spinner_pack.to_s.upcase)
        if spinners[spinner_option]
          spinner = spinners[spinner_option].dup
          throw(:found)
        end
      }
    }
  end

  # validate spinner
  if !spinner || (!spinner["frames"] && !spinner["proc"])
    raise(ArgumentError, "Whirly: Invalid spinner given")
  end

  spinner
end
configured?() click to toggle source
# File lib/whirly.rb, line 49
def configured?
  !!(@configured)
end
enabled?() click to toggle source
# File lib/whirly.rb, line 45
def enabled?
  !!(defined?(@enabled) && @enabled)
end
initialize_color() click to toggle source
# File lib/whirly.rb, line 240
def self.initialize_color
  if !defined?(Paint)
    warn "Whirly warning: Using colors requires the paint gem"
  else
    @color = "%.6x" % rand(16777216)
    @color_directions = (0..2).map{ |e| rand(3) - 1 }
  end
end
next_color() click to toggle source
# File lib/whirly.rb, line 249
def self.next_color
  @color = @color.scan(/../).map.with_index{ |c, i|
    color_change = rand(@options[:color_change_rate]) * @color_directions[i]
    nc = c.to_i(16) + color_change
    if nc <= 0
      nc = 0
      @color_directions[i] = rand(3) - 1
    elsif nc >= 255
      nc = 255
      @color_directions[i] = rand(3) - 1
    end
    "%.2x" % nc
  }.join
end
render(next_frame = nil) click to toggle source
# File lib/whirly.rb, line 215
def self.render(next_frame = nil)
  unrender

  @current_frame = next_frame || @frames.next
  @current_frame = Paint[@current_frame, @color] if @options[:color]
  @current_frame += "  #{@status}" if @status

  @options[:stream].print(render_prefix + @current_frame.to_s + render_suffix)
end
render_prefix() click to toggle source
# File lib/whirly.rb, line 225
def self.render_prefix
  res = ""
  res << "\n" if @options[:position] == "below"
  res << "\e7" if @options[:ansi_escape_mode] == "restore"
  res << "\e[G" if @options[:ansi_escape_mode] == "line"
  res
end
render_suffix() click to toggle source
# File lib/whirly.rb, line 233
def self.render_suffix
  res = ""
  res << "\e8" if @options[:ansi_escape_mode] == "restore"
  res << "\e[1A" if @options[:position] == "below"
  res
end
reset() click to toggle source
# File lib/whirly.rb, line 194
def self.reset
  at_exit_handler_registered = defined?(@at_exit_handler_registered) && @at_exit_handler_registered
  instance_variables.each{ |iv| remove_instance_variable(iv) }
  @at_exit_handler_registered = at_exit_handler_registered
  @configured = false
end
start(**options) { || ... } click to toggle source
# File lib/whirly.rb, line 132
def self.start(**options)
  # optionally overwrite configuration on start
  configure(**options)

  # only enable once
  return false if defined?(@enabled) && @enabled

  # set status to enabled
  @enabled = true

  # only do something if we are on a real terminal (or forced)
  return false unless @options[:stream].tty? || @options[:non_tty]

  # ensure cursor is visible after exit the program (only register for the very first time)
  if (!defined?(@at_exit_handler_registered) || !@at_exit_handler_registered) && @options[:hide_cursor]
    @at_exit_handler_registered = true
    stream = @options[:stream]
    at_exit{ stream.print CLI_COMMANDS[:show_cursor] }
  end

  # init color
  initialize_color if @options[:color]

  # hide cursor
  @options[:stream].print CLI_COMMANDS[:hide_cursor] if @options[:hide_cursor]

  # start spinner loop
  @thread = Thread.new do
    @current_frame = nil
    while true # it's just a spinner, no exact timing here
      next_color if @color
      render
      sleep(@interval)
    end
  end

  # idiomatic block syntax support
  if block_given?
    begin
      yield
    ensure
      Whirly.stop
    end
  end

  true
end
stop(stop_frame = nil) click to toggle source
# File lib/whirly.rb, line 180
def self.stop(stop_frame = nil)
  return false unless @enabled
  @enabled = false
  return false unless @options[:stream].tty? || @options[:non_tty]

  @thread.terminate if @thread
  render(stop_frame || @stop) if stop_frame || @stop
  unrender if @options[:remove_after_stop]
  @options[:stream].puts if @options[:append_newline]
  @options[:stream].print CLI_COMMANDS[:show_cursor] if @options[:hide_cursor]

  true
end
unrender() click to toggle source
    • -

# File lib/whirly.rb, line 203
def self.unrender
  return unless @current_frame
  case @options[:ansi_escape_mode]
  when "restore"
    @options[:stream].print(render_prefix + (
        ' ' * (Unicode::DisplayWidth.of(@current_frame, @options[:ambiguous_character_width]) + 1)
    ) + render_suffix)
  when "line"
    @options[:stream].print "\e[1K"
  end
end