Adding some embedded short codes (including code highlighting)

This commit is contained in:
spf13 2014-01-09 17:33:20 -05:00
parent 13b067b506
commit 3fd6c1a24e
3 changed files with 201 additions and 149 deletions

View file

@ -14,12 +14,12 @@
package hugolib package hugolib
import ( import (
"bytes" "bytes"
"fmt" "fmt"
"github.com/spf13/hugo/template/bundle" "github.com/spf13/hugo/template/bundle"
"html/template" "html/template"
"strings" "strings"
"unicode" "unicode"
) )
var _ = fmt.Println var _ = fmt.Println
@ -27,195 +27,201 @@ var _ = fmt.Println
type ShortcodeFunc func([]string) string type ShortcodeFunc func([]string) string
type Shortcode struct { type Shortcode struct {
Name string Name string
Func ShortcodeFunc Func ShortcodeFunc
} }
type ShortcodeWithPage struct { type ShortcodeWithPage struct {
Params interface{} Params interface{}
Inner template.HTML Inner template.HTML
Page *Page Page *Page
} }
type Shortcodes map[string]ShortcodeFunc type Shortcodes map[string]ShortcodeFunc
func ShortcodesHandle(stringToParse string, p *Page, t bundle.Template) string { func ShortcodesHandle(stringToParse string, p *Page, t bundle.Template) string {
leadStart := strings.Index(stringToParse, `{{%`) leadStart := strings.Index(stringToParse, `{{%`)
if leadStart >= 0 { if leadStart >= 0 {
leadEnd := strings.Index(stringToParse[leadStart:], `%}}`) + leadStart leadEnd := strings.Index(stringToParse[leadStart:], `%}}`) + leadStart
if leadEnd > leadStart { if leadEnd > leadStart {
name, par := SplitParams(stringToParse[leadStart+3 : leadEnd]) name, par := SplitParams(stringToParse[leadStart+3 : leadEnd])
tmpl := GetTemplate(name, t) tmpl := GetTemplate(name, t)
if tmpl == nil { if tmpl == nil {
return stringToParse return stringToParse
} }
params := Tokenize(par) params := Tokenize(par)
// Always look for closing tag. // Always look for closing tag.
endStart, endEnd := FindEnd(stringToParse[leadEnd:], name) endStart, endEnd := FindEnd(stringToParse[leadEnd:], name)
var data = &ShortcodeWithPage{Params: params, Page: p} var data = &ShortcodeWithPage{Params: params, Page: p}
if endStart > 0 { if endStart > 0 {
s := stringToParse[leadEnd+3 : leadEnd+endStart] s := stringToParse[leadEnd+3 : leadEnd+endStart]
data.Inner = template.HTML(CleanP(ShortcodesHandle(s, p, t))) data.Inner = template.HTML(CleanP(ShortcodesHandle(s, p, t)))
remainder := CleanP(stringToParse[leadEnd+endEnd:]) remainder := CleanP(stringToParse[leadEnd+endEnd:])
return CleanP(stringToParse[:leadStart]) + return CleanP(stringToParse[:leadStart]) +
ShortcodeRender(tmpl, data) + ShortcodeRender(tmpl, data) +
CleanP(ShortcodesHandle(remainder, p, t)) CleanP(ShortcodesHandle(remainder, p, t))
} }
return CleanP(stringToParse[:leadStart]) + return CleanP(stringToParse[:leadStart]) +
ShortcodeRender(tmpl, data) + ShortcodeRender(tmpl, data) +
CleanP(ShortcodesHandle(stringToParse[leadEnd+3:], p, CleanP(ShortcodesHandle(stringToParse[leadEnd+3:], p,
t)) t))
} }
} }
return stringToParse return stringToParse
} }
// Clean up odd behavior when closing tag is on first line // 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 // or opening tag is on the last line due to extra line in markdown file
func CleanP(str string) string { func CleanP(str string) string {
if strings.HasSuffix(strings.TrimSpace(str), "<p>") { if strings.HasSuffix(strings.TrimSpace(str), "<p>") {
idx := strings.LastIndex(str, "<p>") idx := strings.LastIndex(str, "<p>")
str = str[:idx] str = str[:idx]
} }
if strings.HasPrefix(strings.TrimSpace(str), "</p>") { if strings.HasPrefix(strings.TrimSpace(str), "</p>") {
str = str[strings.Index(str, "</p>")+5:] str = str[strings.Index(str, "</p>")+5:]
} }
return str return str
} }
func FindEnd(str string, name string) (int, int) { func FindEnd(str string, name string) (int, int) {
var endPos int var endPos int
var startPos int var startPos int
var try []string var try []string
try = append(try, "{{% /"+name+" %}}") try = append(try, "{{% /"+name+" %}}")
try = append(try, "{{% /"+name+"%}}") try = append(try, "{{% /"+name+"%}}")
try = append(try, "{{%/"+name+"%}}") try = append(try, "{{%/"+name+"%}}")
try = append(try, "{{%/"+name+" %}}") try = append(try, "{{%/"+name+" %}}")
lowest := len(str) lowest := len(str)
for _, x := range try { for _, x := range try {
start := strings.Index(str, x) start := strings.Index(str, x)
if start < lowest && start > 0 { if start < lowest && start > 0 {
startPos = start startPos = start
endPos = startPos + len(x) endPos = startPos + len(x)
} }
} }
return startPos, endPos return startPos, endPos
} }
func GetTemplate(name string, t bundle.Template) *template.Template { func GetTemplate(name string, t bundle.Template) *template.Template {
return t.Lookup("shortcodes/" + name + ".html") if x := t.Lookup("shortcodes/" + name + ".html"); x != nil {
return x
}
return t.Lookup("_internal/shortcodes/" + name + ".html")
} }
func StripShortcodes(stringToParse string) string { func StripShortcodes(stringToParse string) string {
posStart := strings.Index(stringToParse, "{{%") posStart := strings.Index(stringToParse, "{{%")
if posStart > 0 { if posStart > 0 {
posEnd := strings.Index(stringToParse[posStart:], "%}}") + posStart posEnd := strings.Index(stringToParse[posStart:], "%}}") + posStart
if posEnd > posStart { if posEnd > posStart {
newString := stringToParse[:posStart] + StripShortcodes(stringToParse[posEnd+3:]) newString := stringToParse[:posStart] + StripShortcodes(stringToParse[posEnd+3:])
return newString return newString
} }
} }
return stringToParse return stringToParse
} }
func Tokenize(in string) interface{} { func Tokenize(in string) interface{} {
first := strings.Fields(in) first := strings.Fields(in)
var final = make([]string, 0) var final = make([]string, 0)
// if don't need to parse, don't parse. // if don't need to parse, don't parse.
if strings.Index(in, " ") < 0 && strings.Index(in, "=") < 1 { if strings.Index(in, " ") < 0 && strings.Index(in, "=") < 1 {
return append(final, in) return append(final, in)
} }
var keys = make([]string, 0) var keys = make([]string, 0)
inQuote := false inQuote := false
start := 0 start := 0
for i, v := range first { for i, v := range first {
index := strings.Index(v, "=") index := strings.Index(v, "=")
if !inQuote { if !inQuote {
if index > 1 { if index > 1 {
keys = append(keys, v[:index]) keys = append(keys, v[:index])
v = v[index+1:] v = v[index+1:]
} }
} }
// Adjusted to handle htmlencoded and non htmlencoded input // Adjusted to handle htmlencoded and non htmlencoded input
if !strings.HasPrefix(v, "&ldquo;") && !strings.HasPrefix(v, "\"") && !inQuote { if !strings.HasPrefix(v, "&ldquo;") && !strings.HasPrefix(v, "\"") && !inQuote {
final = append(final, v) final = append(final, v)
} else if inQuote && (strings.HasSuffix(v, "&rdquo;") || } else if inQuote && (strings.HasSuffix(v, "&rdquo;") ||
strings.HasSuffix(v, "\"")) && !strings.HasSuffix(v, "\\\"") { strings.HasSuffix(v, "\"")) && !strings.HasSuffix(v, "\\\"") {
if strings.HasSuffix(v, "\"") { if strings.HasSuffix(v, "\"") {
first[i] = v[:len(v)-1] first[i] = v[:len(v)-1]
} else { } else {
first[i] = v[:len(v)-7] first[i] = v[:len(v)-7]
} }
final = append(final, strings.Join(first[start:i+1], " ")) final = append(final, strings.Join(first[start:i+1], " "))
inQuote = false inQuote = false
} else if (strings.HasPrefix(v, "&ldquo;") || } else if (strings.HasPrefix(v, "&ldquo;") ||
strings.HasPrefix(v, "\"")) && !inQuote { strings.HasPrefix(v, "\"")) && !inQuote {
if strings.HasSuffix(v, "&rdquo;") || strings.HasSuffix(v, if strings.HasSuffix(v, "&rdquo;") || strings.HasSuffix(v,
"\"") { "\"") {
if strings.HasSuffix(v, "\"") { if strings.HasSuffix(v, "\"") {
if len(v) > 1 { if len(v) > 1 {
final = append(final, v[1:len(v)-1]) final = append(final, v[1:len(v)-1])
} else { } else {
final = append(final, "") final = append(final, "")
} }
} else { } else {
final = append(final, v[7:len(v)-7]) final = append(final, v[7:len(v)-7])
} }
} else { } else {
start = i start = i
if strings.HasPrefix(v, "\"") { if strings.HasPrefix(v, "\"") {
first[i] = v[1:] first[i] = v[1:]
} else { } else {
first[i] = v[7:] first[i] = v[7:]
} }
inQuote = true inQuote = true
} }
} }
// No closing "... just make remainder the final token // No closing "... just make remainder the final token
if inQuote && i == len(first) { if inQuote && i == len(first) {
final = append(final, first[start:]...) final = append(final, first[start:]...)
} }
} }
if len(keys) > 0 && (len(keys) != len(final)) { if len(keys) > 0 && (len(keys) != len(final)) {
panic("keys and final different lengths") panic("keys and final different lengths")
} }
if len(keys) > 0 { if len(keys) > 0 {
var m = make(map[string]string) var m = make(map[string]string)
for i, k := range keys { for i, k := range keys {
m[k] = final[i] m[k] = final[i]
} }
return m return m
} }
return final return final
} }
func SplitParams(in string) (name string, par2 string) { func SplitParams(in string) (name string, par2 string) {
i := strings.IndexFunc(strings.TrimSpace(in), unicode.IsSpace) i := strings.IndexFunc(strings.TrimSpace(in), unicode.IsSpace)
if i < 1 { if i < 1 {
return strings.TrimSpace(in), "" return strings.TrimSpace(in), ""
} }
return strings.TrimSpace(in[:i+1]), strings.TrimSpace(in[i+1:]) return strings.TrimSpace(in[:i+1]), strings.TrimSpace(in[i+1:])
} }
func ShortcodeRender(tmpl *template.Template, data *ShortcodeWithPage) string { func ShortcodeRender(tmpl *template.Template, data *ShortcodeWithPage) string {
buffer := new(bytes.Buffer) buffer := new(bytes.Buffer)
tmpl.Execute(buffer, data) err := tmpl.Execute(buffer, data)
return buffer.String() if err != nil {
fmt.Println("error processing shortcode", tmpl.Name(), "\n ERR:", err)
}
return buffer.String()
} }

View file

@ -0,0 +1,45 @@
// 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 bundle
type Tmpl struct {
Name string
Data string
}
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")}}
<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 }}
</p> {{ end }}
</figcaption>
{{ end }}
</figure>
<!-- image -->`)
}

View file

@ -173,6 +173,7 @@ func NewTemplate() Template {
} }
func (t *GoHtmlTemplate) LoadEmbedded() { func (t *GoHtmlTemplate) LoadEmbedded() {
t.EmbedShortcodes()
} }
func (t *GoHtmlTemplate) AddInternalTemplate(prefix, name, tpl string) error { func (t *GoHtmlTemplate) AddInternalTemplate(prefix, name, tpl string) error {