aboutsummaryrefslogtreecommitdiff
path: root/cmd/krino/undo.go
diff options
context:
space:
mode:
authorLukasz Kasprzak <lukas@labunix.xyz>2026-09-14 11:05:13 +0200
committerLukasz Kasprzak <lukas@labunix.xyz>2026-09-14 11:05:13 +0200
commitadc3410395771609d2db5ee5ae2b9da71115c5ca (patch)
tree453def41e18f607332be1088920f9d0c563603a3 /cmd/krino/undo.go
parent0468ce38470aa3ae8092b92d4f77e72d25dfa108 (diff)
downloadkrino-adc3410395771609d2db5ee5ae2b9da71115c5ca.tar.gz
krino-adc3410395771609d2db5ee5ae2b9da71115c5ca.zip
krino: coloured output, and --no-color
One palette type styles the plan table, warnings, headers, outcome counts, prompt keys, undo's refused steps and krino log's (undone), from the 16-colour ANSI palette plus bold and faint only. Widths are measured on the plain text, so columns line up; with colour off the output is unchanged. --no-color works before or after any subcommand, as NO_COLOR does. The two search-and-replace colourings are gone.
Diffstat (limited to 'cmd/krino/undo.go')
-rw-r--r--cmd/krino/undo.go44
1 files changed, 22 insertions, 22 deletions
diff --git a/cmd/krino/undo.go b/cmd/krino/undo.go
index c773f9a..cc65eea 100644
--- a/cmd/krino/undo.go
+++ b/cmd/krino/undo.go
@@ -61,6 +61,7 @@ func cmdUndo(g *globals, args []string, stdout, stderr io.Writer) int {
printDiags(stderr, errs)
return 2
}
+ p := palette{on: colourOn(g, stdout)}
// Spec §8.4/§10: with neither -y nor -n, krino asks; a non-terminal
// stdin would just hang, so it refuses instead - the same check
@@ -140,10 +141,10 @@ func cmdUndo(g *globals, args []string, stdout, stderr io.Writer) int {
}()
}
- fmt.Fprintf(stdout, "krino: undo %s\n", up.Run)
+ fmt.Fprintln(stdout, p.bold("krino: undo "+up.Run))
var buf bytes.Buffer
printUndoPlan(&buf, up)
- text := colourRefused(buf.String(), tui.Colour(stdout))
+ text := colourRefused(buf.String(), p)
// Ruling 6 (Task 7), carried over: the plan goes through tui.Page for
// -n as much as for -y and the interactive path.
if err := tui.Page(stdout, text); err != nil {
@@ -169,7 +170,7 @@ func cmdUndo(g *globals, args []string, stdout, stderr io.Writer) int {
approved, action = approveAllUndo(up.Files), 'a'
} else {
var rerr error
- approved, action, rerr = reviewUndoDir(stdout, up.Files)
+ approved, action, rerr = reviewUndoDir(stdout, up.Files, p)
if rerr != nil {
fmt.Fprintf(stderr, "krino: %v\n", rerr)
return 1
@@ -214,7 +215,7 @@ func cmdUndo(g *globals, args []string, stdout, stderr io.Writer) int {
fmt.Fprintf(stderr, "krino: %v\n", aerr)
return 1
}
- fmt.Fprintf(stdout, "%d applied · %d failed · %d declined\n", res.Applied, res.Failed, res.Declined)
+ fmt.Fprintln(stdout, outcome(p, res.Applied, res.Failed, res.Declined))
if ctx.Err() != nil {
return 130
@@ -312,9 +313,10 @@ func finalizeUndoPlan(up *engine.UndoPlan, approved map[int]bool) *engine.UndoPl
}
// reviewUndoDir drives the interactive review over the real terminal,
-// mirroring review.go's reviewDir for the forward path.
-func reviewUndoDir(out io.Writer, files []engine.UndoFile) (map[int]bool, rune, error) {
- return reviewUndoFiles(keyReader{stdin}, out, files)
+// mirroring review.go's reviewDir for the forward path; p styles the
+// prompts.
+func reviewUndoDir(out io.Writer, files []engine.UndoFile, p palette) (map[int]bool, rune, error) {
+ return reviewUndoFiles(keyReader{stdin}, out, files, p)
}
// reviewUndoFiles is spec §10's approval flow for an undo plan: the
@@ -331,8 +333,8 @@ func reviewUndoDir(out io.Writer, files []engine.UndoFile) (map[int]bool, rune,
// name). action is always one of 'a', 'c', 's' or 'q', with the same [q]
// folding rule reviewChains uses: a [c] session's own [q] becomes the same
// top-level 'q', and approved is emptied to match.
-func reviewUndoFiles(in io.Reader, out io.Writer, files []engine.UndoFile) (map[int]bool, rune, error) {
- fmt.Fprint(out, "\n[a] apply all [c] choose per file [s] skip [q] quit\n")
+func reviewUndoFiles(in io.Reader, out io.Writer, files []engine.UndoFile, p palette) (map[int]bool, rune, error) {
+ fmt.Fprint(out, "\n"+p.keys("[a] apply all [c] choose per file [s] skip [q] quit")+"\n")
for {
key, err := readKey(in)
if err != nil {
@@ -346,7 +348,7 @@ func reviewUndoFiles(in io.Reader, out io.Writer, files []engine.UndoFile) (map[
case 'q':
return map[int]bool{}, 'q', nil
case 'c':
- approved, quit, err := reviewUndoPerFile(in, out, files)
+ approved, quit, err := reviewUndoPerFile(in, out, files, p)
if err != nil {
return nil, 0, err
}
@@ -365,7 +367,7 @@ func reviewUndoFiles(in io.Reader, out io.Writer, files []engine.UndoFile) (map[
// its reason and reverses nothing of it regardless of anything chosen here
// - but it still gets its own [i/N] line, so the numbering accounts for
// every file in the plan, not just the reversible ones.
-func reviewUndoPerFile(in io.Reader, out io.Writer, files []engine.UndoFile) (approved map[int]bool, quit bool, err error) {
+func reviewUndoPerFile(in io.Reader, out io.Writer, files []engine.UndoFile, p palette) (approved map[int]bool, quit bool, err error) {
approved = map[int]bool{}
yesRest := false
for i, f := range files {
@@ -382,7 +384,7 @@ func reviewUndoPerFile(in io.Reader, out io.Writer, files []engine.UndoFile) (ap
continue
}
- fmt.Fprint(out, " [y] yes [n] no [a] yes to this and all remaining [d] done, apply chosen so far [q] quit, apply nothing\n")
+ fmt.Fprint(out, " "+p.keys("[y] yes [n] no [a] yes to this and all remaining [d] done, apply chosen so far [q] quit, apply nothing")+"\n")
for {
key, kerr := readKey(in)
if kerr != nil {
@@ -520,16 +522,14 @@ func printUndoTable(w io.Writer, files []engine.UndoFile) {
}
}
-// colourRefused highlights "refused:" in the terminal's own ANSI red (bold,
-// slot 1 - never hex), the undo counterpart of sort.go's
-// colourDeletePermanently: the one thing an undo plan singles out for
-// attention is the file or step nothing will be reversed for. Same
-// no-op-when-plain guarantee: with colour false this never touches the
-// text, which is what keeps every escape byte out of a plan piped to a
-// file or read by another tool.
-func colourRefused(text string, colour bool) string {
- if !colour {
+// colourRefused styles every "refused:" in an undo plan bold red (spec
+// §8.2): the one thing an undo plan singles out is the file or step nothing
+// will be reversed for. With the plain palette the text is untouched, which
+// keeps every escape byte out of a plan piped to a file or read by another
+// tool.
+func colourRefused(text string, p palette) string {
+ if !p.on {
return text
}
- return strings.ReplaceAll(text, "refused:", "\x1b[1;31mrefused:\x1b[0m")
+ return strings.ReplaceAll(text, "refused:", p.alarm("refused:"))
}