refactor(selectors): share exact-match resolution helpers

This commit is contained in:
Peter Steinberger 2026-03-08 23:29:27 +00:00
parent 644951e87b
commit cc2e705220
3 changed files with 89 additions and 77 deletions

View File

@ -66,12 +66,7 @@ func resolveTasklistID(ctx context.Context, svc *tasks.Service, input string) (s
return in, nil
}
type match struct {
ID string
Title string
}
var titleMatches []match
var options []selectorMatch
seenTokens := map[string]bool{}
pageToken := ""
for {
@ -92,13 +87,10 @@ func resolveTasklistID(ctx context.Context, svc *tasks.Service, input string) (s
if tl == nil {
continue
}
id := strings.TrimSpace(tl.Id)
if id != "" && id == in {
return in, nil
}
if id != "" && strings.EqualFold(strings.TrimSpace(tl.Title), in) {
titleMatches = append(titleMatches, match{ID: id, Title: strings.TrimSpace(tl.Title)})
}
options = append(options, selectorMatch{
ID: strings.TrimSpace(tl.Id),
Name: strings.TrimSpace(tl.Title),
})
}
next := strings.TrimSpace(resp.NextPageToken)
if next == "" {
@ -107,20 +99,12 @@ func resolveTasklistID(ctx context.Context, svc *tasks.Service, input string) (s
pageToken = next
}
if len(titleMatches) == 1 {
return titleMatches[0].ID, nil
match, found, err := findByIDOrCaseFoldName(in, "tasklist", options)
if err != nil {
return "", err
}
if len(titleMatches) > 1 {
sort.Slice(titleMatches, func(i, j int) bool { return titleMatches[i].ID < titleMatches[j].ID })
parts := make([]string, 0, len(titleMatches))
for _, m := range titleMatches {
label := m.Title
if label == "" {
label = "(untitled)"
}
parts = append(parts, fmt.Sprintf("%s (%s)", label, m.ID))
}
return "", usagef("ambiguous tasklist %q; matches: %s", in, strings.Join(parts, ", "))
if found {
return match.ID, nil
}
return in, nil

View File

@ -0,0 +1,62 @@
package cmd
import (
"fmt"
"sort"
"strings"
)
type selectorMatch struct {
ID string
Name string
}
func findByIDOrCaseFoldName(input, kind string, options []selectorMatch) (*selectorMatch, bool, error) {
in := strings.TrimSpace(input)
if in == "" {
return nil, false, nil
}
for _, option := range options {
if strings.TrimSpace(option.ID) == in {
match := option
return &match, true, nil
}
}
var matches []selectorMatch
for _, option := range options {
name := strings.TrimSpace(option.Name)
if name == "" || !strings.EqualFold(name, in) {
continue
}
matches = append(matches, selectorMatch{
ID: strings.TrimSpace(option.ID),
Name: name,
})
}
switch len(matches) {
case 0:
return nil, false, nil
case 1:
match := matches[0]
return &match, true, nil
default:
sort.Slice(matches, func(i, j int) bool {
if matches[i].Name == matches[j].Name {
return matches[i].ID < matches[j].ID
}
return matches[i].Name < matches[j].Name
})
parts := make([]string, 0, len(matches))
for _, match := range matches {
label := match.Name
if label == "" {
label = "(unnamed)"
}
parts = append(parts, fmt.Sprintf("%s (%s)", label, match.ID))
}
return nil, false, usagef("ambiguous %s %q; matches: %s", kind, in, strings.Join(parts, ", "))
}
}

View File

@ -3,7 +3,6 @@ package cmd
import (
"context"
"fmt"
"sort"
"strings"
"google.golang.org/api/sheets/v4"
@ -107,11 +106,6 @@ func resolveGridRangeWithCatalog(input string, catalog *spreadsheetRangeCatalog,
return nil, usagef("unknown named range %q", in)
}
type namedRangeMatch struct {
ID string
Name string
}
// resolveNamedRangeByNameOrID finds a named range by:
// - exact ID match, or
// - case-insensitive exact name match (errors if ambiguous).
@ -123,56 +117,28 @@ func resolveNamedRangeByNameOrID(input string, namedRanges []*sheets.NamedRange)
return nil, false, nil
}
// Prefer an exact ID match without any ambiguity.
options := make([]selectorMatch, 0, len(namedRanges))
for _, nr := range namedRanges {
if nr == nil {
continue
}
if strings.TrimSpace(nr.NamedRangeId) == in {
options = append(options, selectorMatch{
ID: strings.TrimSpace(nr.NamedRangeId),
Name: strings.TrimSpace(nr.Name),
})
}
match, found, err := findByIDOrCaseFoldName(in, "named range", options)
if err != nil {
return nil, false, err
}
if !found {
return nil, false, nil
}
for _, nr := range namedRanges {
if nr != nil && strings.TrimSpace(nr.NamedRangeId) == match.ID {
return nr, true, nil
}
}
var matches []namedRangeMatch
for _, nr := range namedRanges {
if nr == nil {
continue
}
name := strings.TrimSpace(nr.Name)
if name == "" {
continue
}
if strings.EqualFold(name, in) {
matches = append(matches, namedRangeMatch{ID: strings.TrimSpace(nr.NamedRangeId), Name: name})
}
}
if len(matches) == 0 {
return nil, false, nil
}
if len(matches) == 1 {
for _, nr := range namedRanges {
if nr != nil && strings.TrimSpace(nr.NamedRangeId) == matches[0].ID {
return nr, true, nil
}
}
// Shouldn't happen, but be safe.
return nil, false, fmt.Errorf("named range match disappeared (id=%q)", matches[0].ID)
}
sort.Slice(matches, func(i, j int) bool {
if matches[i].Name == matches[j].Name {
return matches[i].ID < matches[j].ID
}
return matches[i].Name < matches[j].Name
})
parts := make([]string, 0, len(matches))
for _, m := range matches {
label := m.Name
if label == "" {
label = "(unnamed)"
}
parts = append(parts, fmt.Sprintf("%s (%s)", label, m.ID))
}
return nil, false, usagef("ambiguous named range %q; matches: %s", in, strings.Join(parts, ", "))
return nil, false, fmt.Errorf("named range match disappeared (id=%q)", match.ID)
}