From 7f18ac8f9943a88f29a9f8e61bc6dc7cd3fa2e20 Mon Sep 17 00:00:00 2001 From: Thiago Arrais Date: Tue, 25 Oct 2016 17:38:22 -0300 Subject: [PATCH 1/5] Group using arbitraty Liquid expressions --- docs/_docs/templates.md | 16 +++++ lib/jekyll/filters.rb | 31 +--------- lib/jekyll/filters/grouping_filters.rb | 56 ++++++++++++++++++ test/test_filters.rb | 82 ++++++++++++++++++++++++++ 4 files changed, 157 insertions(+), 28 deletions(-) create mode 100644 lib/jekyll/filters/grouping_filters.rb diff --git a/docs/_docs/templates.md b/docs/_docs/templates.md index 54f914f7..0d37f8e1 100644 --- a/docs/_docs/templates.md +++ b/docs/_docs/templates.md @@ -147,6 +147,22 @@ common tasks easier.

+ + +

Group By Expression

+

Group an array's items using a Liquid expression.

+ + +

+ {% raw %}{{ site.members | group_by_exp:"item", +"item.graduation_year | truncate: 3, \"\"" }}{% endraw %} +

+

+ [{"name"=>"201...", "items"=>[...]}, +{"name"=>"200...", "items"=>[...]}] +

+ +

XML Escape

diff --git a/lib/jekyll/filters.rb b/lib/jekyll/filters.rb index 29f2a2b5..f1467fa5 100644 --- a/lib/jekyll/filters.rb +++ b/lib/jekyll/filters.rb @@ -8,6 +8,8 @@ require_all "jekyll/filters" module Jekyll module Filters include URLFilters + include GroupingFilters + # Convert a Markdown string into HTML output. # # input - The Markdown String to convert. @@ -205,29 +207,6 @@ module Jekyll as_liquid(input).to_json end - # Group an array of items by a property - # - # input - the inputted Enumerable - # property - the property - # - # Returns an array of Hashes, each looking something like this: - # {"name" => "larry" - # "items" => [...] } # all the items where `property` == "larry" - def group_by(input, property) - if groupable?(input) - input.group_by { |item| item_property(item, property).to_s } - .each_with_object([]) do |item, array| - array << { - "name" => item.first, - "items" => item.last, - "size" => item.last.size - } - end - else - input - end - end - # Filter an array of objects # # input - the object array @@ -381,11 +360,6 @@ module Jekyll end.localtime end - private - def groupable?(element) - element.respond_to?(:group_by) - end - private def item_property(item, property) if item.respond_to?(:to_liquid) @@ -436,6 +410,7 @@ module Jekyll condition end + end end diff --git a/lib/jekyll/filters/grouping_filters.rb b/lib/jekyll/filters/grouping_filters.rb new file mode 100644 index 00000000..372b1e7b --- /dev/null +++ b/lib/jekyll/filters/grouping_filters.rb @@ -0,0 +1,56 @@ +module Jekyll + module Filters + module GroupingFilters + # Group an array of items by a property + # + # input - the inputted Enumerable + # property - the property + # + # Returns an array of Hashes, each looking something like this: + # {"name" => "larry" + # "items" => [...] } # all the items where `property` == "larry" + def group_by(input, property) + if groupable?(input) + groups = input.group_by { |item| item_property(item, property).to_s } + make_grouped_array(groups) + else + input + end + end + + def group_by_exp(input, variable, expression) + return input unless groupable?(input) + + parsed_expr = parse_expression(expression) + @context.stack do + groups = input.group_by do |item| + @context[variable] = item + parsed_expr.render(@context) + end + make_grouped_array(groups) + end + end + + private + def parse_expression(str) + Liquid::Variable.new(str, {}) + end + + private + def groupable?(element) + element.respond_to?(:group_by) + end + + private + def make_grouped_array(groups) + groups.each_with_object([]) do |item, array| + array << { + "name" => item.first, + "items" => item.last, + "size" => item.last.size + } + end + end + end + end +end diff --git a/test/test_filters.rb b/test/test_filters.rb index 9a0f87d4..9a2909ef 100644 --- a/test/test_filters.rb +++ b/test/test_filters.rb @@ -747,6 +747,88 @@ class TestFilters < JekyllUnitTest end end + context "group_by_exp filter" do + should "successfully group array of Jekyll::Page's" do + @filter.site.process + groups = @filter.group_by_exp(@filter.site.pages, "page", "page.layout | upcase") + groups.each do |g| + assert( + ["DEFAULT", "NIL", ""].include?(g["name"]), + "#{g["name"]} isn't a valid grouping." + ) + case g["name"] + when "DEFAULT" + assert( + g["items"].is_a?(Array), + "The list of grouped items for 'default' is not an Array." + ) + assert_equal 5, g["items"].size + when "nil" + assert( + g["items"].is_a?(Array), + "The list of grouped items for 'nil' is not an Array." + ) + assert_equal 2, g["items"].size + when "" + assert( + g["items"].is_a?(Array), + "The list of grouped items for '' is not an Array." + ) + assert_equal 15, g["items"].size + end + end + end + + should "include the size of each grouping" do + groups = @filter.group_by_exp(@filter.site.pages, "page", "page.layout") + groups.each do |g| + p g + assert_equal( + g["items"].size, + g["size"], + "The size property for '#{g["name"]}' doesn't match the size of the Array." + ) + end + end + + should "allow more complex filters" do + items = [ + { "version"=>"1.0", "result"=>"slow" }, + { "version"=>"1.1.5", "result"=>"medium" }, + { "version"=>"2.7.3", "result"=>"fast" } + ] + + result = @filter.group_by_exp(items, "item", "item.version | split: '.' | first") + assert_equal 2, result.size + end + + should "be equivalent of group_by" do + actual = @filter.group_by_exp(@filter.site.pages, "page", "page.layout") + expected = @filter.group_by(@filter.site.pages, "layout") + + assert_equal expected, actual + end + + should "return any input that is not an array" do + assert_equal "some string", @filter.group_by_exp("some string", "la", "le") + end + + should "group by full element (as opposed to a field of the element)" do + items = %w(a b c d) + + result = @filter.group_by_exp(items, "item", "item") + assert_equal 4, result.length + assert_equal ["a"], result.first["items"] + end + + should "accept hashes" do + hash = { 1 => "a", 2 => "b", 3 => "c", 4 => "d" } + + result = @filter.group_by_exp(hash, "item", "item") + assert_equal 4, result.length + end + end + context "sort filter" do should "raise Exception when input is nil" do err = assert_raises ArgumentError do From 7ac9653f4efe61f12b27adb0a7261547c17e266c Mon Sep 17 00:00:00 2001 From: Thiago Arrais Date: Fri, 4 Nov 2016 18:32:52 -0300 Subject: [PATCH 2/5] RDoc for group_by_exp --- lib/jekyll/filters/grouping_filters.rb | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lib/jekyll/filters/grouping_filters.rb b/lib/jekyll/filters/grouping_filters.rb index 372b1e7b..804c6559 100644 --- a/lib/jekyll/filters/grouping_filters.rb +++ b/lib/jekyll/filters/grouping_filters.rb @@ -18,6 +18,13 @@ module Jekyll end end + # Group an array of items by 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 group_by_exp(input, variable, expression) return input unless groupable?(input) From 4ed41558d13fa282853b8eea5250c69936dda4da Mon Sep 17 00:00:00 2001 From: Thiago Arrais Date: Wed, 30 Nov 2016 17:54:59 -0300 Subject: [PATCH 3/5] Whoops! --- test/test_filters.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/test/test_filters.rb b/test/test_filters.rb index 9a2909ef..782ac52a 100644 --- a/test/test_filters.rb +++ b/test/test_filters.rb @@ -782,7 +782,6 @@ class TestFilters < JekyllUnitTest should "include the size of each grouping" do groups = @filter.group_by_exp(@filter.site.pages, "page", "page.layout") groups.each do |g| - p g assert_equal( g["items"].size, g["size"], From 91f0b91d6a4421af22f79fa96400038940b60606 Mon Sep 17 00:00:00 2001 From: Thiago Arrais Date: Wed, 30 Nov 2016 18:16:25 -0300 Subject: [PATCH 4/5] Rename for more idiomatic Ruby --- lib/jekyll/filters/grouping_filters.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/jekyll/filters/grouping_filters.rb b/lib/jekyll/filters/grouping_filters.rb index 804c6559..a16901d9 100644 --- a/lib/jekyll/filters/grouping_filters.rb +++ b/lib/jekyll/filters/grouping_filters.rb @@ -12,7 +12,7 @@ module Jekyll def group_by(input, property) if groupable?(input) groups = input.group_by { |item| item_property(item, property).to_s } - make_grouped_array(groups) + grouped_array(groups) else input end @@ -34,7 +34,7 @@ module Jekyll @context[variable] = item parsed_expr.render(@context) end - make_grouped_array(groups) + grouped_array(groups) end end @@ -49,7 +49,7 @@ module Jekyll end private - def make_grouped_array(groups) + def grouped_array(groups) groups.each_with_object([]) do |item, array| array << { "name" => item.first, From d4c8d7fd2be52a75a805ed920f38c38cb3611e3d Mon Sep 17 00:00:00 2001 From: Thiago Arrais Date: Fri, 9 Dec 2016 09:56:40 -0300 Subject: [PATCH 5/5] Ignore symlinked file in windows --- test/test_filters.rb | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/test/test_filters.rb b/test/test_filters.rb index 782ac52a..686e3a47 100644 --- a/test/test_filters.rb +++ b/test/test_filters.rb @@ -762,7 +762,9 @@ class TestFilters < JekyllUnitTest g["items"].is_a?(Array), "The list of grouped items for 'default' is not an Array." ) - assert_equal 5, g["items"].size + # adjust array.size to ignore symlinked page in Windows + qty = Utils::Platforms.really_windows? ? 4 : 5 + assert_equal qty, g["items"].size when "nil" assert( g["items"].is_a?(Array), @@ -774,7 +776,9 @@ class TestFilters < JekyllUnitTest g["items"].is_a?(Array), "The list of grouped items for '' is not an Array." ) - assert_equal 15, g["items"].size + # adjust array.size to ignore symlinked page in Windows + qty = Utils::Platforms.really_windows? ? 14 : 15 + assert_equal qty, g["items"].size end end end