From 9c5f503ef661542125e3f251d8346704e7a546fc Mon Sep 17 00:00:00 2001 From: PhatPhuckDave Date: Thu, 20 Nov 2025 16:14:38 +0100 Subject: [PATCH] Move all summary logic out of main into filesystem.go --- filesystem.go | 148 ++++++++++++++++++++++++++++++++++++++++++++++++++ main.go | 72 +----------------------- 2 files changed, 150 insertions(+), 70 deletions(-) diff --git a/filesystem.go b/filesystem.go index 19b918b..92b9cfa 100644 --- a/filesystem.go +++ b/filesystem.go @@ -3,6 +3,8 @@ package main import ( "fmt" "os" + "sort" + "strings" "sync" ) @@ -82,6 +84,16 @@ func (r operationRecord) resultLabel() string { return fmt.Sprintf("%sOK%s", BGreen, Reset) } +func (r operationRecord) resultLabelPlain() string { + if r.err != nil { + return "FAIL" + } + if r.dryRun { + return "DRY-RUN" + } + return "OK" +} + func (r operationRecord) formattedSource() string { if r.source == "" { return "-" @@ -89,6 +101,13 @@ func (r operationRecord) formattedSource() string { return FormatSourcePath(r.source) } +func (r operationRecord) plainSource() string { + if r.source == "" { + return "-" + } + return r.source +} + func (r operationRecord) formattedTarget() string { if r.target == "" { return "-" @@ -96,6 +115,13 @@ func (r operationRecord) formattedTarget() string { return FormatTargetPath(r.target) } +func (r operationRecord) plainTarget() string { + if r.target == "" { + return "-" + } + return r.target +} + func (r operationRecord) detail() string { if r.err != nil { return r.err.Error() @@ -245,3 +271,125 @@ func (fs *dryRunFileSystem) SummaryRecords() []operationRecord { func (fs *dryRunFileSystem) IsDryRun() bool { return true } + +func BuildSummaryLines(records []operationRecord) []string { + if len(records) == 0 { + return nil + } + + sorted := make([]operationRecord, len(records)) + copy(sorted, records) + + opOrder := map[string]int{ + opRemove: 0, + opRemoveAll: 1, + opMkdirAll: 2, + opSymlink: 3, + opHardlink: 4, + } + + sort.SliceStable(sorted, func(i, j int) bool { + ti := strings.ToLower(sorted[i].plainTarget()) + tj := strings.ToLower(sorted[j].plainTarget()) + if ti != tj { + return ti < tj + } + + si := strings.ToLower(sorted[i].plainSource()) + sj := strings.ToLower(sorted[j].plainSource()) + if si != sj { + return si < sj + } + + oi := opOrderValue(sorted[i].kind, opOrder) + oj := opOrderValue(sorted[j].kind, opOrder) + if oi != oj { + return oi < oj + } + + return sorted[i].detail() < sorted[j].detail() + }) + + header := []string{"RESULT", "OPERATION", "SOURCE", "TARGET", "DETAIL"} + widths := make([]int, len(header)) + for i, h := range header { + widths[i] = len(h) + } + + plainRows := make([][]string, len(sorted)) + coloredRows := make([][]string, len(sorted)) + for i, record := range sorted { + detail := record.detail() + if detail == "" { + detail = "-" + } + + plain := []string{ + record.resultLabelPlain(), + record.kindLabel(), + record.plainSource(), + record.plainTarget(), + detail, + } + colored := []string{ + record.resultLabel(), + record.kindLabel(), + record.formattedSource(), + record.formattedTarget(), + detail, + } + + plainRows[i] = plain + coloredRows[i] = colored + + for j, val := range plain { + if val == "" { + val = "-" + } + if len(val) > widths[j] { + widths[j] = len(val) + } + } + } + + lines := make([]string, 0, len(sorted)+1) + lines = append(lines, formatSummaryRow(header, header, widths)) + for i := range coloredRows { + lines = append(lines, formatSummaryRow(coloredRows[i], plainRows[i], widths)) + } + + return lines +} + +func formatSummaryRow(colored, plain []string, widths []int) string { + var b strings.Builder + for i := range colored { + p := plain[i] + if p == "" { + p = "-" + } + col := colored[i] + if col == "" { + col = p + } + pad := widths[i] - len(p) + if pad < 0 { + pad = 0 + } + b.WriteString(col) + if pad > 0 { + b.WriteString(strings.Repeat(" ", pad)) + } + if i < len(colored)-1 { + b.WriteString(" ") + } + } + return b.String() +} + +func opOrderValue(kind string, order map[string]int) int { + if v, ok := order[kind]; ok { + return v + } + return len(order) +} diff --git a/main.go b/main.go index 9c83e80..87cd663 100644 --- a/main.go +++ b/main.go @@ -8,9 +8,7 @@ import ( "log" "os" "path/filepath" - "regexp" "runtime/debug" - "strings" "sync/atomic" utils "git.site.quack-lab.dev/dave/cyutils" @@ -187,77 +185,11 @@ func processInstructions(instructions chan *LinkInstruction) int32 { } func printSummary(fs FileSystem) { - records := fs.SummaryRecords() - if len(records) == 0 { - return - } - - header := []string{"RESULT", "OPERATION", "SOURCE", "TARGET", "DETAIL"} - widths := make([]int, len(header)) - for i, h := range header { - widths[i] = len(h) - } - - type row struct { - values []string - err error - } - rows := make([]row, len(records)) - - for i, record := range records { - values := []string{ - record.resultLabel(), - record.kindLabel(), - FormatSourcePath(record.source), - FormatTargetPath(record.target), - record.detail(), - } - rows[i] = row{values: values, err: record.err} - - for j, val := range values { - if l := visibleLength(val); l > widths[j] { - widths[j] = l - } - } - } - + lines := BuildSummaryLines(fs.SummaryRecords()) LogInfo("Summary:") - var lineBuilder strings.Builder - - writeSummaryRow(&lineBuilder, header, widths) - for _, line := range strings.Split(strings.TrimRight(lineBuilder.String(), "\n"), "\n") { + for _, line := range lines { LogInfo("%s", line) } - - for _, r := range rows { - lineBuilder.Reset() - writeSummaryRow(&lineBuilder, r.values, widths) - line := strings.TrimRight(lineBuilder.String(), "\n") - LogInfo("%s", line) - } -} - -func writeSummaryRow(b *strings.Builder, cols []string, widths []int) { - for i, val := range cols { - if val == "" { - val = "-" - } - b.WriteString(val) - pad := widths[i] - visibleLength(val) - if pad < 0 { - pad = 0 - } - if i < len(cols)-1 { - b.WriteString(strings.Repeat(" ", pad+2)) - } - } - b.WriteByte('\n') -} - -var ansiRegexp = regexp.MustCompile(`\x1b\[[0-9;]*m`) - -func visibleLength(s string) int { - return len(ansiRegexp.ReplaceAllString(s, "")) } func IsPipeInput() bool {