feat: implement remove command with sequential removals
This commit is contained in:
106
internal/remove/preview.go
Normal file
106
internal/remove/preview.go
Normal file
@@ -0,0 +1,106 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user