class TTY::Spinner::Multi

Used for managing multiple terminal spinners

@api public

Constants

DEFAULT_INSET

Attributes

rows[R]

The current count of all rendered rows

@api public

Public Class Methods

new(*args) click to toggle source

Initialize a multispinner

@example

spinner = TTY::Spinner::Multi.new

@param [String] message

the optional message to print in front of the top level spinner

@param [Hash] options @option options [Hash] :style

keys :top :middle and :bottom can contain Strings that are used to
indent the spinners. Ignored if message is blank

@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/multi.rb, line 54
def initialize(*args)
  super()
  @options = args.last.is_a?(::Hash) ? args.pop : {}
  message = args.empty? ? nil : args.pop
  @inset_opts  = @options.delete(:style) { DEFAULT_INSET }
  @rows        = 0
  @spinners    = []
  @spinners_count = 0
  @top_spinner = nil
  @last_spin_at = nil
  unless message.nil?
    @top_spinner = register(message, observable: false, row: next_row)
  end

  @callbacks = {
    success: [],
    error:   [],
    done:    [],
    spin:    []
  }
end

Public Instance Methods

auto_spin() click to toggle source

Auto spin the top level spinner & all child spinners that have scheduled jobs

@api public

# File lib/tty/spinner/multi.rb, line 143
def auto_spin
  raise "No top level spinner" if @top_spinner.nil?

  jobs = []
  @spinners.each do |spinner|
    if spinner.job?
      spinner.auto_spin
      jobs << Thread.new { spinner.execute_job }
    end
  end
  jobs.each(&:join)
end
create_spinner(pattern_or_spinner, options) click to toggle source

Create a spinner instance

@api private

# File lib/tty/spinner/multi.rb, line 104
def create_spinner(pattern_or_spinner, options)
  case pattern_or_spinner
  when ::String
    TTY::Spinner.new(
      pattern_or_spinner,
      @options.merge(options)
    )
  when ::TTY::Spinner
    pattern_or_spinner
  else
    raise ArgumentError, "Expected a pattern or spinner, " \
      "got: #{pattern_or_spinner.class}"
  end
end
done?() click to toggle source

Check if all spinners are done

@return [Boolean]

@api public

# File lib/tty/spinner/multi.rb, line 208
def done?
  synchronize do
    (@spinners - [@top_spinner]).all?(&:done?)
  end
end
error() click to toggle source

Stop all spinners with error status

@api public

# File lib/tty/spinner/multi.rb, line 253
def error
  @spinners.dup.each(&:error)
end
error?() click to toggle source

Check if any spinner errored

@return [Boolean]

@api public

# File lib/tty/spinner/multi.rb, line 230
def error?
  synchronize do
    (@spinners - [@top_spinner]).any?(&:error?)
  end
end
line_inset(line_no) click to toggle source

Find the number of characters to move into the line before printing the spinner

@param [Integer] line_no

the current spinner line number for which line inset is calculated

@return [String]

the inset

@api public

# File lib/tty/spinner/multi.rb, line 191
def line_inset(line_no)
  return '' if @top_spinner.nil?

  if line_no == 1
    @inset_opts[:top]
  elsif line_no == @spinners_count
    @inset_opts[:bottom]
  else
    @inset_opts[:middle]
  end
end
next_row() click to toggle source

Increase a row count

@api public

# File lib/tty/spinner/multi.rb, line 122
def next_row
  synchronize do
    @rows += 1
  end
end
on(key, &callback) click to toggle source

Listen on event

@api public

# File lib/tty/spinner/multi.rb, line 260
def on(key, &callback)
  unless @callbacks.key?(key)
    raise ArgumentError, "The event #{key} does not exist. "\
                         ' Use :spin, :success, :error, or :done instead'
  end
  @callbacks[key] << callback
  self
end
pause() click to toggle source

Pause all spinners

@api public

# File lib/tty/spinner/multi.rb, line 170
def pause
  @spinners.dup.each(&:pause)
end
register(pattern_or_spinner, **options, &job) click to toggle source

Register a new spinner

@param [String, TTY::Spinner] pattern_or_spinner

the pattern used for creating spinner, or a spinner instance

@api public

# File lib/tty/spinner/multi.rb, line 82
def register(pattern_or_spinner, **options, &job)
  observable = options.delete(:observable) { true }
  spinner = nil

  synchronize do
    spinner = create_spinner(pattern_or_spinner, options)
    spinner.attach_to(self)
    spinner.job(&job) if block_given?
    observe(spinner) if observable
    @spinners << spinner
    @spinners_count += 1
    if @top_spinner
      @spinners.each { |sp| sp.redraw_indent if sp.spinning? || sp.done? }
    end
  end

  spinner
end
resume() click to toggle source

Resume all spinners

@api public

# File lib/tty/spinner/multi.rb, line 177
def resume
  @spinners.dup.each(&:resume)
end
spin() click to toggle source

Perform a single spin animation

@api public

# File lib/tty/spinner/multi.rb, line 159
def spin
  raise "No top level spinner" if @top_spinner.nil?

  synchronize do
    throttle { @top_spinner.spin }
  end
end
stop() click to toggle source

Stop all spinners

@api public

# File lib/tty/spinner/multi.rb, line 239
def stop
  @spinners.dup.each(&:stop)
end
success() click to toggle source

Stop all spinners with success status

@api public

# File lib/tty/spinner/multi.rb, line 246
def success
  @spinners.dup.each(&:success)
end
success?() click to toggle source

Check if all spinners succeeded

@return [Boolean]

@api public

# File lib/tty/spinner/multi.rb, line 219
def success?
  synchronize do
    (@spinners - [@top_spinner]).all?(&:success?)
  end
end
top_spinner() click to toggle source

Get the top level spinner if it exists

@return [TTY::Spinner] the top level spinner

@api public

# File lib/tty/spinner/multi.rb, line 133
def top_spinner
  raise "No top level spinner" if @top_spinner.nil?

  @top_spinner
end

Private Instance Methods

done_handler() click to toggle source

Handle the done state

@api private

# File lib/tty/spinner/multi.rb, line 343
def done_handler
  proc do
    if done?
      @top_spinner.stop if @top_spinner && !error? && !success?
      emit(:done)
    end
  end
end
emit(key, *args) click to toggle source

Fire an event

@api private

# File lib/tty/spinner/multi.rb, line 287
def emit(key, *args)
  @callbacks[key].each do |block|
    block.call(*args)
  end
end
error_handler() click to toggle source

Handle the error state

@api private

# File lib/tty/spinner/multi.rb, line 331
def error_handler
  proc do
    if error?
      @top_spinner.error if @top_spinner
      @fired ||= emit(:error) # fire once
    end
  end
end
observe(spinner) click to toggle source

Observe spinner for events to notify top spinner of current state

@param [TTY::Spinner] spinner

the spinner to listen to for events

@api private

# File lib/tty/spinner/multi.rb, line 299
def observe(spinner)
  spinner.on(:spin, &spin_handler)
         .on(:success, &success_handler)
         .on(:error, &error_handler)
         .on(:done, &done_handler)
end
spin_handler() click to toggle source

Handle spin event

@api private

# File lib/tty/spinner/multi.rb, line 309
def spin_handler
  proc do
    spin if @top_spinner
    emit(:spin)
  end
end
success_handler() click to toggle source

Handle the success state

@api private

# File lib/tty/spinner/multi.rb, line 319
def success_handler
  proc do
    if success?
      @top_spinner.success if @top_spinner
      emit(:success)
    end
  end
end
throttle() { || ... } click to toggle source

Check if this spinner should revolve to keep constant speed matching top spinner interval

@api private

# File lib/tty/spinner/multi.rb, line 275
def throttle
  sleep_time = 1.0 / @top_spinner.interval
  if @last_spin_at && Time.now - @last_spin_at < sleep_time
    return
  end
  yield if block_given?
  @last_spin_at = Time.now
end