Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add deep search for filter taking in properties #1749

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
88 changes: 69 additions & 19 deletions lib/liquid/standardfilters.rb
Original file line number Diff line number Diff line change
Expand Up @@ -367,7 +367,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?
Expand All @@ -378,7 +381,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]
nil_safe_compare(a.dig(*deep[:properties]), b.dig(*deep[:properties]))
else
nil_safe_compare(a[property], b[property])
end
end
rescue TypeError
raise_property_error(property)
end
Expand All @@ -396,7 +405,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?
Expand All @@ -407,7 +419,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]
nil_safe_casecmp(a.dig(*deep[:properties]), b.dig(*deep[:properties]))
else
nil_safe_casecmp(a[property], b[property])
end
end
rescue TypeError
raise_property_error(property)
end
Expand All @@ -423,7 +441,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?
Expand All @@ -439,7 +460,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
Expand All @@ -456,7 +478,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?
Expand All @@ -465,7 +490,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
Expand Down Expand Up @@ -494,15 +519,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
Expand All @@ -516,7 +545,10 @@ def map(input, property)
# Removes any `nil` items from an array.
# @liquid_syntax array | compact
# @liquid_return [array[untyped]]
def compact(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 compact(input, property = nil, options = {})
options = {} unless options.is_a?(Hash)
deep = deep_search_properties(property, options)
ary = InputIterator.new(input, context)

if property.nil?
Expand All @@ -525,7 +557,7 @@ def compact(input, property = nil)
[]
else
ary.reject do |item|
item[property].nil?
deep[:enable] ? item.dig(*deep[:properties]).nil? : item[property].nil?
rescue TypeError
raise_property_error(property)
rescue NoMethodError
Expand Down Expand Up @@ -891,15 +923,19 @@ 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?

values_for_sum = ary.map do |item|
if property.nil?
item
elsif item.respond_to?(:[])
item[property]
deep[:enable] ? item.dig(*deep[:properties]) : item[property]
else
0
end
Expand Down Expand Up @@ -949,6 +985,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'].is_a?(String) ? options['deep'] : '.' if enable
properties = key.to_s.split(separator) if enable

{
enable: enable,
separator: separator,
properties: properties,
}
end

class InputIterator
include Enumerable

Expand Down
Loading