diff --git a/extras/_test/insert.txt b/extras/_test/insert.txt new file mode 100644 index 0000000..c3d50ef --- /dev/null +++ b/extras/_test/insert.txt @@ -0,0 +1,57 @@ +1 +//- - - - - - - - -// +++Hi++ Hello, world! +//- - - - - - - - -// +

Hi Hello, world!

+//= = = = = = = = = = = = = = = = = = = = = = = =// + +2 +//- - - - - - - - -// +This ++has a + +new paragraph++. +//- - - - - - - - -// +

This ++has a

+

new paragraph++.

+//= = = = = = = = = = = = = = = = = = = = = = = =// + +3 +//- - - - - - - - -// +x ++++foo++ bar++ +//- - - - - - - - -// +

x foo bar

+//= = = = = = = = = = = = = = = = = = = = = = = =// + +4 +//- - - - - - - - -// +x ++foo ++bar++++ +//- - - - - - - - -// +

x foo bar

+//= = = = = = = = = = = = = = = = = = = = = = = =// + +5 +//- - - - - - - - -// +x ++++foo++++ +//- - - - - - - - -// +

x foo

+//= = = = = = = = = = = = = = = = = = = = = = = =// + +6 +//- - - - - - - - -// +**++test**++ + +++**test++** +//- - - - - - - - -// +

++test++

+

**test**

+//= = = = = = = = = = = = = = = = = = = = = = = =// + +7 +//- - - - - - - - -// +[++link]()++ + +++[link++]() +//- - - - - - - - -// +

++link++

+

++link++

+//= = = = = = = = = = = = = = = = = = = = = = = =// diff --git a/extras/_test/mark.txt b/extras/_test/mark.txt new file mode 100644 index 0000000..a3261ad --- /dev/null +++ b/extras/_test/mark.txt @@ -0,0 +1,57 @@ +1 +//- - - - - - - - -// +==Hello==, world! +//- - - - - - - - -// +

Hello, world!

+//= = = = = = = = = = = = = = = = = = = = = = = =// + +2 +//- - - - - - - - -// +This mark ==has a + +new paragraph==. +//- - - - - - - - -// +

This mark ==has a

+

new paragraph==.

+//= = = = = = = = = = = = = = = = = = = = = = = =// + +3 +//- - - - - - - - -// +x ====foo== bar== +//- - - - - - - - -// +

x foo bar

+//= = = = = = = = = = = = = = = = = = = = = = = =// + +4 +//- - - - - - - - -// +x ==foo ==bar==== +//- - - - - - - - -// +

x foo bar

+//= = = = = = = = = = = = = = = = = = = = = = = =// + +5 +//- - - - - - - - -// +x ====foo==== +//- - - - - - - - -// +

x foo

+//= = = = = = = = = = = = = = = = = = = = = = = =// + +6 +//- - - - - - - - -// +**==test**== + +==**test==** +//- - - - - - - - -// +

==test==

+

**test**

+//= = = = = = = = = = = = = = = = = = = = = = = =// + +7 +//- - - - - - - - -// +[==link]()== + +==[link==]() +//- - - - - - - - -// +

==link==

+

==link==

+//= = = = = = = = = = = = = = = = = = = = = = = =// diff --git a/extras/_test/subscript.txt b/extras/_test/subscript.txt new file mode 100644 index 0000000..59f3ebb --- /dev/null +++ b/extras/_test/subscript.txt @@ -0,0 +1,62 @@ +1: Surrounded by tildes +//- - - - - - - - -// +~foo~ +//- - - - - - - - -// +

foo

+//= = = = = = = = = = = = = = = = = = = = = = = =// + +2: Formula with tildes in the middle +//- - - - - - - - -// +H~2~O +//- - - - - - - - -// +

H2O

+//= = = = = = = = = = = = = = = = = = = = = = = =// + +3: Indices +//- - - - - - - - -// +x~i~ + x~j~ +//- - - - - - - - -// +

xi + xj

+//= = = = = = = = = = = = = = = = = = = = = = = =// + +4: Escaped tilde +//- - - - - - - - -// +~foo\~ +//- - - - - - - - -// +

~foo~

+//= = = = = = = = = = = = = = = = = = = = = = = =// + +5: Non-breaking space entity +//- - - - - - - - -// +~foo bar~ +//- - - - - - - - -// +

foo bar

+//= = = = = = = = = = = = = = = = = = = = = = = =// + +6: Non-breaking space UTF-8 +//- - - - - - - - -// +~foo bar~ +//- - - - - - - - -// +

foo bar

+//= = = = = = = = = = = = = = = = = = = = = = = =// + +7: Mixed subscript and strikethrough +//- - - - - - - - -// +~~x~foobar~~~ +//- - - - - - - - -// +

xfoobar

+//= = = = = = = = = = = = = = = = = = = = = = = =// + +8: Tildes in the middle and text before +//- - - - - - - - -// +text H~2~O +//- - - - - - - - -// +

text H2O

+//= = = = = = = = = = = = = = = = = = = = = = = =// + +9: Tildes in the middle and text after +//- - - - - - - - -// +H~2~O text +//- - - - - - - - -// +

H2O text

+//= = = = = = = = = = = = = = = = = = = = = = = =// diff --git a/extras/_test/superscript.txt b/extras/_test/superscript.txt new file mode 100644 index 0000000..7bf06bf --- /dev/null +++ b/extras/_test/superscript.txt @@ -0,0 +1,69 @@ +1: Surrounded by cares +//- - - - - - - - -// +^foo^ +//- - - - - - - - -// +

foo

+//= = = = = = = = = = = = = = = = = = = = = = = =// + +2: Ordinal indicator +//- - - - - - - - -// +2^nd^ +//- - - - - - - - -// +

2nd

+//= = = = = = = = = = = = = = = = = = = = = = = =// + +3: Powers +//- - - - - - - - -// +x^2^ + x^5^ +//- - - - - - - - -// +

x2 + x5

+//= = = = = = = = = = = = = = = = = = = = = = = =// + +4: Escaped care +//- - - - - - - - -// +^foo\^ +//- - - - - - - - -// +

^foo^

+//= = = = = = = = = = = = = = = = = = = = = = = =// + +5: : Cares surround text with a non-breaking space entity +//- - - - - - - - -// +^foo bar^ +//- - - - - - - - -// +

foo bar

+//= = = = = = = = = = = = = = = = = = = = = = = =// + +6: Cares surround text Surround with a non-breaking space (UTF-8) +//- - - - - - - - -// +^foo bar^ +//- - - - - - - - -// +

foo bar

+//= = = = = = = = = = = = = = = = = = = = = = = =// + +7: Cares in the middle and text before +//- - - - - - - - -// +text C^foo^C +//- - - - - - - - -// +

text CfooC

+//= = = = = = = = = = = = = = = = = = = = = = = =// + +8: Cares in the middle and text after +//- - - - - - - - -// +C^foo^C text +//- - - - - - - - -// +

CfooC text

+//= = = = = = = = = = = = = = = = = = = = = = = =// + +9: Wrong markers in LaTeX style notation should be left untouched +//- - - - - - - - -// +x^2 + x^3 +//- - - - - - - - -// +

x^2 + x^3

+//= = = = = = = = = = = = = = = = = = = = = = = =// + +10: Footnote markers should be left untouched +//- - - - - - - - -// +text[^1] text[^2] +//- - - - - - - - -// +

text[^1] text[^2]

+//= = = = = = = = = = = = = = = = = = = = = = = =// diff --git a/extras/ast/inline.go b/extras/ast/inline.go new file mode 100644 index 0000000..512105b --- /dev/null +++ b/extras/ast/inline.go @@ -0,0 +1,106 @@ +package ast + +import ( + gast "github.com/yuin/goldmark/ast" +) + +type InlineTagType int + +const ( + Superscript InlineTagType = iota + 1 + Subscript + Insert + Mark +) + +type InlineTag struct { + TagType InlineTagType + Char byte + Number int + Html string + WhitespaceAllowed bool + ParsePriority int + RenderPriority int +} + +var SuperscriptTag = InlineTag{ + TagType: Superscript, + Char: '^', + Number: 1, + Html: "sup", + WhitespaceAllowed: false, + ParsePriority: 600, + RenderPriority: 600, +} + +var SubscriptTag = InlineTag{ + TagType: Subscript, + Char: '~', + Number: 1, + Html: "sub", + WhitespaceAllowed: false, + ParsePriority: 602, + RenderPriority: 602, +} + +var InsertTag = InlineTag{ + TagType: Insert, + Char: '+', + Number: 2, + Html: "ins", + WhitespaceAllowed: true, + ParsePriority: 501, + RenderPriority: 501, +} + +var MarkTag = InlineTag{ + TagType: Mark, + Char: '=', + Number: 2, + Html: "mark", + WhitespaceAllowed: true, + ParsePriority: 550, + RenderPriority: 550, +} + +type InlineTagNode struct { + gast.BaseInline + + InlineTag +} + +func NewInlineTag(tag InlineTag) *InlineTagNode { + return &InlineTagNode{ + BaseInline: gast.BaseInline{}, + + InlineTag: tag, + } +} + +var KindSuperscript = gast.NewNodeKind("Superscript") +var KindSubscript = gast.NewNodeKind("Subscript") +var KindInsert = gast.NewNodeKind("Insert") +var KindMark = gast.NewNodeKind("Mark") + +func NewInlineTagNodeKind(tag InlineTagType) gast.NodeKind { + var kind gast.NodeKind + switch tag { + case Superscript: + kind = KindSuperscript + case Subscript: + kind = KindSubscript + case Insert: + kind = KindInsert + case Mark: + kind = KindMark + } + return kind +} + +func (n *InlineTagNode) Kind() gast.NodeKind { + return NewInlineTagNodeKind(n.TagType) +} + +func (n *InlineTagNode) Dump(source []byte, level int) { + gast.DumpHelper(n, source, level, nil, nil) +} diff --git a/extras/go.mod b/extras/go.mod new file mode 100644 index 0000000..75e4ded --- /dev/null +++ b/extras/go.mod @@ -0,0 +1,5 @@ +module github.com/gohugoio/hugo-goldmark-extensions/extras + +go 1.20 + +require github.com/yuin/goldmark v1.7.1 diff --git a/extras/go.sum b/extras/go.sum new file mode 100644 index 0000000..931841e --- /dev/null +++ b/extras/go.sum @@ -0,0 +1,4 @@ +github.com/yuin/goldmark v1.7.0 h1:EfOIvIMZIzHdB/R/zVrikYLPPwJlfMcNczJFMs1m6sA= +github.com/yuin/goldmark v1.7.0/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E= +github.com/yuin/goldmark v1.7.1 h1:3bajkSilaCbjdKVsKdZjZCLBNPL9pYzrCakKaf4U49U= +github.com/yuin/goldmark v1.7.1/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E= diff --git a/extras/inline.go b/extras/inline.go new file mode 100644 index 0000000..87c71f6 --- /dev/null +++ b/extras/inline.go @@ -0,0 +1,159 @@ +package extras + +import ( + "github.com/gohugoio/hugo-goldmark-extensions/extras/ast" + "github.com/yuin/goldmark" + gast "github.com/yuin/goldmark/ast" + "github.com/yuin/goldmark/parser" + "github.com/yuin/goldmark/renderer" + "github.com/yuin/goldmark/renderer/html" + "github.com/yuin/goldmark/text" + "github.com/yuin/goldmark/util" +) + +type inlineTagDelimiterProcessor struct { + ast.InlineTag +} + +func newInlineTagDelimiterProcessor(tag ast.InlineTag) parser.DelimiterProcessor { + return &inlineTagDelimiterProcessor{tag} +} + +func (p *inlineTagDelimiterProcessor) IsDelimiter(b byte) bool { + return b == p.Char +} + +func (p *inlineTagDelimiterProcessor) CanOpenCloser(opener, closer *parser.Delimiter) bool { + return opener.Char == closer.Char +} + +func (p *inlineTagDelimiterProcessor) OnMatch(_ int) gast.Node { + return ast.NewInlineTag(p.InlineTag) +} + +type inlineTagParser struct { + ast.InlineTag +} + +func newInlineTagParser(tag ast.InlineTag) parser.InlineParser { + return &inlineTagParser{InlineTag: tag} +} + +// Trigger implements parser.InlineParser. +func (s *inlineTagParser) Trigger() []byte { + return []byte{s.Char} +} + +// Parse implements the parser.InlineParser for all types of InlineTags. +func (s *inlineTagParser) Parse(_ gast.Node, block text.Reader, pc parser.Context) gast.Node { + before := block.PrecendingCharacter() + line, segment := block.PeekLine() + node := parser.ScanDelimiter(line, before, s.Number, newInlineTagDelimiterProcessor(s.InlineTag)) + if node == nil { + return nil + } + if !s.WhitespaceAllowed && node.CanOpen && hasSpace(line) { + if !(node.CanClose && pc.LastDelimiter() != nil && pc.LastDelimiter().Char == node.Char) { + return nil + } + } + node.Segment = segment.WithStop(segment.Start + node.OriginalLength) + block.Advance(node.OriginalLength) + pc.PushDelimiter(node) + return node +} + +// Check if there is an ordinary white space in the line before the next marker +func hasSpace(line []byte) bool { + marker := line[0] + for i := 1; i < len(line); i++ { + c := line[i] + if c == marker { + break + } + if util.IsSpace(c) { + return true + } + } + return false +} + +type inlineTagHTMLRenderer struct { + htmlTag string + tagType ast.InlineTagType + html.Config +} + +// newInlineTagHTMLRenderer returns a new NodeRenderer that renders InlineTagNode nodes to HTML. +func newInlineTagHTMLRenderer(tag ast.InlineTag, opts ...html.Option) renderer.NodeRenderer { + r := &inlineTagHTMLRenderer{ + htmlTag: tag.Html, + tagType: tag.TagType, + Config: html.NewConfig(), + } + for _, opt := range opts { + opt.SetHTMLOption(&r.Config) + } + return r +} + +// RegisterFuncs registers rendering functions to the given NodeRendererFuncRegisterer. +func (r *inlineTagHTMLRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) { + reg.Register(ast.NewInlineTagNodeKind(r.tagType), r.renderInlineTag) +} + +// inlineTagAttributeFilter is a global filter for attributes. +var inlineTagAttributeFilter = html.GlobalAttributeFilter + +// renderInlineTag renders an inline tag. +func (r *inlineTagHTMLRenderer) renderInlineTag( + w util.BufWriter, _ []byte, n gast.Node, entering bool) (gast.WalkStatus, error) { + if entering { + _ = w.WriteByte('<') + _, _ = w.WriteString(r.htmlTag) + if n.Attributes() != nil { + html.RenderAttributes(w, n, inlineTagAttributeFilter) + } + } else { + _, _ = w.WriteString("') + return gast.WalkContinue, nil +} + +// inlineTag is an extension that adds inline tags to the Markdown parser and renderer. +type inlineTag struct { + ast.InlineTag +} + +// InlineTagConfig is a configuration struct for the ExtraInlineTag extension. +type Config struct { + ast.InlineTagType +} + +func New(config Config) goldmark.Extender { + var extension inlineTag + + switch config.InlineTagType { + case ast.Superscript: + extension = inlineTag{ast.SuperscriptTag} + case ast.Subscript: + extension = inlineTag{ast.SubscriptTag} + case ast.Insert: + extension = inlineTag{ast.InsertTag} + case ast.Mark: + extension = inlineTag{ast.MarkTag} + } + return &extension +} + +// Extend adds inline tags to the Markdown parser and renderer. +func (tag *inlineTag) Extend(md goldmark.Markdown) { + md.Parser().AddOptions(parser.WithInlineParsers( + util.Prioritized(newInlineTagParser(tag.InlineTag), tag.ParsePriority), + )) + md.Renderer().AddOptions(renderer.WithNodeRenderers( + util.Prioritized(newInlineTagHTMLRenderer(tag.InlineTag), tag.RenderPriority), + )) +} diff --git a/extras/inline_test.go b/extras/inline_test.go new file mode 100644 index 0000000..69bba40 --- /dev/null +++ b/extras/inline_test.go @@ -0,0 +1,175 @@ +package extras + +import ( + "bytes" + xast "github.com/gohugoio/hugo-goldmark-extensions/extras/ast" + "github.com/yuin/goldmark/extension" + "github.com/yuin/goldmark/text" + "testing" + + "github.com/yuin/goldmark" + "github.com/yuin/goldmark/testutil" +) + +func buildGoldmarkWithInlineTag(tag xast.InlineTagType) goldmark.Markdown { + return goldmark.New(goldmark.WithExtensions(New(Config{InlineTagType: tag}))) +} + +var markdown = goldmark.New() +var markdownWithSuperscript = buildGoldmarkWithInlineTag(xast.Superscript) +var markdownWithSubscript = buildGoldmarkWithInlineTag(xast.Subscript) +var markdownWithInsert = buildGoldmarkWithInlineTag(xast.Insert) +var markdownWithMark = buildGoldmarkWithInlineTag(xast.Mark) + +func TestSuperscript(t *testing.T) { + testutil.DoTestCaseFile(markdownWithSuperscript, "_test/superscript.txt", t, testutil.ParseCliCaseArg()...) +} + +func TestSuperscriptDump(t *testing.T) { + input := "Parabola: f(x) = x^2^. Amazing" + root := markdownWithSuperscript.Parser().Parse(text.NewReader([]byte(input))) + root.Dump([]byte(input), 0) +} + +func BenchmarkWithAndWithoutOneSuperscript(b *testing.B) { + const input = ` +## Parabola + +This formula contains one superscript: f(x) = x^2^ .` + + b.Run("without superscript", func(b *testing.B) { + b.ResetTimer() + for i := 0; i < b.N; i++ { + var buf bytes.Buffer + if err := markdown.Convert([]byte(input), &buf); err != nil { + b.Fatal(err) + } + } + }) + + b.Run("with superscript", func(b *testing.B) { + b.ResetTimer() + for i := 0; i < b.N; i++ { + var buf bytes.Buffer + if err := markdownWithSuperscript.Convert([]byte(input), &buf); err != nil { + b.Fatal(err) + } + } + }) +} + +func TestSubscript(t *testing.T) { + var markdown = goldmark.New( + goldmark.WithExtensions(New(Config{ + InlineTagType: xast.Subscript}), extension.Strikethrough)) + testutil.DoTestCaseFile(markdown, "_test/subscript.txt", t, testutil.ParseCliCaseArg()...) +} + +func TestSubscriptDump(t *testing.T) { + input := "The H~2~O molecule" + root := markdownWithSubscript.Parser().Parse(text.NewReader([]byte(input))) + root.Dump([]byte(input), 0) +} + +func BenchmarkWithAndWithoutOneSubscript(b *testing.B) { + const input = ` +## Water formula + +The chemical formula for water H~2~O contains one subscript.` + + b.Run("without subscript", func(b *testing.B) { + b.ResetTimer() + for i := 0; i < b.N; i++ { + var buf bytes.Buffer + if err := markdown.Convert([]byte(input), &buf); err != nil { + b.Fatal(err) + } + } + }) + + b.Run("with subscript", func(b *testing.B) { + b.ResetTimer() + for i := 0; i < b.N; i++ { + var buf bytes.Buffer + if err := markdownWithSubscript.Convert([]byte(input), &buf); err != nil { + b.Fatal(err) + } + } + }) +} + +func TestInsert(t *testing.T) { + testutil.DoTestCaseFile(markdownWithInsert, "_test/insert.txt", t, testutil.ParseCliCaseArg()...) +} + +func TestInsertDump(t *testing.T) { + input := "Add some text: ++insertion++. Amazing." + root := markdownWithInsert.Parser().Parse(text.NewReader([]byte(input))) + root.Dump([]byte(input), 0) + // Prints to stdout, so just test that it doesn't crash +} + +func BenchmarkWithAndWithoutInsert(b *testing.B) { + const input = ` +## Insert text explicitly + +Add some text: ++insertion++. Amazing.` + + b.Run("without insert", func(b *testing.B) { + b.ResetTimer() + for i := 0; i < b.N; i++ { + var buf bytes.Buffer + if err := markdown.Convert([]byte(input), &buf); err != nil { + b.Fatal(err) + } + } + }) + + b.Run("with insert", func(b *testing.B) { + b.ResetTimer() + for i := 0; i < b.N; i++ { + var buf bytes.Buffer + if err := markdownWithInsert.Convert([]byte(input), &buf); err != nil { + b.Fatal(err) + } + } + }) +} + +func TestMark(t *testing.T) { + testutil.DoTestCaseFile(markdownWithMark, "_test/mark.txt", t, testutil.ParseCliCaseArg()...) +} + +func TestMarkDump(t *testing.T) { + input := "Add some marked text: ==marked==. Amazing." + root := markdownWithMark.Parser().Parse(text.NewReader([]byte(input))) + root.Dump([]byte(input), 0) + // Prints to stdout, so just test that it doesn't crash +} + +func BenchmarkWithAndWithoutMark(b *testing.B) { + const input = ` +## Mark text + +Add some marked text: ==marked==. Amazing.` + + b.Run("without mark extension", func(b *testing.B) { + b.ResetTimer() + for i := 0; i < b.N; i++ { + var buf bytes.Buffer + if err := markdown.Convert([]byte(input), &buf); err != nil { + b.Fatal(err) + } + } + }) + + b.Run("with mark extension", func(b *testing.B) { + b.ResetTimer() + for i := 0; i < b.N; i++ { + var buf bytes.Buffer + if err := markdownWithMark.Convert([]byte(input), &buf); err != nil { + b.Fatal(err) + } + } + }) +} diff --git a/passthrough/go.mod b/passthrough/go.mod index b902ba1..89d7f71 100644 --- a/passthrough/go.mod +++ b/passthrough/go.mod @@ -5,9 +5,9 @@ go 1.20 require github.com/frankban/quicktest v1.14.6 require ( - github.com/yuin/goldmark v1.6.0 - github.com/google/go-cmp v0.5.9 // indirect + github.com/google/go-cmp v0.6.0 // indirect github.com/kr/pretty v0.3.1 // indirect github.com/kr/text v0.2.0 // indirect - github.com/rogpeppe/go-internal v1.9.0 // indirect + github.com/rogpeppe/go-internal v1.12.0 // indirect + github.com/yuin/goldmark v1.7.1 ) diff --git a/passthrough/go.sum b/passthrough/go.sum index dc839b8..1cc8e1a 100644 --- a/passthrough/go.sum +++ b/passthrough/go.sum @@ -3,6 +3,8 @@ github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHk github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= @@ -10,5 +12,9 @@ github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= +github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/yuin/goldmark v1.6.0 h1:boZcn2GTjpsynOsC0iJHnBWa4Bi0qzfJjthwauItG68= github.com/yuin/goldmark v1.6.0/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/yuin/goldmark v1.7.1 h1:3bajkSilaCbjdKVsKdZjZCLBNPL9pYzrCakKaf4U49U= +github.com/yuin/goldmark v1.7.1/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=