hugo

Fork of github.com/gohugoio/hugo with reverse pagination support

git clone git://git.shimmy1996.com/hugo.git

render.go (4987B)

    1 // Copyright 2022 The Hugo Authors. All rights reserved.
    2 //
    3 // Licensed under the Apache License, Version 2.0 (the "License");
    4 // you may not use this file except in compliance with the License.
    5 // You may obtain a copy of the License at
    6 // http://www.apache.org/licenses/LICENSE-2.0
    7 //
    8 // Unless required by applicable law or agreed to in writing, software
    9 // distributed under the License is distributed on an "AS IS" BASIS,
   10 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   11 // See the License for the specific language governing permissions and
   12 // limitations under the License.
   13 
   14 package codeblocks
   15 
   16 import (
   17 	"bytes"
   18 	"fmt"
   19 	"sync"
   20 
   21 	"github.com/alecthomas/chroma/v2/lexers"
   22 	"github.com/gohugoio/hugo/common/herrors"
   23 	htext "github.com/gohugoio/hugo/common/text"
   24 	"github.com/gohugoio/hugo/markup/converter/hooks"
   25 	"github.com/gohugoio/hugo/markup/goldmark/internal/render"
   26 	"github.com/gohugoio/hugo/markup/internal/attributes"
   27 	"github.com/yuin/goldmark"
   28 	"github.com/yuin/goldmark/ast"
   29 	"github.com/yuin/goldmark/parser"
   30 	"github.com/yuin/goldmark/renderer"
   31 	"github.com/yuin/goldmark/text"
   32 	"github.com/yuin/goldmark/util"
   33 )
   34 
   35 type (
   36 	codeBlocksExtension struct{}
   37 	htmlRenderer        struct{}
   38 )
   39 
   40 func New() goldmark.Extender {
   41 	return &codeBlocksExtension{}
   42 }
   43 
   44 func (e *codeBlocksExtension) Extend(m goldmark.Markdown) {
   45 	m.Parser().AddOptions(
   46 		parser.WithASTTransformers(
   47 			util.Prioritized(&Transformer{}, 100),
   48 		),
   49 	)
   50 	m.Renderer().AddOptions(renderer.WithNodeRenderers(
   51 		util.Prioritized(newHTMLRenderer(), 100),
   52 	))
   53 }
   54 
   55 func newHTMLRenderer() renderer.NodeRenderer {
   56 	r := &htmlRenderer{}
   57 	return r
   58 }
   59 
   60 func (r *htmlRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) {
   61 	reg.Register(KindCodeBlock, r.renderCodeBlock)
   62 }
   63 
   64 func (r *htmlRenderer) renderCodeBlock(w util.BufWriter, src []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
   65 	ctx := w.(*render.Context)
   66 
   67 	if entering {
   68 		return ast.WalkContinue, nil
   69 	}
   70 
   71 	n := node.(*codeBlock)
   72 	lang := string(n.b.Language(src))
   73 	renderer := ctx.RenderContext().GetRenderer(hooks.CodeBlockRendererType, lang)
   74 	if renderer == nil {
   75 		return ast.WalkStop, fmt.Errorf("no code renderer found for %q", lang)
   76 	}
   77 
   78 	ordinal := n.ordinal
   79 
   80 	var buff bytes.Buffer
   81 
   82 	l := n.b.Lines().Len()
   83 	for i := 0; i < l; i++ {
   84 		line := n.b.Lines().At(i)
   85 		buff.Write(line.Value(src))
   86 	}
   87 
   88 	s := htext.Chomp(buff.String())
   89 
   90 	var info []byte
   91 	if n.b.Info != nil {
   92 		info = n.b.Info.Segment.Value(src)
   93 	}
   94 
   95 	attrtp := attributes.AttributesOwnerCodeBlockCustom
   96 	if isd, ok := renderer.(hooks.IsDefaultCodeBlockRendererProvider); (ok && isd.IsDefaultCodeBlockRenderer()) || lexers.Get(lang) != nil {
   97 		// We say that this is a Chroma code block if it's the default code block renderer
   98 		// or if the language is supported by Chroma.
   99 		attrtp = attributes.AttributesOwnerCodeBlockChroma
  100 	}
  101 
  102 	// IsDefaultCodeBlockRendererProvider
  103 	attrs := getAttributes(n.b, info)
  104 	cbctx := &codeBlockContext{
  105 		page:             ctx.DocumentContext().Document,
  106 		lang:             lang,
  107 		code:             s,
  108 		ordinal:          ordinal,
  109 		AttributesHolder: attributes.New(attrs, attrtp),
  110 	}
  111 
  112 	cbctx.createPos = func() htext.Position {
  113 		if resolver, ok := renderer.(hooks.ElementPositionResolver); ok {
  114 			return resolver.ResolvePosition(cbctx)
  115 		}
  116 		return htext.Position{
  117 			Filename:     ctx.DocumentContext().Filename,
  118 			LineNumber:   1,
  119 			ColumnNumber: 1,
  120 		}
  121 	}
  122 
  123 	cr := renderer.(hooks.CodeBlockRenderer)
  124 
  125 	err := cr.RenderCodeblock(
  126 		w,
  127 		cbctx,
  128 	)
  129 
  130 	ctx.AddIdentity(cr)
  131 
  132 	if err != nil {
  133 		return ast.WalkContinue, herrors.NewFileErrorFromPos(err, cbctx.createPos())
  134 	}
  135 
  136 	return ast.WalkContinue, nil
  137 }
  138 
  139 type codeBlockContext struct {
  140 	page    any
  141 	lang    string
  142 	code    string
  143 	ordinal int
  144 
  145 	// This is only used in error situations and is expensive to create,
  146 	// to deleay creation until needed.
  147 	pos       htext.Position
  148 	posInit   sync.Once
  149 	createPos func() htext.Position
  150 
  151 	*attributes.AttributesHolder
  152 }
  153 
  154 func (c *codeBlockContext) Page() any {
  155 	return c.page
  156 }
  157 
  158 func (c *codeBlockContext) Type() string {
  159 	return c.lang
  160 }
  161 
  162 func (c *codeBlockContext) Inner() string {
  163 	return c.code
  164 }
  165 
  166 func (c *codeBlockContext) Ordinal() int {
  167 	return c.ordinal
  168 }
  169 
  170 func (c *codeBlockContext) Position() htext.Position {
  171 	c.posInit.Do(func() {
  172 		c.pos = c.createPos()
  173 	})
  174 	return c.pos
  175 }
  176 
  177 func getAttributes(node *ast.FencedCodeBlock, infostr []byte) []ast.Attribute {
  178 	if node.Attributes() != nil {
  179 		return node.Attributes()
  180 	}
  181 	if infostr != nil {
  182 		attrStartIdx := -1
  183 
  184 		for idx, char := range infostr {
  185 			if char == '{' {
  186 				attrStartIdx = idx
  187 				break
  188 			}
  189 		}
  190 
  191 		if attrStartIdx > 0 {
  192 			n := ast.NewTextBlock() // dummy node for storing attributes
  193 			attrStr := infostr[attrStartIdx:]
  194 			if attrs, hasAttr := parser.ParseAttributes(text.NewReader(attrStr)); hasAttr {
  195 				for _, attr := range attrs {
  196 					n.SetAttribute(attr.Name, attr.Value)
  197 				}
  198 				return n.Attributes()
  199 			}
  200 		}
  201 	}
  202 	return nil
  203 }