Files
renamer/internal/remove/preview.go

107 lines
2.5 KiB
Go

package remove
import (
"context"
"fmt"
"io"
"os"
"path/filepath"
)
// PlannedOperation represents a rename that will be executed during apply.
type PlannedOperation struct {
Result Result
TargetRelative string
TargetAbsolute string
}
// Preview computes removals and writes a human-readable summary to out.
func Preview(ctx context.Context, req *Request, parsed ParseArgsResult, out io.Writer) (Summary, []PlannedOperation, error) {
summary := NewSummary()
for _, dup := range parsed.Duplicates {
summary.AddDuplicate(dup)
}
planned := make([]PlannedOperation, 0)
plannedTargets := make(map[string]string)
err := Traverse(ctx, req, func(candidate Candidate) error {
res := ApplyTokens(candidate, parsed.Tokens)
summary.RecordCandidate(res)
if !res.Changed {
return nil
}
if res.ProposedName == "" {
summary.AddEmpty(candidate.RelativePath)
return nil
}
dir := filepath.Dir(candidate.RelativePath)
if dir == "." {
dir = ""
}
targetRelative := res.ProposedName
if dir != "" {
targetRelative = filepath.ToSlash(filepath.Join(dir, res.ProposedName))
} else {
targetRelative = filepath.ToSlash(res.ProposedName)
}
if targetRelative == candidate.RelativePath {
return nil
}
if existing, ok := plannedTargets[targetRelative]; ok && existing != candidate.RelativePath {
summary.AddConflict(ConflictDetail{
OriginalPath: candidate.RelativePath,
ProposedPath: targetRelative,
Reason: "duplicate target generated in preview",
})
return nil
}
targetAbsolute := filepath.Join(req.WorkingDir, filepath.FromSlash(targetRelative))
if info, err := os.Stat(targetAbsolute); err == nil {
if candidate.OriginalPath != targetAbsolute {
if origInfo, origErr := os.Stat(candidate.OriginalPath); origErr == nil && os.SameFile(info, origInfo) {
goto recordOperation
}
reason := "target already exists"
if info.IsDir() {
reason = "target directory already exists"
}
summary.AddConflict(ConflictDetail{
OriginalPath: candidate.RelativePath,
ProposedPath: targetRelative,
Reason: reason,
})
return nil
}
}
plannedTargets[targetRelative] = candidate.RelativePath
if out != nil {
fmt.Fprintf(out, "%s -> %s\n", candidate.RelativePath, targetRelative)
}
recordOperation:
planned = append(planned, PlannedOperation{
Result: res,
TargetRelative: targetRelative,
TargetAbsolute: targetAbsolute,
})
return nil
})
if err != nil {
return Summary{}, nil, err
}
return summary, planned, nil
}