Skip to content

Commit b056bec

Browse files
committed
choice: Add ChooseWithDefault
1 parent eb69b5d commit b056bec

File tree

4 files changed

+88
-63
lines changed

4 files changed

+88
-63
lines changed

choice/choice.go

Lines changed: 68 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,18 @@ package choice
33
import (
44
"bufio"
55
"bytes"
6+
"errors"
67
"fmt"
78
"os"
89
"strconv"
910
"strings"
1011
)
1112

13+
// Description interface defines a method for obtaining the description of a choice.
14+
type Description interface {
15+
Description() string
16+
}
17+
1218
func choiceStr(choice any) string {
1319
if v, ok := choice.(Description); ok {
1420
return v.Description()
@@ -18,7 +24,11 @@ func choiceStr(choice any) string {
1824
return fmt.Sprint(choice)
1925
}
2026

21-
func Menu[E Choice](choices []E) string {
27+
// Menu function is used to generate a string representation of the menu, associating choices with numbers.
28+
func Menu[E any](choices []E) string {
29+
if len(choices) == 0 {
30+
return ""
31+
}
2232
var digit int
2333
for n := len(choices); n != 0; digit++ {
2434
n /= 10
@@ -28,32 +38,78 @@ func Menu[E Choice](choices []E) string {
2838
for i, choice := range choices {
2939
fmt.Fprintf(&b, "%s. %s\n", fmt.Sprintf(option, i+1), choiceStr(choice))
3040
}
41+
fmt.Fprintf(&b, "%s. Quit\n", fmt.Sprintf(fmt.Sprintf("%%%ds", digit), "q"))
3142
return b.String()
3243
}
3344

34-
func choose[E Choice](choice string, choices []E) (res any, err error) {
45+
// ErrBadChoice defines an error that represents an invalid choice made by the user.
46+
var ErrBadChoice = errors.New("bad choice")
47+
48+
var _ error = choiceError("")
49+
50+
type choiceError string
51+
52+
func (err choiceError) Error() string {
53+
return "bad choice: " + string(err)
54+
}
55+
56+
func (choiceError) Unwrap() error {
57+
return ErrBadChoice
58+
}
59+
60+
func choose[E any](choice string, choices []E) (res E, err error) {
3561
n, err := strconv.Atoi(choice)
3662
if err != nil {
37-
return nil, choiceError(choice)
63+
err = choiceError(choice)
64+
return
3865
}
3966
if length := len(choices); n < 1 || n > length {
40-
return nil, choiceError(fmt.Sprintf("out of range(1-%d): %d", length, n))
67+
err = choiceError(fmt.Sprintf("out of range(1-%d): %d", length, n))
68+
return
4169
}
42-
return choices[n-1].Run()
70+
return choices[n-1], nil
71+
}
72+
73+
// Choose function allows the user to make a choice from the given options with no default value.
74+
func Choose[E any](choices []E) (choice bool, res E, err error) {
75+
return ChooseWithDefault(choices, 0)
4376
}
4477

45-
func Choose[E Choice](choices []E) (choice string, res any, err error) {
46-
if length := len(choices); length == 0 {
78+
// ChooseWithDefault function allows the user to make a choice from the given options with an optional default value.
79+
func ChooseWithDefault[E any](choices []E, def int) (choice bool, res E, err error) {
80+
if n := len(choices); n == 0 {
81+
err = errors.New("no choices")
82+
return
83+
} else if def > n {
84+
err = errors.New("invalid default choice")
4785
return
4886
}
49-
fmt.Print(Menu(choices))
50-
fmt.Print("\nPlease choose: ")
87+
var prompt string
88+
if def > 0 {
89+
prompt = fmt.Sprintf("Please choose (default: %d): ", def)
90+
} else {
91+
prompt = "Please choose: "
92+
}
5193
scanner := bufio.NewScanner(os.Stdin)
52-
scanner.Scan()
53-
b := bytes.TrimSpace(scanner.Bytes())
54-
if bytes.EqualFold(b, []byte("q")) || bytes.Contains(b, []byte{27}) {
94+
var b []byte
95+
if def <= 0 {
96+
for len(b) == 0 {
97+
fmt.Print(prompt)
98+
scanner.Scan()
99+
b = bytes.TrimSpace(scanner.Bytes())
100+
}
101+
} else {
102+
fmt.Print(prompt)
103+
scanner.Scan()
104+
b = bytes.TrimSpace(scanner.Bytes())
105+
if len(b) == 0 {
106+
return true, choices[def-1], nil
107+
}
108+
}
109+
if bytes.EqualFold(b, []byte("q")) {
55110
return
56111
}
112+
choice = true
57113
res, err = choose(string(b), choices)
58114
return
59115
}

choice/choice_test.go

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,14 @@ package choice
22

33
import (
44
"errors"
5-
"fmt"
65
"strings"
76
"testing"
87
)
98

10-
var (
11-
_ Choice = a("")
12-
_ Description = a("")
13-
)
9+
var _ Description = a("")
1410

1511
type a string
1612

17-
func (s a) Run() (any, error) {
18-
return s, nil
19-
}
20-
2113
func (s a) Description() string {
2214
return strings.Repeat(string(s), 2)
2315
}
@@ -27,10 +19,18 @@ func (s a) String() string {
2719
}
2820

2921
func TestMenu(t *testing.T) {
30-
choices := []a{"a", "b", "c"}
31-
expect := `1. aa
32-
2. bb
33-
3. cc
22+
choices := []a{"a", "b", "c", "d", "e", "f", "g", "h", "i", "j"}
23+
expect := ` 1. aa
24+
2. bb
25+
3. cc
26+
4. dd
27+
5. ee
28+
6. ff
29+
7. gg
30+
8. hh
31+
9. ii
32+
10. jj
33+
q. Quit
3434
`
3535
if s := Menu(choices); s != expect {
3636
t.Errorf("expected %q; got %q", expect, s)
@@ -44,7 +44,13 @@ func TestChoose(t *testing.T) {
4444
}
4545
if s, err := choose("2", choices); err != nil {
4646
t.Fatal(err)
47-
} else if expect := "b"; fmt.Sprint(s) != expect {
47+
} else if expect := "b"; s.String() != expect {
4848
t.Errorf("expected %q; got %q", expect, s)
4949
}
5050
}
51+
52+
func TestError(t *testing.T) {
53+
if !errors.Is(choiceError(""), ErrBadChoice) {
54+
t.Error("expected err is ErrBadChoice; got not")
55+
}
56+
}

choice/types.go

Lines changed: 0 additions & 25 deletions
This file was deleted.

choice/types_test.go

Lines changed: 0 additions & 12 deletions
This file was deleted.

0 commit comments

Comments
 (0)