Class: Ruborg::Progress

Inherits:
Object
  • Object
show all
Defined in:
lib/ruborg/progress.rb

Overview

Terminal progress display: named stages, inline progress bar, and spinner. Writes to $stderr so stdout remains clean for –json or piped output. Degrades to plain text lines when output is not a TTY (piped / redirected).

Constant Summary collapse

SPINNER_FRAMES =
%w[         ].freeze
BAR_WIDTH =
28
LINE_WIDTH =
80

Instance Method Summary collapse

Constructor Details

#initialize(output: $stderr) ⇒ Progress

Returns a new instance of Progress.



12
13
14
15
16
17
18
# File 'lib/ruborg/progress.rb', line 12

def initialize(output: $stderr)
  @output = output
  @tty = output.respond_to?(:isatty) && output.isatty
  @spinner_thread = nil
  @spin_label = nil
  @spin_start = nil
end

Instance Method Details

#bar(current, total, label = "") ⇒ Object

Redraw an inline progress bar. Call once per item in a loop. label is truncated to fit the terminal line.



66
67
68
69
70
71
72
73
74
75
# File 'lib/ruborg/progress.rb', line 66

def bar(current, total, label = "")
  return unless @tty

  pct = total.positive? ? (current.to_f / total) : 0
  filled = (BAR_WIDTH * pct).round
  bar_str = filled.positive? ? "#{"=" * (filled - 1)}>" : ""
  bar_str = bar_str.ljust(BAR_WIDTH)
  short_label = truncate_left(label.to_s, 28)
  @output.print "\r  [#{bar_str}]  #{current}/#{total}  #{short_label.ljust(28)}"
end

#done(label = nil) ⇒ Object

Halt any in-progress display and print a completion line.



78
79
80
81
82
# File 'lib/ruborg/progress.rb', line 78

def done(label = nil)
  stop_spin
  clear_line if @tty
  @output.puts "#{label}" if label
end

#spin(label) ⇒ Object

Start a spinner on the current line for an indeterminate operation. Appends elapsed time after 3 seconds so long-running steps are visible. Call stop_spin (or done) to halt it.



30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
# File 'lib/ruborg/progress.rb', line 30

def spin(label)
  stop_spin
  return unless @tty

  @spin_label = label
  @spin_start = Time.now
  frame = 0
  @spinner_thread = Thread.new do
    loop do
      elapsed = (Time.now - @spin_start).to_i
      time_str = elapsed >= 3 ? " (#{elapsed}s)" : ""
      @output.print "\r  #{SPINNER_FRAMES[frame % SPINNER_FRAMES.size]}  #{@spin_label}#{time_str}"
      frame += 1
      sleep 0.1
    end
  end
end

#stage(index, total, label) ⇒ Object

Print a numbered stage header: “[2/3] Label”



21
22
23
24
25
# File 'lib/ruborg/progress.rb', line 21

def stage(index, total, label)
  stop_spin
  clear_line if @tty
  @output.puts "[#{index}/#{total}] #{label}"
end

#stop_spinObject

Stop the spinner and erase its line.



55
56
57
58
59
60
61
62
# File 'lib/ruborg/progress.rb', line 55

def stop_spin
  return unless @spinner_thread

  @spinner_thread.kill
  @spinner_thread.join(0.2)
  @spinner_thread = nil
  clear_line if @tty
end

#update_spin(label) ⇒ Object

Update the label on a running spinner without restarting it. Safe to call from the main thread while the spinner runs in the background.



50
51
52
# File 'lib/ruborg/progress.rb', line 50

def update_spin(label)
  @spin_label = label
end