feat: implement remove command with sequential removals

This commit is contained in:
Rogee
2025-10-29 18:59:55 +08:00
parent 446bd46b95
commit f66c59fd57
31 changed files with 986 additions and 110 deletions

View File

@@ -2,112 +2,98 @@ package remove
import "sort"
// Summary aggregates results across preview/apply phases.
// ConflictDetail describes a rename that cannot proceed.
type ConflictDetail struct {
OriginalPath string
ProposedPath string
Reason string
}
// Summary aggregates preview/apply metrics for reporting and ledger metadata.
type Summary struct {
totalCandidates int
changedCount int
conflicts []Conflict
empties []string
tokenMatches map[string]int
duplicates []string
TotalCandidates int
ChangedCount int
TokenMatches map[string]int
Conflicts []ConflictDetail
Empties []string
Duplicates []string
}
// Conflict describes a rename conflict detected during planning.
type Conflict struct {
Original string
Proposed string
Reason string
}
// NewSummary constructs a ready-to-use Summary.
// NewSummary constructs an initialized summary instance.
func NewSummary() Summary {
return Summary{
tokenMatches: make(map[string]int),
conflicts: make([]Conflict, 0),
empties: make([]string, 0),
duplicates: make([]string, 0),
TokenMatches: make(map[string]int),
}
}
// RecordCandidate increments the total candidate count.
func (s *Summary) RecordCandidate() {
s.totalCandidates++
// RecordCandidate updates aggregate counts based on a candidate result.
func (s *Summary) RecordCandidate(res Result) {
s.TotalCandidates++
if !res.Changed {
return
}
s.ChangedCount++
for token, count := range res.Matches {
s.TokenMatches[token] += count
}
}
// RecordChange increments changed items.
func (s *Summary) RecordChange() {
s.changedCount++
// AddConflict registers a conflict for reporting.
func (s *Summary) AddConflict(conflict ConflictDetail) {
s.Conflicts = append(s.Conflicts, conflict)
}
// AddTokenMatch records the number of matches for a token.
func (s *Summary) AddTokenMatch(token string, count int) {
s.tokenMatches[token] += count
}
// AddConflict registers a detected conflict.
func (s *Summary) AddConflict(c Conflict) {
s.conflicts = append(s.conflicts, c)
}
// AddEmpty registers a path skipped due to empty result names.
// AddEmpty records a path whose resulting name would be empty.
func (s *Summary) AddEmpty(path string) {
s.empties = append(s.empties, path)
s.Empties = append(s.Empties, path)
}
// AddDuplicate tracks duplicate tokens encountered during parsing.
// AddDuplicate stores duplicate tokens captured during parsing.
func (s *Summary) AddDuplicate(token string) {
s.duplicates = append(s.duplicates, token)
if token == "" {
return
}
s.Duplicates = append(s.Duplicates, token)
}
// TotalCandidates returns how many items were considered.
func (s Summary) TotalCandidates() int {
return s.totalCandidates
// SortedDuplicates returns unique duplicate tokens sorted for deterministic output.
func (s *Summary) SortedDuplicates() []string {
if len(s.Duplicates) == 0 {
return nil
}
seen := make(map[string]struct{}, len(s.Duplicates))
result := make([]string, 0, len(s.Duplicates))
for _, dup := range s.Duplicates {
if _, ok := seen[dup]; ok {
continue
}
seen[dup] = struct{}{}
result = append(result, dup)
}
sort.Strings(result)
return result
}
// ChangedCount returns the number of items whose names changed.
func (s Summary) ChangedCount() int {
return s.changedCount
}
// Conflicts returns a copy of conflict info.
func (s Summary) Conflicts() []Conflict {
out := make([]Conflict, len(s.conflicts))
copy(out, s.conflicts)
return out
}
// Empties returns paths skipped for empty basename results.
func (s Summary) Empties() []string {
out := make([]string, len(s.empties))
copy(out, s.empties)
return out
}
// TokenMatches returns a sorted slice of tokens and counts.
func (s Summary) TokenMatches() []struct {
// SortedTokenMatches returns token match counts sorted alphabetically by token.
func (s *Summary) SortedTokenMatches() []struct {
Token string
Count int
} {
pairs := make([]struct {
if len(s.TokenMatches) == 0 {
return nil
}
result := make([]struct {
Token string
Count int
}, 0, len(s.tokenMatches))
for token, count := range s.tokenMatches {
pairs = append(pairs, struct {
}, 0, len(s.TokenMatches))
for token, count := range s.TokenMatches {
result = append(result, struct {
Token string
Count int
}{Token: token, Count: count})
}
sort.Slice(pairs, func(i, j int) bool {
return pairs[i].Token < pairs[j].Token
sort.Slice(result, func(i, j int) bool {
return result[i].Token < result[j].Token
})
return pairs
}
// Duplicates returns duplicates flagged by the parser.
func (s Summary) Duplicates() []string {
out := make([]string, len(s.duplicates))
copy(out, s.duplicates)
sort.Strings(out)
return out
return result
}