From 1a00beadfe7436b29c246c88af0ed4083f195bf4 Mon Sep 17 00:00:00 2001 From: Bennett Clement Date: Mon, 26 Dec 2022 21:56:24 +0800 Subject: [PATCH 1/4] renderer: add support for extending node renderers --- renderer.go | 119 ++++++++++++++++++++++++++++--------------------- writer.go | 44 +++++++++++++++--- writer_test.go | 10 +++-- 3 files changed, 115 insertions(+), 58 deletions(-) diff --git a/renderer.go b/renderer.go index df4a1df..312e29b 100644 --- a/renderer.go +++ b/renderer.go @@ -5,6 +5,7 @@ import ( "bytes" "fmt" "io" + "sync" "github.com/yuin/goldmark/ast" "github.com/yuin/goldmark/renderer" @@ -14,8 +15,10 @@ import ( // NewRenderer returns a new markdown Renderer that is configured by default values. func NewRenderer(options ...Option) *Renderer { r := &Renderer{ - config: NewConfig(), - rc: renderContext{}, + config: NewConfig(), + rc: renderContext{}, + maxKind: 20, // a random number slightly larger than the number of default ast kinds + nodeRendererFuncsTmp: map[ast.NodeKind]renderer.NodeRendererFunc{}, } for _, opt := range options { opt.SetMarkdownOption(r.config) @@ -25,10 +28,16 @@ func NewRenderer(options ...Option) *Renderer { // Renderer is an implementation of renderer.Renderer that renders nodes as Markdown type Renderer struct { - config *Config - rc renderContext + config *Config + rc renderContext + nodeRendererFuncsTmp map[ast.NodeKind]renderer.NodeRendererFunc + maxKind int + nodeRendererFuncs []nodeRenderer + initSync sync.Once } +var _ renderer.Renderer = &Renderer{} + // AddOptions implements renderer.Renderer.AddOptions func (r *Renderer) AddOptions(opts ...renderer.Option) { config := renderer.NewConfig() @@ -38,64 +47,74 @@ func (r *Renderer) AddOptions(opts ...renderer.Option) { for name, value := range config.Options { r.config.SetOption(name, value) } - // TODO handle any config.NodeRenderers set by opts + + // handle any config.NodeRenderers set by opts + config.NodeRenderers.Sort() + l := len(config.NodeRenderers) + for i := l - 1; i >= 0; i-- { + v := config.NodeRenderers[i] + nr, _ := v.Value.(renderer.NodeRenderer) + nr.RegisterFuncs(r) + } +} + +func (r *Renderer) Register(kind ast.NodeKind, fun renderer.NodeRendererFunc) { + r.nodeRendererFuncsTmp[kind] = fun + if int(kind) > r.maxKind { + r.maxKind = int(kind) + } } // Render implements renderer.Renderer.Render func (r *Renderer) Render(w io.Writer, source []byte, n ast.Node) error { r.rc = newRenderContext(w, source, r.config) - /* TODO - reg.Register(ast.KindString, r.renderString) - */ + r.initSync.Do(func() { + r.nodeRendererFuncs = make([]nodeRenderer, r.maxKind+1) + // add default functions + // blocks + r.nodeRendererFuncs[ast.KindDocument] = r.renderBlockSeparator + r.nodeRendererFuncs[ast.KindHeading] = r.chainRenderers(r.renderBlockSeparator, r.renderHeading) + r.nodeRendererFuncs[ast.KindBlockquote] = r.chainRenderers(r.renderBlockSeparator, r.renderBlockquote) + r.nodeRendererFuncs[ast.KindCodeBlock] = r.chainRenderers(r.renderBlockSeparator, r.renderCodeBlock) + r.nodeRendererFuncs[ast.KindFencedCodeBlock] = r.chainRenderers(r.renderBlockSeparator, r.renderFencedCodeBlock) + r.nodeRendererFuncs[ast.KindHTMLBlock] = r.chainRenderers(r.renderBlockSeparator, r.renderHTMLBlock) + r.nodeRendererFuncs[ast.KindList] = r.chainRenderers(r.renderBlockSeparator, r.renderList) + r.nodeRendererFuncs[ast.KindListItem] = r.chainRenderers(r.renderBlockSeparator, r.renderListItem) + r.nodeRendererFuncs[ast.KindParagraph] = r.renderBlockSeparator + r.nodeRendererFuncs[ast.KindTextBlock] = r.renderBlockSeparator + r.nodeRendererFuncs[ast.KindThematicBreak] = r.chainRenderers(r.renderBlockSeparator, r.renderThematicBreak) + + // inlines + r.nodeRendererFuncs[ast.KindAutoLink] = r.renderAutoLink + r.nodeRendererFuncs[ast.KindCodeSpan] = r.renderCodeSpan + r.nodeRendererFuncs[ast.KindEmphasis] = r.renderEmphasis + r.nodeRendererFuncs[ast.KindImage] = r.renderImage + r.nodeRendererFuncs[ast.KindLink] = r.renderLink + r.nodeRendererFuncs[ast.KindRawHTML] = r.renderRawHTML + r.nodeRendererFuncs[ast.KindText] = r.renderText + // TODO: add KindString + // r.nodeRendererFuncs[ast.KindString] = r.renderString + + for kind, fun := range r.nodeRendererFuncsTmp { + r.nodeRendererFuncs[kind] = r.transform(fun) + } + r.nodeRendererFuncsTmp = nil + }) return ast.Walk(n, func(n ast.Node, entering bool) (ast.WalkStatus, error) { - return r.getRenderer(n)(n, entering), r.rc.writer.Err() + return r.nodeRendererFuncs[n.Kind()](n, entering), r.rc.writer.Err() }) } -// nodeRenderer is a markdown node renderer func. -type nodeRenderer func(ast.Node, bool) ast.WalkStatus - -func (r *Renderer) getRenderer(node ast.Node) nodeRenderer { - renderers := []nodeRenderer{} - switch node.Type() { - case ast.TypeBlock: - renderers = append(renderers, r.renderBlockSeparator) - } - switch node.Kind() { - case ast.KindAutoLink: - renderers = append(renderers, r.renderAutoLink) - case ast.KindHeading: - renderers = append(renderers, r.renderHeading) - case ast.KindBlockquote: - renderers = append(renderers, r.renderBlockquote) - case ast.KindCodeBlock: - renderers = append(renderers, r.renderCodeBlock) - case ast.KindCodeSpan: - renderers = append(renderers, r.renderCodeSpan) - case ast.KindEmphasis: - renderers = append(renderers, r.renderEmphasis) - case ast.KindThematicBreak: - renderers = append(renderers, r.renderThematicBreak) - case ast.KindFencedCodeBlock: - renderers = append(renderers, r.renderFencedCodeBlock) - case ast.KindHTMLBlock: - renderers = append(renderers, r.renderHTMLBlock) - case ast.KindImage: - renderers = append(renderers, r.renderImage) - case ast.KindList: - renderers = append(renderers, r.renderList) - case ast.KindListItem: - renderers = append(renderers, r.renderListItem) - case ast.KindRawHTML: - renderers = append(renderers, r.renderRawHTML) - case ast.KindText: - renderers = append(renderers, r.renderText) - case ast.KindLink: - renderers = append(renderers, r.renderLink) +func (r *Renderer) transform(fn renderer.NodeRendererFunc) nodeRenderer { + return func(n ast.Node, entering bool) ast.WalkStatus { + status, _ := fn(r.rc.writer, r.rc.source, n, entering) + return status } - return r.chainRenderers(renderers...) } +// nodeRenderer is a markdown node renderer func. +type nodeRenderer func(ast.Node, bool) ast.WalkStatus + func (r *Renderer) chainRenderers(renderers ...nodeRenderer) nodeRenderer { return func(node ast.Node, entering bool) ast.WalkStatus { var walkStatus ast.WalkStatus diff --git a/writer.go b/writer.go index 736b118..9fe0cc7 100644 --- a/writer.go +++ b/writer.go @@ -4,6 +4,8 @@ import ( "bytes" "io" "unicode" + + "github.com/yuin/goldmark/util" ) // Line delimiter @@ -33,6 +35,8 @@ type markdownWriter struct { err error } +var _ util.BufWriter = &markdownWriter{} + // newMarkdownWriter returns a new markdownWriter func newMarkdownWriter(w io.Writer, config *Config) *markdownWriter { result := &markdownWriter{ @@ -55,7 +59,7 @@ func (m *markdownWriter) Reset(w io.Writer) { // 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) + n, _ = m.Write(line) m.FlushLine() return n @@ -98,9 +102,9 @@ func (p *markdownWriter) PopPrefix() { // 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) { +func (m *markdownWriter) Write(data []byte) (n int, err error) { if m.err != nil { - return 0 + return 0, m.err } // Writing to a bytes.Buffer always returns a nil error n, _ = m.buf.Write(data) @@ -123,15 +127,45 @@ func (m *markdownWriter) Write(data []byte) (n int) { _, err := m.output.Write(prefixedLine.Bytes()) if err != nil { m.err = err - return 0 + return 0, m.err } m.line += 1 prefixedLine.Reset() } - return n + return n, nil } // Err returns the last write error, or nil. func (m *markdownWriter) Err() error { return m.err } + +// returns how many bytes are unused in the buffer. +func (m *markdownWriter) Available() int { + return m.buf.Cap() - m.buf.Len() +} + +func (m *markdownWriter) Buffered() int { + return m.buf.Len() +} + +func (m *markdownWriter) Flush() error { + _, err := m.output.Write(m.buf.Bytes()) + if err != nil { + return err + } + m.buf.Reset() + 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) +} diff --git a/writer_test.go b/writer_test.go index cc8e61d..0fc5f0e 100644 --- a/writer_test.go +++ b/writer_test.go @@ -160,15 +160,19 @@ func TestWriteError(t *testing.T) { writer := newMarkdownWriter(&ew, NewConfig()) data := []byte("foo\n") - assert.Equal(len(data), writer.Write(data), "Writes should succeed before error") + var n int + n, _ = writer.Write(data) + assert.Equal(len(data), n, "Writes should succeed before error") assert.Equal(len(data), writer.WriteLine(data), "Writes should succeed before error") ew.err = err - assert.Equal(0, writer.Write(data), "Once error is set, writes become no-op") + n, _ = writer.Write(data) + assert.Equal(0, n, "Once error is set, writes become no-op") assert.Equal(0, writer.WriteLine(data), "Once error is set, writes become no-op") assert.Equal(err, writer.Err(), "Err() should match error returned by errorWriter") ew.err = nil writer.Reset(&ew) - assert.Equal(len(data), writer.Write(data), "Writes should succeed after Reset") + n, _ = writer.Write(data) + assert.Equal(len(data), n, "Writes should succeed after Reset") assert.Equal(len(data), writer.WriteLine(data), "Writes should succeed after Reset") } From 0e6a9a7ea65db3012d1e24d3a947bdb15510dc69 Mon Sep 17 00:00:00 2001 From: Terrance Kennedy Date: Wed, 7 Feb 2024 22:04:00 -0700 Subject: [PATCH 2/4] Add tests for custom node renderers --- renderer.go | 1 + renderer_test.go | 20 +++++++++++++++++++- writer.go | 12 +++++------- writer_test.go | 23 ++++++++++++++++++++++- 4 files changed, 47 insertions(+), 9 deletions(-) diff --git a/renderer.go b/renderer.go index 312e29b..32afa17 100644 --- a/renderer.go +++ b/renderer.go @@ -105,6 +105,7 @@ func (r *Renderer) Render(w io.Writer, source []byte, n ast.Node) error { }) } +// transform wraps a renderer.NodeRendererFunc to match the nodeRenderer function signature func (r *Renderer) transform(fn renderer.NodeRendererFunc) nodeRenderer { return func(n ast.Node, entering bool) ast.WalkStatus { status, _ := fn(r.rc.writer, r.rc.source, n, entering) diff --git a/renderer_test.go b/renderer_test.go index 5848258..cea2dde 100644 --- a/renderer_test.go +++ b/renderer_test.go @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/yuin/goldmark" "github.com/yuin/goldmark/ast" + "github.com/yuin/goldmark/extension" "github.com/yuin/goldmark/parser" "github.com/yuin/goldmark/text" "github.com/yuin/goldmark/util" @@ -59,9 +60,26 @@ func TestRenderError(t *testing.T) { assert.Equal(t, err, result) } +// TestCustomRenderers tests that the renderer uses any config.NodeRenderers defined by the user +func TestCustomRenderers(t *testing.T) { + md := goldmark.New( + goldmark.WithRenderer(NewRenderer()), + goldmark.WithParserOptions(parser.WithASTTransformers(util.Prioritized(&transformer, 0))), + ) + buf := bytes.Buffer{} + source := `# My Tasks +- [x] Add support for custom renderers + ` + + extension.TaskList.Extend(md) + err := md.Convert([]byte(source), &buf) + assert.NoError(t, err) + t.Log(buf.String()) +} + // TestRenderedOutput tests that the renderer produces the expected output for all test cases func TestRenderedOutput(t *testing.T) { - var testCases = []struct { + testCases := []struct { name string options []Option source string diff --git a/writer.go b/writer.go index 9fe0cc7..29df2b6 100644 --- a/writer.go +++ b/writer.go @@ -140,21 +140,19 @@ func (m *markdownWriter) Err() error { return m.err } -// returns how many bytes are unused in the buffer. +// Available returns how many bytes are unused in the buffer. func (m *markdownWriter) Available() int { - return m.buf.Cap() - m.buf.Len() + 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 { - _, err := m.output.Write(m.buf.Bytes()) - if err != nil { - return err - } - m.buf.Reset() + m.FlushLine() return nil } diff --git a/writer_test.go b/writer_test.go index 0fc5f0e..2a5e1ef 100644 --- a/writer_test.go +++ b/writer_test.go @@ -7,8 +7,29 @@ import ( "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) +func TestWrite(t *testing.T) { + buf := &bytes.Buffer{} + writer := newMarkdownWriter(buf, NewConfig()) + + available := writer.Available() + assert.Equal(t, 0, available) + assert.Equal(t, 0, writer.Buffered()) + + writer.WriteByte(byte('b')) + writer.WriteRune('r') + writer.WriteString("s") + + assert.Less(t, available, writer.Available()) + assert.Equal(t, 3, writer.Buffered()) + + err := writer.Flush() + require.NoError(t, err) + assert.Equal(t, "brs\n", buf.String()) +} + // TestFlushLine tests that the writer will flush the current buffered line if non-empty. func TestFlushLine(t *testing.T) { assert := assert.New(t) @@ -38,7 +59,7 @@ func TestEndLine(t *testing.T) { // TestWriterOutputs tests that the writer produces expected output in various scenarios. func TestWriterOutputs(t *testing.T) { - var testCases = []struct { + testCases := []struct { name string writeFunc func(writer *markdownWriter) expected string From b59aae59a6219a44cb24b6d17b9679a9566b1d95 Mon Sep 17 00:00:00 2001 From: Terrance Kennedy Date: Wed, 7 Feb 2024 22:20:46 -0700 Subject: [PATCH 3/4] Update module to go 1.21 --- .github/workflows/test.yml | 2 +- go.mod | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f0fd8f4..1ffa4e5 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -9,7 +9,7 @@ on: - main env: - go_version: 1.18.x + go_version: 1.21.x jobs: lint: diff --git a/go.mod b/go.mod index cde0509..77d27ed 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/teekennedy/goldmark-markdown -go 1.18 +go 1.21 require ( github.com/rhysd/go-fakeio v1.0.0 From 10814c2726ad2527feed4007da8412f8f2b84a58 Mon Sep 17 00:00:00 2001 From: Terrance Kennedy Date: Wed, 7 Feb 2024 22:51:18 -0700 Subject: [PATCH 4/4] Fix lint errors --- .github/workflows/test.yml | 2 +- renderer.go | 50 +++++++++++++++++++------------------- writer.go | 12 ++++++--- writer_test.go | 23 +++++++++++------- 4 files changed, 48 insertions(+), 39 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 1ffa4e5..cf196fe 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -24,7 +24,7 @@ jobs: - name: Run linters uses: golangci/golangci-lint-action@v3 with: - version: v1.52.2 + version: v1.56.0 test: runs-on: ubuntu-latest diff --git a/renderer.go b/renderer.go index 32afa17..d1fc294 100644 --- a/renderer.go +++ b/renderer.go @@ -146,10 +146,10 @@ func (r *Renderer) renderBlockSeparator(node ast.Node, entering bool) ast.WalkSt func (r *Renderer) renderAutoLink(node ast.Node, entering bool) ast.WalkStatus { n := node.(*ast.AutoLink) if entering { - r.rc.writer.Write([]byte("<")) - r.rc.writer.Write(n.URL(r.rc.source)) + r.rc.writer.WriteBytes([]byte("<")) + r.rc.writer.WriteBytes(n.URL(r.rc.source)) } else { - r.rc.writer.Write([]byte(">")) + r.rc.writer.WriteBytes([]byte(">")) } return ast.WalkContinue } @@ -182,15 +182,15 @@ func (r *Renderer) renderHeading(node ast.Node, entering bool) ast.WalkStatus { func (r *Renderer) renderATXHeading(node *ast.Heading, entering bool) ast.WalkStatus { if entering { - r.rc.writer.Write(bytes.Repeat([]byte("#"), node.Level)) + r.rc.writer.WriteBytes(bytes.Repeat([]byte("#"), node.Level)) // Only print space after heading if non-empty if node.HasChildren() { - r.rc.writer.Write([]byte(" ")) + r.rc.writer.WriteBytes([]byte(" ")) } } else { if r.config.HeadingStyle == HeadingStyleATXSurround { - r.rc.writer.Write([]byte(" ")) - r.rc.writer.Write(bytes.Repeat([]byte("#"), node.Level)) + r.rc.writer.WriteBytes([]byte(" ")) + r.rc.writer.WriteBytes(bytes.Repeat([]byte("#"), node.Level)) } } return ast.WalkContinue @@ -213,8 +213,8 @@ func (r *Renderer) renderSetextHeading(node *ast.Heading, entering bool) ast.Wal } } } - r.rc.writer.Write([]byte("\n")) - r.rc.writer.Write(bytes.Repeat(underlineChar, underlineWidth)) + r.rc.writer.WriteBytes([]byte("\n")) + r.rc.writer.WriteBytes(bytes.Repeat(underlineChar, underlineWidth)) return ast.WalkContinue } @@ -228,7 +228,7 @@ func (r *Renderer) renderThematicBreak(node ast.Node, entering bool) ast.WalkSta } else { breakLen = int(r.config.ThematicBreakLength) } - r.rc.writer.Write(bytes.Repeat(breakChar, breakLen)) + r.rc.writer.WriteBytes(bytes.Repeat(breakChar, breakLen)) } return ast.WalkContinue } @@ -245,10 +245,10 @@ func (r *Renderer) renderCodeBlock(node ast.Node, entering bool) ast.WalkStatus func (r *Renderer) renderFencedCodeBlock(node ast.Node, entering bool) ast.WalkStatus { n := node.(*ast.FencedCodeBlock) - r.rc.writer.Write([]byte("```")) + r.rc.writer.WriteBytes([]byte("```")) if entering { if info := n.Info; info != nil { - r.rc.writer.Write(info.Text(r.rc.source)) + r.rc.writer.WriteBytes(info.Text(r.rc.source)) } r.rc.writer.FlushLine() r.renderLines(node, entering) @@ -315,7 +315,7 @@ func (r *Renderer) renderText(node ast.Node, entering bool) ast.WalkStatus { if entering { text := n.Text(r.rc.source) - r.rc.writer.Write(text) + r.rc.writer.WriteBytes(text) if n.SoftLineBreak() { r.rc.writer.EndLine() } @@ -327,7 +327,7 @@ func (r *Renderer) renderSegments(segments *text.Segments, asLines bool) { for i := 0; i < segments.Len(); i++ { segment := segments.At(i) value := segment.Value(r.rc.source) - r.rc.writer.Write(value) + r.rc.writer.WriteBytes(value) if asLines { r.rc.writer.FlushLine() } @@ -350,32 +350,32 @@ func (r *Renderer) renderLink(node ast.Node, entering bool) ast.WalkStatus { func (r *Renderer) renderImage(node ast.Node, entering bool) ast.WalkStatus { n := node.(*ast.Image) if entering { - r.rc.writer.Write([]byte("!")) + r.rc.writer.WriteBytes([]byte("!")) } return r.renderLinkCommon(n.Title, n.Destination, entering) } func (r *Renderer) renderLinkCommon(title, destination []byte, entering bool) ast.WalkStatus { if entering { - r.rc.writer.Write([]byte("[")) + r.rc.writer.WriteBytes([]byte("[")) } else { - r.rc.writer.Write([]byte("](")) - r.rc.writer.Write(destination) + r.rc.writer.WriteBytes([]byte("](")) + r.rc.writer.WriteBytes(destination) if len(title) > 0 { - r.rc.writer.Write([]byte(" \"")) - r.rc.writer.Write(title) - r.rc.writer.Write([]byte("\"")) + r.rc.writer.WriteBytes([]byte(" \"")) + r.rc.writer.WriteBytes(title) + r.rc.writer.WriteBytes([]byte("\"")) } - r.rc.writer.Write([]byte(")")) + r.rc.writer.WriteBytes([]byte(")")) } return ast.WalkContinue } func (r *Renderer) renderCodeSpan(node ast.Node, entering bool) ast.WalkStatus { if bytes.Count(node.Text(r.rc.source), []byte("`"))%2 != 0 { - r.rc.writer.Write([]byte("``")) + r.rc.writer.WriteBytes([]byte("``")) } else { - r.rc.writer.Write([]byte("`")) + r.rc.writer.WriteBytes([]byte("`")) } return ast.WalkContinue @@ -383,7 +383,7 @@ func (r *Renderer) renderCodeSpan(node ast.Node, entering bool) ast.WalkStatus { func (r *Renderer) renderEmphasis(node ast.Node, entering bool) ast.WalkStatus { n := node.(*ast.Emphasis) - r.rc.writer.Write(bytes.Repeat([]byte{'*'}, n.Level)) + r.rc.writer.WriteBytes(bytes.Repeat([]byte{'*'}, n.Level)) return ast.WalkContinue } diff --git a/writer.go b/writer.go index 29df2b6..4c9ca02 100644 --- a/writer.go +++ b/writer.go @@ -75,7 +75,7 @@ func (m *markdownWriter) FlushLine() { // EndLine ends the current line, flushing the line buffer regardless of whether it's empty. func (m *markdownWriter) EndLine() { - m.Write([]byte{lineDelim}) + _, _ = m.Write([]byte{lineDelim}) } // PushPrefix adds the given bytes as a prefix for lines written to the output. The prefix @@ -103,8 +103,12 @@ func (p *markdownWriter) PopPrefix() { // 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, m.err + return 0 } // Writing to a bytes.Buffer always returns a nil error n, _ = m.buf.Write(data) @@ -127,12 +131,12 @@ func (m *markdownWriter) Write(data []byte) (n int, err error) { _, err := m.output.Write(prefixedLine.Bytes()) if err != nil { m.err = err - return 0, m.err + return 0 } m.line += 1 prefixedLine.Reset() } - return n, nil + return n } // Err returns the last write error, or nil. diff --git a/writer_test.go b/writer_test.go index 2a5e1ef..010d2c2 100644 --- a/writer_test.go +++ b/writer_test.go @@ -18,16 +18,21 @@ func TestWrite(t *testing.T) { assert.Equal(t, 0, available) assert.Equal(t, 0, writer.Buffered()) - writer.WriteByte(byte('b')) - writer.WriteRune('r') - writer.WriteString("s") + err := writer.WriteByte(byte('b')) + require.NoError(t, err) + _, err = writer.Write([]byte("y")) + require.NoError(t, err) + _, err = writer.WriteRune('t') + require.NoError(t, err) + _, err = writer.WriteString("e") + require.NoError(t, err) assert.Less(t, available, writer.Available()) - assert.Equal(t, 3, writer.Buffered()) + assert.Equal(t, 4, writer.Buffered()) - err := writer.Flush() + err = writer.Flush() require.NoError(t, err) - assert.Equal(t, "brs\n", buf.String()) + assert.Equal(t, "byte\n", buf.String()) } // TestFlushLine tests that the writer will flush the current buffered line if non-empty. @@ -38,7 +43,7 @@ func TestFlushLine(t *testing.T) { writer.FlushLine() assert.Equal("", buf.String(), "FlushLine() on an empty buffer should not produce output") - writer.Write([]byte("foobar")) + writer.WriteBytes([]byte("foobar")) writer.FlushLine() assert.Equal("foobar\n", buf.String(), "FlushLine() on partial line should produce output.") } @@ -51,7 +56,7 @@ func TestEndLine(t *testing.T) { writer.EndLine() assert.Equal("\n", buf.String(), "EndLine() should write newline to output") - writer.Write([]byte("A line")) + writer.WriteBytes([]byte("A line")) assert.Equal("\n", buf.String(), "Writing a partial line should not produce output.") writer.FlushLine() assert.Equal("\nA line\n", buf.String(), "FlushLine() on partial line should produce output.") @@ -100,7 +105,7 @@ func TestWriterOutputs(t *testing.T) { func(writer *markdownWriter) { lines := []string{"Consider me", "As one who loved poetry", "And persimmons."} writer.PushPrefix([]byte(" "), 1) - writer.Write([]byte("- ")) + writer.WriteBytes([]byte("- ")) for _, line := range lines { writer.WriteLine([]byte(line)) }