github.com/kovansky/hugo@v0.92.3-0.20220224232819-63076e4ff19f/markup/goldmark/codeblocks/render.go (about)

     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  
    20  	"github.com/gohugoio/hugo/markup/converter/hooks"
    21  	"github.com/gohugoio/hugo/markup/goldmark/internal/render"
    22  	"github.com/gohugoio/hugo/markup/internal/attributes"
    23  	"github.com/yuin/goldmark"
    24  	"github.com/yuin/goldmark/ast"
    25  	"github.com/yuin/goldmark/parser"
    26  	"github.com/yuin/goldmark/renderer"
    27  	"github.com/yuin/goldmark/text"
    28  	"github.com/yuin/goldmark/util"
    29  )
    30  
    31  type (
    32  	diagrams     struct{}
    33  	htmlRenderer struct{}
    34  )
    35  
    36  func New() goldmark.Extender {
    37  	return &diagrams{}
    38  }
    39  
    40  func (e *diagrams) Extend(m goldmark.Markdown) {
    41  	m.Parser().AddOptions(
    42  		parser.WithASTTransformers(
    43  			util.Prioritized(&Transformer{}, 100),
    44  		),
    45  	)
    46  	m.Renderer().AddOptions(renderer.WithNodeRenderers(
    47  		util.Prioritized(newHTMLRenderer(), 100),
    48  	))
    49  }
    50  
    51  func newHTMLRenderer() renderer.NodeRenderer {
    52  	r := &htmlRenderer{}
    53  	return r
    54  }
    55  
    56  func (r *htmlRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) {
    57  	reg.Register(KindCodeBlock, r.renderCodeBlock)
    58  }
    59  
    60  func (r *htmlRenderer) renderCodeBlock(w util.BufWriter, src []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
    61  	ctx := w.(*render.Context)
    62  
    63  	if entering {
    64  		return ast.WalkContinue, nil
    65  	}
    66  
    67  	n := node.(*codeBlock)
    68  	lang := string(n.b.Language(src))
    69  	ordinal := n.ordinal
    70  
    71  	var buff bytes.Buffer
    72  
    73  	l := n.b.Lines().Len()
    74  	for i := 0; i < l; i++ {
    75  		line := n.b.Lines().At(i)
    76  		buff.Write(line.Value(src))
    77  	}
    78  	text := buff.String()
    79  
    80  	var info []byte
    81  	if n.b.Info != nil {
    82  		info = n.b.Info.Segment.Value(src)
    83  	}
    84  	attrs := getAttributes(n.b, info)
    85  
    86  	v := ctx.RenderContext().GetRenderer(hooks.CodeBlockRendererType, lang)
    87  	if v == nil {
    88  		return ast.WalkStop, fmt.Errorf("no code renderer found for %q", lang)
    89  	}
    90  
    91  	cr := v.(hooks.CodeBlockRenderer)
    92  
    93  	err := cr.RenderCodeblock(
    94  		w,
    95  		codeBlockContext{
    96  			page:             ctx.DocumentContext().Document,
    97  			lang:             lang,
    98  			code:             text,
    99  			ordinal:          ordinal,
   100  			AttributesHolder: attributes.New(attrs, attributes.AttributesOwnerCodeBlock),
   101  		},
   102  	)
   103  
   104  	ctx.AddIdentity(cr)
   105  
   106  	return ast.WalkContinue, err
   107  }
   108  
   109  type codeBlockContext struct {
   110  	page    interface{}
   111  	lang    string
   112  	code    string
   113  	ordinal int
   114  	*attributes.AttributesHolder
   115  }
   116  
   117  func (c codeBlockContext) Page() interface{} {
   118  	return c.page
   119  }
   120  
   121  func (c codeBlockContext) Lang() string {
   122  	return c.lang
   123  }
   124  
   125  func (c codeBlockContext) Code() string {
   126  	return c.code
   127  }
   128  
   129  func (c codeBlockContext) Ordinal() int {
   130  	return c.ordinal
   131  }
   132  
   133  func getAttributes(node *ast.FencedCodeBlock, infostr []byte) []ast.Attribute {
   134  	if node.Attributes() != nil {
   135  		return node.Attributes()
   136  	}
   137  	if infostr != nil {
   138  		attrStartIdx := -1
   139  
   140  		for idx, char := range infostr {
   141  			if char == '{' {
   142  				attrStartIdx = idx
   143  				break
   144  			}
   145  		}
   146  
   147  		if attrStartIdx > 0 {
   148  			n := ast.NewTextBlock() // dummy node for storing attributes
   149  			attrStr := infostr[attrStartIdx:]
   150  			if attrs, hasAttr := parser.ParseAttributes(text.NewReader(attrStr)); hasAttr {
   151  				for _, attr := range attrs {
   152  					n.SetAttribute(attr.Name, attr.Value)
   153  				}
   154  				return n.Attributes()
   155  			}
   156  		}
   157  	}
   158  	return nil
   159  }