-
Notifications
You must be signed in to change notification settings - Fork 5
/
writer.go
173 lines (149 loc) · 4.7 KB
/
writer.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
package markdown
import (
"bytes"
"io"
"unicode"
"github.com/yuin/goldmark/util"
)
// Line delimiter
const lineDelim byte = '\n'
// linePrefix associates a line prefix with its starting line.
type linePrefix struct {
// startLine and endLine are the line numbers the prefix starts and ends on
startLine, endLine int
// bytes is the bytes of the prefix
bytes []byte
}
// markdownWriter provides an interface similar to io.Writer for writing markdown files. It handles
// errors returned by the underlying writer, and manages output of some rendering contexts like
// container block prefixes.
type markdownWriter struct {
buf *bytes.Buffer
config *Config
// output holds the underlying output writer
output io.Writer
// prefixes holds the current line prefixes
prefixes []linePrefix
// line is the current line number
line int
// err holds the last write error. If non-nil, all write operations become no-ops
err error
}
var _ util.BufWriter = &markdownWriter{}
// newMarkdownWriter returns a new markdownWriter
func newMarkdownWriter(w io.Writer, config *Config) *markdownWriter {
result := &markdownWriter{
config: config,
buf: &bytes.Buffer{},
}
// Reset initializes the rest of the struct
result.Reset(w)
return result
}
// Reset resets all internal state and switches writes to the given writer.
func (m *markdownWriter) Reset(w io.Writer) {
m.buf.Reset()
m.output = w
m.prefixes = make([]linePrefix, 0)
m.line = 0
m.err = nil
}
// WriteLine writes the given bytes as a finished line, regardless of trailing newline.
func (m *markdownWriter) WriteLine(line []byte) (n int) {
n, _ = m.Write(line)
m.FlushLine()
return n
}
// FlushLine ends the current buffered line if non-empty, flushing the contents to the underlying
// writer.
func (m *markdownWriter) FlushLine() {
if m.buf.Len() > 0 {
m.EndLine()
}
}
// EndLine ends the current line, flushing the line buffer regardless of whether it's empty.
func (m *markdownWriter) EndLine() {
_, _ = m.Write([]byte{lineDelim})
}
// PushPrefix adds the given bytes as a prefix for lines written to the output. The prefix
// will be added to the current line and all subsequent lines by default, but can optionally be
// given a start line relative to the current line, and an end line relative to the start line.
func (p *markdownWriter) PushPrefix(bytes []byte, lineRanges ...int) {
prefix := linePrefix{
endLine: -1,
bytes: bytes,
}
if len(lineRanges) > 0 {
prefix.startLine = p.line + lineRanges[0]
if len(lineRanges) > 1 {
prefix.endLine = prefix.startLine + lineRanges[1]
}
}
p.prefixes = append(p.prefixes, prefix)
}
// PopPrefix removes the most recently pushed line prefix from future lines.
func (p *markdownWriter) PopPrefix() {
p.prefixes = p.prefixes[0 : len(p.prefixes)-1]
}
// Write writes the given data to an internal buffer, then writes any complete lines to the
// underlying writer.
func (m *markdownWriter) Write(data []byte) (n int, err error) {
return m.WriteBytes(data), m.err
}
func (m *markdownWriter) WriteBytes(data []byte) (n int) {
if m.err != nil {
return 0
}
// Writing to a bytes.Buffer always returns a nil error
n, _ = m.buf.Write(data)
prefixedLine := bytes.Buffer{}
for bytes.Contains(m.buf.Bytes(), []byte{lineDelim}) {
// err will only be non-nil if lineDelim is not in m.buf, which we already checked for.
line, _ := m.buf.ReadBytes(lineDelim)
// build the prefix for the line
for _, prefix := range m.prefixes {
if prefix.startLine <= m.line && (prefix.endLine == -1 || m.line <= prefix.endLine) {
prefixedLine.Write(prefix.bytes)
}
}
prefixedLine.Write(line)
// trim whitespace off the end of the line
trimmedSlice := bytes.TrimRightFunc(prefixedLine.Bytes(), unicode.IsSpace)
prefixedLine.Truncate(len(trimmedSlice))
prefixedLine.WriteByte(lineDelim)
_, err := m.output.Write(prefixedLine.Bytes())
if err != nil {
m.err = err
return 0
}
m.line += 1
prefixedLine.Reset()
}
return n
}
// Err returns the last write error, or nil.
func (m *markdownWriter) Err() error {
return m.err
}
// Available returns how many bytes are unused in the buffer.
func (m *markdownWriter) Available() int {
return m.buf.Available()
}
// Buffered returns the number of bytes that have been written into the current buffer.
func (m *markdownWriter) Buffered() int {
return m.buf.Len()
}
// Flush flushes the contents of the buffer to the output.
func (m *markdownWriter) Flush() error {
m.FlushLine()
return nil
}
func (m *markdownWriter) WriteByte(c byte) error {
return m.buf.WriteByte(c)
}
func (m *markdownWriter) WriteRune(r rune) (size int, err error) {
return m.buf.WriteRune(r)
}
func (m *markdownWriter) WriteString(s string) (n int, err error) {
return m.buf.WriteString(s)
}