Skip to content

Commit

Permalink
Add Markdown render hooks for tables
Browse files Browse the repository at this point in the history
Fixes #9316
Fixes #12811
  • Loading branch information
bep committed Aug 31, 2024
1 parent a3684c8 commit 4a28a25
Show file tree
Hide file tree
Showing 13 changed files with 583 additions and 269 deletions.
4 changes: 3 additions & 1 deletion hugolib/page__per_output.go
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,8 @@ func (pco *pageContentOutput) initRenderHooks() error {
if id != nil {
layoutDescriptor.KindVariants = id.(string)
}
case hooks.TableRendererType:
layoutDescriptor.Kind = "render-table"
case hooks.CodeBlockRendererType:
layoutDescriptor.Kind = "render-codeblock"
if id != nil {
Expand Down Expand Up @@ -334,7 +336,7 @@ func (pco *pageContentOutput) initRenderHooks() error {

templ, found1 := getHookTemplate(pco.po.f)

if pco.po.p.reusePageOutputContent() {
if !found1 || pco.po.p.reusePageOutputContent() {
// Check if some of the other output formats would give a different template.
for _, f := range pco.po.p.s.renderFormats {
if f.Name == pco.po.f.Name {
Expand Down
4 changes: 4 additions & 0 deletions hugolib/site.go
Original file line number Diff line number Diff line change
Expand Up @@ -930,6 +930,10 @@ func (hr hookRendererTemplate) RenderBlockquote(cctx context.Context, w hugio.Fl
return hr.templateHandler.ExecuteWithContext(cctx, hr.templ, w, ctx)
}

func (hr hookRendererTemplate) RenderTable(cctx context.Context, w hugio.FlexiWriter, ctx hooks.TableContext) error {
return hr.templateHandler.ExecuteWithContext(cctx, hr.templ, w, ctx)
}

func (hr hookRendererTemplate) ResolvePosition(ctx any) text.Position {
return hr.resolvePosition(ctx)
}
Expand Down
50 changes: 37 additions & 13 deletions markup/converter/hooks/hooks.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,8 @@ type ImageLinkContext interface {

// CodeblockContext is the context passed to a code block render hook.
type CodeblockContext interface {
BaseContext
AttributesProvider
text.Positioner
PageProvider

// Chroma highlighting processing options. This will only be filled if Type is a known Chroma Lexer.
Options() map[string]any
Expand All @@ -73,19 +72,31 @@ type CodeblockContext interface {

// The text between the code fences.
Inner() string

// Zero-based ordinal for all code blocks in the current document.
Ordinal() int
}

// BlockquoteContext is the context passed to a blockquote render hook.
type BlockquoteContext interface {
// TableContext is the context passed to a table render hook.
type TableContext interface {
BaseContext
AttributesProvider

THead() []TableRow
TBody() []TableRow
}

// BaseContext is the base context used in most render hooks.
type BaseContext interface {
text.Positioner
PageProvider

// Zero-based ordinal for all block quotes in the current document.
// Zero-based ordinal for all elements of this kind in the current document.
Ordinal() int
}

// BlockquoteContext is the context passed to a blockquote render hook.
type BlockquoteContext interface {
BaseContext

AttributesProvider

// The blockquote text.
// If type is "alert", this will be the alert text.
Expand All @@ -107,18 +118,14 @@ type PositionerSourceTargetProvider interface {

// PassThroughContext is the context passed to a passthrough render hook.
type PassthroughContext interface {
BaseContext
AttributesProvider
text.Positioner
PageProvider

// Currently one of "inline" or "block".
Type() string

// The inner content of the passthrough element, excluding the delimiters.
Inner() string

// Zero-based ordinal for all passthrough elements in the document.
Ordinal() int
}

type AttributesOptionsSliceProvider interface {
Expand All @@ -138,6 +145,10 @@ type BlockquoteRenderer interface {
RenderBlockquote(cctx context.Context, w hugio.FlexiWriter, ctx BlockquoteContext) error
}

type TableRenderer interface {
RenderTable(cctx context.Context, w hugio.FlexiWriter, ctx TableContext) error
}

type PassthroughRenderer interface {
RenderPassthrough(cctx context.Context, w io.Writer, ctx PassthroughContext) error
}
Expand Down Expand Up @@ -196,6 +207,19 @@ const (
CodeBlockRendererType
PassthroughRendererType
BlockquoteRendererType
TableRendererType
)

type GetRendererFunc func(t RendererType, id any) any

type TableCell struct {
Text hstring.RenderedString
Alignment string // left, center, or right
}

type TableRow []TableCell

type Table struct {
THead []TableRow
TBody []TableRow
}
101 changes: 10 additions & 91 deletions markup/goldmark/blockquotes/blockquotes.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,8 @@ package blockquotes
import (
"regexp"
"strings"
"sync"

"github.com/gohugoio/hugo/common/herrors"
htext "github.com/gohugoio/hugo/common/text"
"github.com/gohugoio/hugo/common/types/hstring"
"github.com/gohugoio/hugo/markup/converter/hooks"
"github.com/gohugoio/hugo/markup/goldmark/internal/render"
Expand Down Expand Up @@ -71,70 +69,36 @@ func (r *htmlRenderer) renderBlockquote(w util.BufWriter, src []byte, node ast.N
return ast.WalkContinue, nil
}

pos := ctx.PopPos()
text := ctx.Buffer.Bytes()[pos:]
ctx.Buffer.Truncate(pos)
text := ctx.PopRenderedString()

ordinal := ctx.GetAndIncrementOrdinal(ast.KindBlockquote)

texts := string(text)
typ := typeRegular
alertType := resolveGitHubAlert(texts)
alertType := resolveGitHubAlert(string(text))
if alertType != "" {
typ = typeAlert
}

renderer := ctx.RenderContext().GetRenderer(hooks.BlockquoteRendererType, typ)
if renderer == nil {
return r.renderBlockquoteDefault(w, n, texts)
return r.renderBlockquoteDefault(w, n, text)
}

if typ == typeAlert {
// Trim preamble: <p>[!NOTE]<br>\n but preserve leading paragraph.
// We could possibly complicate this by moving this to the parser, but
// keep it simple for now.
texts = "<p>" + texts[strings.Index(texts, "\n")+1:]
}

var sourceRef []byte

// Extract a source sample to use for position information.
if nn := n.FirstChild(); nn != nil {
var start, stop int
for i := 0; i < nn.Lines().Len() && i < 2; i++ {
line := nn.Lines().At(i)
if i == 0 {
start = line.Start
}
stop = line.Stop
}
// We do not mutate the source, so this is safe.
sourceRef = src[start:stop]
text = "<p>" + text[strings.Index(text, "\n")+1:]
}

bqctx := &blockquoteContext{
page: ctx.DocumentContext().Document,
pageInner: r.getPageInner(ctx),
BaseContext: render.NewBaseContext(ctx, renderer, n, src, nil, ordinal),
typ: typ,
alertType: alertType,
text: hstring.RenderedString(texts),
sourceRef: sourceRef,
ordinal: ordinal,
text: hstring.RenderedString(text),
AttributesHolder: attributes.New(n.Attributes(), attributes.AttributesOwnerGeneral),
}

bqctx.createPos = func() htext.Position {
if resolver, ok := renderer.(hooks.ElementPositionResolver); ok {
return resolver.ResolvePosition(bqctx)
}

return htext.Position{
Filename: ctx.DocumentContext().Filename,
LineNumber: 1,
ColumnNumber: 1,
}
}

cr := renderer.(hooks.BlockquoteRenderer)

err := cr.RenderBlockquote(
Expand All @@ -143,24 +107,12 @@ func (r *htmlRenderer) renderBlockquote(w util.BufWriter, src []byte, node ast.N
bqctx,
)
if err != nil {
return ast.WalkContinue, herrors.NewFileErrorFromPos(err, bqctx.createPos())
return ast.WalkContinue, herrors.NewFileErrorFromPos(err, bqctx.Position())
}

return ast.WalkContinue, nil
}

func (r *htmlRenderer) getPageInner(rctx *render.Context) any {
pid := rctx.PeekPid()
if pid > 0 {
if lookup := rctx.DocumentContext().DocumentLookup; lookup != nil {
if v := rctx.DocumentContext().DocumentLookup(pid); v != nil {
return v
}
}
}
return rctx.DocumentContext().Document
}

// Code borrowed from goldmark's html renderer.
func (r *htmlRenderer) renderBlockquoteDefault(
w util.BufWriter, n ast.Node, text string,
Expand All @@ -180,19 +132,11 @@ func (r *htmlRenderer) renderBlockquoteDefault(
}

type blockquoteContext struct {
page any
pageInner any
hooks.BaseContext

text hstring.RenderedString
typ string
sourceRef []byte
alertType string
ordinal int

// This is only used in error situations and is expensive to create,
// so delay creation until needed.
pos htext.Position
posInit sync.Once
createPos func() htext.Position
typ string

*attributes.AttributesHolder
}
Expand All @@ -205,35 +149,10 @@ func (c *blockquoteContext) AlertType() string {
return c.alertType
}

func (c *blockquoteContext) Page() any {
return c.page
}

func (c *blockquoteContext) PageInner() any {
return c.pageInner
}

func (c *blockquoteContext) Text() hstring.RenderedString {
return c.text
}

func (c *blockquoteContext) Ordinal() int {
return c.ordinal
}

func (c *blockquoteContext) Position() htext.Position {
c.posInit.Do(func() {
c.pos = c.createPos()
})
return c.pos
}

func (c *blockquoteContext) PositionerSourceTarget() []byte {
return c.sourceRef
}

var _ hooks.PositionerSourceTargetProvider = (*blockquoteContext)(nil)

// https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax#alerts
// Five types:
// [!NOTE], [!TIP], [!WARNING], [!IMPORTANT], [!CAUTION]
Expand Down
Loading

0 comments on commit 4a28a25

Please sign in to comment.