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 / next PS1.

#start owns the worker. #stop joins it. #halt_all! stops every live spinner so a missed ensure cannot leave dots on the TTY.

Constant Summary collapse

JOIN_SECS =
0.5
LIVE =

rubocop:disable Style/MutableConstant

[]
LIVE_MUTEX =
Mutex.new

Class Method Summary collapse

Class Method Details

.authorsObject

Author(s)

0day Inc. support@0dayinc.com



158
159
160
# File 'lib/pwn/plugins/tty_spinner.rb', line 158

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

.halt_all!(opts = {}) ⇒ Object

Supported Method Parameters

PWN::Plugins::TTYSpinner.halt_all!



83
84
85
86
87
88
89
# File 'lib/pwn/plugins/tty_spinner.rb', line 83

public_class_method def self.halt_all!(opts = {})
  return nil unless opts.is_a?(Hash)

  list = LIVE_MUTEX.synchronize { LIVE.dup }
  list.each { |spin| stop(spin: spin) }
  nil
end

.helpObject

Display usage info



164
165
166
167
168
169
170
# File 'lib/pwn/plugins/tty_spinner.rb', line 164

public_class_method def self.help
  puts "USAGE:
    spin = #{self}.start
    #{self}.stop(spin: spin)
    #{self}.halt_all!
  "
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)' )



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

public_class_method def self.start(opts = {})
  args = {
    format: opts[:format] || :dots,
    clear: true,
    hide_cursor: false
  }
  args[:output] = if opts[:output]
                    opts[:output]
                  elsif defined?(PWN::Plugins::Log) && PWN::Plugins::Log.respond_to?(:raw_stderr)
                    PWN::Plugins::Log.raw_stderr
                  else
                    $stderr
                  end

  spin = TTY::Spinner.new(**args)
  halt_all!
  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 }
  track(spin: spin)
  spin
end

.stop(opts = {}) ⇒ Object

Supported Method Parameters

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



63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
# File 'lib/pwn/plugins/tty_spinner.rb', line 63

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)
  show_cursor(spin: spin)
  untrack(spin: spin)
  nil
end