hugo

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

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

attributes.go (5666B)

    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 attributes
   15 
   16 import (
   17 	"fmt"
   18 	"strconv"
   19 	"strings"
   20 	"sync"
   21 
   22 	"github.com/gohugoio/hugo/common/hugio"
   23 	"github.com/spf13/cast"
   24 	"github.com/yuin/goldmark/ast"
   25 	"github.com/yuin/goldmark/util"
   26 )
   27 
   28 // Markdown attributes used as options by the Chroma highlighter.
   29 var chromaHightlightProcessingAttributes = map[string]bool{
   30 	"anchorLineNos":      true,
   31 	"guessSyntax":        true,
   32 	"hl_Lines":           true,
   33 	"hl_inline":          true,
   34 	"lineAnchors":        true,
   35 	"lineNos":            true,
   36 	"lineNoStart":        true,
   37 	"lineNumbersInTable": true,
   38 	"noClasses":          true,
   39 	"nohl":               true,
   40 	"style":              true,
   41 	"tabWidth":           true,
   42 }
   43 
   44 func init() {
   45 	for k, v := range chromaHightlightProcessingAttributes {
   46 		chromaHightlightProcessingAttributes[strings.ToLower(k)] = v
   47 	}
   48 }
   49 
   50 type AttributesOwnerType int
   51 
   52 const (
   53 	AttributesOwnerGeneral AttributesOwnerType = iota
   54 	AttributesOwnerCodeBlockChroma
   55 	AttributesOwnerCodeBlockCustom
   56 )
   57 
   58 func New(astAttributes []ast.Attribute, ownerType AttributesOwnerType) *AttributesHolder {
   59 	var (
   60 		attrs []Attribute
   61 		opts  []Attribute
   62 	)
   63 	for _, v := range astAttributes {
   64 		nameLower := strings.ToLower(string(v.Name))
   65 		if strings.HasPrefix(string(nameLower), "on") {
   66 			continue
   67 		}
   68 		var vv any
   69 		switch vvv := v.Value.(type) {
   70 		case bool, float64:
   71 			vv = vvv
   72 		case []any:
   73 			// Highlight line number hlRanges.
   74 			var hlRanges [][2]int
   75 			for _, l := range vvv {
   76 				if ln, ok := l.(float64); ok {
   77 					hlRanges = append(hlRanges, [2]int{int(ln) - 1, int(ln) - 1})
   78 				} else if rng, ok := l.([]uint8); ok {
   79 					slices := strings.Split(string([]byte(rng)), "-")
   80 					lhs, err := strconv.Atoi(slices[0])
   81 					if err != nil {
   82 						continue
   83 					}
   84 					rhs := lhs
   85 					if len(slices) > 1 {
   86 						rhs, err = strconv.Atoi(slices[1])
   87 						if err != nil {
   88 							continue
   89 						}
   90 					}
   91 					hlRanges = append(hlRanges, [2]int{lhs - 1, rhs - 1})
   92 				}
   93 			}
   94 			vv = hlRanges
   95 		case []byte:
   96 			// Note that we don't do any HTML escaping here.
   97 			// We used to do that, but that changed in #9558.
   98 			// Noww it's up to the templates to decide.
   99 			vv = string(vvv)
  100 		default:
  101 			panic(fmt.Sprintf("not implemented: %T", vvv))
  102 		}
  103 
  104 		if ownerType == AttributesOwnerCodeBlockChroma && chromaHightlightProcessingAttributes[nameLower] {
  105 			attr := Attribute{Name: string(v.Name), Value: vv}
  106 			opts = append(opts, attr)
  107 		} else {
  108 			attr := Attribute{Name: nameLower, Value: vv}
  109 			attrs = append(attrs, attr)
  110 		}
  111 
  112 	}
  113 
  114 	return &AttributesHolder{
  115 		attributes: attrs,
  116 		options:    opts,
  117 	}
  118 }
  119 
  120 type Attribute struct {
  121 	Name  string
  122 	Value any
  123 }
  124 
  125 func (a Attribute) ValueString() string {
  126 	return cast.ToString(a.Value)
  127 }
  128 
  129 type AttributesHolder struct {
  130 	// What we get from Goldmark.
  131 	attributes []Attribute
  132 
  133 	// Attributes considered to be an option (code blocks)
  134 	options []Attribute
  135 
  136 	// What we send to the the render hooks.
  137 	attributesMapInit sync.Once
  138 	attributesMap     map[string]any
  139 	optionsMapInit    sync.Once
  140 	optionsMap        map[string]any
  141 }
  142 
  143 type Attributes map[string]any
  144 
  145 func (a *AttributesHolder) Attributes() map[string]any {
  146 	a.attributesMapInit.Do(func() {
  147 		a.attributesMap = make(map[string]any)
  148 		for _, v := range a.attributes {
  149 			a.attributesMap[v.Name] = v.Value
  150 		}
  151 	})
  152 	return a.attributesMap
  153 }
  154 
  155 func (a *AttributesHolder) Options() map[string]any {
  156 	a.optionsMapInit.Do(func() {
  157 		a.optionsMap = make(map[string]any)
  158 		for _, v := range a.options {
  159 			a.optionsMap[v.Name] = v.Value
  160 		}
  161 	})
  162 	return a.optionsMap
  163 }
  164 
  165 func (a *AttributesHolder) AttributesSlice() []Attribute {
  166 	return a.attributes
  167 }
  168 
  169 func (a *AttributesHolder) OptionsSlice() []Attribute {
  170 	return a.options
  171 }
  172 
  173 // RenderASTAttributes writes the AST attributes to the given as attributes to an HTML element.
  174 // This is used by the default HTML renderers, e.g. for headings etc. where no hook template could be found.
  175 // This performs HTML esacaping of string attributes.
  176 func RenderASTAttributes(w hugio.FlexiWriter, attributes ...ast.Attribute) {
  177 	for _, attr := range attributes {
  178 
  179 		a := strings.ToLower(string(attr.Name))
  180 		if strings.HasPrefix(a, "on") {
  181 			continue
  182 		}
  183 
  184 		_, _ = w.WriteString(" ")
  185 		_, _ = w.Write(attr.Name)
  186 		_, _ = w.WriteString(`="`)
  187 
  188 		switch v := attr.Value.(type) {
  189 		case []byte:
  190 			_, _ = w.Write(util.EscapeHTML(v))
  191 		default:
  192 			w.WriteString(cast.ToString(v))
  193 		}
  194 
  195 		_ = w.WriteByte('"')
  196 	}
  197 }
  198 
  199 // Render writes the attributes to the given as attributes to an HTML element.
  200 // This is used for the default codeblock renderering.
  201 // This performs HTML esacaping of string attributes.
  202 func RenderAttributes(w hugio.FlexiWriter, skipClass bool, attributes ...Attribute) {
  203 	for _, attr := range attributes {
  204 		a := strings.ToLower(string(attr.Name))
  205 		if skipClass && a == "class" {
  206 			continue
  207 		}
  208 		_, _ = w.WriteString(" ")
  209 		_, _ = w.WriteString(attr.Name)
  210 		_, _ = w.WriteString(`="`)
  211 
  212 		switch v := attr.Value.(type) {
  213 		case []byte:
  214 			_, _ = w.Write(util.EscapeHTML(v))
  215 		default:
  216 			w.WriteString(cast.ToString(v))
  217 		}
  218 
  219 		_ = w.WriteByte('"')
  220 	}
  221 }