Module: PWN::Plugins::TTYSpinner

Defined in:
lib/pwn/plugins/tty_spinner.rb

Overview

Shared TTY::Spinner lifecycle for REST / AI calls.

TTY::Spinner#stop marks @done and Thread#kills the auto_spin worker but does NOT join it. The worker is typically mid-sleep on its interval, so it stays alive long enough to write another frame (cursor col 1 + glyph) over the HTTP response that the caller is about to print.

#start uses clear:true + hide_cursor and owns the worker (spin.start + Thread.new { spin.spin until spin.done? }). #stop captures that thread, calls spin.stop, force-halts on error, kills if still alive, joins (re-kill on timeout), then clear_line + cursor show after the worker is dead.

Constant Summary collapse

JOIN_SECS =
0.5

Class Method Summary collapse

Class Method Details

.authorsObject

Author(s)

0day Inc. support@0dayinc.com



106
107
108
# File 'lib/pwn/plugins/tty_spinner.rb', line 106

public_class_method def self.authors
  "AUTHOR(S):\n  0day Inc. <support@0dayinc.com>\n"
end

.helpObject

Display usage info



112
113
114
115
116
117
# File 'lib/pwn/plugins/tty_spinner.rb', line 112

public_class_method def self.help
  puts "USAGE:
    spin = #{self}.start
    #{self}.stop(spin: spin)
  "
end

.start(opts = {}) ⇒ Object

Supported Method Parameters

spin = PWN::Plugins::TTYSpinner.start( format: 'optional - TTY::Spinner format (defaults to :dots)', output: 'optional - IO to draw on (defaults to $stderr)' )



30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
# File 'lib/pwn/plugins/tty_spinner.rb', line 30

public_class_method def self.start(opts = {})
  args = {
    format: opts[:format] || :dots,
    clear: true,
    hide_cursor: true
  }
  args[:output] = opts[:output] unless opts[:output].nil?

  spin = TTY::Spinner.new(**args)
  spin.start
  interval = 1.0 / [spin.interval.to_f, 1.0].max
  worker = Thread.new do
    Thread.current.report_on_exception = false
    until spin.done?
      spin.spin
      sleep(interval)
    end
  end
  spin.define_singleton_method(:pwn_worker_thread) { worker }
  spin
end

.stop(opts = {}) ⇒ Object

Supported Method Parameters

PWN::Plugins::TTYSpinner.stop( spin: 'optional - TTY::Spinner from #start (no-op if nil)' )



57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
# File 'lib/pwn/plugins/tty_spinner.rb', line 57

public_class_method def self.stop(opts = {})
  spin = opts.is_a?(Hash) ? opts[:spin] : opts
  return if spin.nil?

  worker = spin.respond_to?(:pwn_worker_thread) ? spin.pwn_worker_thread : nil
  begin
    spin.stop if spin.respond_to?(:stop)
  rescue StandardError
    spin.kill if spin.respond_to?(:kill)
  end
  join_thread(thread: worker)
  join_spinner(spin: spin)
  begin
    spin.clear_line if spin.respond_to?(:clear_line)
    show = defined?(TTY::Cursor) ? TTY::Cursor.show : "\e[?25h"
    out = spin.output if spin.respond_to?(:output)
    out.print(show) if out.respond_to?(:print)
    $stdout.write(show) if $stdout.respond_to?(:write)
    $stdout.flush if $stdout.respond_to?(:flush)
  rescue StandardError
    nil
  end
  nil
end