class TTY::Spinner

Used for creating terminal spinner

@api public

Constants

CROSS
CURSOR_LOCK
ECMA_CSI
MATCHER
NotSpinningError

@raised when attempting to join dead thread

TICK
VERSION

Attributes

format[R]

The current format type

@return [String]

@api public

hide_cursor[R]

Whether to show or hide cursor

@return [Boolean]

@api public

interval[R]

The amount of time between frames in auto spinning

@api public

message[R]

The message to print before the spinner

@return [String]

the current message

@api public

output[R]

The object that responds to print call defaulting to stderr

@api public

row[R]

The current row inside the multi spinner

@api public

tokens[R]

Tokens for the message

@return [Hash[Symbol, Object]]

the current tokens

@api public

Public Class Methods

new(*args) click to toggle source

Initialize a spinner

@example

spinner = TTY::Spinner.new

@param [String] message

the message to print in front of the spinner

@param [Hash] options @option options [String] :format

the spinner format type defaulting to :spin_1

@option options [Object] :output

the object that responds to print call defaulting to stderr

@option options [Boolean] :hide_cursor

display or hide cursor

@option options [Boolean] :clear

clear ouptut when finished

@option options [Float] :interval

the interval for auto spinning

@api public

Calls superclass method
# File lib/tty/spinner.rb, line 94
def initialize(*args)
  super()
  options  = args.last.is_a?(::Hash) ? args.pop : {}
  @message = args.empty? ? ':spinner' : args.pop
  @tokens  = {}

  @format      = options.fetch(:format) { :classic }
  @output      = options.fetch(:output) { $stderr }
  @hide_cursor = options.fetch(:hide_cursor) { false }
  @frames      = options.fetch(:frames) do
                   fetch_format(@format.to_sym, :frames)
                 end
  @clear       = options.fetch(:clear) { false }
  @success_mark= options.fetch(:success_mark) { TICK }
  @error_mark  = options.fetch(:error_mark) { CROSS }
  @interval    = options.fetch(:interval) do
                   fetch_format(@format.to_sym, :interval)
                 end
  @row         = options[:row]

  @callbacks   = Hash.new { |h, k| h[k] = [] }
  @length      = @frames.length
  @thread      = nil
  @job         = nil
  @multispinner= nil
  reset
end

Public Instance Methods

attach_to(multispinner) click to toggle source

Notifies the TTY::Spinner that it is running under a multispinner

@param [TTY::Spinner::Multi] the multispinner that it is running under

@api private

# File lib/tty/spinner.rb, line 140
def attach_to(multispinner)
  @multispinner = multispinner
end
auto_spin() click to toggle source

Start automatic spinning animation

@api public

# File lib/tty/spinner.rb, line 240
def auto_spin
  CURSOR_LOCK.synchronize do
    start
    sleep_time = 1.0 / @interval

    spin
    @thread = Thread.new do
      sleep(sleep_time)
      while @started_at
        if Thread.current['pause']
          Thread.stop
          Thread.current['pause'] = false
        end
        spin
        sleep(sleep_time)
      end
    end
  end
ensure
  if @hide_cursor
    write(TTY::Cursor.show, false)
  end
end
clear_line() click to toggle source

Clear current line

@api public

# File lib/tty/spinner.rb, line 460
def clear_line
  write(ECMA_CSI + '0m' + TTY::Cursor.clear_line)
end
done?() click to toggle source

Whether the spinner has completed spinning

@return [Boolean] whether or not the spinner has finished

@api public

# File lib/tty/spinner.rb, line 149
def done?
  @done
end
duration() click to toggle source

Duration of the spinning animation

@return [Numeric]

@api public

# File lib/tty/spinner.rb, line 319
def duration
  @started_at ? Time.now - @started_at : nil
end
error(stop_message = '') click to toggle source

Finish spinning and set state to :error

@api public

# File lib/tty/spinner.rb, line 447
def error(stop_message = '')
  return if done?

  synchronize do
    @succeeded = :error
    stop(stop_message)
    emit(:error)
  end
end
error?() click to toggle source

Whether the spinner is in the error state. This is only true temporarily while it is being marked with a failure mark.

@return [Boolean] whether or not the spinner is erroring

@api public

# File lib/tty/spinner.rb, line 178
def error?
  @succeeded == :error
end
execute_job() click to toggle source

Execute this spinner job

@yield [TTY::Spinner]

@api public

# File lib/tty/spinner.rb, line 224
def execute_job
  job.(self) if job?
end
job(&work) click to toggle source

Add job to this spinner

@api public

# File lib/tty/spinner.rb, line 209
def job(&work)
  synchronize do
    if block_given?
      @job = work
    else
      @job
    end
  end
end
job?() click to toggle source

Check if this spinner has a scheduled job

@return [Boolean]

@api public

# File lib/tty/spinner.rb, line 233
def job?
  !@job.nil?
end
join(timeout = nil) click to toggle source

Join running spinner

@param [Float] timeout

the timeout for join

@api public

# File lib/tty/spinner.rb, line 329
def join(timeout = nil)
  unless @thread
    raise(NotSpinningError, 'Cannot join spinner that is not running')
  end

  timeout ? @thread.join(timeout) : @thread.join
end
kill() click to toggle source

Kill running spinner

@api public

# File lib/tty/spinner.rb, line 340
def kill
  synchronize do
    @thread.kill if @thread
  end
end
next_char() click to toggle source

Retrieve next character

@return [String]

@api private

# File lib/tty/spinner.rb, line 421
def next_char
  if success?
    @success_mark
  elsif error?
    @error_mark
  else
    @frames[@current - 1]
  end
end
on(name, &block) click to toggle source

Register callback

@param [Symbol] name

the name for the event to listen for, e.i. :complete

@return [self]

@api public

# File lib/tty/spinner.rb, line 190
def on(name, &block)
  synchronize do
    @callbacks[name] << block
  end
  self
end
pause() click to toggle source

Pause spinner automatic animation

@api public

# File lib/tty/spinner.rb, line 276
def pause
  return if paused?

  synchronize do
    @thread['pause'] = true if @thread
  end
end
paused?() click to toggle source

Checked if current spinner is paused

@return [Boolean]

@api public

# File lib/tty/spinner.rb, line 269
def paused?
  !!(@thread && @thread['pause'])
end
redraw_indent() click to toggle source

Redraw the indent for this spinner, if it exists

@api private

# File lib/tty/spinner.rb, line 373
def redraw_indent
  if @hide_cursor && !spinning?
    write(TTY::Cursor.hide)
  end

  write("", false)
end
reset() click to toggle source

Reset the spinner to initial frame

@api public

# File lib/tty/spinner.rb, line 125
def reset
  synchronize do
    @current   = 0
    @done      = false
    @state     = :stopped
    @succeeded = false
    @first_run = true
  end
end
resume() click to toggle source

Resume spinner automatic animation

@api public

# File lib/tty/spinner.rb, line 287
def resume
  return unless paused?

  @thread.wakeup if @thread
end
run(stop_message = '', &block) click to toggle source

Run spinner while executing job

@param [String] stop_message

the message displayed when block is finished

@yield automatically animate and finish spinner

@example

spinner.run('Migrated DB') { ... }

@api public

# File lib/tty/spinner.rb, line 304
def run(stop_message = '', &block)
  job(&block)
  auto_spin

  @work = Thread.new { execute_job }
  @work.join
ensure
  stop(stop_message)
end
spin() click to toggle source

Perform a spin

@return [String]

the printed data

@api public

# File lib/tty/spinner.rb, line 352
def spin
  synchronize do
    return if @done
    emit(:spin)

    if @hide_cursor && !spinning?
      write(TTY::Cursor.hide)
    end

    data = message.gsub(MATCHER, @frames[@current])
    data = replace_tokens(data)
    write(data, true)
    @current = (@current + 1) % @length
    @state = :spinning
    data
  end
end
spinning?() click to toggle source

Whether the spinner is spinning

@return [Boolean] whether or not the spinner is spinning

@api public

# File lib/tty/spinner.rb, line 158
def spinning?
  @state == :spinning
end
start() click to toggle source

Start timer and unlock spinner

@api public

# File lib/tty/spinner.rb, line 200
def start
  @started_at = Time.now
  @done = false
  reset
end
stop(stop_message = '') click to toggle source

Finish spining

@param [String] stop_message

the stop message to print

@api public

# File lib/tty/spinner.rb, line 387
def stop(stop_message = '')
  mon_enter
  return if done?

  clear_line
  return if @clear

  data = message.gsub(MATCHER, next_char)
  data = replace_tokens(data)
  if !stop_message.empty?
    data << ' ' + stop_message
  end

  write(data, false)
  write("\n", false) unless @clear || @multispinner
ensure
  @state      = :stopped
  @done       = true
  @started_at = nil

  if @hide_cursor
    write(TTY::Cursor.show, false)
  end

  emit(:done)
  kill
  mon_exit
end
success(stop_message = '') click to toggle source

Finish spinning and set state to :success

@api public

# File lib/tty/spinner.rb, line 434
def success(stop_message = '')
  return if done?

  synchronize do
    @succeeded = :success
    stop(stop_message)
    emit(:success)
  end
end
success?() click to toggle source

Whether the spinner is in the success state. When true the spinner is marked with a success mark.

@return [Boolean] whether or not the spinner succeeded

@api public

# File lib/tty/spinner.rb, line 168
def success?
  @succeeded == :success
end
update(tokens) click to toggle source

Update string formatting tokens

@param [Hash] tokens

the tokens used in formatting string

@api public

# File lib/tty/spinner.rb, line 470
def update(tokens)
  synchronize do
    clear_line if spinning?
    @tokens.merge!(tokens)
  end
end

Private Instance Methods

emit(name, *args) click to toggle source

Emit callback

@api private

# File lib/tty/spinner.rb, line 533
def emit(name, *args)
  @callbacks[name].each do |callback|
    callback.call(*args)
  end
end
execute_on_line() { || ... } click to toggle source

Execute a block on the proper terminal line if the spinner is running under a multispinner. Otherwise, execute the block on the current line.

@api private

# File lib/tty/spinner.rb, line 483
def execute_on_line
  if @multispinner
    @multispinner.synchronize do
      if @first_run
        @row ||= @multispinner.next_row
        yield if block_given?
        output.print "\n"
        @first_run = false
      else
        lines_up = (@multispinner.rows + 1) - @row
        output.print TTY::Cursor.save
        output.print TTY::Cursor.up(lines_up)
        yield if block_given?
        output.print TTY::Cursor.restore
      end
    end
  else
    yield if block_given?
  end
end
fetch_format(token, property) click to toggle source

Find frames by token name

@param [Symbol] token

the name for the frames

@return [Array, String]

@api private

# File lib/tty/spinner.rb, line 547
def fetch_format(token, property)
  if FORMATS.key?(token)
    FORMATS[token][property]
  else
    raise ArgumentError, "Unknown format token `:#{token}`"
  end
end
replace_tokens(string) click to toggle source

Replace any token inside string

@param [String] string

the string containing tokens

@return [String]

@api private

# File lib/tty/spinner.rb, line 563
def replace_tokens(string)
  data = string.dup
  @tokens.each do |name, val|
    data.gsub!(/\:#{name}/, val.to_s)
  end
  data
end
tty?() click to toggle source

Check if IO is attached to a terminal

return [Boolean]

@api public

# File lib/tty/spinner.rb, line 526
def tty?
  output.respond_to?(:tty?) && output.tty?
end
write(data, clear_first = false) click to toggle source

Write data out to output

@return [nil]

@api private

# File lib/tty/spinner.rb, line 509
def write(data, clear_first = false)
  return unless tty? # write only to terminal

  execute_on_line do
    output.print(TTY::Cursor.column(1)) if clear_first
    # If there's a top level spinner, print with inset
    characters_in = @multispinner.line_inset(@row) if @multispinner
    output.print("#{characters_in}#{data}")
    output.flush
  end
end