Compare commits

..
43 changed files with 232 additions and 865 deletions
+4 -4
View File
@@ -6,12 +6,12 @@ jobs:
strategy:
matrix:
entry:
- { ruby: 2.7, allowed-failure: false } # minimum supported
- { ruby: 3.1, allowed-failure: false } # latest
- { ruby: 2.5, allowed-failure: false } # minimum supported
- { ruby: 3.0, allowed-failure: false } # latest
- { ruby: ruby-head, allowed-failure: true }
name: test (${{ matrix.entry.ruby }})
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v2
- uses: ruby/setup-ruby@v1
with:
ruby-version: ${{ matrix.entry.ruby }}
@@ -26,7 +26,7 @@ jobs:
memory_profile:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v2
- uses: ruby/setup-ruby@v1
with:
ruby-version: 2.7
+1 -5
View File
@@ -10,7 +10,7 @@ Performance:
Enabled: true
AllCops:
TargetRubyVersion: 2.7
TargetRubyVersion: 2.4
NewCops: disable
Exclude:
- 'vendor/bundle/**/*'
@@ -18,7 +18,3 @@ AllCops:
Naming/MethodName:
Exclude:
- 'example/server/liquid_servlet.rb'
# Backport https://github.com/Shopify/ruby-style-guide/pull/258
Layout/BeginEndAlignment:
Enabled: true
+14 -145
View File
@@ -1,82 +1,27 @@
# This configuration was generated by
# `rubocop --auto-gen-config`
# on 2022-06-01 17:13:35 UTC using RuboCop version 1.30.0.
# on 2020-12-11 18:53:41 UTC using RuboCop version 1.6.1.
# The point is for the user to remove these configuration records
# one by one as the offenses are removed from the code base.
# Note that changes in the inspected code, or installation of new
# versions of RuboCop, may require this file to be generated again.
# Offense count: 1
# This cop supports safe autocorrection (--autocorrect).
# Configuration parameters: Include.
# Include: **/*.gemspec
Gemspec/DeprecatedAttributeAssignment:
# Offense count: 2
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle.
# SupportedStyles: runtime_error, standard_error
Lint/InheritException:
Exclude:
- 'liquid.gemspec'
- 'lib/liquid/interrupts.rb'
# Offense count: 1
# This cop supports safe autocorrection (--autocorrect).
# Configuration parameters: TreatCommentsAsGroupSeparators, ConsiderPunctuation, Include.
# Include: **/*.gemspec
Gemspec/OrderedDependencies:
Exclude:
- 'liquid.gemspec'
# Offense count: 6
# This cop supports safe autocorrection (--autocorrect).
Layout/ClosingHeredocIndentation:
Exclude:
- 'test/integration/tags/for_tag_test.rb'
# Offense count: 34
# This cop supports safe autocorrection (--autocorrect).
Layout/EmptyLineAfterGuardClause:
Exclude:
- 'lib/liquid/block.rb'
- 'lib/liquid/block_body.rb'
- 'lib/liquid/context.rb'
- 'lib/liquid/drop.rb'
- 'lib/liquid/lexer.rb'
- 'lib/liquid/parser.rb'
- 'lib/liquid/profiler/hooks.rb'
- 'lib/liquid/standardfilters.rb'
- 'lib/liquid/tags/for.rb'
- 'lib/liquid/tags/if.rb'
- 'lib/liquid/utils.rb'
- 'lib/liquid/variable.rb'
- 'lib/liquid/variable_lookup.rb'
- 'performance/shopify/money_filter.rb'
- 'performance/shopify/paginate.rb'
# Offense count: 113
# Cop supports --auto-correct.
# Configuration parameters: AutoCorrect, AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns.
# URISchemes: http, https
Layout/LineLength:
Max: 260
# Offense count: 8
# This cop supports safe autocorrection (--autocorrect).
# Configuration parameters: AllowAliasSyntax, AllowedMethods.
# AllowedMethods: alias_method, public, protected, private
Layout/EmptyLinesAroundAttributeAccessor:
Exclude:
- 'lib/liquid/template.rb'
- 'test/integration/filter_test.rb'
- 'test/integration/tags/include_tag_test.rb'
- 'test/unit/strainer_template_unit_test.rb'
# Offense count: 17
# This cop supports safe autocorrection (--autocorrect).
# Configuration parameters: EnforcedStyle, IndentationWidth.
# SupportedStyles: aligned, indented
Layout/LineEndStringConcatenationIndentation:
Exclude:
- 'test/integration/tags/for_tag_test.rb'
- 'test/integration/tags/increment_tag_test.rb'
# Offense count: 1
# This cop supports safe autocorrection (--autocorrect).
# Configuration parameters: EnforcedStyle, IndentationWidth.
# SupportedStyles: aligned, indented
Layout/MultilineOperationIndentation:
Exclude:
- 'lib/liquid/expression.rb'
# Offense count: 9
Lint/MissingSuper:
Exclude:
- 'lib/liquid/forloop_drop.rb'
@@ -88,7 +33,7 @@ Lint/MissingSuper:
- 'test/integration/tags/for_tag_test.rb'
- 'test/integration/tags/table_row_test.rb'
# Offense count: 44
# Offense count: 43
Naming/ConstantName:
Exclude:
- 'lib/liquid.rb'
@@ -106,83 +51,7 @@ Naming/ConstantName:
- 'performance/shopify/paginate.rb'
- 'test/integration/tags/include_tag_test.rb'
# Offense count: 9
# Configuration parameters: CheckIdentifiers, CheckConstants, CheckVariables, CheckStrings, CheckSymbols, CheckComments, CheckFilepaths, FlaggedTerms.
Naming/InclusiveLanguage:
Exclude:
- 'lib/liquid/drop.rb'
- 'lib/liquid/parse_context.rb'
- 'test/integration/drop_test.rb'
- 'test/integration/tags/if_else_tag_test.rb'
# Offense count: 2
Style/ClassVars:
Exclude:
- 'lib/liquid/condition.rb'
# Offense count: 3
# This cop supports safe autocorrection (--autocorrect).
Style/ExplicitBlockArgument:
Exclude:
- 'test/integration/context_test.rb'
- 'test/integration/tag/disableable_test.rb'
- 'test/integration/tags/for_tag_test.rb'
# Offense count: 2982
# This cop supports safe autocorrection (--autocorrect).
# Configuration parameters: EnforcedStyle, ConsistentQuotesInMultiline.
# SupportedStyles: single_quotes, double_quotes
Style/StringLiterals:
Enabled: false
# Offense count: 20
# This cop supports safe autocorrection (--autocorrect).
# Configuration parameters: EnforcedStyle.
# SupportedStyles: single_quotes, double_quotes
Style/StringLiteralsInInterpolation:
Exclude:
- 'lib/liquid/condition.rb'
- 'lib/liquid/strainer_template.rb'
- 'lib/liquid/tag/disableable.rb'
- 'performance/shopify/shop_filter.rb'
- 'performance/shopify/tag_filter.rb'
# Offense count: 6
# This cop supports safe autocorrection (--autocorrect).
# Configuration parameters: EnforcedStyleForMultiline.
# SupportedStylesForMultiline: comma, consistent_comma, no_comma
Style/TrailingCommaInArrayLiteral:
Exclude:
- 'example/server/example_servlet.rb'
- 'lib/liquid/condition.rb'
- 'test/integration/context_test.rb'
- 'test/integration/standard_filter_test.rb'
- 'test/unit/parse_tree_visitor_test.rb'
# Offense count: 1
# This cop supports safe autocorrection (--autocorrect).
# Configuration parameters: EnforcedStyleForMultiline.
# SupportedStylesForMultiline: comma, consistent_comma, no_comma
Style/TrailingCommaInHashLiteral:
Exclude:
- 'lib/liquid/expression.rb'
# Offense count: 19
# This cop supports safe autocorrection (--autocorrect).
# Configuration parameters: EnforcedStyle, MinSize, WordRegex.
# SupportedStyles: percent, brackets
Style/WordArray:
Exclude:
- 'lib/liquid/tags/if.rb'
- 'liquid.gemspec'
- 'test/integration/assign_test.rb'
- 'test/integration/context_test.rb'
- 'test/integration/drop_test.rb'
- 'test/integration/standard_filter_test.rb'
# Offense count: 117
# This cop supports safe autocorrection (--autocorrect).
# Configuration parameters: AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, AllowedPatterns, IgnoredPatterns.
# URISchemes: http, https
Layout/LineLength:
Max: 260
+2 -1
View File
@@ -18,7 +18,8 @@ group :benchmark, :test do
end
group :test do
gem 'rubocop-shopify', '~> 2.6', require: false
gem 'rubocop', '~> 1.4', require: false
gem 'rubocop-shopify', '~> 1.0.7', require: false
gem 'rubocop-performance', require: false
platform :mri, :truffleruby do
+1 -41
View File
@@ -1,49 +1,9 @@
# Liquid Change Log
## 5.4.0 (unreleased)
### Breaking Changes
* Drop support for end-of-life Ruby versions (2.5 and 2.6) (#1578) [Andy Waite]
### Features
* Allow `#` to be used as an inline comment tag (#1498) [CP Clermont]
### Fixes
* `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]
### Changed
* Liquid::Context#registers now always returns a Liquid::StaticRegisters object, though supports the most used Hash functions for compatibility (#1553)
## 5.3.0 2022-03-22
### Fixes
* StandardFilter: Fix missing @context on iterations (#1525) [Thierry Joyal]
* Fix warning about block and default value in `static_registers.rb` (#1531) [Peter Zhu]
### 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]
## 5.0.2 (unreleased)
### 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
+4 -4
View File
@@ -5,7 +5,7 @@
* [Contributing guidelines](CONTRIBUTING.md)
* [Version history](History.md)
* [Liquid documentation from Shopify](https://shopify.dev/api/liquid)
* [Liquid documentation from Shopify](http://docs.shopify.com/themes/liquid-basics)
* [Liquid Wiki at GitHub](https://github.com/Shopify/liquid/wiki)
* [Website](http://liquidmarkup.org/)
@@ -56,20 +56,20 @@ 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:
```ruby
Liquid::Template.error_mode = :strict # Raises a SyntaxError when invalid syntax is used
Liquid::Template.error_mode = :warn # Adds strict errors to template.errors but continues as normal
Liquid::Template.error_mode = :warn # Adds errors to template.errors but continues as normal
Liquid::Template.error_mode = :lax # The default mode, accepts almost anything.
```
If you want to set the error mode only on specific templates you can pass `:error_mode` as an option to `parse`:
```ruby
Liquid::Template.parse(source, error_mode: :strict)
Liquid::Template.parse(source, :error_mode => :strict)
```
This is useful for doing things like enabling strict mode only in the theme editor.
+2 -3
View File
@@ -29,7 +29,6 @@ module Liquid
WhitespaceControl = '-'
TagStart = /\{\%/
TagEnd = /\%\}/
TagName = /#|\w+/
VariableSignature = /\(?[\w\-\.\[\]]\)?/
VariableSegment = /[\w\-]/
VariableStart = /\{\{/
@@ -37,7 +36,7 @@ module Liquid
VariableIncompleteEnd = /\}\}?/
QuotedString = /"[^"]*"|'[^']*'/
QuotedFragment = /#{QuotedString}|(?:[^\s,\|'"]|#{QuotedString})+/o
TagAttributes = /(\w[\w-]*)\s*\:\s*(#{QuotedFragment})/o
TagAttributes = /(\w+)\s*\:\s*(#{QuotedFragment})/o
AnyStartingTag = /#{TagStart}|#{VariableStart}/o
PartialTemplateParser = /#{TagStart}.*?#{TagEnd}|#{VariableStart}.*?#{VariableIncompleteEnd}/om
TemplateParser = /(#{PartialTemplateParser}|#{AnyStartingTag})/om
@@ -60,8 +59,8 @@ require 'liquid/forloop_drop'
require 'liquid/extensions'
require 'liquid/errors'
require 'liquid/interrupts'
require 'liquid/strainer_template'
require 'liquid/strainer_factory'
require 'liquid/strainer_template'
require 'liquid/expression'
require 'liquid/context'
require 'liquid/parser_switching'
+6 -6
View File
@@ -4,8 +4,8 @@ require 'English'
module Liquid
class BlockBody
LiquidTagToken = /\A\s*(#{TagName})\s*(.*?)\z/o
FullToken = /\A#{TagStart}#{WhitespaceControl}?(\s*)(#{TagName})(\s*)(.*?)#{WhitespaceControl}?#{TagEnd}\z/om
LiquidTagToken = /\A\s*(\w+)\s*(.*?)\z/o
FullToken = /\A#{TagStart}#{WhitespaceControl}?(\s*)(\w+)(\s*)(.*?)#{WhitespaceControl}?#{TagEnd}\z/om
ContentOfVariable = /\A#{VariableStart}#{WhitespaceControl}?(.*?)#{WhitespaceControl}?#{VariableEnd}\z/om
WhitespaceOrNothing = /\A\s*\z/
TAGSTART = "{%"
@@ -37,7 +37,7 @@ module Liquid
private def parse_for_liquid_tag(tokenizer, parse_context)
while (token = tokenizer.shift)
unless token.empty? || token.match?(WhitespaceOrNothing)
unless token.empty? || token =~ WhitespaceOrNothing
unless token =~ LiquidTagToken
# line isn't empty but didn't match tag syntax, yield and let the
# caller raise a syntax error
@@ -150,7 +150,7 @@ module Liquid
end
parse_context.trim_whitespace = false
@nodelist << token
@blank &&= token.match?(WhitespaceOrNothing)
@blank &&= !!(token =~ WhitespaceOrNothing)
end
parse_context.line_number = tokenizer.line_number
end
@@ -231,8 +231,8 @@ module Liquid
end
def create_variable(token, parse_context)
if token =~ ContentOfVariable
markup = Regexp.last_match(1)
token.scan(ContentOfVariable) do |content|
markup = content.first
return Variable.new(markup, parse_context)
end
BlockBody.raise_missing_variable_terminator(token, parse_context)
+4 -10
View File
@@ -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 = deprecated_default_context)
def evaluate(context = Context.new)
condition = self
result = nil
loop do
@@ -134,8 +134,8 @@ module Liquid
# return this as the result.
return context.evaluate(left) if op.nil?
left = Liquid::Utils.to_liquid_value(context.evaluate(left))
right = Liquid::Utils.to_liquid_value(context.evaluate(right))
left = context.evaluate(left)
right = context.evaluate(right)
operation = self.class.operators[op] || raise(Liquid::ArgumentError, "Unknown operator #{op}")
@@ -150,12 +150,6 @@ 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
[
+2 -6
View File
@@ -28,7 +28,7 @@ module Liquid
@static_environments = [static_environments].flat_map(&:freeze).freeze
@scopes = [(outer_scope || {})]
@registers = registers.is_a?(StaticRegisters) ? registers : StaticRegisters.new(registers)
@registers = registers
@errors = []
@partial = false
@strict_variables = false
@@ -39,10 +39,6 @@ module Liquid
@global_filter = nil
@disabled_tags = {}
@registers.static[:cached_partials] ||= {}
@registers.static[:file_system] ||= Liquid::Template.file_system
@registers.static[:template_factory] ||= Liquid::TemplateFactory.new
self.exception_renderer = Template.default_exception_renderer
if rethrow_errors
self.exception_renderer = Liquid::RAISE_EXCEPTION_LAMBDA
@@ -128,7 +124,7 @@ module Liquid
# context['var'] = 'hi'
# end
#
# context['var'] #=> nil
# context['var] #=> nil
def stack(new_scope = {})
push(new_scope)
yield
+10 -11
View File
@@ -10,23 +10,21 @@ module Liquid
'empty' => ''
}.freeze
INTEGERS_REGEX = /\A(-?\d+)\z/
FLOATS_REGEX = /\A(-?\d[\d\.]+)\z/
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/
# 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*\)\z/
RANGES_REGEX = /\A\s*\(\s*(?>(\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
@@ -34,6 +32,7 @@ module Liquid
when FLOATS_REGEX
Regexp.last_match(1).to_f
else
markup = markup.strip
if LITERALS.key?(markup)
LITERALS[markup]
else
+5 -6
View File
@@ -13,16 +13,15 @@
for_invalid_attribute: "Invalid attribute in for loop. Valid attributes are limit and offset"
if: "Syntax Error in tag 'if' - Valid syntax: if [expression]"
include: "Error in tag 'include' - Valid syntax: include '[template]' (with|for) [object|collection]"
inline_comment_invalid: "Syntax error in tag '#' - Each line of comments must be prefixed by the '#' character"
unknown_tag: "Unknown tag '%{tag}'"
invalid_delimiter: "'%{tag}' is not a valid delimiter for %{block_name} tags. use %{block_delimiter}"
render: "Syntax error in tag 'render' - Template name must be a quoted string"
table_row: "Syntax Error in 'table_row loop' - Valid syntax: table_row [item] in [collection] cols=3"
tag_never_closed: "'%{block_name}' tag was never closed"
tag_termination: "Tag '%{token}' was not properly terminated with regexp: %{tag_end}"
unexpected_else: "%{block_name} tag does not expect 'else' tag"
unexpected_outer_tag: "Unexpected outer '%{tag}' tag"
unknown_tag: "Unknown tag '%{tag}'"
tag_termination: "Tag '%{token}' was not properly terminated with regexp: %{tag_end}"
variable_termination: "Variable '%{token}' was not properly terminated with regexp: %{tag_end}"
tag_never_closed: "'%{block_name}' tag was never closed"
table_row: "Syntax Error in 'table_row loop' - Valid syntax: table_row [item] in [collection] cols=3"
render: "Syntax error in tag 'render' - Template name must be a quoted string"
argument:
include: "Argument error in tag 'include' - Illegal template name"
disabled:
+3 -3
View File
@@ -3,16 +3,16 @@
module Liquid
class PartialCache
def self.load(template_name, context:, parse_context:)
cached_partials = context.registers[:cached_partials]
cached_partials = (context.registers[:cached_partials] ||= {})
cached = cached_partials[template_name]
return cached if cached
file_system = context.registers[:file_system]
file_system = (context.registers[:file_system] ||= Liquid::Template.file_system)
source = file_system.read_template_file(template_name)
parse_context.partial = true
template_factory = context.registers[:template_factory]
template_factory = (context.registers[:template_factory] ||= Liquid::TemplateFactory.new)
template = template_factory.for(template_name)
partial = template.parse(source, parse_context)
-8
View File
@@ -12,8 +12,6 @@ module Liquid
end
end
attr_reader :start_obj, :end_obj
def initialize(start_obj, end_obj)
@start_obj = start_obj
@end_obj = end_obj
@@ -37,11 +35,5 @@ module Liquid
Utils.to_integer(input)
end
end
class ParseTreeVisitor < Liquid::ParseTreeVisitor
def children
[@node.start_obj, @node.end_obj]
end
end
end
end
+20 -87
View File
@@ -1,12 +1,10 @@
# frozen_string_literal: true
require 'cgi'
require 'base64'
require 'bigdecimal'
module Liquid
module StandardFilters
MAX_INT = (1 << 31) - 1
HTML_ESCAPE = {
'&' => '&amp;',
'>' => '&gt;',
@@ -64,26 +62,6 @@ module Liquid
result
end
def base64_encode(input)
Base64.strict_encode64(input.to_s)
end
def base64_decode(input)
Base64.strict_decode64(input.to_s)
rescue ::ArgumentError
raise Liquid::ArgumentError, "invalid base64 provided to base64_decode"
end
def base64_url_safe_encode(input)
Base64.urlsafe_encode64(input.to_s)
end
def base64_url_safe_decode(input)
Base64.urlsafe_decode64(input.to_s)
rescue ::ArgumentError
raise Liquid::ArgumentError, "invalid base64 provided to base64_url_safe_decode"
end
def slice(input, offset, length = nil)
offset = Utils.to_integer(offset)
length = length ? Utils.to_integer(length) : 1
@@ -115,13 +93,7 @@ module Liquid
words = Utils.to_integer(words)
words = 1 if words <= 0
wordlist = begin
input.split(" ", words + 1)
rescue RangeError
raise if words + 1 < MAX_INT
# e.g. integer #{words} too big to convert to `int'
raise Liquid::ArgumentError, "integer #{words} too big for truncatewords"
end
wordlist = input.split(" ", words + 1)
return input if wordlist.length <= words
wordlist.pop
@@ -213,23 +185,17 @@ module Liquid
if ary.empty?
[]
elsif target_value.nil?
ary.select do |item|
item[property]
elsif ary.first.respond_to?(:[]) && target_value.nil?
begin
ary.select { |item| item[property] }
rescue TypeError
raise_property_error(property)
rescue NoMethodError
return nil unless item.respond_to?(:[])
raise
end
else
ary.select do |item|
item[property] == target_value
elsif ary.first.respond_to?(:[])
begin
ary.select { |item| item[property] == target_value }
rescue TypeError
raise_property_error(property)
rescue NoMethodError
return nil unless item.respond_to?(:[])
raise
end
end
end
@@ -243,14 +209,11 @@ module Liquid
ary.uniq
elsif ary.empty? # The next two cases assume a non-empty array.
[]
else
ary.uniq do |item|
item[property]
elsif ary.first.respond_to?(:[])
begin
ary.uniq { |a| a[property] }
rescue TypeError
raise_property_error(property)
rescue NoMethodError
return nil unless item.respond_to?(:[])
raise
end
end
end
@@ -286,14 +249,11 @@ module Liquid
ary.compact
elsif ary.empty? # The next two cases assume a non-empty array.
[]
else
ary.reject do |item|
item[property].nil?
elsif ary.first.respond_to?(:[])
begin
ary.reject { |a| a[property].nil? }
rescue TypeError
raise_property_error(property)
rescue NoMethodError
return nil unless item.respond_to?(:[])
raise
end
end
end
@@ -308,34 +268,14 @@ module Liquid
input.to_s.sub(string.to_s, replacement.to_s)
end
# 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
def remove(input, string)
replace(input, string, '')
input.to_s.gsub(string.to_s, '')
end
# remove the first occurrences of a substring
def remove_first(input, string)
replace_first(input, string, '')
end
# remove the last occurences of a substring
def remove_last(input, string)
replace_last(input, string, '')
input.to_s.sub(string.to_s, '')
end
# add one string to another
@@ -500,7 +440,7 @@ module Liquid
#
def default(input, default_value = '', options = {})
options = {} unless options.is_a?(Hash)
false_check = options['allow_false'] ? input.nil? : !Liquid::Utils.to_liquid_value(input)
false_check = options['allow_false'] ? input.nil? : !input
false_check || (input.respond_to?(:empty?) && input.empty?) ? default_value : input
end
@@ -518,16 +458,10 @@ module Liquid
end
def nil_safe_compare(a, b)
result = a <=> b
if result
result
elsif a.nil?
1
elsif b.nil?
-1
if !a.nil? && !b.nil?
a <=> b
else
raise Liquid::ArgumentError, "cannot sort values of incompatible types"
a.nil? ? 1 : -1
end
end
@@ -582,9 +516,8 @@ 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)
yield(e.respond_to?(:to_liquid) ? e.to_liquid : e)
end
end
end
+1 -5
View File
@@ -31,11 +31,7 @@ module Liquid
if @registers.key?(key)
@registers.fetch(key)
elsif default != UNDEFINED
if block_given?
@static.fetch(key, &block)
else
@static.fetch(key, default)
end
@static.fetch(key, default, &block)
else
@static.fetch(key, &block)
end
+10 -11
View File
@@ -7,26 +7,25 @@ module Liquid
def add_global_filter(filter)
strainer_class_cache.clear
GlobalCache.add_filter(filter)
global_filters << 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)
if filters.empty?
GlobalCache
else
strainer_class_cache[filters] ||= begin
klass = Class.new(GlobalCache)
filters.each { |f| klass.add_filter(f) }
klass
end
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
end
end
-5
View File
@@ -31,11 +31,6 @@ 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 -8
View File
@@ -52,14 +52,7 @@ module Liquid
@blocks.each do |block|
if block.else?
block.attachment.render_to_output_buffer(context, output) if execute_else_block
next
end
result = Liquid::Utils.to_liquid_value(
block.evaluate(context)
)
if result
elsif block.evaluate(context)
execute_else_block = false
block.attachment.render_to_output_buffer(context, output)
end
+1 -5
View File
@@ -50,11 +50,7 @@ module Liquid
def render_to_output_buffer(context, output)
@blocks.each do |block|
result = Liquid::Utils.to_liquid_value(
block.evaluate(context)
)
if result
if block.evaluate(context)
return block.attachment.render_to_output_buffer(context, output)
end
end
-30
View File
@@ -1,30 +0,0 @@
# frozen_string_literal: true
module Liquid
class InlineComment < Tag
def initialize(tag_name, markup, options)
super
# Semantically, a comment should only ignore everything after it on the line.
# Currently, this implementation doesn't support mixing a comment with another tag
# but we need to reserve future support for this and prevent the introduction
# of inline comments from being backward incompatible change.
#
# As such, we're forcing users to put a # symbol on every line otherwise this
# tag will throw an error.
if markup.match?(/\n\s*[^#\s]/)
raise SyntaxError, options[:locale].t("errors.syntax.inline_comment_invalid")
end
end
def render_to_output_buffer(_context, output)
output
end
def blank?
true
end
end
Template.register_tag('#', InlineComment)
end
+3 -3
View File
@@ -34,9 +34,9 @@ module Liquid
end
def render_tag(context, output)
# The expression should be a String literal, which parses to a String object
template_name = @template_name_expr
raise ::ArgumentError unless template_name.is_a?(String)
# Though we evaluate this here we will only ever parse it as a string literal.
template_name = context.evaluate(@template_name_expr)
raise ArgumentError, options[:locale].t("errors.argument.include") unless template_name
partial = PartialCache.load(
template_name,
+2 -10
View File
@@ -11,21 +11,13 @@ module Liquid
def render_to_output_buffer(context, output)
# First condition is interpreted backwards ( if not )
first_block = @blocks.first
result = Liquid::Utils.to_liquid_value(
first_block.evaluate(context)
)
unless result
unless first_block.evaluate(context)
return first_block.attachment.render_to_output_buffer(context, output)
end
# After the first condition unless works just like if
@blocks[1..-1].each do |block|
result = Liquid::Utils.to_liquid_value(
block.evaluate(context)
)
if result
if block.evaluate(context)
return block.attachment.render_to_output_buffer(context, output)
end
end
+3 -2
View File
@@ -167,14 +167,15 @@ module Liquid
output = nil
context_register = context.registers.is_a?(StaticRegisters) ? context.registers.static : context.registers
case args.last
when Hash
options = args.pop
output = options[:output] if options[:output]
static_registers = context.registers.static
options[:registers]&.each do |key, register|
static_registers[key] = register
context_register[key] = register
end
apply_options_to_context(context, options)
-8
View File
@@ -81,13 +81,5 @@ module Liquid
rescue ::ArgumentError
nil
end
def self.to_liquid_value(obj)
# Enable "obj" to represent itself as a primitive value like integer, string, or boolean
return obj.to_liquid_value if obj.respond_to?(:to_liquid_value)
# Otherwise return the object itself
obj
end
end
end
+6 -8
View File
@@ -2,7 +2,8 @@
module Liquid
class VariableLookup
COMMAND_METHODS = ['size', 'first', 'last'].freeze
SQUARE_BRACKETED = /\A\[(.*)\]\z/m
COMMAND_METHODS = ['size', 'first', 'last'].freeze
attr_reader :name, :lookups
@@ -14,8 +15,8 @@ module Liquid
lookups = markup.scan(VariableParser)
name = lookups.shift
if name&.start_with?('[') && name&.end_with?(']')
name = Expression.parse(name[1..-2])
if name =~ SQUARE_BRACKETED
name = Expression.parse(Regexp.last_match(1))
end
@name = name
@@ -24,8 +25,8 @@ module Liquid
@lookups.each_index do |i|
lookup = lookups[i]
if lookup&.start_with?('[') && lookup&.end_with?(']')
lookups[i] = Expression.parse(lookup[1..-2])
if lookup =~ SQUARE_BRACKETED
lookups[i] = Expression.parse(Regexp.last_match(1))
elsif COMMAND_METHODS.include?(lookup)
@command_flags |= 1 << i
end
@@ -39,9 +40,6 @@ module Liquid
@lookups.each_index do |i|
key = context.evaluate(@lookups[i])
# Cast "key" to its liquid value to enable it to act as a primitive value
key = Liquid::Utils.to_liquid_value(key)
# If object is a hash- or array-like object we look for the
# presence of the key and if its available we return it
if object.respond_to?(:[]) &&
+1 -1
View File
@@ -2,5 +2,5 @@
# frozen_string_literal: true
module Liquid
VERSION = "5.4.0.alpha"
VERSION = "5.0.2.alpha"
end
+1 -1
View File
@@ -17,7 +17,7 @@ Gem::Specification.new do |s|
s.license = "MIT"
# s.description = "A secure, non-evaling end user template engine with aesthetic markup."
s.required_ruby_version = ">= 2.7.0"
s.required_ruby_version = ">= 2.5.0"
s.required_rubygems_version = ">= 1.3.7"
s.metadata['allowed_push_host'] = 'https://rubygems.org'
+1 -9
View File
@@ -3,19 +3,11 @@
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 =
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
db = YAML.load_file("#{__dir__}/vision.database.yml")
# From vision source
db['products'].each do |product|
+1 -1
View File
@@ -3,7 +3,7 @@
# This profiler run simulates Shopify.
# We are looking in the tests directory for liquid files and render them within the designated layout file.
# We will also export a substantial database to liquid which the templates can render values of.
# All this is to make the benchmark as non synthetic as possible. All templates and tests are lifted from
# All this is to make the benchmark as non syntetic as possible. All templates and tests are lifted from
# direct real-world usage and the profiler measures code that looks very similar to the way it looks in
# Shopify which is likely the biggest user of liquid in the world which something to the tune of several
# million Template#render calls a day.
+14 -26
View File
@@ -24,7 +24,7 @@ class ContextSensitiveDrop < Liquid::Drop
end
end
class Category
class Category < Liquid::Drop
attr_accessor :name
def initialize(name)
@@ -36,9 +36,8 @@ class Category
end
end
class CategoryDrop < Liquid::Drop
class CategoryDrop
attr_accessor :category, :context
def initialize(category)
@category = category
end
@@ -406,42 +405,45 @@ class ContextTest < Minitest::Test
end
def test_lambda_is_called_once
@global = 0
@context['callcount'] = proc {
@global += 1
@global ||= 0
@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 += 1
@global ||= 0
@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 += 1
@global ||= 0
@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
@@ -618,20 +620,6 @@ class ContextTest < Minitest::Test
end
end
def test_context_always_uses_static_registers
registers = {
my_register: :my_value,
}
c = Context.new({}, {}, registers)
assert_instance_of(StaticRegisters, c.registers)
assert_equal(:my_value, c.registers[:my_register])
r = StaticRegisters.new(registers)
c = Context.new({}, {}, r)
assert_instance_of(StaticRegisters, c.registers)
assert_equal(:my_value, c.registers[:my_register])
end
private
def assert_no_object_allocations
-24
View File
@@ -1,24 +0,0 @@
# 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
+6 -33
View File
@@ -3,27 +3,6 @@
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
@@ -219,22 +198,16 @@ class ProfilerTest < Minitest::Test
def test_profiling_supports_self_time
t = Template.parse("{% for item in collection %} {{ item }} {% endfor %}", profile: true)
collection = [
TestDrop.new("one"),
TestDrop.new("two"),
]
output = t.render!("collection" => collection)
assert_equal(" one two ", output)
t.render!("collection" => ["one", "two"])
leaf = t.profiler[0].children[0]
assert_operator(leaf.self_time, :>, 0.0)
assert_operator(leaf.self_time, :>, 0)
end
def test_profiling_supports_total_time
t = Template.parse("{% if true %} {{ test }} {% endif %}", profile: true)
output = t.render!("test" => TestDrop.new("one"))
assert_equal(" one ", output)
t = Template.parse("{% if true %} {% increment test %} {{ test }} {% endif %}", profile: true)
t.render!
assert_operator(t.profiler[0].total_time, :>, 0.0)
assert_operator(t.profiler[0].total_time, :>, 0)
end
end
+32 -119
View File
@@ -3,6 +3,10 @@
require 'test_helper'
class Filters
include Liquid::StandardFilters
end
class TestThing
attr_reader :foo
@@ -25,24 +29,8 @@ class TestThing
end
class TestDrop < Liquid::Drop
def initialize(value:)
@value = value
end
attr_reader :value
def registers
{ @value => @context.registers[@value] }
end
end
class TestModel
def initialize(value:)
@value = value
end
def to_liquid
TestDrop.new(value: @value)
def test
"testfoo"
end
end
@@ -65,13 +53,10 @@ 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(Context.new)
@filters = Filters.new
end
def test_size
@@ -160,40 +145,6 @@ class StandardFiltersTest < Minitest::Test
assert_equal('&lt;strong&gt;Hulk&lt;/strong&gt;', @filters.escape_once('&lt;strong&gt;Hulk</strong>'))
end
def test_base64_encode
assert_equal('b25lIHR3byB0aHJlZQ==', @filters.base64_encode('one two three'))
assert_equal('', @filters.base64_encode(nil))
end
def test_base64_decode
assert_equal('one two three', @filters.base64_decode('b25lIHR3byB0aHJlZQ=='))
exception = assert_raises(Liquid::ArgumentError) do
@filters.base64_decode("invalidbase64")
end
assert_equal('Liquid error: invalid base64 provided to base64_decode', exception.message)
end
def test_base64_url_safe_encode
assert_equal(
'YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXogQUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVogMTIzNDU2Nzg5MCAhQCMkJV4mKigpLT1fKy8_Ljo7W117fVx8',
@filters.base64_url_safe_encode('abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ 1234567890 !@#$%^&*()-=_+/?.:;[]{}\|')
)
assert_equal('', @filters.base64_url_safe_encode(nil))
end
def test_base64_url_safe_decode
assert_equal(
'abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ 1234567890 !@#$%^&*()-=_+/?.:;[]{}\|',
@filters.base64_url_safe_decode('YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXogQUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVogMTIzNDU2Nzg5MCAhQCMkJV4mKigpLT1fKy8_Ljo7W117fVx8')
)
exception = assert_raises(Liquid::ArgumentError) do
@filters.base64_url_safe_decode("invalidbase64")
end
assert_equal('Liquid error: invalid base64 provided to base64_url_safe_decode', exception.message)
end
def test_url_encode
assert_equal('foo%2B1%40example.com', @filters.url_encode('[email protected]'))
assert_equal('1', @filters.url_encode(1))
@@ -227,10 +178,6 @@ class StandardFiltersTest < Minitest::Test
assert_equal('one two three...', @filters.truncatewords("one two\tthree\nfour", 3))
assert_equal('one two...', @filters.truncatewords("one two three four", 2))
assert_equal('one...', @filters.truncatewords("one two three four", 0))
exception = assert_raises(Liquid::ArgumentError) do
@filters.truncatewords("one two three four", 1 << 31)
end
assert_equal("Liquid error: integer #{1 << 31} too big for truncatewords", exception.message)
end
def test_strip_html
@@ -274,8 +221,8 @@ class StandardFiltersTest < Minitest::Test
{ "price" => 1, "handle" => "gamma" },
{ "price" => 2, "handle" => "epsilon" },
{ "price" => 4, "handle" => "alpha" },
{ "handle" => "beta" },
{ "handle" => "delta" },
{ "handle" => "beta" },
]
assert_equal(expectation, @filters.sort(input, "price"))
end
@@ -378,9 +325,8 @@ 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"))
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'))
testdrop = TestDrop.new
assert_equal([testdrop], @filters.uniq([testdrop, TestDrop.new], 'test'))
end
def test_uniq_empty_array
@@ -439,16 +385,6 @@ 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 }] })
@@ -467,9 +403,9 @@ class StandardFiltersTest < Minitest::Test
end
def test_map_over_proc
drop = TestDrop.new(value: "testfoo")
drop = TestDrop.new
p = proc { drop }
templ = '{{ procs | map: "value" }}'
templ = '{{ procs | map: "test" }}'
assert_template_result("testfoo", templ, "procs" => [p])
end
@@ -565,31 +501,19 @@ class StandardFiltersTest < Minitest::Test
end
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))
assert_equal('2 2 2 2', @filters.replace('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('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_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('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 ' }}")
end
def test_pipes_in_string_arguments
@@ -766,8 +690,6 @@ 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
@@ -778,8 +700,6 @@ 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
@@ -808,18 +728,6 @@ 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 },
@@ -865,7 +773,7 @@ class StandardFiltersTest < Minitest::Test
end
def test_all_filters_never_raise_non_liquid_exception
test_drop = TestDrop.new(value: "test")
test_drop = TestDrop.new
test_drop.context = Context.new
test_enum = TestEnumerable.new
test_enum.context = Context.new
@@ -890,14 +798,19 @@ class StandardFiltersTest < Minitest::Test
{ 1 => "bar" },
["foo", 123, nil, true, false, Drop, ["foo"], { foo: "bar" }],
]
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
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
end
end
end
+45
View File
@@ -187,4 +187,49 @@ class IfElseTagTest < Minitest::Test
assert_template_result(expected.to_s, tpl, assigns, assigns.to_s)
end
end
def test_operators_order
expected_groupings = [
->(w, x, y, z) { (w && (x || (y && z))) },
->(w, x, y, z) { (w || (x && (y || z))) },
]
liquid_conditions = [
'w and x or y and z',
'w or x and y or z',
]
conditions = (0..0b1111).map do |cond|
[
(cond & 0b1000) == 0 ? false : true,
(cond & 0b0100) == 0 ? false : true,
(cond & 0b0010) == 0 ? false : true,
(cond & 0b0001) == 0 ? false : true,
]
end
expected_results = expected_groupings.map do |test|
conditions.map do |condition|
test.call(*condition)
end
end
liquid_results = liquid_conditions.map do |conditional_expression|
tpl = "{% if #{conditional_expression} %}true{% else %}false{% endif %}"
template = Liquid::Template.parse(tpl, line_numbers: true)
conditions.map do |condition|
w, x, y, z = condition
assigns = { 'w' => w, 'x' => x, 'y' => y, 'z' => z }
template.render!(assigns)
end.map { |r| true?(r) }
end
assert_equal(expected_results, liquid_results)
end
private
def true?(obj)
obj.to_s.casecmp('true').zero?
end
end
@@ -1,69 +0,0 @@
# frozen_string_literal: true
require 'test_helper'
class InlineCommentTest < Minitest::Test
include Liquid
def test_inline_comment_returns_nothing
assert_template_result('', '{%- # this is an inline comment -%}')
assert_template_result('', '{%-# this is an inline comment -%}')
assert_template_result('', '{% # this is an inline comment %}')
assert_template_result('', '{%# this is an inline comment %}')
end
def test_inline_comment_does_not_require_a_space_after_the_pound_sign
assert_template_result('', '{%#this is an inline comment%}')
end
def test_liquid_inline_comment_returns_nothing
assert_template_result('Hey there, how are you doing today?', <<~LIQUID)
{%- liquid
# This is how you'd write a block comment in a liquid tag.
# It looks a lot like what you'd have in ruby.
# You can use it as inline documentation in your
# liquid blocks to explain why you're doing something.
echo "Hey there, "
# It won't affect the output.
echo "how are you doing today?"
-%}
LIQUID
end
def test_inline_comment_can_be_written_on_multiple_lines
assert_template_result('', <<~LIQUID)
{%-
# That kind of block comment is also allowed.
# It would only be a stylistic difference.
# Much like JavaScript's /* */ comments and their
# leading * on new lines.
-%}
LIQUID
end
def test_inline_comment_multiple_pound_signs
assert_template_result('', <<~LIQUID)
{%- liquid
######################################
# We support comments like this too. #
######################################
-%}
LIQUID
end
def test_inline_comments_require_the_pound_sign_on_every_new_line
assert_match_syntax_error("Each line of comments must be prefixed by the '#' character", <<~LIQUID)
{%-
# some comment
echo 'hello world'
-%}
LIQUID
end
def test_inline_comment_does_not_support_nested_tags
assert_template_result(' -%}', "{%- # {% echo 'hello world' %} -%}")
end
end
-31
View File
@@ -15,33 +15,6 @@ class VariableTest < Minitest::Test
assert_template_result('foobar', '{{ foo }}', 'foo' => ThingWithToLiquid.new)
end
def test_variable_lookup_calls_to_liquid_value
assert_template_result('1', '{{ foo }}', 'foo' => IntegerDrop.new('1'))
assert_template_result('2', '{{ list[foo] }}', 'foo' => IntegerDrop.new('1'), 'list' => [1, 2, 3])
assert_template_result('one', '{{ list[foo] }}', 'foo' => IntegerDrop.new('1'), 'list' => { 1 => 'one' })
assert_template_result('Yay', '{{ foo }}', 'foo' => BooleanDrop.new(true))
assert_template_result('YAY', '{{ foo | upcase }}', 'foo' => BooleanDrop.new(true))
end
def test_if_tag_calls_to_liquid_value
assert_template_result('one', '{% if foo == 1 %}one{% endif %}', 'foo' => IntegerDrop.new('1'))
assert_template_result('one', '{% if 0 < foo %}one{% endif %}', 'foo' => IntegerDrop.new('1'))
assert_template_result('one', '{% if foo > 0 %}one{% endif %}', 'foo' => IntegerDrop.new('1'))
assert_template_result('true', '{% if foo == true %}true{% endif %}', 'foo' => BooleanDrop.new(true))
assert_template_result('true', '{% if foo %}true{% endif %}', 'foo' => BooleanDrop.new(true))
assert_template_result('', '{% if foo %}true{% endif %}', 'foo' => BooleanDrop.new(false))
assert_template_result('', '{% if foo == true %}True{% endif %}', 'foo' => BooleanDrop.new(false))
end
def test_unless_tag_calls_to_liquid_value
assert_template_result('', '{% unless foo %}true{% endunless %}', 'foo' => BooleanDrop.new(true))
end
def test_case_tag_calls_to_liquid_value
assert_template_result('One', '{% case foo %}{% when 1 %}One{% endcase %}', 'foo' => IntegerDrop.new('1'))
end
def test_simple_with_whitespaces
template = Template.parse(%( {{ test }} ))
assert_equal(' worked ', template.render!('test' => 'worked'))
@@ -131,8 +104,4 @@ class VariableTest < Minitest::Test
def test_dynamic_find_var
assert_template_result('bar', '{{ [key] }}', 'key' => 'foo', 'foo' => 'bar')
end
def test_raw_value_variable
assert_template_result('bar', '{{ [key] }}', 'key' => 'foo', 'foo' => 'bar')
end
end
+10 -48
View File
@@ -72,21 +72,21 @@ module Minitest
end
def with_global_filter(*globals)
original_global_cache = Liquid::StrainerFactory::GlobalCache
Liquid::StrainerFactory.send(:remove_const, :GlobalCache)
Liquid::StrainerFactory.const_set(:GlobalCache, Class.new(Liquid::StrainerTemplate))
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
globals.each do |global|
Liquid::Template.register_filter(global)
end
yield
ensure
Liquid::StrainerFactory.send(:strainer_class_cache).clear
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
Liquid::StrainerFactory.instance_variable_set(:@global_filters, original_global_filters)
end
def with_error_mode(mode)
@@ -119,44 +119,6 @@ class ThingWithToLiquid
end
end
class IntegerDrop < Liquid::Drop
def initialize(value)
super()
@value = value.to_i
end
def ==(other)
@value == other
end
def to_s
@value.to_s
end
def to_liquid_value
@value
end
end
class BooleanDrop < Liquid::Drop
def initialize(value)
super()
@value = value
end
def ==(other)
@value == other
end
def to_liquid_value
@value
end
def to_s
@value ? "Yay" : "Nay"
end
end
class ErrorDrop < Liquid::Drop
def standard_error
raise Liquid::StandardError, 'standard error'
+14 -27
View File
@@ -10,8 +10,8 @@ class ConditionUnitTest < Minitest::Test
end
def test_basic_condition
assert_equal(false, Condition.new(1, '==', 2).evaluate(Context.new))
assert_equal(true, Condition.new(1, '==', 1).evaluate(Context.new))
assert_equal(false, Condition.new(1, '==', 2).evaluate)
assert_equal(true, Condition.new(1, '==', 1).evaluate)
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(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))
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)
end
def test_contains_works_on_arrays
@@ -106,29 +106,30 @@ class ConditionUnitTest < Minitest::Test
def test_or_condition
condition = Condition.new(1, '==', 2)
assert_equal(false, condition.evaluate(Context.new))
assert_equal(false, condition.evaluate)
condition.or(Condition.new(2, '==', 1))
assert_equal(false, condition.evaluate(Context.new))
assert_equal(false, condition.evaluate)
condition.or(Condition.new(1, '==', 1))
assert_equal(true, condition.evaluate(Context.new))
assert_equal(true, condition.evaluate)
end
def test_and_condition
condition = Condition.new(1, '==', 1)
assert_equal(true, condition.evaluate(Context.new))
assert_equal(true, condition.evaluate)
condition.and(Condition.new(2, '==', 2))
assert_equal(true, condition.evaluate(Context.new))
assert_equal(true, condition.evaluate)
condition.and(Condition.new(2, '==', 1))
assert_equal(false, condition.evaluate(Context.new))
assert_equal(false, condition.evaluate)
end
def test_should_allow_custom_proc_operator
@@ -147,20 +148,6 @@ 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)
-7
View File
@@ -159,13 +159,6 @@ 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"],
-31
View File
@@ -125,35 +125,4 @@ class PartialCacheUnitTest < Minitest::Test
assert_equal('my partial body', partial.render)
assert_equal(1, template_factory.count)
end
def test_cache_state_is_shared_for_subcontexts
parse_context = Liquid::ParseContext.new
shared_file_system = StubFileSystem.new(
'my_partial' => 'my shared value'
)
context = Liquid::Context.build(
registers: Liquid::StaticRegisters.new(
file_system: shared_file_system,
)
)
subcontext = context.new_isolated_subcontext
assert_equal(subcontext.registers[:cached_partials].object_id, context.registers[:cached_partials].object_id)
2.times do
Liquid::PartialCache.load(
'my_partial',
context: context,
parse_context: parse_context
)
Liquid::PartialCache.load(
'my_partial',
context: subcontext,
parse_context: parse_context
)
end
assert_equal(1, shared_file_system.file_read_count)
end
end
+1 -2
View File
@@ -52,8 +52,7 @@ class StrainerFactoryUnitTest < Minitest::Test
/\ALiquid error: wrong number of arguments \((1 for 0|given 1, expected 0)\)\z/,
exception.message
)
source = AccessScopeFilters.instance_method(:public_filter).source_location
assert_equal(source.map(&:to_s), exception.backtrace[0].split(':')[0..1])
assert_equal(exception.backtrace[0].split(':')[0], __FILE__)
end
def test_strainer_only_invokes_public_filter_methods
+1 -1
View File
@@ -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