From 1949a57a54b9dbf345ce4541b85441f49a9790a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20S=C3=B8gaard?= <9662430+andershagbard@users.noreply.github.com> Date: Sat, 11 Oct 2025 12:49:05 +0200 Subject: [PATCH] Add order option to sort and sort_natural filters The sort and sort_natural filters now accept an 'order' option to specify ascending or descending sort order. Tests were added to verify correct behavior and error handling for invalid order values. --- lib/liquid/standardfilters.rb | 34 +++++++++++++++++------- test/integration/standard_filter_test.rb | 18 +++++++++++++ 2 files changed, 42 insertions(+), 10 deletions(-) diff --git a/lib/liquid/standardfilters.rb b/lib/liquid/standardfilters.rb index 6e072fcf5..c60c7815e 100644 --- a/lib/liquid/standardfilters.rb +++ b/lib/liquid/standardfilters.rb @@ -375,18 +375,19 @@ def join(input, glue = ' ') # Sorts the items in an array in case-sensitive alphabetical, or numerical, order. # @liquid_syntax array | sort # @liquid_return [array[untyped]] - def sort(input, property = nil) + # @liquid_optional_param order: [string] Which direction to sort the array in. Default is "asc". Valid values are "asc" and "desc". + def sort(input, property = nil, options = {}) ary = InputIterator.new(input, context) return [] if ary.empty? if property.nil? ary.sort do |a, b| - nil_safe_compare(a, b) + nil_safe_compare(a, b, options['order']) end elsif ary.all? { |el| el.respond_to?(:[]) } begin - ary.sort { |a, b| nil_safe_compare(a[property], b[property]) } + ary.sort { |a, b| nil_safe_compare(a[property], b[property], options['order']) } rescue TypeError raise_property_error(property) end @@ -404,18 +405,19 @@ def sort(input, property = nil) # > string, so sorting on numerical values can lead to unexpected results. # @liquid_syntax array | sort_natural # @liquid_return [array[untyped]] - def sort_natural(input, property = nil) + # @liquid_optional_param order: [string] Which direction to sort the array in. Default is "asc". Valid values are "asc" and "desc". + def sort_natural(input, property = nil, options = {}) ary = InputIterator.new(input, context) return [] if ary.empty? if property.nil? ary.sort do |a, b| - nil_safe_casecmp(a, b) + nil_safe_casecmp(a, b, options['order']) end elsif ary.all? { |el| el.respond_to?(:[]) } begin - ary.sort { |a, b| nil_safe_casecmp(a[property], b[property]) } + ary.sort { |a, b| nil_safe_casecmp(a[property], b[property], options['order']) } rescue TypeError raise_property_error(property) end @@ -1005,11 +1007,17 @@ def apply_operation(input, operand, operation) result.is_a?(BigDecimal) ? result.to_f : result end - def nil_safe_compare(a, b) + def nil_safe_compare(a, b, order) + order = order ? Utils.to_s(order).downcase : 'asc' + + if order != 'asc' && order != 'desc' + raise Liquid::ArgumentError, "invalid order provided to sort_natural. '#{order}' was provided" + end + result = a <=> b if result - result + result * (order == 'desc' ? -1 : 1) elsif a.nil? 1 elsif b.nil? @@ -1019,9 +1027,15 @@ def nil_safe_compare(a, b) end end - def nil_safe_casecmp(a, b) + def nil_safe_casecmp(a, b, order) + order = order ? Utils.to_s(order).downcase : 'asc' + + if order != 'asc' && order != 'desc' + raise Liquid::ArgumentError, "invalid order provided to sort_natural. '#{order}' was provided" + end + if !a.nil? && !b.nil? - a.to_s.casecmp(b.to_s) + a.to_s.casecmp(b.to_s) * (order == 'desc' ? -1 : 1) elsif a.nil? && b.nil? 0 else diff --git a/test/integration/standard_filter_test.rb b/test/integration/standard_filter_test.rb index e1370fae1..16cf46a27 100644 --- a/test/integration/standard_filter_test.rb +++ b/test/integration/standard_filter_test.rb @@ -313,6 +313,15 @@ def test_sort_with_nils assert_equal([{ "a" => 1 }, { "a" => 2 }, { "a" => 3 }, { "a" => 4 }, {}], @filters.sort([{ "a" => 4 }, { "a" => 3 }, {}, { "a" => 1 }, { "a" => 2 }], "a")) end + def test_sort_with_order + assert_equal([4, 3, 2, 1], @filters.sort([4, 3, 2, 1], nil, "order" => "desc")) + assert_equal([{ "a" => 4 }, { "a" => 3 }, { "a" => 2 }, { "a" => 1 }], @filters.sort([{ "a" => 4 }, { "a" => 3 }, { "a" => 2 }, { "a" => 1 }], "a", "order" => "desc")) + + assert_raises(Liquid::ArgumentError) do + @filters.sort([1, 2], nil, "order" => "invalid") + end + end + def test_sort_when_property_is_sometimes_missing_puts_nils_last input = [ { "price" => 4, "handle" => "alpha" }, @@ -341,6 +350,15 @@ def test_sort_natural_with_nils assert_equal([{ "a" => "a" }, { "a" => "B" }, { "a" => "c" }, { "a" => "D" }, {}], @filters.sort_natural([{ "a" => "D" }, { "a" => "c" }, {}, { "a" => "a" }, { "a" => "B" }], "a")) end + def test_sort_natural_with_order + assert_equal(["D", "c", "B", "a"], @filters.sort_natural(["c", "D", "a", "B"], nil, "order" => "desc")) + assert_equal([{ "a" => "D" }, { "a" => "c" }, { "a" => "B" }, { "a" => "a" }], @filters.sort_natural([{ "a" => "D" }, { "a" => "c" }, { "a" => "a" }, { "a" => "B" }], "a", "order" => "desc")) + + assert_raises(Liquid::ArgumentError) do + @filters.sort_natural(["a", "b"], nil, "order" => "invalid") + end + end + def test_sort_natural_when_property_is_sometimes_missing_puts_nils_last input = [ { "price" => "4", "handle" => "alpha" },