diff --git a/lib/liquid/standardfilters.rb b/lib/liquid/standardfilters.rb index 661235a9d..461d6bd1e 100644 --- a/lib/liquid/standardfilters.rb +++ b/lib/liquid/standardfilters.rb @@ -352,7 +352,10 @@ 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 deep [boolean | string] Whether to use dot notation to perform a deep search. A string can be passed to change separator. + def sort(input, property = nil, options = {}) + options = {} unless options.is_a?(Hash) + deep = deep_search_properties(property, options) ary = InputIterator.new(input, context) return [] if ary.empty? @@ -363,7 +366,13 @@ def sort(input, property = nil) end elsif ary.all? { |el| el.respond_to?(:[]) } begin - ary.sort { |a, b| nil_safe_compare(a[property], b[property]) } + ary.sort do |a, b| + if deep[:enable] + return nil_safe_compare(a.dig(*deep[:properties]), b.dig(*deep[:properties])) + end + + nil_safe_compare(a[property], b[property]) + end rescue TypeError raise_property_error(property) end @@ -381,7 +390,10 @@ 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 deep [boolean | string] Whether to use dot notation to perform a deep search. A string can be passed to change separator. + def sort_natural(input, property = nil, options = {}) + options = {} unless options.is_a?(Hash) + deep = deep_search_properties(property, options) ary = InputIterator.new(input, context) return [] if ary.empty? @@ -392,7 +404,13 @@ def sort_natural(input, property = nil) end elsif ary.all? { |el| el.respond_to?(:[]) } begin - ary.sort { |a, b| nil_safe_casecmp(a[property], b[property]) } + ary.sort do |a, b| + if deep[:enable] + return nil_safe_casecmp(a.dig(*deep[:properties]), b.dig(*deep[:properties])) + end + + nil_safe_casecmp(a[property], b[property]) + end rescue TypeError raise_property_error(property) end @@ -408,7 +426,10 @@ def sort_natural(input, property = nil) # This requires you to provide both the property name and the associated value. # @liquid_syntax array | where: string, string # @liquid_return [array[untyped]] - def where(input, property, target_value = nil) + # @liquid_optional_param deep [boolean | string] Whether to use dot notation to perform a deep search. A string can be passed to change separator. + def where(input, property, target_value = nil, options = {}) + options = {} unless options.is_a?(Hash) + deep = deep_search_properties(property, options) ary = InputIterator.new(input, context) if ary.empty? @@ -424,7 +445,8 @@ def where(input, property, target_value = nil) end else ary.select do |item| - item[property] == target_value + item_value = deep[:enable] ? item.dig(*deep[:properties]) : item[property] + item_value == target_value rescue TypeError raise_property_error(property) rescue NoMethodError @@ -441,7 +463,10 @@ def where(input, property, target_value = nil) # Removes any duplicate items in an array. # @liquid_syntax array | uniq # @liquid_return [array[untyped]] - def uniq(input, property = nil) + # @liquid_optional_param deep [boolean | string] Whether to use dot notation to perform a deep search. A string can be passed to change separator. + def uniq(input, property = nil, options = {}) + options = {} unless options.is_a?(Hash) + deep = deep_search_properties(property, options) ary = InputIterator.new(input, context) if property.nil? @@ -450,7 +475,7 @@ def uniq(input, property = nil) [] else ary.uniq do |item| - item[property] + deep[:enable] ? item.dig(*deep[:properties]) : item[property] rescue TypeError raise_property_error(property) rescue NoMethodError @@ -479,15 +504,19 @@ def reverse(input) # Creates an array of values from a specific property of the items in an array. # @liquid_syntax array | map: string # @liquid_return [array[untyped]] - def map(input, property) - InputIterator.new(input, context).map do |e| - e = e.call if e.is_a?(Proc) + # @liquid_optional_param deep [boolean | string] Whether to use dot notation to perform a deep search. A string can be passed to change separator. + def map(input, property, options = {}) + options = {} unless options.is_a?(Hash) + deep = deep_search_properties(property, options) + + InputIterator.new(input, context).map do |item| + item = item.call if item.is_a?(Proc) if property == "to_liquid" - e - elsif e.respond_to?(:[]) - r = e[property] - r.is_a?(Proc) ? r.call : r + item + elsif item.respond_to?(:[]) + result = deep[:enable] ? item.dig(*deep[:properties]) : item[property] + result.is_a?(Proc) ? result.call : result end end rescue TypeError @@ -876,7 +905,11 @@ def default(input, default_value = '', options = {}) # Returns the sum of all elements in an array. # @liquid_syntax array | sum # @liquid_return [number] - def sum(input, property = nil) + # @liquid_optional_param deep [boolean | string] Whether to use dot notation to perform a deep search. A string can be passed to change separator. + def sum(input, property = nil, options = {}) + options = {} unless options.is_a?(Hash) + deep = deep_search_properties(property, options) + ary = InputIterator.new(input, context) return 0 if ary.empty? @@ -884,7 +917,7 @@ def sum(input, property = nil) if property.nil? item elsif item.respond_to?(:[]) - item[property] + deep[:enable] ? item.dig(*deep[:properties]) : item[property] else 0 end @@ -932,6 +965,20 @@ def nil_safe_casecmp(a, b) end end + def deep_search_properties(key, options = {}) + options = {} unless options.is_a?(Hash) + + enable = options['deep'] ? true : false + separator = options['deep'].kind_of?(String) ? options['deep'] : '.' if enable + properties = key.to_s.split(separator) if enable + + { + :enable => enable, + :separator => separator, + :properties => properties + } + end + class InputIterator include Enumerable