aboutsummaryrefslogtreecommitdiffhomepage
path: root/markup/goldmark/passthrough/passthrough.go
diff options
context:
space:
mode:
Diffstat (limited to 'markup/goldmark/passthrough/passthrough.go')
-rw-r--r--markup/goldmark/passthrough/passthrough.go219
1 files changed, 219 insertions, 0 deletions
diff --git a/markup/goldmark/passthrough/passthrough.go b/markup/goldmark/passthrough/passthrough.go
new file mode 100644
index 000000000..20e42211e
--- /dev/null
+++ b/markup/goldmark/passthrough/passthrough.go
@@ -0,0 +1,219 @@
+// Copyright 2024 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 passthrough
+
+import (
+ "bytes"
+ "sync"
+
+ htext "github.com/gohugoio/hugo/common/text"
+
+ "github.com/gohugoio/hugo-goldmark-extensions/passthrough"
+ "github.com/gohugoio/hugo/markup/converter/hooks"
+ "github.com/gohugoio/hugo/markup/goldmark/goldmark_config"
+ "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/renderer"
+ "github.com/yuin/goldmark/util"
+)
+
+func New(cfg goldmark_config.Passthrough) goldmark.Extender {
+ if !cfg.Enable {
+ return nil
+ }
+ return &passthroughExtension{cfg: cfg}
+}
+
+type (
+ passthroughExtension struct {
+ cfg goldmark_config.Passthrough
+ }
+ htmlRenderer struct{}
+)
+
+func (e *passthroughExtension) Extend(m goldmark.Markdown) {
+ configuredInlines := e.cfg.Delimiters.Inline
+ configuredBlocks := e.cfg.Delimiters.Block
+
+ inlineDelimiters := make([]passthrough.Delimiters, len(configuredInlines))
+ blockDelimiters := make([]passthrough.Delimiters, len(configuredBlocks))
+
+ for i, d := range configuredInlines {
+ inlineDelimiters[i] = passthrough.Delimiters{
+ Open: d[0],
+ Close: d[1],
+ }
+ }
+
+ for i, d := range configuredBlocks {
+ blockDelimiters[i] = passthrough.Delimiters{
+ Open: d[0],
+ Close: d[1],
+ }
+ }
+
+ pse := passthrough.New(
+ passthrough.Config{
+ InlineDelimiters: inlineDelimiters,
+ BlockDelimiters: blockDelimiters,
+ },
+ )
+
+ pse.Extend(m)
+
+ // Set up render hooks if configured.
+ // Upstream passthrough inline = 101
+ // Upstream passthrough block = 99
+ m.Renderer().AddOptions(renderer.WithNodeRenderers(
+ util.Prioritized(newHTMLRenderer(), 90),
+ ))
+}
+
+func newHTMLRenderer() renderer.NodeRenderer {
+ r := &htmlRenderer{}
+ return r
+}
+
+func (r *htmlRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) {
+ reg.Register(passthrough.KindPassthroughBlock, r.renderPassthroughBlock)
+ reg.Register(passthrough.KindPassthroughInline, r.renderPassthroughBlock)
+}
+
+func (r *htmlRenderer) renderPassthroughBlock(w util.BufWriter, src []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
+ ctx := w.(*render.Context)
+
+ if entering {
+ return ast.WalkContinue, nil
+ }
+
+ var (
+ s string
+ typ string
+ delims *passthrough.Delimiters
+ )
+
+ switch nn := node.(type) {
+ case *passthrough.PassthroughInline:
+ s = string(nn.Text(src))
+ typ = "inline"
+ delims = nn.Delimiters
+ case (*passthrough.PassthroughBlock):
+ l := nn.Lines().Len()
+ var buff bytes.Buffer
+ for i := 0; i < l; i++ {
+ line := nn.Lines().At(i)
+ buff.Write(line.Value(src))
+ }
+ s = buff.String()
+ typ = "block"
+ delims = nn.Delimiters
+ }
+
+ renderer := ctx.RenderContext().GetRenderer(hooks.PassthroughRendererType, typ)
+ if renderer == nil {
+ // Write the raw content if no renderer is found.
+ ctx.WriteString(s)
+ return ast.WalkContinue, nil
+ }
+
+ // Inline and block passthroughs share the same ordinal counter.
+ ordinal := ctx.GetAndIncrementOrdinal(passthrough.KindPassthroughBlock)
+
+ // Trim the delimiters.
+ s = s[len(delims.Open) : len(s)-len(delims.Close)]
+
+ pctx := &passthroughContext{
+ ordinal: ordinal,
+ page: ctx.DocumentContext().Document,
+ pageInner: r.getPageInner(ctx),
+ inner: s,
+ typ: typ,
+ AttributesHolder: attributes.New(node.Attributes(), attributes.AttributesOwnerGeneral),
+ }
+
+ pctx.createPos = func() htext.Position {
+ if resolver, ok := renderer.(hooks.ElementPositionResolver); ok {
+ return resolver.ResolvePosition(pctx)
+ }
+ return htext.Position{
+ Filename: ctx.DocumentContext().Filename,
+ LineNumber: 1,
+ ColumnNumber: 1,
+ }
+ }
+
+ pr := renderer.(hooks.PassthroughRenderer)
+
+ if err := pr.RenderPassthrough(ctx.RenderContext().Ctx, w, pctx); err != nil {
+ return ast.WalkStop, err
+ }
+
+ 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
+}
+
+type passthroughContext struct {
+ page any
+ pageInner any
+ typ string // inner or block
+ inner 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
+ *attributes.AttributesHolder
+}
+
+func (p *passthroughContext) Page() any {
+ return p.page
+}
+
+func (p *passthroughContext) PageInner() any {
+ return p.pageInner
+}
+
+func (p *passthroughContext) Type() string {
+ return p.typ
+}
+
+func (p *passthroughContext) Inner() string {
+ return p.inner
+}
+
+func (p *passthroughContext) Ordinal() int {
+ return p.ordinal
+}
+
+func (p *passthroughContext) Position() htext.Position {
+ p.posInit.Do(func() {
+ p.pos = p.createPos()
+ })
+ return p.pos
+}