From 03b3446119c83e5c89e3b7207042d1c1f6756917 Mon Sep 17 00:00:00 2001 From: Martin Hanzel Date: Sun, 3 May 2015 20:53:58 -0400 Subject: [PATCH 1/4] Resolves #529. Resolves #404. Added natural sorting filter and tests. --- lib/liquid/standardfilters.rb | 21 +++++++++++++++++++ test/integration/filter_test.rb | 36 +++++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+) diff --git a/lib/liquid/standardfilters.rb b/lib/liquid/standardfilters.rb index b5332f0e..712e1727 100644 --- a/lib/liquid/standardfilters.rb +++ b/lib/liquid/standardfilters.rb @@ -122,6 +122,27 @@ module Liquid end end + # Sort elements of an array ignoring case if strings + # provide optional property with which to sort an array of hashes or drops + def sort_natural(input, property = nil) + ary = InputIterator.new(input) + + # Quick function that returns the downcased object if it has a downcase, + # otherwise it returns the object itself. + insensitive = lambda do |obj| + obj = obj.downcase if obj.respond_to? :downcase + obj + end + + if property.nil? + ary.sort {|a,b| insensitive.call(a) <=> insensitive.call(b) } + elsif ary.first.respond_to?(:[]) && !ary.first[property].nil? + ary.sort {|a,b| insensitive.call(a[property]) <=> insensitive.call(b[property]) } + elsif ary.first.respond_to?(property) + ary.sort {|a,b| insensitive.call(a.send(property)) <=> insensitive.call(b.send(property)) } + end + end + # Remove duplicate elements from an array # provide optional property with which to determine uniqueness def uniq(input, property = nil) diff --git a/test/integration/filter_test.rb b/test/integration/filter_test.rb index 0a45075e..23f64b76 100644 --- a/test/integration/filter_test.rb +++ b/test/integration/filter_test.rb @@ -74,11 +74,38 @@ class FiltersTest < Minitest::Test @context['numbers'] = [2,1,4,3] @context['words'] = ['expected', 'as', 'alphabetic'] @context['arrays'] = ['flower', 'are'] + @context['case_sensitive'] = ['sensitive', 'Expected', 'case'] assert_equal [1,2,3,4], Variable.new("numbers | sort").render(@context) assert_equal ['alphabetic', 'as', 'expected'], Variable.new("words | sort").render(@context) assert_equal [3], Variable.new("value | sort").render(@context) assert_equal ['are', 'flower'], Variable.new("arrays | sort").render(@context) + assert_equal ['Expected', 'case', 'sensitive'], Variable.new("case_sensitive | sort").render(@context) + end + + def test_sort_natural + @context['value'] = 3 + @context['numbers'] = [2,1,4,3] + @context['words'] = ['case', 'Assert', 'Insensitive'] + # This specific syntax forces hashes to have string keys. Colons won't work. + @context['hashes'] = [{ 'a' => 'A'}, { 'a' => 'b'}, { 'a' => 'C' }] + @context['objects'] = [TestObject.new('A'), TestObject.new('b'), TestObject.new('C')] + + assert_equal [1,2,3,4], Variable.new("numbers | sort_natural").render(@context) + assert_equal ['Assert', 'case', 'Insensitive'], Variable.new("words | sort_natural").render(@context) + assert_equal [3], Variable.new("value | sort_natural").render(@context) + + # Test hashes + sorted = Variable.new("hashes | sort_natural: 'a'").render(@context) + assert_equal sorted[0]['a'], 'A' + assert_equal sorted[1]['a'], 'b' + assert_equal sorted[2]['a'], 'C' + + # Test objects + sorted = Variable.new("objects | sort_natural: 'a'").render(@context) + assert_equal sorted[0].a, 'A' + assert_equal sorted[1].a, 'b' + assert_equal sorted[2].a, 'C' end def test_strip_html @@ -136,3 +163,12 @@ class FiltersInTemplate < Minitest::Test assert_equal " 1000$ CAD ", Template.parse("{{1000 | money}}").render!(nil, [CanadianMoneyFilter]) end end # FiltersTest + +# Simple object that may be passed into a filter. +# Note to test subjects: do not smuggle test objects out of the testing area. +class TestObject + attr_accessor :a + def initialize(a) + @a = a + end +end From 3a082ddbbd4d846a402a89e6f9a2b034359d913e Mon Sep 17 00:00:00 2001 From: Martin Hanzel Date: Mon, 4 May 2015 11:55:14 -0400 Subject: [PATCH 2/4] Changed sort_natural filter to use casecmp. Strings only. --- lib/liquid/standardfilters.rb | 13 +++---------- test/integration/filter_test.rb | 7 ++----- 2 files changed, 5 insertions(+), 15 deletions(-) diff --git a/lib/liquid/standardfilters.rb b/lib/liquid/standardfilters.rb index 712e1727..745eb940 100644 --- a/lib/liquid/standardfilters.rb +++ b/lib/liquid/standardfilters.rb @@ -127,19 +127,12 @@ module Liquid def sort_natural(input, property = nil) ary = InputIterator.new(input) - # Quick function that returns the downcased object if it has a downcase, - # otherwise it returns the object itself. - insensitive = lambda do |obj| - obj = obj.downcase if obj.respond_to? :downcase - obj - end - if property.nil? - ary.sort {|a,b| insensitive.call(a) <=> insensitive.call(b) } + ary.sort {|a,b| a.casecmp b } elsif ary.first.respond_to?(:[]) && !ary.first[property].nil? - ary.sort {|a,b| insensitive.call(a[property]) <=> insensitive.call(b[property]) } + ary.sort {|a,b| a[property].casecmp b[property] } elsif ary.first.respond_to?(property) - ary.sort {|a,b| insensitive.call(a.send(property)) <=> insensitive.call(b.send(property)) } + ary.sort {|a,b| a.send(property).casecmp b.send(property) } end end diff --git a/test/integration/filter_test.rb b/test/integration/filter_test.rb index 23f64b76..da2593a7 100644 --- a/test/integration/filter_test.rb +++ b/test/integration/filter_test.rb @@ -84,16 +84,13 @@ class FiltersTest < Minitest::Test end def test_sort_natural - @context['value'] = 3 - @context['numbers'] = [2,1,4,3] @context['words'] = ['case', 'Assert', 'Insensitive'] # This specific syntax forces hashes to have string keys. Colons won't work. @context['hashes'] = [{ 'a' => 'A'}, { 'a' => 'b'}, { 'a' => 'C' }] @context['objects'] = [TestObject.new('A'), TestObject.new('b'), TestObject.new('C')] - assert_equal [1,2,3,4], Variable.new("numbers | sort_natural").render(@context) + # Test strings assert_equal ['Assert', 'case', 'Insensitive'], Variable.new("words | sort_natural").render(@context) - assert_equal [3], Variable.new("value | sort_natural").render(@context) # Test hashes sorted = Variable.new("hashes | sort_natural: 'a'").render(@context) @@ -164,7 +161,7 @@ class FiltersInTemplate < Minitest::Test end end # FiltersTest -# Simple object that may be passed into a filter. +# Simple object that gmay be passed into a filter. # Note to test subjects: do not smuggle test objects out of the testing area. class TestObject attr_accessor :a From 068791d698051b7da6b26e4a324826aed60c0fe3 Mon Sep 17 00:00:00 2001 From: Martin Hanzel Date: Tue, 5 May 2015 11:49:14 -0400 Subject: [PATCH 3/4] Added method parens --- lib/liquid/standardfilters.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/liquid/standardfilters.rb b/lib/liquid/standardfilters.rb index 745eb940..8da9ec35 100644 --- a/lib/liquid/standardfilters.rb +++ b/lib/liquid/standardfilters.rb @@ -128,11 +128,11 @@ module Liquid ary = InputIterator.new(input) if property.nil? - ary.sort {|a,b| a.casecmp b } + ary.sort {|a,b| a.casecmp(b) } elsif ary.first.respond_to?(:[]) && !ary.first[property].nil? - ary.sort {|a,b| a[property].casecmp b[property] } + ary.sort {|a,b| a[property].casecmp(b[property]) } elsif ary.first.respond_to?(property) - ary.sort {|a,b| a.send(property).casecmp b.send(property) } + ary.sort {|a,b| a.send(property).casecmp(b.send(property)) } end end From 32460c255bb2d2d006a27870a34afad3ed0b4690 Mon Sep 17 00:00:00 2001 From: Martin Hanzel Date: Fri, 8 May 2015 11:48:33 -0400 Subject: [PATCH 4/4] Removed a few superfluous comments --- test/integration/filter_test.rb | 3 --- 1 file changed, 3 deletions(-) diff --git a/test/integration/filter_test.rb b/test/integration/filter_test.rb index da2593a7..ba58e0f7 100644 --- a/test/integration/filter_test.rb +++ b/test/integration/filter_test.rb @@ -85,7 +85,6 @@ class FiltersTest < Minitest::Test def test_sort_natural @context['words'] = ['case', 'Assert', 'Insensitive'] - # This specific syntax forces hashes to have string keys. Colons won't work. @context['hashes'] = [{ 'a' => 'A'}, { 'a' => 'b'}, { 'a' => 'C' }] @context['objects'] = [TestObject.new('A'), TestObject.new('b'), TestObject.new('C')] @@ -161,8 +160,6 @@ class FiltersInTemplate < Minitest::Test end end # FiltersTest -# Simple object that gmay be passed into a filter. -# Note to test subjects: do not smuggle test objects out of the testing area. class TestObject attr_accessor :a def initialize(a)