diff --git a/lib/jekyll/filters.rb b/lib/jekyll/filters.rb index 02523d9c..16377623 100644 --- a/lib/jekyll/filters.rb +++ b/lib/jekyll/filters.rb @@ -1,6 +1,7 @@ require 'uri' require 'json' require 'date' +require 'liquid' module Jekyll module Filters @@ -225,6 +226,26 @@ module Jekyll input.select { |object| Array(item_property(object, property)).map(&:to_s).include?(value.to_s) } end + # Filters an array of objects against an expression + # + # input - the object array + # variable - the variable to assign each item to in the expression + # expression - a Liquid comparison expression passed in as a string + # + # Returns the filtered array of objects + def where_exp(input, variable, expression) + return input unless input.is_a?(Enumerable) + input = input.values if input.is_a?(Hash) # FIXME + + condition = parse_condition(expression) + @context.stack do + input.select do |object| + @context[variable] = object + condition.evaluate(@context) + end + end + end + # Sort an array of objects # # input - the object array @@ -363,5 +384,21 @@ module Jekyll end end end + + # Parse a string to a Liquid Condition + def parse_condition(exp) + parser = Liquid::Parser.new(exp) + left_expr = parser.expression + operator = parser.consume?(:comparison) + condition = + if operator + Liquid::Condition.new(left_expr, operator, parser.expression) + else + Liquid::Condition.new(left_expr) + end + parser.consume(:end_of_string) + + condition + end end end diff --git a/site/_docs/templates.md b/site/_docs/templates.md index 60892505..663a060d 100644 --- a/site/_docs/templates.md +++ b/site/_docs/templates.md @@ -88,6 +88,22 @@ common tasks easier.
+Where Expression
+Select all the objects in an array where the expression is true.
+
+ {% raw %}{{ site.members | where_exp:"item",
+"item.graduation_year == 2014" }}{% endraw %}
+ {% raw %}{{ site.members | where_exp:"item",
+"item.graduation_year < 2014" }}{% endraw %}
+ {% raw %}{{ site.members | where_exp:"item",
+"item.projects contains 'foo'" }}{% endraw %}
+
Group By
diff --git a/test/test_filters.rb b/test/test_filters.rb index ae685872..565e8208 100644 --- a/test/test_filters.rb +++ b/test/test_filters.rb @@ -354,6 +354,64 @@ class TestFilters < JekyllUnitTest end end + context "where_exp filter" do + should "return any input that is not an array" do + assert_equal "some string", @filter.where_exp("some string", "la", "le") + end + + should "filter objects in a hash appropriately" do + hash = {"a"=>{"color"=>"red"}, "b"=>{"color"=>"blue"}} + assert_equal 1, @filter.where_exp(hash, "item", "item.color == 'red'").length + assert_equal [{"color"=>"red"}], @filter.where_exp(hash, "item", "item.color == 'red'") + end + + should "filter objects appropriately" do + assert_equal 2, @filter.where_exp(@array_of_objects, "item", "item.color == 'red'").length + end + + should "stringify during comparison for compatibility with liquid parsing" do + hash = { + "The Words" => {"rating" => 1.2, "featured" => false}, + "Limitless" => {"rating" => 9.2, "featured" => true}, + "Hustle" => {"rating" => 4.7, "featured" => true}, + } + + results = @filter.where_exp(hash, "item", "item.featured == true") + assert_equal 2, results.length + assert_equal 9.2, results[0]["rating"] + assert_equal 4.7, results[1]["rating"] + + results = @filter.where_exp(hash, "item", "item.rating == 4.7") + assert_equal 1, results.length + assert_equal 4.7, results[0]["rating"] + end + + should "filter with other operators" do + assert_equal [3, 4, 5], @filter.where_exp([ 1, 2, 3, 4, 5 ], "n", "n >= 3") + end + + objects = [ + { "id" => "a", "groups" => [1, 2] }, + { "id" => "b", "groups" => [2, 3] }, + { "id" => "c" }, + { "id" => "d", "groups" => [1, 3] } + ] + should "filter with the contains operator over arrays" do + results = @filter.where_exp(objects, "obj", "obj.groups contains 1") + assert_equal 2, results.length + assert_equal "a", results[0]["id"] + assert_equal "d", results[1]["id"] + end + + should "filter with the contains operator over hash keys" do + results = @filter.where_exp(objects, "obj", "obj contains 'groups'") + assert_equal 3, results.length + assert_equal "a", results[0]["id"] + assert_equal "b", results[1]["id"] + assert_equal "d", results[2]["id"] + end + end + context "sort filter" do should "raise Exception when input is nil" do err = assert_raises ArgumentError do