1
0
Fork 0
mirror of https://github.com/gohugoio/hugo.git synced 2025-04-14 23:35:06 +00:00

Shortcode cleanup. Added a ton of tests. Much more flexible with input. Doesn't crash with bad input. Fixed

Also added the .Get function to short codes and documentation for that function.
This commit is contained in:
spf13 2014-02-25 23:57:31 -05:00
parent dc068ccb87
commit 64572d2d60
5 changed files with 202 additions and 49 deletions

View file

@ -120,15 +120,24 @@ parameters named parameters work best.
**Inside the template**
To access a parameter by either position or name the index method can be used.
To access a parameter by position the .Get method can be used.
{{ index .Params 0 }}
or
{{ index .Params "class" }}
{{ .Get 0 }}
To check if a parameter has been provided use the isset method provided by Hugo.
To access a parameter by name the .Get method should be utilized
{{ if isset .Params "class"}} class="{{ index .Params "class"}}" {{ end }}
{{ .Get "class" }}
With is great when the output depends on a parameter being set
{{ with .Get "class"}} class="{{.}}"{{ end }}
Get can also be used to check if a parameter has been provided. This is
most helpful when the condition depends on either one value or another...
or both.
{{ or .Get "title" | .Get "alt" | if }} alt="{{ with .Get "alt"}}{{.}}{{else}}{{.Get "title"}}{{end}}"{{ end }}
If a closing shortcode is used, the variable .Inner will be populated with all
of the content between the opening and closing shortcodes. If a closing
@ -162,20 +171,19 @@ This would be rendered as
{{ % img src="/media/spf13.jpg" title="Steve Francia" %}}
Would load the template /layouts/shortcodes/img.html
<!-- image -->
<figure {{ if isset .Params "class" }}class="{{ index .Params "class" }}"{{ end }}>
{{ if isset .Params "link"}}<a href="{{ index .Params "link"}}">{{ end }}
<img src="{{ index .Params "src" }}" {{ if or (isset .Params "alt") (isset .Params "caption") }}alt="{{ if isset .Params "alt"}}{{ index .Params "alt"}}{{else}}{{ index .Params "caption" }}{{ end }}"{{ end }} />
{{ if isset .Params "link"}}</a>{{ end }}
{{ if or (or (isset .Params "title") (isset .Params "caption")) (isset .Params "attr")}}
<figure {{ with .Get "class" }}class="{{.}}"{{ end }}>
{{ with .Get "link"}}<a href="{{.}}">{{ end }}
<img src="{{ .Get "src" }}" {{ if or (.Get "alt") (.Get "caption") }}alt="{{ with .Get "alt"}}{{.}}{{else}}{{ .Get "caption" }}{{ end }}"{{ end }} />
{{ if .Get "link"}}</a>{{ end }}
{{ if or (or (.Get "title") (.Get "caption")) (.Get "attr")}}
<figcaption>{{ if isset .Params "title" }}
<h4>{{ index .Params "title" }}</h4>{{ end }}
{{ if or (isset .Params "caption") (isset .Params "attr")}}<p>
{{ index .Params "caption" }}
{{ if isset .Params "attrlink"}}<a href="{{ index .Params "attrlink"}}"> {{ end }}
{{ index .Params "attr" }}
{{ if isset .Params "attrlink"}}</a> {{ end }}
<h4>{{ .Get "title" }}</h4>{{ end }}
{{ if or (.Get "caption") (.Get "attr")}}<p>
{{ .Get "caption" }}
{{ with .Get "attrlink"}}<a href="{{.}}"> {{ end }}
{{ .Get "attr" }}
{{ if .Get "attrlink"}}</a> {{ end }}
</p> {{ end }}
</figcaption>
{{ end }}
@ -203,8 +211,7 @@ Would be rendered as:
{{% /highlight %}}
The template for this utilizes the following code (already include in hugo)
{{ $lang := index .Params 0 }}{{ highlight .Inner $lang }}
{{ .Get 0 | highlight .Inner }}
And will be rendered as:

View file

@ -18,6 +18,7 @@ import (
"fmt"
"github.com/spf13/hugo/template/bundle"
"html/template"
"reflect"
"strings"
"unicode"
)
@ -37,6 +38,45 @@ type ShortcodeWithPage struct {
Page *Page
}
func (scp *ShortcodeWithPage) Get(key interface{}) interface{} {
if reflect.ValueOf(scp.Params).Len() == 0 {
return nil
}
var x reflect.Value
switch key.(type) {
case int64, int32, int16, int8, int:
if reflect.TypeOf(scp.Params).Kind() == reflect.Map {
return "error: cannot access named params by position"
} else if reflect.TypeOf(scp.Params).Kind() == reflect.Slice {
x = reflect.ValueOf(scp.Params).Index(int(reflect.ValueOf(key).Int()))
}
case string:
if reflect.TypeOf(scp.Params).Kind() == reflect.Map {
x = reflect.ValueOf(scp.Params).MapIndex(reflect.ValueOf(key))
if !x.IsValid() {
return ""
}
} else if reflect.TypeOf(scp.Params).Kind() == reflect.Slice {
if reflect.ValueOf(scp.Params).Len() == 1 && reflect.ValueOf(scp.Params).Index(0).String() == "" {
return nil
}
return "error: cannot access positional params by string name"
}
}
switch x.Kind() {
case reflect.String:
return x.String()
case reflect.Int64, reflect.Int32, reflect.Int16, reflect.Int8, reflect.Int:
return x.Int()
default:
return x
}
}
type Shortcodes map[string]ShortcodeFunc
func ShortcodesHandle(stringToParse string, p *Page, t bundle.Template) string {
@ -127,12 +167,44 @@ func StripShortcodes(stringToParse string) string {
return stringToParse
}
func CleanupSpacesAroundEquals(rawfirst []string) []string {
var first = make([]string, 0)
for i := 0; i < len(rawfirst); i++ {
v := rawfirst[i]
index := strings.Index(v, "=")
if index == len(v)-1 {
// Trailing '='
if len(rawfirst) > i {
if v == "=" {
first[len(first)-1] = first[len(first)-1] + v + rawfirst[i+1] // concat prior with this and next
i++ // Skip next
} else {
// Trailing ' = '
first = append(first, v+rawfirst[i+1]) // append this token and the next
i++ // Skip next
}
} else {
break
}
} else if index == 0 {
// Leading '='
first[len(first)-1] = first[len(first)-1] + v // concat this token to the prior one
continue
} else {
first = append(first, v)
}
}
return first
}
func Tokenize(in string) interface{} {
first := strings.Fields(in)
var final = make([]string, 0)
// if don't need to parse, don't parse.
if strings.Index(in, " ") < 0 && strings.Index(in, "=") < 1 {
// if there isn't a space or an equal sign, no need to parse
if strings.Index(in, " ") < 0 && strings.Index(in, "=") < 0 {
return append(final, in)
}
@ -140,14 +212,10 @@ func Tokenize(in string) interface{} {
inQuote := false
start := 0
first := CleanupSpacesAroundEquals(strings.Fields(in))
for i, v := range first {
index := strings.Index(v, "=")
if index < 0 {
fmt.Printf("Shortcode parameters must be key=value pairs (no spaces) (saw '%s')\n", v)
continue
}
if !inQuote {
if index > 1 {
keys = append(keys, v[:index])
@ -198,7 +266,8 @@ func Tokenize(in string) interface{} {
}
if len(keys) > 0 && (len(keys) != len(final)) {
panic("keys and final different lengths")
// This will happen if the quotes aren't balanced
return final
}
if len(keys) > 0 {
@ -214,12 +283,13 @@ func Tokenize(in string) interface{} {
}
func SplitParams(in string) (name string, par2 string) {
i := strings.IndexFunc(strings.TrimSpace(in), unicode.IsSpace)
newIn := strings.TrimSpace(in)
i := strings.IndexFunc(newIn, unicode.IsSpace)
if i < 1 {
return strings.TrimSpace(in), ""
}
return strings.TrimSpace(in[:i+1]), strings.TrimSpace(in[i+1:])
return strings.TrimSpace(newIn[:i+1]), strings.TrimSpace(newIn[i+1:])
}
func ShortcodeRender(tmpl *template.Template, data *ShortcodeWithPage) string {
@ -227,6 +297,7 @@ func ShortcodeRender(tmpl *template.Template, data *ShortcodeWithPage) string {
err := tmpl.Execute(buffer, data)
if err != nil {
fmt.Println("error processing shortcode", tmpl.Name(), "\n ERR:", err)
fmt.Println(data)
}
return buffer.String()
}

72
hugolib/shortcode_test.go Normal file
View file

@ -0,0 +1,72 @@
package hugolib
import (
"github.com/spf13/hugo/template/bundle"
"strings"
"testing"
)
func pageFromString(in, filename string) (*Page, error) {
return ReadFrom(strings.NewReader(in), filename)
}
func CheckShortCodeMatch(t *testing.T, input, expected string, template bundle.Template) {
p, _ := pageFromString(SIMPLE_PAGE, "simple.md")
output := ShortcodesHandle(input, p, template)
if output != expected {
t.Fatalf("Shortcode render didn't match. Expected: %q, Got: %q", expected, output)
}
}
func TestNonSC(t *testing.T) {
tem := bundle.NewTemplate()
CheckShortCodeMatch(t, "{{% movie 47238zzb %}}", "{{% movie 47238zzb %}}", tem)
}
func TestPositionalParamSC(t *testing.T) {
tem := bundle.NewTemplate()
tem.AddInternalShortcode("video.html", `Playing Video {{ .Get 0 }}`)
CheckShortCodeMatch(t, "{{% video 47238zzb %}}", "Playing Video 47238zzb", tem)
CheckShortCodeMatch(t, "{{% video 47238zzb 132 %}}", "Playing Video 47238zzb", tem)
CheckShortCodeMatch(t, "{{%video 47238zzb%}}", "Playing Video 47238zzb", tem)
CheckShortCodeMatch(t, "{{%video 47238zzb %}}", "Playing Video 47238zzb", tem)
CheckShortCodeMatch(t, "{{% video 47238zzb %}}", "Playing Video 47238zzb", tem)
}
func TestNamedParamSC(t *testing.T) {
tem := bundle.NewTemplate()
tem.AddInternalShortcode("img.html", `<img{{ with .Get "src" }} src="{{.}}"{{end}}{{with .Get "class"}} class="{{.}}"{{end}}>`)
CheckShortCodeMatch(t, `{{% img src="one" %}}`, `<img src="one">`, tem)
CheckShortCodeMatch(t, `{{% img class="aspen" %}}`, `<img class="aspen">`, tem)
CheckShortCodeMatch(t, `{{% img src= "one" %}}`, `<img src="one">`, tem)
CheckShortCodeMatch(t, `{{% img src ="one" %}}`, `<img src="one">`, tem)
CheckShortCodeMatch(t, `{{% img src = "one" %}}`, `<img src="one">`, tem)
CheckShortCodeMatch(t, `{{% img src = "one" class = "aspen grove" %}}`, `<img src="one" class="aspen grove">`, tem)
}
func TestInnerSC(t *testing.T) {
tem := bundle.NewTemplate()
tem.AddInternalShortcode("inside.html", `<div{{with .Get "class"}} class="{{.}}"{{end}}>{{ .Inner }}</div>`)
CheckShortCodeMatch(t, `{{% inside class="aspen" %}}`, `<div class="aspen"></div>`, tem)
CheckShortCodeMatch(t, `{{% inside class="aspen" %}}More Here{{% /inside %}}`, `<div class="aspen">More Here</div>`, tem)
CheckShortCodeMatch(t, `{{% inside %}}More Here{{% /inside %}}`, `<div>More Here</div>`, tem)
}
func TestEmbeddedSC(t *testing.T) {
tem := bundle.NewTemplate()
CheckShortCodeMatch(t, "{{% test %}}", "This is a simple Test", tem)
CheckShortCodeMatch(t, `{{% figure src="/found/here" class="bananas orange" %}}`, "\n<figure class=\"bananas orange\">\n \n <img src=\"/found/here\" />\n \n \n</figure>\n", tem)
CheckShortCodeMatch(t, `{{% figure src="/found/here" class="bananas orange" caption="This is a caption" %}}`, "\n<figure class=\"bananas orange\">\n \n <img src=\"/found/here\" alt=\"This is a caption\" />\n \n \n <figcaption>\n <p>\n This is a caption\n \n \n \n </p> \n </figcaption>\n \n</figure>\n", tem)
}
func TestUnbalancedQuotes(t *testing.T) {
tem := bundle.NewTemplate()
CheckShortCodeMatch(t, `{{% figure src="/uploads/2011/12/spf13-mongosv-speaking-copy-1024x749.jpg "Steve Francia speaking at OSCON 2012" alt="MongoSV 2011" %}}`, "\n<figure >\n \n <img src=\"/uploads/2011/12/spf13-mongosv-speaking-copy-1024x749.jpg%20%22Steve%20Francia%20speaking%20at%20OSCON%202012\" alt=\"MongoSV 2011\" />\n \n \n</figure>\n", tem)
}

View file

@ -19,27 +19,24 @@ type Tmpl struct {
}
func (t *GoHtmlTemplate) EmbedShortcodes() {
const k = "shortcodes"
t.AddInternalTemplate(k, "highlight.html", `{{ $lang := index .Params 0 }}{{ highlight .Inner $lang }}`)
t.AddInternalTemplate(k, "test.html", `This is a simple Test`)
t.AddInternalTemplate(k, "figure.html", `<!-- image -->
<figure {{ if isset .Params "class" }}class="{{ index .Params "class" }}"{{ end }}>
{{ if isset .Params "link"}}<a href="{{ index .Params "link"}}">{{ end }}
<img src="{{ index .Params "src" }}" {{ if or (isset .Params "alt") (isset .Params "caption") }}alt="{{ if isset .Params "alt"}}{{ index .Params "alt"}}{{else}}{{ index .Params "caption" }}{{ end }}"{{ end }} />
{{ if isset .Params "link"}}</a>{{ end }}
{{ if or (or (isset .Params "title") (isset .Params "caption")) (isset .Params "attr")}}
t.AddInternalShortcode("highlight.html", `{{ .Get 0 | highlight .Inner }}`)
t.AddInternalShortcode("test.html", `This is a simple Test`)
t.AddInternalShortcode("figure.html", `<!-- image -->
<figure {{ with .Get "class" }}class="{{.}}"{{ end }}>
{{ with .Get "link"}}<a href="{{.}}">{{ end }}
<img src="{{ .Get "src" }}" {{ if or (.Get "alt") (.Get "caption") }}alt="{{ with .Get "alt"}}{{.}}{{else}}{{ .Get "caption" }}{{ end }}"{{ end }} />
{{ if .Get "link"}}</a>{{ end }}
{{ if or (or (.Get "title") (.Get "caption")) (.Get "attr")}}
<figcaption>{{ if isset .Params "title" }}
<h4>{{ index .Params "title" }}</h4>{{ end }}
{{ if or (isset .Params "caption") (isset .Params "attr")}}<p>
{{ index .Params "caption" }}
{{ if isset .Params "attrlink"}}<a href="{{ index .Params "attrlink"}}"> {{ end }}
{{ index .Params "attr" }}
{{ if isset .Params "attrlink"}}</a> {{ end }}
<h4>{{ .Get "title" }}</h4>{{ end }}
{{ if or (.Get "caption") (.Get "attr")}}<p>
{{ .Get "caption" }}
{{ with .Get "attrlink"}}<a href="{{.}}"> {{ end }}
{{ .Get "attr" }}
{{ if .Get "attrlink"}}</a> {{ end }}
</p> {{ end }}
</figcaption>
{{ end }}
</figure>
<!-- image -->`)
}

View file

@ -138,6 +138,8 @@ type Template interface {
New(name string) *template.Template
LoadTemplates(absPath string)
AddTemplate(name, tpl string) error
AddInternalTemplate(prefix, name, tpl string) error
AddInternalShortcode(name, tpl string) error
}
type templateErr struct {
@ -189,6 +191,10 @@ func (t *GoHtmlTemplate) AddInternalTemplate(prefix, name, tpl string) error {
return t.AddTemplate("_internal/"+prefix+"/"+name, tpl)
}
func (t *GoHtmlTemplate) AddInternalShortcode(name, content string) error {
return t.AddInternalTemplate("shortcodes", name, content)
}
func (t *GoHtmlTemplate) AddTemplate(name, tpl string) error {
_, err := t.New(name).Parse(tpl)
if err != nil {