github.com/onsi/ginkgo@v1.16.6-0.20211118180735-4e1925ba4c95/formatter/formatter.go (about)

     1  package formatter
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  	"regexp"
     7  	"strings"
     8  )
     9  
    10  // ColorableStdOut and ColorableStdErr enable color output support on Windows
    11  var ColorableStdOut = newColorable(os.Stdout)
    12  var ColorableStdErr = newColorable(os.Stderr)
    13  
    14  const COLS = 80
    15  
    16  type ColorMode uint8
    17  
    18  const (
    19  	ColorModeNone ColorMode = iota
    20  	ColorModeTerminal
    21  	ColorModePassthrough
    22  )
    23  
    24  var SingletonFormatter = New(ColorModeTerminal)
    25  
    26  func F(format string, args ...interface{}) string {
    27  	return SingletonFormatter.F(format, args...)
    28  }
    29  
    30  func Fi(indentation uint, format string, args ...interface{}) string {
    31  	return SingletonFormatter.Fi(indentation, format, args...)
    32  }
    33  
    34  func Fiw(indentation uint, maxWidth uint, format string, args ...interface{}) string {
    35  	return SingletonFormatter.Fiw(indentation, maxWidth, format, args...)
    36  }
    37  
    38  type Formatter struct {
    39  	ColorMode                ColorMode
    40  	colors                   map[string]string
    41  	styleRe                  *regexp.Regexp
    42  	preserveColorStylingTags bool
    43  }
    44  
    45  func NewWithNoColorBool(noColor bool) Formatter {
    46  	if noColor {
    47  		return New(ColorModeNone)
    48  	}
    49  	return New(ColorModeTerminal)
    50  }
    51  
    52  func New(colorMode ColorMode) Formatter {
    53  	f := Formatter{
    54  		ColorMode: colorMode,
    55  		colors: map[string]string{
    56  			"/":         "\x1b[0m",
    57  			"bold":      "\x1b[1m",
    58  			"underline": "\x1b[4m",
    59  
    60  			"red":          "\x1b[38;5;9m",
    61  			"orange":       "\x1b[38;5;214m",
    62  			"coral":        "\x1b[38;5;204m",
    63  			"magenta":      "\x1b[38;5;13m",
    64  			"green":        "\x1b[38;5;10m",
    65  			"dark-green":   "\x1b[38;5;28m",
    66  			"yellow":       "\x1b[38;5;11m",
    67  			"light-yellow": "\x1b[38;5;228m",
    68  			"cyan":         "\x1b[38;5;14m",
    69  			"gray":         "\x1b[38;5;243m",
    70  			"light-gray":   "\x1b[38;5;246m",
    71  			"blue":         "\x1b[38;5;12m",
    72  		},
    73  	}
    74  	colors := []string{}
    75  	for color := range f.colors {
    76  		colors = append(colors, color)
    77  	}
    78  	f.styleRe = regexp.MustCompile("{{(" + strings.Join(colors, "|") + ")}}")
    79  	return f
    80  }
    81  
    82  func (f Formatter) F(format string, args ...interface{}) string {
    83  	return f.Fi(0, format, args...)
    84  }
    85  
    86  func (f Formatter) Fi(indentation uint, format string, args ...interface{}) string {
    87  	return f.Fiw(indentation, 0, format, args...)
    88  }
    89  
    90  func (f Formatter) Fiw(indentation uint, maxWidth uint, format string, args ...interface{}) string {
    91  	out := fmt.Sprintf(f.style(format), args...)
    92  
    93  	if indentation == 0 && maxWidth == 0 {
    94  		return out
    95  	}
    96  
    97  	lines := strings.Split(out, "\n")
    98  
    99  	if maxWidth != 0 {
   100  		outLines := []string{}
   101  
   102  		maxWidth = maxWidth - indentation*2
   103  		for _, line := range lines {
   104  			if f.length(line) <= maxWidth {
   105  				outLines = append(outLines, line)
   106  				continue
   107  			}
   108  			words := strings.Split(line, " ")
   109  			outWords := []string{words[0]}
   110  			length := uint(f.length(words[0]))
   111  			for _, word := range words[1:] {
   112  				wordLength := f.length(word)
   113  				if length+wordLength+1 <= maxWidth {
   114  					length += wordLength + 1
   115  					outWords = append(outWords, word)
   116  					continue
   117  				}
   118  				outLines = append(outLines, strings.Join(outWords, " "))
   119  				outWords = []string{word}
   120  				length = wordLength
   121  			}
   122  			if len(outWords) > 0 {
   123  				outLines = append(outLines, strings.Join(outWords, " "))
   124  			}
   125  		}
   126  
   127  		lines = outLines
   128  	}
   129  
   130  	if indentation == 0 {
   131  		return strings.Join(lines, "\n")
   132  	}
   133  
   134  	padding := strings.Repeat("  ", int(indentation))
   135  	for i := range lines {
   136  		if lines[i] != "" {
   137  			lines[i] = padding + lines[i]
   138  		}
   139  	}
   140  
   141  	return strings.Join(lines, "\n")
   142  }
   143  
   144  func (f Formatter) length(styled string) uint {
   145  	n := uint(0)
   146  	inStyle := false
   147  	for _, b := range styled {
   148  		if inStyle {
   149  			if b == 'm' {
   150  				inStyle = false
   151  			}
   152  			continue
   153  		}
   154  		if b == '\x1b' {
   155  			inStyle = true
   156  			continue
   157  		}
   158  		n += 1
   159  	}
   160  	return n
   161  }
   162  
   163  func (f Formatter) CycleJoin(elements []string, joiner string, cycle []string) string {
   164  	if len(elements) == 0 {
   165  		return ""
   166  	}
   167  	n := len(cycle)
   168  	out := ""
   169  	for i, text := range elements {
   170  		out += cycle[i%n] + text
   171  		if i < len(elements)-1 {
   172  			out += joiner
   173  		}
   174  	}
   175  	out += "{{/}}"
   176  	return f.style(out)
   177  }
   178  
   179  func (f Formatter) style(s string) string {
   180  	switch f.ColorMode {
   181  	case ColorModeNone:
   182  		return f.styleRe.ReplaceAllString(s, "")
   183  	case ColorModePassthrough:
   184  		return s
   185  	case ColorModeTerminal:
   186  		return f.styleRe.ReplaceAllStringFunc(s, func(match string) string {
   187  			if out, ok := f.colors[strings.Trim(match, "{}")]; ok {
   188  				return out
   189  			}
   190  			return match
   191  		})
   192  	}
   193  
   194  	return ""
   195  }