Introduce internal/ui package and rewrite user-facing output
All user-facing output now goes through a single ui.Writer with a
uniform style:
》 (white) for begin / info / notice
》 (green) for complete / success
Warning: for warnings (orange)
ERROR: for errors (red)
》 (indented) for progress heartbeats
Color is enabled when stdout is a TTY and NO_COLOR is unset.
Standards:
- Complete-sentence messages with fully qualified terms ("backup
destination store", "local index database", "snapshot source
files enumeration").
- Every Complete has a matching Begin.
- Natural verb tense conveys state ("Uploading" -> "Uploaded"). The
words "begin"/"complete" never appear in message bodies; the marker
color carries that information.
- ETA means clock time, not duration. Progress lines say "estimated
remaining time (<dur>), finish at <time>" with both labeled.
Adds globals.CommitDate (populated by Makefile/Dockerfile/goreleaser
via ldflags from `git show -s --format=%cI HEAD`) and a startup banner
printed once per invocation.
Strips fx call-chain noise from startup errors so users see the actual
underlying error (e.g. "creating base path: mkdir /Volumes/BACKUPS:
permission denied" instead of three layers of "could not build
arguments for function ...").
README documents the output style and the ui package conventions.
This commit is contained in:
86
internal/ui/ui_test.go
Normal file
86
internal/ui/ui_test.go
Normal file
@@ -0,0 +1,86 @@
|
||||
package ui
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func newTestWriter(color bool) (*Writer, *bytes.Buffer) {
|
||||
buf := &bytes.Buffer{}
|
||||
return NewWithColor(buf, color), buf
|
||||
}
|
||||
|
||||
func TestMessageMethodsPlain(t *testing.T) {
|
||||
tests := []struct {
|
||||
method string
|
||||
fn func(*Writer)
|
||||
want string
|
||||
}{
|
||||
{"Begin", func(w *Writer) { w.Begin("starting %s", "thing") }, "》 starting thing\n"},
|
||||
{"Complete", func(w *Writer) { w.Complete("done %s", "thing") }, "》 done thing\n"},
|
||||
{"Info", func(w *Writer) { w.Info("status") }, "》 status\n"},
|
||||
{"Notice", func(w *Writer) { w.Notice("note") }, "》 note\n"},
|
||||
{"Warning", func(w *Writer) { w.Warning("oops") }, "Warning: oops\n"},
|
||||
{"Error", func(w *Writer) { w.Error("boom") }, "ERROR: boom\n"},
|
||||
{"Progress", func(w *Writer) { w.Progress("p") }, " 》 p\n"},
|
||||
{"Banner", func(w *Writer) { w.Banner("hello") }, "hello\n"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.method, func(t *testing.T) {
|
||||
w, buf := newTestWriter(false)
|
||||
tt.fn(w)
|
||||
if got := buf.String(); got != tt.want {
|
||||
t.Errorf("got %q, want %q", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestColorOutputContainsANSI(t *testing.T) {
|
||||
w, buf := newTestWriter(true)
|
||||
w.Error("boom")
|
||||
out := buf.String()
|
||||
if !strings.Contains(out, "\033[") {
|
||||
t.Errorf("expected ANSI escapes in color output, got %q", out)
|
||||
}
|
||||
if !strings.Contains(out, "ERROR: ") {
|
||||
t.Errorf("expected 'ERROR: ' text in output, got %q", out)
|
||||
}
|
||||
}
|
||||
|
||||
func TestValueFormattersPlain(t *testing.T) {
|
||||
w, _ := newTestWriter(false)
|
||||
|
||||
if got := w.Hex("0123456789abcdef0123"); got != "0123456789ab..." {
|
||||
t.Errorf("Hex long: got %q", got)
|
||||
}
|
||||
if got := w.Hex("short"); got != "short" {
|
||||
t.Errorf("Hex short: got %q", got)
|
||||
}
|
||||
if got := w.Size(1024); got != "1.0 kB" {
|
||||
t.Errorf("Size: got %q", got)
|
||||
}
|
||||
if got := w.Duration(90 * time.Second); got != "1m30s" {
|
||||
t.Errorf("Duration: got %q", got)
|
||||
}
|
||||
if got := w.Count(12345); got != "12,345" {
|
||||
t.Errorf("Count: got %q", got)
|
||||
}
|
||||
if got := w.Percent(12.34); got != "12.3%" {
|
||||
t.Errorf("Percent: got %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestValueFormattersColored(t *testing.T) {
|
||||
w, _ := newTestWriter(true)
|
||||
hex := w.Hex("0123456789abcdef0123")
|
||||
if !strings.Contains(hex, "\033[") {
|
||||
t.Errorf("expected ANSI in colored Hex output, got %q", hex)
|
||||
}
|
||||
if !strings.Contains(hex, "0123456789ab") {
|
||||
t.Errorf("expected hex content in output, got %q", hex)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user