diff options
| author | Lukasz Kasprzak <lukas@labunix.xyz> | 2026-09-14 16:48:53 +0200 |
|---|---|---|
| committer | Lukasz Kasprzak <lukas@labunix.xyz> | 2026-09-14 16:48:53 +0200 |
| commit | e64f162bffa6ced845bac4d3015954c41c4b4a3e (patch) | |
| tree | 87b8fb52b476644ec99cd091bb34ac6521ae9b33 /cmd/krino/review.go | |
| parent | 0b5d0eb92c5be2f0ddb2fa73990f31e5654e57fe (diff) | |
| download | krino-e64f162bffa6ced845bac4d3015954c41c4b4a3e.tar.gz krino-e64f162bffa6ced845bac4d3015954c41c4b4a3e.zip | |
krino: 0.0.6 — w applies and quits, choices echoed in redv0.0.6
Diffstat (limited to 'cmd/krino/review.go')
| -rw-r--r-- | cmd/krino/review.go | 79 |
1 files changed, 59 insertions, 20 deletions
diff --git a/cmd/krino/review.go b/cmd/krino/review.go index 29b3915..36231be 100644 --- a/cmd/krino/review.go +++ b/cmd/krino/review.go @@ -49,13 +49,16 @@ func (k keyReader) Read(p []byte) (int, error) { // // [y] yes [n] no [a] yes to this and all remaining [t] trash [d] delete permanently [w] write, apply chosen so far [q] quit, apply nothing // -// prompt. action is always one of 'a', 'c', 's' or 'q': a [c] session's own -// [q] ("quit, apply nothing") folds into the same 'q' the caller already -// handles for the top-level menu, and approved is emptied to match - even a -// file already marked yes in that session is discarded, per spec §8.3's -// wording ("apply nothing"), unlike [w] ("apply chosen so far"), which -// keeps it. replaced holds the files [t] or [d] chose to trash or delete -// instead of what the rules planned; replaceChains applies it. root is the directory being reviewed - passed only to +// prompt. action is always one of 'a', 'c', 's', 'w' or 'q': a [c] +// session's own [q] ("quit, apply nothing") folds into the same 'q' the +// caller already handles for the top-level menu, and approved is emptied to +// match - even a file already marked yes in that session is discarded, per +// spec §8.3's wording ("apply nothing"), unlike [w] ("write, apply chosen so +// far"), which keeps it and is returned as 'w': the caller applies what was +// decided and stops. approved holds every file decided, true for yes and +// false for no; a file [w] left unreviewed is absent. replaced holds the +// files [t] or [d] chose to trash or delete instead of what the rules +// planned; replaceChains applies it. Enter is ignored at both prompts. root is the directory being reviewed - passed only to // reviewPerFile's destination rendering (review finding 1, fix round // 2026-09-12); nothing here uses it directly. func reviewChains(in io.Reader, out io.Writer, chains []plan.Chain, root string, p palette) (map[string]bool, map[string]plan.Kind, rune, error) { @@ -69,6 +72,8 @@ func reviewChains(in io.Reader, out io.Writer, chains []plan.Chain, root string, return nil, nil, 0, err } switch key { + case '\r', '\n': + continue case 'a': return approveAll(chains), nil, 'a', nil case 's': @@ -76,12 +81,15 @@ func reviewChains(in io.Reader, out io.Writer, chains []plan.Chain, root string, case 'q': return map[string]bool{}, nil, 'q', nil case 'c': - approved, replaced, quit, err := reviewPerFile(in, out, chains, root, p) + approved, replaced, end, err := reviewPerFile(in, out, chains, root, p) if err != nil { return nil, nil, 0, err } - if quit { + switch end { + case 'q': return map[string]bool{}, nil, 'q', nil + case 'w': + return approved, replaced, 'w', nil } return approved, replaced, 'c', nil default: @@ -97,14 +105,14 @@ func reviewChains(in io.Reader, out io.Writer, chains []plan.Chain, root string, // just that file; [a] approves it and every remaining file without asking // again; [t] and [d] approve it with its chain replaced by one trash or // permanent delete step, [d] only after a y to its own confirmation (any -// other key asks about the same file again); [w] stops asking and applies -// whatever was already chosen, -// declining the rest; [q] aborts the review entirely, discarding even files -// already marked yes - reported back to reviewChains via quit=true. root is +// other key asks about the same file again); every choice is echoed in red +// on its own line; [w] stops asking and applies whatever was already chosen, +// leaving the rest unreviewed - reported back as end 'w'; [q] aborts the +// review entirely, discarding even files already marked yes - end 'q'. root is // passed down so a destination inside root renders root-relative and one // outside it renders ~-abbreviated, exactly as in the plan (review finding // 1, fix round 2026-09-12). -func reviewPerFile(in io.Reader, out io.Writer, chains []plan.Chain, root string, p palette) (approved map[string]bool, replaced map[string]plan.Kind, quit bool, err error) { +func reviewPerFile(in io.Reader, out io.Writer, chains []plan.Chain, root string, p palette) (approved map[string]bool, replaced map[string]plan.Kind, end rune, err error) { approved = map[string]bool{} replaced = map[string]plan.Kind{} yesRest := false @@ -128,24 +136,31 @@ func reviewPerFile(in io.Reader, out io.Writer, chains []plan.Chain, root string } key, kerr := readKey(in) if kerr != nil { - return nil, nil, false, kerr + return nil, nil, 0, kerr } + var choice string switch key { + case '\r', '\n': + continue case 'y': approved[c.File.Rel] = true + choice = "yes" case 'n': - // leave unapproved + approved[c.File.Rel] = false + choice = "no" case 'a': approved[c.File.Rel] = true yesRest = true + choice = "yes, and all remaining" case 't': approved[c.File.Rel] = true replaced[c.File.Rel] = plan.Trash + choice = "trash" case 'd': fmt.Fprintf(out, " delete %s permanently? [y/N] ", c.File.Rel) confirm, kerr := readKey(in) if kerr != nil { - return nil, nil, false, kerr + return nil, nil, 0, kerr } fmt.Fprintln(out) if confirm != 'y' { @@ -155,18 +170,42 @@ func reviewPerFile(in io.Reader, out io.Writer, chains []plan.Chain, root string } approved[c.File.Rel] = true replaced[c.File.Rel] = plan.DeletePermanent + choice = "DELETE permanently" case 'w': - return approved, replaced, false, nil + return approved, replaced, 'w', nil case 'q': - return nil, nil, true, nil + return nil, nil, 'q', nil default: fmt.Fprintf(out, "%q is not y, n, a, t, d, w or q\n", key) continue } + fmt.Fprintln(out, " "+p.bad("→ "+choice)) break } } - return approved, replaced, false, nil + return approved, replaced, 0, nil +} + +// reviewedChains returns the chains of the files decided holds, yes or no, +// in order: after [w], only these go to Apply, so a file never reviewed is +// neither applied nor logged as declined. +func reviewedChains(chains []plan.Chain, decided map[string]bool) []plan.Chain { + var out []plan.Chain + for _, c := range chains { + if _, ok := decided[c.File.Rel]; ok { + out = append(out, c) + } + } + return out +} + +// withNotReviewed appends to an outcome line how many files [w] left +// unreviewed, when any were. +func withNotReviewed(line string, n int) string { + if n == 0 { + return line + } + return fmt.Sprintf("%s · %d not reviewed", line, n) } // perFileKeys is the per-file prompt of spec §8.3, wrapped to the terminal |
