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

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

@@ -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)
}