mirror of
https://github.com/Shopify/liquid.git
synced 2026-09-15 00:40:40 -07:00
Compare commits
47
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9eeaf89665 | ||
|
|
a77c37db0a | ||
|
|
7357dcf185 | ||
|
|
68c3827ef2 | ||
|
|
4af38bc549 | ||
|
|
df241abf70 | ||
|
|
10f8337209 | ||
|
|
5ed0410a8b | ||
|
|
0f5220c391 | ||
|
|
7a23f46fab | ||
|
|
3f7edf00b9 | ||
|
|
b4a2a79e26 | ||
|
|
1d2bee1f60 | ||
|
|
01e6eec97a | ||
|
|
fbdab19358 | ||
|
|
ce85ac5d3d | ||
|
|
c0ffee16a3 | ||
|
|
a7eb33fa39 | ||
|
|
1a85e98793 | ||
|
|
c588337aac | ||
|
|
97f7922457 | ||
|
|
0d83e64cfe | ||
|
|
0d5e01ae98 | ||
|
|
15eaa49e48 | ||
|
|
91c54c579d | ||
|
|
1310c4978d | ||
|
|
3de1db3c3a | ||
|
|
03522caaf8 | ||
|
|
7acea2a9c9 | ||
|
|
d8ef698539 | ||
|
|
ebdfdb80e5 | ||
|
|
95e9fa5010 | ||
|
|
db3999a008 | ||
|
|
10e2aa8d5b | ||
|
|
7c4114671b | ||
|
|
b01de9d325 | ||
|
|
a5369c26a8 | ||
|
|
a03de8f9ea | ||
|
|
e86fe27259 | ||
|
|
c8906d05b9 | ||
|
|
50d1a2ffc9 | ||
|
|
aa8ce87b96 | ||
|
|
698f5e0d96 | ||
|
|
996bfe0c82 | ||
|
|
be81c9ae5a | ||
|
|
edd4d70aee | ||
|
|
ac66dbbafe |
@@ -7,7 +7,7 @@ jobs:
|
||||
matrix:
|
||||
entry:
|
||||
- { ruby: 2.5, allowed-failure: false } # minimum supported
|
||||
- { ruby: 3.0, allowed-failure: false } # latest
|
||||
- { ruby: 3.1, allowed-failure: false } # latest
|
||||
- { ruby: ruby-head, allowed-failure: true }
|
||||
name: test (${{ matrix.entry.ruby }})
|
||||
steps:
|
||||
|
||||
+22
-1
@@ -1,12 +1,33 @@
|
||||
# Liquid Change Log
|
||||
|
||||
## 5.0.2 (unreleased)
|
||||
## 5.3.0 (unreleased)
|
||||
|
||||
### Fixes
|
||||
* StandardFilter: Fix missing @context on iterations (#1525) [Thierry Joyal]
|
||||
|
||||
### Deprecation
|
||||
* Condition#evaluate to require mandatory context argument in Liquid 6.0.0 (#1527) [Thierry Joyal]
|
||||
|
||||
## 5.2.0 2022-03-01
|
||||
|
||||
### Features
|
||||
* Add `remove_last`, and `replace_last` filters (#1422) [Anders Hagbard]
|
||||
* Eagerly cache global filters (#1524) [Jean Boussier]
|
||||
|
||||
### 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]
|
||||
|
||||
## 5.1.0 / 2021-09-09
|
||||
|
||||
### Features
|
||||
* Add `base64_encode`, `base64_decode`, `base64_url_safe_encode`, and `base64_url_safe_decode` filters (#1450) [Daniel Insley]
|
||||
* Introduce `to_liquid_value` in `Liquid::Drop` (#1441) [Michael Go]
|
||||
|
||||
### Fixes
|
||||
* Fix support for using a String subclass for the liquid source (#1421) [Dylan Thacker-Smith]
|
||||
* Add `ParseTreeVisitor` to `RangeLookup` (#1470) [CP Clermont]
|
||||
* Translate `RangeError` to `Liquid::Error` for `truncatewords` with large int (#1431) [Dylan Thacker-Smith]
|
||||
|
||||
## 5.0.1 / 2021-03-24
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
* [Contributing guidelines](CONTRIBUTING.md)
|
||||
* [Version history](History.md)
|
||||
* [Liquid documentation from Shopify](http://docs.shopify.com/themes/liquid-basics)
|
||||
* [Liquid documentation from Shopify](https://shopify.dev/api/liquid)
|
||||
* [Liquid Wiki at GitHub](https://github.com/Shopify/liquid/wiki)
|
||||
* [Website](http://liquidmarkup.org/)
|
||||
|
||||
@@ -56,7 +56,7 @@ For standard use you can just pass it the content of a file and call render with
|
||||
|
||||
Setting the error mode of Liquid lets you specify how strictly you want your templates to be interpreted.
|
||||
Normally the parser is very lax and will accept almost anything without error. Unfortunately this can make
|
||||
it very hard to debug and can lead to unexpected behaviour.
|
||||
it very hard to debug and can lead to unexpected behaviour.
|
||||
|
||||
Liquid also comes with a stricter parser that can be used when editing templates to give better error messages
|
||||
when templates are invalid. You can enable this new parser like this:
|
||||
|
||||
+2
-2
@@ -36,7 +36,7 @@ module Liquid
|
||||
VariableIncompleteEnd = /\}\}?/
|
||||
QuotedString = /"[^"]*"|'[^']*'/
|
||||
QuotedFragment = /#{QuotedString}|(?:[^\s,\|'"]|#{QuotedString})+/o
|
||||
TagAttributes = /(\w+)\s*\:\s*(#{QuotedFragment})/o
|
||||
TagAttributes = /(\w[\w-]*)\s*\:\s*(#{QuotedFragment})/o
|
||||
AnyStartingTag = /#{TagStart}|#{VariableStart}/o
|
||||
PartialTemplateParser = /#{TagStart}.*?#{TagEnd}|#{VariableStart}.*?#{VariableIncompleteEnd}/om
|
||||
TemplateParser = /(#{PartialTemplateParser}|#{AnyStartingTag})/om
|
||||
@@ -59,8 +59,8 @@ require 'liquid/forloop_drop'
|
||||
require 'liquid/extensions'
|
||||
require 'liquid/errors'
|
||||
require 'liquid/interrupts'
|
||||
require 'liquid/strainer_factory'
|
||||
require 'liquid/strainer_template'
|
||||
require 'liquid/strainer_factory'
|
||||
require 'liquid/expression'
|
||||
require 'liquid/context'
|
||||
require 'liquid/parser_switching'
|
||||
|
||||
@@ -35,6 +35,18 @@ module Liquid
|
||||
super
|
||||
end
|
||||
|
||||
# @public_docs
|
||||
# @type tag
|
||||
# @category theme
|
||||
# @title liquid
|
||||
# @summary
|
||||
# Allows you to write multiple tags within one set of delimiters.
|
||||
# @description
|
||||
# Use the [`echo`](/api/liquid/tags/theme-tags#echo) tag to output an expression within a `liquid` tag.
|
||||
# @syntax
|
||||
# {% liquid
|
||||
# statement
|
||||
# %}
|
||||
private def parse_for_liquid_tag(tokenizer, parse_context)
|
||||
while (token = tokenizer.shift)
|
||||
unless token.empty? || token =~ WhitespaceOrNothing
|
||||
@@ -231,8 +243,8 @@ module Liquid
|
||||
end
|
||||
|
||||
def create_variable(token, parse_context)
|
||||
token.scan(ContentOfVariable) do |content|
|
||||
markup = content.first
|
||||
if token =~ ContentOfVariable
|
||||
markup = Regexp.last_match(1)
|
||||
return Variable.new(markup, parse_context)
|
||||
end
|
||||
BlockBody.raise_missing_variable_terminator(token, parse_context)
|
||||
|
||||
@@ -8,7 +8,7 @@ module Liquid
|
||||
# c = Condition.new(1, '==', 1)
|
||||
# c.evaluate #=> true
|
||||
#
|
||||
class Condition #:nodoc:
|
||||
class Condition # :nodoc:
|
||||
@@operators = {
|
||||
'==' => ->(cond, left, right) { cond.send(:equal_variables, left, right) },
|
||||
'!=' => ->(cond, left, right) { !cond.send(:equal_variables, left, right) },
|
||||
@@ -61,7 +61,7 @@ module Liquid
|
||||
@child_condition = nil
|
||||
end
|
||||
|
||||
def evaluate(context = Context.new)
|
||||
def evaluate(context = deprecated_default_context)
|
||||
condition = self
|
||||
result = nil
|
||||
loop do
|
||||
@@ -150,6 +150,12 @@ module Liquid
|
||||
end
|
||||
end
|
||||
|
||||
def deprecated_default_context
|
||||
warn("DEPRECATION WARNING: Condition#evaluate without a context argument is deprecated" \
|
||||
" and will be removed from Liquid 6.0.0.")
|
||||
Context.new
|
||||
end
|
||||
|
||||
class ParseTreeVisitor < Liquid::ParseTreeVisitor
|
||||
def children
|
||||
[
|
||||
|
||||
@@ -124,7 +124,7 @@ module Liquid
|
||||
# context['var'] = 'hi'
|
||||
# end
|
||||
#
|
||||
# context['var] #=> nil
|
||||
# context['var'] #=> nil
|
||||
def stack(new_scope = {})
|
||||
push(new_scope)
|
||||
yield
|
||||
|
||||
+11
-10
@@ -10,21 +10,23 @@ module Liquid
|
||||
'empty' => ''
|
||||
}.freeze
|
||||
|
||||
SINGLE_QUOTED_STRING = /\A\s*'(.*)'\s*\z/m
|
||||
DOUBLE_QUOTED_STRING = /\A\s*"(.*)"\s*\z/m
|
||||
INTEGERS_REGEX = /\A\s*(-?\d+)\s*\z/
|
||||
FLOATS_REGEX = /\A\s*(-?\d[\d\.]+)\s*\z/
|
||||
INTEGERS_REGEX = /\A(-?\d+)\z/
|
||||
FLOATS_REGEX = /\A(-?\d[\d\.]+)\z/
|
||||
|
||||
# Use an atomic group (?>...) to avoid pathological backtracing from
|
||||
# malicious input as described in https://github.com/Shopify/liquid/issues/1357
|
||||
RANGES_REGEX = /\A\s*\(\s*(?>(\S+)\s*\.\.)\s*(\S+)\s*\)\s*\z/
|
||||
RANGES_REGEX = /\A\(\s*(?>(\S+)\s*\.\.)\s*(\S+)\s*\)\z/
|
||||
|
||||
def self.parse(markup)
|
||||
return nil unless markup
|
||||
|
||||
markup = markup.strip
|
||||
if (markup.start_with?('"') && markup.end_with?('"')) ||
|
||||
(markup.start_with?("'") && markup.end_with?("'"))
|
||||
return markup[1..-2]
|
||||
end
|
||||
|
||||
case markup
|
||||
when nil
|
||||
nil
|
||||
when SINGLE_QUOTED_STRING, DOUBLE_QUOTED_STRING
|
||||
Regexp.last_match(1)
|
||||
when INTEGERS_REGEX
|
||||
Regexp.last_match(1).to_i
|
||||
when RANGES_REGEX
|
||||
@@ -32,7 +34,6 @@ module Liquid
|
||||
when FLOATS_REGEX
|
||||
Regexp.last_match(1).to_f
|
||||
else
|
||||
markup = markup.strip
|
||||
if LITERALS.key?(markup)
|
||||
LITERALS[markup]
|
||||
else
|
||||
|
||||
@@ -12,6 +12,8 @@ module Liquid
|
||||
end
|
||||
end
|
||||
|
||||
attr_reader :start_obj, :end_obj
|
||||
|
||||
def initialize(start_obj, end_obj)
|
||||
@start_obj = start_obj
|
||||
@end_obj = end_obj
|
||||
@@ -35,5 +37,11 @@ module Liquid
|
||||
Utils.to_integer(input)
|
||||
end
|
||||
end
|
||||
|
||||
class ParseTreeVisitor < Liquid::ParseTreeVisitor
|
||||
def children
|
||||
[@node.start_obj, @node.end_obj]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
+357
-47
@@ -28,20 +28,47 @@ module Liquid
|
||||
end
|
||||
|
||||
# convert an input string to DOWNCASE
|
||||
#
|
||||
# @public_docs
|
||||
# @type filter
|
||||
# @summary Converts a string into lowercase.
|
||||
# @category string
|
||||
# @syntax {{ string | downcase }}
|
||||
# @return string
|
||||
def downcase(input)
|
||||
input.to_s.downcase
|
||||
end
|
||||
|
||||
# convert an input string to UPCASE
|
||||
#
|
||||
# @public_docs
|
||||
# @type filter
|
||||
# @summary Converts a string into uppercase.
|
||||
# @category string
|
||||
# @syntax {{ string | upcase }}
|
||||
# @return string
|
||||
def upcase(input)
|
||||
input.to_s.upcase
|
||||
end
|
||||
|
||||
# capitalize words in the input centence
|
||||
# capitalize words in the input sentence
|
||||
#
|
||||
# @public_docs
|
||||
# @type filter
|
||||
# @summary Capitalizes the first word in a string.
|
||||
# @category string
|
||||
# @syntax {{ string | capitalize }}
|
||||
# @return string
|
||||
def capitalize(input)
|
||||
input.to_s.capitalize
|
||||
end
|
||||
|
||||
# @public_docs
|
||||
# @type filter
|
||||
# @summary Escapes a string.
|
||||
# @category string
|
||||
# @syntax {{ string | escape }}
|
||||
# @return string
|
||||
def escape(input)
|
||||
CGI.escapeHTML(input.to_s) unless input.nil?
|
||||
end
|
||||
@@ -64,20 +91,47 @@ module Liquid
|
||||
result
|
||||
end
|
||||
|
||||
# @public_docs
|
||||
# @type filter
|
||||
# @title base64_encode
|
||||
# @summary Encodes a string into Base64.
|
||||
# @category string
|
||||
# @syntax {{ string | base64_encode }}
|
||||
# @return string
|
||||
def base64_encode(input)
|
||||
Base64.strict_encode64(input.to_s)
|
||||
end
|
||||
|
||||
# @public_docs
|
||||
# @type filter
|
||||
# @summary Decodes a string from Base64.
|
||||
# @category string
|
||||
# @syntax {{ string | base64_decode }}
|
||||
# @return string
|
||||
def base64_decode(input)
|
||||
Base64.strict_decode64(input.to_s)
|
||||
rescue ::ArgumentError
|
||||
raise Liquid::ArgumentError, "invalid base64 provided to base64_decode"
|
||||
end
|
||||
|
||||
# @public_docs
|
||||
# @type filter
|
||||
# @summary Encodes a string into URL-safe Base64
|
||||
# @category string
|
||||
# @syntax {{ string | base64_url_safe_encode }}
|
||||
# @return string
|
||||
# @description
|
||||
# To produce URL-safe Base64, this filter uses `-`` and `_`` in place of `+`` and `/``.
|
||||
def base64_url_safe_encode(input)
|
||||
Base64.urlsafe_encode64(input.to_s)
|
||||
end
|
||||
|
||||
# @public_docs
|
||||
# @type filter
|
||||
# @summary Decodes a string from URL-safe Base64.
|
||||
# @category string
|
||||
# @syntax {{ string | base64_url_safe_decode }}
|
||||
# @return string
|
||||
def base64_url_safe_decode(input)
|
||||
Base64.urlsafe_decode64(input.to_s)
|
||||
rescue ::ArgumentError
|
||||
@@ -95,7 +149,34 @@ module Liquid
|
||||
end
|
||||
end
|
||||
|
||||
# Truncate a string down to x characters
|
||||
# @public_docs
|
||||
# @type filter
|
||||
# @category string
|
||||
# @summary
|
||||
# Truncates a string down to a specified number of characters.
|
||||
# @description
|
||||
# By default, an ellipsis (`...`)
|
||||
# is appended to the truncated string.
|
||||
#
|
||||
# > Tip:
|
||||
# > The number of characters in both default and custom ellipses is included in the
|
||||
# > character count for the truncated string. For example, if you want to truncate a string
|
||||
# > down to ten characters and use the default ellipsis (`...`), then you should set the
|
||||
# > character count parameter to `13`.
|
||||
#
|
||||
# ### Custom ellipsis
|
||||
#
|
||||
# The `truncate` filter accepts an optional parameter to specify a custom ellipsis to be
|
||||
# appended to the truncated string.
|
||||
#
|
||||
# ### No ellipsis
|
||||
#
|
||||
# If you don't want an ellipsis appended to your truncated string, then you can set the
|
||||
# ellipsis parameter to a blank string (`''`).
|
||||
# @syntax {{ string | truncate: character_count, ellipsis }}
|
||||
# @required_param character_count [number] The number of characters to include in the truncated string. Includes the characters in the ellipsis.
|
||||
# @optional_param ellipsis [string] The format of the ellipsis appended to the truncated string. Default is `...`. Pass a blank string (`''`) to remove the ellipsis completely.
|
||||
# @return string
|
||||
def truncate(input, length = 50, truncate_string = "...")
|
||||
return if input.nil?
|
||||
input_str = input.to_s
|
||||
@@ -109,6 +190,27 @@ module Liquid
|
||||
input_str.length > length ? input_str[0...l].concat(truncate_string_str) : input_str
|
||||
end
|
||||
|
||||
# @public_docs
|
||||
# @type filter
|
||||
# @category string
|
||||
# @summary
|
||||
# Truncates a string down to a specified number of words.
|
||||
# @description
|
||||
# By default, an ellipsis (`...`) is appended to the truncated string.
|
||||
#
|
||||
# ### Custom ellipsis
|
||||
#
|
||||
# The `truncate` filter accepts an optional parameter to specify a custom ellipsis to be
|
||||
# appended to the truncated string.
|
||||
#
|
||||
# ### No ellipsis
|
||||
#
|
||||
# If you don't want an ellipsis appended to your truncated string, then you can set the
|
||||
# ellipsis parameter to a blank string (`''`).
|
||||
# @syntax {{ string | truncatewords: word_count, ellipsis }}
|
||||
# @required_param word_count [number] The number of words to include in the truncated string. Includes the characters in the ellipsis.
|
||||
# @optional_param ellipsis [string] The format of the ellipsis appended to the truncated string. Default is `...`. Pass a blank string (`''`) to remove the ellipsis completely.
|
||||
# @return string
|
||||
def truncatewords(input, words = 15, truncate_string = "...")
|
||||
return if input.nil?
|
||||
input = input.to_s
|
||||
@@ -137,18 +239,46 @@ module Liquid
|
||||
input.to_s.split(pattern.to_s)
|
||||
end
|
||||
|
||||
# @public_docs
|
||||
# @type filter
|
||||
# @category string
|
||||
# @summary
|
||||
# Strips all whitespace, such as tabs, spaces, and newlines, from the left and right sides of a string.
|
||||
# @syntax {{ string | strip }}
|
||||
# @return string
|
||||
def strip(input)
|
||||
input.to_s.strip
|
||||
end
|
||||
|
||||
# @public_docs
|
||||
# @type filter
|
||||
# @category string
|
||||
# @summary
|
||||
# Strips all whitespace, such as tabs, spaces, and newlines, from the left side of a string.
|
||||
# @syntax {{ string | lstrip }}
|
||||
# @return string
|
||||
def lstrip(input)
|
||||
input.to_s.lstrip
|
||||
end
|
||||
|
||||
# @public_docs
|
||||
# @type filter
|
||||
# @category string
|
||||
# @summary
|
||||
# Strips all whitespace, such as tabs, spaces, and newlines, from the right side of a string.
|
||||
# @syntax {{ string | rstrip }}
|
||||
# @return string
|
||||
def rstrip(input)
|
||||
input.to_s.rstrip
|
||||
end
|
||||
|
||||
# @public_docs
|
||||
# @type filter
|
||||
# @category string
|
||||
# @summary
|
||||
# Strips all HTML tags from a string.
|
||||
# @syntax {{ string | strip_html }}
|
||||
# @return string
|
||||
def strip_html(input)
|
||||
empty = ''
|
||||
result = input.to_s.gsub(STRIP_HTML_BLOCKS, empty)
|
||||
@@ -156,18 +286,36 @@ module Liquid
|
||||
result
|
||||
end
|
||||
|
||||
# Remove all newlines from the string
|
||||
# @public_docs
|
||||
# @type filter
|
||||
# @category string
|
||||
# @summary
|
||||
# Strips all line breaks and newlines from a string.
|
||||
# @syntax {{ string | strip_newlines }}
|
||||
# @return string
|
||||
def strip_newlines(input)
|
||||
input.to_s.gsub(/\r?\n/, '')
|
||||
end
|
||||
|
||||
# Join elements of the array with certain character between them
|
||||
# @public_docs
|
||||
# @type filter
|
||||
# @category array
|
||||
# @summary Joins the elements in an array. The result is a single string.
|
||||
# @syntax {{ array | join: ', ' }}
|
||||
# @required_param delimiter [string] The separator to join the array elements with.
|
||||
# @return string
|
||||
def join(input, glue = ' ')
|
||||
InputIterator.new(input, context).join(glue)
|
||||
end
|
||||
|
||||
# Sort elements of the array
|
||||
# provide optional property with which to sort an array of hashes or drops
|
||||
# @public_docs
|
||||
# @type filter
|
||||
# @category array
|
||||
# @summary Sorts the elements of an array.
|
||||
# @description The order of the sorted array is case-sensitive.
|
||||
# @syntax {{ array | sort: property }}
|
||||
# @optional_param property [string] The property of the element to sort by.
|
||||
# @return array
|
||||
def sort(input, property = nil)
|
||||
ary = InputIterator.new(input, context)
|
||||
|
||||
@@ -213,17 +361,23 @@ module Liquid
|
||||
|
||||
if ary.empty?
|
||||
[]
|
||||
elsif ary.first.respond_to?(:[]) && target_value.nil?
|
||||
begin
|
||||
ary.select { |item| item[property] }
|
||||
elsif target_value.nil?
|
||||
ary.select do |item|
|
||||
item[property]
|
||||
rescue TypeError
|
||||
raise_property_error(property)
|
||||
rescue NoMethodError
|
||||
return nil unless item.respond_to?(:[])
|
||||
raise
|
||||
end
|
||||
elsif ary.first.respond_to?(:[])
|
||||
begin
|
||||
ary.select { |item| item[property] == target_value }
|
||||
else
|
||||
ary.select do |item|
|
||||
item[property] == target_value
|
||||
rescue TypeError
|
||||
raise_property_error(property)
|
||||
rescue NoMethodError
|
||||
return nil unless item.respond_to?(:[])
|
||||
raise
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -237,22 +391,36 @@ module Liquid
|
||||
ary.uniq
|
||||
elsif ary.empty? # The next two cases assume a non-empty array.
|
||||
[]
|
||||
elsif ary.first.respond_to?(:[])
|
||||
begin
|
||||
ary.uniq { |a| a[property] }
|
||||
else
|
||||
ary.uniq do |item|
|
||||
item[property]
|
||||
rescue TypeError
|
||||
raise_property_error(property)
|
||||
rescue NoMethodError
|
||||
return nil unless item.respond_to?(:[])
|
||||
raise
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Reverse the elements of an array
|
||||
# @public_docs
|
||||
# @type tag
|
||||
# @category array
|
||||
# @summary Reverses the order of the items in an array.
|
||||
# @syntax {{ array | reverse }}
|
||||
# @return array
|
||||
def reverse(input)
|
||||
ary = InputIterator.new(input, context)
|
||||
ary.reverse
|
||||
end
|
||||
|
||||
# map/collect on a given property
|
||||
# @public_docs
|
||||
# @type tag
|
||||
# @category array
|
||||
# @summary Accepts an array element's property as a parameter and creates an array out of the property value for each array element.
|
||||
# @required_param property [string] The property to extract from the element.
|
||||
# @syntax {{ array | map: 'property' }}
|
||||
# @return array
|
||||
def map(input, property)
|
||||
InputIterator.new(input, context).map do |e|
|
||||
e = e.call if e.is_a?(Proc)
|
||||
@@ -277,16 +445,26 @@ module Liquid
|
||||
ary.compact
|
||||
elsif ary.empty? # The next two cases assume a non-empty array.
|
||||
[]
|
||||
elsif ary.first.respond_to?(:[])
|
||||
begin
|
||||
ary.reject { |a| a[property].nil? }
|
||||
else
|
||||
ary.reject do |item|
|
||||
item[property].nil?
|
||||
rescue TypeError
|
||||
raise_property_error(property)
|
||||
rescue NoMethodError
|
||||
return nil unless item.respond_to?(:[])
|
||||
raise
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Replace occurrences of a string with another
|
||||
# @public_docs
|
||||
# @summary Replaces all occurrences of a string with a substring.
|
||||
# @type filter
|
||||
# @category string
|
||||
# @syntax {{ string | replace: original_string, replacement_string }}
|
||||
# @required_param original_string [string] The string to replace.
|
||||
# @required_param replacement_string [string] The replacement string.
|
||||
# @return string
|
||||
def replace(input, string, replacement = '')
|
||||
input.to_s.gsub(string.to_s, replacement.to_s)
|
||||
end
|
||||
@@ -296,21 +474,71 @@ module Liquid
|
||||
input.to_s.sub(string.to_s, replacement.to_s)
|
||||
end
|
||||
|
||||
# remove a substring
|
||||
def remove(input, string)
|
||||
input.to_s.gsub(string.to_s, '')
|
||||
# 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 the first occurrences of a substring
|
||||
# @public_docs
|
||||
# @summary Removes a substring from a string.
|
||||
# @type filter
|
||||
# @category string
|
||||
# @syntax {{ string | remove: substring }}
|
||||
# @required_param substring [string] The substring to remove from the string.
|
||||
# @return string
|
||||
def remove(input, string)
|
||||
replace(input, string, '')
|
||||
end
|
||||
|
||||
# @public_docs
|
||||
# @summary Removes the first occurrences of a substring.
|
||||
# @type filter
|
||||
# @category string
|
||||
# @syntax {{ string | remove_first: substring }}
|
||||
# @required_param substring [string] The substring to remove from the string.
|
||||
# @return string
|
||||
def remove_first(input, string)
|
||||
input.to_s.sub(string.to_s, '')
|
||||
replace_first(input, string, '')
|
||||
end
|
||||
|
||||
# remove the last occurences of a substring
|
||||
def remove_last(input, string)
|
||||
replace_last(input, string, '')
|
||||
end
|
||||
|
||||
# add one string to another
|
||||
#
|
||||
# @public_docs
|
||||
# @type filter
|
||||
# @summary Appends characters to a string.
|
||||
# @category string
|
||||
# @syntax {{ string | append: to_append }}
|
||||
# @required_param to_append [string] characters to append to the original string
|
||||
# @return string
|
||||
def append(input, string)
|
||||
input.to_s + string.to_s
|
||||
end
|
||||
|
||||
# @public_docs
|
||||
# @type filter
|
||||
# @category array
|
||||
# @summary Concatenates (combines) an array with another array.
|
||||
# @description
|
||||
# The resulting array contains all the elements of the original arrays.
|
||||
# `concat` won't remove duplicate entries from the concatenated array unless you also use the [`uniq`](/api/liquid/filters/array-filters#uniq) filter.
|
||||
# @syntax {{ first_array | concat: second_array }}
|
||||
# @required_param second_array [array] The array to concatenate with the primary array.
|
||||
# @return array
|
||||
def concat(input, array)
|
||||
unless array.respond_to?(:to_ary)
|
||||
raise ArgumentError, "concat filter requires an array argument"
|
||||
@@ -318,7 +546,13 @@ module Liquid
|
||||
InputIterator.new(input, context).concat(array)
|
||||
end
|
||||
|
||||
# prepend a string to another
|
||||
# @public_docs
|
||||
# @summary Prepends a string to another string.
|
||||
# @type filter
|
||||
# @category string
|
||||
# @syntax {{ string | prepend: additional_string }}
|
||||
# @required_param additional_string [string] The string to prepend to the original string.
|
||||
# @return string
|
||||
def prepend(input, string)
|
||||
string.to_s + input.to_s
|
||||
end
|
||||
@@ -367,58 +601,101 @@ module Liquid
|
||||
date.strftime(format.to_s)
|
||||
end
|
||||
|
||||
# Get the first element of the passed in array
|
||||
#
|
||||
# Example:
|
||||
# {{ product.images | first | to_img }}
|
||||
#
|
||||
# @public_docs
|
||||
# @type filter
|
||||
# @category array
|
||||
# @summary Returns the first element of an array.
|
||||
# @syntax {{ array | first }}
|
||||
def first(array)
|
||||
array.first if array.respond_to?(:first)
|
||||
end
|
||||
|
||||
# Get the last element of the passed in array
|
||||
#
|
||||
# Example:
|
||||
# {{ product.images | last | to_img }}
|
||||
#
|
||||
# @public_docs
|
||||
# @type filter
|
||||
# @category array
|
||||
# @summary Returns the last element of an array, or the last character inside of a string.
|
||||
# @syntax {{ array | last }}
|
||||
def last(array)
|
||||
array.last if array.respond_to?(:last)
|
||||
end
|
||||
|
||||
# absolute value
|
||||
# @public_docs
|
||||
# @syntax {{ number | abs }}
|
||||
# @summary Returns the absolute value of a number.
|
||||
# @type filter
|
||||
# @category math
|
||||
# @return number
|
||||
# @description
|
||||
# `abs` will also work on a string if the string only contains a number.
|
||||
def abs(input)
|
||||
result = Utils.to_number(input).abs
|
||||
result.is_a?(BigDecimal) ? result.to_f : result
|
||||
end
|
||||
|
||||
# addition
|
||||
# @public_docs
|
||||
# @summary Adds a number to an output.
|
||||
# @type filter
|
||||
# @category math
|
||||
# @syntax {{ number | plus: number }}
|
||||
# @required_param number [number] The number to add to the original number.
|
||||
# @return number
|
||||
def plus(input, operand)
|
||||
apply_operation(input, operand, :+)
|
||||
end
|
||||
|
||||
# subtraction
|
||||
# @public_docs
|
||||
# @summary Subtracts a number from an output.
|
||||
# @type filter
|
||||
# @category math
|
||||
# @syntax {{ number | minus: number }}
|
||||
# @return number
|
||||
def minus(input, operand)
|
||||
apply_operation(input, operand, :-)
|
||||
end
|
||||
|
||||
# multiplication
|
||||
# @public_docs
|
||||
# @summary Multiplies an output by a number.
|
||||
# @type filter
|
||||
# @category math
|
||||
# @syntax {{ number | times: number }}
|
||||
# @required_param number [number] The number to multiply the original number by.
|
||||
# @return number
|
||||
def times(input, operand)
|
||||
apply_operation(input, operand, :*)
|
||||
end
|
||||
|
||||
# division
|
||||
# @public_docs
|
||||
# @summary Divides an output by a number. The output is rounded down to the nearest integer.
|
||||
# @type filter
|
||||
# @category math
|
||||
# @syntax {{ number | divided_by: number }}
|
||||
# @return number
|
||||
def divided_by(input, operand)
|
||||
apply_operation(input, operand, :/)
|
||||
rescue ::ZeroDivisionError => e
|
||||
raise Liquid::ZeroDivisionError, e.message
|
||||
end
|
||||
|
||||
# @public_docs
|
||||
# @summary Divides an output by a number and returns the remainder.
|
||||
# @type filter
|
||||
# @category math
|
||||
# @syntax {{ number | modulo: number }}
|
||||
# @required_param number [number] The number to divide the original number by.
|
||||
# @return number
|
||||
def modulo(input, operand)
|
||||
apply_operation(input, operand, :%)
|
||||
rescue ::ZeroDivisionError => e
|
||||
raise Liquid::ZeroDivisionError, e.message
|
||||
end
|
||||
|
||||
# @public_docs
|
||||
# @summary Rounds the output to the nearest integer or specified number of decimals.
|
||||
# @type filter
|
||||
# @category math
|
||||
# @syntax {{ number | round }}
|
||||
# @optional_param decimals [number] The number of decimal places to round to.
|
||||
# @return number
|
||||
def round(input, n = 0)
|
||||
result = Utils.to_number(input).round(Utils.to_number(n))
|
||||
result = result.to_f if result.is_a?(BigDecimal)
|
||||
@@ -428,18 +705,37 @@ module Liquid
|
||||
raise Liquid::FloatDomainError, e.message
|
||||
end
|
||||
|
||||
# @public_docs
|
||||
# @summary Rounds an output up to the nearest integer.
|
||||
# @type filter
|
||||
# @category math
|
||||
# @syntax {{ number | ceil }}
|
||||
# @return number
|
||||
def ceil(input)
|
||||
Utils.to_number(input).ceil.to_i
|
||||
rescue ::FloatDomainError => e
|
||||
raise Liquid::FloatDomainError, e.message
|
||||
end
|
||||
|
||||
# @public_docs
|
||||
# @summary Rounds an output down to the nearest integer.
|
||||
# @type filter
|
||||
# @category math
|
||||
# @syntax {{ number | floor }}
|
||||
# @return number
|
||||
def floor(input)
|
||||
Utils.to_number(input).floor.to_i
|
||||
rescue ::FloatDomainError => e
|
||||
raise Liquid::FloatDomainError, e.message
|
||||
end
|
||||
|
||||
# @public_docs
|
||||
# @summary Limits a number to a minimum value.
|
||||
# @type filter
|
||||
# @category math
|
||||
# @syntax {{ number | at_least: number }}
|
||||
# @required_param number [number] The minimum valid value.
|
||||
# @return number
|
||||
def at_least(input, n)
|
||||
min_value = Utils.to_number(n)
|
||||
|
||||
@@ -448,6 +744,13 @@ module Liquid
|
||||
result.is_a?(BigDecimal) ? result.to_f : result
|
||||
end
|
||||
|
||||
# @public_docs
|
||||
# @summary Limits a number to a maximum value.
|
||||
# @type filter
|
||||
# @category math
|
||||
# @syntax {{ number | at_most: number }}
|
||||
# @required_param number [number] The maximum valid value.
|
||||
# @return number
|
||||
def at_most(input, n)
|
||||
max_value = Utils.to_number(n)
|
||||
|
||||
@@ -468,7 +771,7 @@ module Liquid
|
||||
#
|
||||
def default(input, default_value = '', options = {})
|
||||
options = {} unless options.is_a?(Hash)
|
||||
false_check = options['allow_false'] ? input.nil? : !input
|
||||
false_check = options['allow_false'] ? input.nil? : !Liquid::Utils.to_liquid_value(input)
|
||||
false_check || (input.respond_to?(:empty?) && input.empty?) ? default_value : input
|
||||
end
|
||||
|
||||
@@ -486,10 +789,16 @@ module Liquid
|
||||
end
|
||||
|
||||
def nil_safe_compare(a, b)
|
||||
if !a.nil? && !b.nil?
|
||||
a <=> b
|
||||
result = a <=> b
|
||||
|
||||
if result
|
||||
result
|
||||
elsif a.nil?
|
||||
1
|
||||
elsif b.nil?
|
||||
-1
|
||||
else
|
||||
a.nil? ? 1 : -1
|
||||
raise Liquid::ArgumentError, "cannot sort values of incompatible types"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -544,8 +853,9 @@ module Liquid
|
||||
|
||||
def each
|
||||
@input.each do |e|
|
||||
e = e.respond_to?(:to_liquid) ? e.to_liquid : e
|
||||
e.context = @context if e.respond_to?(:context=)
|
||||
yield(e.respond_to?(:to_liquid) ? e.to_liquid : e)
|
||||
yield(e)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -31,7 +31,11 @@ module Liquid
|
||||
if @registers.key?(key)
|
||||
@registers.fetch(key)
|
||||
elsif default != UNDEFINED
|
||||
@static.fetch(key, default, &block)
|
||||
if block_given?
|
||||
@static.fetch(key, &block)
|
||||
else
|
||||
@static.fetch(key, default)
|
||||
end
|
||||
else
|
||||
@static.fetch(key, &block)
|
||||
end
|
||||
|
||||
@@ -7,25 +7,26 @@ module Liquid
|
||||
|
||||
def add_global_filter(filter)
|
||||
strainer_class_cache.clear
|
||||
global_filters << filter
|
||||
GlobalCache.add_filter(filter)
|
||||
end
|
||||
|
||||
def create(context, filters = [])
|
||||
strainer_from_cache(filters).new(context)
|
||||
end
|
||||
|
||||
GlobalCache = Class.new(StrainerTemplate)
|
||||
|
||||
private
|
||||
|
||||
def global_filters
|
||||
@global_filters ||= []
|
||||
end
|
||||
|
||||
def strainer_from_cache(filters)
|
||||
strainer_class_cache[filters] ||= begin
|
||||
klass = Class.new(StrainerTemplate)
|
||||
global_filters.each { |f| klass.add_filter(f) }
|
||||
filters.each { |f| klass.add_filter(f) }
|
||||
klass
|
||||
if filters.empty?
|
||||
GlobalCache
|
||||
else
|
||||
strainer_class_cache[filters] ||= begin
|
||||
klass = Class.new(GlobalCache)
|
||||
filters.each { |f| klass.add_filter(f) }
|
||||
klass
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -31,6 +31,11 @@ module Liquid
|
||||
filter_methods.include?(method.to_s)
|
||||
end
|
||||
|
||||
def inherited(subclass)
|
||||
super
|
||||
subclass.instance_variable_set(:@filter_methods, @filter_methods.dup)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def filter_methods
|
||||
|
||||
@@ -1,6 +1,27 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Liquid
|
||||
# @public_docs
|
||||
# @title case
|
||||
# @type tag
|
||||
# @category controlflow
|
||||
# @summary Creates a switch statement to execute a particular block of code when a variable has a specified value.
|
||||
# @description
|
||||
# `case` initializes the switch statement, and `when` statements define the various conditions.
|
||||
#
|
||||
# An optional `else` statement at the end of the case provides code to execute if none of the conditions are met.
|
||||
# @syntax
|
||||
# {% case variable %}
|
||||
# {% when value1 %}
|
||||
# statement1
|
||||
# {% when value2 %}
|
||||
# statement2
|
||||
# {% when value3 %}
|
||||
# statement3
|
||||
# ...
|
||||
# {% else %}
|
||||
# else_statement
|
||||
# {% endcase %}
|
||||
class Case < Block
|
||||
Syntax = /(#{QuotedFragment})/o
|
||||
WhenSyntax = /(#{QuotedFragment})(?:(?:\s+or\s+|\s*\,\s*)(#{QuotedFragment}.*))?/om
|
||||
|
||||
@@ -1,6 +1,17 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Liquid
|
||||
# @public_docs
|
||||
# @type tag
|
||||
# @category theme
|
||||
# @title comment
|
||||
# @summary
|
||||
# Allows you to comment out parts of a Liquid file.
|
||||
# Any text within the opening and closing `comment` blocks won't be output,
|
||||
# and any Liquid code won't be executed.
|
||||
# @syntax
|
||||
# {% comment %}
|
||||
# statement
|
||||
# {% endcomment %}
|
||||
class Comment < Block
|
||||
def render_to_output_buffer(_context, output)
|
||||
output
|
||||
|
||||
@@ -13,6 +13,19 @@ module Liquid
|
||||
# <div class="red"> Item four </div>
|
||||
# <div class="green"> Item five</div>
|
||||
#
|
||||
# @public_docs
|
||||
# @title cycle
|
||||
# @type tag
|
||||
# @category iteration
|
||||
# @summary Loops through a group of strings and prints them in the order that they were passed as parameters.
|
||||
# @description
|
||||
# Each time `cycle` is called, the next string that was passed as a parameter is printed.
|
||||
#
|
||||
# `cycle` must be used within a `for` loop block.
|
||||
# @syntax
|
||||
# {% for condition %}
|
||||
# {% cycle value1, value2 ... %}
|
||||
# {% endfor %}
|
||||
class Cycle < Tag
|
||||
SimpleSyntax = /\A#{QuotedFragment}+/o
|
||||
NamedSyntax = /\A(#{QuotedFragment})\s*\:\s*(.*)/om
|
||||
|
||||
+10
-10
@@ -1,16 +1,16 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Liquid
|
||||
# Echo outputs an expression
|
||||
#
|
||||
# {% echo monkey %}
|
||||
# {% echo user.name %}
|
||||
#
|
||||
# This is identical to variable output syntax, like {{ foo }}, but works
|
||||
# inside {% liquid %} tags. The full syntax is supported, including filters:
|
||||
#
|
||||
# {% echo user | link %}
|
||||
#
|
||||
# @public_docs
|
||||
# @type tag
|
||||
# @category theme
|
||||
# @title echo
|
||||
# @summary
|
||||
# Outputs an expression, or Liquid object, in the rendered HTML.
|
||||
# Works the same as wrapping an expression in double curly brace delimiters `{{ }}`.
|
||||
# Works inside the [`liquid`](/api/liquid/tags/theme-tags#liquid) tag and supports [filters](/api/liquid/filters).
|
||||
# @syntax
|
||||
# {% echo 'string' %}
|
||||
class Echo < Tag
|
||||
attr_reader :variable
|
||||
|
||||
|
||||
@@ -45,6 +45,20 @@ module Liquid
|
||||
# forloop.last:: Returns true if the item is the last item.
|
||||
# forloop.parentloop:: Provides access to the parent loop, if present.
|
||||
#
|
||||
# @public_docs
|
||||
# @title for
|
||||
# @type tag
|
||||
# @category iteration
|
||||
# @summary Repeatedly executes a block of code.
|
||||
# @description
|
||||
# You can output a maximum of 50 results per page with `for` loops. In cases where there are more than 50 results,
|
||||
# use the [`paginate`](/api/liquid/tags/theme-tags#paginate) tag to split them across multiple pages.
|
||||
#
|
||||
# For a full list of attributes available within a `for` loop, refer to the [`forloop`](/api/liquid/objects/for-loops) object.
|
||||
# @syntax
|
||||
# {% for variable in iterable parameters %}
|
||||
# @optional_param limit [number] Exit the `for` loop at the specified index.
|
||||
# @optional_param offset [number] Start the `for` loop at the specified index.
|
||||
class For < Block
|
||||
Syntax = /\A(#{VariableSegment}+)\s+in\s+(#{QuotedFragment}+)\s*(reversed)?/o
|
||||
|
||||
|
||||
@@ -3,14 +3,15 @@
|
||||
module Liquid
|
||||
# If is the conditional block
|
||||
#
|
||||
# {% if user.admin %}
|
||||
# Admin user!
|
||||
# {% else %}
|
||||
# Not admin user
|
||||
# @public_docs
|
||||
# @title if
|
||||
# @type tag
|
||||
# @category controlflow
|
||||
# @summary Executes a block of code only if a certain condition is met (if the result is `truthy`).
|
||||
# @syntax
|
||||
# {% if variable operator value %}
|
||||
# statement
|
||||
# {% endif %}
|
||||
#
|
||||
# There are {% if count < 5 %} less {% else %} more {% endif %} items than you need.
|
||||
#
|
||||
class If < Block
|
||||
Syntax = /(#{QuotedFragment})\s*([=!<>a-z_]+)?\s*(#{QuotedFragment})?/o
|
||||
ExpressionsAndOperators = /(?:\b(?:\s?and\s?|\s?or\s?)\b|(?:\s*(?!\b(?:\s?and\s?|\s?or\s?)\b)(?:#{QuotedFragment}|\S+)\s*)+)/o
|
||||
|
||||
@@ -1,6 +1,16 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Liquid
|
||||
# @public_docs
|
||||
# @type tag
|
||||
# @category theme
|
||||
# @title raw
|
||||
# @summary
|
||||
# Allows you to output Liquid code on a page without it being parsed.
|
||||
# @syntax
|
||||
# {% raw %}
|
||||
# liquid
|
||||
# {% endraw %}
|
||||
class Raw < Block
|
||||
Syntax = /\A\s*\z/
|
||||
FullTokenPossiblyInvalid = /\A(.*)#{TagStart}\s*(\w+)\s*(.*)?#{TagEnd}\z/om
|
||||
|
||||
@@ -1,6 +1,28 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Liquid
|
||||
# @public_docs
|
||||
# @type tag
|
||||
# @category theme
|
||||
# @title render
|
||||
# @summary
|
||||
# Renders a snippet from the **snippets** folder of a theme, or [code for an app block](/themes/architecture/sections/section-schema#render-app-blocks).
|
||||
# You don't need to write the file's `.liquid` extension.
|
||||
# @description
|
||||
# When a snippet is rendered, the code inside it doesn't automatically have access to the variables assigned
|
||||
# using [variable tags](/api/liquid/tags/variable-tags) within the snippet's parent template.
|
||||
# Similarly, variables assigned within the snippet can't be accessed by the code outside of the snippet.
|
||||
# This encapsulation increases performance and helps make theme code easier to understand and maintain.
|
||||
#
|
||||
# You can't use an [`include`](/api/liquid/tags/theme-tags#include) tag inside of a snippet rendered using the `render` tag.
|
||||
#
|
||||
# > Tip:
|
||||
# > This tag replaces the deprecated [`include`](/api/liquid/tags/theme-tags#include) tag.
|
||||
# @syntax
|
||||
# {% render reference %}
|
||||
# @optional_param with [string] Pass an object to use in the snippet. Use with the `as` parameter.
|
||||
# @optional_param for [string] Render the snippet once for each value of an enumerable object. Use with the `as` parameter. When using the `for` parameter, the [`forloop`](/api/liquid/objects/for-loops) object is accessible within the snippet.
|
||||
# @optional_param as [string] The variable that the object referenced by a `with` or `for` parameter represents within the snippet.
|
||||
class Render < Tag
|
||||
FOR = 'for'
|
||||
SYNTAX = /(#{QuotedString}+)(\s+(with|#{FOR})\s+(#{QuotedFragment}+))?(\s+(?:as)\s+(#{VariableSegment}+))?/o
|
||||
|
||||
@@ -1,6 +1,23 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Liquid
|
||||
# @public_docs
|
||||
# @title tablerow
|
||||
# @type tag
|
||||
# @category iteration
|
||||
# @summary Generates rows for an HTML table.
|
||||
# @description
|
||||
# Must be wrapped in opening `<table>` and closing `</table>` HTML tags.
|
||||
# For a full list of attributes available within a `tablerow` loop, refer to the [`tablerow`](/api/liquid/objects/tablerow) object.
|
||||
# @syntax
|
||||
# <table>
|
||||
# {% tablerow variable in interable parameters %}
|
||||
# content
|
||||
# {% endtablerow %}
|
||||
# </table>
|
||||
# @optional_param cols [number] The number of columns the table should have.
|
||||
# @optional_param limit [number] Exit the `tablerow` loop at the specified index.
|
||||
# @optional_param offset [number] Start the `tablerow` loop at the specified index.
|
||||
class TableRow < Block
|
||||
Syntax = /(\w+)\s+in\s+(#{QuotedFragment}+)/o
|
||||
|
||||
|
||||
@@ -3,10 +3,15 @@
|
||||
require_relative 'if'
|
||||
|
||||
module Liquid
|
||||
# Unless is a conditional just like 'if' but works on the inverse logic.
|
||||
#
|
||||
# {% unless x < 0 %} x is greater than zero {% endunless %}
|
||||
#
|
||||
# @public_docs
|
||||
# @title unless
|
||||
# @type tag
|
||||
# @category controlflow
|
||||
# @summary Executes a block of code only if a certain condition is not met (if the result is `falsy`).
|
||||
# @syntax
|
||||
# {% unless variable operator value %}
|
||||
# statement
|
||||
# {% endunless %}
|
||||
class Unless < If
|
||||
def render_to_output_buffer(context, output)
|
||||
# First condition is interpreted backwards ( if not )
|
||||
@@ -21,8 +26,9 @@ module Liquid
|
||||
|
||||
# After the first condition unless works just like if
|
||||
@blocks[1..-1].each do |block|
|
||||
result = block.evaluate(context)
|
||||
result = result.to_liquid_value if result.is_a?(Liquid::Drop)
|
||||
result = Liquid::Utils.to_liquid_value(
|
||||
block.evaluate(context)
|
||||
)
|
||||
|
||||
if result
|
||||
return block.attachment.render_to_output_buffer(context, output)
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Liquid
|
||||
VERSION = "5.0.2.alpha"
|
||||
VERSION = "5.3.0.alpha"
|
||||
end
|
||||
|
||||
@@ -3,11 +3,19 @@
|
||||
require 'yaml'
|
||||
|
||||
module Database
|
||||
DATABASE_FILE_PATH = "#{__dir__}/vision.database.yml"
|
||||
|
||||
# Load the standard vision toolkit database and re-arrage it to be simply exportable
|
||||
# to liquid as assigns. All this is based on Shopify
|
||||
def self.tables
|
||||
@tables ||= begin
|
||||
db = YAML.load_file("#{__dir__}/vision.database.yml")
|
||||
db =
|
||||
if YAML.respond_to?(:unsafe_load_file) # Only Psych 4+ can use unsafe_load_file
|
||||
# unsafe_load_file is needed for YAML references
|
||||
YAML.unsafe_load_file(DATABASE_FILE_PATH)
|
||||
else
|
||||
YAML.load_file(DATABASE_FILE_PATH)
|
||||
end
|
||||
|
||||
# From vision source
|
||||
db['products'].each do |product|
|
||||
|
||||
@@ -24,7 +24,7 @@ class ContextSensitiveDrop < Liquid::Drop
|
||||
end
|
||||
end
|
||||
|
||||
class Category < Liquid::Drop
|
||||
class Category
|
||||
attr_accessor :name
|
||||
|
||||
def initialize(name)
|
||||
@@ -36,8 +36,9 @@ class Category < Liquid::Drop
|
||||
end
|
||||
end
|
||||
|
||||
class CategoryDrop
|
||||
class CategoryDrop < Liquid::Drop
|
||||
attr_accessor :category, :context
|
||||
|
||||
def initialize(category)
|
||||
@category = category
|
||||
end
|
||||
@@ -405,45 +406,42 @@ class ContextTest < Minitest::Test
|
||||
end
|
||||
|
||||
def test_lambda_is_called_once
|
||||
@global = 0
|
||||
|
||||
@context['callcount'] = proc {
|
||||
@global ||= 0
|
||||
@global += 1
|
||||
@global += 1
|
||||
@global.to_s
|
||||
}
|
||||
|
||||
assert_equal('1', @context['callcount'])
|
||||
assert_equal('1', @context['callcount'])
|
||||
assert_equal('1', @context['callcount'])
|
||||
|
||||
@global = nil
|
||||
end
|
||||
|
||||
def test_nested_lambda_is_called_once
|
||||
@global = 0
|
||||
|
||||
@context['callcount'] = { "lambda" => proc {
|
||||
@global ||= 0
|
||||
@global += 1
|
||||
@global += 1
|
||||
@global.to_s
|
||||
} }
|
||||
|
||||
assert_equal('1', @context['callcount.lambda'])
|
||||
assert_equal('1', @context['callcount.lambda'])
|
||||
assert_equal('1', @context['callcount.lambda'])
|
||||
|
||||
@global = nil
|
||||
end
|
||||
|
||||
def test_lambda_in_array_is_called_once
|
||||
@global = 0
|
||||
|
||||
@context['callcount'] = [1, 2, proc {
|
||||
@global ||= 0
|
||||
@global += 1
|
||||
@global += 1
|
||||
@global.to_s
|
||||
}, 4, 5]
|
||||
|
||||
assert_equal('1', @context['callcount[2]'])
|
||||
assert_equal('1', @context['callcount[2]'])
|
||||
assert_equal('1', @context['callcount[2]'])
|
||||
|
||||
@global = nil
|
||||
end
|
||||
|
||||
def test_access_to_context_from_proc
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'test_helper'
|
||||
|
||||
class FilterKwargTest < Minitest::Test
|
||||
module KwargFilter
|
||||
def html_tag(_tag, attributes)
|
||||
attributes
|
||||
.map { |key, value| "#{key}='#{value}'" }
|
||||
.join(' ')
|
||||
end
|
||||
end
|
||||
|
||||
include Liquid
|
||||
|
||||
def test_can_parse_data_kwargs
|
||||
with_global_filter(KwargFilter) do
|
||||
assert_equal(
|
||||
"data-src='src' data-widths='100, 200'",
|
||||
Template.parse("{{ 'img' | html_tag: data-src: 'src', data-widths: '100, 200' }}").render(nil, nil)
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -3,6 +3,27 @@
|
||||
require 'test_helper'
|
||||
|
||||
class ProfilerTest < Minitest::Test
|
||||
class TestDrop < Liquid::Drop
|
||||
def initialize(value)
|
||||
super()
|
||||
@value = value
|
||||
end
|
||||
|
||||
def to_s
|
||||
artificial_execution_time
|
||||
|
||||
@value
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Monotonic clock precision fluctuate based on the operating system
|
||||
# By introducing a small sleep we ensure ourselves to register a non zero unit of time
|
||||
def artificial_execution_time
|
||||
sleep(Process.clock_getres(Process::CLOCK_MONOTONIC))
|
||||
end
|
||||
end
|
||||
|
||||
include Liquid
|
||||
|
||||
class ProfilingFileSystem
|
||||
@@ -198,16 +219,22 @@ class ProfilerTest < Minitest::Test
|
||||
|
||||
def test_profiling_supports_self_time
|
||||
t = Template.parse("{% for item in collection %} {{ item }} {% endfor %}", profile: true)
|
||||
t.render!("collection" => ["one", "two"])
|
||||
leaf = t.profiler[0].children[0]
|
||||
collection = [
|
||||
TestDrop.new("one"),
|
||||
TestDrop.new("two"),
|
||||
]
|
||||
output = t.render!("collection" => collection)
|
||||
assert_equal(" one two ", output)
|
||||
|
||||
assert_operator(leaf.self_time, :>, 0)
|
||||
leaf = t.profiler[0].children[0]
|
||||
assert_operator(leaf.self_time, :>, 0.0)
|
||||
end
|
||||
|
||||
def test_profiling_supports_total_time
|
||||
t = Template.parse("{% if true %} {% increment test %} {{ test }} {% endif %}", profile: true)
|
||||
t.render!
|
||||
t = Template.parse("{% if true %} {{ test }} {% endif %}", profile: true)
|
||||
output = t.render!("test" => TestDrop.new("one"))
|
||||
assert_equal(" one ", output)
|
||||
|
||||
assert_operator(t.profiler[0].total_time, :>, 0)
|
||||
assert_operator(t.profiler[0].total_time, :>, 0.0)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -3,10 +3,6 @@
|
||||
|
||||
require 'test_helper'
|
||||
|
||||
class Filters
|
||||
include Liquid::StandardFilters
|
||||
end
|
||||
|
||||
class TestThing
|
||||
attr_reader :foo
|
||||
|
||||
@@ -29,8 +25,24 @@ class TestThing
|
||||
end
|
||||
|
||||
class TestDrop < Liquid::Drop
|
||||
def test
|
||||
"testfoo"
|
||||
def initialize(value:)
|
||||
@value = value
|
||||
end
|
||||
|
||||
attr_reader :value
|
||||
|
||||
def registers
|
||||
@context.registers
|
||||
end
|
||||
end
|
||||
|
||||
class TestModel
|
||||
def initialize(value:)
|
||||
@value = value
|
||||
end
|
||||
|
||||
def to_liquid
|
||||
TestDrop.new(value: @value)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -53,10 +65,13 @@ class NumberLikeThing < Liquid::Drop
|
||||
end
|
||||
|
||||
class StandardFiltersTest < Minitest::Test
|
||||
Filters = Class.new(Liquid::StrainerTemplate)
|
||||
Filters.add_filter(Liquid::StandardFilters)
|
||||
|
||||
include Liquid
|
||||
|
||||
def setup
|
||||
@filters = Filters.new
|
||||
@filters = Filters.new(Context.new)
|
||||
end
|
||||
|
||||
def test_size
|
||||
@@ -259,8 +274,8 @@ class StandardFiltersTest < Minitest::Test
|
||||
{ "price" => 1, "handle" => "gamma" },
|
||||
{ "price" => 2, "handle" => "epsilon" },
|
||||
{ "price" => 4, "handle" => "alpha" },
|
||||
{ "handle" => "delta" },
|
||||
{ "handle" => "beta" },
|
||||
{ "handle" => "delta" },
|
||||
]
|
||||
assert_equal(expectation, @filters.sort(input, "price"))
|
||||
end
|
||||
@@ -363,8 +378,9 @@ class StandardFiltersTest < Minitest::Test
|
||||
assert_equal(["foo"], @filters.uniq("foo"))
|
||||
assert_equal([1, 3, 2, 4], @filters.uniq([1, 1, 3, 2, 3, 1, 4, 3, 2, 1]))
|
||||
assert_equal([{ "a" => 1 }, { "a" => 3 }, { "a" => 2 }], @filters.uniq([{ "a" => 1 }, { "a" => 3 }, { "a" => 1 }, { "a" => 2 }], "a"))
|
||||
testdrop = TestDrop.new
|
||||
assert_equal([testdrop], @filters.uniq([testdrop, TestDrop.new], 'test'))
|
||||
test_drop = TestDrop.new(value: "test")
|
||||
test_drop_alternate = TestDrop.new(value: "test")
|
||||
assert_equal([test_drop], @filters.uniq([test_drop, test_drop_alternate], 'value'))
|
||||
end
|
||||
|
||||
def test_uniq_empty_array
|
||||
@@ -423,6 +439,16 @@ class StandardFiltersTest < Minitest::Test
|
||||
assert_template_result("woot: 1", '{{ foo | map: "whatever" }}', "foo" => [t])
|
||||
end
|
||||
|
||||
def test_map_calls_context=
|
||||
model = TestModel.new(value: "test")
|
||||
|
||||
template = Template.parse('{{ foo | map: "registers" }}')
|
||||
template.registers[:test] = 1234
|
||||
template.assigns['foo'] = [model]
|
||||
|
||||
assert_template_result("{:test=>1234}", template.render!)
|
||||
end
|
||||
|
||||
def test_map_on_hashes
|
||||
assert_template_result("4217", '{{ thing | map: "foo" | map: "bar" }}',
|
||||
"thing" => { "foo" => [{ "bar" => 42 }, { "bar" => 17 }] })
|
||||
@@ -441,9 +467,9 @@ class StandardFiltersTest < Minitest::Test
|
||||
end
|
||||
|
||||
def test_map_over_proc
|
||||
drop = TestDrop.new
|
||||
drop = TestDrop.new(value: "testfoo")
|
||||
p = proc { drop }
|
||||
templ = '{{ procs | map: "test" }}'
|
||||
templ = '{{ procs | map: "value" }}'
|
||||
assert_template_result("testfoo", templ, "procs" => [p])
|
||||
end
|
||||
|
||||
@@ -539,19 +565,31 @@ class StandardFiltersTest < Minitest::Test
|
||||
end
|
||||
|
||||
def test_replace
|
||||
assert_equal('2 2 2 2', @filters.replace('1 1 1 1', '1', 2))
|
||||
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))
|
||||
assert_equal('2 1 1 1', @filters.replace_first('1 1 1 1', '1', 2))
|
||||
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 }}")
|
||||
|
||||
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))
|
||||
assert_template_result('1 1 1 2', "{{ '1 1 1 1' | replace_last: '1', 2 }}")
|
||||
end
|
||||
|
||||
def test_remove
|
||||
assert_equal(' ', @filters.remove("a a a a", 'a'))
|
||||
assert_equal(' ', @filters.remove("1 1 1 1", 1))
|
||||
assert_equal('a a a', @filters.remove_first("a a a a", 'a '))
|
||||
assert_equal(' 1 1 1', @filters.remove_first("1 1 1 1", 1))
|
||||
assert_template_result('a a a', "{{ 'a a a a' | remove_first: 'a ' }}")
|
||||
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 }}")
|
||||
end
|
||||
|
||||
def test_pipes_in_string_arguments
|
||||
@@ -728,6 +766,8 @@ class StandardFiltersTest < Minitest::Test
|
||||
assert_equal("bar", @filters.default([], "bar"))
|
||||
assert_equal("bar", @filters.default({}, "bar"))
|
||||
assert_template_result('bar', "{{ false | default: 'bar' }}")
|
||||
assert_template_result('bar', "{{ drop | default: 'bar' }}", 'drop' => BooleanDrop.new(false))
|
||||
assert_template_result('Yay', "{{ drop | default: 'bar' }}", 'drop' => BooleanDrop.new(true))
|
||||
end
|
||||
|
||||
def test_default_handle_false
|
||||
@@ -738,6 +778,8 @@ class StandardFiltersTest < Minitest::Test
|
||||
assert_equal("bar", @filters.default([], "bar", "allow_false" => true))
|
||||
assert_equal("bar", @filters.default({}, "bar", "allow_false" => true))
|
||||
assert_template_result('false', "{{ false | default: 'bar', allow_false: true }}")
|
||||
assert_template_result('Nay', "{{ drop | default: 'bar', allow_false: true }}", 'drop' => BooleanDrop.new(false))
|
||||
assert_template_result('Yay', "{{ drop | default: 'bar', allow_false: true }}", 'drop' => BooleanDrop.new(true))
|
||||
end
|
||||
|
||||
def test_cannot_access_private_methods
|
||||
@@ -766,6 +808,18 @@ class StandardFiltersTest < Minitest::Test
|
||||
assert_equal(expectation, @filters.where(input, "ok"))
|
||||
end
|
||||
|
||||
def test_where_string_keys
|
||||
input = [
|
||||
"alpha", "beta", "gamma", "delta"
|
||||
]
|
||||
|
||||
expectation = [
|
||||
"beta",
|
||||
]
|
||||
|
||||
assert_equal(expectation, @filters.where(input, "be"))
|
||||
end
|
||||
|
||||
def test_where_no_key_set
|
||||
input = [
|
||||
{ "handle" => "alpha", "ok" => true },
|
||||
@@ -811,7 +865,7 @@ class StandardFiltersTest < Minitest::Test
|
||||
end
|
||||
|
||||
def test_all_filters_never_raise_non_liquid_exception
|
||||
test_drop = TestDrop.new
|
||||
test_drop = TestDrop.new(value: "test")
|
||||
test_drop.context = Context.new
|
||||
test_enum = TestEnumerable.new
|
||||
test_enum.context = Context.new
|
||||
@@ -836,19 +890,14 @@ class StandardFiltersTest < Minitest::Test
|
||||
{ 1 => "bar" },
|
||||
["foo", 123, nil, true, false, Drop, ["foo"], { foo: "bar" }],
|
||||
]
|
||||
test_types.each do |first|
|
||||
test_types.each do |other|
|
||||
(@filters.methods - Object.methods).each do |method|
|
||||
arg_count = @filters.method(method).arity
|
||||
arg_count *= -1 if arg_count < 0
|
||||
inputs = [first]
|
||||
inputs << ([other] * (arg_count - 1)) if arg_count > 1
|
||||
begin
|
||||
@filters.send(method, *inputs)
|
||||
rescue Liquid::ArgumentError, Liquid::ZeroDivisionError
|
||||
nil
|
||||
end
|
||||
end
|
||||
StandardFilters.public_instance_methods(false).each do |method|
|
||||
arg_count = @filters.method(method).arity
|
||||
arg_count *= -1 if arg_count < 0
|
||||
|
||||
test_types.repeated_permutation(arg_count) do |args|
|
||||
@filters.send(method, *args)
|
||||
rescue Liquid::Error
|
||||
nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
+10
-10
@@ -72,21 +72,21 @@ module Minitest
|
||||
end
|
||||
|
||||
def with_global_filter(*globals)
|
||||
original_global_filters = Liquid::StrainerFactory.instance_variable_get(:@global_filters)
|
||||
Liquid::StrainerFactory.instance_variable_set(:@global_filters, [])
|
||||
globals.each do |global|
|
||||
Liquid::StrainerFactory.add_global_filter(global)
|
||||
end
|
||||
|
||||
Liquid::StrainerFactory.send(:strainer_class_cache).clear
|
||||
original_global_cache = Liquid::StrainerFactory::GlobalCache
|
||||
Liquid::StrainerFactory.send(:remove_const, :GlobalCache)
|
||||
Liquid::StrainerFactory.const_set(:GlobalCache, Class.new(Liquid::StrainerTemplate))
|
||||
|
||||
globals.each do |global|
|
||||
Liquid::Template.register_filter(global)
|
||||
end
|
||||
yield
|
||||
ensure
|
||||
Liquid::StrainerFactory.send(:strainer_class_cache).clear
|
||||
Liquid::StrainerFactory.instance_variable_set(:@global_filters, original_global_filters)
|
||||
begin
|
||||
yield
|
||||
ensure
|
||||
Liquid::StrainerFactory.send(:remove_const, :GlobalCache)
|
||||
Liquid::StrainerFactory.const_set(:GlobalCache, original_global_cache)
|
||||
Liquid::StrainerFactory.send(:strainer_class_cache).clear
|
||||
end
|
||||
end
|
||||
|
||||
def with_error_mode(mode)
|
||||
|
||||
@@ -10,8 +10,8 @@ class ConditionUnitTest < Minitest::Test
|
||||
end
|
||||
|
||||
def test_basic_condition
|
||||
assert_equal(false, Condition.new(1, '==', 2).evaluate)
|
||||
assert_equal(true, Condition.new(1, '==', 1).evaluate)
|
||||
assert_equal(false, Condition.new(1, '==', 2).evaluate(Context.new))
|
||||
assert_equal(true, Condition.new(1, '==', 1).evaluate(Context.new))
|
||||
end
|
||||
|
||||
def test_default_operators_evalute_true
|
||||
@@ -67,11 +67,11 @@ class ConditionUnitTest < Minitest::Test
|
||||
end
|
||||
|
||||
def test_hash_compare_backwards_compatibility
|
||||
assert_nil(Condition.new({}, '>', 2).evaluate)
|
||||
assert_nil(Condition.new(2, '>', {}).evaluate)
|
||||
assert_equal(false, Condition.new({}, '==', 2).evaluate)
|
||||
assert_equal(true, Condition.new({ 'a' => 1 }, '==', 'a' => 1).evaluate)
|
||||
assert_equal(true, Condition.new({ 'a' => 2 }, 'contains', 'a').evaluate)
|
||||
assert_nil(Condition.new({}, '>', 2).evaluate(Context.new))
|
||||
assert_nil(Condition.new(2, '>', {}).evaluate(Context.new))
|
||||
assert_equal(false, Condition.new({}, '==', 2).evaluate(Context.new))
|
||||
assert_equal(true, Condition.new({ 'a' => 1 }, '==', 'a' => 1).evaluate(Context.new))
|
||||
assert_equal(true, Condition.new({ 'a' => 2 }, 'contains', 'a').evaluate(Context.new))
|
||||
end
|
||||
|
||||
def test_contains_works_on_arrays
|
||||
@@ -106,30 +106,29 @@ class ConditionUnitTest < Minitest::Test
|
||||
|
||||
def test_or_condition
|
||||
condition = Condition.new(1, '==', 2)
|
||||
|
||||
assert_equal(false, condition.evaluate)
|
||||
assert_equal(false, condition.evaluate(Context.new))
|
||||
|
||||
condition.or(Condition.new(2, '==', 1))
|
||||
|
||||
assert_equal(false, condition.evaluate)
|
||||
assert_equal(false, condition.evaluate(Context.new))
|
||||
|
||||
condition.or(Condition.new(1, '==', 1))
|
||||
|
||||
assert_equal(true, condition.evaluate)
|
||||
assert_equal(true, condition.evaluate(Context.new))
|
||||
end
|
||||
|
||||
def test_and_condition
|
||||
condition = Condition.new(1, '==', 1)
|
||||
|
||||
assert_equal(true, condition.evaluate)
|
||||
assert_equal(true, condition.evaluate(Context.new))
|
||||
|
||||
condition.and(Condition.new(2, '==', 2))
|
||||
|
||||
assert_equal(true, condition.evaluate)
|
||||
assert_equal(true, condition.evaluate(Context.new))
|
||||
|
||||
condition.and(Condition.new(2, '==', 1))
|
||||
|
||||
assert_equal(false, condition.evaluate)
|
||||
assert_equal(false, condition.evaluate(Context.new))
|
||||
end
|
||||
|
||||
def test_should_allow_custom_proc_operator
|
||||
@@ -148,6 +147,20 @@ class ConditionUnitTest < Minitest::Test
|
||||
assert_evaluates_true(VariableLookup.new("one"), '==', VariableLookup.new("another"))
|
||||
end
|
||||
|
||||
def test_default_context_is_deprecated
|
||||
if Gem::Version.new(Liquid::VERSION) >= Gem::Version.new('6.0.0')
|
||||
flunk("Condition#evaluate without a context argument is to be removed")
|
||||
end
|
||||
|
||||
_out, err = capture_io do
|
||||
assert_equal(true, Condition.new(1, '==', 1).evaluate)
|
||||
end
|
||||
|
||||
expected = "DEPRECATION WARNING: Condition#evaluate without a context argument is deprecated" \
|
||||
" and will be removed from Liquid 6.0.0."
|
||||
assert_includes(err.lines.map(&:strip), expected)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def assert_evaluates_true(left, op, right)
|
||||
|
||||
@@ -159,6 +159,13 @@ class ParseTreeVisitorTest < Minitest::Test
|
||||
)
|
||||
end
|
||||
|
||||
def test_for_range
|
||||
assert_equal(
|
||||
["test"],
|
||||
visit(%({% for x in (1..test) %}{% endfor %}))
|
||||
)
|
||||
end
|
||||
|
||||
def test_tablerow_in
|
||||
assert_equal(
|
||||
["test"],
|
||||
|
||||
@@ -52,7 +52,8 @@ class StrainerFactoryUnitTest < Minitest::Test
|
||||
/\ALiquid error: wrong number of arguments \((1 for 0|given 1, expected 0)\)\z/,
|
||||
exception.message
|
||||
)
|
||||
assert_equal(exception.backtrace[0].split(':')[0], __FILE__)
|
||||
source = AccessScopeFilters.instance_method(:public_filter).source_location
|
||||
assert_equal(source.map(&:to_s), exception.backtrace[0].split(':')[0..1])
|
||||
end
|
||||
|
||||
def test_strainer_only_invokes_public_filter_methods
|
||||
|
||||
@@ -57,8 +57,8 @@ class StrainerTemplateUnitTest < Minitest::Test
|
||||
end
|
||||
|
||||
def test_add_filter_does_not_raise_when_module_overrides_previously_registered_method
|
||||
strainer = Context.new.strainer
|
||||
with_global_filter do
|
||||
strainer = Context.new.strainer
|
||||
strainer.class.add_filter(PublicMethodOverrideFilter)
|
||||
assert(strainer.class.send(:filter_methods).include?('public_filter'))
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user