feat: implement remove command with sequential removals
This commit is contained in:
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user