From e819bc4af0a61800019e20d986d68aae21461748 Mon Sep 17 00:00:00 2001 From: Ashwin Maroli Date: Wed, 30 Jan 2019 20:14:45 +0530 Subject: [PATCH] Support for binary operators in where_exp filter (#6998) Merge pull request 6998 --- lib/jekyll/filters.rb | 50 ++++++++++++++++++++++++++++++++----------- test/test_filters.rb | 23 ++++++++++++++++++++ 2 files changed, 61 insertions(+), 12 deletions(-) diff --git a/lib/jekyll/filters.rb b/lib/jekyll/filters.rb index 62cd0f65..23ad0cde 100644 --- a/lib/jekyll/filters.rb +++ b/lib/jekyll/filters.rb @@ -365,23 +365,49 @@ module Jekyll end end + # ----------- The following set of code was *adapted* from Liquid::If + # ----------- ref: https://git.io/vp6K6 + # 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(Liquid::Expression.parse(left_expr), - operator, - Liquid::Expression.parse(parser.expression)) - else - Liquid::Condition.new(Liquid::Expression.parse(left_expr)) - end - parser.consume(:end_of_string) + parser = Liquid::Parser.new(exp) + condition = parse_binary_comparison(parser) + parser.consume(:end_of_string) condition end + + # Generate a Liquid::Condition object from a Liquid::Parser object additionally processing + # the parsed expression based on whether the expression consists of binary operations with + # Liquid operators `and` or `or` + # + # - parser: an instance of Liquid::Parser + # + # Returns an instance of Liquid::Condition + def parse_binary_comparison(parser) + parse_comparison(parser).tap do |condition| + binary_operator = parser.id?("and") || parser.id?("or") + condition.send(binary_operator, parse_comparison(parser)) if binary_operator + end + end + + # Generates a Liquid::Condition object from a Liquid::Parser object based on whether the parsed + # expression involves a "comparison" operator (e.g. <, ==, >, !=, etc) + # + # - parser: an instance of Liquid::Parser + # + # Returns an instance of Liquid::Condition + def parse_comparison(parser) + left_operand = Liquid::Expression.parse(parser.expression) + operator = parser.consume?(:comparison) + + # No comparison-operator detected. Initialize a Liquid::Condition using only left operand + return Liquid::Condition.new(left_operand) unless operator + + # Parse what remained after extracting the left operand and the `:comparison` operator + # and initialize a Liquid::Condition object using the operands and the comparison-operator + Liquid::Condition.new(left_operand, operator, Liquid::Expression.parse(parser.expression)) + end end end diff --git a/test/test_filters.rb b/test/test_filters.rb index c8909f25..c007dc86 100644 --- a/test/test_filters.rb +++ b/test/test_filters.rb @@ -48,6 +48,7 @@ class TestFilters < JekyllUnitTest @time_as_numeric = 1_399_680_607 @integer_as_string = "142857" @array_of_objects = [ + { "color" => "teal", "size" => "large" }, { "color" => "red", "size" => "large" }, { "color" => "red", "size" => "medium" }, { "color" => "blue", "size" => "medium" }, @@ -914,6 +915,28 @@ class TestFilters < JekyllUnitTest ) end + should "filter objects appropriately with 'or', 'and' operators" do + assert_equal( + [ + { "color" => "teal", "size" => "large" }, + { "color" => "red", "size" => "large" }, + { "color" => "red", "size" => "medium" }, + ], + @filter.where_exp( + @array_of_objects, "item", "item.color == 'red' or item.size == 'large'" + ) + ) + + assert_equal( + [ + { "color" => "red", "size" => "large" }, + ], + @filter.where_exp( + @array_of_objects, "item", "item.color == 'red' and item.size == 'large'" + ) + ) + end + should "stringify during comparison for compatibility with liquid parsing" do hash = { "The Words" => { "rating" => 1.2, "featured" => false },