summaryrefslogtreecommitdiffstats
path: root/markup/goldmark/hugocontext/hugocontext.go
diff options
context:
space:
mode:
Diffstat (limited to 'markup/goldmark/hugocontext/hugocontext.go')
-rw-r--r--markup/goldmark/hugocontext/hugocontext.go165
1 files changed, 165 insertions, 0 deletions
diff --git a/markup/goldmark/hugocontext/hugocontext.go b/markup/goldmark/hugocontext/hugocontext.go
new file mode 100644
index 000000000..ed62bb8c6
--- /dev/null
+++ b/markup/goldmark/hugocontext/hugocontext.go
@@ -0,0 +1,165 @@
+// 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 hugocontext
+
+import (
+ "bytes"
+ "fmt"
+ "strconv"
+
+ "github.com/gohugoio/hugo/bufferpool"
+ "github.com/gohugoio/hugo/markup/goldmark/internal/render"
+ "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"
+)
+
+func New() goldmark.Extender {
+ return &hugoContextExtension{}
+}
+
+// Wrap wraps the given byte slice in a Hugo context that used to determine the correct Page
+// in .RenderShortcodes.
+func Wrap(b []byte, pid uint64) []byte {
+ buf := bufferpool.GetBuffer()
+ defer bufferpool.PutBuffer(buf)
+ buf.Write(prefix)
+ buf.WriteString(" pid=")
+ buf.WriteString(strconv.FormatUint(pid, 10))
+ buf.Write(endDelim)
+ buf.WriteByte('\n')
+ buf.Write(b)
+ buf.Write(prefix)
+ buf.Write(closingDelimAndNewline)
+ return buf.Bytes()
+}
+
+var kindHugoContext = ast.NewNodeKind("HugoContext")
+
+// HugoContext is a node that represents a Hugo context.
+type HugoContext struct {
+ ast.BaseInline
+
+ Closing bool
+
+ // Internal page ID. Not persisted.
+ Pid uint64
+}
+
+// Dump implements Node.Dump.
+func (n *HugoContext) Dump(source []byte, level int) {
+ m := map[string]string{}
+ m["Pid"] = fmt.Sprintf("%v", n.Pid)
+ ast.DumpHelper(n, source, level, m, nil)
+}
+
+func (n *HugoContext) parseAttrs(attrBytes []byte) {
+ keyPairs := bytes.Split(attrBytes, []byte(" "))
+ for _, keyPair := range keyPairs {
+ kv := bytes.Split(keyPair, []byte("="))
+ if len(kv) != 2 {
+ continue
+ }
+ key := string(kv[0])
+ val := string(kv[1])
+ switch key {
+ case "pid":
+ pid, _ := strconv.ParseUint(val, 10, 64)
+ n.Pid = pid
+ }
+ }
+}
+
+func (h *HugoContext) Kind() ast.NodeKind {
+ return kindHugoContext
+}
+
+var (
+ prefix = []byte("{{__hugo_ctx")
+ endDelim = []byte("}}")
+ closingDelimAndNewline = []byte("/}}\n")
+)
+
+var _ parser.InlineParser = (*hugoContextParser)(nil)
+
+type hugoContextParser struct{}
+
+func (s *hugoContextParser) Parse(parent ast.Node, block text.Reader, pc parser.Context) ast.Node {
+ line, _ := block.PeekLine()
+ if !bytes.HasPrefix(line, prefix) {
+ return nil
+ }
+ end := bytes.Index(line, endDelim)
+ if end == -1 {
+ return nil
+ }
+
+ block.Advance(end + len(endDelim) + 1) // +1 for the newline
+
+ if line[end-1] == '/' {
+ return &HugoContext{Closing: true}
+ }
+
+ attrBytes := line[len(prefix)+1 : end]
+ h := &HugoContext{}
+ h.parseAttrs(attrBytes)
+ return h
+}
+
+func (a *hugoContextParser) Trigger() []byte {
+ return []byte{'{'}
+}
+
+type hugoContextRenderer struct{}
+
+func (r *hugoContextRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) {
+ reg.Register(kindHugoContext, r.handleHugoContext)
+}
+
+func (r *hugoContextRenderer) handleHugoContext(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
+ if !entering {
+ return ast.WalkContinue, nil
+ }
+
+ hctx := node.(*HugoContext)
+ ctx, ok := w.(*render.Context)
+ if !ok {
+ return ast.WalkContinue, nil
+ }
+ if hctx.Closing {
+ _ = ctx.PopPid()
+ } else {
+ ctx.PushPid(hctx.Pid)
+ }
+ return ast.WalkContinue, nil
+}
+
+type hugoContextExtension struct{}
+
+func (a *hugoContextExtension) Extend(m goldmark.Markdown) {
+ m.Parser().AddOptions(
+ parser.WithInlineParsers(
+ util.Prioritized(&hugoContextParser{}, 50),
+ ),
+ )
+
+ m.Renderer().AddOptions(
+ renderer.WithNodeRenderers(
+ util.Prioritized(&hugoContextRenderer{}, 50),
+ ),
+ )
+}