diff --git a/internal/cli/interactive.go b/internal/cli/interactive.go index 29d77f8e..fe357947 100644 --- a/internal/cli/interactive.go +++ b/internal/cli/interactive.go @@ -198,26 +198,14 @@ func resolveFamily(p *ui.Printer, pr prompter, path string) (push.Family, error) return s.Family, nil } p.PromptHint("We couldn't tell the data type from what's there — which is it?") + opts := push.FamilyNouns() ans, err := pr.Select("What kind of data is this?", "tabular = a CSV table; image = labels.csv + images/; text = labels.csv + texts/", - []string{"tabular", "image", "text"}, "tabular") + opts, opts[0]) if err != nil { return 0, err } - return familyFromNoun(ans), nil -} - -// familyFromNoun maps the plain family words the picker offers back to the -// push.Family enum. -func familyFromNoun(s string) push.Family { - switch s { - case "image": - return push.FamilyImage - case "text": - return push.FamilyText - default: - return push.FamilyTabular - } + return push.FamilyFromNoun(ans), nil } // pickTask renders the family's tasks — "Display name — one-liner · diff --git a/internal/push/category.go b/internal/push/category.go index d9385ddb..e22aebc2 100644 --- a/internal/push/category.go +++ b/internal/push/category.go @@ -146,17 +146,56 @@ func CategoriesByFamily(fam Family) []CategorySpec { return out } +// familyNounTable is the single source of truth pairing each Family with the +// plain word shown in prompts, echoes, and the interactive family picker. The +// slice order is the picker's display order — tabular first, since it's the +// most common family and the default when the layout sniff is ambiguous. That +// order is deliberately NOT the Family iota order (which is layout-internal). +// FamilyNoun (forward), FamilyFromNoun (reverse), and FamilyNouns (picker +// options + default) all derive from this one table, so they can't drift apart. +var familyNounTable = []struct { + family Family + noun string +}{ + {FamilyTabular, "tabular"}, + {FamilyImage, "image"}, + {FamilyText, "text"}, +} + // FamilyNoun is the plain word for a family, used in prompts and echoes -// ("tasks for tabular data", "this is image data"). +// ("tasks for tabular data", "this is image data"). Falls back to the picker +// default ("tabular") for an unrecognized family. func FamilyNoun(fam Family) string { - switch fam { - case FamilyImage: - return "image" - case FamilyText: - return "text" - default: - return "tabular" + for _, e := range familyNounTable { + if e.family == fam { + return e.noun + } + } + return "tabular" +} + +// FamilyFromNoun maps a family noun ("image"/"text"/"tabular") back to its +// Family — the reverse of FamilyNoun. Unrecognized input falls back to +// FamilyTabular, matching the picker default so a stray answer degrades to the +// safe common case. +func FamilyFromNoun(noun string) Family { + for _, e := range familyNounTable { + if e.noun == noun { + return e.family + } + } + return FamilyTabular +} + +// FamilyNouns returns the family nouns in picker/display order; the first +// element is the choice the picker pre-selects. The interactive family +// prompt derives both its options and its default from here. +func FamilyNouns() []string { + nouns := make([]string, len(familyNounTable)) + for i, e := range familyNounTable { + nouns[i] = e.noun } + return nouns } // SelfSupervisedText reports whether a text category trains without an