diff --git a/interact/prompt.go b/interact/prompt.go index 8b6d683..3d0748f 100644 --- a/interact/prompt.go +++ b/interact/prompt.go @@ -43,11 +43,14 @@ func promptLines(in io.Reader) chan result { promptReaders.ch = ch go func(r io.Reader) { + defer close(ch) s := bufio.NewScanner(r) for s.Scan() { ch <- result{answer: strings.TrimSpace(s.Text())} } - ch <- result{err: s.Err()} + if err := s.Err(); err != nil { + ch <- result{err: err} + } }(in) return ch @@ -64,7 +67,10 @@ func Prompt(ctx context.Context, query, defaultAnswer string) (string, error) { _, _ = fmt.Fprintf(cutypes.Output, "%s [%s]: ", query, defaultAnswer) select { - case r := <-promptLines(cutypes.Input): + case r, ok := <-promptLines(cutypes.Input): + if !ok { + return "", io.EOF + } if r.err != nil { return "", r.err } diff --git a/interact/prompt_eof_test.go b/interact/prompt_eof_test.go new file mode 100644 index 0000000..8f2ec57 --- /dev/null +++ b/interact/prompt_eof_test.go @@ -0,0 +1,45 @@ +package interact_test + +import ( + "context" + "errors" + "io" + "strings" + "testing" + "time" + + "github.com/gookit/cliui" + "github.com/gookit/cliui/interact" + "github.com/gookit/goutil/x/assert" +) + +func TestPromptReportsStreamEOF(t *testing.T) { + for _, tc := range []struct { + name, input string + answers []string + }{ + {name: "empty stream"}, + {name: "one line", input: "Ada\n", answers: []string{"Ada"}}, + {name: "final unterminated line", input: "Ada", answers: []string{"Ada"}}, + {name: "blank line uses default", input: "\n", answers: []string{"fallback"}}, + {name: "several lines", input: "Ada\nGrace\n", answers: []string{"Ada", "Grace"}}, + } { + t.Run(tc.name, func(t *testing.T) { + cliui.CustomIO(strings.NewReader(tc.input), io.Discard) + defer cliui.ResetIO() + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + for _, want := range tc.answers { + got, err := interact.Prompt(ctx, "Name", "fallback") + assert.NoErr(t, err) + assert.Eq(t, want, got) + } + for i := 0; i < 3; i++ { + got, err := interact.Prompt(ctx, "Name", "fallback") + if !errors.Is(err, io.EOF) || got != "" { + t.Fatalf("Prompt after EOF #%d = %q,%v; want empty answer and EOF", i+1, got, err) + } + } + }) + } +}