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
This commit is contained in:
Bjørn Erik Pedersen
2022-02-17 13:04:00 +01:00
parent 2c20f5bc00
commit 08fdca9d93
73 changed files with 1887 additions and 1986 deletions

View File

@@ -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>")
}