feat: add replace subcommand with multi-pattern support

This commit is contained in:
Rogee
2025-10-29 17:46:54 +08:00
parent fa57af8a26
commit ceea09f7be
42 changed files with 1848 additions and 14 deletions

View File

@@ -0,0 +1,80 @@
package replace
import "sort"
// ConflictDetail describes a rename that could not be applied.
type ConflictDetail struct {
OriginalPath string
ProposedPath string
Reason string
}
// Summary aggregates metrics for previews, applies, and ledger entries.
type Summary struct {
TotalCandidates int
ChangedCount int
PatternMatches map[string]int
Conflicts []ConflictDetail
Duplicates []string
EmptyReplacement bool
}
// NewSummary constructs an initialized summary.
func NewSummary() Summary {
return Summary{
PatternMatches: make(map[string]int),
}
}
// AddDuplicate records a duplicate pattern supplied by the user.
func (s *Summary) AddDuplicate(pattern string) {
if pattern == "" {
return
}
s.Duplicates = append(s.Duplicates, pattern)
}
// AddResult incorporates an individual candidate replacement result.
// RecordCandidate updates aggregate counts for a processed candidate and any matches.
func (s *Summary) RecordCandidate(res Result) {
s.TotalCandidates++
if !res.Changed {
return
}
s.ChangedCount++
for pattern, count := range res.Matches {
s.PatternMatches[pattern] += count
}
}
// AddConflict appends a conflict detail to the summary.
func (s *Summary) AddConflict(conflict ConflictDetail) {
s.Conflicts = append(s.Conflicts, conflict)
}
// SortedDuplicates returns de-duplicated duplicates list for reporting.
func (s *Summary) SortedDuplicates() []string {
if len(s.Duplicates) == 0 {
return nil
}
copyList := make([]string, 0, len(s.Duplicates))
seen := make(map[string]struct{}, len(s.Duplicates))
for _, dup := range s.Duplicates {
if _, ok := seen[dup]; ok {
continue
}
seen[dup] = struct{}{}
copyList = append(copyList, dup)
}
sort.Strings(copyList)
return copyList
}
// ReplacementWasEmpty records whether the replacement string is empty and returns true.
func (s *Summary) ReplacementWasEmpty(replacement string) bool {
if replacement == "" {
s.EmptyReplacement = true
return true
}
return false
}