package main import ( "fmt" "os" "sort" "strings" "sync" ) var filesystem FileSystem = NewRealFileSystem() // FileSystem abstracts filesystem operations so we can swap implementations type FileSystem interface { Remove(path string) error RemoveAll(path string) error MkdirAll(path string, perm os.FileMode) error Symlink(source, target string) error Link(source, target string) error RecordLinkAttempt(kind string, source, target string, err error, dryRun bool) SummaryLines() []string SummaryRecords() []operationRecord IsDryRun() bool } const ( opRemove = "remove" opRemoveAll = "remove_all" opMkdirAll = "mkdir_all" opSymlink = "symlink" opHardlink = "hardlink" ) type operationRecord struct { kind string source string target string err error dryRun bool } func (r operationRecord) summaryLine() (string, bool) { detail := r.detail() if detail != "" { return fmt.Sprintf("%s %s → %s (%s) %s", r.kindLabel(), r.formattedSource(), r.formattedTarget(), r.resultLabel(), detail), true } return fmt.Sprintf("%s %s → %s (%s)", r.kindLabel(), r.formattedSource(), r.formattedTarget(), r.resultLabel()), true } func (r operationRecord) kindLabel() string { switch r.kind { case opSymlink: return "Symlink" case opHardlink: return "Hardlink" case opRemove: return "Remove" case opRemoveAll: return "RemoveAll" case opMkdirAll: return "MkdirAll" default: return r.kind } } func (r operationRecord) resultLabel() string { if r.err != nil { return fmt.Sprintf("%sFAIL%s", BRed, Reset) } if r.dryRun { return fmt.Sprintf("%sDRY-RUN%s", BCyan, 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 { if r.source == "" { return "-" } 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 "-" } 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() } if r.dryRun { return "dry-run" } return "" } type baseFileSystem struct { mu sync.Mutex operations []operationRecord } func (b *baseFileSystem) addOperation(kind, source, target string, err error, dryRun bool) { b.mu.Lock() defer b.mu.Unlock() b.operations = append(b.operations, operationRecord{ kind: kind, source: source, target: target, err: err, dryRun: dryRun, }) } func (b *baseFileSystem) snapshot() []operationRecord { b.mu.Lock() defer b.mu.Unlock() ops := make([]operationRecord, len(b.operations)) copy(ops, b.operations) return ops } func summarizeOperations(records []operationRecord) []string { var lines []string for _, record := range records { if line, ok := record.summaryLine(); ok { lines = append(lines, line) } } return lines } type realFileSystem struct { baseFileSystem } // NewRealFileSystem returns a filesystem implementation that writes to disk func NewRealFileSystem() FileSystem { return &realFileSystem{} } func (fs *realFileSystem) Remove(path string) error { err := os.Remove(path) fs.addOperation(opRemove, "", path, err, false) return err } func (fs *realFileSystem) RemoveAll(path string) error { err := os.RemoveAll(path) fs.addOperation(opRemoveAll, "", path, err, false) return err } func (fs *realFileSystem) MkdirAll(path string, perm os.FileMode) error { err := os.MkdirAll(path, perm) fs.addOperation(opMkdirAll, "", path, err, false) return err } func (fs *realFileSystem) Symlink(source, target string) error { err := os.Symlink(source, target) fs.RecordLinkAttempt(opSymlink, source, target, err, false) return err } func (fs *realFileSystem) Link(source, target string) error { err := os.Link(source, target) fs.RecordLinkAttempt(opHardlink, source, target, err, false) return err } func (fs *realFileSystem) RecordLinkAttempt(kind string, source, target string, err error, dryRun bool) { fs.addOperation(kind, source, target, err, dryRun) } func (fs *realFileSystem) SummaryLines() []string { return summarizeOperations(fs.snapshot()) } func (fs *realFileSystem) SummaryRecords() []operationRecord { return fs.snapshot() } func (fs *realFileSystem) IsDryRun() bool { return false } type dryRunFileSystem struct { baseFileSystem } // NewDryRunFileSystem returns a filesystem implementation that only records operations func NewDryRunFileSystem() FileSystem { return &dryRunFileSystem{} } func (fs *dryRunFileSystem) Remove(path string) error { fs.addOperation(opRemove, "", path, nil, true) return nil } func (fs *dryRunFileSystem) RemoveAll(path string) error { fs.addOperation(opRemoveAll, "", path, nil, true) return nil } func (fs *dryRunFileSystem) MkdirAll(path string, perm os.FileMode) error { fs.addOperation(opMkdirAll, "", path, nil, true) return nil } func (fs *dryRunFileSystem) Symlink(source, target string) error { fs.RecordLinkAttempt(opSymlink, source, target, nil, true) return nil } func (fs *dryRunFileSystem) Link(source, target string) error { fs.RecordLinkAttempt(opHardlink, source, target, nil, true) return nil } func (fs *dryRunFileSystem) RecordLinkAttempt(kind string, source, target string, err error, dryRun bool) { fs.addOperation(kind, source, target, err, dryRun) } func (fs *dryRunFileSystem) SummaryLines() []string { return summarizeOperations(fs.snapshot()) } func (fs *dryRunFileSystem) SummaryRecords() []operationRecord { return fs.snapshot() } 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) }