...

Source file src/github.com/onsi/ginkgo/v2/formatter/formatter.go

Documentation: github.com/onsi/ginkgo/v2/formatter

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

View as plain text