aboutsummaryrefslogtreecommitdiffhomepage
path: root/markup/internal
diff options
context:
space:
mode:
authorBjørn Erik Pedersen <[email protected]>2022-02-17 13:04:00 +0100
committerBjørn Erik Pedersen <[email protected]>2022-02-24 18:59:50 +0100
commit08fdca9d9365eaf1e496a12e2af5e18617bd0e66 (patch)
tree6c6942d1b74a4160d93a997860bafd52b92025f5 /markup/internal
parent2c20f5bc00b604e72b3b7e401fbdbf9447fe3470 (diff)
downloadhugo-08fdca9d9365eaf1e496a12e2af5e18617bd0e66.tar.gz
hugo-08fdca9d9365eaf1e496a12e2af5e18617bd0e66.zip
Add Markdown diagrams and render hooks for code blocks
You can now create custom hook templates for code blocks, either one for all (`render-codeblock.html`) or for a given code language (e.g. `render-codeblock-go.html`). We also used this new hook to add support for diagrams in Hugo: * Goat (Go ASCII Tool) is built-in and enabled by default; just create a fenced code block with the language `goat` and start draw your Ascii diagrams. * Another popular alternative for diagrams in Markdown, Mermaid (supported by GitHub), can also be implemented with a simple template. See the Hugo documentation for more information. Updates #7765 Closes #9538 Fixes #9553 Fixes #8520 Fixes #6702 Fixes #9558
Diffstat (limited to 'markup/internal')
-rw-r--r--markup/internal/attributes/attributes.go219
1 files changed, 219 insertions, 0 deletions
diff --git a/markup/internal/attributes/attributes.go b/markup/internal/attributes/attributes.go
new file mode 100644
index 000000000..1cce7edd1
--- /dev/null
+++ b/markup/internal/attributes/attributes.go
@@ -0,0 +1,219 @@
+// Copyright 2022 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 attributes
+
+import (
+ "fmt"
+ "strconv"
+ "strings"
+ "sync"
+
+ "github.com/gohugoio/hugo/common/hugio"
+ "github.com/spf13/cast"
+ "github.com/yuin/goldmark/ast"
+ "github.com/yuin/goldmark/util"
+)
+
+// Markdown attributes used as options by the Chroma highlighter.
+var chromaHightlightProcessingAttributes = map[string]bool{
+ "anchorLineNos": true,
+ "guessSyntax": true,
+ "hl_Lines": true,
+ "lineAnchors": true,
+ "lineNos": true,
+ "lineNoStart": true,
+ "lineNumbersInTable": true,
+ "noClasses": true,
+ "nohl": true,
+ "style": true,
+ "tabWidth": true,
+}
+
+func init() {
+ for k, v := range chromaHightlightProcessingAttributes {
+ chromaHightlightProcessingAttributes[strings.ToLower(k)] = v
+ }
+}
+
+type AttributesOwnerType int
+
+const (
+ AttributesOwnerGeneral AttributesOwnerType = iota
+ AttributesOwnerCodeBlock
+)
+
+func New(astAttributes []ast.Attribute, ownerType AttributesOwnerType) *AttributesHolder {
+ var (
+ attrs []Attribute
+ opts []Attribute
+ )
+ for _, v := range astAttributes {
+ nameLower := strings.ToLower(string(v.Name))
+ if strings.HasPrefix(string(nameLower), "on") {
+ continue
+ }
+ var vv interface{}
+ switch vvv := v.Value.(type) {
+ case bool, float64:
+ vv = vvv
+ case []interface{}:
+ // Highlight line number hlRanges.
+ var hlRanges [][2]int
+ for _, l := range vvv {
+ if ln, ok := l.(float64); ok {
+ hlRanges = append(hlRanges, [2]int{int(ln) - 1, int(ln) - 1})
+ } else if rng, ok := l.([]uint8); ok {
+ slices := strings.Split(string([]byte(rng)), "-")
+ lhs, err := strconv.Atoi(slices[0])
+ if err != nil {
+ continue
+ }
+ rhs := lhs
+ if len(slices) > 1 {
+ rhs, err = strconv.Atoi(slices[1])
+ if err != nil {
+ continue
+ }
+ }
+ hlRanges = append(hlRanges, [2]int{lhs - 1, rhs - 1})
+ }
+ }
+ vv = hlRanges
+ case []byte:
+ // Note that we don't do any HTML escaping here.
+ // We used to do that, but that changed in #9558.
+ // Noww it's up to the templates to decide.
+ vv = string(vvv)
+ default:
+ panic(fmt.Sprintf("not implemented: %T", vvv))
+ }
+
+ if ownerType == AttributesOwnerCodeBlock && chromaHightlightProcessingAttributes[nameLower] {
+ attr := Attribute{Name: string(v.Name), Value: vv}
+ opts = append(opts, attr)
+ } else {
+ attr := Attribute{Name: nameLower, Value: vv}
+ attrs = append(attrs, attr)
+ }
+
+ }
+
+ return &AttributesHolder{
+ attributes: attrs,
+ options: opts,
+ }
+}
+
+type Attribute struct {
+ Name string
+ Value interface{}
+}
+
+func (a Attribute) ValueString() string {
+ return cast.ToString(a.Value)
+}
+
+type AttributesHolder struct {
+ // What we get from Goldmark.
+ attributes []Attribute
+
+ // Attributes considered to be an option (code blocks)
+ options []Attribute
+
+ // What we send to the the render hooks.
+ attributesMapInit sync.Once
+ attributesMap map[string]interface{}
+ optionsMapInit sync.Once
+ optionsMap map[string]interface{}
+}
+
+type Attributes map[string]interface{}
+
+func (a *AttributesHolder) Attributes() map[string]interface{} {
+ a.attributesMapInit.Do(func() {
+ a.attributesMap = make(map[string]interface{})
+ for _, v := range a.attributes {
+ a.attributesMap[v.Name] = v.Value
+ }
+ })
+ return a.attributesMap
+}
+
+func (a *AttributesHolder) Options() map[string]interface{} {
+ a.optionsMapInit.Do(func() {
+ a.optionsMap = make(map[string]interface{})
+ for _, v := range a.options {
+ a.optionsMap[v.Name] = v.Value
+ }
+ })
+ return a.optionsMap
+}
+
+func (a *AttributesHolder) AttributesSlice() []Attribute {
+ return a.attributes
+}
+
+func (a *AttributesHolder) OptionsSlice() []Attribute {
+ return a.options
+}
+
+// RenderASTAttributes writes the AST attributes to the given as attributes to an HTML element.
+// This is used by the default HTML renderers, e.g. for headings etc. where no hook template could be found.
+// This performs HTML esacaping of string attributes.
+func RenderASTAttributes(w hugio.FlexiWriter, attributes ...ast.Attribute) {
+ for _, attr := range attributes {
+
+ a := strings.ToLower(string(attr.Name))
+ if strings.HasPrefix(a, "on") {
+ continue
+ }
+
+ _, _ = w.WriteString(" ")
+ _, _ = w.Write(attr.Name)
+ _, _ = w.WriteString(`="`)
+
+ switch v := attr.Value.(type) {
+ case []byte:
+ _, _ = w.Write(util.EscapeHTML(v))
+ default:
+ w.WriteString(cast.ToString(v))
+ }
+
+ _ = w.WriteByte('"')
+ }
+}
+
+// Render writes the attributes to the given as attributes to an HTML element.
+// This is used for the default codeblock renderering.
+// This performs HTML esacaping of string attributes.
+func RenderAttributes(w hugio.FlexiWriter, skipClass bool, attributes ...Attribute) {
+ for _, attr := range attributes {
+ a := strings.ToLower(string(attr.Name))
+ if skipClass && a == "class" {
+ continue
+ }
+ _, _ = w.WriteString(" ")
+ _, _ = w.WriteString(attr.Name)
+ _, _ = w.WriteString(`="`)
+
+ switch v := attr.Value.(type) {
+ case []byte:
+ _, _ = w.Write(util.EscapeHTML(v))
+ default:
+ w.WriteString(cast.ToString(v))
+ }
+
+ _ = w.WriteByte('"')
+ }
+}