Class: TTY::Spinner

Inherits:
Object
  • Object
show all
Includes:
MonitorMixin, Formats
Defined in:
lib/tty/spinner.rb,
lib/tty/spinner/multi.rb,
lib/tty/spinner/version.rb

Overview

Used for creating terminal spinner

Defined Under Namespace

Classes: Multi

Constant Summary collapse

NotSpinningError =
Class.new(StandardError)
ECMA_CSI =
"\x1b["
MATCHER =
/:spinner/
TICK =
''
CROSS =
''
CURSOR_LOCK =
Monitor.new
VERSION =
'0.9.0'

Constants included from Formats

Formats::FORMATS

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(*args) ⇒ Spinner

Initialize a spinner

Examples:

spinner = TTY::Spinner.new

Parameters:

  • message (String)

    the message to print in front of the spinner

  • options (Hash)

94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
# 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

Instance Attribute Details

#formatString (readonly)

The current format type

Returns:

  • (String)

38
39
40
# File 'lib/tty/spinner.rb', line 38

def format
  @format
end

#hide_cursorBoolean (readonly)

Whether to show or hide cursor

Returns:

  • (Boolean)

45
46
47
# File 'lib/tty/spinner.rb', line 45

def hide_cursor
  @hide_cursor
end

#intervalObject (readonly)

The amount of time between frames in auto spinning


66
67
68
# File 'lib/tty/spinner.rb', line 66

def interval
  @interval
end

#messageString (readonly)

The message to print before the spinner

Returns:

  • (String)

    the current message


53
54
55
# File 'lib/tty/spinner.rb', line 53

def message
  @message
end

#outputObject (readonly)

The object that responds to print call defaulting to stderr


31
32
33
# File 'lib/tty/spinner.rb', line 31

def output
  @output
end

#rowObject (readonly)

The current row inside the multi spinner


71
72
73
# File 'lib/tty/spinner.rb', line 71

def row
  @row
end

#tokensHash[Symbol, Object] (readonly)

Tokens for the message

Returns:

  • (Hash[Symbol, Object])

    the current tokens


61
62
63
# File 'lib/tty/spinner.rb', line 61

def tokens
  @tokens
end

Instance Method Details

#attach_to(multispinner) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

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

Parameters:


140
141
142
# File 'lib/tty/spinner.rb', line 140

def attach_to(multispinner)
  @multispinner = multispinner
end

#auto_spinObject

Start automatic spinning animation


240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
# 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_lineObject

Clear current line


460
461
462
# File 'lib/tty/spinner.rb', line 460

def clear_line
  write(ECMA_CSI + '0m' + TTY::Cursor.clear_line)
end

#done?Boolean

Whether the spinner has completed spinning

Returns:

  • (Boolean)

    whether or not the spinner has finished


149
150
151
# File 'lib/tty/spinner.rb', line 149

def done?
  @done
end

#durationNumeric

Duration of the spinning animation

Returns:

  • (Numeric)

319
320
321
# File 'lib/tty/spinner.rb', line 319

def duration
  @started_at ? Time.now - @started_at : nil
end

#error(stop_message = '') ⇒ Object

Finish spinning and set state to :error


447
448
449
450
451
452
453
454
455
# 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?Boolean

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

Returns:

  • (Boolean)

    whether or not the spinner is erroring


178
179
180
# File 'lib/tty/spinner.rb', line 178

def error?
  @succeeded == :error
end

#execute_job {|TTY::Spinner| ... } ⇒ Object

Execute this spinner job

Yields:


224
225
226
# File 'lib/tty/spinner.rb', line 224

def execute_job
  job.(self) if job?
end

#job(&work) ⇒ Object

Add job to this spinner


209
210
211
212
213
214
215
216
217
# File 'lib/tty/spinner.rb', line 209

def job(&work)
  synchronize do
    if block_given?
      @job = work
    else
      @job
    end
  end
end

#job?Boolean

Check if this spinner has a scheduled job

Returns:

  • (Boolean)

233
234
235
# File 'lib/tty/spinner.rb', line 233

def job?
  !@job.nil?
end

#join(timeout = nil) ⇒ Object

Join running spinner

Parameters:

  • timeout (Float) (defaults to: nil)

    the timeout for join


329
330
331
332
333
334
335
# 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

#killObject

Kill running spinner


340
341
342
343
344
# File 'lib/tty/spinner.rb', line 340

def kill
  synchronize do
    @thread.kill if @thread
  end
end

#next_charString

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Retrieve next character

Returns:

  • (String)

421
422
423
424
425
426
427
428
429
# 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) ⇒ self

Register callback

Parameters:

  • name (Symbol)

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

Returns:

  • (self)

190
191
192
193
194
195
# File 'lib/tty/spinner.rb', line 190

def on(name, &block)
  synchronize do
    @callbacks[name] << block
  end
  self
end

#pauseObject

Pause spinner automatic animation


276
277
278
279
280
281
282
# File 'lib/tty/spinner.rb', line 276

def pause
  return if paused?

  synchronize do
    @thread['pause'] = true if @thread
  end
end

#paused?Boolean

Checked if current spinner is paused

Returns:

  • (Boolean)

269
270
271
# File 'lib/tty/spinner.rb', line 269

def paused?
  !!(@thread && @thread['pause'])
end

#redraw_indentObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Redraw the indent for this spinner, if it exists


373
374
375
376
377
378
379
# File 'lib/tty/spinner.rb', line 373

def redraw_indent
  if @hide_cursor && !spinning?
    write(ECMA_CSI + DEC_TCEM + DEC_RST)
  end

  write("", false)
end

#resetObject

Reset the spinner to initial frame


125
126
127
128
129
130
131
132
133
# File 'lib/tty/spinner.rb', line 125

def reset
  synchronize do
    @current   = 0
    @done      = false
    @state     = :stopped
    @succeeded = false
    @first_run = true
  end
end

#resumeObject

Resume spinner automatic animation


287
288
289
290
291
# File 'lib/tty/spinner.rb', line 287

def resume
  return unless paused?

  @thread.wakeup if @thread
end

#run(stop_message = '') { ... } ⇒ Object

Run spinner while executing job

Examples:

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

Parameters:

  • stop_message (String) (defaults to: '')

    the message displayed when block is finished

Yields:

  • automatically animate and finish spinner


304
305
306
307
308
309
310
311
312
# 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

#spinString

Perform a spin

Returns:

  • (String)

    the printed data


352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
# 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?Boolean

Whether the spinner is spinning

Returns:

  • (Boolean)

    whether or not the spinner is spinning


158
159
160
# File 'lib/tty/spinner.rb', line 158

def spinning?
  @state == :spinning
end

#startObject

Start timer and unlock spinner


200
201
202
203
204
# File 'lib/tty/spinner.rb', line 200

def start
  @started_at = Time.now
  @done = false
  reset
end

#stop(stop_message = '') ⇒ Object

Finish spining

Parameters:

  • stop_message (String) (defaults to: '')

    the stop message to print


387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
# 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 = '') ⇒ Object

Finish spinning and set state to :success


434
435
436
437
438
439
440
441
442
# 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?Boolean

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

Returns:

  • (Boolean)

    whether or not the spinner succeeded


168
169
170
# File 'lib/tty/spinner.rb', line 168

def success?
  @succeeded == :success
end

#update(tokens) ⇒ Object

Update string formatting tokens

Parameters:

  • tokens (Hash[Symbol])

    the tokens used in formatting string


470
471
472
473
474
475
# File 'lib/tty/spinner.rb', line 470

def update(tokens)
  synchronize do
    clear_line if spinning?
    @tokens.merge!(tokens)
  end
end