diff --git a/lib/liquid/standardfilters.rb b/lib/liquid/standardfilters.rb index b5332f0eb..712e1727e 100644 --- a/lib/liquid/standardfilters.rb +++ b/lib/liquid/standardfilters.rb @@ -122,6 +122,27 @@ def sort(input, property = nil) 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 0a45075e7..23f64b76d 100644 --- a/test/integration/filter_test.rb +++ b/test/integration/filter_test.rb @@ -74,11 +74,38 @@ def test_sort @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 @@ def test_local_filter_with_deprecated_syntax 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