summaryrefslogtreecommitdiffstats
path: root/markup
diff options
context:
space:
mode:
authorBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>2022-02-17 13:04:00 +0100
committerBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>2022-02-24 18:59:50 +0100
commit08fdca9d9365eaf1e496a12e2af5e18617bd0e66 (patch)
tree6c6942d1b74a4160d93a997860bafd52b92025f5 /markup
parent2c20f5bc00b604e72b3b7e401fbdbf9447fe3470 (diff)
Add Markdown diagrams and render hooks for code blocks
You can now create custom hook templates for code blocks, either one for all (`render-codeblock.html`) or for a given code language (e.g. `render-codeblock-go.html`). We also used this new hook to add support for diagrams in Hugo: * Goat (Go ASCII Tool) is built-in and enabled by default; just create a fenced code block with the language `goat` and start draw your Ascii diagrams. * Another popular alternative for diagrams in Markdown, Mermaid (supported by GitHub), can also be implemented with a simple template. See the Hugo documentation for more information. Updates #7765 Closes #9538 Fixes #9553 Fixes #8520 Fixes #6702 Fixes #9558
Diffstat (limited to 'markup')
-rw-r--r--markup/converter/converter.go10
-rw-r--r--markup/converter/hooks/hooks.go100
-rw-r--r--markup/goldmark/codeblocks/integration_test.go115
-rw-r--r--markup/goldmark/codeblocks/render.go159
-rw-r--r--markup/goldmark/codeblocks/transform.go53
-rw-r--r--markup/goldmark/convert.go146
-rw-r--r--markup/goldmark/convert_test.go25
-rw-r--r--markup/goldmark/integration_test.go141
-rw-r--r--markup/goldmark/internal/render/context.go81
-rw-r--r--markup/goldmark/render_hooks.go143
-rw-r--r--markup/goldmark/toc_test.go9
-rw-r--r--markup/highlight/config.go99
-rw-r--r--markup/highlight/highlight.go178
-rw-r--r--markup/internal/attributes/attributes.go219
-rw-r--r--markup/markup.go13
-rw-r--r--markup/org/convert.go3
16 files changed, 1130 insertions, 364 deletions
diff --git a/markup/converter/converter.go b/markup/converter/converter.go
index 180208a7b..30addfec6 100644
--- a/markup/converter/converter.go
+++ b/markup/converter/converter.go
@@ -21,6 +21,7 @@ import (
"github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/identity"
"github.com/gohugoio/hugo/markup/converter/hooks"
+ "github.com/gohugoio/hugo/markup/highlight"
"github.com/gohugoio/hugo/markup/markup_config"
"github.com/gohugoio/hugo/markup/tableofcontents"
"github.com/spf13/afero"
@@ -34,7 +35,7 @@ type ProviderConfig struct {
ContentFs afero.Fs
Logger loggers.Logger
Exec *hexec.Exec
- Highlight func(code, lang, optsStr string) (string, error)
+ highlight.Highlighter
}
// ProviderProvider creates converter providers.
@@ -127,9 +128,10 @@ type DocumentContext struct {
// RenderContext holds contextual information about the content to render.
type RenderContext struct {
- Src []byte
- RenderTOC bool
- RenderHooks hooks.Renderers
+ Src []byte
+ RenderTOC bool
+
+ GetRenderer hooks.GetRendererFunc
}
var FeatureRenderHooks = identity.NewPathIdentity("markup", "renderingHooks")
diff --git a/markup/converter/hooks/hooks.go b/markup/converter/hooks/hooks.go
index d36dad288..987cb1dc3 100644
--- a/markup/converter/hooks/hooks.go
+++ b/markup/converter/hooks/hooks.go
@@ -14,15 +14,17 @@
package hooks
import (
- "fmt"
"io"
- "strings"
+ "github.com/gohugoio/hugo/common/hugio"
"github.com/gohugoio/hugo/identity"
+ "github.com/gohugoio/hugo/markup/internal/attributes"
)
+var _ AttributesOptionsSliceProvider = (*attributes.AttributesHolder)(nil)
+
type AttributesProvider interface {
- Attributes() map[string]string
+ Attributes() map[string]interface{}
}
type LinkContext interface {
@@ -33,11 +35,30 @@ type LinkContext interface {
PlainText() string
}
+type CodeblockContext interface {
+ AttributesProvider
+ Options() map[string]interface{}
+ Lang() string
+ Code() string
+ Ordinal() int
+ Page() interface{}
+}
+
+type AttributesOptionsSliceProvider interface {
+ AttributesSlice() []attributes.Attribute
+ OptionsSlice() []attributes.Attribute
+}
+
type LinkRenderer interface {
RenderLink(w io.Writer, ctx LinkContext) error
identity.Provider
}
+type CodeBlockRenderer interface {
+ RenderCodeblock(w hugio.FlexiWriter, ctx CodeblockContext) error
+ identity.Provider
+}
+
// HeadingContext contains accessors to all attributes that a HeadingRenderer
// can use to render a heading.
type HeadingContext interface {
@@ -63,70 +84,13 @@ type HeadingRenderer interface {
identity.Provider
}
-type Renderers struct {
- LinkRenderer LinkRenderer
- ImageRenderer LinkRenderer
- HeadingRenderer HeadingRenderer
-}
-
-func (r Renderers) Eq(other interface{}) bool {
- ro, ok := other.(Renderers)
- if !ok {
- return false
- }
-
- if r.IsZero() || ro.IsZero() {
- return r.IsZero() && ro.IsZero()
- }
-
- var b1, b2 bool
- b1, b2 = r.ImageRenderer == nil, ro.ImageRenderer == nil
- if (b1 || b2) && (b1 != b2) {
- return false
- }
- if !b1 && r.ImageRenderer.GetIdentity() != ro.ImageRenderer.GetIdentity() {
- return false
- }
-
- b1, b2 = r.LinkRenderer == nil, ro.LinkRenderer == nil
- if (b1 || b2) && (b1 != b2) {
- return false
- }
- if !b1 && r.LinkRenderer.GetIdentity() != ro.LinkRenderer.GetIdentity() {
- return false
- }
-
- b1, b2 = r.HeadingRenderer == nil, ro.HeadingRenderer == nil
- if (b1 || b2) && (b1 != b2) {
- return false
- }
- if !b1 && r.HeadingRenderer.GetIdentity() != ro.HeadingRenderer.GetIdentity() {
- return false
- }
-
- return true
-}
-
-func (r Renderers) IsZero() bool {
- return r.HeadingRenderer == nil && r.LinkRenderer == nil && r.ImageRenderer == nil
-}
+type RendererType int
-func (r Renderers) String() string {
- if r.IsZero() {
- return "<zero>"
- }
-
- var sb strings.Builder
-
- if r.LinkRenderer != nil {
- sb.WriteString(fmt.Sprintf("LinkRenderer<%s>|", r.LinkRenderer.GetIdentity()))
- }
- if r.HeadingRenderer != nil {
- sb.WriteString(fmt.Sprintf("HeadingRenderer<%s>|", r.HeadingRenderer.GetIdentity()))
- }
- if r.ImageRenderer != nil {
- sb.WriteString(fmt.Sprintf("ImageRenderer<%s>|", r.ImageRenderer.GetIdentity()))
- }
+const (
+ LinkRendererType RendererType = iota + 1
+ ImageRendererType
+ HeadingRendererType
+ CodeBlockRendererType
+)
- return sb.String()
-}
+type GetRendererFunc func(t RendererType, id interface{}) interface{}
diff --git a/markup/goldmark/codeblocks/integration_test.go b/markup/goldmark/codeblocks/integration_test.go
new file mode 100644
index 000000000..d662b3911
--- /dev/null
+++ b/markup/goldmark/codeblocks/integration_test.go
@@ -0,0 +1,115 @@
+// Copyright 2022 The Hugo Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package codeblocks_test
+
+import (
+ "strings"
+ "testing"
+
+ "github.com/gohugoio/hugo/hugolib"
+)
+
+func TestCodeblocks(t *testing.T) {
+ t.Parallel()
+
+ files := `
+-- config.toml --
+[markup]
+ [markup.highlight]
+ anchorLineNos = false
+ codeFences = true
+ guessSyntax = false
+ hl_Lines = ''
+ lineAnchors = ''
+ lineNoStart = 1
+ lineNos = false
+ lineNumbersInTable = true
+ noClasses = false
+ style = 'monokai'
+ tabWidth = 4
+-- layouts/_default/_markup/render-codeblock-goat.html --
+{{ $diagram := diagrams.Goat .Code }}
+Goat SVG:{{ substr $diagram.SVG 0 100 | safeHTML }} }}|
+Goat Attribute: {{ .Attributes.width}}|
+-- layouts/_default/_markup/render-codeblock-go.html --
+Go Code: {{ .Code | safeHTML }}|
+Go Language: {{ .Lang }}|
+-- layouts/_default/single.html --
+{{ .Content }}
+-- content/p1.md --
+---
+title: "p1"
+---
+
+## Ascii Diagram
+
+CODE_FENCEgoat { width="600" }
+--->
+CODE_FENCE
+
+## Go Code
+
+CODE_FENCEgo
+fmt.Println("Hello, World!");
+CODE_FENCE
+
+## Golang Code
+
+CODE_FENCEgolang
+fmt.Println("Hello, Golang!");
+CODE_FENCE
+
+## Bash Code
+
+CODE_FENCEbash { linenos=inline,hl_lines=[2,"5-6"],linenostart=32 class=blue }
+echo "l1";
+echo "l2";
+echo "l3";
+echo "l4";
+echo "l5";
+echo "l6";
+echo "l7";
+echo "l8";
+CODE_FENCE
+`
+
+ files = strings.ReplaceAll(files, "CODE_FENCE", "```")
+
+ b := hugolib.NewIntegrationTestBuilder(
+ hugolib.IntegrationTestConfig{
+ T: t,
+ TxtarString: files,
+ NeedsOsFS: false,
+ },
+ ).Build()
+
+ b.AssertFileContent("public/p1/index.html", `
+Goat SVG:<svg class='diagram'
+Goat Attribute: 600|
+
+Go Language: go|
+Go Code: fmt.Println("Hello, World!");
+
+Go Code: fmt.Println("Hello, Golang!");
+Go Language: golang|
+
+
+ `,
+ "Goat SVG:<svg class='diagram' xmlns='http://www.w3.org/2000/svg' version='1.1' height='25' width='40'",
+ "Goat Attribute: 600|",
+ "<h2 id=\"go-code\">Go Code</h2>\nGo Code: fmt.Println(\"Hello, World!\");\n|\nGo Language: go|",
+ "<h2 id=\"golang-code\">Golang Code</h2>\nGo Code: fmt.Println(\"Hello, Golang!\");\n|\nGo Language: golang|",
+ "<h2 id=\"bash-code\">Bash Code</h2>\n<div class=\"highlight blue\"><pre tabindex=\"0\" class=\"chroma\"><code class=\"language-bash\" data-lang=\"bash\"><span class=\"line\"><span class=\"ln\">32</span><span class=\"cl\"><span class=\"nb\">echo</span> <span class=\"s2\">&#34;l1&#34;</span><span class=\"p\">;</span>\n</span></span><span class=\"line hl\"><span class=\"ln\">33</span>",
+ )
+}
diff --git a/markup/goldmark/codeblocks/render.go b/markup/goldmark/codeblocks/render.go
new file mode 100644
index 000000000..59d142e23
--- /dev/null
+++ b/markup/goldmark/codeblocks/render.go
@@ -0,0 +1,159 @@
+// Copyright 2022 The Hugo Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package codeblocks
+
+import (
+ "bytes"
+ "fmt"
+
+ "github.com/gohugoio/hugo/markup/converter/hooks"
+ "github.com/gohugoio/hugo/markup/goldmark/internal/render"
+ "github.com/gohugoio/hugo/markup/internal/attributes"
+ "github.com/yuin/goldmark"
+ "github.com/yuin/goldmark/ast"
+ "github.com/yuin/goldmark/parser"
+ "github.com/yuin/goldmark/renderer"
+ "github.com/yuin/goldmark/text"
+ "github.com/yuin/goldmark/util"
+)
+
+type (
+ diagrams struct{}
+ htmlRenderer struct{}
+)
+
+func New() goldmark.Extender {
+ return &diagrams{}
+}
+
+func (e *diagrams) Extend(m goldmark.Markdown) {
+ m.Parser().AddOptions(
+ parser.WithASTTransformers(
+ util.Prioritized(&Transformer{}, 100),
+ ),
+ )
+ m.Renderer().AddOptions(renderer.WithNodeRenderers(
+ util.Prioritized(newHTMLRenderer(), 100),
+ ))
+}
+
+func newHTMLRenderer() renderer.NodeRenderer {
+ r := &htmlRenderer{}
+ return r
+}
+
+func (r *htmlRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) {
+ reg.Register(KindCodeBlock, r.renderCodeBlock)
+}
+
+func (r *htmlRenderer) renderCodeBlock(w util.BufWriter, src []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
+ ctx := w.(*render.Context)
+
+ if entering {
+ return ast.WalkContinue, nil
+ }
+
+ n := node.(*codeBlock)
+ lang := string(n.b.Language(src))
+ ordinal := n.ordinal
+
+ var buff bytes.Buffer
+
+ l := n.b.Lines().Len()
+ for i := 0; i < l; i++ {
+ line := n.b.Lines().At(i)
+ buff.Write(line.Value(src))
+ }
+ text := buff.String()
+
+ var info []byte
+ if n.b.Info != nil {
+ info = n.b.Info.Segment.Value(src)
+ }
+ attrs := getAttributes(n.b, info)
+
+ v := ctx.RenderContext().GetRenderer(hooks.CodeBlockRendererType, lang)
+ if v == nil {
+ return ast.WalkStop, fmt.Errorf("no code renderer found for %q", lang)
+ }
+
+ cr := v.(hooks.CodeBlockRenderer)
+
+ err := cr.RenderCodeblock(
+ w,
+ codeBlockContext{
+ page: ctx.DocumentContext().Document,
+ lang: lang,
+ code: text,
+ ordinal: ordinal,
+ AttributesHolder: attributes.New(attrs, attributes.AttributesOwnerCodeBlock),
+ },
+ )
+
+ ctx.AddIdentity(cr)
+
+ return ast.WalkContinue, err
+}
+
+type codeBlockContext struct {
+ page interface{}
+ lang string
+ code string
+ ordinal int
+ *attributes.AttributesHolder
+}
+
+func (c codeBlockContext) Page() interface{} {
+ return c.page
+}
+
+func (c codeBlockContext) Lang() string {
+ return c.lang
+}
+
+func (c codeBlockContext) Code() string {
+ return c.code
+}
+
+func (c codeBlockContext) Ordinal() int {
+ return c.ordinal
+}
+
+func getAttributes(node *ast.FencedCodeBlock, infostr []byte) []ast.Attribute {
+ if node.Attributes() != nil {
+ return node.Attributes()
+ }
+ if infostr != nil {
+ attrStartIdx := -1
+
+ for idx, char := range infostr {
+ if char == '{' {
+ attrStartIdx = idx
+ break
+ }
+ }
+
+ if attrStartIdx > 0 {
+ n := ast.NewTextBlock() // dummy node for storing attributes
+ attrStr := infostr[attrStartIdx:]
+ if attrs, hasAttr := parser.ParseAttributes(text.NewReader(attrStr)); hasAttr {
+ for _, attr := range attrs {
+ n.SetAttribute(attr.Name, attr.Value)
+ }
+ return n.Attributes()
+ }
+ }
+ }
+ return nil
+}
diff --git a/markup/goldmark/codeblocks/transform.go b/markup/goldmark/codeblocks/transform.go
new file mode 100644
index 000000000..791e99a5c
--- /dev/null
+++ b/markup/goldmark/codeblocks/transform.go
@@ -0,0 +1,53 @@
+package codeblocks
+
+import (
+ "github.com/yuin/goldmark/ast"
+ "github.com/yuin/goldmark/parser"
+ "github.com/yuin/goldmark/text"
+)
+
+// Kind is the kind of an Hugo code block.
+var KindCodeBlock = ast.NewNodeKind("HugoCodeBlock")
+
+// Its raw contents are the plain text of the code block.
+type codeBlock struct {
+ ast.BaseBlock
+ ordinal int
+ b *ast.FencedCodeBlock
+}
+
+func (*codeBlock) Kind() ast.NodeKind { return KindCodeBlock }
+
+func (*codeBlock) IsRaw() bool { return true }
+
+func (b *codeBlock) Dump(src []byte, level int) {
+}
+
+type Transformer struct{}
+
+// Transform transforms the provided Markdown AST.
+func (*Transformer) Transform(doc *ast.Document, reader text.Reader, pctx parser.Context) {
+ var codeBlocks []*ast.FencedCodeBlock
+
+ ast.Walk(doc, func(node ast.Node, enter bool) (ast.WalkStatus, error) {
+ if !enter {
+ return ast.WalkContinue, nil
+ }
+
+ cb, ok := node.(*ast.FencedCodeBlock)
+ if !ok {
+ return ast.WalkContinue, nil
+ }
+
+ codeBlocks = append(codeBlocks, cb)
+ return ast.WalkContinue, nil
+ })
+
+ for i, cb := range codeBlocks {
+ b := &codeBlock{b: cb, ordinal: i}
+ parent := cb.Parent()
+ if parent != nil {
+ parent.ReplaceChild(parent, cb, b)
+ }
+ }
+}
diff --git a/markup/goldmark/convert.go b/markup/goldmark/convert.go
index c547fe1e0..4c1641a0b 100644
--- a/markup/goldmark/convert.go
+++ b/markup/goldmark/convert.go
@@ -17,12 +17,12 @@ package goldmark
import (
"bytes"
"fmt"
- "math/bits"
"path/filepath"
"runtime/debug"
+ "github.com/gohugoio/hugo/markup/goldmark/codeblocks"
"github.com/gohugoio/hugo/markup/goldmark/internal/extensions/attributes"
- "github.com/yuin/goldmark/ast"
+ "github.com/gohugoio/hugo/markup/goldmark/internal/render"
"github.com/gohugoio/hugo/identity"
@@ -32,16 +32,13 @@ import (
"github.com/gohugoio/hugo/hugofs"
"github.com/gohugoio/hugo/markup/converter"
- "github.com/gohugoio/hugo/markup/highlight"
"github.com/gohugoio/hugo/markup/tableofcontents"
"github.com/yuin/goldmark"
- hl "github.com/yuin/goldmark-highlighting"
"github.com/yuin/goldmark/extension"
"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"
)
// Provider is the package entry point.
@@ -104,7 +101,7 @@ func newMarkdown(pcfg converter.ProviderConfig) goldmark.Markdown {
)
if mcfg.Highlight.CodeFences {
- extensions = append(extensions, newHighlighting(mcfg.Highlight))
+ extensions = append(extensions, codeblocks.New())
}
if cfg.Extensions.Table {
@@ -178,65 +175,6 @@ func (c converterResult) GetIdentities() identity.Identities {
return c.ids
}
-type bufWriter struct {
- *bytes.Buffer
-}
-
-const maxInt = 1<<(bits.UintSize-1) - 1
-
-func (b *bufWriter) Available() int {
- return maxInt
-}
-
-func (b *bufWriter) Buffered() int {
- return b.Len()
-}
-
-func (b *bufWriter) Flush() error {
- return nil
-}
-
-type renderContext struct {
- *bufWriter
- positions []int
- renderContextData
-}
-
-func (ctx *renderContext) pushPos(n int) {
- ctx.positions = append(ctx.positions, n)
-}
-
-func (ctx *renderContext) popPos() int {
- i := len(ctx.positions) - 1
- p := ctx.positions[i]
- ctx.positions = ctx.positions[:i]
- return p
-}
-
-type renderContextData interface {
- RenderContext() converter.RenderContext
- DocumentContext() converter.DocumentContext
- AddIdentity(id identity.Provider)
-}
-
-type renderContextDataHolder struct {
- rctx converter.RenderContext
- dctx converter.DocumentContext
- ids identity.Manager
-}
-
-func (ctx *renderContextDataHolder) RenderContext() converter.RenderContext {
- return ctx.rctx
-}
-
-func (ctx *renderContextDataHolder) DocumentContext() converter.DocumentContext {
- return ctx.dctx
-}
-
-func (ctx *renderContextDataHolder) AddIdentity(id identity.Provider) {
- ctx.ids.Add(id)
-}
-
var converterIdentity = identity.KeyValueIdentity{Key: "goldmark", Value: "converter"}
func (c *goldmarkConverter) Convert(ctx converter.RenderContext) (result converter.Result, err error) {
@@ -251,7 +189,7 @@ func (c *goldmarkConverter) Convert(ctx converter.RenderContext) (result convert
}
}()
- buf := &bufWriter{Buffer: &bytes.Buffer{}}
+ buf := &render.BufWriter{Buffer: &bytes.Buffer{}}
result = buf
pctx := c.newParserContext(ctx)
reader := text.NewReader(ctx.Src)
@@ -261,15 +199,15 @@ func (c *goldmarkConverter) Convert(ctx converter.RenderContext) (result convert
parser.WithContext(pctx),
)
- rcx := &renderContextDataHolder{
- rctx: ctx,
- dctx: c.ctx,
- ids: identity.NewManager(converterIdentity),
+ rcx := &render.RenderContextDataHolder{
+ Rctx: ctx,
+ Dctx: c.ctx,
+ IDs: identity.NewManager(converterIdentity),
}
- w := &renderContext{
- bufWriter: buf,
- renderContextData: rcx,
+ w := &render.Context{
+ BufWriter: buf,
+ ContextData: rcx,
}
if err := c.md.Renderer().Render(w, ctx.Src, doc); err != nil {
@@ -278,7 +216,7 @@ func (c *goldmarkConverter) Convert(ctx converter.RenderContext) (result convert
return converterResult{
Result: buf,
- ids: rcx.ids.GetIdentities(),
+ ids: rcx.IDs.GetIdentities(),
toc: pctx.TableOfContents(),
}, nil
}
@@ -309,63 +247,3 @@ func (p *parserContext) TableOfContents() tableofcontents.Root {
}
return tableofcontents.Root{}
}
-
-func newHighlighting(cfg highlight.Config) goldmark.Extender {
- return hl.NewHighlighting(
- hl.WithStyle(cfg.Style),
- hl.WithGuessLanguage(cfg.GuessSyntax),
- hl.WithCodeBlockOptions(highlight.GetCodeBlockOptions()),
- hl.WithFormatOptions(
- cfg.ToHTMLOptions()...,
- ),
-
- hl.WithWrapperRenderer(func(w util.BufWriter, ctx hl.CodeBlockContext, entering bool) {
- var language string
- if l, hasLang := ctx.Language(); hasLang {
- language = string(l)
- }
-
- if ctx.Highlighted() {
- if entering {
- writeDivStart(w, ctx)
- } else {
- writeDivEnd(w)
- }
- } else {
- if entering {
- highlight.WritePreStart(w, language, "")
- } else {
- highlight.WritePreEnd(w)
- }
- }
- }),
- )
-}
-
-func writeDivStart(w util.BufWriter, ctx hl.CodeBlockContext) {
- w.WriteString(`<div class="highlight`)
-
- var attributes []ast.Attribute
- if ctx.Attributes() != nil {
- attributes = ctx.Attributes().All()
- }
-
- if attributes != nil {
- class, found := ctx.Attributes().GetString("class")
- if found {
- w.WriteString(" ")
- w.Write(util.EscapeHTML(class.([]byte)))
-
- }
- _, _ = w.WriteString("\"")
- renderAttributes(w, true, attributes...)
- } else {
- _, _ = w.WriteString("\"")
- }
-
- w.WriteString(">")
-}
-
-func writeDivEnd(w util.BufWriter) {
- w.WriteString("</div>")
-}
diff --git a/markup/goldmark/convert_test.go b/markup/goldmark/convert_test.go
index 684f22c54..ecb308eba 100644
--- a/markup/goldmark/convert_test.go
+++ b/markup/goldmark/convert_test.go
@@ -20,6 +20,7 @@ import (
"github.com/spf13/cast"
+ "github.com/gohugoio/hugo/markup/converter/hooks"
"github.com/gohugoio/hugo/markup/goldmark/goldmark_config"
"github.com/gohugoio/hugo/markup/highlight"
@@ -41,9 +42,18 @@ func convert(c *qt.C, mconf markup_config.Config, content string) converter.Resu
},
)
c.Assert(err, qt.IsNil)
+ h := highlight.New(mconf.Highlight)
+
+ getRenderer := func(t hooks.RendererType, id interface{}) interface{} {
+ if t == hooks.CodeBlockRendererType {
+ return h
+ }
+ return nil
+ }
+
conv, err := p.New(converter.DocumentContext{DocumentID: "thedoc"})
c.Assert(err, qt.IsNil)
- b, err := conv.Convert(converter.RenderContext{RenderTOC: true, Src: []byte(content)})
+ b, err := conv.Convert(converter.RenderContext{RenderTOC: true, Src: []byte(content), GetRenderer: getRenderer})
c.Assert(err, qt.IsNil)
return b
@@ -372,12 +382,21 @@ LINE5
},
)
+ h := highlight.New(conf)
+
+ getRenderer := func(t hooks.RendererType, id interface{}) interface{} {
+ if t == hooks.CodeBlockRendererType {
+ return h
+ }
+ return nil
+ }
+
content := "```" + language + "\n" + code + "\n```"
c.Assert(err, qt.IsNil)
conv, err := p.New(converter.DocumentContext{})
c.Assert(err, qt.IsNil)
- b, err := conv.Convert(converter.RenderContext{Src: []byte(content)})
+ b, err := conv.Convert(converter.RenderContext{Src: []byte(content), GetRenderer: getRenderer})
c.Assert(err, qt.IsNil)
return string(b.Bytes())
@@ -391,7 +410,7 @@ LINE5
// TODO(bep) there is a whitespace mismatch (\n) between this and the highlight template func.
c.Assert(result, qt.Equals, "<div class=\"highlight\"><pre tabindex=\"0\" class=\"chroma\"><code class=\"language-bash\" data-lang=\"bash\"><span class=\"line\"><span class=\"cl\"><span class=\"nb\">echo</span> <span class=\"s2\">&#34;Hugo Rocks!&#34;</span>\n</span></span></code></pre></div>")
result = convertForConfig(c, cfg, `echo "Hugo Rocks!"`, "unknown")
- c.Assert(result, qt.Equals, "<pre tabindex=\"0\"><code class=\"language-unknown\" data-lang=\"unknown\">echo &quot;Hugo Rocks!&quot;\n</code></pre>")
+ c.Assert(result, qt.Equals, "<pre tabindex=\"0\"><code class=\"language-unknown\" data-lang=\"unknown\">echo &#34;Hugo Rocks!&#34;\n</code></pre>")
})
c.Run("Highlight lines, default config", func(c *qt.C) {
diff --git a/markup/goldmark/integration_test.go b/markup/goldmark/integration_test.go
index 4ace04f75..f1fa745c5 100644
--- a/markup/goldmark/integration_test.go
+++ b/markup/goldmark/integration_test.go
@@ -36,12 +36,12 @@ func TestAttributeExclusion(t *testing.T) {
---
title: "p1"
---
-## Heading {class="a" onclick="alert('heading')" linenos="inline"}
+## Heading {class="a" onclick="alert('heading')"}
> Blockquote
-{class="b" ondblclick="alert('blockquote')" LINENOS="inline"}
+{class="b" ondblclick="alert('blockquote')"}
-~~~bash {id="c" onmouseover="alert('code fence')"}
+~~~bash {id="c" onmouseover="alert('code fence')" LINENOS=true}
foo
~~~
-- layouts/_default/single.html --
@@ -96,6 +96,63 @@ title: "p1"
`)
}
+func TestAttributesDefaultRenderer(t *testing.T) {
+ t.Parallel()
+
+ files := `
+-- content/p1.md --
+---
+title: "p1"
+---
+## Heading Attribute Which Needs Escaping { class="a < b" }
+-- layouts/_default/single.html --
+{{ .Content }}
+`
+
+ b := hugolib.NewIntegrationTestBuilder(
+ hugolib.IntegrationTestConfig{
+ T: t,
+ TxtarString: files,
+ NeedsOsFS: false,
+ },
+ ).Build()
+
+ b.AssertFileContent("public/p1/index.html", `
+class="a &lt; b"
+ `)
+}
+
+// Issue 9558.
+func TestAttributesHookNoEscape(t *testing.T) {
+ t.Parallel()
+
+ files := `
+-- content/p1.md --
+---
+title: "p1"
+---
+## Heading Attribute Which Needs Escaping { class="Smith & Wesson" }
+-- layouts/_default/_markup/render-heading.html --
+plain: |{{- range $k, $v := .Attributes -}}{{ $k }}: {{ $v }}|{{ end }}|
+safeHTML: |{{- range $k, $v := .Attributes -}}{{ $k }}: {{ $v | safeHT