Go 80 lines
package projecttype
// prompter.go is the Layer-3 interactive selection: when the deterministic
// scan is ambiguous and no --type/--ai flag forced the outcome, eeco asks the
// operator to pick a category, describe the project (routing to Layer 4), or
// fall back to generic. It is AI-free and reads a single readline.
import (
"bufio"
"fmt"
"io"
"strconv"
"strings"
)
// stdinPrompter is the deterministic, AI-free layer-3 prompter: a single
// readline question with numbered candidates and two escapes — describe
// the project freely (routes to the AI layer) or pick the generic
// fallback.
type stdinPrompter struct {
in *bufio.Scanner
out io.Writer
}
// NewStdinPrompter builds a Prompter that reads operator choices from in
// and writes its question to out. It is the prompter `eeco init` wires
// to os.Stdin / os.Stderr; tests inject buffers.
func NewStdinPrompter(in io.Reader, out io.Writer) Prompter {
return &stdinPrompter{in: bufio.NewScanner(in), out: out}
}
const maxPromptRetries = 5
func (p *stdinPrompter) Pick(candidates []Category, cat *Catalog) (Category, bool, string, error) {
fmt.Fprintln(p.out, "eeco init: project type is ambiguous.")
for i, c := range candidates {
desc := ""
if e, ok := cat.Get(c); ok {
desc = e.Description
}
fmt.Fprintf(p.out, " %d) %s — %s\n", i+1, c, desc)
}
fmt.Fprintln(p.out, " d) describe the project in your own words")
fmt.Fprintln(p.out, " g) generic (no project-specific scaffold)")
for range maxPromptRetries {
if len(candidates) > 0 {
fmt.Fprintf(p.out, "pick [1-%d/d/g]: ", len(candidates))
} else {
fmt.Fprint(p.out, "pick [d/g]: ")
}
if !p.in.Scan() {
// No more input (EOF / closed stdin): degrade to generic so a
// piped, non-interactive `eeco init` still completes.
return Generic, false, "", nil
}
line := strings.TrimSpace(p.in.Text())
switch strings.ToLower(line) {
case "d":
fmt.Fprint(p.out, "describe the project: ")
free := ""
if p.in.Scan() {
free = strings.TrimSpace(p.in.Text())
}
return "", true, free, nil
case "g", "":
if line == "" {
continue
}
return Generic, false, "", nil
}
n, err := strconv.Atoi(line)
if err == nil && n >= 1 && n <= len(candidates) {
return candidates[n-1], false, "", nil
}
fmt.Fprintln(p.out, " unrecognised choice; try again.")
}
return Generic, false, "", nil
}