Add a where_exp filter for filtering by expression

This commit introduces a where_exp filter, which can be used as follows:
  `{{ array | where_exp: "item", "item == 10" }}`
  `{{ array | where_exp: "item", "item.field > 10" }}`
  `{{ site.posts | where_exp: "post", "post contains 'field'" }}`
  `{{ site.posts | where_exp: "post", "post.array contains 'giraffes'" }}`

This permits a variety of use cases, such as reported in: jekyll#4467,
jekyll#4385, jekyll#2787.
This commit is contained in:
Thomas Wood 2016-02-05 20:27:33 +00:00
parent 64d1a81968
commit 66c4ff8800
3 changed files with 110 additions and 0 deletions

View File

@ -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)
c = parse_condition(expression)
@context.stack do
input.select do |object|
@context[variable] = object
c.evaluate(@context)
end
end
end
# Sort an array of objects
#
# input - the object array
@ -363,5 +384,20 @@ module Jekyll
end
end
end
# Parse a string to a Liquid Condition
def parse_condition(exp)
parser = Liquid::Parser.new(exp)
a = parser.expression
if op = parser.consume?(:comparison)
b = parser.expression
condition = Liquid::Condition.new(a, op, b)
else
condition = Liquid::Condition.new(a)
end
parser.consume(:end_of_string)
condition
end
end
end

View File

@ -88,6 +88,22 @@ common tasks easier.
</p>
</td>
</tr>
<tr>
<td>
<p class="name"><strong>Where Expression</strong></p>
<p>Select all the objects in an array where the expression is true.</p>
</td>
<td class="align-center">
<p>
<code class="filter">{% raw %}{{ site.members | where_exp:"item",
"item.graduation_year == 2014" }}{% endraw %}</code>
<code class="filter">{% raw %}{{ site.members | where_exp:"item",
"item.graduation_year < 2014" }}{% endraw %}</code>
<code class="filter">{% raw %}{{ site.members | where_exp:"item",
"item.projects includes 'foo'" }}{% endraw %}</code>
</p>
</td>
</tr>
<tr>
<td>
<p class="name"><strong>Group By</strong></p>

View File

@ -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