• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

muesli / termenv / 5943658581

22 Aug 2023 08:20PM UTC coverage: 57.655% (-0.2%) from 57.843%
5943658581

Pull #122

github

aymanbagabas
fix(output): export output writer

Termenv output can be a buffer, ssh session, a file, or anything that
implements the io.Writer interface.

In the case of an ssh session, the std ssh library returns a
io.ReadWriter for ssh sessions and the current Termenv implementation
makes it impossible to read terminal status reports since it expects a
file in return.

In addition, the TTY() function is confusing since it implies that the
returned value is a TTY. Which is not always true until we check that
using the isTTY() function.

Deprecate TTY() in favor of Writer() which simply returns the underlying
writer. The caller then can infer the type of the writer and decide what
to do with it.

This also deprecate parts of commit <a class=hub.com/muesli/termenv/commit/669c9abfb65169a7a146968e38702d3d80327a42">669c9abfb https://github.com/muesli/termenv/commit/669c9abfb65169a7a146968e38702d3d80327a42
Pull Request #122: fix(output): export output writer

56 of 56 new or added lines in 3 files covered. (100.0%)

531 of 921 relevant lines covered (57.65%)

8.83 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

75.0
/output.go
1
package termenv
2

3
import (
4
        "io"
5
        "os"
6
        "sync"
7
)
8

9
var (
10
        // output is the default global output.
11
        output = NewOutput(os.Stdout)
12
)
13

14
// File represents a file descriptor.
15
//
16
// Deprecated: Use *os.File instead.
17
type File interface {
18
        io.ReadWriter
19
        Fd() uintptr
20
}
21

22
// OutputOption sets an option on Output.
23
type OutputOption = func(*Output)
24

25
// Output is a terminal output.
26
type Output struct {
27
        Profile
28
        w       io.Writer
29
        environ Environ
30

31
        assumeTTY bool
32
        unsafe    bool
33
        cache     bool
34
        fgSync    *sync.Once
35
        fgColor   Color
36
        bgSync    *sync.Once
37
        bgColor   Color
38
}
39

40
// Environ is an interface for getting environment variables.
41
type Environ interface {
42
        Environ() []string
43
        Getenv(string) string
44
}
45

46
type osEnviron struct{}
47

48
func (oe *osEnviron) Environ() []string {
×
49
        return os.Environ()
×
50
}
×
51

52
func (oe *osEnviron) Getenv(key string) string {
100✔
53
        return os.Getenv(key)
100✔
54
}
100✔
55

56
// DefaultOutput returns the default global output.
57
func DefaultOutput() *Output {
×
58
        return output
×
59
}
×
60

61
// SetDefaultOutput sets the default global output.
62
func SetDefaultOutput(o *Output) {
×
63
        output = o
×
64
}
×
65

66
// NewOutput returns a new Output for the given writer.
67
func NewOutput(w io.Writer, opts ...OutputOption) *Output {
64✔
68
        o := &Output{
64✔
69
                w:       w,
64✔
70
                environ: &osEnviron{},
64✔
71
                Profile: -1,
64✔
72
                fgSync:  &sync.Once{},
64✔
73
                fgColor: NoColor{},
64✔
74
                bgSync:  &sync.Once{},
64✔
75
                bgColor: NoColor{},
64✔
76
        }
64✔
77

64✔
78
        if o.w == nil {
64✔
79
                o.w = os.Stdout
×
80
        }
×
81
        for _, opt := range opts {
158✔
82
                opt(o)
94✔
83
        }
94✔
84
        if o.Profile < 0 {
82✔
85
                o.Profile = o.EnvColorProfile()
18✔
86
        }
18✔
87

88
        return o
64✔
89
}
90

91
// WithEnvironment returns a new OutputOption for the given environment.
92
func WithEnvironment(environ Environ) OutputOption {
45✔
93
        return func(o *Output) {
90✔
94
                o.environ = environ
45✔
95
        }
45✔
96
}
97

98
// WithProfile returns a new OutputOption for the given profile.
99
func WithProfile(profile Profile) OutputOption {
46✔
100
        return func(o *Output) {
92✔
101
                o.Profile = profile
46✔
102
        }
46✔
103
}
104

105
// WithColorCache returns a new OutputOption with fore- and background color values
106
// pre-fetched and cached.
107
func WithColorCache(v bool) OutputOption {
1✔
108
        return func(o *Output) {
2✔
109
                o.cache = v
1✔
110

1✔
111
                // cache the values now
1✔
112
                _ = o.ForegroundColor()
1✔
113
                _ = o.BackgroundColor()
1✔
114
        }
1✔
115
}
116

117
// WithTTY returns a new OutputOption to assume whether or not the output is a TTY.
118
// This is useful when mocking console output.
119
func WithTTY(v bool) OutputOption {
2✔
120
        return func(o *Output) {
4✔
121
                o.assumeTTY = v
2✔
122
        }
2✔
123
}
124

125
// WithUnsafe returns a new OutputOption with unsafe mode enabled. Unsafe mode doesn't
126
// check whether or not the terminal is a TTY.
127
//
128
// This option supersedes WithTTY.
129
//
130
// This is useful when mocking console output and enforcing ANSI escape output
131
// e.g. on SSH sessions.
132
func WithUnsafe() OutputOption {
×
133
        return func(o *Output) {
×
134
                o.unsafe = true
×
135
        }
×
136
}
137

138
// ForegroundColor returns the terminal's default foreground color.
139
func (o *Output) ForegroundColor() Color {
4✔
140
        f := func() {
8✔
141
                if !o.isTTY() {
8✔
142
                        return
4✔
143
                }
4✔
144

145
                o.fgColor = o.foregroundColor()
×
146
        }
147

148
        if o.cache {
5✔
149
                o.fgSync.Do(f)
1✔
150
        } else {
4✔
151
                f()
3✔
152
        }
3✔
153

154
        return o.fgColor
4✔
155
}
156

157
// BackgroundColor returns the terminal's default background color.
158
func (o *Output) BackgroundColor() Color {
6✔
159
        f := func() {
12✔
160
                if !o.isTTY() {
12✔
161
                        return
6✔
162
                }
6✔
163

164
                o.bgColor = o.backgroundColor()
×
165
        }
166

167
        if o.cache {
7✔
168
                o.bgSync.Do(f)
1✔
169
        } else {
6✔
170
                f()
5✔
171
        }
5✔
172

173
        return o.bgColor
6✔
174
}
175

176
// HasDarkBackground returns whether terminal uses a dark-ish background.
177
func (o *Output) HasDarkBackground() bool {
2✔
178
        c := ConvertToRGB(o.BackgroundColor())
2✔
179
        _, _, l := c.Hsl()
2✔
180
        return l < 0.5
2✔
181
}
2✔
182

183
// TTY returns the terminal's file descriptor. This may be nil if the output is
184
// not a terminal.
185
//
186
// Deprecated: Use Writer() instead.
187
func (o Output) TTY() File {
×
188
        if f, ok := o.w.(File); ok {
×
189
                return f
×
190
        }
×
191
        return nil
×
192
}
193

194
// Writer returns the underlying writer. This may be of type io.Writer,
195
// io.ReadWriter, or *os.File.
196
func (o Output) Writer() io.Writer {
×
197
        return o.w
×
198
}
×
199

200
func (o Output) Write(p []byte) (int, error) {
4✔
201
        return o.w.Write(p)
4✔
202
}
4✔
203

204
// WriteString writes the given string to the output.
205
func (o Output) WriteString(s string) (int, error) {
1✔
206
        return o.Write([]byte(s))
1✔
207
}
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc