Skip to content

Commit eb69b5d

Browse files
authored
Create choice package (#16)
1 parent dcbf242 commit eb69b5d

File tree

4 files changed

+146
-0
lines changed

4 files changed

+146
-0
lines changed

choice/choice.go

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package choice
2+
3+
import (
4+
"bufio"
5+
"bytes"
6+
"fmt"
7+
"os"
8+
"strconv"
9+
"strings"
10+
)
11+
12+
func choiceStr(choice any) string {
13+
if v, ok := choice.(Description); ok {
14+
return v.Description()
15+
} else if v, ok := choice.(fmt.Stringer); ok {
16+
return v.String()
17+
}
18+
return fmt.Sprint(choice)
19+
}
20+
21+
func Menu[E Choice](choices []E) string {
22+
var digit int
23+
for n := len(choices); n != 0; digit++ {
24+
n /= 10
25+
}
26+
option := fmt.Sprintf("%%%dd", digit)
27+
var b strings.Builder
28+
for i, choice := range choices {
29+
fmt.Fprintf(&b, "%s. %s\n", fmt.Sprintf(option, i+1), choiceStr(choice))
30+
}
31+
return b.String()
32+
}
33+
34+
func choose[E Choice](choice string, choices []E) (res any, err error) {
35+
n, err := strconv.Atoi(choice)
36+
if err != nil {
37+
return nil, choiceError(choice)
38+
}
39+
if length := len(choices); n < 1 || n > length {
40+
return nil, choiceError(fmt.Sprintf("out of range(1-%d): %d", length, n))
41+
}
42+
return choices[n-1].Run()
43+
}
44+
45+
func Choose[E Choice](choices []E) (choice string, res any, err error) {
46+
if length := len(choices); length == 0 {
47+
return
48+
}
49+
fmt.Print(Menu(choices))
50+
fmt.Print("\nPlease choose: ")
51+
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}) {
55+
return
56+
}
57+
res, err = choose(string(b), choices)
58+
return
59+
}

choice/choice_test.go

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package choice
2+
3+
import (
4+
"errors"
5+
"fmt"
6+
"strings"
7+
"testing"
8+
)
9+
10+
var (
11+
_ Choice = a("")
12+
_ Description = a("")
13+
)
14+
15+
type a string
16+
17+
func (s a) Run() (any, error) {
18+
return s, nil
19+
}
20+
21+
func (s a) Description() string {
22+
return strings.Repeat(string(s), 2)
23+
}
24+
25+
func (s a) String() string {
26+
return string(s)
27+
}
28+
29+
func TestMenu(t *testing.T) {
30+
choices := []a{"a", "b", "c"}
31+
expect := `1. aa
32+
2. bb
33+
3. cc
34+
`
35+
if s := Menu(choices); s != expect {
36+
t.Errorf("expected %q; got %q", expect, s)
37+
}
38+
}
39+
40+
func TestChoose(t *testing.T) {
41+
choices := []a{"a", "b", "c"}
42+
if _, err := choose("4", choices); !errors.Is(err, ErrBadChoice) {
43+
t.Errorf("expected ErrBadChoice; got %s", err)
44+
}
45+
if s, err := choose("2", choices); err != nil {
46+
t.Fatal(err)
47+
} else if expect := "b"; fmt.Sprint(s) != expect {
48+
t.Errorf("expected %q; got %q", expect, s)
49+
}
50+
}

choice/types.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package choice
2+
3+
import "errors"
4+
5+
type Choice interface {
6+
Run() (any, error)
7+
}
8+
9+
type Description interface {
10+
Description() string
11+
}
12+
13+
var ErrBadChoice = errors.New("bad choice")
14+
15+
var _ error = choiceError("")
16+
17+
type choiceError string
18+
19+
func (err choiceError) Error() string {
20+
return "bad choice: " + string(err)
21+
}
22+
23+
func (choiceError) Unwrap() error {
24+
return ErrBadChoice
25+
}

choice/types_test.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package choice
2+
3+
import (
4+
"errors"
5+
"testing"
6+
)
7+
8+
func TestError(t *testing.T) {
9+
if !errors.Is(choiceError(""), ErrBadChoice) {
10+
t.Error("expected err is ErrBadChoice; got not")
11+
}
12+
}

0 commit comments

Comments
 (0)