Skip to content

Commit

Permalink
Filters can have (single) parameters
Browse files Browse the repository at this point in the history
  • Loading branch information
osteele committed Jun 27, 2017
1 parent 30211ac commit 70aa70d
Show file tree
Hide file tree
Showing 7 changed files with 185 additions and 133 deletions.
6 changes: 3 additions & 3 deletions chunks/render_test.go
Expand Up @@ -49,9 +49,9 @@ var filterTests = []struct{ in, expected string }{
// list filters
// {{ site.pages | map: 'category' | compact | join "," %}
// {% assign my_array = "apples, oranges, peaches, plums" | split: ", " %}{{ my_array.first }}
// {`{{"John, Paul, George, Ringo" | split: ", " | join: "and"}}`, "John and Paul and George and Ringo"},
{`{{ animals | sort | join }}`, "Sally Snake, giraffe, octopus, zebra"},
// {`{{ animals | sort | join: "," }}`, "Sally Snake, giraffe, octopus, zebra"},
// {`{{"John, Paul, George, Ringo" | split: ", " }}`, "John and Paul and George and Ringo"},
{`{{"John, Paul, George, Ringo" | split: ", " | join: " and "}}`, "John and Paul and George and Ringo"},
{`{{ animals | sort | join: ", " }}`, "Sally Snake, giraffe, octopus, zebra"},
// join, last, map, slice, sort, sort_natural, reverse, size, uniq

// string filters
Expand Down
16 changes: 10 additions & 6 deletions expressions/expressions.y
Expand Up @@ -15,17 +15,17 @@ func init() {
val interface{}
f func(Context) interface{}
}
%type <f> expr rel
%type <f> expr rel expr1
%token <val> LITERAL
%token <name> IDENTIFIER RELATION
%token <name> IDENTIFIER KEYWORD RELATION
%token ASSIGN
%token EQ
%left '.'
%left '.' '|'
%left '<' '>'
%%
start:
rel ';' { yylex.(*lexer).val = $1 }
| ASSIGN IDENTIFIER '=' expr ';' {
| ASSIGN IDENTIFIER '=' expr1 ';' {
name, expr := $2, $4
yylex.(*lexer).val = func(ctx Context) interface{} {
ctx.Set(name, expr(ctx))
Expand All @@ -51,7 +51,6 @@ expr:
return nil
}
}
| expr '|' IDENTIFIER { $$ = makeFilter($1, $3) }
| expr '[' expr ']' {
e, i := $1, $3
$$ = func(ctx Context) interface{} {
Expand All @@ -70,10 +69,15 @@ expr:
return nil
}
}

expr1:
expr
| expr1 '|' IDENTIFIER { $$ = makeFilter($1, $3, nil) }
| expr1 '|' KEYWORD expr { $$ = makeFilter($1, $3, $4) }
;

rel:
expr
expr1
| expr EQ expr {
a, b := $1, $3
$$ = func(ctx Context) interface{} {
Expand Down
38 changes: 32 additions & 6 deletions expressions/filters.go
Expand Up @@ -9,12 +9,16 @@ import (

type valueFn func(Context) interface{}

func joinFilter(in []interface{}) interface{} {
func joinFilter(in []interface{}, sep interface{}) interface{} {
a := make([]string, len(in))
s := ", "
if sep != nil {
s = fmt.Sprint(sep)
}
for i, x := range in {
a[i] = fmt.Sprint(x)
}
return strings.Join(a, ", ")
return strings.Join(a, s)
}

func sortFilter(in []interface{}) []interface{} {
Expand All @@ -26,19 +30,41 @@ func sortFilter(in []interface{}) []interface{} {
return a
}

var filters = map[string]interface{}{
"join": joinFilter,
"sort": sortFilter,
func splitFilter(in, sep string) interface{} {
return strings.Split(in, sep)
}

var filters = map[string]interface{}{}

func init() {
DefineStandardFilters()
}

func DefineStandardFilters() {
DefineFilter("join", joinFilter)
DefineFilter("sort", sortFilter)
DefineFilter("split", splitFilter)
}

func DefineFilter(name string, fn interface{}) {
rf := reflect.ValueOf(fn)
if rf.Kind() != reflect.Func || rf.Type().NumIn() < 0 || rf.Type().NumOut() != 1 {
panic(fmt.Errorf("a filter must be a function with at least one input and exactly one output"))
}
filters[name] = fn
}

func makeFilter(f valueFn, name string) valueFn {
func makeFilter(f valueFn, name string, param valueFn) valueFn {
fn, ok := filters[name]
if !ok {
panic(fmt.Errorf("unknown filter: %s", name))
}
fr := reflect.ValueOf(fn)
return func(ctx Context) interface{} {
args := []interface{}{f(ctx)}
if param != nil {
args = append(args, param(ctx))
}
in := convertArguments(fr, args)
r := fr.Call(in)[0]
return r.Interface()
Expand Down
5 changes: 4 additions & 1 deletion expressions/generics.go
Expand Up @@ -30,6 +30,9 @@ func convertType(val interface{}, t reflect.Type) reflect.Value {
if r.Type().ConvertibleTo(t) {
return r.Convert(t)
}
if reflect.PtrTo(r.Type()) == t {
return reflect.ValueOf(&val)
}
switch t.Kind() {
case reflect.Slice:
if r.Kind() != reflect.Array && r.Kind() != reflect.Slice {
Expand All @@ -42,7 +45,7 @@ func convertType(val interface{}, t reflect.Type) reflect.Value {
}
return x
}
panic(fmt.Errorf("convertType: can't convert %v to %v", val, t))
panic(fmt.Errorf("convertType: can't convert %#v<%s> to %v", val, r.Type(), t))
}

// Convert args to match the input types of fr, which should be a function reflection.
Expand Down

0 comments on commit 70aa70d

Please sign in to comment.