blob: 97c7a07ac5589cced642f054f399a597cba8bc41 [file] [log] [blame]
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package site
import (
"fmt"
"log"
"strconv"
"strings"
"unicode/utf8"
"golang.org/x/go.dev/cmd/internal/html/template"
)
// A ShortCode is a parsed Hugo shortcode like {{% foo %}} or {{< foo >}}.
// It is Hugo's wrapping of a template call and will be replaced by actual template calls.
type ShortCode struct {
Kind string
Name string
Args []string
Keys map[string]string
Inner template.HTML
Page *Page
}
func (c *ShortCode) run() (template.HTML, error) {
return c.Page.Site.runTemplate("layouts/shortcodes/"+c.Name+".html", c)
}
func (c *ShortCode) String() string {
if c == nil {
return ""
}
return fmt.Sprintf("<code %s %v %v %q>", c.Name, c.Args, c.Keys, c.Inner)
}
func (c *ShortCode) Get(x interface{}) string {
switch x := x.(type) {
case int:
if 0 <= x && x < len(c.Args) {
return c.Args[x]
}
return ""
case string:
return c.Keys[x]
}
panic(fmt.Sprintf("bad Get %v", x))
}
func (c *ShortCode) IsNamedParams() bool {
return len(c.Keys) != 0
}
// parseCodes parses the shortcode invocations in the Hugo markdown file.
// It returns a slice containing only two types of elements: string and *ShortCode.
// The even indexes are strings and the odd indexes are *ShortCode.
// There are an odd number of elements (the slice begins and ends with a string).
func (p *Page) parseCodes(markdown string) []interface{} {
t1, c1, t2, kind1 := findCode(markdown)
t2, c2, t3, kind2 := findCode(t2)
var ret []interface{}
for c1 != nil {
c1.Kind = kind1
c1.Page = p
if c2 != nil && c2.Name == "/"+c1.Name {
c1.Inner = template.HTML(t2)
t2, c2, t3, kind2 = findCode(t3)
continue
}
ret = append(ret, t1)
ret = append(ret, c1)
t1, c1, kind1 = t2, c2, kind2
t2, c2, t3, kind2 = findCode(t3)
}
ret = append(ret, t1)
return ret
}
func findCode(text string) (before string, code *ShortCode, after string, kind string) {
end := "%}}"
kind = "%"
i := strings.Index(text, "{{%")
j := strings.Index(text, "{{<")
if i < 0 || j >= 0 && j < i {
i = j
kind = "<"
end = ">}}"
}
if i < 0 {
return text, nil, "", ""
}
j = strings.Index(text[i+3:], end)
if j < 0 {
return text, nil, "", ""
}
before, codeText, after := text[:i], text[i+3:i+3+j], text[i+3+j+3:]
codeText = strings.TrimSpace(codeText)
name, args, _ := cutAny(codeText, " \t\r\n")
if name == "" {
log.Fatalf("empty code")
}
args = strings.TrimSpace(args)
code = &ShortCode{Name: name, Keys: make(map[string]string)}
for args != "" {
k, v := "", args
if strings.HasPrefix(args, `"`) {
goto Value
}
{
i := strings.Index(args, "=")
if i < 0 {
goto Value
}
for j := 0; j < i; j++ {
if args[j] == ' ' || args[j] == '\t' {
goto Value
}
}
k, v = args[:i], args[i+1:]
}
Value:
v = strings.TrimSpace(v)
if strings.HasPrefix(v, `"`) {
j := 1
for ; ; j++ {
if j >= len(v) {
log.Fatalf("unterminated quoted string: %s", args)
}
if v[j] == '"' {
v, args = v[:j+1], v[j+1:]
break
}
if v[j] == '\\' {
j++
}
}
u, err := strconv.Unquote(v)
if err != nil {
log.Fatalf("malformed k=v: %s=%s", k, v)
}
v = u
} else {
v, args, _ = cutAny(v, " \t\r\n")
}
if k == "" {
code.Args = append(code.Args, v)
} else {
code.Keys[k] = v
}
args = strings.TrimSpace(args)
}
return
}
func cutAny(s, any string) (before, after string, ok bool) {
if i := strings.IndexAny(s, any); i >= 0 {
_, size := utf8.DecodeRuneInString(s[i:])
return s[:i], s[i+size:], true
}
return s, "", false
}