/
iostreams.go
142 lines (111 loc) · 2.75 KB
/
iostreams.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
135
136
137
138
139
140
141
142
package iostreams
import (
"io"
"os"
"time"
"github.com/briandowns/spinner"
"github.com/mattn/go-colorable"
"github.com/mattn/go-isatty"
)
const (
spinnerDuration = 400 * time.Millisecond
)
// IOStreams contains information for input/output used in the CLI.
type IOStreams struct {
In io.ReadCloser
Out io.Writer
ErrOut io.Writer
progressIndicator *spinner.Spinner
progressIndicatorEnabled bool
stdinTTYOverride bool
stdinIsTTY bool
stdoutTTYOverride bool
stdoutIsTTY bool
stderrTTYOverride bool
stderrIsTTY bool
}
// System returns an IOStreams object with prepared input/output.
func System() *IOStreams {
stdoutIsTTY := isTerminal(os.Stdout)
stderrIsTTY := isTerminal(os.Stderr)
st := &IOStreams{
In: os.Stdin,
Out: colorable.NewColorable(os.Stdout),
ErrOut: colorable.NewColorable(os.Stderr),
}
if stdoutIsTTY && stderrIsTTY {
st.progressIndicatorEnabled = true
}
// prevent duplicate isTerminal queries now that we know the answer
st.SetStdoutTTY(stdoutIsTTY)
st.SetStderrTTY(stderrIsTTY)
return st
}
// SetStdinTTY marks stdin as TTY.
func (s *IOStreams) SetStdinTTY(isTTY bool) {
s.stdinTTYOverride = true
s.stdinIsTTY = isTTY
}
// IsStdinTTY checks if stdin is a TTY.
func (s *IOStreams) IsStdinTTY() bool {
if s.stdinTTYOverride {
return s.stdinIsTTY
}
if stdin, ok := s.In.(*os.File); ok {
return isTerminal(stdin)
}
return false
}
// SetStdoutTTY marks stdout as TTY.
func (s *IOStreams) SetStdoutTTY(isTTY bool) {
s.stdoutTTYOverride = true
s.stdoutIsTTY = isTTY
}
// IsStdoutTTY checks if stdout is a TTY.
func (s *IOStreams) IsStdoutTTY() bool {
if s.stdoutTTYOverride {
return s.stdoutIsTTY
}
if stdout, ok := s.Out.(*os.File); ok {
return isTerminal(stdout)
}
return false
}
// SetStderrTTY marks stderr as TTY.
func (s *IOStreams) SetStderrTTY(isTTY bool) {
s.stderrTTYOverride = true
s.stderrIsTTY = isTTY
}
// IsStderrTTY checks if stderr is a TTY.
func (s *IOStreams) IsStderrTTY() bool {
if s.stderrTTYOverride {
return s.stderrIsTTY
}
if stderr, ok := s.ErrOut.(*os.File); ok {
return isTerminal(stderr)
}
return false
}
// StartProgressIndicator creates a new spinner indicator.
func (s *IOStreams) StartProgressIndicator(str string) {
if !s.progressIndicatorEnabled {
return
}
sp := spinner.New(spinner.CharSets[11], spinnerDuration, spinner.WithWriter(s.ErrOut))
sp.Start()
if str != "" {
sp.Suffix = str
}
s.progressIndicator = sp
}
// StopProgressIndicator stops the running spinner indicator.
func (s *IOStreams) StopProgressIndicator() {
if s.progressIndicator == nil {
return
}
s.progressIndicator.Stop()
s.progressIndicator = nil
}
func isTerminal(f *os.File) bool {
return isatty.IsTerminal(f.Fd()) || isatty.IsCygwinTerminal(f.Fd())
}