output.go 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197
  1. package termenv
  2. import (
  3. "io"
  4. "os"
  5. "sync"
  6. )
  7. var (
  8. // output is the default global output.
  9. output = NewOutput(os.Stdout)
  10. )
  11. // File represents a file descriptor.
  12. type File interface {
  13. io.ReadWriter
  14. Fd() uintptr
  15. }
  16. // OutputOption sets an option on Output.
  17. type OutputOption = func(*Output)
  18. // Output is a terminal output.
  19. type Output struct {
  20. Profile
  21. tty io.Writer
  22. environ Environ
  23. assumeTTY bool
  24. unsafe bool
  25. cache bool
  26. fgSync *sync.Once
  27. fgColor Color
  28. bgSync *sync.Once
  29. bgColor Color
  30. }
  31. // Environ is an interface for getting environment variables.
  32. type Environ interface {
  33. Environ() []string
  34. Getenv(string) string
  35. }
  36. type osEnviron struct{}
  37. func (oe *osEnviron) Environ() []string {
  38. return os.Environ()
  39. }
  40. func (oe *osEnviron) Getenv(key string) string {
  41. return os.Getenv(key)
  42. }
  43. // DefaultOutput returns the default global output.
  44. func DefaultOutput() *Output {
  45. return output
  46. }
  47. // SetDefaultOutput sets the default global output.
  48. func SetDefaultOutput(o *Output) {
  49. output = o
  50. }
  51. // NewOutput returns a new Output for the given file descriptor.
  52. func NewOutput(tty io.Writer, opts ...OutputOption) *Output {
  53. o := &Output{
  54. tty: tty,
  55. environ: &osEnviron{},
  56. Profile: -1,
  57. fgSync: &sync.Once{},
  58. fgColor: NoColor{},
  59. bgSync: &sync.Once{},
  60. bgColor: NoColor{},
  61. }
  62. if o.tty == nil {
  63. o.tty = os.Stdout
  64. }
  65. for _, opt := range opts {
  66. opt(o)
  67. }
  68. if o.Profile < 0 {
  69. o.Profile = o.EnvColorProfile()
  70. }
  71. return o
  72. }
  73. // WithEnvironment returns a new OutputOption for the given environment.
  74. func WithEnvironment(environ Environ) OutputOption {
  75. return func(o *Output) {
  76. o.environ = environ
  77. }
  78. }
  79. // WithProfile returns a new OutputOption for the given profile.
  80. func WithProfile(profile Profile) OutputOption {
  81. return func(o *Output) {
  82. o.Profile = profile
  83. }
  84. }
  85. // WithColorCache returns a new OutputOption with fore- and background color values
  86. // pre-fetched and cached.
  87. func WithColorCache(v bool) OutputOption {
  88. return func(o *Output) {
  89. o.cache = v
  90. // cache the values now
  91. _ = o.ForegroundColor()
  92. _ = o.BackgroundColor()
  93. }
  94. }
  95. // WithTTY returns a new OutputOption to assume whether or not the output is a TTY.
  96. // This is useful when mocking console output.
  97. func WithTTY(v bool) OutputOption {
  98. return func(o *Output) {
  99. o.assumeTTY = v
  100. }
  101. }
  102. // WithUnsafe returns a new OutputOption with unsafe mode enabled. Unsafe mode doesn't
  103. // check whether or not the terminal is a TTY.
  104. //
  105. // This option supersedes WithTTY.
  106. //
  107. // This is useful when mocking console output and enforcing ANSI escape output
  108. // e.g. on SSH sessions.
  109. func WithUnsafe() OutputOption {
  110. return func(o *Output) {
  111. o.unsafe = true
  112. }
  113. }
  114. // ForegroundColor returns the terminal's default foreground color.
  115. func (o *Output) ForegroundColor() Color {
  116. f := func() {
  117. if !o.isTTY() {
  118. return
  119. }
  120. o.fgColor = o.foregroundColor()
  121. }
  122. if o.cache {
  123. o.fgSync.Do(f)
  124. } else {
  125. f()
  126. }
  127. return o.fgColor
  128. }
  129. // BackgroundColor returns the terminal's default background color.
  130. func (o *Output) BackgroundColor() Color {
  131. f := func() {
  132. if !o.isTTY() {
  133. return
  134. }
  135. o.bgColor = o.backgroundColor()
  136. }
  137. if o.cache {
  138. o.bgSync.Do(f)
  139. } else {
  140. f()
  141. }
  142. return o.bgColor
  143. }
  144. // HasDarkBackground returns whether terminal uses a dark-ish background.
  145. func (o *Output) HasDarkBackground() bool {
  146. c := ConvertToRGB(o.BackgroundColor())
  147. _, _, l := c.Hsl()
  148. return l < 0.5
  149. }
  150. // TTY returns the terminal's file descriptor. This may be nil if the output is
  151. // not a terminal.
  152. func (o Output) TTY() File {
  153. if f, ok := o.tty.(File); ok {
  154. return f
  155. }
  156. return nil
  157. }
  158. func (o Output) Write(p []byte) (int, error) {
  159. return o.tty.Write(p)
  160. }
  161. // WriteString writes the given string to the output.
  162. func (o Output) WriteString(s string) (int, error) {
  163. return o.Write([]byte(s))
  164. }