package hugolib import ( "bytes" "errors" "html" "html/template" "io" "io/ioutil" "os" "path/filepath" "reflect" "strconv" "strings" "github.com/eknkc/amber" "github.com/spf13/hugo/helpers" jww "github.com/spf13/jwalterweatherman" ) var localTemplates *template.Template func Eq(x, y interface{}) bool { return reflect.DeepEqual(x, y) } func Ne(x, y interface{}) bool { return !Eq(x, y) } func Ge(a, b interface{}) bool { left, right := compareGetFloat(a, b) return left >= right } func Gt(a, b interface{}) bool { left, right := compareGetFloat(a, b) return left > right } func Le(a, b interface{}) bool { left, right := compareGetFloat(a, b) return left <= right } func Lt(a, b interface{}) bool { left, right := compareGetFloat(a, b) return left < right } func compareGetFloat(a interface{}, b interface{}) (float64, float64) { var left, right float64 av := reflect.ValueOf(a) switch av.Kind() { case reflect.Array, reflect.Chan, reflect.Map, reflect.Slice: left = float64(av.Len()) case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: left = float64(av.Int()) case reflect.Float32, reflect.Float64: left = av.Float() case reflect.String: left, _ = strconv.ParseFloat(av.String(), 64) } bv := reflect.ValueOf(b) switch bv.Kind() { case reflect.Array, reflect.Chan, reflect.Map, reflect.Slice: right = float64(bv.Len()) case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: right = float64(bv.Int()) case reflect.Float32, reflect.Float64: right = bv.Float() case reflect.String: right, _ = strconv.ParseFloat(bv.String(), 64) } return left, right } // First is exposed to templates, to iterate over the first N items in a // rangeable list. func First(limit int, seq interface{}) (interface{}, error) { if limit < 1 { return nil, errors.New("can't return negative/empty count of items from sequence") } seqv := reflect.ValueOf(seq) // this is better than my first pass; ripped from text/template/exec.go indirect(): for ; seqv.Kind() == reflect.Ptr || seqv.Kind() == reflect.Interface; seqv = seqv.Elem() { if seqv.IsNil() { return nil, errors.New("can't iterate over a nil value") } if seqv.Kind() == reflect.Interface && seqv.NumMethod() > 0 { break } } switch seqv.Kind() { case reflect.Array, reflect.Slice, reflect.String: // okay default: return nil, errors.New("can't iterate over " + reflect.ValueOf(seq).Type().String()) } if limit > seqv.Len() { limit = seqv.Len() } return seqv.Slice(0, limit).Interface(), nil } func Where(seq, key, match interface{}) (interface{}, error) { seqv := reflect.ValueOf(seq) kv := reflect.ValueOf(key) mv := reflect.ValueOf(match) // this is better than my first pass; ripped from text/template/exec.go indirect(): for ; seqv.Kind() == reflect.Ptr || seqv.Kind() == reflect.Interface; seqv = seqv.Elem() { if seqv.IsNil() { return nil, errors.New("can't iterate over a nil value") } if seqv.Kind() == reflect.Interface && seqv.NumMethod() > 0 { break } } switch seqv.Kind() { case reflect.Array, reflect.Slice: r := reflect.MakeSlice(seqv.Type(), 0, 0) for i := 0; i < seqv.Len(); i++ { var vvv reflect.Value vv := seqv.Index(i) switch vv.Kind() { case reflect.Map: if kv.Type() == vv.Type().Key() && vv.MapIndex(kv).IsValid() { vvv = vv.MapIndex(kv) } case reflect.Struct: if kv.Kind() == reflect.String && vv.FieldByName(kv.String()).IsValid() { vvv = vv.FieldByName(kv.String()) } case reflect.Ptr: if !vv.IsNil() { ev := vv.Elem() switch ev.Kind() { case reflect.Map: if kv.Type() == ev.Type().Key() && ev.MapIndex(kv).IsValid() { vvv = ev.MapIndex(kv) } case reflect.Struct: if kv.Kind() == reflect.String && ev.FieldByName(kv.String()).IsValid() { vvv = ev.FieldByName(kv.String()) } } } } if vvv.IsValid() && mv.Type() == vvv.Type() { switch mv.Kind() { case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: if mv.Int() == vvv.Int() { r = reflect.Append(r, vv) } case reflect.String: if mv.String() == vvv.String() { r = reflect.Append(r, vv) } } } } return r.Interface(), nil default: return nil, errors.New("can't iterate over " + reflect.ValueOf(seq).Type().String()) } } func IsSet(a interface{}, key interface{}) bool { av := reflect.ValueOf(a) kv := reflect.ValueOf(key) switch av.Kind() { case reflect.Array, reflect.Chan, reflect.Slice: if int64(av.Len()) > kv.Int() { return true } case reflect.Map: if kv.Type() == av.Type().Key() { return av.MapIndex(kv).IsValid() } } return false } func ReturnWhenSet(a interface{}, index int) interface{} { av := reflect.ValueOf(a) switch av.Kind() { case reflect.Array, reflect.Slice: if av.Len() > index { avv := av.Index(index) switch avv.Kind() { case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: return avv.Int() case reflect.String: return avv.String() } } } return "" } func Highlight(in interface{}, lang string) template.HTML { var str string av := reflect.ValueOf(in) switch av.Kind() { case reflect.String: str = av.String() } if strings.HasPrefix(strings.TrimSpace(str), "
") {
str = str[strings.Index(str, "")+11:]
}
if strings.HasSuffix(strings.TrimSpace(str), "
") {
str = str[:strings.LastIndex(str, "
")]
}
return template.HTML(helpers.Highlight(html.UnescapeString(str), lang))
}
func SafeHtml(text string) template.HTML {
return template.HTML(text)
}
func doArithmetic(a, b interface{}, op rune) (interface{}, error) {
av := reflect.ValueOf(a)
bv := reflect.ValueOf(b)
var ai, bi int64
var af, bf float64
var au, bu uint64
switch av.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
ai = av.Int()
switch bv.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
bi = bv.Int()
case reflect.Float32, reflect.Float64:
af = float64(ai) // may overflow
ai = 0
bf = bv.Float()
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
bu = bv.Uint()
if ai >= 0 {
au = uint64(ai)
ai = 0
} else {
bi = int64(bu) // may overflow
bu = 0
}
default:
return nil, errors.New("Can't apply the operator to the values")
}
case reflect.Float32, reflect.Float64:
af = av.Float()
switch bv.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
bf = float64(bv.Int()) // may overflow
case reflect.Float32, reflect.Float64:
bf = bv.Float()
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
bf = float64(bv.Uint()) // may overflow
default:
return nil, errors.New("Can't apply the operator to the values")
}
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
au = av.Uint()
switch bv.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
bi = bv.Int()
if bi >= 0 {
bu = uint64(bi)
bi = 0
} else {
ai = int64(au) // may overflow
au = 0
}
case reflect.Float32, reflect.Float64:
af = float64(au) // may overflow
au = 0
bf = bv.Float()
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
bu = bv.Uint()
default:
return nil, errors.New("Can't apply the operator to the values")
}
case reflect.String:
as := av.String()
if bv.Kind() == reflect.String && op == '+' {
bs := bv.String()
return as + bs, nil
} else {
return nil, errors.New("Can't apply the operator to the values")
}
default:
return nil, errors.New("Can't apply the operator to the values")
}
switch op {
case '+':
if ai != 0 || bi != 0 {
return ai + bi, nil
} else if af != 0 || bf != 0 {
return af + bf, nil
} else if au != 0 || bu != 0 {
return au + bu, nil
} else {
return 0, nil
}
case '-':
if ai != 0 || bi != 0 {
return ai - bi, nil
} else if af != 0 || bf != 0 {
return af - bf, nil
} else if au != 0 || bu != 0 {
return au - bu, nil
} else {
return 0, nil
}
case '*':
if ai != 0 || bi != 0 {
return ai * bi, nil
} else if af != 0 || bf != 0 {
return af * bf, nil
} else if au != 0 || bu != 0 {
return au * bu, nil
} else {
return 0, nil
}
case '/':
if bi != 0 {
return ai / bi, nil
} else if bf != 0 {
return af / bf, nil
} else if bu != 0 {
return au / bu, nil
} else {
return nil, errors.New("Can't divide the value by 0")
}
default:
return nil, errors.New("There is no such an operation")
}
}
type Template interface {
ExecuteTemplate(wr io.Writer, name string, data interface{}) error
Lookup(name string) *template.Template
Templates() []*template.Template
New(name string) *template.Template
LoadTemplates(absPath string)
LoadTemplatesWithPrefix(absPath, prefix string)
AddTemplate(name, tpl string) error
AddInternalTemplate(prefix, name, tpl string) error
AddInternalShortcode(name, tpl string) error
}
type templateErr struct {
name string
err error
}
type GoHtmlTemplate struct {
template.Template
errors []*templateErr
}
func NewTemplate() Template {
var templates = &GoHtmlTemplate{
Template: *template.New(""),
errors: make([]*templateErr, 0),
}
localTemplates = &templates.Template
funcMap := template.FuncMap{
"urlize": helpers.Urlize,
"sanitizeurl": helpers.SanitizeUrl,
"eq": Eq,
"ne": Ne,
"gt": Gt,
"ge": Ge,
"lt": Lt,
"le": Le,
"isset": IsSet,
"echoParam": ReturnWhenSet,
"safeHtml": SafeHtml,
"first": First,
"where": Where,
"highlight": Highlight,
"add": func(a, b interface{}) (interface{}, error) { return doArithmetic(a, b, '+') },
"sub": func(a, b interface{}) (interface{}, error) { return doArithmetic(a, b, '-') },
"div": func(a, b interface{}) (interface{}, error) { return doArithmetic(a, b, '/') },
"mod": func(a, b int) int { return a % b },
"mul": func(a, b interface{}) (interface{}, error) { return doArithmetic(a, b, '*') },
"modBool": func(a, b int) bool { return a%b == 0 },
"lower": func(a string) string { return strings.ToLower(a) },
"upper": func(a string) string { return strings.ToUpper(a) },
"title": func(a string) string { return strings.Title(a) },
"partial": Partial,
}
templates.Funcs(funcMap)
templates.LoadEmbedded()
return templates
}
func Partial(name string, context_list ...interface{}) template.HTML {
if strings.HasPrefix("partials/", name) {
name = name[8:]
}
var context interface{}
if len(context_list) == 0 {
context = nil
} else {
context = context_list[0]
}
return ExecuteTemplateToHTML(context, "partials/"+name, "theme/partials/"+name)
}
func ExecuteTemplate(context interface{}, layouts ...string) *bytes.Buffer {
buffer := new(bytes.Buffer)
worked := false
for _, layout := range layouts {
if localTemplates.Lookup(layout) != nil {
err := localTemplates.ExecuteTemplate(buffer, layout, context)
if err != nil {
jww.ERROR.Println(err, "in", layout)
}
worked = true
break
}
}
if !worked {
jww.ERROR.Println("Unable to render", layouts)
jww.ERROR.Println("Expecting to find a template in either the theme/layouts or /layouts in one of the following relative locations", layouts)
}
return buffer
}
func ExecuteTemplateToHTML(context interface{}, layouts ...string) template.HTML {
b := ExecuteTemplate(context, layouts...)
return template.HTML(string(b.Bytes()))
}
func (t *GoHtmlTemplate) LoadEmbedded() {
t.EmbedShortcodes()
t.EmbedTemplates()
}
func (t *GoHtmlTemplate) AddInternalTemplate(prefix, name, tpl string) error {
if prefix != "" {
return t.AddTemplate("_internal/"+prefix+"/"+name, tpl)
} else {
return t.AddTemplate("_internal/"+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 {
t.errors = append(t.errors, &templateErr{name: name, err: err})
}
return err
}
func (t *GoHtmlTemplate) AddTemplateFile(name, path string) error {
// get the suffix and switch on that
ext := filepath.Ext(path)
switch ext {
case ".amber":
compiler := amber.New()
// Parse the input file
if err := compiler.ParseFile(path); err != nil {
return nil
}
if _, err := compiler.CompileWithTemplate(t.New(name)); err != nil {
return err
}
default:
b, err := ioutil.ReadFile(path)
if err != nil {
return err
}
return t.AddTemplate(name, string(b))
}
return nil
}
func (t *GoHtmlTemplate) generateTemplateNameFrom(base, path string) string {
return filepath.ToSlash(path[len(base)+1:])
}
func ignoreDotFile(path string) bool {
return filepath.Base(path)[0] == '.'
}
func (t *GoHtmlTemplate) loadTemplates(absPath string, prefix string) {
walker := func(path string, fi os.FileInfo, err error) error {
if err != nil {
return nil
}
if !fi.IsDir() {
if ignoreDotFile(path) {
return nil
}
tplName := t.generateTemplateNameFrom(absPath, path)
if prefix != "" {
tplName = strings.Trim(prefix, "/") + "/" + tplName
}
t.AddTemplateFile(tplName, path)
}
return nil
}
filepath.Walk(absPath, walker)
}
func (t *GoHtmlTemplate) LoadTemplatesWithPrefix(absPath string, prefix string) {
t.loadTemplates(absPath, prefix)
}
func (t *GoHtmlTemplate) LoadTemplates(absPath string) {
t.loadTemplates(absPath, "")
}