From 66c4ff8800f11858a1d4073ae83c4ea30519efbc Mon Sep 17 00:00:00 2001
From: Thomas Wood
Date: Fri, 5 Feb 2016 20:27:33 +0000
Subject: [PATCH 1/3] 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.
---
lib/jekyll/filters.rb | 36 +++++++++++++++++++++++++
site/_docs/templates.md | 16 ++++++++++++
test/test_filters.rb | 58 +++++++++++++++++++++++++++++++++++++++++
3 files changed, 110 insertions(+)
diff --git a/lib/jekyll/filters.rb b/lib/jekyll/filters.rb
index 02523d9c..613726fd 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)
+
+ 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
diff --git a/site/_docs/templates.md b/site/_docs/templates.md
index 60892505..cb6a145b 100644
--- a/site/_docs/templates.md
+++ b/site/_docs/templates.md
@@ -88,6 +88,22 @@ common tasks easier.
+
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
From 1ef7653fed25b5ce5ee327b75030122fbe9abdb0 Mon Sep 17 00:00:00 2001
From: Thomas Wood
Date: Tue, 12 Apr 2016 18:52:34 +0100
Subject: [PATCH 2/3] Fix minor code style recommendations and typos.
---
lib/jekyll/filters.rb | 20 ++++++++++----------
site/_docs/templates.md | 2 +-
2 files changed, 11 insertions(+), 11 deletions(-)
diff --git a/lib/jekyll/filters.rb b/lib/jekyll/filters.rb
index 613726fd..0d55e9ba 100644
--- a/lib/jekyll/filters.rb
+++ b/lib/jekyll/filters.rb
@@ -235,13 +235,13 @@ module Jekyll
# 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)
+ input = input.values if input.is_a?(Hash) # FIXME
- c = parse_condition(expression)
+ condition = parse_condition(expression)
@context.stack do
input.select do |object|
@context[variable] = object
- c.evaluate(@context)
+ condition.evaluate(@context)
end
end
end
@@ -388,13 +388,13 @@ module Jekyll
# 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
+ left_expr = parser.expression
+ condition =
+ if operator = parser.consume?(:comparison)
+ Liquid::Condition.new(left_expr, operator, parser.expression)
+ else
+ Liquid::Condition.new(left_expr)
+ end
parser.consume(:end_of_string)
condition
diff --git a/site/_docs/templates.md b/site/_docs/templates.md
index cb6a145b..663a060d 100644
--- a/site/_docs/templates.md
+++ b/site/_docs/templates.md
@@ -100,7 +100,7 @@ common tasks easier.
{% raw %}{{ site.members | where_exp:"item",
"item.graduation_year < 2014" }}{% endraw %}
{% raw %}{{ site.members | where_exp:"item",
-"item.projects includes 'foo'" }}{% endraw %}
+"item.projects contains 'foo'" }}{% endraw %}
|
From e470cae6da5a50b317627a39bd1f83144b68501f Mon Sep 17 00:00:00 2001
From: Thomas Wood