diff --git a/docs/content/templates/functions.md b/docs/content/templates/functions.md index 9d1981e80..7b6e0a3ee 100644 --- a/docs/content/templates/functions.md +++ b/docs/content/templates/functions.md @@ -67,7 +67,7 @@ Creates a dictionary `(map[string, interface{})`, expects parameters added in va Invalid combinations like keys that are not strings or uneven number of parameters, will result in an exception thrown. Useful for passing maps to partials when adding to a template. -e.g. Pass into "foo.html" a map with the keys "important, content" +e.g. Pass into "foo.html" a map with the keys "important, content" {{$important := .Site.Params.SomethingImportant }} {{range .Site.Params.Bar}} @@ -78,9 +78,8 @@ e.g. Pass into "foo.html" a map with the keys "important, content" Important {{.important}} {{.content}} - -or create a map on the fly to pass into +or create a map on the fly to pass into {{partial "foo" (dict "important" "Smiles" "content" "You should do more")}} @@ -313,6 +312,15 @@ Following operators are now available - `<`, `lt`: True if a given field value is lesser than a matching value - `in`: True if a given field value is included in a matching value. A matching value must be an array or a slice - `not in`: True if a given field value isn't included in a matching value. A matching value must be an array or a slice +- `intersect`: True if a given field value that is a slice / array of strings or integers contains elements in common with the matching value. It follows the same rules as the intersect function. + +*`intersect` operator, e.g.:* + + {{ range where .Site.Pages ".Params.tags" "intersect" .Params.tags }} + {{ if ne .Permalink $.Permalink }} + {{ .Render "summary" }} + {{ end }} + {{ end }} *`where` and `first` can be stacked, e.g.:* @@ -340,7 +348,7 @@ e.g. ### readDir -Gets a directory listing from a directory relative to the current project working dir. +Gets a directory listing from a directory relative to the current project working dir. So, If the project working dir has a single file named `README.txt`: @@ -349,7 +357,7 @@ So, If the project working dir has a single file named `README.txt`: ### readFile Reads a file from disk and converts it into a string. Note that the filename must be relative to the current project working dir. So, if you have a file with the name `README.txt` in the root of your project with the content `Hugo Rocks!`: - + `{{readFile "README.txt"}}` → `"Hugo Rocks!"` ## Math diff --git a/tpl/template_funcs.go b/tpl/template_funcs.go index 9131cc1ec..8b99cc09d 100644 --- a/tpl/template_funcs.go +++ b/tpl/template_funcs.go @@ -646,6 +646,7 @@ func checkCondition(v, mv reflect.Value, op string) (bool, error) { var ivp, imvp *int64 var svp, smvp *string + var slv, slmv interface{} var ima []int64 var sma []string if mv.Type() == v.Type() { @@ -668,6 +669,9 @@ func checkCondition(v, mv reflect.Value, op string) (bool, error) { imv := toTimeUnix(mv) imvp = &imv } + case reflect.Array, reflect.Slice: + slv = v.Interface() + slmv = mv.Interface() } } else { if mv.Kind() != reflect.Array && mv.Kind() != reflect.Slice { @@ -765,8 +769,24 @@ func checkCondition(v, mv reflect.Value, op string) (bool, error) { return !r, nil } return r, nil + case "intersect": + r, err := intersect(slv, slmv) + if err != nil { + return false, err + } + + if reflect.TypeOf(r).Kind() == reflect.Slice { + s := reflect.ValueOf(r) + + if s.Len() > 0 { + return true, nil + } + return false, nil + } else { + return false, errors.New("invalid intersect values") + } default: - return false, errors.New("no such an operator") + return false, errors.New("no such operator") } return false, nil } diff --git a/tpl/template_funcs_test.go b/tpl/template_funcs_test.go index 8d604e817..3152b13a1 100644 --- a/tpl/template_funcs_test.go +++ b/tpl/template_funcs_test.go @@ -18,11 +18,6 @@ import ( "encoding/base64" "errors" "fmt" - "github.com/spf13/afero" - "github.com/spf13/cast" - "github.com/spf13/hugo/hugofs" - "github.com/spf13/viper" - "github.com/stretchr/testify/assert" "html/template" "math/rand" "path" @@ -32,6 +27,12 @@ import ( "strings" "testing" "time" + + "github.com/spf13/afero" + "github.com/spf13/cast" + "github.com/spf13/hugo/hugofs" + "github.com/spf13/viper" + "github.com/stretchr/testify/assert" ) type tstNoStringer struct { @@ -1202,6 +1203,78 @@ func TestWhere(t *testing.T) { {"a": 3, "b": 4}, }, }, + { + sequence: []map[string][]string{ + {"a": []string{"A", "B", "C"}, "b": []string{"D", "E", "F"}}, {"a": []string{"G", "H", "I"}, "b": []string{"J", "K", "L"}}, {"a": []string{"M", "N", "O"}, "b": []string{"P", "Q", "R"}}, + }, + key: "b", op: "intersect", match: []string{"D", "P", "Q"}, + expect: []map[string][]string{ + {"a": []string{"A", "B", "C"}, "b": []string{"D", "E", "F"}}, {"a": []string{"M", "N", "O"}, "b": []string{"P", "Q", "R"}}, + }, + }, + { + sequence: []map[string][]int{ + {"a": []int{1, 2, 3}, "b": []int{4, 5, 6}}, {"a": []int{7, 8, 9}, "b": []int{10, 11, 12}}, {"a": []int{13, 14, 15}, "b": []int{16, 17, 18}}, + }, + key: "b", op: "intersect", match: []int{4, 10, 12}, + expect: []map[string][]int{ + {"a": []int{1, 2, 3}, "b": []int{4, 5, 6}}, {"a": []int{7, 8, 9}, "b": []int{10, 11, 12}}, + }, + }, + { + sequence: []map[string][]int8{ + {"a": []int8{1, 2, 3}, "b": []int8{4, 5, 6}}, {"a": []int8{7, 8, 9}, "b": []int8{10, 11, 12}}, {"a": []int8{13, 14, 15}, "b": []int8{16, 17, 18}}, + }, + key: "b", op: "intersect", match: []int8{4, 10, 12}, + expect: []map[string][]int8{ + {"a": []int8{1, 2, 3}, "b": []int8{4, 5, 6}}, {"a": []int8{7, 8, 9}, "b": []int8{10, 11, 12}}, + }, + }, + { + sequence: []map[string][]int16{ + {"a": []int16{1, 2, 3}, "b": []int16{4, 5, 6}}, {"a": []int16{7, 8, 9}, "b": []int16{10, 11, 12}}, {"a": []int16{13, 14, 15}, "b": []int16{16, 17, 18}}, + }, + key: "b", op: "intersect", match: []int16{4, 10, 12}, + expect: []map[string][]int16{ + {"a": []int16{1, 2, 3}, "b": []int16{4, 5, 6}}, {"a": []int16{7, 8, 9}, "b": []int16{10, 11, 12}}, + }, + }, + { + sequence: []map[string][]int32{ + {"a": []int32{1, 2, 3}, "b": []int32{4, 5, 6}}, {"a": []int32{7, 8, 9}, "b": []int32{10, 11, 12}}, {"a": []int32{13, 14, 15}, "b": []int32{16, 17, 18}}, + }, + key: "b", op: "intersect", match: []int32{4, 10, 12}, + expect: []map[string][]int32{ + {"a": []int32{1, 2, 3}, "b": []int32{4, 5, 6}}, {"a": []int32{7, 8, 9}, "b": []int32{10, 11, 12}}, + }, + }, + { + sequence: []map[string][]int64{ + {"a": []int64{1, 2, 3}, "b": []int64{4, 5, 6}}, {"a": []int64{7, 8, 9}, "b": []int64{10, 11, 12}}, {"a": []int64{13, 14, 15}, "b": []int64{16, 17, 18}}, + }, + key: "b", op: "intersect", match: []int64{4, 10, 12}, + expect: []map[string][]int64{ + {"a": []int64{1, 2, 3}, "b": []int64{4, 5, 6}}, {"a": []int64{7, 8, 9}, "b": []int64{10, 11, 12}}, + }, + }, + { + sequence: []map[string][]float32{ + {"a": []float32{1.0, 2.0, 3.0}, "b": []float32{4.0, 5.0, 6.0}}, {"a": []float32{7.0, 8.0, 9.0}, "b": []float32{10.0, 11.0, 12.0}}, {"a": []float32{13.0, 14.0, 15.0}, "b": []float32{16.0, 17.0, 18.0}}, + }, + key: "b", op: "intersect", match: []float32{4, 10, 12}, + expect: []map[string][]float32{ + {"a": []float32{1.0, 2.0, 3.0}, "b": []float32{4.0, 5.0, 6.0}}, {"a": []float32{7.0, 8.0, 9.0}, "b": []float32{10.0, 11.0, 12.0}}, + }, + }, + { + sequence: []map[string][]float64{ + {"a": []float64{1.0, 2.0, 3.0}, "b": []float64{4.0, 5.0, 6.0}}, {"a": []float64{7.0, 8.0, 9.0}, "b": []float64{10.0, 11.0, 12.0}}, {"a": []float64{13.0, 14.0, 15.0}, "b": []float64{16.0, 17.0, 18.0}}, + }, + key: "b", op: "intersect", match: []float64{4, 10, 12}, + expect: []map[string][]float64{ + {"a": []float64{1.0, 2.0, 3.0}, "b": []float64{4.0, 5.0, 6.0}}, {"a": []float64{7.0, 8.0, 9.0}, "b": []float64{10.0, 11.0, 12.0}}, + }, + }, { sequence: []map[string]int{ {"a": 1, "b": 2}, {"a": 3, "b": 4}, {"a": 5, "b": 6},