2013-07-04 15:32:55 +00:00
|
|
|
// Copyright © 2013 Steve Francia <spf@spf13.com>.
|
|
|
|
//
|
|
|
|
// Licensed under the Simple Public 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://opensource.org/licenses/Simple-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 hugolib
|
|
|
|
|
|
|
|
import (
|
2014-01-29 22:50:31 +00:00
|
|
|
"bytes"
|
|
|
|
"html/template"
|
2014-02-26 04:57:31 +00:00
|
|
|
"reflect"
|
2014-01-29 22:50:31 +00:00
|
|
|
"strings"
|
|
|
|
"unicode"
|
2013-07-04 15:32:55 +00:00
|
|
|
|
2014-03-31 17:23:34 +00:00
|
|
|
jww "github.com/spf13/jwalterweatherman"
|
|
|
|
)
|
2013-07-04 15:32:55 +00:00
|
|
|
|
|
|
|
type ShortcodeFunc func([]string) string
|
|
|
|
|
|
|
|
type Shortcode struct {
|
2014-01-29 22:50:31 +00:00
|
|
|
Name string
|
|
|
|
Func ShortcodeFunc
|
2013-07-04 15:32:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type ShortcodeWithPage struct {
|
2014-01-29 22:50:31 +00:00
|
|
|
Params interface{}
|
|
|
|
Inner template.HTML
|
|
|
|
Page *Page
|
2013-07-04 15:32:55 +00:00
|
|
|
}
|
|
|
|
|
2014-02-26 04:57:31 +00:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2013-07-04 15:32:55 +00:00
|
|
|
type Shortcodes map[string]ShortcodeFunc
|
|
|
|
|
2014-04-23 06:52:01 +00:00
|
|
|
func ShortcodesHandle(stringToParse string, p *Page, t Template) string {
|
2014-01-29 22:50:31 +00:00
|
|
|
leadStart := strings.Index(stringToParse, `{{%`)
|
|
|
|
if leadStart >= 0 {
|
|
|
|
leadEnd := strings.Index(stringToParse[leadStart:], `%}}`) + leadStart
|
|
|
|
if leadEnd > leadStart {
|
|
|
|
name, par := SplitParams(stringToParse[leadStart+3 : leadEnd])
|
|
|
|
tmpl := GetTemplate(name, t)
|
|
|
|
if tmpl == nil {
|
|
|
|
return stringToParse
|
|
|
|
}
|
|
|
|
params := Tokenize(par)
|
|
|
|
// Always look for closing tag.
|
|
|
|
endStart, endEnd := FindEnd(stringToParse[leadEnd:], name)
|
|
|
|
var data = &ShortcodeWithPage{Params: params, Page: p}
|
|
|
|
if endStart > 0 {
|
|
|
|
s := stringToParse[leadEnd+3 : leadEnd+endStart]
|
2014-08-28 16:48:31 +00:00
|
|
|
data.Inner = template.HTML(renderBytes([]byte(CleanP(ShortcodesHandle(s, p, t))), p.guessMarkupType()))
|
2014-01-29 22:50:31 +00:00
|
|
|
remainder := CleanP(stringToParse[leadEnd+endEnd:])
|
|
|
|
|
|
|
|
return CleanP(stringToParse[:leadStart]) +
|
|
|
|
ShortcodeRender(tmpl, data) +
|
|
|
|
CleanP(ShortcodesHandle(remainder, p, t))
|
|
|
|
}
|
|
|
|
return CleanP(stringToParse[:leadStart]) +
|
|
|
|
ShortcodeRender(tmpl, data) +
|
|
|
|
CleanP(ShortcodesHandle(stringToParse[leadEnd+3:], p,
|
|
|
|
t))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return stringToParse
|
2013-07-04 15:32:55 +00:00
|
|
|
}
|
|
|
|
|
2013-12-07 04:14:54 +00:00
|
|
|
// Clean up odd behavior when closing tag is on first line
|
|
|
|
// or opening tag is on the last line due to extra line in markdown file
|
|
|
|
func CleanP(str string) string {
|
2014-01-29 22:50:31 +00:00
|
|
|
if strings.HasSuffix(strings.TrimSpace(str), "<p>") {
|
|
|
|
idx := strings.LastIndex(str, "<p>")
|
|
|
|
str = str[:idx]
|
|
|
|
}
|
2013-12-07 04:14:54 +00:00
|
|
|
|
2014-01-29 22:50:31 +00:00
|
|
|
if strings.HasPrefix(strings.TrimSpace(str), "</p>") {
|
|
|
|
str = str[strings.Index(str, "</p>")+5:]
|
|
|
|
}
|
2013-12-07 04:14:54 +00:00
|
|
|
|
2014-01-29 22:50:31 +00:00
|
|
|
return str
|
2013-12-07 04:14:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func FindEnd(str string, name string) (int, int) {
|
2014-01-29 22:50:31 +00:00
|
|
|
var endPos int
|
|
|
|
var startPos int
|
|
|
|
var try []string
|
|
|
|
|
|
|
|
try = append(try, "{{% /"+name+" %}}")
|
|
|
|
try = append(try, "{{% /"+name+"%}}")
|
|
|
|
try = append(try, "{{%/"+name+"%}}")
|
|
|
|
try = append(try, "{{%/"+name+" %}}")
|
|
|
|
|
|
|
|
lowest := len(str)
|
|
|
|
for _, x := range try {
|
|
|
|
start := strings.Index(str, x)
|
|
|
|
if start < lowest && start > 0 {
|
|
|
|
startPos = start
|
|
|
|
endPos = startPos + len(x)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return startPos, endPos
|
2013-12-07 04:14:54 +00:00
|
|
|
}
|
|
|
|
|
2014-04-23 06:52:01 +00:00
|
|
|
func GetTemplate(name string, t Template) *template.Template {
|
2014-01-29 22:50:31 +00:00
|
|
|
if x := t.Lookup("shortcodes/" + name + ".html"); x != nil {
|
|
|
|
return x
|
|
|
|
}
|
2014-06-26 20:47:51 +00:00
|
|
|
if x := t.Lookup("theme/shortcodes/" + name + ".html"); x != nil {
|
|
|
|
return x
|
|
|
|
}
|
2014-01-29 22:50:31 +00:00
|
|
|
return t.Lookup("_internal/shortcodes/" + name + ".html")
|
2013-12-07 04:14:54 +00:00
|
|
|
}
|
|
|
|
|
2013-07-04 15:32:55 +00:00
|
|
|
func StripShortcodes(stringToParse string) string {
|
2014-01-29 22:50:31 +00:00
|
|
|
posStart := strings.Index(stringToParse, "{{%")
|
|
|
|
if posStart > 0 {
|
|
|
|
posEnd := strings.Index(stringToParse[posStart:], "%}}") + posStart
|
|
|
|
if posEnd > posStart {
|
|
|
|
newString := stringToParse[:posStart] + StripShortcodes(stringToParse[posEnd+3:])
|
|
|
|
return newString
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return stringToParse
|
2013-07-04 15:32:55 +00:00
|
|
|
}
|
|
|
|
|
2014-02-26 04:57:31 +00:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2013-07-04 15:32:55 +00:00
|
|
|
func Tokenize(in string) interface{} {
|
2014-01-29 22:50:31 +00:00
|
|
|
var final = make([]string, 0)
|
|
|
|
|
2014-02-26 04:57:31 +00:00
|
|
|
// if there isn't a space or an equal sign, no need to parse
|
|
|
|
if strings.Index(in, " ") < 0 && strings.Index(in, "=") < 0 {
|
2014-01-29 22:50:31 +00:00
|
|
|
return append(final, in)
|
|
|
|
}
|
|
|
|
|
|
|
|
var keys = make([]string, 0)
|
|
|
|
inQuote := false
|
|
|
|
start := 0
|
|
|
|
|
2014-02-26 04:57:31 +00:00
|
|
|
first := CleanupSpacesAroundEquals(strings.Fields(in))
|
|
|
|
|
2014-01-29 22:50:31 +00:00
|
|
|
for i, v := range first {
|
|
|
|
index := strings.Index(v, "=")
|
|
|
|
if !inQuote {
|
|
|
|
if index > 1 {
|
|
|
|
keys = append(keys, v[:index])
|
|
|
|
v = v[index+1:]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Adjusted to handle htmlencoded and non htmlencoded input
|
|
|
|
if !strings.HasPrefix(v, "“") && !strings.HasPrefix(v, "\"") && !inQuote {
|
|
|
|
final = append(final, v)
|
|
|
|
} else if inQuote && (strings.HasSuffix(v, "”") ||
|
|
|
|
strings.HasSuffix(v, "\"")) && !strings.HasSuffix(v, "\\\"") {
|
|
|
|
if strings.HasSuffix(v, "\"") {
|
|
|
|
first[i] = v[:len(v)-1]
|
|
|
|
} else {
|
|
|
|
first[i] = v[:len(v)-7]
|
|
|
|
}
|
|
|
|
final = append(final, strings.Join(first[start:i+1], " "))
|
|
|
|
inQuote = false
|
|
|
|
} else if (strings.HasPrefix(v, "“") ||
|
|
|
|
strings.HasPrefix(v, "\"")) && !inQuote {
|
|
|
|
if strings.HasSuffix(v, "”") || strings.HasSuffix(v,
|
|
|
|
"\"") {
|
|
|
|
if strings.HasSuffix(v, "\"") {
|
|
|
|
if len(v) > 1 {
|
|
|
|
final = append(final, v[1:len(v)-1])
|
|
|
|
} else {
|
|
|
|
final = append(final, "")
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
final = append(final, v[7:len(v)-7])
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
start = i
|
|
|
|
if strings.HasPrefix(v, "\"") {
|
|
|
|
first[i] = v[1:]
|
|
|
|
} else {
|
|
|
|
first[i] = v[7:]
|
|
|
|
}
|
|
|
|
inQuote = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// No closing "... just make remainder the final token
|
|
|
|
if inQuote && i == len(first) {
|
|
|
|
final = append(final, first[start:]...)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(keys) > 0 && (len(keys) != len(final)) {
|
2014-02-26 04:57:31 +00:00
|
|
|
// This will happen if the quotes aren't balanced
|
|
|
|
return final
|
2014-01-29 22:50:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if len(keys) > 0 {
|
|
|
|
var m = make(map[string]string)
|
|
|
|
for i, k := range keys {
|
|
|
|
m[k] = final[i]
|
|
|
|
}
|
|
|
|
|
|
|
|
return m
|
|
|
|
}
|
|
|
|
|
|
|
|
return final
|
2013-07-04 15:32:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func SplitParams(in string) (name string, par2 string) {
|
2014-02-26 04:57:31 +00:00
|
|
|
newIn := strings.TrimSpace(in)
|
|
|
|
i := strings.IndexFunc(newIn, unicode.IsSpace)
|
2014-01-29 22:50:31 +00:00
|
|
|
if i < 1 {
|
|
|
|
return strings.TrimSpace(in), ""
|
|
|
|
}
|
2013-07-04 15:32:55 +00:00
|
|
|
|
2014-02-26 04:57:31 +00:00
|
|
|
return strings.TrimSpace(newIn[:i+1]), strings.TrimSpace(newIn[i+1:])
|
2013-07-04 15:32:55 +00:00
|
|
|
}
|
|
|
|
|
2013-12-07 04:14:54 +00:00
|
|
|
func ShortcodeRender(tmpl *template.Template, data *ShortcodeWithPage) string {
|
2014-01-29 22:50:31 +00:00
|
|
|
buffer := new(bytes.Buffer)
|
|
|
|
err := tmpl.Execute(buffer, data)
|
|
|
|
if err != nil {
|
2014-03-31 17:23:34 +00:00
|
|
|
jww.ERROR.Println("error processing shortcode", tmpl.Name(), "\n ERR:", err)
|
|
|
|
jww.WARN.Println(data)
|
2014-01-29 22:50:31 +00:00
|
|
|
}
|
|
|
|
return buffer.String()
|
2013-07-04 15:32:55 +00:00
|
|
|
}
|