fix(cli): route prompt selection hints to stderr
Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
parent
8a7567c705
commit
d59e6acd72
3 changed files with 49 additions and 2 deletions
|
|
@ -75,12 +75,14 @@ func Select(label string, options []string) (string, error) {
|
||||||
r := newReader()
|
r := newReader()
|
||||||
input, err := r.ReadString('\n')
|
input, err := r.ReadString('\n')
|
||||||
if err != nil && strings.TrimSpace(input) == "" {
|
if err != nil && strings.TrimSpace(input) == "" {
|
||||||
|
promptHint("No input received. Selection cancelled.")
|
||||||
return "", fmt.Errorf("selection cancelled: %w", err)
|
return "", fmt.Errorf("selection cancelled: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
trimmed := strings.TrimSpace(input)
|
trimmed := strings.TrimSpace(input)
|
||||||
n, err := strconv.Atoi(trimmed)
|
n, err := strconv.Atoi(trimmed)
|
||||||
if err != nil || n < 1 || n > len(options) {
|
if err != nil || n < 1 || n > len(options) {
|
||||||
|
promptHint(fmt.Sprintf("Please enter a number between 1 and %d.", len(options)))
|
||||||
return "", fmt.Errorf("invalid selection %q: choose a number between 1 and %d", trimmed, len(options))
|
return "", fmt.Errorf("invalid selection %q: choose a number between 1 and %d", trimmed, len(options))
|
||||||
}
|
}
|
||||||
return options[n-1], nil
|
return options[n-1], nil
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,34 @@ import (
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func captureStderr(t *testing.T, fn func()) string {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
oldErr := os.Stderr
|
||||||
|
r, w, err := os.Pipe()
|
||||||
|
if !assert.NoError(t, err) {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
os.Stderr = w
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
os.Stderr = oldErr
|
||||||
|
}()
|
||||||
|
|
||||||
|
fn()
|
||||||
|
|
||||||
|
if !assert.NoError(t, w.Close()) {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
var buf bytes.Buffer
|
||||||
|
_, err = io.Copy(&buf, r)
|
||||||
|
if !assert.NoError(t, err) {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return buf.String()
|
||||||
|
}
|
||||||
|
|
||||||
func TestPrompt_Good(t *testing.T) {
|
func TestPrompt_Good(t *testing.T) {
|
||||||
SetStdin(strings.NewReader("hello\n"))
|
SetStdin(strings.NewReader("hello\n"))
|
||||||
defer SetStdin(nil) // reset
|
defer SetStdin(nil) // reset
|
||||||
|
|
@ -59,9 +87,13 @@ func TestSelect_Bad_Invalid(t *testing.T) {
|
||||||
SetStdin(strings.NewReader("5\n"))
|
SetStdin(strings.NewReader("5\n"))
|
||||||
defer SetStdin(nil)
|
defer SetStdin(nil)
|
||||||
|
|
||||||
_, err := Select("Pick", []string{"a", "b"})
|
var err error
|
||||||
|
stderr := captureStderr(t, func() {
|
||||||
|
_, err = Select("Pick", []string{"a", "b"})
|
||||||
|
})
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
assert.Contains(t, err.Error(), "choose a number between 1 and 2")
|
assert.Contains(t, err.Error(), "choose a number between 1 and 2")
|
||||||
|
assert.Contains(t, stderr, "Please enter a number between 1 and 2.")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSelect_Bad_EOF(t *testing.T) {
|
func TestSelect_Bad_EOF(t *testing.T) {
|
||||||
|
|
@ -223,6 +255,19 @@ func TestChoose_Bad_FilteredDefaultDoesNotFallBackToFirstVisible(t *testing.T) {
|
||||||
assert.Equal(t, "apricot", val)
|
assert.Equal(t, "apricot", val)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestChoose_Bad_InvalidNumberUsesStderrHint(t *testing.T) {
|
||||||
|
SetStdin(strings.NewReader("5\n2\n"))
|
||||||
|
defer SetStdin(nil)
|
||||||
|
|
||||||
|
var val string
|
||||||
|
stderr := captureStderr(t, func() {
|
||||||
|
val = Choose("Pick", []string{"a", "b"})
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.Equal(t, "b", val)
|
||||||
|
assert.Contains(t, stderr, "Please enter a number between 1 and 2.")
|
||||||
|
}
|
||||||
|
|
||||||
func TestChooseMulti_Good_Filter(t *testing.T) {
|
func TestChooseMulti_Good_Filter(t *testing.T) {
|
||||||
SetStdin(strings.NewReader("ap\n1 2\n"))
|
SetStdin(strings.NewReader("ap\n1 2\n"))
|
||||||
defer SetStdin(nil)
|
defer SetStdin(nil)
|
||||||
|
|
|
||||||
|
|
@ -396,7 +396,7 @@ func Choose[T any](prompt string, items []T, opts ...ChooseOption[T]) T {
|
||||||
if n >= 1 && n <= len(visible) {
|
if n >= 1 && n <= len(visible) {
|
||||||
return items[visible[n-1]]
|
return items[visible[n-1]]
|
||||||
}
|
}
|
||||||
fmt.Printf("Please enter a number between 1 and %d.\n", len(visible))
|
promptHint(fmt.Sprintf("Please enter a number between 1 and %d.", len(visible)))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue