package template
import (
"errors"
"fmt"
"io"
"net/url"
"reflect"
"strings"
"sync"
"unicode"
"unicode/utf8"
)
type FuncMap map [string ]any
func builtins() FuncMap {
return FuncMap {
"and" : and ,
"call" : emptyCall ,
"html" : HTMLEscaper ,
"index" : index ,
"slice" : slice ,
"js" : JSEscaper ,
"len" : length ,
"not" : not ,
"or" : or ,
"print" : fmt .Sprint ,
"printf" : fmt .Sprintf ,
"println" : fmt .Sprintln ,
"urlquery" : URLQueryEscaper ,
"eq" : eq ,
"ge" : ge ,
"gt" : gt ,
"le" : le ,
"lt" : lt ,
"ne" : ne ,
}
}
var builtinFuncsOnce struct {
sync .Once
v map [string ]reflect .Value
}
func builtinFuncs() map [string ]reflect .Value {
builtinFuncsOnce .Do (func () {
builtinFuncsOnce .v = createValueFuncs (builtins ())
})
return builtinFuncsOnce .v
}
func createValueFuncs(funcMap FuncMap ) map [string ]reflect .Value {
m := make (map [string ]reflect .Value )
addValueFuncs (m , funcMap )
return m
}
func addValueFuncs(out map [string ]reflect .Value , in FuncMap ) {
for name , fn := range in {
if !goodName (name ) {
panic (fmt .Errorf ("function name %q is not a valid identifier" , name ))
}
v := reflect .ValueOf (fn )
if v .Kind () != reflect .Func {
panic ("value for " + name + " not a function" )
}
if err := goodFunc (name , v .Type ()); err != nil {
panic (err )
}
out [name ] = v
}
}
func addFuncs(out , in FuncMap ) {
for name , fn := range in {
out [name ] = fn
}
}
func goodFunc(name string , typ reflect .Type ) error {
switch numOut := typ .NumOut (); {
case numOut == 1 :
return nil
case numOut == 2 && typ .Out (1 ) == errorType :
return nil
case numOut == 2 :
return fmt .Errorf ("invalid function signature for %s: second return value should be error; is %s" , name , typ .Out (1 ))
default :
return fmt .Errorf ("function %s has %d return values; should be 1 or 2" , name , typ .NumOut ())
}
}
func goodName(name string ) bool {
if name == "" {
return false
}
for i , r := range name {
switch {
case r == '_' :
case i == 0 && !unicode .IsLetter (r ):
return false
case !unicode .IsLetter (r ) && !unicode .IsDigit (r ):
return false
}
}
return true
}
func findFunction(name string , tmpl *Template ) (v reflect .Value , isBuiltin , ok bool ) {
if tmpl != nil && tmpl .common != nil {
tmpl .muFuncs .RLock ()
defer tmpl .muFuncs .RUnlock ()
if fn := tmpl .execFuncs [name ]; fn .IsValid () {
return fn , false , true
}
}
if fn := builtinFuncs ()[name ]; fn .IsValid () {
return fn , true , true
}
return reflect .Value {}, false , false
}
func prepareArg(value reflect .Value , argType reflect .Type ) (reflect .Value , error ) {
if !value .IsValid () {
if !canBeNil (argType ) {
return reflect .Value {}, fmt .Errorf ("value is nil; should be of type %s" , argType )
}
value = reflect .Zero (argType )
}
if value .Type ().AssignableTo (argType ) {
return value , nil
}
if intLike (value .Kind ()) && intLike (argType .Kind ()) && value .Type ().ConvertibleTo (argType ) {
value = value .Convert (argType )
return value , nil
}
return reflect .Value {}, fmt .Errorf ("value has type %s; should be %s" , value .Type (), argType )
}
func intLike(typ reflect .Kind ) bool {
switch typ {
case reflect .Int , reflect .Int8 , reflect .Int16 , reflect .Int32 , reflect .Int64 :
return true
case reflect .Uint , reflect .Uint8 , reflect .Uint16 , reflect .Uint32 , reflect .Uint64 , reflect .Uintptr :
return true
}
return false
}
func indexArg(index reflect .Value , cap int ) (int , error ) {
var x int64
switch index .Kind () {
case reflect .Int , reflect .Int8 , reflect .Int16 , reflect .Int32 , reflect .Int64 :
x = index .Int ()
case reflect .Uint , reflect .Uint8 , reflect .Uint16 , reflect .Uint32 , reflect .Uint64 , reflect .Uintptr :
x = int64 (index .Uint ())
case reflect .Invalid :
return 0 , fmt .Errorf ("cannot index slice/array with nil" )
default :
return 0 , fmt .Errorf ("cannot index slice/array with type %s" , index .Type ())
}
if x < 0 || int (x ) < 0 || int (x ) > cap {
return 0 , fmt .Errorf ("index out of range: %d" , x )
}
return int (x ), nil
}
func index(item reflect .Value , indexes ...reflect .Value ) (reflect .Value , error ) {
item = indirectInterface (item )
if !item .IsValid () {
return reflect .Value {}, fmt .Errorf ("index of untyped nil" )
}
for _ , index := range indexes {
index = indirectInterface (index )
var isNil bool
if item , isNil = indirect (item ); isNil {
return reflect .Value {}, fmt .Errorf ("index of nil pointer" )
}
switch item .Kind () {
case reflect .Array , reflect .Slice , reflect .String :
x , err := indexArg (index , item .Len ())
if err != nil {
return reflect .Value {}, err
}
item = item .Index (x )
case reflect .Map :
index , err := prepareArg (index , item .Type ().Key ())
if err != nil {
return reflect .Value {}, err
}
if x := item .MapIndex (index ); x .IsValid () {
item = x
} else {
item = reflect .Zero (item .Type ().Elem ())
}
case reflect .Invalid :
panic ("unreachable" )
default :
return reflect .Value {}, fmt .Errorf ("can't index item of type %s" , item .Type ())
}
}
return item , nil
}
func slice(item reflect .Value , indexes ...reflect .Value ) (reflect .Value , error ) {
item = indirectInterface (item )
if !item .IsValid () {
return reflect .Value {}, fmt .Errorf ("slice of untyped nil" )
}
if len (indexes ) > 3 {
return reflect .Value {}, fmt .Errorf ("too many slice indexes: %d" , len (indexes ))
}
var cap int
switch item .Kind () {
case reflect .String :
if len (indexes ) == 3 {
return reflect .Value {}, fmt .Errorf ("cannot 3-index slice a string" )
}
cap = item .Len ()
case reflect .Array , reflect .Slice :
cap = item .Cap ()
default :
return reflect .Value {}, fmt .Errorf ("can't slice item of type %s" , item .Type ())
}
idx := [3 ]int {0 , item .Len ()}
for i , index := range indexes {
x , err := indexArg (index , cap )
if err != nil {
return reflect .Value {}, err
}
idx [i ] = x
}
if idx [0 ] > idx [1 ] {
return reflect .Value {}, fmt .Errorf ("invalid slice index: %d > %d" , idx [0 ], idx [1 ])
}
if len (indexes ) < 3 {
return item .Slice (idx [0 ], idx [1 ]), nil
}
if idx [1 ] > idx [2 ] {
return reflect .Value {}, fmt .Errorf ("invalid slice index: %d > %d" , idx [1 ], idx [2 ])
}
return item .Slice3 (idx [0 ], idx [1 ], idx [2 ]), nil
}
func length(item reflect .Value ) (int , error ) {
item , isNil := indirect (item )
if isNil {
return 0 , fmt .Errorf ("len of nil pointer" )
}
switch item .Kind () {
case reflect .Array , reflect .Chan , reflect .Map , reflect .Slice , reflect .String :
return item .Len (), nil
}
return 0 , fmt .Errorf ("len of type %s" , item .Type ())
}
func emptyCall(fn reflect .Value , args ...reflect .Value ) reflect .Value {
panic ("unreachable" )
}
func call(name string , fn reflect .Value , args ...reflect .Value ) (reflect .Value , error ) {
fn = indirectInterface (fn )
if !fn .IsValid () {
return reflect .Value {}, fmt .Errorf ("call of nil" )
}
typ := fn .Type ()
if typ .Kind () != reflect .Func {
return reflect .Value {}, fmt .Errorf ("non-function %s of type %s" , name , typ )
}
if err := goodFunc (name , typ ); err != nil {
return reflect .Value {}, err
}
numIn := typ .NumIn ()
var dddType reflect .Type
if typ .IsVariadic () {
if len (args ) < numIn -1 {
return reflect .Value {}, fmt .Errorf ("wrong number of args for %s: got %d want at least %d" , name , len (args ), numIn -1 )
}
dddType = typ .In (numIn - 1 ).Elem ()
} else {
if len (args ) != numIn {
return reflect .Value {}, fmt .Errorf ("wrong number of args for %s: got %d want %d" , name , len (args ), numIn )
}
}
argv := make ([]reflect .Value , len (args ))
for i , arg := range args {
arg = indirectInterface (arg )
argType := dddType
if !typ .IsVariadic () || i < numIn -1 {
argType = typ .In (i )
}
var err error
if argv [i ], err = prepareArg (arg , argType ); err != nil {
return reflect .Value {}, fmt .Errorf ("arg %d: %w" , i , err )
}
}
return safeCall (fn , argv )
}
func safeCall(fun reflect .Value , args []reflect .Value ) (val reflect .Value , err error ) {
defer func () {
if r := recover (); r != nil {
if e , ok := r .(error ); ok {
err = e
} else {
err = fmt .Errorf ("%v" , r )
}
}
}()
ret := fun .Call (args )
if len (ret ) == 2 && !ret [1 ].IsNil () {
return ret [0 ], ret [1 ].Interface ().(error )
}
return ret [0 ], nil
}
func truth(arg reflect .Value ) bool {
t , _ := isTrue (indirectInterface (arg ))
return t
}
func and(arg0 reflect .Value , args ...reflect .Value ) reflect .Value {
panic ("unreachable" )
}
func or(arg0 reflect .Value , args ...reflect .Value ) reflect .Value {
panic ("unreachable" )
}
func not(arg reflect .Value ) bool {
return !truth (arg )
}
var (
errBadComparisonType = errors .New ("invalid type for comparison" )
errBadComparison = errors .New ("incompatible types for comparison" )
errNoComparison = errors .New ("missing argument for comparison" )
)
type kind int
const (
invalidKind kind = iota
boolKind
complexKind
intKind
floatKind
stringKind
uintKind
)
func basicKind(v reflect .Value ) (kind , error ) {
switch v .Kind () {
case reflect .Bool :
return boolKind , nil
case reflect .Int , reflect .Int8 , reflect .Int16 , reflect .Int32 , reflect .Int64 :
return intKind , nil
case reflect .Uint , reflect .Uint8 , reflect .Uint16 , reflect .Uint32 , reflect .Uint64 , reflect .Uintptr :
return uintKind , nil
case reflect .Float32 , reflect .Float64 :
return floatKind , nil
case reflect .Complex64 , reflect .Complex128 :
return complexKind , nil
case reflect .String :
return stringKind , nil
}
return invalidKind , errBadComparisonType
}
func isNil(v reflect .Value ) bool {
if !v .IsValid () {
return true
}
switch v .Kind () {
case reflect .Chan , reflect .Func , reflect .Interface , reflect .Map , reflect .Pointer , reflect .Slice :
return v .IsNil ()
}
return false
}
func canCompare(v1 , v2 reflect .Value ) bool {
k1 := v1 .Kind ()
k2 := v2 .Kind ()
if k1 == k2 {
return true
}
return k1 == reflect .Invalid || k2 == reflect .Invalid
}
func eq(arg1 reflect .Value , arg2 ...reflect .Value ) (bool , error ) {
arg1 = indirectInterface (arg1 )
if len (arg2 ) == 0 {
return false , errNoComparison
}
k1 , _ := basicKind (arg1 )
for _ , arg := range arg2 {
arg = indirectInterface (arg )
k2 , _ := basicKind (arg )
truth := false
if k1 != k2 {
switch {
case k1 == intKind && k2 == uintKind :
truth = arg1 .Int () >= 0 && uint64 (arg1 .Int ()) == arg .Uint ()
case k1 == uintKind && k2 == intKind :
truth = arg .Int () >= 0 && arg1 .Uint () == uint64 (arg .Int ())
default :
if arg1 .IsValid () && arg .IsValid () {
return false , errBadComparison
}
}
} else {
switch k1 {
case boolKind :
truth = arg1 .Bool () == arg .Bool ()
case complexKind :
truth = arg1 .Complex () == arg .Complex ()
case floatKind :
truth = arg1 .Float () == arg .Float ()
case intKind :
truth = arg1 .Int () == arg .Int ()
case stringKind :
truth = arg1 .String () == arg .String ()
case uintKind :
truth = arg1 .Uint () == arg .Uint ()
default :
if !canCompare (arg1 , arg ) {
return false , fmt .Errorf ("non-comparable types %s: %v, %s: %v" , arg1 , arg1 .Type (), arg .Type (), arg )
}
if isNil (arg1 ) || isNil (arg ) {
truth = isNil (arg ) == isNil (arg1 )
} else {
if !arg .Type ().Comparable () {
return false , fmt .Errorf ("non-comparable type %s: %v" , arg , arg .Type ())
}
truth = arg1 .Interface () == arg .Interface ()
}
}
}
if truth {
return true , nil
}
}
return false , nil
}
func ne(arg1 , arg2 reflect .Value ) (bool , error ) {
equal , err := eq (arg1 , arg2 )
return !equal , err
}
func lt(arg1 , arg2 reflect .Value ) (bool , error ) {
arg1 = indirectInterface (arg1 )
k1 , err := basicKind (arg1 )
if err != nil {
return false , err
}
arg2 = indirectInterface (arg2 )
k2 , err := basicKind (arg2 )
if err != nil {
return false , err
}
truth := false
if k1 != k2 {
switch {
case k1 == intKind && k2 == uintKind :
truth = arg1 .Int () < 0 || uint64 (arg1 .Int ()) < arg2 .Uint ()
case k1 == uintKind && k2 == intKind :
truth = arg2 .Int () >= 0 && arg1 .Uint () < uint64 (arg2 .Int ())
default :
return false , errBadComparison
}
} else {
switch k1 {
case boolKind , complexKind :
return false , errBadComparisonType
case floatKind :
truth = arg1 .Float () < arg2 .Float ()
case intKind :
truth = arg1 .Int () < arg2 .Int ()
case stringKind :
truth = arg1 .String () < arg2 .String ()
case uintKind :
truth = arg1 .Uint () < arg2 .Uint ()
default :
panic ("invalid kind" )
}
}
return truth , nil
}
func le(arg1 , arg2 reflect .Value ) (bool , error ) {
lessThan , err := lt (arg1 , arg2 )
if lessThan || err != nil {
return lessThan , err
}
return eq (arg1 , arg2 )
}
func gt(arg1 , arg2 reflect .Value ) (bool , error ) {
lessOrEqual , err := le (arg1 , arg2 )
if err != nil {
return false , err
}
return !lessOrEqual , nil
}
func ge(arg1 , arg2 reflect .Value ) (bool , error ) {
lessThan , err := lt (arg1 , arg2 )
if err != nil {
return false , err
}
return !lessThan , nil
}
var (
htmlQuot = []byte (""" )
htmlApos = []byte ("'" )
htmlAmp = []byte ("&" )
htmlLt = []byte ("<" )
htmlGt = []byte (">" )
htmlNull = []byte ("\uFFFD" )
)
func HTMLEscape (w io .Writer , b []byte ) {
last := 0
for i , c := range b {
var html []byte
switch c {
case '\000' :
html = htmlNull
case '"' :
html = htmlQuot
case '\'' :
html = htmlApos
case '&' :
html = htmlAmp
case '<' :
html = htmlLt
case '>' :
html = htmlGt
default :
continue
}
w .Write (b [last :i ])
w .Write (html )
last = i + 1
}
w .Write (b [last :])
}
func HTMLEscapeString (s string ) string {
if !strings .ContainsAny (s , "'\"&<>\000" ) {
return s
}
var b strings .Builder
HTMLEscape (&b , []byte (s ))
return b .String ()
}
func HTMLEscaper (args ...any ) string {
return HTMLEscapeString (evalArgs (args ))
}
var (
jsLowUni = []byte (`\u00` )
hex = []byte ("0123456789ABCDEF" )
jsBackslash = []byte (`\\` )
jsApos = []byte (`\'` )
jsQuot = []byte (`\"` )
jsLt = []byte (`\u003C` )
jsGt = []byte (`\u003E` )
jsAmp = []byte (`\u0026` )
jsEq = []byte (`\u003D` )
)
func JSEscape (w io .Writer , b []byte ) {
last := 0
for i := 0 ; i < len (b ); i ++ {
c := b [i ]
if !jsIsSpecial (rune (c )) {
continue
}
w .Write (b [last :i ])
if c < utf8 .RuneSelf {
switch c {
case '\\' :
w .Write (jsBackslash )
case '\'' :
w .Write (jsApos )
case '"' :
w .Write (jsQuot )
case '<' :
w .Write (jsLt )
case '>' :
w .Write (jsGt )
case '&' :
w .Write (jsAmp )
case '=' :
w .Write (jsEq )
default :
w .Write (jsLowUni )
t , b := c >>4 , c &0x0f
w .Write (hex [t : t +1 ])
w .Write (hex [b : b +1 ])
}
} else {
r , size := utf8 .DecodeRune (b [i :])
if unicode .IsPrint (r ) {
w .Write (b [i : i +size ])
} else {
fmt .Fprintf (w , "\\u%04X" , r )
}
i += size - 1
}
last = i + 1
}
w .Write (b [last :])
}
func JSEscapeString (s string ) string {
if strings .IndexFunc (s , jsIsSpecial ) < 0 {
return s
}
var b strings .Builder
JSEscape (&b , []byte (s ))
return b .String ()
}
func jsIsSpecial(r rune ) bool {
switch r {
case '\\' , '\'' , '"' , '<' , '>' , '&' , '=' :
return true
}
return r < ' ' || utf8 .RuneSelf <= r
}
func JSEscaper (args ...any ) string {
return JSEscapeString (evalArgs (args ))
}
func URLQueryEscaper (args ...any ) string {
return url .QueryEscape (evalArgs (args ))
}
func evalArgs(args []any ) string {
ok := false
var s string
if len (args ) == 1 {
s , ok = args [0 ].(string )
}
if !ok {
for i , arg := range args {
a , ok := printableValue (reflect .ValueOf (arg ))
if ok {
args [i ] = a
}
}
s = fmt .Sprint (args ...)
}
return s
}
The pages are generated with Golds v0.7.0-preview . (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu .
PR and bug reports are welcome and can be submitted to the issue list .
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds .