Skip to content

Commit

Permalink
Add reject filter #1573
Browse files Browse the repository at this point in the history
  • Loading branch information
andershagbard authored and karreiro committed Dec 3, 2024
1 parent 42b6763 commit 4b01977
Show file tree
Hide file tree
Showing 3 changed files with 250 additions and 0 deletions.
11 changes: 11 additions & 0 deletions History.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# Liquid Change Log

<<<<<<< HEAD
## 5.5.0 2024-03-21

Please reference the GitHub release for more information.
Expand All @@ -13,6 +14,7 @@ Please reference the GitHub release for more information.
* Allow `#` to be used as an inline comment tag (#1498) [CP Clermont]

### Fixes
<<<<<<< HEAD
* `PartialCache` now shares snippet cache with subcontexts by default (#1553) [Chris AtLee]
* Hash registers no longer leak into subcontexts as static registers (#1564) [Chris AtLee]
* Fix `ParseTreeVisitor` for `with` variable expressions in `Render` tag (#1596) [CP Clermont]
Expand All @@ -34,10 +36,19 @@ Please reference the GitHub release for more information.
### Features
* Add `remove_last`, and `replace_last` filters (#1422) [Anders Hagbard]
* Eagerly cache global filters (#1524) [Jean Boussier]
=======
## 5.2.0 (unreleased)

### Features
* Add `remove_last`, and `replace_last` filters (#1422) [Anders Hagbard]
>>>>>>> 588d407e (Fix new changelog entry so it is under unreleased)
### Fixes
* Fix some internal errors in filters from invalid input (#1476) [Dylan Thacker-Smith]
* Allow dash in filter kwarg name for consistency with Liquid::C (#1518) [CP Clermont]
=======
* Fix some internal errors in filters from invalid input (#1476) [Dylan Thacker-Smith]
>>>>>>> 50c88fe7 (History.md: Add missing PR number to previous changelog entry)
## 5.1.0 / 2021-09-09

Expand Down
72 changes: 72 additions & 0 deletions lib/liquid/standardfilters.rb
Original file line number Diff line number Diff line change
Expand Up @@ -449,13 +449,41 @@ def where(input, property, target_value = nil)
end
end

<<<<<<< HEAD
# @liquid_public_docs
# @liquid_type filter
# @liquid_category array
# @liquid_summary
# Removes any duplicate items in an array.
# @liquid_syntax array | uniq
# @liquid_return [array[untyped]]
=======
# Reject the elements of an array to those with a certain property value.
# By default the target is any falsy value.
def reject(input, properties, target_value = nil)
raise_property_error(properties) unless properties.is_a?(String)

properties = properties.to_s.split('.')
ary = InputIterator.new(input, context)

ary.reject do |item|
if item.is_a?(Hash)
value = item.dig(*properties)

if target_value.nil?
!value
else
value == target_value
end
else
true
end
end
end

# Remove duplicate elements from an array
# provide optional property with which to determine uniqueness
>>>>>>> bfdcfcea (Add reject filter)
def uniq(input, property = nil)
ary = InputIterator.new(input, context)

Expand Down Expand Up @@ -557,6 +585,7 @@ def replace_first(input, string, replacement = '')
input.to_s.sub(string.to_s, replacement.to_s)
end

<<<<<<< HEAD
# @liquid_public_docs
# @liquid_type filter
# @liquid_category string
Expand Down Expand Up @@ -585,8 +614,34 @@ def replace_last(input, string, replacement)
# Removes any instance of a substring inside a string.
# @liquid_syntax string | remove: string
# @liquid_return [string]
=======
# Replace the last occurrences of a string with another
def replace_last(input, string, replacement)
input = input.to_s
string = string.to_s
replacement = replacement.to_s

start_index = input.rindex(string)

return input unless start_index

output = input.dup
output[start_index, string.length] = replacement
output
end

# remove a substring
>>>>>>> e575c1f1 (Add replace_last and remove_last filters)
def remove(input, string)
<<<<<<< HEAD
<<<<<<< HEAD
replace(input, string, '')
=======
replace(input.to_s, string, '')
>>>>>>> f72cfb13 (Delegate functions to corresponding replace functions)
=======
replace(input, string, '')
>>>>>>> 5187399f (Update lib/liquid/standardfilters.rb)
end

# @liquid_public_docs
Expand All @@ -597,9 +652,18 @@ def remove(input, string)
# @liquid_syntax string | remove_first: string
# @liquid_return [string]
def remove_first(input, string)
<<<<<<< HEAD
<<<<<<< HEAD
replace_first(input, string, '')
=======
replace_first(input.to_s, string, '')
>>>>>>> f72cfb13 (Delegate functions to corresponding replace functions)
=======
replace_first(input, string, '')
>>>>>>> 45f186b4 (Remove string formatter)
end

<<<<<<< HEAD
# @liquid_public_docs
# @liquid_type filter
# @liquid_category string
Expand All @@ -618,6 +682,14 @@ def remove_last(input, string)
# Adds a given string to the end of a string.
# @liquid_syntax string | append: string
# @liquid_return [string]
=======
# remove the last occurences of a substring
def remove_last(input, string)
replace_last(input, string, '')
end

# add one string to another
>>>>>>> e575c1f1 (Add replace_last and remove_last filters)
def append(input, string)
input.to_s + string.to_s
end
Expand Down
167 changes: 167 additions & 0 deletions test/integration/standard_filter_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -603,29 +603,101 @@ def test_first_last
def test_replace
assert_equal('b b b b', @filters.replace('a a a a', 'a', 'b'))
assert_equal('2 2 2 2', @filters.replace('1 1 1 1', 1, 2))
<<<<<<< HEAD
<<<<<<< HEAD
<<<<<<< HEAD
assert_equal('1 1 1 1', @filters.replace('1 1 1 1', 2, 3))
assert_template_result('2 2 2 2', "{{ '1 1 1 1' | replace: '1', 2 }}")

<<<<<<< HEAD
assert_equal('b a a a', @filters.replace_first('a a a a', 'a', 'b'))
=======
>>>>>>> 38b7364f (Improve tests)
assert_equal('2 1 1 1', @filters.replace_first('1 1 1 1', 1, 2))
assert_equal('1 1 1 1', @filters.replace_first('1 1 1 1', 2, 3))
assert_template_result('2 1 1 1', "{{ '1 1 1 1' | replace_first: '1', 2 }}")
<<<<<<< HEAD
<<<<<<< HEAD

assert_equal('a a a b', @filters.replace_last('a a a a', 'a', 'b'))
assert_equal('1 1 1 2', @filters.replace_last('1 1 1 1', 1, 2))
assert_equal('1 1 1 1', @filters.replace_last('1 1 1 1', 2, 3))
=======
assert_equal('1 1 1 2', @filters.replace_last('1 1 1 1', '1', 2))
=======
>>>>>>> 38b7364f (Improve tests)
assert_equal('1 1 1 2', @filters.replace_last('1 1 1 1', 1, 2))
>>>>>>> 832516fb (Add replace_last and remove_last tests)
assert_template_result('1 1 1 2', "{{ '1 1 1 1' | replace_last: '1', 2 }}")
=======
=======
assert_equal('1 1 1 1', @filters.replace('1 1 1 1', 2, 3))
>>>>>>> d81f7f04 (Add tests to make sure it returns original string on no replacement)
=======
>>>>>>> 63ae4cc2 (Remove redundant test assertions)
assert_equal('b a a a', @filters.replace_first('a a a a', 'a', 'b'))
assert_equal('2 1 1 1', @filters.replace_first('1 1 1 1', 1, 2))
assert_equal('1 1 1 1', @filters.replace_first('1 1 1 1', 2, 3))
assert_template_result('2 1 1 1', "{{ '1 1 1 1' | replace_first: '1', 2 }}")

assert_equal('a a a b', @filters.replace_last('a a a a', 'a', 'b'))
assert_equal('1 1 1 2', @filters.replace_last('1 1 1 1', 1, 2))
assert_equal('1 1 1 1', @filters.replace_last('1 1 1 1', 2, 3))
<<<<<<< HEAD
assert_template_result('b b b b', "{{ 'a a a a' | replace: 'a', 'b' }}")
assert_template_result('2 2 2 2', "{{ '1 1 1 1' | replace: 1, 2 }}")
assert_template_result('1 1 1 1', "{{ '1 1 1 1' | replace: 2, 3 }}")
assert_template_result('b a a a', "{{ 'a a a a' | replace_first: 'a', 'b' }}")
assert_template_result('2 1 1 1', "{{ '1 1 1 1' | replace_first: 1, 2 }}")
assert_template_result('a a a a', "{{ 'a a a a' | replace_first: 'b', 'c' }}")
assert_template_result('a a a b', "{{ 'a a a a' | replace_last: 'a', 'b' }}")
assert_template_result('1 1 1 2', "{{ '1 1 1 1' | replace_last: 1, 2 }}")
<<<<<<< HEAD
>>>>>>> b3d14e5b (Update tests)
=======
assert_template_result('a a a a', "{{ 'a a a a' | replace_last: 'b', 'c' }}")
>>>>>>> d81f7f04 (Add tests to make sure it returns original string on no replacement)
=======
assert_template_result('1 1 1 2', "{{ '1 1 1 1' | replace_last: '1', 2 }}")
>>>>>>> 63ae4cc2 (Remove redundant test assertions)
end

def test_remove
assert_equal(' ', @filters.remove("a a a a", 'a'))
<<<<<<< HEAD
<<<<<<< HEAD
assert_template_result(' ', "{{ '1 1 1 1' | remove: 1 }}")

assert_equal('b a a', @filters.remove_first("a b a a", 'a '))
assert_template_result(' 1 1 1', "{{ '1 1 1 1' | remove_first: 1 }}")

assert_equal('a a b', @filters.remove_last("a a b a", ' a'))
assert_template_result('1 1 1 ', "{{ '1 1 1 1' | remove_last: 1 }}")
=======
assert_equal(' ', @filters.remove("1 1 1 1", 1))
assert_equal('b a a', @filters.remove_first("a b a a", 'a '))
assert_equal(' 1 1 1', @filters.remove_first("1 1 1 1", 1))
assert_equal('a a b', @filters.remove_last("a a b a", ' a'))
assert_equal('1 1 1 ', @filters.remove_last("1 1 1 1", 1))
<<<<<<< HEAD
<<<<<<< HEAD
assert_template_result('a a a', "{{ 'a a a a' | remove_last: ' a' }}")
>>>>>>> 832516fb (Add replace_last and remove_last tests)
=======
assert_template_result('a a b', "{{ 'a a b a' | remove_last: ' a' }}")
>>>>>>> effee1ed (Update tests)
=======
assert_template_result(' ', "{{ 'a a a a' | remove: 'a' }}")
=======
>>>>>>> 63ae4cc2 (Remove redundant test assertions)
assert_template_result(' ', "{{ '1 1 1 1' | remove: 1 }}")

assert_equal('b a a', @filters.remove_first("a b a a", 'a '))
assert_template_result(' 1 1 1', "{{ '1 1 1 1' | remove_first: 1 }}")

assert_equal('a a b', @filters.remove_last("a a b a", ' a'))
assert_template_result('1 1 1 ', "{{ '1 1 1 1' | remove_last: 1 }}")
>>>>>>> b3d14e5b (Update tests)
end

def test_pipes_in_string_arguments
Expand Down Expand Up @@ -900,6 +972,81 @@ def test_where_array_of_only_unindexable_values
assert_nil(@filters.where([nil], "ok"))
end

def test_reject
input = [
{ "handle" => "alpha", "ok" => true },
{ "handle" => "beta", "ok" => false },
{ "handle" => "gamma", "ok" => false },
{ "handle" => "delta", "ok" => true },
]

expectation = [
{ "handle" => "alpha", "ok" => true },
{ "handle" => "delta", "ok" => true },
]

assert_equal(expectation, @filters.reject(input, "ok", false))
assert_equal(expectation, @filters.reject(input, "ok"))
end

def test_reject_no_key_set
input = [
{ "handle" => "alpha", "ok" => true },
{ "handle" => "beta" },
{ "handle" => "gamma" },
{ "handle" => "delta", "ok" => true },
]

expectation = [
{ "handle" => "alpha", "ok" => true },
{ "handle" => "delta", "ok" => true },
]

assert_equal(expectation, @filters.reject(input, "ok"))
end

def test_reject_non_array_map_input
assert_equal([], @filters.reject({ "foo" => "bar" }, "foo", "bar"))
assert_equal([{ "foo" => "baz" }], @filters.reject({ "foo" => "baz" }, "foo", "bar"))
end

def test_reject_indexable_but_non_map_value
assert_equal([], @filters.reject(1, "ok", true))
assert_equal([], @filters.reject(1, "ok"))
end

def test_reject_non_boolean_value
input = [
{ "message" => "Bonjour!", "language" => "French" },
{ "message" => "Hello!", "language" => "English" },
]

assert_equal([{ "message" => "Hello!", "language" => "English" }], @filters.reject(input, "language", "French"))
assert_equal([{ "message" => "Bonjour!", "language" => "French" }], @filters.reject(input, "language", "English"))
end

def test_reject_array_of_only_unindexable_values
assert_equal([], @filters.reject([nil, nil], "ok", true))
assert_equal([], @filters.reject([nil, nil], "ok"))
end

def test_reject_deep
input = [
{ "item" => { "handle" => "alpha", "ok" => true } },
{ "item" => { "handle" => "beta", "ok" => false } },
{ "item" => { "handle" => "gamma", "ok" => false } },
{ "item" => { "handle" => "delta", "ok" => true } },
]

expectation = [
{ "item" => { "handle" => "alpha", "ok" => true } },
{ "item" => { "handle" => "delta", "ok" => true } },
]

assert_equal(expectation, @filters.reject(input, "item.ok", false))
assert_equal(expectation, @filters.reject(input, "item.ok"))
end

def test_all_filters_never_raise_non_liquid_exception
test_drop = TestDrop.new(value: "test")
test_drop.context = Context.new
Expand All @@ -926,6 +1073,7 @@ def test_all_filters_never_raise_non_liquid_exception
{ 1 => "bar" },
["foo", 123, nil, true, false, Drop, ["foo"], { foo: "bar" }],
]
<<<<<<< HEAD
StandardFilters.public_instance_methods(false).each do |method|
arg_count = @filters.method(method).arity
arg_count *= -1 if arg_count < 0
Expand All @@ -934,6 +1082,25 @@ def test_all_filters_never_raise_non_liquid_exception
@filters.send(method, *args)
rescue Liquid::Error
nil
=======
test_types.each do |first|
test_types.each do |second|
test_types.each do |third|
(@filters.methods - Object.methods).each do |method|
arg_count = @filters.method(method).arity
arg_count *= -1 if arg_count < 0
inputs = [first]
inputs << ([second] * (arg_count - 1)) if arg_count > 1
inputs << ([third] * (arg_count - 1)) if arg_count > 2

begin
@filters.send(method, *inputs)
rescue Liquid::ArgumentError, Liquid::ZeroDivisionError
nil
end
end
end
>>>>>>> f5e77b6d (Update test to support third argument)
end
end
end
Expand Down

0 comments on commit 4b01977

Please sign in to comment.