93 lines
2.8 KiB
Go
93 lines
2.8 KiB
Go
package extension
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"sort"
|
|
"strings"
|
|
)
|
|
|
|
// Preview generates a summary and planned operations for an extension normalization run.
|
|
func Preview(ctx context.Context, req *ExtensionRequest, out io.Writer) (*ExtensionSummary, []PlannedRename, error) {
|
|
if req == nil {
|
|
return nil, nil, errors.New("extension request cannot be nil")
|
|
}
|
|
|
|
plan, err := BuildPlan(ctx, req)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
summary := plan.Summary
|
|
|
|
for _, dup := range req.DuplicateSources {
|
|
summary.AddWarning(fmt.Sprintf("duplicate source extension ignored: %s", dup))
|
|
}
|
|
for _, noop := range req.NoOpSources {
|
|
summary.AddWarning(fmt.Sprintf("source extension matches target and is skipped: %s", noop))
|
|
}
|
|
|
|
if summary.TotalCandidates == 0 {
|
|
summary.AddWarning("no candidates found for provided extensions")
|
|
}
|
|
|
|
if out != nil {
|
|
// Ensure deterministic ordering for preview output.
|
|
sort.SliceStable(summary.Entries, func(i, j int) bool {
|
|
if summary.Entries[i].OriginalPath == summary.Entries[j].OriginalPath {
|
|
return summary.Entries[i].ProposedPath < summary.Entries[j].ProposedPath
|
|
}
|
|
return summary.Entries[i].OriginalPath < summary.Entries[j].OriginalPath
|
|
})
|
|
|
|
conflictReasons := make(map[string]string, len(summary.Conflicts))
|
|
for _, conflict := range summary.Conflicts {
|
|
key := fmt.Sprintf("%s->%s", conflict.OriginalPath, conflict.ProposedPath)
|
|
conflictReasons[key] = conflict.Reason
|
|
}
|
|
|
|
for _, entry := range summary.Entries {
|
|
switch entry.Status {
|
|
case PreviewStatusChanged:
|
|
if entry.ProposedPath == entry.OriginalPath {
|
|
fmt.Fprintf(out, "%s (pending extension update)\n", entry.OriginalPath)
|
|
} else {
|
|
fmt.Fprintf(out, "%s -> %s\n", entry.OriginalPath, entry.ProposedPath)
|
|
}
|
|
case PreviewStatusNoChange:
|
|
fmt.Fprintf(out, "%s (no change)\n", entry.OriginalPath)
|
|
case PreviewStatusSkipped:
|
|
reason := conflictReasons[fmt.Sprintf("%s->%s", entry.OriginalPath, entry.ProposedPath)]
|
|
if reason == "" {
|
|
reason = "skipped"
|
|
}
|
|
fmt.Fprintf(out, "%s -> %s (skipped: %s)\n", entry.OriginalPath, entry.ProposedPath, reason)
|
|
default:
|
|
fmt.Fprintf(out, "%s (status: %s)\n", entry.OriginalPath, entry.Status)
|
|
}
|
|
}
|
|
|
|
if summary.TotalCandidates > 0 {
|
|
fmt.Fprintf(out, "\nSummary: %d candidates, %d will change, %d already target extension\n",
|
|
summary.TotalCandidates, summary.TotalChanged, summary.NoChange)
|
|
} else {
|
|
fmt.Fprintln(out, "No candidates found.")
|
|
}
|
|
|
|
if len(summary.Warnings) > 0 {
|
|
fmt.Fprintln(out)
|
|
for _, warning := range summary.Warnings {
|
|
if !strings.HasPrefix(strings.ToLower(warning), "warning:") {
|
|
fmt.Fprintf(out, "Warning: %s\n", warning)
|
|
} else {
|
|
fmt.Fprintln(out, warning)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return summary, plan.Operations, nil
|
|
}
|