Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
aos
GitHub Repository: aos/grafana-agent
Path: blob/main/pkg/river/printer/trimmer.go
4095 views
1
package printer
2
3
import (
4
"io"
5
"text/tabwriter"
6
)
7
8
// A trimmer is an io.Writer which filters tabwriter.Escape characters,
9
// trailing blanks and tabs from lines, and converting \f and \v characters
10
// into \n and \t (if no text/tabwriter is used when printing).
11
//
12
// Text wrapped by tabwriter.Escape characters is written to the underlying
13
// io.Writer unmodified.
14
type trimmer struct {
15
next io.Writer
16
state int
17
space []byte
18
}
19
20
const (
21
trimStateSpace = iota // Trimmer is reading space characters
22
trimStateEscape // Trimmer is reading escaped characters
23
trimStateText // Trimmer is reading text
24
)
25
26
func (t *trimmer) discardWhitespace() {
27
t.state = trimStateSpace
28
t.space = t.space[0:0]
29
}
30
31
func (t *trimmer) Write(data []byte) (n int, err error) {
32
// textStart holds the index of the start of a chunk of text not containing
33
// whitespace. It is reset every time a new chunk of text is encountered.
34
var textStart int
35
36
for off, b := range data {
37
// Convert \v to \t
38
if b == '\v' {
39
b = '\t'
40
}
41
42
switch t.state {
43
case trimStateSpace:
44
// Accumulate tabs and spaces in t.space until finding a non-tab or
45
// non-space character.
46
//
47
// If we find a newline, we write it directly and discard our pending
48
// whitespace (so that trailing whitespace up to the newline is ignored).
49
//
50
// If we find a tabwriter.Escape or text character we transition states.
51
switch b {
52
case '\t', ' ':
53
t.space = append(t.space, b)
54
case '\n', '\f':
55
// Discard all unwritten whitespace before the end of the line and write
56
// a newline.
57
t.discardWhitespace()
58
_, err = t.next.Write([]byte("\n"))
59
case tabwriter.Escape:
60
_, err = t.next.Write(t.space)
61
t.state = trimStateEscape
62
textStart = off + 1 // Skip escape character
63
default:
64
// Non-space character. Write our pending whitespace
65
// and then move to text state.
66
_, err = t.next.Write(t.space)
67
t.state = trimStateText
68
textStart = off
69
}
70
71
case trimStateText:
72
// We're reading a chunk of text. Accumulate characters in the chunk
73
// until we find a whitespace character or a tabwriter.Escape.
74
switch b {
75
case '\t', ' ':
76
_, err = t.next.Write(data[textStart:off])
77
t.discardWhitespace()
78
t.space = append(t.space, b)
79
case '\n', '\f':
80
_, err = t.next.Write(data[textStart:off])
81
t.discardWhitespace()
82
if err == nil {
83
_, err = t.next.Write([]byte("\n"))
84
}
85
case tabwriter.Escape:
86
_, err = t.next.Write(data[textStart:off])
87
t.state = trimStateEscape
88
textStart = off + 1 // +1: skip tabwriter.Escape
89
}
90
91
case trimStateEscape:
92
// Accumulate everything until finding the closing tabwriter.Escape.
93
if b == tabwriter.Escape {
94
_, err = t.next.Write(data[textStart:off])
95
t.discardWhitespace()
96
}
97
98
default:
99
panic("unreachable")
100
}
101
if err != nil {
102
return off, err
103
}
104
}
105
n = len(data)
106
107
// Flush the remainder of the text (as long as it's not whitespace).
108
switch t.state {
109
case trimStateEscape, trimStateText:
110
_, err = t.next.Write(data[textStart:n])
111
t.discardWhitespace()
112
}
113
114
return
115
}
116
117