diff --git a/tools/transformer/goldmark/renderer/markdown/LICENSE b/tools/transformer/goldmark/renderer/markdown/LICENSE deleted file mode 100644 index dc5b2a6..0000000 --- a/tools/transformer/goldmark/renderer/markdown/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2019 Yusuke Inuzuka - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/tools/transformer/goldmark/renderer/markdown/block.go b/tools/transformer/goldmark/renderer/markdown/block.go index 988dc9b..32652f8 100644 --- a/tools/transformer/goldmark/renderer/markdown/block.go +++ b/tools/transformer/goldmark/renderer/markdown/block.go @@ -4,39 +4,71 @@ import ( "strings" "github.com/yuin/goldmark/ast" + rendererHTML "github.com/yuin/goldmark/renderer/html" "github.com/yuin/goldmark/util" ) +func (r *Renderer) renderBlockquote(w util.BufWriter, source []byte, n ast.Node, entering bool) (ast.WalkStatus, error) { + panic("TODO: implement") + if entering { + if n.Attributes() != nil { + _, _ = w.WriteString("') + } else { + _, _ = w.WriteString("
\n") + } + } else { + _, _ = w.WriteString("
\n") + } + return ast.WalkContinue, nil +} + +func (r *Renderer) renderCodeBlock(w util.BufWriter, source []byte, n ast.Node, entering bool) (ast.WalkStatus, error) { + panic("TODO: implement") + if entering { + _, _ = w.WriteString("
")
+		r.writeLines(w, source, n)
+	} else {
+		_, _ = w.WriteString("
\n") + } + return ast.WalkContinue, nil +} + +func (r *Renderer) renderDocument(_ util.BufWriter, _ []byte, _ ast.Node, _ bool) (ast.WalkStatus, error) { + return ast.WalkContinue, nil +} + func (r *Renderer) renderFencedCodeBlock(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) { n := node.(*ast.FencedCodeBlock) if entering { - r.Write(w, "```") + r.write(w, "```") language := n.Language(source) if language != nil { - r.Write(w, language) + r.write(w, language) } - r.Write(w, '\n') + r.write(w, '\n') r.writeLines(w, source, n) } else { - r.Write(w, "```") + r.write(w, "```") if r.Config.KillercodaActions { if _, ok := n.AttributeString("data-killercoda-exec"); ok { - r.Write(w, "{{exec}}") + r.write(w, "{{exec}}") } if _, ok := n.AttributeString("data-killercoda-copy"); ok { - r.Write(w, "{{copy}}") + r.write(w, "{{copy}}") } } - r.Write(w, '\n') + r.write(w, '\n') if node.NextSibling() != nil { - r.Write(w, '\n') + r.write(w, '\n') } } @@ -47,13 +79,13 @@ func (r *Renderer) renderHeading(w util.BufWriter, _ []byte, node ast.Node, ente n := node.(*ast.Heading) if entering { - r.Write(w, strings.Repeat("#", n.Level)) - r.Write(w, ' ') + r.write(w, strings.Repeat("#", n.Level)) + r.write(w, ' ') } else { - r.Write(w, '\n') + r.write(w, '\n') if node.NextSibling() != nil { - r.Write(w, '\n') + r.write(w, '\n') } } @@ -64,14 +96,14 @@ func (r *Renderer) renderHTMLBlock(w util.BufWriter, source []byte, node ast.Nod n := node.(*ast.HTMLBlock) if entering { - r.Write(w, "\n") + r.write(w, "\n") } else { if n.HasClosure() { - r.Write(w, "\n") + r.write(w, "\n") } if n.NextSibling() != nil { - r.Write(w, '\n') + r.write(w, '\n') } } @@ -81,7 +113,7 @@ func (r *Renderer) renderHTMLBlock(w util.BufWriter, source []byte, node ast.Nod func (r *Renderer) renderList(w util.BufWriter, _ []byte, node ast.Node, entering bool) (ast.WalkStatus, error) { if !entering { if node.NextSibling() != nil { - r.Write(w, '\n') + r.write(w, '\n') } } @@ -97,13 +129,13 @@ func (r *Renderer) renderListItem(w util.BufWriter, _ []byte, node ast.Node, ent } if entering { - r.Write(w, marker) + r.write(w, marker) r.indent += indent } else { r.indent -= indent if node.NextSibling() != nil { - r.Write(w, '\n') + r.write(w, '\n') } } @@ -112,10 +144,10 @@ func (r *Renderer) renderListItem(w util.BufWriter, _ []byte, node ast.Node, ent func (r *Renderer) renderParagraph(w util.BufWriter, _ []byte, node ast.Node, entering bool) (ast.WalkStatus, error) { if !entering { - r.Write(w, '\n') + r.write(w, '\n') if node.NextSibling() != nil { - r.Write(w, '\n') + r.write(w, '\n') } } @@ -124,8 +156,21 @@ func (r *Renderer) renderParagraph(w util.BufWriter, _ []byte, node ast.Node, en func (r *Renderer) renderTextBlock(w util.BufWriter, _ []byte, n ast.Node, entering bool) (ast.WalkStatus, error) { if !entering { - r.Write(w, '\n') + r.write(w, '\n') } return ast.WalkContinue, nil } + +func (r *Renderer) renderThematicBreak(w util.BufWriter, source []byte, n ast.Node, entering bool) (ast.WalkStatus, error) { + panic("TODO: implement") + if !entering { + return ast.WalkContinue, nil + } + _, _ = w.WriteString("\n") + return ast.WalkContinue, nil +} diff --git a/tools/transformer/goldmark/renderer/markdown/goldmark.go b/tools/transformer/goldmark/renderer/markdown/goldmark.go deleted file mode 100644 index 9b5493a..0000000 --- a/tools/transformer/goldmark/renderer/markdown/goldmark.go +++ /dev/null @@ -1,465 +0,0 @@ -// Copyright (c) 2019 Yusuke Inuzuka -// The MIT license associated with that copyright notice is included in the LICENSE file in this directory. -package markdown - -import ( - "bytes" - "strconv" - - "github.com/yuin/goldmark/ast" - "github.com/yuin/goldmark/renderer" - "github.com/yuin/goldmark/util" -) - -// RegisterFuncs implements NodeRenderer.RegisterFuncs . -func (r *Renderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) { - // blocks - - reg.Register(ast.KindDocument, r.renderDocument) - reg.Register(ast.KindHeading, r.renderHeading) - reg.Register(ast.KindBlockquote, r.renderBlockquote) - reg.Register(ast.KindCodeBlock, r.renderCodeBlock) - reg.Register(ast.KindFencedCodeBlock, r.renderFencedCodeBlock) - reg.Register(ast.KindHTMLBlock, r.renderHTMLBlock) - reg.Register(ast.KindList, r.renderList) - reg.Register(ast.KindListItem, r.renderListItem) - reg.Register(ast.KindParagraph, r.renderParagraph) - reg.Register(ast.KindTextBlock, r.renderTextBlock) - reg.Register(ast.KindThematicBreak, r.renderThematicBreak) - - // inlines - - reg.Register(ast.KindAutoLink, r.renderAutoLink) - reg.Register(ast.KindCodeSpan, r.renderCodeSpan) - reg.Register(ast.KindEmphasis, r.renderEmphasis) - reg.Register(ast.KindImage, r.renderImage) - reg.Register(ast.KindLink, r.renderLink) - reg.Register(ast.KindRawHTML, r.renderRawHTML) - reg.Register(ast.KindText, r.renderText) - reg.Register(ast.KindString, r.renderString) -} - -// GlobalAttributeFilter defines attribute names which any elements can have. -var GlobalAttributeFilter = util.NewBytesFilter( - []byte("accesskey"), - []byte("autocapitalize"), - []byte("autofocus"), - []byte("class"), - []byte("contenteditable"), - []byte("dir"), - []byte("draggable"), - []byte("enterkeyhint"), - []byte("hidden"), - []byte("id"), - []byte("inert"), - []byte("inputmode"), - []byte("is"), - []byte("itemid"), - []byte("itemprop"), - []byte("itemref"), - []byte("itemscope"), - []byte("itemtype"), - []byte("lang"), - []byte("part"), - []byte("role"), - []byte("slot"), - []byte("spellcheck"), - []byte("style"), - []byte("tabindex"), - []byte("title"), - []byte("translate"), -) - -func (r *Renderer) renderDocument( - w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) { - // nothing to do - return ast.WalkContinue, nil -} - -// HeadingAttributeFilter defines attribute names which heading elements can have. -var HeadingAttributeFilter = GlobalAttributeFilter - -// BlockquoteAttributeFilter defines attribute names which blockquote elements can have. -var BlockquoteAttributeFilter = GlobalAttributeFilter.Extend( - []byte("cite"), -) - -func (r *Renderer) renderBlockquote( - w util.BufWriter, source []byte, n ast.Node, entering bool) (ast.WalkStatus, error) { - if entering { - if n.Attributes() != nil { - _, _ = w.WriteString("') - } else { - _, _ = w.WriteString("
\n") - } - } else { - _, _ = w.WriteString("
\n") - } - return ast.WalkContinue, nil -} - -func (r *Renderer) renderCodeBlock(w util.BufWriter, source []byte, n ast.Node, entering bool) (ast.WalkStatus, error) { - if entering { - _, _ = w.WriteString("
")
-		r.writeLines(w, source, n)
-	} else {
-		_, _ = w.WriteString("
\n") - } - return ast.WalkContinue, nil -} - -// ListAttributeFilter defines attribute names which list elements can have. -var ListAttributeFilter = GlobalAttributeFilter.Extend( - []byte("start"), - []byte("reversed"), - []byte("type"), -) - -// ListItemAttributeFilter defines attribute names which list item elements can have. -var ListItemAttributeFilter = GlobalAttributeFilter.Extend( - []byte("value"), -) - -// ParagraphAttributeFilter defines attribute names which paragraph elements can have. -var ParagraphAttributeFilter = GlobalAttributeFilter - -// ThematicAttributeFilter defines attribute names which hr elements can have. -var ThematicAttributeFilter = GlobalAttributeFilter.Extend( - []byte("align"), // [Deprecated] - []byte("color"), // [Not Standardized] - []byte("noshade"), // [Deprecated] - []byte("size"), // [Deprecated] - []byte("width"), // [Deprecated] -) - -func (r *Renderer) renderThematicBreak( - w util.BufWriter, source []byte, n ast.Node, entering bool) (ast.WalkStatus, error) { - if !entering { - return ast.WalkContinue, nil - } - _, _ = w.WriteString("\n") - return ast.WalkContinue, nil -} - -// LinkAttributeFilter defines attribute names which link elements can have. -var LinkAttributeFilter = GlobalAttributeFilter.Extend( - []byte("download"), - // []byte("href"), - []byte("hreflang"), - []byte("media"), - []byte("ping"), - []byte("referrerpolicy"), - []byte("rel"), - []byte("shape"), - []byte("target"), -) - -func (r *Renderer) renderAutoLink( - w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) { - n := node.(*ast.AutoLink) - if !entering { - return ast.WalkContinue, nil - } - _, _ = w.WriteString(`') - } else { - _, _ = w.WriteString(`">`) - } - _, _ = w.Write(util.EscapeHTML(label)) - _, _ = w.WriteString(``) - return ast.WalkContinue, nil -} - -// CodeAttributeFilter defines attribute names which code elements can have. -var CodeAttributeFilter = GlobalAttributeFilter - -// EmphasisAttributeFilter defines attribute names which emphasis elements can have. -var EmphasisAttributeFilter = GlobalAttributeFilter - -// ImageAttributeFilter defines attribute names which image elements can have. -var ImageAttributeFilter = GlobalAttributeFilter.Extend( - []byte("align"), - []byte("border"), - []byte("crossorigin"), - []byte("decoding"), - []byte("height"), - []byte("importance"), - []byte("intrinsicsize"), - []byte("ismap"), - []byte("loading"), - []byte("referrerpolicy"), - []byte("sizes"), - []byte("srcset"), - []byte("usemap"), - []byte("width"), -) - -var dataPrefix = []byte("data-") - -// RenderAttributes renders given node's attributes. -// You can specify attribute names to render by the filter. -// If filter is nil, RenderAttributes renders all attributes. -func RenderAttributes(w util.BufWriter, node ast.Node, filter util.BytesFilter) { - for _, attr := range node.Attributes() { - if filter != nil && !filter.Contains(attr.Name) { - if !bytes.HasPrefix(attr.Name, dataPrefix) { - continue - } - } - _, _ = w.WriteString(" ") - _, _ = w.Write(attr.Name) - _, _ = w.WriteString(`="`) - // TODO: convert numeric values to strings - var value []byte - switch typed := attr.Value.(type) { - case []byte: - value = typed - case string: - value = util.StringToReadOnlyBytes(typed) - } - _, _ = w.Write(util.EscapeHTML(value)) - _ = w.WriteByte('"') - } -} - -// A Writer interface writes textual contents to a writer. -type Writer interface { - // Write writes the given source to writer with resolving references and unescaping - // backslash escaped characters. - Write(writer util.BufWriter, source []byte) - - // RawWrite writes the given source to writer without resolving references and - // unescaping backslash escaped characters. - RawWrite(writer util.BufWriter, source []byte) - - // SecureWrite writes the given source to writer with replacing insecure characters. - SecureWrite(writer util.BufWriter, source []byte) -} - -var replacementCharacter = []byte("\ufffd") - -// A WriterConfig struct has configurations for the HTML based writers. -type WriterConfig struct { - // EscapedSpace is an option that indicates that a '\' escaped half-space(0x20) should not be rendered. - EscapedSpace bool -} - -// A WriterOption interface sets options for HTML based writers. -type WriterOption func(*WriterConfig) - -// WithEscapedSpace is a WriterOption indicates that a '\' escaped half-space(0x20) should not be rendered. -func WithEscapedSpace() WriterOption { - return func(c *WriterConfig) { - c.EscapedSpace = true - } -} - -type defaultWriter struct { - WriterConfig -} - -// NewWriter returns a new Writer. -func NewWriter(opts ...WriterOption) Writer { - w := &defaultWriter{} - for _, opt := range opts { - opt(&w.WriterConfig) - } - return w -} - -func escapeRune(writer util.BufWriter, r rune) { - if r < 256 { - v := util.EscapeHTMLByte(byte(r)) - if v != nil { - _, _ = writer.Write(v) - return - } - } - _, _ = writer.WriteRune(util.ToValidRune(r)) -} - -func (d *defaultWriter) SecureWrite(writer util.BufWriter, source []byte) { - n := 0 - l := len(source) - for i := 0; i < l; i++ { - if source[i] == '\u0000' { - _, _ = writer.Write(source[i-n : i]) - n = 0 - _, _ = writer.Write(replacementCharacter) - continue - } - n++ - } - if n != 0 { - _, _ = writer.Write(source[l-n:]) - } -} - -func (d *defaultWriter) RawWrite(writer util.BufWriter, source []byte) { - n := 0 - l := len(source) - for i := 0; i < l; i++ { - v := util.EscapeHTMLByte(source[i]) - if v != nil { - _, _ = writer.Write(source[i-n : i]) - n = 0 - _, _ = writer.Write(v) - continue - } - n++ - } - if n != 0 { - _, _ = writer.Write(source[l-n:]) - } -} - -func (d *defaultWriter) Write(writer util.BufWriter, source []byte) { - escaped := false - var ok bool - limit := len(source) - n := 0 - for i := 0; i < limit; i++ { - c := source[i] - if escaped { - if util.IsPunct(c) { - d.RawWrite(writer, source[n:i-1]) - n = i - escaped = false - continue - } - if d.EscapedSpace && c == ' ' { - d.RawWrite(writer, source[n:i-1]) - n = i + 1 - escaped = false - continue - } - } - if c == '\x00' { - d.RawWrite(writer, source[n:i]) - d.RawWrite(writer, replacementCharacter) - n = i + 1 - escaped = false - continue - } - if c == '&' { - pos := i - next := i + 1 - if next < limit && source[next] == '#' { - nnext := next + 1 - if nnext < limit { - nc := source[nnext] - // code point like #x22; - if nnext < limit && nc == 'x' || nc == 'X' { - start := nnext + 1 - i, ok = util.ReadWhile(source, [2]int{start, limit}, util.IsHexDecimal) - if ok && i < limit && source[i] == ';' && i-start < 7 { - v, _ := strconv.ParseUint(util.BytesToReadOnlyString(source[start:i]), 16, 32) - d.RawWrite(writer, source[n:pos]) - n = i + 1 - escapeRune(writer, rune(v)) - continue - } - // code point like #1234; - } else if nc >= '0' && nc <= '9' { - start := nnext - i, ok = util.ReadWhile(source, [2]int{start, limit}, util.IsNumeric) - if ok && i < limit && i-start < 8 && source[i] == ';' { - v, _ := strconv.ParseUint(util.BytesToReadOnlyString(source[start:i]), 10, 32) - d.RawWrite(writer, source[n:pos]) - n = i + 1 - escapeRune(writer, rune(v)) - continue - } - } - } - } else { - start := next - i, ok = util.ReadWhile(source, [2]int{start, limit}, util.IsAlphaNumeric) - // entity reference - if ok && i < limit && source[i] == ';' { - name := util.BytesToReadOnlyString(source[start:i]) - entity, ok := util.LookUpHTML5EntityByName(name) - if ok { - d.RawWrite(writer, source[n:pos]) - n = i + 1 - d.RawWrite(writer, entity.Characters) - continue - } - } - } - i = next - 1 - } - if c == '\\' { - escaped = true - continue - } - escaped = false - } - d.RawWrite(writer, source[n:]) -} - -// DefaultWriter is a default instance of the Writer. -var DefaultWriter = NewWriter() - -var bDataImage = []byte("data:image/") -var bPng = []byte("png;") -var bGif = []byte("gif;") -var bJpeg = []byte("jpeg;") -var bWebp = []byte("webp;") -var bSvg = []byte("svg+xml;") -var bJs = []byte("javascript:") -var bVb = []byte("vbscript:") -var bFile = []byte("file:") -var bData = []byte("data:") - -func hasPrefix(s, prefix []byte) bool { - return len(s) >= len(prefix) && bytes.Equal(bytes.ToLower(s[0:len(prefix)]), bytes.ToLower(prefix)) -} - -// IsDangerousURL returns true if the given url seems a potentially dangerous url, -// otherwise false. -func IsDangerousURL(url []byte) bool { - if hasPrefix(url, bDataImage) && len(url) >= 11 { - v := url[11:] - if hasPrefix(v, bPng) || hasPrefix(v, bGif) || - hasPrefix(v, bJpeg) || hasPrefix(v, bWebp) || - hasPrefix(v, bSvg) { - return false - } - return true - } - return hasPrefix(url, bJs) || hasPrefix(url, bVb) || - hasPrefix(url, bFile) || hasPrefix(url, bData) -} - -func nodeToHTMLText(n ast.Node, source []byte) []byte { - var buf bytes.Buffer - for c := n.FirstChild(); c != nil; c = c.NextSibling() { - if s, ok := c.(*ast.String); ok && s.IsCode() { - buf.Write(s.Text(source)) - } else if !c.HasChildren() { - buf.Write(util.EscapeHTML(c.Text(source))) - if t, ok := c.(*ast.Text); ok && t.SoftLineBreak() { - buf.WriteByte('\n') - } - } else { - buf.Write(nodeToHTMLText(c, source)) - } - } - return buf.Bytes() -} diff --git a/tools/transformer/goldmark/renderer/markdown/inline.go b/tools/transformer/goldmark/renderer/markdown/inline.go index 3ba27bb..6edbcb5 100644 --- a/tools/transformer/goldmark/renderer/markdown/inline.go +++ b/tools/transformer/goldmark/renderer/markdown/inline.go @@ -5,29 +5,55 @@ import ( "html" "github.com/yuin/goldmark/ast" + rendererHTML "github.com/yuin/goldmark/renderer/html" "github.com/yuin/goldmark/util" ) +func (r *Renderer) renderAutoLink(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) { + panic("TODO: implement") + n := node.(*ast.AutoLink) + if !entering { + return ast.WalkContinue, nil + } + _, _ = w.WriteString(`') + } else { + _, _ = w.WriteString(`">`) + } + _, _ = w.Write(util.EscapeHTML(label)) + _, _ = w.WriteString(``) + return ast.WalkContinue, nil +} + func (r *Renderer) renderCodeSpan(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) { if entering { - r.Write(w, '`') + r.write(w, '`') for c := node.FirstChild(); c != nil; c = c.NextSibling() { segment := c.(*ast.Text).Segment value := segment.Value(source) if bytes.HasSuffix(value, []byte("\n")) { - r.Write(w, value[:len(value)-1]) - r.Write(w, []byte(" ")) + r.write(w, value[:len(value)-1]) + r.write(w, []byte(" ")) } else { - r.Write(w, value) + r.write(w, value) } } return ast.WalkSkipChildren, nil } - r.Write(w, '`') + r.write(w, '`') return ast.WalkContinue, nil } @@ -41,9 +67,9 @@ func (r *Renderer) renderEmphasis(w util.BufWriter, source []byte, node ast.Node } if entering { - r.Write(w, delim) + r.write(w, delim) } else { - r.Write(w, delim) + r.write(w, delim) } return ast.WalkContinue, nil @@ -51,19 +77,19 @@ func (r *Renderer) renderEmphasis(w util.BufWriter, source []byte, node ast.Node func (r *Renderer) renderImage(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) { if entering { - r.Write(w, "![") + r.write(w, "![") } else { n := node.(*ast.Image) - r.Write(w, "](") - r.Write(w, n.Destination) + r.write(w, "](") + r.write(w, n.Destination) if n.Title != nil { - r.Write(w, " \"") - r.Write(w, n.Title) - r.Write(w, "\"") + r.write(w, " \"") + r.write(w, n.Title) + r.write(w, "\"") } - r.Write(w, ')') + r.write(w, ')') } return ast.WalkContinue, nil @@ -73,17 +99,17 @@ func (r *Renderer) renderLink(w util.BufWriter, _ []byte, node ast.Node, enterin n := node.(*ast.Link) if entering { - r.Write(w, '[') + r.write(w, '[') } else { - r.Write(w, "](") - r.Write(w, n.Destination) + r.write(w, "](") + r.write(w, n.Destination) if n.Title != nil { - r.Write(w, " \"") - r.Write(w, n.Title) - r.Write(w, "\"") + r.write(w, " \"") + r.write(w, n.Title) + r.write(w, "\"") } - r.Write(w, ')') + r.write(w, ')') } return ast.WalkContinue, nil @@ -94,7 +120,7 @@ func (r *Renderer) renderRawHTML(w util.BufWriter, source []byte, node ast.Node, return ast.WalkSkipChildren, nil } - r.Write(w, "") + r.write(w, "") return ast.WalkSkipChildren, nil } @@ -107,7 +133,7 @@ func (r *Renderer) renderString(w util.BufWriter, _ []byte, node ast.Node, enter n := node.(*ast.String) // TODO: understand if there is any risk associated with this. - r.Write(w, html.UnescapeString(string(n.Value))) + r.write(w, html.UnescapeString(string(n.Value))) return ast.WalkContinue, nil } @@ -121,11 +147,11 @@ func (r *Renderer) renderText(w util.BufWriter, source []byte, node ast.Node, en segment := n.Segment value := segment.Value(source) - r.Write(w, value) + r.write(w, value) if n.HardLineBreak() { - r.Write(w, "\n\n") + r.write(w, "\n\n") } else if n.SoftLineBreak() { - r.Write(w, '\n') + r.write(w, '\n') } return ast.WalkContinue, nil diff --git a/tools/transformer/goldmark/renderer/markdown/markdown.go b/tools/transformer/goldmark/renderer/markdown/markdown.go index 70e08ed..2832dda 100644 --- a/tools/transformer/goldmark/renderer/markdown/markdown.go +++ b/tools/transformer/goldmark/renderer/markdown/markdown.go @@ -1,6 +1,4 @@ // Package renderer implements Goldmark renderer that outputs Markdown. -// This package borrows code from https://github.com/yuin/goldmark/blob/v1.7.2/renderer/html/html.go -// All borrowed code is in goldmark.go. package markdown import ( @@ -27,8 +25,36 @@ func isNewline(writee any) bool { } } +// RegisterFuncs implements NodeRenderer.RegisterFuncs . +func (r *Renderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) { + // blocks + + reg.Register(ast.KindDocument, r.renderDocument) + reg.Register(ast.KindHeading, r.renderHeading) + reg.Register(ast.KindBlockquote, r.renderBlockquote) + reg.Register(ast.KindCodeBlock, r.renderCodeBlock) + reg.Register(ast.KindFencedCodeBlock, r.renderFencedCodeBlock) + reg.Register(ast.KindHTMLBlock, r.renderHTMLBlock) + reg.Register(ast.KindList, r.renderList) + reg.Register(ast.KindListItem, r.renderListItem) + reg.Register(ast.KindParagraph, r.renderParagraph) + reg.Register(ast.KindTextBlock, r.renderTextBlock) + reg.Register(ast.KindThematicBreak, r.renderThematicBreak) + + // inlines + + reg.Register(ast.KindAutoLink, r.renderAutoLink) + reg.Register(ast.KindCodeSpan, r.renderCodeSpan) + reg.Register(ast.KindEmphasis, r.renderEmphasis) + reg.Register(ast.KindImage, r.renderImage) + reg.Register(ast.KindLink, r.renderLink) + reg.Register(ast.KindRawHTML, r.renderRawHTML) + reg.Register(ast.KindText, r.renderText) + reg.Register(ast.KindString, r.renderString) +} + // TODO: replace with implementation of renderer.Writer interface. -func (r *Renderer) Write(w util.BufWriter, writee any) { +func (r *Renderer) write(w util.BufWriter, writee any) { // fmt.Printf("Writing %q with indent %d, previously wrote %q\n", writee, r.indent, r.lastWrittenByte) if r.lastWrittenByte == '\n' && !isNewline(writee) { @@ -63,7 +89,7 @@ func (r *Renderer) Write(w util.BufWriter, writee any) { func (r *Renderer) writeLines(w util.BufWriter, source []byte, n ast.Node) { for i := 0; i < n.Lines().Len(); i++ { line := n.Lines().At(i) - r.Write(w, line.Value(source)) + r.write(w, line.Value(source)) } } diff --git a/tools/transformer/transform.go b/tools/transformer/transform.go index 19c05fc..067d261 100644 --- a/tools/transformer/transform.go +++ b/tools/transformer/transform.go @@ -121,6 +121,32 @@ func (t *ActionTransformer) Transform(node *ast.Document, reader text.Reader, _ } } +type AdmonitionTransformer struct{} + +// Transform implements the parser.ASTTransformer interface and replaces all admonition shortcodes with blockquotes. +func (t *AdmonitionTransformer) Transform(node *ast.Document, reader text.Reader, _ parser.Context) { + source := reader.Source() + + err := ast.Walk(node, func(node ast.Node, entering bool) (ast.WalkStatus, error) { + if !entering { + return ast.WalkContinue, nil + } + + if paragraph, ok := node.(*ast.Paragraph); ok { + raw := strings.TrimSpace(rawText(paragraph, source)) + + if strings.HasPrefix(raw, "{{<") && strings.HasSuffix(raw, ">}}") && strings.Contains(raw, "admonition") { + panic("TODO: implement") + } + } + + return ast.WalkContinue, nil + }) + if err != nil { + fmt.Fprintf(os.Stderr, "Error transforming AST: %v\n", err) + } +} + type FigureTransformer struct{} // Transform implements the parser.ASTTransformer interface and replaces all figure shortcodes with image elements. diff --git a/tools/transformer/transform_test.go b/tools/transformer/transform_test.go index a47ac9a..68b93b4 100644 --- a/tools/transformer/transform_test.go +++ b/tools/transformer/transform_test.go @@ -70,6 +70,63 @@ func TestActionTransformer_Transform(t *testing.T) { }) } +func TestAdmonitionTransformer_Transform(t *testing.T) { + t.Parallel() + + t.Run("with {{< syntax in a single paragraph", func(t *testing.T) { + t.Parallel() + + b := &bytes.Buffer{} + w := bufio.NewWriter(b) + md := goldmark.NewMarkdown() + md.Parser().AddOptions(parser.WithASTTransformers(util.Prioritized(&AdmonitionTransformer{}, 0))) + md.SetRenderer(renderer.NewRenderer(renderer.WithNodeRenderers(util.Prioritized(markdown.NewRenderer(), 1000)))) + + src := []byte(`{{< admonition type="note" >}} +This is a note. +{{< /admonition >}} +`) + + root := md.Parser().Parse(text.NewReader(src)) + require.NoError(t, md.Renderer().Render(w, src, root)) + + w.Flush() + + want := `> **Note:** +> This is a note. +` + + assert.Equal(t, want, b.String()) + }) + t.Run("with {{< syntax over multiple paragraphs", func(t *testing.T) { + t.Parallel() + + b := &bytes.Buffer{} + w := bufio.NewWriter(b) + md := goldmark.NewMarkdown() + md.Parser().AddOptions(parser.WithASTTransformers(util.Prioritized(&AdmonitionTransformer{}, 0))) + md.SetRenderer(renderer.NewRenderer(renderer.WithNodeRenderers(util.Prioritized(markdown.NewRenderer(), 1000)))) + + src := []byte(`{{< admonition type="note" >}} + +This is a note. + +{{< /admonition >}} +`) + + root := md.Parser().Parse(text.NewReader(src)) + require.NoError(t, md.Renderer().Render(w, src, root)) + + w.Flush() + + want := `> **Note:** +> This is a note. +` + + assert.Equal(t, want, b.String()) + }) +} + func TestFigureTransformer_Transform(t *testing.T) { t.Parallel()