Move all summary logic out of main into filesystem.go

This commit is contained in:
2025-11-20 16:14:38 +01:00
parent 2586386cc3
commit 9c5f503ef6
2 changed files with 150 additions and 70 deletions

View File

@@ -3,6 +3,8 @@ package main
import ( import (
"fmt" "fmt"
"os" "os"
"sort"
"strings"
"sync" "sync"
) )
@@ -82,6 +84,16 @@ func (r operationRecord) resultLabel() string {
return fmt.Sprintf("%sOK%s", BGreen, Reset) 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 { func (r operationRecord) formattedSource() string {
if r.source == "" { if r.source == "" {
return "-" return "-"
@@ -89,6 +101,13 @@ func (r operationRecord) formattedSource() string {
return FormatSourcePath(r.source) return FormatSourcePath(r.source)
} }
func (r operationRecord) plainSource() string {
if r.source == "" {
return "-"
}
return r.source
}
func (r operationRecord) formattedTarget() string { func (r operationRecord) formattedTarget() string {
if r.target == "" { if r.target == "" {
return "-" return "-"
@@ -96,6 +115,13 @@ func (r operationRecord) formattedTarget() string {
return FormatTargetPath(r.target) return FormatTargetPath(r.target)
} }
func (r operationRecord) plainTarget() string {
if r.target == "" {
return "-"
}
return r.target
}
func (r operationRecord) detail() string { func (r operationRecord) detail() string {
if r.err != nil { if r.err != nil {
return r.err.Error() return r.err.Error()
@@ -245,3 +271,125 @@ func (fs *dryRunFileSystem) SummaryRecords() []operationRecord {
func (fs *dryRunFileSystem) IsDryRun() bool { func (fs *dryRunFileSystem) IsDryRun() bool {
return true 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)
}

72
main.go
View File

@@ -8,9 +8,7 @@ import (
"log" "log"
"os" "os"
"path/filepath" "path/filepath"
"regexp"
"runtime/debug" "runtime/debug"
"strings"
"sync/atomic" "sync/atomic"
utils "git.site.quack-lab.dev/dave/cyutils" utils "git.site.quack-lab.dev/dave/cyutils"
@@ -187,77 +185,11 @@ func processInstructions(instructions chan *LinkInstruction) int32 {
} }
func printSummary(fs FileSystem) { func printSummary(fs FileSystem) {
records := fs.SummaryRecords() lines := BuildSummaryLines(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
}
}
}
LogInfo("Summary:") LogInfo("Summary:")
var lineBuilder strings.Builder for _, line := range lines {
writeSummaryRow(&lineBuilder, header, widths)
for _, line := range strings.Split(strings.TrimRight(lineBuilder.String(), "\n"), "\n") {
LogInfo("%s", line) 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 { func IsPipeInput() bool {