diff --git a/tpl/template.go b/tpl/template.go index fc7457e55..bc3a57a70 100644 --- a/tpl/template.go +++ b/tpl/template.go @@ -15,30 +15,22 @@ package tpl import ( "bytes" - "errors" - "fmt" "github.com/eknkc/amber" - "github.com/spf13/cast" bp "github.com/spf13/hugo/bufferpool" "github.com/spf13/hugo/helpers" "github.com/spf13/hugo/hugofs" jww "github.com/spf13/jwalterweatherman" "github.com/yosssi/ace" - "html" "html/template" "io" "io/ioutil" "os" "path/filepath" - "reflect" - "sort" - "strconv" "strings" ) var localTemplates *template.Template var tmpl Template -var funcMap template.FuncMap type Template interface { ExecuteTemplate(wr io.Writer, name string, data interface{}) error @@ -93,1123 +85,6 @@ func New() Template { return templates } -func Eq(x, y interface{}) bool { - normalize := func(v interface{}) interface{} { - vv := reflect.ValueOf(v) - switch vv.Kind() { - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - return vv.Int() - case reflect.Float32, reflect.Float64: - return vv.Float() - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: - return vv.Uint() - default: - return v - } - } - x = normalize(x) - y = normalize(y) - 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 - var leftStr, rightStr *string - var err error - 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, err = strconv.ParseFloat(av.String(), 64) - if err != nil { - str := av.String() - leftStr = &str - } - } - - 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, err = strconv.ParseFloat(bv.String(), 64) - if err != nil { - str := bv.String() - rightStr = &str - } - - } - - switch { - case leftStr == nil || rightStr == nil: - case *leftStr < *rightStr: - return 0, 1 - case *leftStr > *rightStr: - return 1, 0 - default: - return 0, 0 - } - - return left, right -} - -// Slicing in Slicestr is done by specifying a half-open range with -// two indices, start and end. 1 and 4 creates a slice including elements 1 through 3. -// The end index can be omitted, it defaults to the string's length. -func Slicestr(a interface{}, startEnd ...int) (string, error) { - aStr, err := cast.ToStringE(a) - if err != nil { - return "", err - } - - if len(startEnd) > 2 { - return "", errors.New("too many arguments") - } - - if len(startEnd) == 2 { - return aStr[startEnd[0]:startEnd[1]], nil - } else if len(startEnd) == 1 { - return aStr[startEnd[0]:], nil - } else { - return aStr[:], nil - } - -} - -// Substr extracts parts of a string, beginning at the character at the specified -// position, and returns the specified number of characters. -// -// It normally takes two parameters: start and length. -// It can also take one parameter: start, i.e. length is omitted, in which case -// the substring starting from start until the end of the string will be returned. -// -// To extract characters from the end of the string, use a negative start number. -// -// In addition, borrowing from the extended behavior described at http://php.net/substr, -// if length is given and is negative, then that many characters will be omitted from -// the end of string. -func Substr(a interface{}, nums ...int) (string, error) { - aStr, err := cast.ToStringE(a) - if err != nil { - return "", err - } - - var start, length int - switch len(nums) { - case 1: - start = nums[0] - length = len(aStr) - case 2: - start = nums[0] - length = nums[1] - default: - return "", errors.New("too many arguments") - } - - if start < -len(aStr) { - start = 0 - } - if start > len(aStr) { - return "", errors.New(fmt.Sprintf("start position out of bounds for %d-byte string", len(aStr))) - } - - var s, e int - if start >= 0 && length >= 0 { - s = start - e = start + length - } else if start < 0 && length >= 0 { - s = len(aStr) + start - length + 1 - e = len(aStr) + start + 1 - } else if start >= 0 && length < 0 { - s = start - e = len(aStr) + length - } else { - s = len(aStr) + start - e = len(aStr) + length - } - - if s > e { - return "", errors.New(fmt.Sprintf("calculated start position greater than end position: %d > %d", s, e)) - } - if e > len(aStr) { - e = len(aStr) - } - - return aStr[s:e], nil - -} - -func Split(a interface{}, delimiter string) ([]string, error) { - aStr, err := cast.ToStringE(a) - if err != nil { - return []string{}, err - } - return strings.Split(aStr, delimiter), nil -} - -func Intersect(l1, l2 interface{}) (interface{}, error) { - if l1 == nil || l2 == nil { - return make([]interface{}, 0), nil - } - - l1v := reflect.ValueOf(l1) - l2v := reflect.ValueOf(l2) - - switch l1v.Kind() { - case reflect.Array, reflect.Slice: - switch l2v.Kind() { - case reflect.Array, reflect.Slice: - r := reflect.MakeSlice(l1v.Type(), 0, 0) - for i := 0; i < l1v.Len(); i++ { - l1vv := l1v.Index(i) - for j := 0; j < l2v.Len(); j++ { - l2vv := l2v.Index(j) - switch l1vv.Kind() { - case reflect.String: - if l1vv.Type() == l2vv.Type() && l1vv.String() == l2vv.String() && !In(r, l2vv) { - r = reflect.Append(r, l2vv) - } - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - switch l2vv.Kind() { - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - if l1vv.Int() == l2vv.Int() && !In(r, l2vv) { - r = reflect.Append(r, l2vv) - } - } - case reflect.Float32, reflect.Float64: - switch l2vv.Kind() { - case reflect.Float32, reflect.Float64: - if l1vv.Float() == l2vv.Float() && !In(r, l2vv) { - r = reflect.Append(r, l2vv) - } - } - } - } - } - return r.Interface(), nil - default: - return nil, errors.New("can't iterate over " + reflect.ValueOf(l2).Type().String()) - } - default: - return nil, errors.New("can't iterate over " + reflect.ValueOf(l1).Type().String()) - } -} - -func In(l interface{}, v interface{}) bool { - lv := reflect.ValueOf(l) - vv := reflect.ValueOf(v) - - switch lv.Kind() { - case reflect.Array, reflect.Slice: - for i := 0; i < lv.Len(); i++ { - lvv := lv.Index(i) - switch lvv.Kind() { - case reflect.String: - if vv.Type() == lvv.Type() && vv.String() == lvv.String() { - return true - } - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - switch vv.Kind() { - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - if vv.Int() == lvv.Int() { - return true - } - } - case reflect.Float32, reflect.Float64: - switch vv.Kind() { - case reflect.Float32, reflect.Float64: - if vv.Float() == lvv.Float() { - return true - } - } - } - } - case reflect.String: - if vv.Type() == lv.Type() && strings.Contains(lv.String(), vv.String()) { - return true - } - } - return false -} - -// indirect is taken from 'text/template/exec.go' -func indirect(v reflect.Value) (rv reflect.Value, isNil bool) { - for ; v.Kind() == reflect.Ptr || v.Kind() == reflect.Interface; v = v.Elem() { - if v.IsNil() { - return v, true - } - if v.Kind() == reflect.Interface && v.NumMethod() > 0 { - break - } - } - return v, false -} - -// First is exposed to templates, to iterate over the first N items in a -// rangeable list. -func First(limit interface{}, seq interface{}) (interface{}, error) { - - if limit == nil || seq == nil { - return nil, errors.New("both limit and seq must be provided") - } - - limitv, err := cast.ToIntE(limit) - - if err != nil { - return nil, err - } - - if limitv < 1 { - return nil, errors.New("can't return negative/empty count of items from sequence") - } - - seqv := reflect.ValueOf(seq) - seqv, isNil := indirect(seqv) - if isNil { - return nil, errors.New("can't iterate over a nil value") - } - - 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 limitv > seqv.Len() { - limitv = seqv.Len() - } - return seqv.Slice(0, limitv).Interface(), nil -} - -var ( - zero reflect.Value - errorType = reflect.TypeOf((*error)(nil)).Elem() -) - -func evaluateSubElem(obj reflect.Value, elemName string) (reflect.Value, error) { - if !obj.IsValid() { - return zero, errors.New("can't evaluate an invalid value") - } - typ := obj.Type() - obj, isNil := indirect(obj) - - // first, check whether obj has a method. In this case, obj is - // an interface, a struct or its pointer. If obj is a struct, - // to check all T and *T method, use obj pointer type Value - objPtr := obj - if objPtr.Kind() != reflect.Interface && objPtr.CanAddr() { - objPtr = objPtr.Addr() - } - mt, ok := objPtr.Type().MethodByName(elemName) - if ok { - if mt.PkgPath != "" { - return zero, fmt.Errorf("%s is an unexported method of type %s", elemName, typ) - } - // struct pointer has one receiver argument and interface doesn't have an argument - if mt.Type.NumIn() > 1 || mt.Type.NumOut() == 0 || mt.Type.NumOut() > 2 { - return zero, fmt.Errorf("%s is a method of type %s but doesn't satisfy requirements", elemName, typ) - } - if mt.Type.NumOut() == 1 && mt.Type.Out(0).Implements(errorType) { - return zero, fmt.Errorf("%s is a method of type %s but doesn't satisfy requirements", elemName, typ) - } - if mt.Type.NumOut() == 2 && !mt.Type.Out(1).Implements(errorType) { - return zero, fmt.Errorf("%s is a method of type %s but doesn't satisfy requirements", elemName, typ) - } - res := objPtr.Method(mt.Index).Call([]reflect.Value{}) - if len(res) == 2 && !res[1].IsNil() { - return zero, fmt.Errorf("error at calling a method %s of type %s: %s", elemName, typ, res[1].Interface().(error)) - } - return res[0], nil - } - - // elemName isn't a method so next start to check whether it is - // a struct field or a map value. In both cases, it mustn't be - // a nil value - if isNil { - return zero, fmt.Errorf("can't evaluate a nil pointer of type %s by a struct field or map key name %s", typ, elemName) - } - switch obj.Kind() { - case reflect.Struct: - ft, ok := obj.Type().FieldByName(elemName) - if ok { - if ft.PkgPath != "" { - return zero, fmt.Errorf("%s is an unexported field of struct type %s", elemName, typ) - } - return obj.FieldByIndex(ft.Index), nil - } - return zero, fmt.Errorf("%s isn't a field of struct type %s", elemName, typ) - case reflect.Map: - kv := reflect.ValueOf(elemName) - if kv.Type().AssignableTo(obj.Type().Key()) { - return obj.MapIndex(kv), nil - } - return zero, fmt.Errorf("%s isn't a key of map type %s", elemName, typ) - } - return zero, fmt.Errorf("%s is neither a struct field, a method nor a map element of type %s", elemName, typ) -} - -func checkCondition(v, mv reflect.Value, op string) (bool, error) { - if !v.IsValid() || !mv.IsValid() { - return false, nil - } - - var isNil bool - v, isNil = indirect(v) - if isNil { - return false, nil - } - mv, isNil = indirect(mv) - if isNil { - return false, nil - } - - var ivp, imvp *int64 - var svp, smvp *string - var ima []int64 - var sma []string - if mv.Type() == v.Type() { - switch v.Kind() { - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - iv := v.Int() - ivp = &iv - imv := mv.Int() - imvp = &imv - case reflect.String: - sv := v.String() - svp = &sv - smv := mv.String() - smvp = &smv - } - } else { - if mv.Kind() != reflect.Array && mv.Kind() != reflect.Slice { - return false, nil - } - if mv.Type().Elem() != v.Type() { - return false, nil - } - switch v.Kind() { - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - iv := v.Int() - ivp = &iv - for i := 0; i < mv.Len(); i++ { - ima = append(ima, mv.Index(i).Int()) - } - case reflect.String: - sv := v.String() - svp = &sv - for i := 0; i < mv.Len(); i++ { - sma = append(sma, mv.Index(i).String()) - } - } - } - - switch op { - case "", "=", "==", "eq": - if ivp != nil && imvp != nil { - return *ivp == *imvp, nil - } else if svp != nil && smvp != nil { - return *svp == *smvp, nil - } - case "!=", "<>", "ne": - if ivp != nil && imvp != nil { - return *ivp != *imvp, nil - } else if svp != nil && smvp != nil { - return *svp != *smvp, nil - } - case ">=", "ge": - if ivp != nil && imvp != nil { - return *ivp >= *imvp, nil - } else if svp != nil && smvp != nil { - return *svp >= *smvp, nil - } - case ">", "gt": - if ivp != nil && imvp != nil { - return *ivp > *imvp, nil - } else if svp != nil && smvp != nil { - return *svp > *smvp, nil - } - case "<=", "le": - if ivp != nil && imvp != nil { - return *ivp <= *imvp, nil - } else if svp != nil && smvp != nil { - return *svp <= *smvp, nil - } - case "<", "lt": - if ivp != nil && imvp != nil { - return *ivp < *imvp, nil - } else if svp != nil && smvp != nil { - return *svp < *smvp, nil - } - case "in", "not in": - var r bool - if ivp != nil && len(ima) > 0 { - r = In(ima, *ivp) - } else if svp != nil { - if len(sma) > 0 { - r = In(sma, *svp) - } else if smvp != nil { - r = In(*smvp, *svp) - } - } else { - return false, nil - } - if op == "not in" { - return !r, nil - } else { - return r, nil - } - default: - return false, errors.New("no such an operator") - } - return false, nil -} - -func Where(seq, key interface{}, args ...interface{}) (r interface{}, err error) { - seqv := reflect.ValueOf(seq) - kv := reflect.ValueOf(key) - - var mv reflect.Value - var op string - switch len(args) { - case 1: - mv = reflect.ValueOf(args[0]) - case 2: - var ok bool - if op, ok = args[0].(string); !ok { - return nil, errors.New("operator argument must be string type") - } - op = strings.TrimSpace(strings.ToLower(op)) - mv = reflect.ValueOf(args[1]) - default: - return nil, errors.New("can't evaluate the array by no match argument or more than or equal to two arguments") - } - - seqv, isNil := indirect(seqv) - if isNil { - return nil, errors.New("can't iterate over a nil value of type " + reflect.ValueOf(seq).Type().String()) - } - - var path []string - if kv.Kind() == reflect.String { - path = strings.Split(strings.Trim(kv.String(), "."), ".") - } - - switch seqv.Kind() { - case reflect.Array, reflect.Slice: - rv := reflect.MakeSlice(seqv.Type(), 0, 0) - for i := 0; i < seqv.Len(); i++ { - var vvv reflect.Value - rvv := seqv.Index(i) - if kv.Kind() == reflect.String { - vvv = rvv - for _, elemName := range path { - vvv, err = evaluateSubElem(vvv, elemName) - if err != nil { - return nil, err - } - } - } else { - vv, _ := indirect(rvv) - if vv.Kind() == reflect.Map && kv.Type().AssignableTo(vv.Type().Key()) { - vvv = vv.MapIndex(kv) - } - } - if ok, err := checkCondition(vvv, mv, op); ok { - rv = reflect.Append(rv, rvv) - } else if err != nil { - return nil, err - } - } - return rv.Interface(), nil - default: - return nil, errors.New("can't iterate over " + reflect.ValueOf(seq).Type().String()) - } -} - -// Apply, given a map, array, or slice, returns a new slice with the function fname applied over it. -func Apply(seq interface{}, fname string, args ...interface{}) (interface{}, error) { - if seq == nil { - return make([]interface{}, 0), nil - } - - if fname == "apply" { - return nil, errors.New("can't apply myself (no turtles allowed)") - } - - seqv := reflect.ValueOf(seq) - seqv, isNil := indirect(seqv) - if isNil { - return nil, errors.New("can't iterate over a nil value") - } - - fn, found := funcMap[fname] - if !found { - return nil, errors.New("can't find function " + fname) - } - - fnv := reflect.ValueOf(fn) - - switch seqv.Kind() { - case reflect.Array, reflect.Slice: - r := make([]interface{}, seqv.Len()) - for i := 0; i < seqv.Len(); i++ { - vv := seqv.Index(i) - - vvv, err := applyFnToThis(fnv, vv, args...) - - if err != nil { - return nil, err - } - - r[i] = vvv.Interface() - } - - return r, nil - default: - return nil, errors.New("can't apply over " + reflect.ValueOf(seq).Type().String()) - } -} - -func applyFnToThis(fn, this reflect.Value, args ...interface{}) (reflect.Value, error) { - n := make([]reflect.Value, len(args)) - for i, arg := range args { - if arg == "." { - n[i] = this - } else { - n[i] = reflect.ValueOf(arg) - } - } - - res := fn.Call(n) - - if len(res) == 1 || res[1].IsNil() { - return res[0], nil - } else { - return reflect.ValueOf(nil), res[1].Interface().(error) - } -} - -func Delimit(seq, delimiter interface{}, last ...interface{}) (template.HTML, error) { - d, err := cast.ToStringE(delimiter) - if err != nil { - return "", err - } - - var dLast *string - for _, l := range last { - dStr, err := cast.ToStringE(l) - if err != nil { - dLast = nil - } - dLast = &dStr - break - } - - seqv := reflect.ValueOf(seq) - seqv, isNil := indirect(seqv) - if isNil { - return "", errors.New("can't iterate over a nil value") - } - - var str string - switch seqv.Kind() { - case reflect.Map: - sortSeq, err := Sort(seq) - if err != nil { - return "", err - } - seqv = reflect.ValueOf(sortSeq) - fallthrough - case reflect.Array, reflect.Slice, reflect.String: - for i := 0; i < seqv.Len(); i++ { - val := seqv.Index(i).Interface() - valStr, err := cast.ToStringE(val) - if err != nil { - continue - } - switch { - case i == seqv.Len()-2 && dLast != nil: - str += valStr + *dLast - case i == seqv.Len()-1: - str += valStr - default: - str += valStr + d - } - } - - default: - return "", errors.New("can't iterate over " + reflect.ValueOf(seq).Type().String()) - } - - return template.HTML(str), nil -} - -func Sort(seq interface{}, args ...interface{}) ([]interface{}, error) { - seqv := reflect.ValueOf(seq) - seqv, isNil := indirect(seqv) - if isNil { - return nil, errors.New("can't iterate over a nil value") - } - - // Create a list of pairs that will be used to do the sort - p := pairList{SortAsc: true} - p.Pairs = make([]pair, seqv.Len()) - - for i, l := range args { - dStr, err := cast.ToStringE(l) - switch { - case i == 0 && err != nil: - p.SortByField = "" - case i == 0 && err == nil: - p.SortByField = dStr - case i == 1 && err == nil && dStr == "desc": - p.SortAsc = false - case i == 1: - p.SortAsc = true - } - } - - var sorted []interface{} - switch seqv.Kind() { - case reflect.Array, reflect.Slice: - for i := 0; i < seqv.Len(); i++ { - p.Pairs[i].Key = reflect.ValueOf(i) - p.Pairs[i].Value = seqv.Index(i) - } - if p.SortByField == "" { - p.SortByField = "value" - } - - case reflect.Map: - keys := seqv.MapKeys() - for i := 0; i < seqv.Len(); i++ { - p.Pairs[i].Key = keys[i] - p.Pairs[i].Value = seqv.MapIndex(keys[i]) - } - - default: - return nil, errors.New("can't sort " + reflect.ValueOf(seq).Type().String()) - } - sorted = p.sort() - return sorted, nil -} - -// Credit for pair sorting method goes to Andrew Gerrand -// https://groups.google.com/forum/#!topic/golang-nuts/FT7cjmcL7gw -// A data structure to hold a key/value pair. -type pair struct { - Key reflect.Value - Value reflect.Value -} - -// A slice of pairs that implements sort.Interface to sort by Value. -type pairList struct { - Pairs []pair - SortByField string - SortAsc bool -} - -func (p pairList) Swap(i, j int) { p.Pairs[i], p.Pairs[j] = p.Pairs[j], p.Pairs[i] } -func (p pairList) Len() int { return len(p.Pairs) } -func (p pairList) Less(i, j int) bool { - var truth bool - switch { - case p.SortByField == "value": - iVal := p.Pairs[i].Value - jVal := p.Pairs[j].Value - truth = Lt(iVal.Interface(), jVal.Interface()) - - case p.SortByField != "": - if p.Pairs[i].Value.FieldByName(p.SortByField).IsValid() { - iVal := p.Pairs[i].Value.FieldByName(p.SortByField) - jVal := p.Pairs[j].Value.FieldByName(p.SortByField) - truth = Lt(iVal.Interface(), jVal.Interface()) - } - default: - iVal := p.Pairs[i].Key - jVal := p.Pairs[j].Key - truth = Lt(iVal.Interface(), jVal.Interface()) - } - return truth -} - -// sorts a pairList and returns a slice of sorted values -func (p pairList) sort() []interface{} { - if p.SortAsc { - sort.Sort(p) - } else { - sort.Sort(sort.Reverse(p)) - } - sorted := make([]interface{}, len(p.Pairs)) - for i, v := range p.Pairs { - sorted[i] = v.Value.Interface() - } - - return sorted -} - -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, k interface{}) interface{} { - av, isNil := indirect(reflect.ValueOf(a)) - if isNil { - return "" - } - - var avv reflect.Value - switch av.Kind() { - case reflect.Array, reflect.Slice: - index, ok := k.(int) - if ok && av.Len() > index { - avv = av.Index(index) - } - case reflect.Map: - kv := reflect.ValueOf(k) - if kv.Type().AssignableTo(av.Type().Key()) { - avv = av.MapIndex(kv) - } - } - - if avv.IsValid() { - switch avv.Kind() { - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - return avv.Int() - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: - return avv.Uint() - case reflect.Float32, reflect.Float64: - return avv.Float() - 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() - } - - return template.HTML(helpers.Highlight(html.UnescapeString(str), lang)) -} - -var markdownTrimPrefix = []byte("

") -var markdownTrimSuffix = []byte("

\n") - -func Markdownify(text string) template.HTML { - m := helpers.RenderBytes(&helpers.RenderingContext{Content: []byte(text), PageFmt: "markdown"}) - m = bytes.TrimPrefix(m, markdownTrimPrefix) - m = bytes.TrimSuffix(m, markdownTrimSuffix) - return template.HTML(m) -} - -func refPage(page interface{}, ref, methodName string) template.HTML { - value := reflect.ValueOf(page) - - method := value.MethodByName(methodName) - - if method.IsValid() && method.Type().NumIn() == 1 && method.Type().NumOut() == 2 { - result := method.Call([]reflect.Value{reflect.ValueOf(ref)}) - - url, err := result[0], result[1] - - if !err.IsNil() { - jww.ERROR.Printf("%s", err.Interface()) - return template.HTML(fmt.Sprintf("%s", err.Interface())) - } - - if url.String() == "" { - jww.ERROR.Printf("ref %s could not be found\n", ref) - return template.HTML(ref) - } - - return template.HTML(url.String()) - } - - jww.ERROR.Printf("Can only create references from Page and Node objects.") - return template.HTML(ref) -} - -func Ref(page interface{}, ref string) template.HTML { - return refPage(page, ref, "Ref") -} - -func RelRef(page interface{}, ref string) template.HTML { - return refPage(page, ref, "RelRef") -} - -func Chomp(text interface{}) (string, error) { - s, err := cast.ToStringE(text) - if err != nil { - return "", err - } - - return strings.TrimRight(s, "\r\n"), nil -} - -// Trim leading/trailing characters defined by b from a -func Trim(a interface{}, b string) (string, error) { - aStr, err := cast.ToStringE(a) - if err != nil { - return "", err - } - return strings.Trim(aStr, b), nil -} - -// Replace all occurences of b with c in a -func Replace(a, b, c interface{}) (string, error) { - aStr, err := cast.ToStringE(a) - if err != nil { - return "", err - } - bStr, err := cast.ToStringE(b) - if err != nil { - return "", err - } - cStr, err := cast.ToStringE(c) - if err != nil { - return "", err - } - return strings.Replace(aStr, bStr, cStr, -1), nil -} - -// DateFormat converts the textual representation of the datetime string into -// the other form or returns it of the time.Time value. These are formatted -// with the layout string -func DateFormat(layout string, v interface{}) (string, error) { - t, err := cast.ToTimeE(v) - if err != nil { - return "", err - } - return t.Format(layout), nil -} - -func SafeHTML(text string) template.HTML { - return template.HTML(text) -} - -// "safeHTMLAttr" is currently disabled, pending further discussion -// on its use case. 2015-01-19 -func SafeHTMLAttr(text string) template.HTMLAttr { - return template.HTMLAttr(text) -} - -func SafeCSS(text string) template.CSS { - return template.CSS(text) -} - -func SafeURL(text string) template.URL { - return template.URL(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") - } -} - -func Mod(a, b interface{}) (int64, error) { - av := reflect.ValueOf(a) - bv := reflect.ValueOf(b) - var ai, bi int64 - - switch av.Kind() { - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - ai = av.Int() - default: - return 0, errors.New("Modulo operator can't be used with non integer value") - } - - switch bv.Kind() { - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - bi = bv.Int() - default: - return 0, errors.New("Modulo operator can't be used with non integer value") - } - - if bi == 0 { - return 0, errors.New("The number can't be divided by zero at modulo operation") - } - - return ai % bi, nil -} - -func ModBool(a, b interface{}) (bool, error) { - res, err := Mod(a, b) - if err != nil { - return false, err - } - return res == int64(0), nil -} - func Partial(name string, context_list ...interface{}) template.HTML { if strings.HasPrefix("partials/", name) { name = name[8:] @@ -1351,8 +226,6 @@ func isBackupFile(path string) bool { return path[len(path)-1] == '~' } -// TODO(bep) split this file in two => template_funcs.go + tests. - const baseAceFilename = "baseof.ace" var aceTemplateInnerMarker = []byte("= content") @@ -1441,82 +314,3 @@ func (t *GoHTMLTemplate) PrintErrors() { jww.ERROR.Println(e.err) } } - -func init() { - funcMap = template.FuncMap{ - "urlize": helpers.URLize, - "sanitizeURL": helpers.SanitizeURL, - "sanitizeurl": helpers.SanitizeURL, - "eq": Eq, - "ne": Ne, - "gt": Gt, - "ge": Ge, - "lt": Lt, - "le": Le, - "in": In, - "slicestr": Slicestr, - "substr": Substr, - "split": Split, - "intersect": Intersect, - "isSet": IsSet, - "isset": IsSet, - "echoParam": ReturnWhenSet, - "safeHTML": SafeHTML, - "safeCSS": SafeCSS, - "safeURL": SafeURL, - "markdownify": Markdownify, - "first": First, - "where": Where, - "delimit": Delimit, - "sort": Sort, - "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": Mod, - "mul": func(a, b interface{}) (interface{}, error) { return doArithmetic(a, b, '*') }, - "modBool": ModBool, - "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, - "ref": Ref, - "relref": RelRef, - "apply": Apply, - "chomp": Chomp, - "replace": Replace, - "trim": Trim, - "dateFormat": DateFormat, - "getJSON": GetJSON, - "getCSV": GetCSV, - "seq": helpers.Seq, - "getenv": func(varName string) string { return os.Getenv(varName) }, - - // "getJson" is deprecated. Will be removed in 0.15. - "getJson": func(urlParts ...string) interface{} { - helpers.Deprecated("Template", "getJson", "getJSON") - return GetJSON(urlParts...) - }, - // "getJson" is deprecated. Will be removed in 0.15. - "getCsv": func(sep string, urlParts ...string) [][]string { - helpers.Deprecated("Template", "getCsv", "getCSV") - return GetCSV(sep, urlParts...) - }, - // "safeHtml" is deprecated. Will be removed in 0.15. - "safeHtml": func(text string) template.HTML { - helpers.Deprecated("Template", "safeHtml", "safeHTML") - return SafeHTML(text) - }, - // "safeCss" is deprecated. Will be removed in 0.15. - "safeCss": func(text string) template.CSS { - helpers.Deprecated("Template", "safeCss", "safeCSS") - return SafeCSS(text) - }, - // "safeUrl" is deprecated. Will be removed in 0.15. - "safeUrl": func(text string) template.URL { - helpers.Deprecated("Template", "safeUrl", "safeURL") - return SafeURL(text) - }, - } - -} diff --git a/tpl/template_funcs.go b/tpl/template_funcs.go new file mode 100644 index 000000000..996456f9f --- /dev/null +++ b/tpl/template_funcs.go @@ -0,0 +1,1228 @@ +// Copyright © 2013-14 Steve Francia . +// +// 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 tpl + +import ( + "bytes" + "errors" + "fmt" + "github.com/spf13/cast" + "github.com/spf13/hugo/helpers" + jww "github.com/spf13/jwalterweatherman" + "html" + "html/template" + "os" + "reflect" + "sort" + "strconv" + "strings" +) + +var funcMap template.FuncMap + +func Eq(x, y interface{}) bool { + normalize := func(v interface{}) interface{} { + vv := reflect.ValueOf(v) + switch vv.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return vv.Int() + case reflect.Float32, reflect.Float64: + return vv.Float() + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + return vv.Uint() + default: + return v + } + } + x = normalize(x) + y = normalize(y) + 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 + var leftStr, rightStr *string + var err error + 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, err = strconv.ParseFloat(av.String(), 64) + if err != nil { + str := av.String() + leftStr = &str + } + } + + 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, err = strconv.ParseFloat(bv.String(), 64) + if err != nil { + str := bv.String() + rightStr = &str + } + + } + + switch { + case leftStr == nil || rightStr == nil: + case *leftStr < *rightStr: + return 0, 1 + case *leftStr > *rightStr: + return 1, 0 + default: + return 0, 0 + } + + return left, right +} + +// Slicing in Slicestr is done by specifying a half-open range with +// two indices, start and end. 1 and 4 creates a slice including elements 1 through 3. +// The end index can be omitted, it defaults to the string's length. +func Slicestr(a interface{}, startEnd ...int) (string, error) { + aStr, err := cast.ToStringE(a) + if err != nil { + return "", err + } + + if len(startEnd) > 2 { + return "", errors.New("too many arguments") + } + + if len(startEnd) == 2 { + return aStr[startEnd[0]:startEnd[1]], nil + } else if len(startEnd) == 1 { + return aStr[startEnd[0]:], nil + } else { + return aStr[:], nil + } + +} + +// Substr extracts parts of a string, beginning at the character at the specified +// position, and returns the specified number of characters. +// +// It normally takes two parameters: start and length. +// It can also take one parameter: start, i.e. length is omitted, in which case +// the substring starting from start until the end of the string will be returned. +// +// To extract characters from the end of the string, use a negative start number. +// +// In addition, borrowing from the extended behavior described at http://php.net/substr, +// if length is given and is negative, then that many characters will be omitted from +// the end of string. +func Substr(a interface{}, nums ...int) (string, error) { + aStr, err := cast.ToStringE(a) + if err != nil { + return "", err + } + + var start, length int + switch len(nums) { + case 1: + start = nums[0] + length = len(aStr) + case 2: + start = nums[0] + length = nums[1] + default: + return "", errors.New("too many arguments") + } + + if start < -len(aStr) { + start = 0 + } + if start > len(aStr) { + return "", errors.New(fmt.Sprintf("start position out of bounds for %d-byte string", len(aStr))) + } + + var s, e int + if start >= 0 && length >= 0 { + s = start + e = start + length + } else if start < 0 && length >= 0 { + s = len(aStr) + start - length + 1 + e = len(aStr) + start + 1 + } else if start >= 0 && length < 0 { + s = start + e = len(aStr) + length + } else { + s = len(aStr) + start + e = len(aStr) + length + } + + if s > e { + return "", errors.New(fmt.Sprintf("calculated start position greater than end position: %d > %d", s, e)) + } + if e > len(aStr) { + e = len(aStr) + } + + return aStr[s:e], nil + +} + +func Split(a interface{}, delimiter string) ([]string, error) { + aStr, err := cast.ToStringE(a) + if err != nil { + return []string{}, err + } + return strings.Split(aStr, delimiter), nil +} + +func Intersect(l1, l2 interface{}) (interface{}, error) { + if l1 == nil || l2 == nil { + return make([]interface{}, 0), nil + } + + l1v := reflect.ValueOf(l1) + l2v := reflect.ValueOf(l2) + + switch l1v.Kind() { + case reflect.Array, reflect.Slice: + switch l2v.Kind() { + case reflect.Array, reflect.Slice: + r := reflect.MakeSlice(l1v.Type(), 0, 0) + for i := 0; i < l1v.Len(); i++ { + l1vv := l1v.Index(i) + for j := 0; j < l2v.Len(); j++ { + l2vv := l2v.Index(j) + switch l1vv.Kind() { + case reflect.String: + if l1vv.Type() == l2vv.Type() && l1vv.String() == l2vv.String() && !In(r, l2vv) { + r = reflect.Append(r, l2vv) + } + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + switch l2vv.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + if l1vv.Int() == l2vv.Int() && !In(r, l2vv) { + r = reflect.Append(r, l2vv) + } + } + case reflect.Float32, reflect.Float64: + switch l2vv.Kind() { + case reflect.Float32, reflect.Float64: + if l1vv.Float() == l2vv.Float() && !In(r, l2vv) { + r = reflect.Append(r, l2vv) + } + } + } + } + } + return r.Interface(), nil + default: + return nil, errors.New("can't iterate over " + reflect.ValueOf(l2).Type().String()) + } + default: + return nil, errors.New("can't iterate over " + reflect.ValueOf(l1).Type().String()) + } +} + +func In(l interface{}, v interface{}) bool { + lv := reflect.ValueOf(l) + vv := reflect.ValueOf(v) + + switch lv.Kind() { + case reflect.Array, reflect.Slice: + for i := 0; i < lv.Len(); i++ { + lvv := lv.Index(i) + switch lvv.Kind() { + case reflect.String: + if vv.Type() == lvv.Type() && vv.String() == lvv.String() { + return true + } + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + switch vv.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + if vv.Int() == lvv.Int() { + return true + } + } + case reflect.Float32, reflect.Float64: + switch vv.Kind() { + case reflect.Float32, reflect.Float64: + if vv.Float() == lvv.Float() { + return true + } + } + } + } + case reflect.String: + if vv.Type() == lv.Type() && strings.Contains(lv.String(), vv.String()) { + return true + } + } + return false +} + +// indirect is taken from 'text/template/exec.go' +func indirect(v reflect.Value) (rv reflect.Value, isNil bool) { + for ; v.Kind() == reflect.Ptr || v.Kind() == reflect.Interface; v = v.Elem() { + if v.IsNil() { + return v, true + } + if v.Kind() == reflect.Interface && v.NumMethod() > 0 { + break + } + } + return v, false +} + +// First is exposed to templates, to iterate over the first N items in a +// rangeable list. +func First(limit interface{}, seq interface{}) (interface{}, error) { + + if limit == nil || seq == nil { + return nil, errors.New("both limit and seq must be provided") + } + + limitv, err := cast.ToIntE(limit) + + if err != nil { + return nil, err + } + + if limitv < 1 { + return nil, errors.New("can't return negative/empty count of items from sequence") + } + + seqv := reflect.ValueOf(seq) + seqv, isNil := indirect(seqv) + if isNil { + return nil, errors.New("can't iterate over a nil value") + } + + 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 limitv > seqv.Len() { + limitv = seqv.Len() + } + return seqv.Slice(0, limitv).Interface(), nil +} + +var ( + zero reflect.Value + errorType = reflect.TypeOf((*error)(nil)).Elem() +) + +func evaluateSubElem(obj reflect.Value, elemName string) (reflect.Value, error) { + if !obj.IsValid() { + return zero, errors.New("can't evaluate an invalid value") + } + typ := obj.Type() + obj, isNil := indirect(obj) + + // first, check whether obj has a method. In this case, obj is + // an interface, a struct or its pointer. If obj is a struct, + // to check all T and *T method, use obj pointer type Value + objPtr := obj + if objPtr.Kind() != reflect.Interface && objPtr.CanAddr() { + objPtr = objPtr.Addr() + } + mt, ok := objPtr.Type().MethodByName(elemName) + if ok { + if mt.PkgPath != "" { + return zero, fmt.Errorf("%s is an unexported method of type %s", elemName, typ) + } + // struct pointer has one receiver argument and interface doesn't have an argument + if mt.Type.NumIn() > 1 || mt.Type.NumOut() == 0 || mt.Type.NumOut() > 2 { + return zero, fmt.Errorf("%s is a method of type %s but doesn't satisfy requirements", elemName, typ) + } + if mt.Type.NumOut() == 1 && mt.Type.Out(0).Implements(errorType) { + return zero, fmt.Errorf("%s is a method of type %s but doesn't satisfy requirements", elemName, typ) + } + if mt.Type.NumOut() == 2 && !mt.Type.Out(1).Implements(errorType) { + return zero, fmt.Errorf("%s is a method of type %s but doesn't satisfy requirements", elemName, typ) + } + res := objPtr.Method(mt.Index).Call([]reflect.Value{}) + if len(res) == 2 && !res[1].IsNil() { + return zero, fmt.Errorf("error at calling a method %s of type %s: %s", elemName, typ, res[1].Interface().(error)) + } + return res[0], nil + } + + // elemName isn't a method so next start to check whether it is + // a struct field or a map value. In both cases, it mustn't be + // a nil value + if isNil { + return zero, fmt.Errorf("can't evaluate a nil pointer of type %s by a struct field or map key name %s", typ, elemName) + } + switch obj.Kind() { + case reflect.Struct: + ft, ok := obj.Type().FieldByName(elemName) + if ok { + if ft.PkgPath != "" { + return zero, fmt.Errorf("%s is an unexported field of struct type %s", elemName, typ) + } + return obj.FieldByIndex(ft.Index), nil + } + return zero, fmt.Errorf("%s isn't a field of struct type %s", elemName, typ) + case reflect.Map: + kv := reflect.ValueOf(elemName) + if kv.Type().AssignableTo(obj.Type().Key()) { + return obj.MapIndex(kv), nil + } + return zero, fmt.Errorf("%s isn't a key of map type %s", elemName, typ) + } + return zero, fmt.Errorf("%s is neither a struct field, a method nor a map element of type %s", elemName, typ) +} + +func checkCondition(v, mv reflect.Value, op string) (bool, error) { + if !v.IsValid() || !mv.IsValid() { + return false, nil + } + + var isNil bool + v, isNil = indirect(v) + if isNil { + return false, nil + } + mv, isNil = indirect(mv) + if isNil { + return false, nil + } + + var ivp, imvp *int64 + var svp, smvp *string + var ima []int64 + var sma []string + if mv.Type() == v.Type() { + switch v.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + iv := v.Int() + ivp = &iv + imv := mv.Int() + imvp = &imv + case reflect.String: + sv := v.String() + svp = &sv + smv := mv.String() + smvp = &smv + } + } else { + if mv.Kind() != reflect.Array && mv.Kind() != reflect.Slice { + return false, nil + } + if mv.Type().Elem() != v.Type() { + return false, nil + } + switch v.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + iv := v.Int() + ivp = &iv + for i := 0; i < mv.Len(); i++ { + ima = append(ima, mv.Index(i).Int()) + } + case reflect.String: + sv := v.String() + svp = &sv + for i := 0; i < mv.Len(); i++ { + sma = append(sma, mv.Index(i).String()) + } + } + } + + switch op { + case "", "=", "==", "eq": + if ivp != nil && imvp != nil { + return *ivp == *imvp, nil + } else if svp != nil && smvp != nil { + return *svp == *smvp, nil + } + case "!=", "<>", "ne": + if ivp != nil && imvp != nil { + return *ivp != *imvp, nil + } else if svp != nil && smvp != nil { + return *svp != *smvp, nil + } + case ">=", "ge": + if ivp != nil && imvp != nil { + return *ivp >= *imvp, nil + } else if svp != nil && smvp != nil { + return *svp >= *smvp, nil + } + case ">", "gt": + if ivp != nil && imvp != nil { + return *ivp > *imvp, nil + } else if svp != nil && smvp != nil { + return *svp > *smvp, nil + } + case "<=", "le": + if ivp != nil && imvp != nil { + return *ivp <= *imvp, nil + } else if svp != nil && smvp != nil { + return *svp <= *smvp, nil + } + case "<", "lt": + if ivp != nil && imvp != nil { + return *ivp < *imvp, nil + } else if svp != nil && smvp != nil { + return *svp < *smvp, nil + } + case "in", "not in": + var r bool + if ivp != nil && len(ima) > 0 { + r = In(ima, *ivp) + } else if svp != nil { + if len(sma) > 0 { + r = In(sma, *svp) + } else if smvp != nil { + r = In(*smvp, *svp) + } + } else { + return false, nil + } + if op == "not in" { + return !r, nil + } else { + return r, nil + } + default: + return false, errors.New("no such an operator") + } + return false, nil +} + +func Where(seq, key interface{}, args ...interface{}) (r interface{}, err error) { + seqv := reflect.ValueOf(seq) + kv := reflect.ValueOf(key) + + var mv reflect.Value + var op string + switch len(args) { + case 1: + mv = reflect.ValueOf(args[0]) + case 2: + var ok bool + if op, ok = args[0].(string); !ok { + return nil, errors.New("operator argument must be string type") + } + op = strings.TrimSpace(strings.ToLower(op)) + mv = reflect.ValueOf(args[1]) + default: + return nil, errors.New("can't evaluate the array by no match argument or more than or equal to two arguments") + } + + seqv, isNil := indirect(seqv) + if isNil { + return nil, errors.New("can't iterate over a nil value of type " + reflect.ValueOf(seq).Type().String()) + } + + var path []string + if kv.Kind() == reflect.String { + path = strings.Split(strings.Trim(kv.String(), "."), ".") + } + + switch seqv.Kind() { + case reflect.Array, reflect.Slice: + rv := reflect.MakeSlice(seqv.Type(), 0, 0) + for i := 0; i < seqv.Len(); i++ { + var vvv reflect.Value + rvv := seqv.Index(i) + if kv.Kind() == reflect.String { + vvv = rvv + for _, elemName := range path { + vvv, err = evaluateSubElem(vvv, elemName) + if err != nil { + return nil, err + } + } + } else { + vv, _ := indirect(rvv) + if vv.Kind() == reflect.Map && kv.Type().AssignableTo(vv.Type().Key()) { + vvv = vv.MapIndex(kv) + } + } + if ok, err := checkCondition(vvv, mv, op); ok { + rv = reflect.Append(rv, rvv) + } else if err != nil { + return nil, err + } + } + return rv.Interface(), nil + default: + return nil, errors.New("can't iterate over " + reflect.ValueOf(seq).Type().String()) + } +} + +// Apply, given a map, array, or slice, returns a new slice with the function fname applied over it. +func Apply(seq interface{}, fname string, args ...interface{}) (interface{}, error) { + if seq == nil { + return make([]interface{}, 0), nil + } + + if fname == "apply" { + return nil, errors.New("can't apply myself (no turtles allowed)") + } + + seqv := reflect.ValueOf(seq) + seqv, isNil := indirect(seqv) + if isNil { + return nil, errors.New("can't iterate over a nil value") + } + + fn, found := funcMap[fname] + if !found { + return nil, errors.New("can't find function " + fname) + } + + fnv := reflect.ValueOf(fn) + + switch seqv.Kind() { + case reflect.Array, reflect.Slice: + r := make([]interface{}, seqv.Len()) + for i := 0; i < seqv.Len(); i++ { + vv := seqv.Index(i) + + vvv, err := applyFnToThis(fnv, vv, args...) + + if err != nil { + return nil, err + } + + r[i] = vvv.Interface() + } + + return r, nil + default: + return nil, errors.New("can't apply over " + reflect.ValueOf(seq).Type().String()) + } +} + +func applyFnToThis(fn, this reflect.Value, args ...interface{}) (reflect.Value, error) { + n := make([]reflect.Value, len(args)) + for i, arg := range args { + if arg == "." { + n[i] = this + } else { + n[i] = reflect.ValueOf(arg) + } + } + + res := fn.Call(n) + + if len(res) == 1 || res[1].IsNil() { + return res[0], nil + } else { + return reflect.ValueOf(nil), res[1].Interface().(error) + } +} + +func Delimit(seq, delimiter interface{}, last ...interface{}) (template.HTML, error) { + d, err := cast.ToStringE(delimiter) + if err != nil { + return "", err + } + + var dLast *string + for _, l := range last { + dStr, err := cast.ToStringE(l) + if err != nil { + dLast = nil + } + dLast = &dStr + break + } + + seqv := reflect.ValueOf(seq) + seqv, isNil := indirect(seqv) + if isNil { + return "", errors.New("can't iterate over a nil value") + } + + var str string + switch seqv.Kind() { + case reflect.Map: + sortSeq, err := Sort(seq) + if err != nil { + return "", err + } + seqv = reflect.ValueOf(sortSeq) + fallthrough + case reflect.Array, reflect.Slice, reflect.String: + for i := 0; i < seqv.Len(); i++ { + val := seqv.Index(i).Interface() + valStr, err := cast.ToStringE(val) + if err != nil { + continue + } + switch { + case i == seqv.Len()-2 && dLast != nil: + str += valStr + *dLast + case i == seqv.Len()-1: + str += valStr + default: + str += valStr + d + } + } + + default: + return "", errors.New("can't iterate over " + reflect.ValueOf(seq).Type().String()) + } + + return template.HTML(str), nil +} + +func Sort(seq interface{}, args ...interface{}) ([]interface{}, error) { + seqv := reflect.ValueOf(seq) + seqv, isNil := indirect(seqv) + if isNil { + return nil, errors.New("can't iterate over a nil value") + } + + // Create a list of pairs that will be used to do the sort + p := pairList{SortAsc: true} + p.Pairs = make([]pair, seqv.Len()) + + for i, l := range args { + dStr, err := cast.ToStringE(l) + switch { + case i == 0 && err != nil: + p.SortByField = "" + case i == 0 && err == nil: + p.SortByField = dStr + case i == 1 && err == nil && dStr == "desc": + p.SortAsc = false + case i == 1: + p.SortAsc = true + } + } + + var sorted []interface{} + switch seqv.Kind() { + case reflect.Array, reflect.Slice: + for i := 0; i < seqv.Len(); i++ { + p.Pairs[i].Key = reflect.ValueOf(i) + p.Pairs[i].Value = seqv.Index(i) + } + if p.SortByField == "" { + p.SortByField = "value" + } + + case reflect.Map: + keys := seqv.MapKeys() + for i := 0; i < seqv.Len(); i++ { + p.Pairs[i].Key = keys[i] + p.Pairs[i].Value = seqv.MapIndex(keys[i]) + } + + default: + return nil, errors.New("can't sort " + reflect.ValueOf(seq).Type().String()) + } + sorted = p.sort() + return sorted, nil +} + +// Credit for pair sorting method goes to Andrew Gerrand +// https://groups.google.com/forum/#!topic/golang-nuts/FT7cjmcL7gw +// A data structure to hold a key/value pair. +type pair struct { + Key reflect.Value + Value reflect.Value +} + +// A slice of pairs that implements sort.Interface to sort by Value. +type pairList struct { + Pairs []pair + SortByField string + SortAsc bool +} + +func (p pairList) Swap(i, j int) { p.Pairs[i], p.Pairs[j] = p.Pairs[j], p.Pairs[i] } +func (p pairList) Len() int { return len(p.Pairs) } +func (p pairList) Less(i, j int) bool { + var truth bool + switch { + case p.SortByField == "value": + iVal := p.Pairs[i].Value + jVal := p.Pairs[j].Value + truth = Lt(iVal.Interface(), jVal.Interface()) + + case p.SortByField != "": + if p.Pairs[i].Value.FieldByName(p.SortByField).IsValid() { + iVal := p.Pairs[i].Value.FieldByName(p.SortByField) + jVal := p.Pairs[j].Value.FieldByName(p.SortByField) + truth = Lt(iVal.Interface(), jVal.Interface()) + } + default: + iVal := p.Pairs[i].Key + jVal := p.Pairs[j].Key + truth = Lt(iVal.Interface(), jVal.Interface()) + } + return truth +} + +// sorts a pairList and returns a slice of sorted values +func (p pairList) sort() []interface{} { + if p.SortAsc { + sort.Sort(p) + } else { + sort.Sort(sort.Reverse(p)) + } + sorted := make([]interface{}, len(p.Pairs)) + for i, v := range p.Pairs { + sorted[i] = v.Value.Interface() + } + + return sorted +} + +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, k interface{}) interface{} { + av, isNil := indirect(reflect.ValueOf(a)) + if isNil { + return "" + } + + var avv reflect.Value + switch av.Kind() { + case reflect.Array, reflect.Slice: + index, ok := k.(int) + if ok && av.Len() > index { + avv = av.Index(index) + } + case reflect.Map: + kv := reflect.ValueOf(k) + if kv.Type().AssignableTo(av.Type().Key()) { + avv = av.MapIndex(kv) + } + } + + if avv.IsValid() { + switch avv.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return avv.Int() + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + return avv.Uint() + case reflect.Float32, reflect.Float64: + return avv.Float() + 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() + } + + return template.HTML(helpers.Highlight(html.UnescapeString(str), lang)) +} + +var markdownTrimPrefix = []byte("

") +var markdownTrimSuffix = []byte("

\n") + +func Markdownify(text string) template.HTML { + m := helpers.RenderBytes(&helpers.RenderingContext{Content: []byte(text), PageFmt: "markdown"}) + m = bytes.TrimPrefix(m, markdownTrimPrefix) + m = bytes.TrimSuffix(m, markdownTrimSuffix) + return template.HTML(m) +} + +func refPage(page interface{}, ref, methodName string) template.HTML { + value := reflect.ValueOf(page) + + method := value.MethodByName(methodName) + + if method.IsValid() && method.Type().NumIn() == 1 && method.Type().NumOut() == 2 { + result := method.Call([]reflect.Value{reflect.ValueOf(ref)}) + + url, err := result[0], result[1] + + if !err.IsNil() { + jww.ERROR.Printf("%s", err.Interface()) + return template.HTML(fmt.Sprintf("%s", err.Interface())) + } + + if url.String() == "" { + jww.ERROR.Printf("ref %s could not be found\n", ref) + return template.HTML(ref) + } + + return template.HTML(url.String()) + } + + jww.ERROR.Printf("Can only create references from Page and Node objects.") + return template.HTML(ref) +} + +func Ref(page interface{}, ref string) template.HTML { + return refPage(page, ref, "Ref") +} + +func RelRef(page interface{}, ref string) template.HTML { + return refPage(page, ref, "RelRef") +} + +func Chomp(text interface{}) (string, error) { + s, err := cast.ToStringE(text) + if err != nil { + return "", err + } + + return strings.TrimRight(s, "\r\n"), nil +} + +// Trim leading/trailing characters defined by b from a +func Trim(a interface{}, b string) (string, error) { + aStr, err := cast.ToStringE(a) + if err != nil { + return "", err + } + return strings.Trim(aStr, b), nil +} + +// Replace all occurences of b with c in a +func Replace(a, b, c interface{}) (string, error) { + aStr, err := cast.ToStringE(a) + if err != nil { + return "", err + } + bStr, err := cast.ToStringE(b) + if err != nil { + return "", err + } + cStr, err := cast.ToStringE(c) + if err != nil { + return "", err + } + return strings.Replace(aStr, bStr, cStr, -1), nil +} + +// DateFormat converts the textual representation of the datetime string into +// the other form or returns it of the time.Time value. These are formatted +// with the layout string +func DateFormat(layout string, v interface{}) (string, error) { + t, err := cast.ToTimeE(v) + if err != nil { + return "", err + } + return t.Format(layout), nil +} + +func SafeHTML(text string) template.HTML { + return template.HTML(text) +} + +// "safeHTMLAttr" is currently disabled, pending further discussion +// on its use case. 2015-01-19 +func SafeHTMLAttr(text string) template.HTMLAttr { + return template.HTMLAttr(text) +} + +func SafeCSS(text string) template.CSS { + return template.CSS(text) +} + +func SafeURL(text string) template.URL { + return template.URL(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") + } +} + +func Mod(a, b interface{}) (int64, error) { + av := reflect.ValueOf(a) + bv := reflect.ValueOf(b) + var ai, bi int64 + + switch av.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + ai = av.Int() + default: + return 0, errors.New("Modulo operator can't be used with non integer value") + } + + switch bv.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + bi = bv.Int() + default: + return 0, errors.New("Modulo operator can't be used with non integer value") + } + + if bi == 0 { + return 0, errors.New("The number can't be divided by zero at modulo operation") + } + + return ai % bi, nil +} + +func ModBool(a, b interface{}) (bool, error) { + res, err := Mod(a, b) + if err != nil { + return false, err + } + return res == int64(0), nil +} + +func init() { + funcMap = template.FuncMap{ + "urlize": helpers.URLize, + "sanitizeURL": helpers.SanitizeURL, + "sanitizeurl": helpers.SanitizeURL, + "eq": Eq, + "ne": Ne, + "gt": Gt, + "ge": Ge, + "lt": Lt, + "le": Le, + "in": In, + "slicestr": Slicestr, + "substr": Substr, + "split": Split, + "intersect": Intersect, + "isSet": IsSet, + "isset": IsSet, + "echoParam": ReturnWhenSet, + "safeHTML": SafeHTML, + "safeCSS": SafeCSS, + "safeURL": SafeURL, + "markdownify": Markdownify, + "first": First, + "where": Where, + "delimit": Delimit, + "sort": Sort, + "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": Mod, + "mul": func(a, b interface{}) (interface{}, error) { return doArithmetic(a, b, '*') }, + "modBool": ModBool, + "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, + "ref": Ref, + "relref": RelRef, + "apply": Apply, + "chomp": Chomp, + "replace": Replace, + "trim": Trim, + "dateFormat": DateFormat, + "getJSON": GetJSON, + "getCSV": GetCSV, + "seq": helpers.Seq, + "getenv": func(varName string) string { return os.Getenv(varName) }, + + // "getJson" is deprecated. Will be removed in 0.15. + "getJson": func(urlParts ...string) interface{} { + helpers.Deprecated("Template", "getJson", "getJSON") + return GetJSON(urlParts...) + }, + // "getJson" is deprecated. Will be removed in 0.15. + "getCsv": func(sep string, urlParts ...string) [][]string { + helpers.Deprecated("Template", "getCsv", "getCSV") + return GetCSV(sep, urlParts...) + }, + // "safeHtml" is deprecated. Will be removed in 0.15. + "safeHtml": func(text string) template.HTML { + helpers.Deprecated("Template", "safeHtml", "safeHTML") + return SafeHTML(text) + }, + // "safeCss" is deprecated. Will be removed in 0.15. + "safeCss": func(text string) template.CSS { + helpers.Deprecated("Template", "safeCss", "safeCSS") + return SafeCSS(text) + }, + // "safeUrl" is deprecated. Will be removed in 0.15. + "safeUrl": func(text string) template.URL { + helpers.Deprecated("Template", "safeUrl", "safeURL") + return SafeURL(text) + }, + } + +} diff --git a/tpl/template_funcs_test.go b/tpl/template_funcs_test.go new file mode 100644 index 000000000..8e99f2fb4 --- /dev/null +++ b/tpl/template_funcs_test.go @@ -0,0 +1,1222 @@ +package tpl + +import ( + "bytes" + "errors" + "fmt" + "github.com/stretchr/testify/assert" + "html/template" + "path" + "reflect" + "runtime" + "testing" + "time" +) + +type tstNoStringer struct { +} + +type tstCompareType int + +const ( + tstEq tstCompareType = iota + tstNe + tstGt + tstGe + tstLt + tstLe +) + +func tstIsEq(tp tstCompareType) bool { + return tp == tstEq || tp == tstGe || tp == tstLe +} + +func tstIsGt(tp tstCompareType) bool { + return tp == tstGt || tp == tstGe +} + +func tstIsLt(tp tstCompareType) bool { + return tp == tstLt || tp == tstLe +} + +func TestCompare(t *testing.T) { + for _, this := range []struct { + tstCompareType + funcUnderTest func(a, b interface{}) bool + }{ + {tstGt, Gt}, + {tstLt, Lt}, + {tstGe, Ge}, + {tstLe, Le}, + {tstEq, Eq}, + {tstNe, Ne}, + } { + doTestCompare(t, this.tstCompareType, this.funcUnderTest) + } + +} + +func doTestCompare(t *testing.T, tp tstCompareType, funcUnderTest func(a, b interface{}) bool) { + for i, this := range []struct { + left interface{} + right interface{} + expectIndicator int + }{ + {5, 8, -1}, + {8, 5, 1}, + {5, 5, 0}, + {int(5), int64(5), 0}, + {int32(5), int(5), 0}, + {int16(4), int(5), -1}, + {uint(15), uint64(15), 0}, + {-2, 1, -1}, + {2, -5, 1}, + {0.0, 1.23, -1}, + {1.1, 1.1, 0}, + {float32(1.0), float64(1.0), 0}, + {1.23, 0.0, 1}, + {"5", "5", 0}, + {"8", "5", 1}, + {"5", "0001", 1}, + {[]int{100, 99}, []int{1, 2, 3, 4}, -1}, + } { + result := funcUnderTest(this.left, this.right) + success := false + + if this.expectIndicator == 0 { + if tstIsEq(tp) { + success = result + } else { + success = !result + } + } + + if this.expectIndicator < 0 { + success = result && (tstIsLt(tp) || tp == tstNe) + success = success || (!result && !tstIsLt(tp)) + } + + if this.expectIndicator > 0 { + success = result && (tstIsGt(tp) || tp == tstNe) + success = success || (!result && (!tstIsGt(tp) || tp != tstNe)) + } + + if !success { + t.Errorf("[%d][%s] %v compared to %v: %t", i, path.Base(runtime.FuncForPC(reflect.ValueOf(funcUnderTest).Pointer()).Name()), this.left, this.right, result) + } + } +} + +func TestArethmic(t *testing.T) { + for i, this := range []struct { + a interface{} + b interface{} + op rune + expect interface{} + }{ + {1, 2, '+', int64(3)}, + {1, 2, '-', int64(-1)}, + {2, 2, '*', int64(4)}, + {4, 2, '/', int64(2)}, + {uint8(1), uint8(3), '+', uint64(4)}, + {uint8(3), uint8(2), '-', uint64(1)}, + {uint8(2), uint8(2), '*', uint64(4)}, + {uint16(4), uint8(2), '/', uint64(2)}, + {4, 2, '¤', false}, + {4, 0, '/', false}, + } { + // TODO(bep): Take precision into account. + result, err := doArithmetic(this.a, this.b, this.op) + if b, ok := this.expect.(bool); ok && !b { + if err == nil { + t.Errorf("[%d] doArethmic didn't return an expected error", i) + } + } else { + if err != nil { + t.Errorf("[%d] failed: %s", i, err) + continue + } + if !reflect.DeepEqual(result, this.expect) { + t.Errorf("[%d] doArethmic got %v (%T) but expected %v (%T)", i, result, result, this.expect, this.expect) + } + } + } +} + +func TestMod(t *testing.T) { + for i, this := range []struct { + a interface{} + b interface{} + expect interface{} + }{ + {3, 2, int64(1)}, + {3, 1, int64(0)}, + {3, 0, false}, + {0, 3, int64(0)}, + {3.1, 2, false}, + {3, 2.1, false}, + {3.1, 2.1, false}, + {int8(3), int8(2), int64(1)}, + {int16(3), int16(2), int64(1)}, + {int32(3), int32(2), int64(1)}, + {int64(3), int64(2), int64(1)}, + } { + result, err := Mod(this.a, this.b) + if b, ok := this.expect.(bool); ok && !b { + if err == nil { + t.Errorf("[%d] modulo didn't return an expected error", i) + } + } else { + if err != nil { + t.Errorf("[%d] failed: %s", i, err) + continue + } + if !reflect.DeepEqual(result, this.expect) { + t.Errorf("[%d] modulo got %v but expected %v", i, result, this.expect) + } + } + } +} + +func TestModBool(t *testing.T) { + for i, this := range []struct { + a interface{} + b interface{} + expect interface{} + }{ + {3, 3, true}, + {3, 2, false}, + {3, 1, true}, + {3, 0, nil}, + {0, 3, true}, + {3.1, 2, nil}, + {3, 2.1, nil}, + {3.1, 2.1, nil}, + {int8(3), int8(3), true}, + {int8(3), int8(2), false}, + {int16(3), int16(3), true}, + {int16(3), int16(2), false}, + {int32(3), int32(3), true}, + {int32(3), int32(2), false}, + {int64(3), int64(3), true}, + {int64(3), int64(2), false}, + } { + result, err := ModBool(this.a, this.b) + if this.expect == nil { + if err == nil { + t.Errorf("[%d] modulo didn't return an expected error", i) + } + } else { + if err != nil { + t.Errorf("[%d] failed: %s", i, err) + continue + } + if !reflect.DeepEqual(result, this.expect) { + t.Errorf("[%d] modulo got %v but expected %v", i, result, this.expect) + } + } + } +} + +func TestFirst(t *testing.T) { + for i, this := range []struct { + count interface{} + sequence interface{} + expect interface{} + }{ + {int(2), []string{"a", "b", "c"}, []string{"a", "b"}}, + {int32(3), []string{"a", "b"}, []string{"a", "b"}}, + {int64(2), []int{100, 200, 300}, []int{100, 200}}, + {100, []int{100, 200}, []int{100, 200}}, + {"1", []int{100, 200, 300}, []int{100}}, + {int64(-1), []int{100, 200, 300}, false}, + {"noint", []int{100, 200, 300}, false}, + {1, nil, false}, + {nil, []int{100}, false}, + {1, t, false}, + } { + results, err := First(this.count, this.sequence) + if b, ok := this.expect.(bool); ok && !b { + if err == nil { + t.Errorf("[%d] First didn't return an expected error", i) + } + } else { + if err != nil { + t.Errorf("[%d] failed: %s", i, err) + continue + } + if !reflect.DeepEqual(results, this.expect) { + t.Errorf("[%d] First %d items, got %v but expected %v", i, this.count, results, this.expect) + } + } + } +} + +func TestIn(t *testing.T) { + for i, this := range []struct { + v1 interface{} + v2 interface{} + expect bool + }{ + {[]string{"a", "b", "c"}, "b", true}, + {[]string{"a", "b", "c"}, "d", false}, + {[]string{"a", "12", "c"}, 12, false}, + {[]int{1, 2, 4}, 2, true}, + {[]int{1, 2, 4}, 3, false}, + {[]float64{1.23, 2.45, 4.67}, 1.23, true}, + {[]float64{1.234567, 2.45, 4.67}, 1.234568, false}, + {"this substring should be found", "substring", true}, + {"this substring should not be found", "subseastring", false}, + } { + result := In(this.v1, this.v2) + + if result != this.expect { + t.Errorf("[%d] Got %v but expected %v", i, result, this.expect) + } + } +} + +func TestSlicestr(t *testing.T) { + for i, this := range []struct { + v1 interface{} + v2 []int + expect interface{} + }{ + {"abc", []int{1, 2}, "b"}, + {"abc", []int{1, 3}, "bc"}, + {"abc", []int{0, 1}, "a"}, + {"abcdef", []int{}, "abcdef"}, + {"abcdef", []int{0, 6}, "abcdef"}, + {"abcdef", []int{0, 2}, "ab"}, + {"abcdef", []int{2}, "cdef"}, + {123, []int{1, 3}, "23"}, + {123, []int{1, 2, 3}, false}, + {tstNoStringer{}, []int{0, 1}, false}, + } { + result, err := Slicestr(this.v1, this.v2...) + + if b, ok := this.expect.(bool); ok && !b { + if err == nil { + t.Errorf("[%d] Slice didn't return an expected error", i) + } + } else { + if err != nil { + t.Errorf("[%d] failed: %s", i, err) + continue + } + if !reflect.DeepEqual(result, this.expect) { + t.Errorf("[%d] Got %s but expected %s", i, result, this.expect) + } + } + } +} + +func TestSubstr(t *testing.T) { + for i, this := range []struct { + v1 interface{} + v2 int + v3 int + expect interface{} + }{ + {"abc", 1, 2, "bc"}, + {"abc", 0, 1, "a"}, + {"abcdef", -1, 2, "ef"}, + {"abcdef", -3, 3, "bcd"}, + {"abcdef", 0, -1, "abcde"}, + {"abcdef", 2, -1, "cde"}, + {"abcdef", 4, -4, false}, + {"abcdef", 7, 1, false}, + {"abcdef", 1, 100, "bcdef"}, + {"abcdef", -100, 3, "abc"}, + {"abcdef", -3, -1, "de"}, + {123, 1, 3, "23"}, + {1.2e3, 0, 4, "1200"}, + {tstNoStringer{}, 0, 1, false}, + } { + result, err := Substr(this.v1, this.v2, this.v3) + + if b, ok := this.expect.(bool); ok && !b { + if err == nil { + t.Errorf("[%d] Substr didn't return an expected error", i) + } + } else { + if err != nil { + t.Errorf("[%d] failed: %s", i, err) + continue + } + if !reflect.DeepEqual(result, this.expect) { + t.Errorf("[%d] Got %s but expected %s", i, result, this.expect) + } + } + } +} + +func TestSplit(t *testing.T) { + for i, this := range []struct { + v1 interface{} + v2 string + expect interface{} + }{ + {"a, b", ", ", []string{"a", "b"}}, + {"a & b & c", " & ", []string{"a", "b", "c"}}, + {"http://exmaple.com", "http://", []string{"", "exmaple.com"}}, + {123, "2", []string{"1", "3"}}, + {tstNoStringer{}, ",", false}, + } { + result, err := Split(this.v1, this.v2) + + if b, ok := this.expect.(bool); ok && !b { + if err == nil { + t.Errorf("[%d] Split didn't return an expected error", i) + } + } else { + if err != nil { + t.Errorf("[%d] failed: %s", i, err) + continue + } + if !reflect.DeepEqual(result, this.expect) { + t.Errorf("[%d] Got %s but expected %s", i, result, this.expect) + } + } + } + +} + +func TestIntersect(t *testing.T) { + for i, this := range []struct { + sequence1 interface{} + sequence2 interface{} + expect interface{} + }{ + {[]string{"a", "b", "c"}, []string{"a", "b"}, []string{"a", "b"}}, + {[]string{"a", "b"}, []string{"a", "b", "c"}, []string{"a", "b"}}, + {[]string{"a", "b", "c"}, []string{"d", "e"}, []string{}}, + {[]string{}, []string{}, []string{}}, + {[]string{"a", "b"}, nil, make([]interface{}, 0)}, + {nil, []string{"a", "b"}, make([]interface{}, 0)}, + {nil, nil, make([]interface{}, 0)}, + {[]string{"1", "2"}, []int{1, 2}, []string{}}, + {[]int{1, 2}, []string{"1", "2"}, []int{}}, + {[]int{1, 2, 4}, []int{2, 4}, []int{2, 4}}, + {[]int{2, 4}, []int{1, 2, 4}, []int{2, 4}}, + {[]int{1, 2, 4}, []int{3, 6}, []int{}}, + {[]float64{2.2, 4.4}, []float64{1.1, 2.2, 4.4}, []float64{2.2, 4.4}}, + } { + results, err := Intersect(this.sequence1, this.sequence2) + if err != nil { + t.Errorf("[%d] failed: %s", i, err) + continue + } + if !reflect.DeepEqual(results, this.expect) { + t.Errorf("[%d] Got %v but expected %v", i, results, this.expect) + } + } + + _, err1 := Intersect("not an array or slice", []string{"a"}) + + if err1 == nil { + t.Error("Excpected error for non array as first arg") + } + + _, err2 := Intersect([]string{"a"}, "not an array or slice") + + if err2 == nil { + t.Error("Excpected error for non array as second arg") + } +} + +func TestIsSet(t *testing.T) { + aSlice := []interface{}{1, 2, 3, 5} + aMap := map[string]interface{}{"a": 1, "b": 2} + + assert.True(t, IsSet(aSlice, 2)) + assert.True(t, IsSet(aMap, "b")) + assert.False(t, IsSet(aSlice, 22)) + assert.False(t, IsSet(aMap, "bc")) +} + +func (x *TstX) TstRp() string { + return "r" + x.A +} + +func (x TstX) TstRv() string { + return "r" + x.B +} + +func (x TstX) unexportedMethod() string { + return x.unexported +} + +func (x TstX) MethodWithArg(s string) string { + return s +} + +func (x TstX) MethodReturnNothing() {} + +func (x TstX) MethodReturnErrorOnly() error { + return errors.New("something error occured") +} + +func (x TstX) MethodReturnTwoValues() (string, string) { + return "foo", "bar" +} + +func (x TstX) MethodReturnValueWithError() (string, error) { + return "", errors.New("something error occured") +} + +func (x TstX) String() string { + return fmt.Sprintf("A: %s, B: %s", x.A, x.B) +} + +type TstX struct { + A, B string + unexported string +} + +func TestEvaluateSubElem(t *testing.T) { + tstx := TstX{A: "foo", B: "bar"} + var inner struct { + S fmt.Stringer + } + inner.S = tstx + interfaceValue := reflect.ValueOf(&inner).Elem().Field(0) + + for i, this := range []struct { + value reflect.Value + key string + expect interface{} + }{ + {reflect.ValueOf(tstx), "A", "foo"}, + {reflect.ValueOf(&tstx), "TstRp", "rfoo"}, + {reflect.ValueOf(tstx), "TstRv", "rbar"}, + //{reflect.ValueOf(map[int]string{1: "foo", 2: "bar"}), 1, "foo"}, + {reflect.ValueOf(map[string]string{"key1": "foo", "key2": "bar"}), "key1", "foo"}, + {interfaceValue, "String", "A: foo, B: bar"}, + {reflect.Value{}, "foo", false}, + //{reflect.ValueOf(map[int]string{1: "foo", 2: "bar"}), 1.2, false}, + {reflect.ValueOf(tstx), "unexported", false}, + {reflect.ValueOf(tstx), "unexportedMethod", false}, + {reflect.ValueOf(tstx), "MethodWithArg", false}, + {reflect.ValueOf(tstx), "MethodReturnNothing", false}, + {reflect.ValueOf(tstx), "MethodReturnErrorOnly", false}, + {reflect.ValueOf(tstx), "MethodReturnTwoValues", false}, + {reflect.ValueOf(tstx), "MethodReturnValueWithError", false}, + {reflect.ValueOf((*TstX)(nil)), "A", false}, + {reflect.ValueOf(tstx), "C", false}, + {reflect.ValueOf(map[int]string{1: "foo", 2: "bar"}), "1", false}, + {reflect.ValueOf([]string{"foo", "bar"}), "1", false}, + } { + result, err := evaluateSubElem(this.value, this.key) + if b, ok := this.expect.(bool); ok && !b { + if err == nil { + t.Errorf("[%d] evaluateSubElem didn't return an expected error", i) + } + } else { + if err != nil { + t.Errorf("[%d] failed: %s", i, err) + continue + } + if result.Kind() != reflect.String || result.String() != this.expect { + t.Errorf("[%d] evaluateSubElem with %v got %v but expected %v", i, this.key, result, this.expect) + } + } + } +} + +func TestCheckCondition(t *testing.T) { + type expect struct { + result bool + isError bool + } + + for i, this := range []struct { + value reflect.Value + match reflect.Value + op string + expect + }{ + {reflect.ValueOf(123), reflect.ValueOf(123), "", expect{true, false}}, + {reflect.ValueOf("foo"), reflect.ValueOf("foo"), "", expect{true, false}}, + {reflect.ValueOf(123), reflect.ValueOf(456), "!=", expect{true, false}}, + {reflect.ValueOf("foo"), reflect.ValueOf("bar"), "!=", expect{true, false}}, + {reflect.ValueOf(456), reflect.ValueOf(123), ">=", expect{true, false}}, + {reflect.ValueOf("foo"), reflect.ValueOf("bar"), ">=", expect{true, false}}, + {reflect.ValueOf(456), reflect.ValueOf(123), ">", expect{true, false}}, + {reflect.ValueOf("foo"), reflect.ValueOf("bar"), ">", expect{true, false}}, + {reflect.ValueOf(123), reflect.ValueOf(456), "<=", expect{true, false}}, + {reflect.ValueOf("bar"), reflect.ValueOf("foo"), "<=", expect{true, false}}, + {reflect.ValueOf(123), reflect.ValueOf(456), "<", expect{true, false}}, + {reflect.ValueOf("bar"), reflect.ValueOf("foo"), "<", expect{true, false}}, + {reflect.ValueOf(123), reflect.ValueOf([]int{123, 45, 678}), "in", expect{true, false}}, + {reflect.ValueOf("foo"), reflect.ValueOf([]string{"foo", "bar", "baz"}), "in", expect{true, false}}, + {reflect.ValueOf(123), reflect.ValueOf([]int{45, 678}), "not in", expect{true, false}}, + {reflect.ValueOf("foo"), reflect.ValueOf([]string{"bar", "baz"}), "not in", expect{true, false}}, + {reflect.ValueOf("foo"), reflect.ValueOf("bar-foo-baz"), "in", expect{true, false}}, + {reflect.ValueOf("foo"), reflect.ValueOf("bar--baz"), "not in", expect{true, false}}, + {reflect.Value{}, reflect.ValueOf("foo"), "", expect{false, false}}, + {reflect.ValueOf("foo"), reflect.Value{}, "", expect{false, false}}, + {reflect.ValueOf((*TstX)(nil)), reflect.ValueOf("foo"), "", expect{false, false}}, + {reflect.ValueOf("foo"), reflect.ValueOf((*TstX)(nil)), "", expect{false, false}}, + {reflect.ValueOf("foo"), reflect.ValueOf(map[int]string{}), "", expect{false, false}}, + {reflect.ValueOf("foo"), reflect.ValueOf([]int{1, 2}), "", expect{false, false}}, + {reflect.ValueOf(123), reflect.ValueOf([]int{}), "in", expect{false, false}}, + {reflect.ValueOf(123), reflect.ValueOf(123), "op", expect{false, true}}, + } { + result, err := checkCondition(this.value, this.match, this.op) + if this.expect.isError { + if err == nil { + t.Errorf("[%d] checkCondition didn't return an expected error", i) + } + } else { + if err != nil { + t.Errorf("[%d] failed: %s", i, err) + continue + } + if result != this.expect.result { + t.Errorf("[%d] check condition %v %s %v, got %v but expected %v", i, this.value, this.op, this.match, result, this.expect.result) + } + } + } +} + +func TestWhere(t *testing.T) { + // TODO(spf): Put these page tests back in + //page1 := &Page{contentType: "v", Source: Source{File: *source.NewFile("/x/y/z/source.md")}} + //page2 := &Page{contentType: "w", Source: Source{File: *source.NewFile("/y/z/a/source.md")}} + + type Mid struct { + Tst TstX + } + + for i, this := range []struct { + sequence interface{} + key interface{} + op string + match interface{} + expect interface{} + }{ + { + sequence: []map[int]string{ + {1: "a", 2: "m"}, {1: "c", 2: "d"}, {1: "e", 3: "m"}, + }, + key: 2, match: "m", + expect: []map[int]string{ + {1: "a", 2: "m"}, + }, + }, + { + sequence: []map[string]int{ + {"a": 1, "b": 2}, {"a": 3, "b": 4}, {"a": 5, "x": 4}, + }, + key: "b", match: 4, + expect: []map[string]int{ + {"a": 3, "b": 4}, + }, + }, + { + sequence: []TstX{ + {A: "a", B: "b"}, {A: "c", B: "d"}, {A: "e", B: "f"}, + }, + key: "B", match: "f", + expect: []TstX{ + {A: "e", B: "f"}, + }, + }, + { + sequence: []*map[int]string{ + {1: "a", 2: "m"}, {1: "c", 2: "d"}, {1: "e", 3: "m"}, + }, + key: 2, match: "m", + expect: []*map[int]string{ + {1: "a", 2: "m"}, + }, + }, + { + sequence: []*TstX{ + {A: "a", B: "b"}, {A: "c", B: "d"}, {A: "e", B: "f"}, + }, + key: "B", match: "f", + expect: []*TstX{ + {A: "e", B: "f"}, + }, + }, + { + sequence: []*TstX{ + {A: "a", B: "b"}, {A: "c", B: "d"}, {A: "e", B: "c"}, + }, + key: "TstRp", match: "rc", + expect: []*TstX{ + {A: "c", B: "d"}, + }, + }, + { + sequence: []TstX{ + {A: "a", B: "b"}, {A: "c", B: "d"}, {A: "e", B: "c"}, + }, + key: "TstRv", match: "rc", + expect: []TstX{ + {A: "e", B: "c"}, + }, + }, + { + sequence: []map[string]TstX{ + {"foo": TstX{A: "a", B: "b"}}, {"foo": TstX{A: "c", B: "d"}}, {"foo": TstX{A: "e", B: "f"}}, + }, + key: "foo.B", match: "d", + expect: []map[string]TstX{ + {"foo": TstX{A: "c", B: "d"}}, + }, + }, + { + sequence: []map[string]TstX{ + {"foo": TstX{A: "a", B: "b"}}, {"foo": TstX{A: "c", B: "d"}}, {"foo": TstX{A: "e", B: "f"}}, + }, + key: ".foo.B", match: "d", + expect: []map[string]TstX{ + {"foo": TstX{A: "c", B: "d"}}, + }, + }, + { + sequence: []map[string]TstX{ + {"foo": TstX{A: "a", B: "b"}}, {"foo": TstX{A: "c", B: "d"}}, {"foo": TstX{A: "e", B: "f"}}, + }, + key: "foo.TstRv", match: "rd", + expect: []map[string]TstX{ + {"foo": TstX{A: "c", B: "d"}}, + }, + }, + { + sequence: []map[string]*TstX{ + {"foo": &TstX{A: "a", B: "b"}}, {"foo": &TstX{A: "c", B: "d"}}, {"foo": &TstX{A: "e", B: "f"}}, + }, + key: "foo.TstRp", match: "rc", + expect: []map[string]*TstX{ + {"foo": &TstX{A: "c", B: "d"}}, + }, + }, + { + sequence: []map[string]Mid{ + {"foo": Mid{Tst: TstX{A: "a", B: "b"}}}, {"foo": Mid{Tst: TstX{A: "c", B: "d"}}}, {"foo": Mid{Tst: TstX{A: "e", B: "f"}}}, + }, + key: "foo.Tst.B", match: "d", + expect: []map[string]Mid{ + {"foo": Mid{Tst: TstX{A: "c", B: "d"}}}, + }, + }, + { + sequence: []map[string]Mid{ + {"foo": Mid{Tst: TstX{A: "a", B: "b"}}}, {"foo": Mid{Tst: TstX{A: "c", B: "d"}}}, {"foo": Mid{Tst: TstX{A: "e", B: "f"}}}, + }, + key: "foo.Tst.TstRv", match: "rd", + expect: []map[string]Mid{ + {"foo": Mid{Tst: TstX{A: "c", B: "d"}}}, + }, + }, + { + sequence: []map[string]*Mid{ + {"foo": &Mid{Tst: TstX{A: "a", B: "b"}}}, {"foo": &Mid{Tst: TstX{A: "c", B: "d"}}}, {"foo": &Mid{Tst: TstX{A: "e", B: "f"}}}, + }, + key: "foo.Tst.TstRp", match: "rc", + expect: []map[string]*Mid{ + {"foo": &Mid{Tst: TstX{A: "c", B: "d"}}}, + }, + }, + { + sequence: []map[string]int{ + {"a": 1, "b": 2}, {"a": 3, "b": 4}, {"a": 5, "b": 6}, + }, + key: "b", op: ">", match: 3, + expect: []map[string]int{ + {"a": 3, "b": 4}, {"a": 5, "b": 6}, + }, + }, + { + sequence: []TstX{ + {A: "a", B: "b"}, {A: "c", B: "d"}, {A: "e", B: "f"}, + }, + key: "B", op: "!=", match: "f", + expect: []TstX{ + {A: "a", B: "b"}, {A: "c", B: "d"}, + }, + }, + { + sequence: []map[string]int{ + {"a": 1, "b": 2}, {"a": 3, "b": 4}, {"a": 5, "b": 6}, + }, + key: "b", op: "in", match: []int{3, 4, 5}, + expect: []map[string]int{ + {"a": 3, "b": 4}, + }, + }, + { + sequence: []TstX{ + {A: "a", B: "b"}, {A: "c", B: "d"}, {A: "e", B: "f"}, + }, + key: "B", op: "not in", match: []string{"c", "d", "e"}, + expect: []TstX{ + {A: "a", B: "b"}, {A: "e", B: "f"}, + }, + }, + {sequence: (*[]TstX)(nil), key: "A", match: "a", expect: false}, + {sequence: TstX{A: "a", B: "b"}, key: "A", match: "a", expect: false}, + {sequence: []map[string]*TstX{{"foo": nil}}, key: "foo.B", match: "d", expect: false}, + { + sequence: []TstX{ + {A: "a", B: "b"}, {A: "c", B: "d"}, {A: "e", B: "f"}, + }, + key: "B", op: "op", match: "f", + expect: false, + }, + //{[]*Page{page1, page2}, "Type", "v", []*Page{page1}}, + //{[]*Page{page1, page2}, "Section", "y", []*Page{page2}}, + } { + var results interface{} + var err error + if len(this.op) > 0 { + results, err = Where(this.sequence, this.key, this.op, this.match) + } else { + results, err = Where(this.sequence, this.key, this.match) + } + if b, ok := this.expect.(bool); ok && !b { + if err == nil { + t.Errorf("[%d] Where didn't return an expected error", i) + } + } else { + if err != nil { + t.Errorf("[%d] failed: %s", i, err) + continue + } + if !reflect.DeepEqual(results, this.expect) { + t.Errorf("[%d] Where clause matching %v with %v, got %v but expected %v", i, this.key, this.match, results, this.expect) + } + } + } + + var err error + _, err = Where(map[string]int{"a": 1, "b": 2}, "a", []byte("="), 1) + if err == nil { + t.Errorf("Where called with none string op value didn't return an expected error") + } + + _, err = Where(map[string]int{"a": 1, "b": 2}, "a", []byte("="), 1, 2) + if err == nil { + t.Errorf("Where called with more than two variable arguments didn't return an expected error") + } + + _, err = Where(map[string]int{"a": 1, "b": 2}, "a") + if err == nil { + t.Errorf("Where called with no variable arguments didn't return an expected error") + } +} + +func TestDelimit(t *testing.T) { + for i, this := range []struct { + sequence interface{} + delimiter interface{} + last interface{} + expect template.HTML + }{ + {[]string{"class1", "class2", "class3"}, " ", nil, "class1 class2 class3"}, + {[]int{1, 2, 3, 4, 5}, ",", nil, "1,2,3,4,5"}, + {[]int{1, 2, 3, 4, 5}, ", ", nil, "1, 2, 3, 4, 5"}, + {[]string{"class1", "class2", "class3"}, " ", " and ", "class1 class2 and class3"}, + {[]int{1, 2, 3, 4, 5}, ",", ",", "1,2,3,4,5"}, + {[]int{1, 2, 3, 4, 5}, ", ", ", and ", "1, 2, 3, 4, and 5"}, + // test maps with and without sorting required + {map[string]int{"1": 10, "2": 20, "3": 30, "4": 40, "5": 50}, "--", nil, "10--20--30--40--50"}, + {map[string]int{"3": 10, "2": 20, "1": 30, "4": 40, "5": 50}, "--", nil, "30--20--10--40--50"}, + {map[string]string{"1": "10", "2": "20", "3": "30", "4": "40", "5": "50"}, "--", nil, "10--20--30--40--50"}, + {map[string]string{"3": "10", "2": "20", "1": "30", "4": "40", "5": "50"}, "--", nil, "30--20--10--40--50"}, + {map[string]string{"one": "10", "two": "20", "three": "30", "four": "40", "five": "50"}, "--", nil, "50--40--10--30--20"}, + {map[int]string{1: "10", 2: "20", 3: "30", 4: "40", 5: "50"}, "--", nil, "10--20--30--40--50"}, + {map[int]string{3: "10", 2: "20", 1: "30", 4: "40", 5: "50"}, "--", nil, "30--20--10--40--50"}, + {map[float64]string{3.3: "10", 2.3: "20", 1.3: "30", 4.3: "40", 5.3: "50"}, "--", nil, "30--20--10--40--50"}, + // test maps with a last delimiter + {map[string]int{"1": 10, "2": 20, "3": 30, "4": 40, "5": 50}, "--", "--and--", "10--20--30--40--and--50"}, + {map[string]int{"3": 10, "2": 20, "1": 30, "4": 40, "5": 50}, "--", "--and--", "30--20--10--40--and--50"}, + {map[string]string{"1": "10", "2": "20", "3": "30", "4": "40", "5": "50"}, "--", "--and--", "10--20--30--40--and--50"}, + {map[string]string{"3": "10", "2": "20", "1": "30", "4": "40", "5": "50"}, "--", "--and--", "30--20--10--40--and--50"}, + {map[string]string{"one": "10", "two": "20", "three": "30", "four": "40", "five": "50"}, "--", "--and--", "50--40--10--30--and--20"}, + {map[int]string{1: "10", 2: "20", 3: "30", 4: "40", 5: "50"}, "--", "--and--", "10--20--30--40--and--50"}, + {map[int]string{3: "10", 2: "20", 1: "30", 4: "40", 5: "50"}, "--", "--and--", "30--20--10--40--and--50"}, + {map[float64]string{3.5: "10", 2.5: "20", 1.5: "30", 4.5: "40", 5.5: "50"}, "--", "--and--", "30--20--10--40--and--50"}, + } { + var result template.HTML + var err error + if this.last == nil { + result, err = Delimit(this.sequence, this.delimiter) + } else { + result, err = Delimit(this.sequence, this.delimiter, this.last) + } + if err != nil { + t.Errorf("[%d] failed: %s", i, err) + continue + } + if !reflect.DeepEqual(result, this.expect) { + t.Errorf("[%d] Delimit called on sequence: %v | delimiter: `%v` | last: `%v`, got %v but expected %v", i, this.sequence, this.delimiter, this.last, result, this.expect) + } + } +} + +func TestSort(t *testing.T) { + type ts struct { + MyInt int + MyFloat float64 + MyString string + } + for i, this := range []struct { + sequence interface{} + sortByField interface{} + sortAsc string + expect []interface{} + }{ + {[]string{"class1", "class2", "class3"}, nil, "asc", []interface{}{"class1", "class2", "class3"}}, + {[]string{"class3", "class1", "class2"}, nil, "asc", []interface{}{"class1", "class2", "class3"}}, + {[]int{1, 2, 3, 4, 5}, nil, "asc", []interface{}{1, 2, 3, 4, 5}}, + {[]int{5, 4, 3, 1, 2}, nil, "asc", []interface{}{1, 2, 3, 4, 5}}, + // test map sorting by keys + {map[string]int{"1": 10, "2": 20, "3": 30, "4": 40, "5": 50}, nil, "asc", []interface{}{10, 20, 30, 40, 50}}, + {map[string]int{"3": 10, "2": 20, "1": 30, "4": 40, "5": 50}, nil, "asc", []interface{}{30, 20, 10, 40, 50}}, + {map[string]string{"1": "10", "2": "20", "3": "30", "4": "40", "5": "50"}, nil, "asc", []interface{}{"10", "20", "30", "40", "50"}}, + {map[string]string{"3": "10", "2": "20", "1": "30", "4": "40", "5": "50"}, nil, "asc", []interface{}{"30", "20", "10", "40", "50"}}, + {map[string]string{"one": "10", "two": "20", "three": "30", "four": "40", "five": "50"}, nil, "asc", []interface{}{"50", "40", "10", "30", "20"}}, + {map[int]string{1: "10", 2: "20", 3: "30", 4: "40", 5: "50"}, nil, "asc", []interface{}{"10", "20", "30", "40", "50"}}, + {map[int]string{3: "10", 2: "20", 1: "30", 4: "40", 5: "50"}, nil, "asc", []interface{}{"30", "20", "10", "40", "50"}}, + {map[float64]string{3.3: "10", 2.3: "20", 1.3: "30", 4.3: "40", 5.3: "50"}, nil, "asc", []interface{}{"30", "20", "10", "40", "50"}}, + // test map sorting by value + {map[string]int{"1": 10, "2": 20, "3": 30, "4": 40, "5": 50}, "value", "asc", []interface{}{10, 20, 30, 40, 50}}, + {map[string]int{"3": 10, "2": 20, "1": 30, "4": 40, "5": 50}, "value", "asc", []interface{}{10, 20, 30, 40, 50}}, + // test map sorting by field value + { + map[string]ts{"1": {10, 10.5, "ten"}, "2": {20, 20.5, "twenty"}, "3": {30, 30.5, "thirty"}, "4": {40, 40.5, "forty"}, "5": {50, 50.5, "fifty"}}, + "MyInt", + "asc", + []interface{}{ts{10, 10.5, "ten"}, ts{20, 20.5, "twenty"}, ts{30, 30.5, "thirty"}, ts{40, 40.5, "forty"}, ts{50, 50.5, "fifty"}}, + }, + { + map[string]ts{"1": {10, 10.5, "ten"}, "2": {20, 20.5, "twenty"}, "3": {30, 30.5, "thirty"}, "4": {40, 40.5, "forty"}, "5": {50, 50.5, "fifty"}}, + "MyFloat", + "asc", + []interface{}{ts{10, 10.5, "ten"}, ts{20, 20.5, "twenty"}, ts{30, 30.5, "thirty"}, ts{40, 40.5, "forty"}, ts{50, 50.5, "fifty"}}, + }, + { + map[string]ts{"1": {10, 10.5, "ten"}, "2": {20, 20.5, "twenty"}, "3": {30, 30.5, "thirty"}, "4": {40, 40.5, "forty"}, "5": {50, 50.5, "fifty"}}, + "MyString", + "asc", + []interface{}{ts{50, 50.5, "fifty"}, ts{40, 40.5, "forty"}, ts{10, 10.5, "ten"}, ts{30, 30.5, "thirty"}, ts{20, 20.5, "twenty"}}, + }, + // Test sort desc + {[]string{"class1", "class2", "class3"}, "value", "desc", []interface{}{"class3", "class2", "class1"}}, + {[]string{"class3", "class1", "class2"}, "value", "desc", []interface{}{"class3", "class2", "class1"}}, + } { + var result []interface{} + var err error + if this.sortByField == nil { + result, err = Sort(this.sequence) + } else { + result, err = Sort(this.sequence, this.sortByField, this.sortAsc) + } + if err != nil { + t.Errorf("[%d] failed: %s", i, err) + continue + } + if !reflect.DeepEqual(result, this.expect) { + t.Errorf("[%d] Sort called on sequence: %v | sortByField: `%v` | got %v but expected %v", i, this.sequence, this.sortByField, result, this.expect) + } + } +} + +func TestReturnWhenSet(t *testing.T) { + for i, this := range []struct { + data interface{} + key interface{} + expect interface{} + }{ + {[]int{1, 2, 3}, 1, int64(2)}, + {[]uint{1, 2, 3}, 1, uint64(2)}, + {[]float64{1.1, 2.2, 3.3}, 1, float64(2.2)}, + {[]string{"foo", "bar", "baz"}, 1, "bar"}, + {[]TstX{{A: "a", B: "b"}, {A: "c", B: "d"}, {A: "e", B: "f"}}, 1, ""}, + {map[string]int{"foo": 1, "bar": 2, "baz": 3}, "bar", int64(2)}, + {map[string]uint{"foo": 1, "bar": 2, "baz": 3}, "bar", uint64(2)}, + {map[string]float64{"foo": 1.1, "bar": 2.2, "baz": 3.3}, "bar", float64(2.2)}, + {map[string]string{"foo": "FOO", "bar": "BAR", "baz": "BAZ"}, "bar", "BAR"}, + {map[string]TstX{"foo": {A: "a", B: "b"}, "bar": {A: "c", B: "d"}, "baz": {A: "e", B: "f"}}, "bar", ""}, + {(*[]string)(nil), "bar", ""}, + } { + result := ReturnWhenSet(this.data, this.key) + if !reflect.DeepEqual(result, this.expect) { + t.Errorf("[%d] ReturnWhenSet got %v (type %v) but expected %v (type %v)", i, result, reflect.TypeOf(result), this.expect, reflect.TypeOf(this.expect)) + } + } +} + +func TestMarkdownify(t *testing.T) { + + result := Markdownify("Hello **World!**") + + expect := template.HTML("Hello World!") + + if result != expect { + t.Errorf("Markdownify: got '%s', expected '%s'", result, expect) + } +} + +func TestApply(t *testing.T) { + strings := []interface{}{"a\n", "b\n"} + noStringers := []interface{}{tstNoStringer{}, tstNoStringer{}} + + var nilErr *error = nil + + chomped, _ := Apply(strings, "chomp", ".") + assert.Equal(t, []interface{}{"a", "b"}, chomped) + + chomped, _ = Apply(strings, "chomp", "c\n") + assert.Equal(t, []interface{}{"c", "c"}, chomped) + + chomped, _ = Apply(nil, "chomp", ".") + assert.Equal(t, []interface{}{}, chomped) + + _, err := Apply(strings, "apply", ".") + if err == nil { + t.Errorf("apply with apply should fail") + } + + _, err = Apply(nilErr, "chomp", ".") + if err == nil { + t.Errorf("apply with nil in seq should fail") + } + + _, err = Apply(strings, "dobedobedo", ".") + if err == nil { + t.Errorf("apply with unknown func should fail") + } + + _, err = Apply(noStringers, "chomp", ".") + if err == nil { + t.Errorf("apply when func fails should fail") + } + + _, err = Apply(tstNoStringer{}, "chomp", ".") + if err == nil { + t.Errorf("apply with non-sequence should fail") + } + +} + +func TestChomp(t *testing.T) { + base := "\n This is\na story " + for i, item := range []string{ + "\n", "\n\n", + "\r", "\r\r", + "\r\n", "\r\n\r\n", + } { + chomped, _ := Chomp(base + item) + + if chomped != base { + t.Errorf("[%d] Chomp failed, got '%v'", i, chomped) + } + + _, err := Chomp(tstNoStringer{}) + + if err == nil { + t.Errorf("Chomp should fail") + } + } +} + +func TestReplace(t *testing.T) { + v, _ := Replace("aab", "a", "b") + assert.Equal(t, "bbb", v) + v, _ = Replace("11a11", 1, 2) + assert.Equal(t, "22a22", v) + v, _ = Replace(12345, 1, 2) + assert.Equal(t, "22345", v) + _, e := Replace(tstNoStringer{}, "a", "b") + assert.NotNil(t, e, "tstNoStringer isn't trimmable") + _, e = Replace("a", tstNoStringer{}, "b") + assert.NotNil(t, e, "tstNoStringer cannot be converted to string") + _, e = Replace("a", "b", tstNoStringer{}) + assert.NotNil(t, e, "tstNoStringer cannot be converted to string") +} + +func TestTrim(t *testing.T) { + v, _ := Trim("1234 my way 13", "123") + assert.Equal(t, "4 my way ", v) + v, _ = Trim(" my way ", " ") + assert.Equal(t, "my way", v) + v, _ = Trim(1234, "14") + assert.Equal(t, "23", v) + _, e := Trim(tstNoStringer{}, " ") + assert.NotNil(t, e, "tstNoStringer isn't trimmable") +} + +func TestDateFormat(t *testing.T) { + for i, this := range []struct { + layout string + value interface{} + expect interface{} + }{ + {"Monday, Jan 2, 2006", "2015-01-21", "Wednesday, Jan 21, 2015"}, + {"Monday, Jan 2, 2006", time.Date(2015, time.January, 21, 0, 0, 0, 0, time.UTC), "Wednesday, Jan 21, 2015"}, + {"This isn't a date layout string", "2015-01-21", "This isn't a date layout string"}, + {"Monday, Jan 2, 2006", 1421733600, false}, + {"Monday, Jan 2, 2006", 1421733600.123, false}, + } { + result, err := DateFormat(this.layout, this.value) + if b, ok := this.expect.(bool); ok && !b { + if err == nil { + t.Errorf("[%d] DateFormat didn't return an expected error", i) + } + } else { + if err != nil { + t.Errorf("[%d] DateFormat failed: %s", i, err) + continue + } + if result != this.expect { + t.Errorf("[%d] DateFormat got %v but expected %v", i, result, this.expect) + } + } + } +} + +func TestSafeHTML(t *testing.T) { + for i, this := range []struct { + str string + tmplStr string + expectWithoutEscape string + expectWithEscape string + }{ + {`
`, `{{ . }}`, `<div></div>`, `
`}, + } { + tmpl, err := template.New("test").Parse(this.tmplStr) + if err != nil { + t.Errorf("[%d] unable to create new html template %q: %s", i, this.tmplStr, err) + continue + } + + buf := new(bytes.Buffer) + err = tmpl.Execute(buf, this.str) + if err != nil { + t.Errorf("[%d] execute template with a raw string value returns unexpected error: %s", i, err) + } + if buf.String() != this.expectWithoutEscape { + t.Errorf("[%d] execute template with a raw string value, got %v but expected %v", i, buf.String(), this.expectWithoutEscape) + } + + buf.Reset() + err = tmpl.Execute(buf, SafeHTML(this.str)) + if err != nil { + t.Errorf("[%d] execute template with an escaped string value by SafeHTML returns unexpected error: %s", i, err) + } + if buf.String() != this.expectWithEscape { + t.Errorf("[%d] execute template with an escaped string value by SafeHTML, got %v but expected %v", i, buf.String(), this.expectWithEscape) + } + } +} + +func TestSafeHTMLAttr(t *testing.T) { + for i, this := range []struct { + str string + tmplStr string + expectWithoutEscape string + expectWithEscape string + }{ + {`href="irc://irc.freenode.net/#golang"`, `irc`, `irc`, `irc`}, + } { + tmpl, err := template.New("test").Parse(this.tmplStr) + if err != nil { + t.Errorf("[%d] unable to create new html template %q: %s", i, this.tmplStr, err) + continue + } + + buf := new(bytes.Buffer) + err = tmpl.Execute(buf, this.str) + if err != nil { + t.Errorf("[%d] execute template with a raw string value returns unexpected error: %s", i, err) + } + if buf.String() != this.expectWithoutEscape { + t.Errorf("[%d] execute template with a raw string value, got %v but expected %v", i, buf.String(), this.expectWithoutEscape) + } + + buf.Reset() + err = tmpl.Execute(buf, SafeHTMLAttr(this.str)) + if err != nil { + t.Errorf("[%d] execute template with an escaped string value by SafeHTMLAttr returns unexpected error: %s", i, err) + } + if buf.String() != this.expectWithEscape { + t.Errorf("[%d] execute template with an escaped string value by SafeHTMLAttr, got %v but expected %v", i, buf.String(), this.expectWithEscape) + } + } +} + +func TestSafeCSS(t *testing.T) { + for i, this := range []struct { + str string + tmplStr string + expectWithoutEscape string + expectWithEscape string + }{ + {`width: 60px;`, `
`, `
`, `
`}, + } { + tmpl, err := template.New("test").Parse(this.tmplStr) + if err != nil { + t.Errorf("[%d] unable to create new html template %q: %s", i, this.tmplStr, err) + continue + } + + buf := new(bytes.Buffer) + err = tmpl.Execute(buf, this.str) + if err != nil { + t.Errorf("[%d] execute template with a raw string value returns unexpected error: %s", i, err) + } + if buf.String() != this.expectWithoutEscape { + t.Errorf("[%d] execute template with a raw string value, got %v but expected %v", i, buf.String(), this.expectWithoutEscape) + } + + buf.Reset() + err = tmpl.Execute(buf, SafeCSS(this.str)) + if err != nil { + t.Errorf("[%d] execute template with an escaped string value by SafeCSS returns unexpected error: %s", i, err) + } + if buf.String() != this.expectWithEscape { + t.Errorf("[%d] execute template with an escaped string value by SafeCSS, got %v but expected %v", i, buf.String(), this.expectWithEscape) + } + } +} + +func TestSafeURL(t *testing.T) { + for i, this := range []struct { + str string + tmplStr string + expectWithoutEscape string + expectWithEscape string + }{ + {`irc://irc.freenode.net/#golang`, `IRC`, `IRC`, `IRC`}, + } { + tmpl, err := template.New("test").Parse(this.tmplStr) + if err != nil { + t.Errorf("[%d] unable to create new html template %q: %s", i, this.tmplStr, err) + continue + } + + buf := new(bytes.Buffer) + err = tmpl.Execute(buf, this.str) + if err != nil { + t.Errorf("[%d] execute template with a raw string value returns unexpected error: %s", i, err) + } + if buf.String() != this.expectWithoutEscape { + t.Errorf("[%d] execute template with a raw string value, got %v but expected %v", i, buf.String(), this.expectWithoutEscape) + } + + buf.Reset() + err = tmpl.Execute(buf, SafeURL(this.str)) + if err != nil { + t.Errorf("[%d] execute template with an escaped string value by SafeURL returns unexpected error: %s", i, err) + } + if buf.String() != this.expectWithEscape { + t.Errorf("[%d] execute template with an escaped string value by SafeURL, got %v but expected %v", i, buf.String(), this.expectWithEscape) + } + } +} diff --git a/tpl/template_test.go b/tpl/template_test.go index 8e99f2fb4..0c440966b 100644 --- a/tpl/template_test.go +++ b/tpl/template_test.go @@ -1,1222 +1,3 @@ package tpl -import ( - "bytes" - "errors" - "fmt" - "github.com/stretchr/testify/assert" - "html/template" - "path" - "reflect" - "runtime" - "testing" - "time" -) - -type tstNoStringer struct { -} - -type tstCompareType int - -const ( - tstEq tstCompareType = iota - tstNe - tstGt - tstGe - tstLt - tstLe -) - -func tstIsEq(tp tstCompareType) bool { - return tp == tstEq || tp == tstGe || tp == tstLe -} - -func tstIsGt(tp tstCompareType) bool { - return tp == tstGt || tp == tstGe -} - -func tstIsLt(tp tstCompareType) bool { - return tp == tstLt || tp == tstLe -} - -func TestCompare(t *testing.T) { - for _, this := range []struct { - tstCompareType - funcUnderTest func(a, b interface{}) bool - }{ - {tstGt, Gt}, - {tstLt, Lt}, - {tstGe, Ge}, - {tstLe, Le}, - {tstEq, Eq}, - {tstNe, Ne}, - } { - doTestCompare(t, this.tstCompareType, this.funcUnderTest) - } - -} - -func doTestCompare(t *testing.T, tp tstCompareType, funcUnderTest func(a, b interface{}) bool) { - for i, this := range []struct { - left interface{} - right interface{} - expectIndicator int - }{ - {5, 8, -1}, - {8, 5, 1}, - {5, 5, 0}, - {int(5), int64(5), 0}, - {int32(5), int(5), 0}, - {int16(4), int(5), -1}, - {uint(15), uint64(15), 0}, - {-2, 1, -1}, - {2, -5, 1}, - {0.0, 1.23, -1}, - {1.1, 1.1, 0}, - {float32(1.0), float64(1.0), 0}, - {1.23, 0.0, 1}, - {"5", "5", 0}, - {"8", "5", 1}, - {"5", "0001", 1}, - {[]int{100, 99}, []int{1, 2, 3, 4}, -1}, - } { - result := funcUnderTest(this.left, this.right) - success := false - - if this.expectIndicator == 0 { - if tstIsEq(tp) { - success = result - } else { - success = !result - } - } - - if this.expectIndicator < 0 { - success = result && (tstIsLt(tp) || tp == tstNe) - success = success || (!result && !tstIsLt(tp)) - } - - if this.expectIndicator > 0 { - success = result && (tstIsGt(tp) || tp == tstNe) - success = success || (!result && (!tstIsGt(tp) || tp != tstNe)) - } - - if !success { - t.Errorf("[%d][%s] %v compared to %v: %t", i, path.Base(runtime.FuncForPC(reflect.ValueOf(funcUnderTest).Pointer()).Name()), this.left, this.right, result) - } - } -} - -func TestArethmic(t *testing.T) { - for i, this := range []struct { - a interface{} - b interface{} - op rune - expect interface{} - }{ - {1, 2, '+', int64(3)}, - {1, 2, '-', int64(-1)}, - {2, 2, '*', int64(4)}, - {4, 2, '/', int64(2)}, - {uint8(1), uint8(3), '+', uint64(4)}, - {uint8(3), uint8(2), '-', uint64(1)}, - {uint8(2), uint8(2), '*', uint64(4)}, - {uint16(4), uint8(2), '/', uint64(2)}, - {4, 2, '¤', false}, - {4, 0, '/', false}, - } { - // TODO(bep): Take precision into account. - result, err := doArithmetic(this.a, this.b, this.op) - if b, ok := this.expect.(bool); ok && !b { - if err == nil { - t.Errorf("[%d] doArethmic didn't return an expected error", i) - } - } else { - if err != nil { - t.Errorf("[%d] failed: %s", i, err) - continue - } - if !reflect.DeepEqual(result, this.expect) { - t.Errorf("[%d] doArethmic got %v (%T) but expected %v (%T)", i, result, result, this.expect, this.expect) - } - } - } -} - -func TestMod(t *testing.T) { - for i, this := range []struct { - a interface{} - b interface{} - expect interface{} - }{ - {3, 2, int64(1)}, - {3, 1, int64(0)}, - {3, 0, false}, - {0, 3, int64(0)}, - {3.1, 2, false}, - {3, 2.1, false}, - {3.1, 2.1, false}, - {int8(3), int8(2), int64(1)}, - {int16(3), int16(2), int64(1)}, - {int32(3), int32(2), int64(1)}, - {int64(3), int64(2), int64(1)}, - } { - result, err := Mod(this.a, this.b) - if b, ok := this.expect.(bool); ok && !b { - if err == nil { - t.Errorf("[%d] modulo didn't return an expected error", i) - } - } else { - if err != nil { - t.Errorf("[%d] failed: %s", i, err) - continue - } - if !reflect.DeepEqual(result, this.expect) { - t.Errorf("[%d] modulo got %v but expected %v", i, result, this.expect) - } - } - } -} - -func TestModBool(t *testing.T) { - for i, this := range []struct { - a interface{} - b interface{} - expect interface{} - }{ - {3, 3, true}, - {3, 2, false}, - {3, 1, true}, - {3, 0, nil}, - {0, 3, true}, - {3.1, 2, nil}, - {3, 2.1, nil}, - {3.1, 2.1, nil}, - {int8(3), int8(3), true}, - {int8(3), int8(2), false}, - {int16(3), int16(3), true}, - {int16(3), int16(2), false}, - {int32(3), int32(3), true}, - {int32(3), int32(2), false}, - {int64(3), int64(3), true}, - {int64(3), int64(2), false}, - } { - result, err := ModBool(this.a, this.b) - if this.expect == nil { - if err == nil { - t.Errorf("[%d] modulo didn't return an expected error", i) - } - } else { - if err != nil { - t.Errorf("[%d] failed: %s", i, err) - continue - } - if !reflect.DeepEqual(result, this.expect) { - t.Errorf("[%d] modulo got %v but expected %v", i, result, this.expect) - } - } - } -} - -func TestFirst(t *testing.T) { - for i, this := range []struct { - count interface{} - sequence interface{} - expect interface{} - }{ - {int(2), []string{"a", "b", "c"}, []string{"a", "b"}}, - {int32(3), []string{"a", "b"}, []string{"a", "b"}}, - {int64(2), []int{100, 200, 300}, []int{100, 200}}, - {100, []int{100, 200}, []int{100, 200}}, - {"1", []int{100, 200, 300}, []int{100}}, - {int64(-1), []int{100, 200, 300}, false}, - {"noint", []int{100, 200, 300}, false}, - {1, nil, false}, - {nil, []int{100}, false}, - {1, t, false}, - } { - results, err := First(this.count, this.sequence) - if b, ok := this.expect.(bool); ok && !b { - if err == nil { - t.Errorf("[%d] First didn't return an expected error", i) - } - } else { - if err != nil { - t.Errorf("[%d] failed: %s", i, err) - continue - } - if !reflect.DeepEqual(results, this.expect) { - t.Errorf("[%d] First %d items, got %v but expected %v", i, this.count, results, this.expect) - } - } - } -} - -func TestIn(t *testing.T) { - for i, this := range []struct { - v1 interface{} - v2 interface{} - expect bool - }{ - {[]string{"a", "b", "c"}, "b", true}, - {[]string{"a", "b", "c"}, "d", false}, - {[]string{"a", "12", "c"}, 12, false}, - {[]int{1, 2, 4}, 2, true}, - {[]int{1, 2, 4}, 3, false}, - {[]float64{1.23, 2.45, 4.67}, 1.23, true}, - {[]float64{1.234567, 2.45, 4.67}, 1.234568, false}, - {"this substring should be found", "substring", true}, - {"this substring should not be found", "subseastring", false}, - } { - result := In(this.v1, this.v2) - - if result != this.expect { - t.Errorf("[%d] Got %v but expected %v", i, result, this.expect) - } - } -} - -func TestSlicestr(t *testing.T) { - for i, this := range []struct { - v1 interface{} - v2 []int - expect interface{} - }{ - {"abc", []int{1, 2}, "b"}, - {"abc", []int{1, 3}, "bc"}, - {"abc", []int{0, 1}, "a"}, - {"abcdef", []int{}, "abcdef"}, - {"abcdef", []int{0, 6}, "abcdef"}, - {"abcdef", []int{0, 2}, "ab"}, - {"abcdef", []int{2}, "cdef"}, - {123, []int{1, 3}, "23"}, - {123, []int{1, 2, 3}, false}, - {tstNoStringer{}, []int{0, 1}, false}, - } { - result, err := Slicestr(this.v1, this.v2...) - - if b, ok := this.expect.(bool); ok && !b { - if err == nil { - t.Errorf("[%d] Slice didn't return an expected error", i) - } - } else { - if err != nil { - t.Errorf("[%d] failed: %s", i, err) - continue - } - if !reflect.DeepEqual(result, this.expect) { - t.Errorf("[%d] Got %s but expected %s", i, result, this.expect) - } - } - } -} - -func TestSubstr(t *testing.T) { - for i, this := range []struct { - v1 interface{} - v2 int - v3 int - expect interface{} - }{ - {"abc", 1, 2, "bc"}, - {"abc", 0, 1, "a"}, - {"abcdef", -1, 2, "ef"}, - {"abcdef", -3, 3, "bcd"}, - {"abcdef", 0, -1, "abcde"}, - {"abcdef", 2, -1, "cde"}, - {"abcdef", 4, -4, false}, - {"abcdef", 7, 1, false}, - {"abcdef", 1, 100, "bcdef"}, - {"abcdef", -100, 3, "abc"}, - {"abcdef", -3, -1, "de"}, - {123, 1, 3, "23"}, - {1.2e3, 0, 4, "1200"}, - {tstNoStringer{}, 0, 1, false}, - } { - result, err := Substr(this.v1, this.v2, this.v3) - - if b, ok := this.expect.(bool); ok && !b { - if err == nil { - t.Errorf("[%d] Substr didn't return an expected error", i) - } - } else { - if err != nil { - t.Errorf("[%d] failed: %s", i, err) - continue - } - if !reflect.DeepEqual(result, this.expect) { - t.Errorf("[%d] Got %s but expected %s", i, result, this.expect) - } - } - } -} - -func TestSplit(t *testing.T) { - for i, this := range []struct { - v1 interface{} - v2 string - expect interface{} - }{ - {"a, b", ", ", []string{"a", "b"}}, - {"a & b & c", " & ", []string{"a", "b", "c"}}, - {"http://exmaple.com", "http://", []string{"", "exmaple.com"}}, - {123, "2", []string{"1", "3"}}, - {tstNoStringer{}, ",", false}, - } { - result, err := Split(this.v1, this.v2) - - if b, ok := this.expect.(bool); ok && !b { - if err == nil { - t.Errorf("[%d] Split didn't return an expected error", i) - } - } else { - if err != nil { - t.Errorf("[%d] failed: %s", i, err) - continue - } - if !reflect.DeepEqual(result, this.expect) { - t.Errorf("[%d] Got %s but expected %s", i, result, this.expect) - } - } - } - -} - -func TestIntersect(t *testing.T) { - for i, this := range []struct { - sequence1 interface{} - sequence2 interface{} - expect interface{} - }{ - {[]string{"a", "b", "c"}, []string{"a", "b"}, []string{"a", "b"}}, - {[]string{"a", "b"}, []string{"a", "b", "c"}, []string{"a", "b"}}, - {[]string{"a", "b", "c"}, []string{"d", "e"}, []string{}}, - {[]string{}, []string{}, []string{}}, - {[]string{"a", "b"}, nil, make([]interface{}, 0)}, - {nil, []string{"a", "b"}, make([]interface{}, 0)}, - {nil, nil, make([]interface{}, 0)}, - {[]string{"1", "2"}, []int{1, 2}, []string{}}, - {[]int{1, 2}, []string{"1", "2"}, []int{}}, - {[]int{1, 2, 4}, []int{2, 4}, []int{2, 4}}, - {[]int{2, 4}, []int{1, 2, 4}, []int{2, 4}}, - {[]int{1, 2, 4}, []int{3, 6}, []int{}}, - {[]float64{2.2, 4.4}, []float64{1.1, 2.2, 4.4}, []float64{2.2, 4.4}}, - } { - results, err := Intersect(this.sequence1, this.sequence2) - if err != nil { - t.Errorf("[%d] failed: %s", i, err) - continue - } - if !reflect.DeepEqual(results, this.expect) { - t.Errorf("[%d] Got %v but expected %v", i, results, this.expect) - } - } - - _, err1 := Intersect("not an array or slice", []string{"a"}) - - if err1 == nil { - t.Error("Excpected error for non array as first arg") - } - - _, err2 := Intersect([]string{"a"}, "not an array or slice") - - if err2 == nil { - t.Error("Excpected error for non array as second arg") - } -} - -func TestIsSet(t *testing.T) { - aSlice := []interface{}{1, 2, 3, 5} - aMap := map[string]interface{}{"a": 1, "b": 2} - - assert.True(t, IsSet(aSlice, 2)) - assert.True(t, IsSet(aMap, "b")) - assert.False(t, IsSet(aSlice, 22)) - assert.False(t, IsSet(aMap, "bc")) -} - -func (x *TstX) TstRp() string { - return "r" + x.A -} - -func (x TstX) TstRv() string { - return "r" + x.B -} - -func (x TstX) unexportedMethod() string { - return x.unexported -} - -func (x TstX) MethodWithArg(s string) string { - return s -} - -func (x TstX) MethodReturnNothing() {} - -func (x TstX) MethodReturnErrorOnly() error { - return errors.New("something error occured") -} - -func (x TstX) MethodReturnTwoValues() (string, string) { - return "foo", "bar" -} - -func (x TstX) MethodReturnValueWithError() (string, error) { - return "", errors.New("something error occured") -} - -func (x TstX) String() string { - return fmt.Sprintf("A: %s, B: %s", x.A, x.B) -} - -type TstX struct { - A, B string - unexported string -} - -func TestEvaluateSubElem(t *testing.T) { - tstx := TstX{A: "foo", B: "bar"} - var inner struct { - S fmt.Stringer - } - inner.S = tstx - interfaceValue := reflect.ValueOf(&inner).Elem().Field(0) - - for i, this := range []struct { - value reflect.Value - key string - expect interface{} - }{ - {reflect.ValueOf(tstx), "A", "foo"}, - {reflect.ValueOf(&tstx), "TstRp", "rfoo"}, - {reflect.ValueOf(tstx), "TstRv", "rbar"}, - //{reflect.ValueOf(map[int]string{1: "foo", 2: "bar"}), 1, "foo"}, - {reflect.ValueOf(map[string]string{"key1": "foo", "key2": "bar"}), "key1", "foo"}, - {interfaceValue, "String", "A: foo, B: bar"}, - {reflect.Value{}, "foo", false}, - //{reflect.ValueOf(map[int]string{1: "foo", 2: "bar"}), 1.2, false}, - {reflect.ValueOf(tstx), "unexported", false}, - {reflect.ValueOf(tstx), "unexportedMethod", false}, - {reflect.ValueOf(tstx), "MethodWithArg", false}, - {reflect.ValueOf(tstx), "MethodReturnNothing", false}, - {reflect.ValueOf(tstx), "MethodReturnErrorOnly", false}, - {reflect.ValueOf(tstx), "MethodReturnTwoValues", false}, - {reflect.ValueOf(tstx), "MethodReturnValueWithError", false}, - {reflect.ValueOf((*TstX)(nil)), "A", false}, - {reflect.ValueOf(tstx), "C", false}, - {reflect.ValueOf(map[int]string{1: "foo", 2: "bar"}), "1", false}, - {reflect.ValueOf([]string{"foo", "bar"}), "1", false}, - } { - result, err := evaluateSubElem(this.value, this.key) - if b, ok := this.expect.(bool); ok && !b { - if err == nil { - t.Errorf("[%d] evaluateSubElem didn't return an expected error", i) - } - } else { - if err != nil { - t.Errorf("[%d] failed: %s", i, err) - continue - } - if result.Kind() != reflect.String || result.String() != this.expect { - t.Errorf("[%d] evaluateSubElem with %v got %v but expected %v", i, this.key, result, this.expect) - } - } - } -} - -func TestCheckCondition(t *testing.T) { - type expect struct { - result bool - isError bool - } - - for i, this := range []struct { - value reflect.Value - match reflect.Value - op string - expect - }{ - {reflect.ValueOf(123), reflect.ValueOf(123), "", expect{true, false}}, - {reflect.ValueOf("foo"), reflect.ValueOf("foo"), "", expect{true, false}}, - {reflect.ValueOf(123), reflect.ValueOf(456), "!=", expect{true, false}}, - {reflect.ValueOf("foo"), reflect.ValueOf("bar"), "!=", expect{true, false}}, - {reflect.ValueOf(456), reflect.ValueOf(123), ">=", expect{true, false}}, - {reflect.ValueOf("foo"), reflect.ValueOf("bar"), ">=", expect{true, false}}, - {reflect.ValueOf(456), reflect.ValueOf(123), ">", expect{true, false}}, - {reflect.ValueOf("foo"), reflect.ValueOf("bar"), ">", expect{true, false}}, - {reflect.ValueOf(123), reflect.ValueOf(456), "<=", expect{true, false}}, - {reflect.ValueOf("bar"), reflect.ValueOf("foo"), "<=", expect{true, false}}, - {reflect.ValueOf(123), reflect.ValueOf(456), "<", expect{true, false}}, - {reflect.ValueOf("bar"), reflect.ValueOf("foo"), "<", expect{true, false}}, - {reflect.ValueOf(123), reflect.ValueOf([]int{123, 45, 678}), "in", expect{true, false}}, - {reflect.ValueOf("foo"), reflect.ValueOf([]string{"foo", "bar", "baz"}), "in", expect{true, false}}, - {reflect.ValueOf(123), reflect.ValueOf([]int{45, 678}), "not in", expect{true, false}}, - {reflect.ValueOf("foo"), reflect.ValueOf([]string{"bar", "baz"}), "not in", expect{true, false}}, - {reflect.ValueOf("foo"), reflect.ValueOf("bar-foo-baz"), "in", expect{true, false}}, - {reflect.ValueOf("foo"), reflect.ValueOf("bar--baz"), "not in", expect{true, false}}, - {reflect.Value{}, reflect.ValueOf("foo"), "", expect{false, false}}, - {reflect.ValueOf("foo"), reflect.Value{}, "", expect{false, false}}, - {reflect.ValueOf((*TstX)(nil)), reflect.ValueOf("foo"), "", expect{false, false}}, - {reflect.ValueOf("foo"), reflect.ValueOf((*TstX)(nil)), "", expect{false, false}}, - {reflect.ValueOf("foo"), reflect.ValueOf(map[int]string{}), "", expect{false, false}}, - {reflect.ValueOf("foo"), reflect.ValueOf([]int{1, 2}), "", expect{false, false}}, - {reflect.ValueOf(123), reflect.ValueOf([]int{}), "in", expect{false, false}}, - {reflect.ValueOf(123), reflect.ValueOf(123), "op", expect{false, true}}, - } { - result, err := checkCondition(this.value, this.match, this.op) - if this.expect.isError { - if err == nil { - t.Errorf("[%d] checkCondition didn't return an expected error", i) - } - } else { - if err != nil { - t.Errorf("[%d] failed: %s", i, err) - continue - } - if result != this.expect.result { - t.Errorf("[%d] check condition %v %s %v, got %v but expected %v", i, this.value, this.op, this.match, result, this.expect.result) - } - } - } -} - -func TestWhere(t *testing.T) { - // TODO(spf): Put these page tests back in - //page1 := &Page{contentType: "v", Source: Source{File: *source.NewFile("/x/y/z/source.md")}} - //page2 := &Page{contentType: "w", Source: Source{File: *source.NewFile("/y/z/a/source.md")}} - - type Mid struct { - Tst TstX - } - - for i, this := range []struct { - sequence interface{} - key interface{} - op string - match interface{} - expect interface{} - }{ - { - sequence: []map[int]string{ - {1: "a", 2: "m"}, {1: "c", 2: "d"}, {1: "e", 3: "m"}, - }, - key: 2, match: "m", - expect: []map[int]string{ - {1: "a", 2: "m"}, - }, - }, - { - sequence: []map[string]int{ - {"a": 1, "b": 2}, {"a": 3, "b": 4}, {"a": 5, "x": 4}, - }, - key: "b", match: 4, - expect: []map[string]int{ - {"a": 3, "b": 4}, - }, - }, - { - sequence: []TstX{ - {A: "a", B: "b"}, {A: "c", B: "d"}, {A: "e", B: "f"}, - }, - key: "B", match: "f", - expect: []TstX{ - {A: "e", B: "f"}, - }, - }, - { - sequence: []*map[int]string{ - {1: "a", 2: "m"}, {1: "c", 2: "d"}, {1: "e", 3: "m"}, - }, - key: 2, match: "m", - expect: []*map[int]string{ - {1: "a", 2: "m"}, - }, - }, - { - sequence: []*TstX{ - {A: "a", B: "b"}, {A: "c", B: "d"}, {A: "e", B: "f"}, - }, - key: "B", match: "f", - expect: []*TstX{ - {A: "e", B: "f"}, - }, - }, - { - sequence: []*TstX{ - {A: "a", B: "b"}, {A: "c", B: "d"}, {A: "e", B: "c"}, - }, - key: "TstRp", match: "rc", - expect: []*TstX{ - {A: "c", B: "d"}, - }, - }, - { - sequence: []TstX{ - {A: "a", B: "b"}, {A: "c", B: "d"}, {A: "e", B: "c"}, - }, - key: "TstRv", match: "rc", - expect: []TstX{ - {A: "e", B: "c"}, - }, - }, - { - sequence: []map[string]TstX{ - {"foo": TstX{A: "a", B: "b"}}, {"foo": TstX{A: "c", B: "d"}}, {"foo": TstX{A: "e", B: "f"}}, - }, - key: "foo.B", match: "d", - expect: []map[string]TstX{ - {"foo": TstX{A: "c", B: "d"}}, - }, - }, - { - sequence: []map[string]TstX{ - {"foo": TstX{A: "a", B: "b"}}, {"foo": TstX{A: "c", B: "d"}}, {"foo": TstX{A: "e", B: "f"}}, - }, - key: ".foo.B", match: "d", - expect: []map[string]TstX{ - {"foo": TstX{A: "c", B: "d"}}, - }, - }, - { - sequence: []map[string]TstX{ - {"foo": TstX{A: "a", B: "b"}}, {"foo": TstX{A: "c", B: "d"}}, {"foo": TstX{A: "e", B: "f"}}, - }, - key: "foo.TstRv", match: "rd", - expect: []map[string]TstX{ - {"foo": TstX{A: "c", B: "d"}}, - }, - }, - { - sequence: []map[string]*TstX{ - {"foo": &TstX{A: "a", B: "b"}}, {"foo": &TstX{A: "c", B: "d"}}, {"foo": &TstX{A: "e", B: "f"}}, - }, - key: "foo.TstRp", match: "rc", - expect: []map[string]*TstX{ - {"foo": &TstX{A: "c", B: "d"}}, - }, - }, - { - sequence: []map[string]Mid{ - {"foo": Mid{Tst: TstX{A: "a", B: "b"}}}, {"foo": Mid{Tst: TstX{A: "c", B: "d"}}}, {"foo": Mid{Tst: TstX{A: "e", B: "f"}}}, - }, - key: "foo.Tst.B", match: "d", - expect: []map[string]Mid{ - {"foo": Mid{Tst: TstX{A: "c", B: "d"}}}, - }, - }, - { - sequence: []map[string]Mid{ - {"foo": Mid{Tst: TstX{A: "a", B: "b"}}}, {"foo": Mid{Tst: TstX{A: "c", B: "d"}}}, {"foo": Mid{Tst: TstX{A: "e", B: "f"}}}, - }, - key: "foo.Tst.TstRv", match: "rd", - expect: []map[string]Mid{ - {"foo": Mid{Tst: TstX{A: "c", B: "d"}}}, - }, - }, - { - sequence: []map[string]*Mid{ - {"foo": &Mid{Tst: TstX{A: "a", B: "b"}}}, {"foo": &Mid{Tst: TstX{A: "c", B: "d"}}}, {"foo": &Mid{Tst: TstX{A: "e", B: "f"}}}, - }, - key: "foo.Tst.TstRp", match: "rc", - expect: []map[string]*Mid{ - {"foo": &Mid{Tst: TstX{A: "c", B: "d"}}}, - }, - }, - { - sequence: []map[string]int{ - {"a": 1, "b": 2}, {"a": 3, "b": 4}, {"a": 5, "b": 6}, - }, - key: "b", op: ">", match: 3, - expect: []map[string]int{ - {"a": 3, "b": 4}, {"a": 5, "b": 6}, - }, - }, - { - sequence: []TstX{ - {A: "a", B: "b"}, {A: "c", B: "d"}, {A: "e", B: "f"}, - }, - key: "B", op: "!=", match: "f", - expect: []TstX{ - {A: "a", B: "b"}, {A: "c", B: "d"}, - }, - }, - { - sequence: []map[string]int{ - {"a": 1, "b": 2}, {"a": 3, "b": 4}, {"a": 5, "b": 6}, - }, - key: "b", op: "in", match: []int{3, 4, 5}, - expect: []map[string]int{ - {"a": 3, "b": 4}, - }, - }, - { - sequence: []TstX{ - {A: "a", B: "b"}, {A: "c", B: "d"}, {A: "e", B: "f"}, - }, - key: "B", op: "not in", match: []string{"c", "d", "e"}, - expect: []TstX{ - {A: "a", B: "b"}, {A: "e", B: "f"}, - }, - }, - {sequence: (*[]TstX)(nil), key: "A", match: "a", expect: false}, - {sequence: TstX{A: "a", B: "b"}, key: "A", match: "a", expect: false}, - {sequence: []map[string]*TstX{{"foo": nil}}, key: "foo.B", match: "d", expect: false}, - { - sequence: []TstX{ - {A: "a", B: "b"}, {A: "c", B: "d"}, {A: "e", B: "f"}, - }, - key: "B", op: "op", match: "f", - expect: false, - }, - //{[]*Page{page1, page2}, "Type", "v", []*Page{page1}}, - //{[]*Page{page1, page2}, "Section", "y", []*Page{page2}}, - } { - var results interface{} - var err error - if len(this.op) > 0 { - results, err = Where(this.sequence, this.key, this.op, this.match) - } else { - results, err = Where(this.sequence, this.key, this.match) - } - if b, ok := this.expect.(bool); ok && !b { - if err == nil { - t.Errorf("[%d] Where didn't return an expected error", i) - } - } else { - if err != nil { - t.Errorf("[%d] failed: %s", i, err) - continue - } - if !reflect.DeepEqual(results, this.expect) { - t.Errorf("[%d] Where clause matching %v with %v, got %v but expected %v", i, this.key, this.match, results, this.expect) - } - } - } - - var err error - _, err = Where(map[string]int{"a": 1, "b": 2}, "a", []byte("="), 1) - if err == nil { - t.Errorf("Where called with none string op value didn't return an expected error") - } - - _, err = Where(map[string]int{"a": 1, "b": 2}, "a", []byte("="), 1, 2) - if err == nil { - t.Errorf("Where called with more than two variable arguments didn't return an expected error") - } - - _, err = Where(map[string]int{"a": 1, "b": 2}, "a") - if err == nil { - t.Errorf("Where called with no variable arguments didn't return an expected error") - } -} - -func TestDelimit(t *testing.T) { - for i, this := range []struct { - sequence interface{} - delimiter interface{} - last interface{} - expect template.HTML - }{ - {[]string{"class1", "class2", "class3"}, " ", nil, "class1 class2 class3"}, - {[]int{1, 2, 3, 4, 5}, ",", nil, "1,2,3,4,5"}, - {[]int{1, 2, 3, 4, 5}, ", ", nil, "1, 2, 3, 4, 5"}, - {[]string{"class1", "class2", "class3"}, " ", " and ", "class1 class2 and class3"}, - {[]int{1, 2, 3, 4, 5}, ",", ",", "1,2,3,4,5"}, - {[]int{1, 2, 3, 4, 5}, ", ", ", and ", "1, 2, 3, 4, and 5"}, - // test maps with and without sorting required - {map[string]int{"1": 10, "2": 20, "3": 30, "4": 40, "5": 50}, "--", nil, "10--20--30--40--50"}, - {map[string]int{"3": 10, "2": 20, "1": 30, "4": 40, "5": 50}, "--", nil, "30--20--10--40--50"}, - {map[string]string{"1": "10", "2": "20", "3": "30", "4": "40", "5": "50"}, "--", nil, "10--20--30--40--50"}, - {map[string]string{"3": "10", "2": "20", "1": "30", "4": "40", "5": "50"}, "--", nil, "30--20--10--40--50"}, - {map[string]string{"one": "10", "two": "20", "three": "30", "four": "40", "five": "50"}, "--", nil, "50--40--10--30--20"}, - {map[int]string{1: "10", 2: "20", 3: "30", 4: "40", 5: "50"}, "--", nil, "10--20--30--40--50"}, - {map[int]string{3: "10", 2: "20", 1: "30", 4: "40", 5: "50"}, "--", nil, "30--20--10--40--50"}, - {map[float64]string{3.3: "10", 2.3: "20", 1.3: "30", 4.3: "40", 5.3: "50"}, "--", nil, "30--20--10--40--50"}, - // test maps with a last delimiter - {map[string]int{"1": 10, "2": 20, "3": 30, "4": 40, "5": 50}, "--", "--and--", "10--20--30--40--and--50"}, - {map[string]int{"3": 10, "2": 20, "1": 30, "4": 40, "5": 50}, "--", "--and--", "30--20--10--40--and--50"}, - {map[string]string{"1": "10", "2": "20", "3": "30", "4": "40", "5": "50"}, "--", "--and--", "10--20--30--40--and--50"}, - {map[string]string{"3": "10", "2": "20", "1": "30", "4": "40", "5": "50"}, "--", "--and--", "30--20--10--40--and--50"}, - {map[string]string{"one": "10", "two": "20", "three": "30", "four": "40", "five": "50"}, "--", "--and--", "50--40--10--30--and--20"}, - {map[int]string{1: "10", 2: "20", 3: "30", 4: "40", 5: "50"}, "--", "--and--", "10--20--30--40--and--50"}, - {map[int]string{3: "10", 2: "20", 1: "30", 4: "40", 5: "50"}, "--", "--and--", "30--20--10--40--and--50"}, - {map[float64]string{3.5: "10", 2.5: "20", 1.5: "30", 4.5: "40", 5.5: "50"}, "--", "--and--", "30--20--10--40--and--50"}, - } { - var result template.HTML - var err error - if this.last == nil { - result, err = Delimit(this.sequence, this.delimiter) - } else { - result, err = Delimit(this.sequence, this.delimiter, this.last) - } - if err != nil { - t.Errorf("[%d] failed: %s", i, err) - continue - } - if !reflect.DeepEqual(result, this.expect) { - t.Errorf("[%d] Delimit called on sequence: %v | delimiter: `%v` | last: `%v`, got %v but expected %v", i, this.sequence, this.delimiter, this.last, result, this.expect) - } - } -} - -func TestSort(t *testing.T) { - type ts struct { - MyInt int - MyFloat float64 - MyString string - } - for i, this := range []struct { - sequence interface{} - sortByField interface{} - sortAsc string - expect []interface{} - }{ - {[]string{"class1", "class2", "class3"}, nil, "asc", []interface{}{"class1", "class2", "class3"}}, - {[]string{"class3", "class1", "class2"}, nil, "asc", []interface{}{"class1", "class2", "class3"}}, - {[]int{1, 2, 3, 4, 5}, nil, "asc", []interface{}{1, 2, 3, 4, 5}}, - {[]int{5, 4, 3, 1, 2}, nil, "asc", []interface{}{1, 2, 3, 4, 5}}, - // test map sorting by keys - {map[string]int{"1": 10, "2": 20, "3": 30, "4": 40, "5": 50}, nil, "asc", []interface{}{10, 20, 30, 40, 50}}, - {map[string]int{"3": 10, "2": 20, "1": 30, "4": 40, "5": 50}, nil, "asc", []interface{}{30, 20, 10, 40, 50}}, - {map[string]string{"1": "10", "2": "20", "3": "30", "4": "40", "5": "50"}, nil, "asc", []interface{}{"10", "20", "30", "40", "50"}}, - {map[string]string{"3": "10", "2": "20", "1": "30", "4": "40", "5": "50"}, nil, "asc", []interface{}{"30", "20", "10", "40", "50"}}, - {map[string]string{"one": "10", "two": "20", "three": "30", "four": "40", "five": "50"}, nil, "asc", []interface{}{"50", "40", "10", "30", "20"}}, - {map[int]string{1: "10", 2: "20", 3: "30", 4: "40", 5: "50"}, nil, "asc", []interface{}{"10", "20", "30", "40", "50"}}, - {map[int]string{3: "10", 2: "20", 1: "30", 4: "40", 5: "50"}, nil, "asc", []interface{}{"30", "20", "10", "40", "50"}}, - {map[float64]string{3.3: "10", 2.3: "20", 1.3: "30", 4.3: "40", 5.3: "50"}, nil, "asc", []interface{}{"30", "20", "10", "40", "50"}}, - // test map sorting by value - {map[string]int{"1": 10, "2": 20, "3": 30, "4": 40, "5": 50}, "value", "asc", []interface{}{10, 20, 30, 40, 50}}, - {map[string]int{"3": 10, "2": 20, "1": 30, "4": 40, "5": 50}, "value", "asc", []interface{}{10, 20, 30, 40, 50}}, - // test map sorting by field value - { - map[string]ts{"1": {10, 10.5, "ten"}, "2": {20, 20.5, "twenty"}, "3": {30, 30.5, "thirty"}, "4": {40, 40.5, "forty"}, "5": {50, 50.5, "fifty"}}, - "MyInt", - "asc", - []interface{}{ts{10, 10.5, "ten"}, ts{20, 20.5, "twenty"}, ts{30, 30.5, "thirty"}, ts{40, 40.5, "forty"}, ts{50, 50.5, "fifty"}}, - }, - { - map[string]ts{"1": {10, 10.5, "ten"}, "2": {20, 20.5, "twenty"}, "3": {30, 30.5, "thirty"}, "4": {40, 40.5, "forty"}, "5": {50, 50.5, "fifty"}}, - "MyFloat", - "asc", - []interface{}{ts{10, 10.5, "ten"}, ts{20, 20.5, "twenty"}, ts{30, 30.5, "thirty"}, ts{40, 40.5, "forty"}, ts{50, 50.5, "fifty"}}, - }, - { - map[string]ts{"1": {10, 10.5, "ten"}, "2": {20, 20.5, "twenty"}, "3": {30, 30.5, "thirty"}, "4": {40, 40.5, "forty"}, "5": {50, 50.5, "fifty"}}, - "MyString", - "asc", - []interface{}{ts{50, 50.5, "fifty"}, ts{40, 40.5, "forty"}, ts{10, 10.5, "ten"}, ts{30, 30.5, "thirty"}, ts{20, 20.5, "twenty"}}, - }, - // Test sort desc - {[]string{"class1", "class2", "class3"}, "value", "desc", []interface{}{"class3", "class2", "class1"}}, - {[]string{"class3", "class1", "class2"}, "value", "desc", []interface{}{"class3", "class2", "class1"}}, - } { - var result []interface{} - var err error - if this.sortByField == nil { - result, err = Sort(this.sequence) - } else { - result, err = Sort(this.sequence, this.sortByField, this.sortAsc) - } - if err != nil { - t.Errorf("[%d] failed: %s", i, err) - continue - } - if !reflect.DeepEqual(result, this.expect) { - t.Errorf("[%d] Sort called on sequence: %v | sortByField: `%v` | got %v but expected %v", i, this.sequence, this.sortByField, result, this.expect) - } - } -} - -func TestReturnWhenSet(t *testing.T) { - for i, this := range []struct { - data interface{} - key interface{} - expect interface{} - }{ - {[]int{1, 2, 3}, 1, int64(2)}, - {[]uint{1, 2, 3}, 1, uint64(2)}, - {[]float64{1.1, 2.2, 3.3}, 1, float64(2.2)}, - {[]string{"foo", "bar", "baz"}, 1, "bar"}, - {[]TstX{{A: "a", B: "b"}, {A: "c", B: "d"}, {A: "e", B: "f"}}, 1, ""}, - {map[string]int{"foo": 1, "bar": 2, "baz": 3}, "bar", int64(2)}, - {map[string]uint{"foo": 1, "bar": 2, "baz": 3}, "bar", uint64(2)}, - {map[string]float64{"foo": 1.1, "bar": 2.2, "baz": 3.3}, "bar", float64(2.2)}, - {map[string]string{"foo": "FOO", "bar": "BAR", "baz": "BAZ"}, "bar", "BAR"}, - {map[string]TstX{"foo": {A: "a", B: "b"}, "bar": {A: "c", B: "d"}, "baz": {A: "e", B: "f"}}, "bar", ""}, - {(*[]string)(nil), "bar", ""}, - } { - result := ReturnWhenSet(this.data, this.key) - if !reflect.DeepEqual(result, this.expect) { - t.Errorf("[%d] ReturnWhenSet got %v (type %v) but expected %v (type %v)", i, result, reflect.TypeOf(result), this.expect, reflect.TypeOf(this.expect)) - } - } -} - -func TestMarkdownify(t *testing.T) { - - result := Markdownify("Hello **World!**") - - expect := template.HTML("Hello World!") - - if result != expect { - t.Errorf("Markdownify: got '%s', expected '%s'", result, expect) - } -} - -func TestApply(t *testing.T) { - strings := []interface{}{"a\n", "b\n"} - noStringers := []interface{}{tstNoStringer{}, tstNoStringer{}} - - var nilErr *error = nil - - chomped, _ := Apply(strings, "chomp", ".") - assert.Equal(t, []interface{}{"a", "b"}, chomped) - - chomped, _ = Apply(strings, "chomp", "c\n") - assert.Equal(t, []interface{}{"c", "c"}, chomped) - - chomped, _ = Apply(nil, "chomp", ".") - assert.Equal(t, []interface{}{}, chomped) - - _, err := Apply(strings, "apply", ".") - if err == nil { - t.Errorf("apply with apply should fail") - } - - _, err = Apply(nilErr, "chomp", ".") - if err == nil { - t.Errorf("apply with nil in seq should fail") - } - - _, err = Apply(strings, "dobedobedo", ".") - if err == nil { - t.Errorf("apply with unknown func should fail") - } - - _, err = Apply(noStringers, "chomp", ".") - if err == nil { - t.Errorf("apply when func fails should fail") - } - - _, err = Apply(tstNoStringer{}, "chomp", ".") - if err == nil { - t.Errorf("apply with non-sequence should fail") - } - -} - -func TestChomp(t *testing.T) { - base := "\n This is\na story " - for i, item := range []string{ - "\n", "\n\n", - "\r", "\r\r", - "\r\n", "\r\n\r\n", - } { - chomped, _ := Chomp(base + item) - - if chomped != base { - t.Errorf("[%d] Chomp failed, got '%v'", i, chomped) - } - - _, err := Chomp(tstNoStringer{}) - - if err == nil { - t.Errorf("Chomp should fail") - } - } -} - -func TestReplace(t *testing.T) { - v, _ := Replace("aab", "a", "b") - assert.Equal(t, "bbb", v) - v, _ = Replace("11a11", 1, 2) - assert.Equal(t, "22a22", v) - v, _ = Replace(12345, 1, 2) - assert.Equal(t, "22345", v) - _, e := Replace(tstNoStringer{}, "a", "b") - assert.NotNil(t, e, "tstNoStringer isn't trimmable") - _, e = Replace("a", tstNoStringer{}, "b") - assert.NotNil(t, e, "tstNoStringer cannot be converted to string") - _, e = Replace("a", "b", tstNoStringer{}) - assert.NotNil(t, e, "tstNoStringer cannot be converted to string") -} - -func TestTrim(t *testing.T) { - v, _ := Trim("1234 my way 13", "123") - assert.Equal(t, "4 my way ", v) - v, _ = Trim(" my way ", " ") - assert.Equal(t, "my way", v) - v, _ = Trim(1234, "14") - assert.Equal(t, "23", v) - _, e := Trim(tstNoStringer{}, " ") - assert.NotNil(t, e, "tstNoStringer isn't trimmable") -} - -func TestDateFormat(t *testing.T) { - for i, this := range []struct { - layout string - value interface{} - expect interface{} - }{ - {"Monday, Jan 2, 2006", "2015-01-21", "Wednesday, Jan 21, 2015"}, - {"Monday, Jan 2, 2006", time.Date(2015, time.January, 21, 0, 0, 0, 0, time.UTC), "Wednesday, Jan 21, 2015"}, - {"This isn't a date layout string", "2015-01-21", "This isn't a date layout string"}, - {"Monday, Jan 2, 2006", 1421733600, false}, - {"Monday, Jan 2, 2006", 1421733600.123, false}, - } { - result, err := DateFormat(this.layout, this.value) - if b, ok := this.expect.(bool); ok && !b { - if err == nil { - t.Errorf("[%d] DateFormat didn't return an expected error", i) - } - } else { - if err != nil { - t.Errorf("[%d] DateFormat failed: %s", i, err) - continue - } - if result != this.expect { - t.Errorf("[%d] DateFormat got %v but expected %v", i, result, this.expect) - } - } - } -} - -func TestSafeHTML(t *testing.T) { - for i, this := range []struct { - str string - tmplStr string - expectWithoutEscape string - expectWithEscape string - }{ - {`
`, `{{ . }}`, `<div></div>`, `
`}, - } { - tmpl, err := template.New("test").Parse(this.tmplStr) - if err != nil { - t.Errorf("[%d] unable to create new html template %q: %s", i, this.tmplStr, err) - continue - } - - buf := new(bytes.Buffer) - err = tmpl.Execute(buf, this.str) - if err != nil { - t.Errorf("[%d] execute template with a raw string value returns unexpected error: %s", i, err) - } - if buf.String() != this.expectWithoutEscape { - t.Errorf("[%d] execute template with a raw string value, got %v but expected %v", i, buf.String(), this.expectWithoutEscape) - } - - buf.Reset() - err = tmpl.Execute(buf, SafeHTML(this.str)) - if err != nil { - t.Errorf("[%d] execute template with an escaped string value by SafeHTML returns unexpected error: %s", i, err) - } - if buf.String() != this.expectWithEscape { - t.Errorf("[%d] execute template with an escaped string value by SafeHTML, got %v but expected %v", i, buf.String(), this.expectWithEscape) - } - } -} - -func TestSafeHTMLAttr(t *testing.T) { - for i, this := range []struct { - str string - tmplStr string - expectWithoutEscape string - expectWithEscape string - }{ - {`href="irc://irc.freenode.net/#golang"`, `irc`, `irc`, `irc`}, - } { - tmpl, err := template.New("test").Parse(this.tmplStr) - if err != nil { - t.Errorf("[%d] unable to create new html template %q: %s", i, this.tmplStr, err) - continue - } - - buf := new(bytes.Buffer) - err = tmpl.Execute(buf, this.str) - if err != nil { - t.Errorf("[%d] execute template with a raw string value returns unexpected error: %s", i, err) - } - if buf.String() != this.expectWithoutEscape { - t.Errorf("[%d] execute template with a raw string value, got %v but expected %v", i, buf.String(), this.expectWithoutEscape) - } - - buf.Reset() - err = tmpl.Execute(buf, SafeHTMLAttr(this.str)) - if err != nil { - t.Errorf("[%d] execute template with an escaped string value by SafeHTMLAttr returns unexpected error: %s", i, err) - } - if buf.String() != this.expectWithEscape { - t.Errorf("[%d] execute template with an escaped string value by SafeHTMLAttr, got %v but expected %v", i, buf.String(), this.expectWithEscape) - } - } -} - -func TestSafeCSS(t *testing.T) { - for i, this := range []struct { - str string - tmplStr string - expectWithoutEscape string - expectWithEscape string - }{ - {`width: 60px;`, `
`, `
`, `
`}, - } { - tmpl, err := template.New("test").Parse(this.tmplStr) - if err != nil { - t.Errorf("[%d] unable to create new html template %q: %s", i, this.tmplStr, err) - continue - } - - buf := new(bytes.Buffer) - err = tmpl.Execute(buf, this.str) - if err != nil { - t.Errorf("[%d] execute template with a raw string value returns unexpected error: %s", i, err) - } - if buf.String() != this.expectWithoutEscape { - t.Errorf("[%d] execute template with a raw string value, got %v but expected %v", i, buf.String(), this.expectWithoutEscape) - } - - buf.Reset() - err = tmpl.Execute(buf, SafeCSS(this.str)) - if err != nil { - t.Errorf("[%d] execute template with an escaped string value by SafeCSS returns unexpected error: %s", i, err) - } - if buf.String() != this.expectWithEscape { - t.Errorf("[%d] execute template with an escaped string value by SafeCSS, got %v but expected %v", i, buf.String(), this.expectWithEscape) - } - } -} - -func TestSafeURL(t *testing.T) { - for i, this := range []struct { - str string - tmplStr string - expectWithoutEscape string - expectWithEscape string - }{ - {`irc://irc.freenode.net/#golang`, `IRC`, `IRC`, `IRC`}, - } { - tmpl, err := template.New("test").Parse(this.tmplStr) - if err != nil { - t.Errorf("[%d] unable to create new html template %q: %s", i, this.tmplStr, err) - continue - } - - buf := new(bytes.Buffer) - err = tmpl.Execute(buf, this.str) - if err != nil { - t.Errorf("[%d] execute template with a raw string value returns unexpected error: %s", i, err) - } - if buf.String() != this.expectWithoutEscape { - t.Errorf("[%d] execute template with a raw string value, got %v but expected %v", i, buf.String(), this.expectWithoutEscape) - } - - buf.Reset() - err = tmpl.Execute(buf, SafeURL(this.str)) - if err != nil { - t.Errorf("[%d] execute template with an escaped string value by SafeURL returns unexpected error: %s", i, err) - } - if buf.String() != this.expectWithEscape { - t.Errorf("[%d] execute template with an escaped string value by SafeURL, got %v but expected %v", i, buf.String(), this.expectWithEscape) - } - } -} +// TODO(bep) test it