Skip to content

Commit 48721c2

Browse files
committed
cli/streams: don't depend on embedding
Define explicit wrapper methods instead of depending on the embedded commonStreams struct. Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
1 parent 39e82e6 commit 48721c2

3 files changed

Lines changed: 92 additions & 41 deletions

File tree

cli/streams/in.go

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,21 @@ import (
1111
// In is an input stream to read user input. It implements [io.ReadCloser]
1212
// with additional utilities, such as putting the terminal in raw mode.
1313
type In struct {
14-
commonStream
1514
in io.ReadCloser
15+
cs commonStream
1616
}
1717

1818
// NewIn returns a new [In] from an [io.ReadCloser].
1919
func NewIn(in io.ReadCloser) *In {
20-
i := &In{in: in}
21-
i.fd, i.isTerminal = term.GetFdInfo(in)
22-
return i
20+
return &In{
21+
in: in,
22+
cs: newCommonStream(in),
23+
}
24+
}
25+
26+
// FD returns the file descriptor number for this stream.
27+
func (i *In) FD() uintptr {
28+
return i.cs.fd
2329
}
2430

2531
// Read implements the [io.Reader] interface.
@@ -32,11 +38,21 @@ func (i *In) Close() error {
3238
return i.in.Close()
3339
}
3440

41+
// IsTerminal returns whether this stream is connected to a terminal.
42+
func (i *In) IsTerminal() bool {
43+
return i.cs.isTerminal()
44+
}
45+
3546
// SetRawTerminal sets raw mode on the input terminal. It is a no-op if In
3647
// is not a TTY, or if the "NORAW" environment variable is set to a non-empty
3748
// value.
3849
func (i *In) SetRawTerminal() error {
39-
return i.setRawTerminal(term.SetRawTerminal)
50+
return i.cs.setRawTerminal(term.SetRawTerminal)
51+
}
52+
53+
// RestoreTerminal restores the terminal state if SetRawTerminal succeeded earlier.
54+
func (i *In) RestoreTerminal() {
55+
i.cs.restoreTerminal()
4056
}
4157

4258
// CheckTty checks if we are trying to attach to a container TTY
@@ -45,7 +61,7 @@ func (i *In) CheckTty(attachStdin, ttyMode bool) error {
4561
// In order to attach to a container tty, input stream for the client must
4662
// be a tty itself: redirecting or piping the client standard input is
4763
// incompatible with `docker run -t`, `docker exec -t` or `docker attach`.
48-
if ttyMode && attachStdin && !i.isTerminal {
64+
if ttyMode && attachStdin && !i.cs.isTerminal() {
4965
const eText = "the input device is not a TTY"
5066
if runtime.GOOS == "windows" {
5167
return errors.New(eText + ". If you are using mintty, try prefixing the command with 'winpty'")
@@ -54,3 +70,10 @@ func (i *In) CheckTty(attachStdin, ttyMode bool) error {
5470
}
5571
return nil
5672
}
73+
74+
// SetIsTerminal overrides whether a terminal is connected. It is used to
75+
// override this property in unit-tests, and should not be depended on for
76+
// other purposes.
77+
func (i *In) SetIsTerminal(isTerminal bool) {
78+
i.cs.setIsTerminal(isTerminal)
79+
}

cli/streams/out.go

Lines changed: 30 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,50 +4,63 @@ import (
44
"io"
55

66
"github.com/moby/term"
7-
"github.com/sirupsen/logrus"
87
)
98

109
// Out is an output stream to write normal program output. It implements
1110
// an [io.Writer], with additional utilities for detecting whether a terminal
1211
// is connected, getting the TTY size, and putting the terminal in raw mode.
1312
type Out struct {
14-
commonStream
1513
out io.Writer
14+
cs commonStream
1615
}
1716

1817
// NewOut returns a new [Out] from an [io.Writer].
1918
func NewOut(out io.Writer) *Out {
20-
o := &Out{out: out}
21-
o.fd, o.isTerminal = term.GetFdInfo(out)
22-
return o
19+
return &Out{
20+
out: out,
21+
cs: newCommonStream(out),
22+
}
23+
}
24+
25+
// FD returns the file descriptor number for this stream.
26+
func (o *Out) FD() uintptr {
27+
return o.cs.FD()
2328
}
2429

30+
// Write writes to the output stream.
2531
func (o *Out) Write(p []byte) (int, error) {
2632
return o.out.Write(p)
2733
}
2834

35+
// IsTerminal returns whether this stream is connected to a terminal.
36+
func (o *Out) IsTerminal() bool {
37+
return o.cs.isTerminal()
38+
}
39+
2940
// SetRawTerminal puts the output of the terminal connected to the stream
3041
// into raw mode.
3142
//
3243
// On UNIX, this does nothing. On Windows, it disables LF -> CRLF/ translation.
3344
// It is a no-op if Out is not a TTY, or if the "NORAW" environment variable is
3445
// set to a non-empty value.
3546
func (o *Out) SetRawTerminal() error {
36-
return o.setRawTerminal(term.SetRawTerminalOutput)
47+
return o.cs.setRawTerminal(term.SetRawTerminalOutput)
48+
}
49+
50+
// RestoreTerminal restores the terminal state if SetRawTerminal succeeded earlier.
51+
func (o *Out) RestoreTerminal() {
52+
o.cs.restoreTerminal()
3753
}
3854

3955
// GetTtySize returns the height and width in characters of the TTY, or
4056
// zero for both if no TTY is connected.
4157
func (o *Out) GetTtySize() (height uint, width uint) {
42-
if !o.isTerminal {
43-
return 0, 0
44-
}
45-
ws, err := term.GetWinsize(o.fd)
46-
if err != nil {
47-
logrus.WithError(err).Debug("Error getting TTY size")
48-
if ws == nil {
49-
return 0, 0
50-
}
51-
}
52-
return uint(ws.Height), uint(ws.Width)
58+
return o.cs.terminalSize()
59+
}
60+
61+
// SetIsTerminal overrides whether a terminal is connected. It is used to
62+
// override this property in unit-tests, and should not be depended on for
63+
// other purposes.
64+
func (o *Out) SetIsTerminal(isTerminal bool) {
65+
o.cs.setIsTerminal(isTerminal)
5366
}

cli/streams/stream.go

Lines changed: 33 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4,33 +4,41 @@ import (
44
"os"
55

66
"github.com/moby/term"
7+
"github.com/sirupsen/logrus"
78
)
89

10+
func newCommonStream(stream any) commonStream {
11+
fd, tty := term.GetFdInfo(stream)
12+
return commonStream{
13+
fd: fd,
14+
tty: tty,
15+
}
16+
}
17+
918
type commonStream struct {
10-
fd uintptr
11-
isTerminal bool
12-
state *term.State
19+
fd uintptr
20+
tty bool
21+
state *term.State
1322
}
1423

1524
// FD returns the file descriptor number for this stream.
16-
func (s *commonStream) FD() uintptr {
17-
return s.fd
18-
}
25+
func (s *commonStream) FD() uintptr { return s.fd }
1926

20-
// IsTerminal returns true if this stream is connected to a terminal.
21-
func (s *commonStream) IsTerminal() bool {
22-
return s.isTerminal
23-
}
27+
// isTerminal returns whether this stream is connected to a terminal.
28+
func (s *commonStream) isTerminal() bool { return s.tty }
2429

25-
// RestoreTerminal restores the terminal state if SetRawTerminal succeeded earlier.
26-
func (s *commonStream) RestoreTerminal() {
30+
// setIsTerminal overrides whether a terminal is connected for testing.
31+
func (s *commonStream) setIsTerminal(isTerminal bool) { s.tty = isTerminal }
32+
33+
// restoreTerminal restores the terminal state if SetRawTerminal succeeded earlier.
34+
func (s *commonStream) restoreTerminal() {
2735
if s.state != nil {
2836
_ = term.RestoreTerminal(s.fd, s.state)
2937
}
3038
}
3139

3240
func (s *commonStream) setRawTerminal(setter func(uintptr) (*term.State, error)) error {
33-
if !s.isTerminal || os.Getenv("NORAW") != "" {
41+
if !s.tty || os.Getenv("NORAW") != "" {
3442
return nil
3543
}
3644
state, err := setter(s.fd)
@@ -41,9 +49,16 @@ func (s *commonStream) setRawTerminal(setter func(uintptr) (*term.State, error))
4149
return nil
4250
}
4351

44-
// SetIsTerminal overrides whether a terminal is connected. It is used to
45-
// override this property in unit-tests, and should not be depended on for
46-
// other purposes.
47-
func (s *commonStream) SetIsTerminal(isTerminal bool) {
48-
s.isTerminal = isTerminal
52+
func (s *commonStream) terminalSize() (height uint, width uint) {
53+
if !s.tty {
54+
return 0, 0
55+
}
56+
ws, err := term.GetWinsize(s.fd)
57+
if err != nil {
58+
logrus.WithError(err).Debug("Error getting TTY size")
59+
if ws == nil {
60+
return 0, 0
61+
}
62+
}
63+
return uint(ws.Height), uint(ws.Width)
4964
}

0 commit comments

Comments
 (0)