output.go

  1package termenv
  2
  3import (
  4	"io"
  5	"os"
  6	"sync"
  7)
  8
  9// output is the default global output.
 10var output = NewOutput(os.Stdout)
 11
 12// File represents a file descriptor.
 13//
 14// Deprecated: Use *os.File instead.
 15type File interface {
 16	io.ReadWriter
 17	Fd() uintptr
 18}
 19
 20// OutputOption sets an option on Output.
 21type OutputOption = func(*Output)
 22
 23// Output is a terminal output.
 24type 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.
 39type Environ interface {
 40	Environ() []string
 41	Getenv(string) string
 42}
 43
 44type osEnviron struct{}
 45
 46func (oe *osEnviron) Environ() []string {
 47	return os.Environ()
 48}
 49
 50func (oe *osEnviron) Getenv(key string) string {
 51	return os.Getenv(key)
 52}
 53
 54// DefaultOutput returns the default global output.
 55func DefaultOutput() *Output {
 56	return output
 57}
 58
 59// SetDefaultOutput sets the default global output.
 60func SetDefaultOutput(o *Output) {
 61	output = o
 62}
 63
 64// NewOutput returns a new Output for the given writer.
 65func NewOutput(w io.Writer, opts ...OutputOption) *Output {
 66	o := &Output{
 67		w:       w,
 68		environ: &osEnviron{},
 69		Profile: -1,
 70		fgSync:  &sync.Once{},
 71		fgColor: NoColor{},
 72		bgSync:  &sync.Once{},
 73		bgColor: NoColor{},
 74	}
 75
 76	if o.w == nil {
 77		o.w = os.Stdout
 78	}
 79	for _, opt := range opts {
 80		opt(o)
 81	}
 82	if o.Profile < 0 {
 83		o.Profile = o.EnvColorProfile()
 84	}
 85
 86	return o
 87}
 88
 89// WithEnvironment returns a new OutputOption for the given environment.
 90func WithEnvironment(environ Environ) OutputOption {
 91	return func(o *Output) {
 92		o.environ = environ
 93	}
 94}
 95
 96// WithProfile returns a new OutputOption for the given profile.
 97func WithProfile(profile Profile) OutputOption {
 98	return func(o *Output) {
 99		o.Profile = profile
100	}
101}
102
103// WithColorCache returns a new OutputOption with fore- and background color values
104// pre-fetched and cached.
105func WithColorCache(v bool) OutputOption {
106	return func(o *Output) {
107		o.cache = v
108
109		// cache the values now
110		_ = o.ForegroundColor()
111		_ = o.BackgroundColor()
112	}
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.
117func WithTTY(v bool) OutputOption {
118	return func(o *Output) {
119		o.assumeTTY = v
120	}
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.
130func WithUnsafe() OutputOption {
131	return func(o *Output) {
132		o.unsafe = true
133	}
134}
135
136// ForegroundColor returns the terminal's default foreground color.
137func (o *Output) ForegroundColor() Color {
138	f := func() {
139		if !o.isTTY() {
140			return
141		}
142
143		o.fgColor = o.foregroundColor()
144	}
145
146	if o.cache {
147		o.fgSync.Do(f)
148	} else {
149		f()
150	}
151
152	return o.fgColor
153}
154
155// BackgroundColor returns the terminal's default background color.
156func (o *Output) BackgroundColor() Color {
157	f := func() {
158		if !o.isTTY() {
159			return
160		}
161
162		o.bgColor = o.backgroundColor()
163	}
164
165	if o.cache {
166		o.bgSync.Do(f)
167	} else {
168		f()
169	}
170
171	return o.bgColor
172}
173
174// HasDarkBackground returns whether terminal uses a dark-ish background.
175func (o *Output) HasDarkBackground() bool {
176	c := ConvertToRGB(o.BackgroundColor())
177	_, _, l := c.Hsl()
178	return l < 0.5 //nolint:mnd
179}
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.
185func (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.
194func (o Output) Writer() io.Writer {
195	return o.w
196}
197
198func (o Output) Write(p []byte) (int, error) {
199	return o.w.Write(p) //nolint:wrapcheck
200}
201
202// WriteString writes the given string to the output.
203func (o Output) WriteString(s string) (int, error) {
204	return o.Write([]byte(s))
205}