progress

package
v0.23.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 18, 2026 License: AGPL-3.0 Imports: 13 Imported by: 0

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func RenderedLineCount added in v0.23.0

func RenderedLineCount(block string) int

RenderedLineCount returns the number of terminal rows the rendered block currently occupies, accounting for line wrapping at the detected terminal width. Use it as the argument to "\033[<N>F" (cursor up + clear) so the wipe lands exactly on the start of what was printed last time, even after the operator has resized (e.g. tmux split, font-size change, full-window resize) mid-render.

`strings.Count(block, "\n")` alone is wrong on resize: the count is the number of logical newlines in the printed string, but the terminal may have wrapped each long line to 2+ rows. Cursor-up by the logical count then lands inside the previous block — the next render appends below it, accumulating ghost headers each tick (visible signature: empty "Resource | Phase | Status" rows stacking under a footer that keeps moving).

Width detection falls back to logical-newline counting when stdout isn't a terminal or term.GetSize fails — same behaviour as before in non-interactive contexts (CI logs, redirected output).

func WithBar added in v0.23.0

func WithBar(ctx context.Context, b *Bar) context.Context

WithBar returns ctx carrying b. Helpers down the call tree can then retrieve the bar via FromCtx without threading *Bar through every signature.

Types

type Bar

type Bar struct {
	// contains filtered or unexported fields
}

Bar is a section-style progress logger. The transcript reads as:

● Provisioning Hetzner infrastructure
─────────────────────────────────────
  ✓ Created Hetzner Network
  ✓ Registered HCloud SSH key
  ✓ Created control-plane Load Balancer
⠴   [12s]

Each major step opens a section: bold "● <step>" header on its own line, then a horizontal rule the same width as the header. Substeps stream below in faint " ✓ <work>" rows as they complete. The bar's spinner ticks on the line below the section to telegraph "still in the middle of this section".

A zero-value Bar{} (returned by FromCtx for contexts with no bar attached) is silently a no-op — every method nil-guards so test code and library callers don't have to care.

func FromCtx added in v0.23.0

func FromCtx(ctx context.Context) *Bar

FromCtx returns the *Bar attached to ctx via WithBar, or a no-op Bar when none is attached. Callers can therefore always do `progress.FromCtx(ctx).Substep(...)` without nil-checking.

func New

func New(description string) *Bar

New creates a spinner-style progress bar (unknown length) with the given header. The header is the whole-run label; it does NOT get a "✓" line of its own — the first Describe call starts the first real step.

func (*Bar) Describe

func (b *Bar) Describe(description string)

Describe opens a new major-step section. Prints a blank line (when transitioning from a previous section), the bold "● <step>" header, and a horizontal rule whose width matches the header text. Substeps that follow stream below in faint " ✓ <work>" rows.

Top-to-bottom the final transcript reads as:

● Provisioning Hetzner infrastructure
─────────────────────────────────────
  ✓ Created Hetzner Network
  ✓ Created NAT Gateway

● Creating management cluster
──────────────────────────────
  ✓ Cloned kubeaid-config repo
  ⠴   [12s]

No-op for repeat Describe calls with the same description.

func (*Bar) Finish

func (b *Bar) Finish()

Finish clears the spinner. Substeps are already a flat list under the section header so there's no tree branch to close — the next major-step section's header serves as the implicit boundary.

func (*Bar) InProgress added in v0.23.0

func (b *Bar) InProgress(text string) (release func())

InProgress emits a transient " ↻ <text>" sub-step under the active major-step header for a long-running step that would otherwise look like silent dead time on the spinner — ArgoCD app sync (~30s+ each), Helm install of Sealed Secrets / ArgoCD, kubeaid-config render, etc. The release closure ERASES the line once the step completes; the caller follows up with a permanent "✓ <text>" via Substep so the audit trail still shows the finished work.

Pair via `defer bar.InProgress("Syncing X")()` around the long-running call. Same caveat as RequestYubiKeyTouch: the erase assumes no other Substep / InProgress / RequestYubiKeyTouch fires between Request and the closure call, so bracket as tightly as possible.

func (*Bar) Pause added in v0.23.0

func (b *Bar) Pause()

Pause silences the bar's writes to stderr — including the spinner's 100ms auto-render goroutine inside progressbar/v3. Use around interactive stdin prompts so the spinner can't overwrite the prompt line via its `\r`-anchored re-render. Internal state (counters, elapsed time) keeps updating; only the visible output is suppressed.

Pair with Resume. Calling Pause clears the spinner line via a direct stderr write so the prompt has a clean line to print into.

func (*Bar) RequestYubiKeyTouch added in v0.23.0

func (b *Bar) RequestYubiKeyTouch(reason string) (release func())

RequestYubiKeyTouch emits a transient "👉 Tap YubiKey to <reason>" sub-step while the spinner is paused on a hardware-touch SSH signature, then ERASES that line once the SSH op completes — the work sub-step that follows ("Cloned X", "Pushed Y") is the audit trail. We don't keep a permanent "Touched ✓" line because a single major step often does several SSH ops back-to-back; a stack of identical "Touched ✓" lines would crowd out the real progress without adding info.

reason names what the operator is authorizing — "clone repo", "push branch", "fetch updates" — and shows up in the prompt so they know what they're approving. Keep it short (a few words); the prompt is one substep line and shouldn't wrap.

Pair via `defer bar.RequestYubiKeyTouch("...")()` around the actual SSH op. Caveat: the erase assumes no other Substep calls fire between Request and the closure call, so bracket as tightly as possible around the op — emitting other sub-steps in between will cause the erase to target the wrong line.

No-op (returns a no-op closure) when no YubiKey-backed identity is loaded in the SSH agent — software-only agents never block for a hardware touch. Card detection is cached at Bar construction; plugging in the YubiKey mid-bootstrap won't be picked up until next run.

func (*Bar) Resume added in v0.23.0

func (b *Bar) Resume()

Resume re-enables the bar's writes. The spinner re-appears on the next render (within ~100ms via the auto-tick goroutine, or sooner if a Substep/Describe call triggers a render).

func (*Bar) Substep added in v0.23.0

func (b *Bar) Substep(text string)

Substep prints " ✓ <text>" in faint style under the active major- step header. Substeps stream below the section's underline as work completes; the bar's spinner re-renders below them on its next tick.

Faint styling is applied immediately rather than retroactively (each substep is the audit trail of finished work — there's no "active substep" concept; the bar's spinner is the active surface). Operator's eye scans past the dim list and lands on the spinner.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL