-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmain.go
134 lines (114 loc) · 2.62 KB
/
main.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
// Package main provides the "multicase" example CLI that reads from
// stdin and outputs each line in lower, upper, and title case. Usage:
//
// $ cat FILE | multicase
// # Or interactive mode (user enters input)
// $ multicase
//
// "multicase" exists to demonstrate use of neilotoole/streamcache.
package main
import (
"bufio"
"context"
"errors"
"fmt"
"io"
"os"
"os/signal"
"strings"
"github.com/neilotoole/streamcache"
)
// main sets up the CLI, and invokes exec to do the actual work.
func main() {
ctx, cancelFn := context.WithCancel(context.Background())
var err error
defer func() {
cancelFn()
if err != nil {
os.Exit(1)
}
}()
go func() {
stopCh := make(chan os.Signal, 1)
signal.Notify(stopCh, os.Interrupt)
<-stopCh
cancelFn()
}()
var fi os.FileInfo
if fi, err = os.Stdin.Stat(); err != nil {
printErr(err)
return
}
if os.ModeNamedPipe&fi.Mode() == 0 && fi.Size() == 0 {
// No data on stdin, thus interactive mode: print a prompt.
fmt.Fprintln(os.Stdout, colorize(ansiFaint, "multicase: enter text and press [ENTER]"))
}
if err = exec(ctx, os.Stdin, os.Stdout); err != nil {
printErr(err)
}
}
func exec(ctx context.Context, in io.Reader, out io.Writer) error {
toUpper := func(s string) string {
return colorize(ansiRed, strings.ToUpper(s))
}
toLower := func(s string) string {
return colorize(ansiGreen, strings.ToLower(s))
}
toTitle := func(s string) string {
return colorize(ansiBlue, strings.Title(s)) //nolint:staticcheck
}
transforms := []func(string) string{toUpper, toLower, toTitle}
stream := streamcache.New(in)
rdrs := make([]*streamcache.Reader, len(transforms))
for i := range rdrs {
rdrs[i] = stream.NewReader(ctx)
}
// Seal the stream to indicate no more readers.
stream.Seal()
errCh := make(chan error, 1)
for i := range transforms {
go func(i int) {
r, t := rdrs[i], transforms[i]
defer r.Close()
sc := bufio.NewScanner(r)
for sc.Scan() {
select {
case <-ctx.Done():
errCh <- ctx.Err()
return
default:
}
text := sc.Text()
fmt.Fprintln(out, t(text))
}
if err := sc.Err(); err != nil {
errCh <- err
}
}(i)
}
var err error
select {
case <-ctx.Done():
err = ctx.Err()
case err = <-errCh:
case <-stream.Done():
err = stream.Err()
}
if errors.Is(err, io.EOF) {
err = nil
}
return err
}
func colorize(ansi, s string) string {
return ansi + s + ansiReset
}
const (
ansiFaint = "\033[2m"
ansiReset = "\033[0m"
ansiRed = "\033[31m"
ansiGreen = "\033[32m"
ansiBlue = "\033[34m"
)
func printErr(err error) {
fmt.Fprintln(os.Stderr, colorize(ansiRed, "multicase: error: "+err.Error()))
}