Class: RepoTender::UI::InteractiveReporter
- Inherits:
-
Object
- Object
- RepoTender::UI::InteractiveReporter
- Defined in:
- lib/repo_tender/ui/interactive_reporter.rb
Overview
Compact, single-line live progress renderer for ‘sync`. Driven by one render-loop fiber spawned as a child of the engine task via `attach(task)` — NO Ruby Thread.
Two phases under one attach/detach (GS6):
Phase 1 — Listing: fires between listing_started and listing_finished.
Live status line shows "listing N orgs… ✓ K done". As each org
completes, a persistent line is emitted (org name + count).
Phase 2 — Sweep: fires after run_started through run_finished.
Reverts to the compact repo counter (synced X/N + tallies).
Output model:
- One live status line, rewritten in place via \r + \e[K.
- Persistent scrollback lines for listing phase (one per org) and
for NON-CLEAN repos only in sweep phase.
- Total output: O(orgs + non_clean + failed + constant).
Invariants:
- The render fiber is the sole writer to `out`; worker fibers only
mutate tally/queue state via the reporter event methods.
- `Kernel#sleep` inside the render fiber yields to the reactor
(cooperative scheduling). Never Thread.new.
- On `^C`, the scheduler cancels the child render fiber; its `ensure`
block restores the cursor unconditionally.
Constant Summary collapse
- FRAMES =
%w[⠋ ⠙ ⠹ ⠸ ⠼ ⠴ ⠦ ⠧ ⠇ ⠏].freeze
- ADDED_LIST_THRESHOLD =
10- IN_FLIGHT_MAX_WIDTH =
40
Instance Method Summary collapse
- #attach(task) ⇒ Object
- #detach ⇒ Object
-
#initialize(out, mode:, cadence: 0.1) ⇒ InteractiveReporter
constructor
A new instance of InteractiveReporter.
- #listing_finished ⇒ Object
-
#listing_started(total:) ⇒ Object
— Listing phase events —.
- #org_listed(ref, count:) ⇒ Object
- #repo_failed(ref, error) ⇒ Object
- #repo_finished(ref, status, action:, commits: 0) ⇒ Object
- #repo_phase(ref, phase) ⇒ Object
- #repo_started(ref) ⇒ Object
- #run_finished(summary) ⇒ Object
-
#run_started(total:) ⇒ Object
— Sweep phase events —.
Constructor Details
#initialize(out, mode:, cadence: 0.1) ⇒ InteractiveReporter
Returns a new instance of InteractiveReporter.
39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 |
# File 'lib/repo_tender/ui/interactive_reporter.rb', line 39 def initialize(out, mode:, cadence: 0.1) @out = out @pastel = Pastel.new(enabled: mode.color) @cadence = cadence # Listing phase state @org_total = 0 @org_done = 0 @pending_org_lines = [] # Sweep phase state @total = 0 @finished = 0 @clean_count = 0 @nonclean_count = 0 @failed_count = 0 @pending_lines = [] # In-flight tracking (insertion-ordered: last entry = most-recently-started) @in_flight = {} # End-of-run breakdown state @action_counts = Hash.new(0) @total_commits = 0 @added_repos = [] @frame_idx = 0 @phase = :listing # :listing | :sweep @done = false @render_task = nil end |
Instance Method Details
#attach(task) ⇒ Object
71 72 73 |
# File 'lib/repo_tender/ui/interactive_reporter.rb', line 71 def attach(task) @render_task = task.async { render_loop } end |
#detach ⇒ Object
75 76 77 78 79 |
# File 'lib/repo_tender/ui/interactive_reporter.rb', line 75 def detach @done = true @render_task&.wait @render_task = nil end |
#listing_finished ⇒ Object
97 98 99 |
# File 'lib/repo_tender/ui/interactive_reporter.rb', line 97 def listing_finished # Phase transition handled by run_started end |
#listing_started(total:) ⇒ Object
— Listing phase events —
83 84 85 86 |
# File 'lib/repo_tender/ui/interactive_reporter.rb', line 83 def listing_started(total:) @org_total = total @phase = :listing end |
#org_listed(ref, count:) ⇒ Object
88 89 90 91 92 93 94 95 |
# File 'lib/repo_tender/ui/interactive_reporter.rb', line 88 def org_listed(ref, count:) @org_done += 1 @pending_org_lines << if count "#{@pastel.green("✓")} #{ref.name} #{count} repo(s)" else "#{@pastel.red("✗")} #{ref.name} FAILED" end end |
#repo_failed(ref, error) ⇒ Object
136 137 138 139 140 141 142 |
# File 'lib/repo_tender/ui/interactive_reporter.rb', line 136 def repo_failed(ref, error) @in_flight.delete(ref) @finished += 1 @failed_count += 1 @action_counts[:error] += 1 @pending_lines << "#{@pastel.red("✗")} #{ref} #{error}" end |
#repo_finished(ref, status, action:, commits: 0) ⇒ Object
122 123 124 125 126 127 128 129 130 131 132 133 134 |
# File 'lib/repo_tender/ui/interactive_reporter.rb', line 122 def repo_finished(ref, status, action:, commits: 0) @in_flight.delete(ref) @finished += 1 @action_counts[action] += 1 @total_commits += commits @added_repos << ref if action == :cloned if status.to_s == "clean" @clean_count += 1 else @nonclean_count += 1 @pending_lines << "#{@pastel.yellow("⚠")} #{ref} #{status}" end end |
#repo_phase(ref, phase) ⇒ Object
112 113 114 115 116 117 118 119 120 |
# File 'lib/repo_tender/ui/interactive_reporter.rb', line 112 def repo_phase(ref, phase) return unless @in_flight.key?(ref) @in_flight[ref] = case phase when :cloning then "cloning" when :fast_forwarding then "fast-forwarding" when :switching then "switching" else @in_flight[ref] end end |
#repo_started(ref) ⇒ Object
108 109 110 |
# File 'lib/repo_tender/ui/interactive_reporter.rb', line 108 def repo_started(ref) @in_flight[ref] = "checking" end |
#run_finished(summary) ⇒ Object
144 |
# File 'lib/repo_tender/ui/interactive_reporter.rb', line 144 def run_finished(summary) = nil |
#run_started(total:) ⇒ Object
— Sweep phase events —
103 104 105 106 |
# File 'lib/repo_tender/ui/interactive_reporter.rb', line 103 def run_started(total:) @total = total @phase = :sweep end |