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

muesli / termenv / 5943573526

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

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
type File interface {
16
        io.ReadWriter
17
        Fd() uintptr
18
}
19

20
// OutputOption sets an option on Output.
21
type OutputOption = func(*Output)
22

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

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

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

44
type osEnviron struct{}
45

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

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

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

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

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

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

86
        return o
64✔
87
}
88

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

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

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

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

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

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

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

143
                o.fgColor = o.foregroundColor()
×
144
        }
145

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

152
        return o.fgColor
4✔
153
}
154

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

162
                o.bgColor = o.backgroundColor()
×
163
        }
164

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

171
        return o.bgColor
6✔
172
}
173

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

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

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

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

202
// WriteString writes the given string to the output.
203
func (o Output) WriteString(s string) (int, error) {
1✔
204
        return o.Write([]byte(s))
1✔
205
}
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