mirror of
https://github.com/kemko/liquid.git
synced 2026-01-08 19:25:40 +03:00
Compare commits
3 Commits
changes-fo
...
no-templat
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d5c31861f9 | ||
|
|
895e63e40a | ||
|
|
219168e89f |
12
README.md
12
README.md
@@ -80,22 +80,24 @@ It is also recommended that you use it in the template editors of existing apps
|
|||||||
|
|
||||||
By default, the renderer doesn't raise or in any other way notify you if some variables or filters are missing, i.e. not passed to the `render` method.
|
By default, the renderer doesn't raise or in any other way notify you if some variables or filters are missing, i.e. not passed to the `render` method.
|
||||||
You can improve this situation by passing `strict_variables: true` and/or `strict_filters: true` options to the `render` method.
|
You can improve this situation by passing `strict_variables: true` and/or `strict_filters: true` options to the `render` method.
|
||||||
When one of these options is set to true, all errors about undefined variables and undefined filters will be stored in `errors` array of a `Liquid::Template` instance.
|
When one of these options is set to true, all errors about undefined variables and undefined filters will be stored in an `errors` array on the `Liquid::Context` instance used for rendering.
|
||||||
Here are some examples:
|
Here are some examples:
|
||||||
|
|
||||||
```ruby
|
```ruby
|
||||||
template = Liquid::Template.parse("{{x}} {{y}} {{z.a}} {{z.b}}")
|
template = Liquid::Template.parse("{{x}} {{y}} {{z.a}} {{z.b}}")
|
||||||
template.render({ 'x' => 1, 'z' => { 'a' => 2 } }, { strict_variables: true })
|
context = Liquid::Context.new({ 'x' => 1, 'z' => { 'a' => 2 } })
|
||||||
|
template.render(context, { strict_variables: true })
|
||||||
#=> '1 2 ' # when a variable is undefined, it's rendered as nil
|
#=> '1 2 ' # when a variable is undefined, it's rendered as nil
|
||||||
template.errors
|
context.errors
|
||||||
#=> [#<Liquid::UndefinedVariable: Liquid error: undefined variable y>, #<Liquid::UndefinedVariable: Liquid error: undefined variable b>]
|
#=> [#<Liquid::UndefinedVariable: Liquid error: undefined variable y>, #<Liquid::UndefinedVariable: Liquid error: undefined variable b>]
|
||||||
```
|
```
|
||||||
|
|
||||||
```ruby
|
```ruby
|
||||||
template = Liquid::Template.parse("{{x | filter1 | upcase}}")
|
template = Liquid::Template.parse("{{x | filter1 | upcase}}")
|
||||||
template.render({ 'x' => 'foo' }, { strict_filters: true })
|
context = Liquid::Context.new({ 'x' => 'foo' })
|
||||||
|
template.render(context, { strict_filters: true })
|
||||||
#=> '' # when at least one filter in the filter chain is undefined, a whole expression is rendered as nil
|
#=> '' # when at least one filter in the filter chain is undefined, a whole expression is rendered as nil
|
||||||
template.errors
|
context.errors
|
||||||
#=> [#<Liquid::UndefinedFilter: Liquid error: undefined filter filter1>]
|
#=> [#<Liquid::UndefinedFilter: Liquid error: undefined filter filter1>]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -63,8 +63,6 @@ require 'liquid/expression'
|
|||||||
require 'liquid/context'
|
require 'liquid/context'
|
||||||
require 'liquid/parser_switching'
|
require 'liquid/parser_switching'
|
||||||
require 'liquid/tag'
|
require 'liquid/tag'
|
||||||
require 'liquid/tag/disabler'
|
|
||||||
require 'liquid/tag/disableable'
|
|
||||||
require 'liquid/block'
|
require 'liquid/block'
|
||||||
require 'liquid/block_body'
|
require 'liquid/block_body'
|
||||||
require 'liquid/document'
|
require 'liquid/document'
|
||||||
@@ -88,3 +86,4 @@ require 'liquid/template_factory'
|
|||||||
# Load all the tags of the standard library
|
# Load all the tags of the standard library
|
||||||
#
|
#
|
||||||
Dir["#{__dir__}/liquid/tags/*.rb"].each { |f| require f }
|
Dir["#{__dir__}/liquid/tags/*.rb"].each { |f| require f }
|
||||||
|
Dir["#{__dir__}/liquid/registers/*.rb"].each { |f| require f }
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ module Liquid
|
|||||||
end
|
end
|
||||||
|
|
||||||
def parse(tokens)
|
def parse(tokens)
|
||||||
@body = new_body
|
@body = BlockBody.new
|
||||||
while parse_body(@body, tokens)
|
while parse_body(@body, tokens)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -28,12 +28,7 @@ module Liquid
|
|||||||
@body.nodelist
|
@body.nodelist
|
||||||
end
|
end
|
||||||
|
|
||||||
def unknown_tag(tag_name, _markup, _tokenizer)
|
def unknown_tag(tag, _params, _tokens)
|
||||||
Block.raise_unknown_tag(tag_name, block_name, block_delimiter, parse_context)
|
|
||||||
end
|
|
||||||
|
|
||||||
# @api private
|
|
||||||
def self.raise_unknown_tag(tag, block_name, block_delimiter, parse_context)
|
|
||||||
if tag == 'else'
|
if tag == 'else'
|
||||||
raise SyntaxError, parse_context.locale.t("errors.syntax.unexpected_else",
|
raise SyntaxError, parse_context.locale.t("errors.syntax.unexpected_else",
|
||||||
block_name: block_name)
|
block_name: block_name)
|
||||||
@@ -55,14 +50,8 @@ module Liquid
|
|||||||
@block_delimiter ||= "end#{block_name}"
|
@block_delimiter ||= "end#{block_name}"
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
protected
|
||||||
|
|
||||||
# @api public
|
|
||||||
def new_body
|
|
||||||
parse_context.new_block_body
|
|
||||||
end
|
|
||||||
|
|
||||||
# @api public
|
|
||||||
def parse_body(body, tokens)
|
def parse_body(body, tokens)
|
||||||
if parse_context.depth >= MAX_DEPTH
|
if parse_context.depth >= MAX_DEPTH
|
||||||
raise StackLevelError, "Nesting too deep"
|
raise StackLevelError, "Nesting too deep"
|
||||||
|
|||||||
@@ -54,60 +54,28 @@ module Liquid
|
|||||||
end
|
end
|
||||||
|
|
||||||
# @api private
|
# @api private
|
||||||
def self.unknown_tag_in_liquid_tag(tag, parse_context)
|
def self.unknown_tag_in_liquid_tag(end_tag_name, end_tag_markup)
|
||||||
Block.raise_unknown_tag(tag, 'liquid', '%}', parse_context)
|
yield end_tag_name, end_tag_markup
|
||||||
|
ensure
|
||||||
|
Usage.increment("liquid_tag_contains_outer_tag") unless $ERROR_INFO.is_a?(SyntaxError)
|
||||||
end
|
end
|
||||||
|
|
||||||
# @api private
|
private def parse_liquid_tag(markup, parse_context, &block)
|
||||||
def self.raise_missing_tag_terminator(token, parse_context)
|
|
||||||
raise SyntaxError, parse_context.locale.t("errors.syntax.tag_termination", token: token, tag_end: TagEnd.inspect)
|
|
||||||
end
|
|
||||||
|
|
||||||
# @api private
|
|
||||||
def self.raise_missing_variable_terminator(token, parse_context)
|
|
||||||
raise SyntaxError, parse_context.locale.t("errors.syntax.variable_termination", token: token, tag_end: VariableEnd.inspect)
|
|
||||||
end
|
|
||||||
|
|
||||||
# @api private
|
|
||||||
def self.render_node(context, output, node)
|
|
||||||
node.render_to_output_buffer(context, output)
|
|
||||||
rescue => exc
|
|
||||||
blank_tag = !node.instance_of?(Variable) && node.blank?
|
|
||||||
rescue_render_node(context, output, node.line_number, exc, blank_tag)
|
|
||||||
end
|
|
||||||
|
|
||||||
# @api private
|
|
||||||
def self.rescue_render_node(context, output, line_number, exc, blank_tag)
|
|
||||||
case exc
|
|
||||||
when MemoryError
|
|
||||||
raise
|
|
||||||
when UndefinedVariable, UndefinedDropMethod, UndefinedFilter
|
|
||||||
context.handle_error(exc, line_number)
|
|
||||||
else
|
|
||||||
error_message = context.handle_error(exc, line_number)
|
|
||||||
unless blank_tag # conditional for backwards compatibility
|
|
||||||
output << error_message
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
private def parse_liquid_tag(markup, parse_context)
|
|
||||||
liquid_tag_tokenizer = Tokenizer.new(markup, line_number: parse_context.line_number, for_liquid_tag: true)
|
liquid_tag_tokenizer = Tokenizer.new(markup, line_number: parse_context.line_number, for_liquid_tag: true)
|
||||||
parse_for_liquid_tag(liquid_tag_tokenizer, parse_context) do |end_tag_name, _end_tag_markup|
|
parse_for_liquid_tag(liquid_tag_tokenizer, parse_context) do |end_tag_name, end_tag_markup|
|
||||||
if end_tag_name
|
next unless end_tag_name
|
||||||
BlockBody.unknown_tag_in_liquid_tag(end_tag_name, parse_context)
|
self.class.unknown_tag_in_liquid_tag(end_tag_name, end_tag_markup, &block)
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
private def parse_for_document(tokenizer, parse_context)
|
private def parse_for_document(tokenizer, parse_context, &block)
|
||||||
while (token = tokenizer.shift)
|
while (token = tokenizer.shift)
|
||||||
next if token.empty?
|
next if token.empty?
|
||||||
case
|
case
|
||||||
when token.start_with?(TAGSTART)
|
when token.start_with?(TAGSTART)
|
||||||
whitespace_handler(token, parse_context)
|
whitespace_handler(token, parse_context)
|
||||||
unless token =~ FullToken
|
unless token =~ FullToken
|
||||||
BlockBody.raise_missing_tag_terminator(token, parse_context)
|
raise_missing_tag_terminator(token, parse_context)
|
||||||
end
|
end
|
||||||
tag_name = Regexp.last_match(2)
|
tag_name = Regexp.last_match(2)
|
||||||
markup = Regexp.last_match(4)
|
markup = Regexp.last_match(4)
|
||||||
@@ -119,7 +87,7 @@ module Liquid
|
|||||||
end
|
end
|
||||||
|
|
||||||
if tag_name == 'liquid'
|
if tag_name == 'liquid'
|
||||||
parse_liquid_tag(markup, parse_context)
|
parse_liquid_tag(markup, parse_context, &block)
|
||||||
next
|
next
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -153,11 +121,7 @@ module Liquid
|
|||||||
if token[2] == WhitespaceControl
|
if token[2] == WhitespaceControl
|
||||||
previous_token = @nodelist.last
|
previous_token = @nodelist.last
|
||||||
if previous_token.is_a?(String)
|
if previous_token.is_a?(String)
|
||||||
first_byte = previous_token.getbyte(0)
|
|
||||||
previous_token.rstrip!
|
previous_token.rstrip!
|
||||||
if previous_token.empty? && parse_context[:bug_compatible_whitespace_trimming] && first_byte
|
|
||||||
previous_token << first_byte
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
parse_context.trim_whitespace = (token[-3] == WhitespaceControl)
|
parse_context.trim_whitespace = (token[-3] == WhitespaceControl)
|
||||||
@@ -167,47 +131,38 @@ module Liquid
|
|||||||
@blank
|
@blank
|
||||||
end
|
end
|
||||||
|
|
||||||
# Remove blank strings in the block body for a control flow tag (e.g. `if`, `for`, `case`, `unless`)
|
|
||||||
# with a blank body.
|
|
||||||
#
|
|
||||||
# For example, in a conditional assignment like the following
|
|
||||||
#
|
|
||||||
# ```
|
|
||||||
# {% if size > max_size %}
|
|
||||||
# {% assign size = max_size %}
|
|
||||||
# {% endif %}
|
|
||||||
# ```
|
|
||||||
#
|
|
||||||
# we assume the intention wasn't to output the blank spaces in the `if` tag's block body, so this method
|
|
||||||
# will remove them to reduce the render output size.
|
|
||||||
#
|
|
||||||
# Note that it is now preferred to use the `liquid` tag for this use case.
|
|
||||||
def remove_blank_strings
|
|
||||||
raise "remove_blank_strings only support being called on a blank block body" unless @blank
|
|
||||||
@nodelist.reject! { |node| node.instance_of?(String) }
|
|
||||||
end
|
|
||||||
|
|
||||||
def render(context)
|
def render(context)
|
||||||
render_to_output_buffer(context, +'')
|
render_to_output_buffer(context, +'')
|
||||||
end
|
end
|
||||||
|
|
||||||
def render_to_output_buffer(context, output)
|
def render_to_output_buffer(context, output)
|
||||||
context.resource_limits.increment_render_score(@nodelist.length)
|
context.resource_limits.render_score += @nodelist.length
|
||||||
|
|
||||||
idx = 0
|
idx = 0
|
||||||
while (node = @nodelist[idx])
|
while (node = @nodelist[idx])
|
||||||
if node.instance_of?(String)
|
previous_output_size = output.bytesize
|
||||||
|
|
||||||
|
case node
|
||||||
|
when String
|
||||||
output << node
|
output << node
|
||||||
else
|
when Variable
|
||||||
render_node(context, output, node)
|
render_node(context, output, node)
|
||||||
|
when Block
|
||||||
|
render_node(context, node.blank? ? +'' : output, node)
|
||||||
|
break if context.interrupt? # might have happened in a for-block
|
||||||
|
when Continue, Break
|
||||||
# If we get an Interrupt that means the block must stop processing. An
|
# If we get an Interrupt that means the block must stop processing. An
|
||||||
# Interrupt is any command that stops block execution such as {% break %}
|
# Interrupt is any command that stops block execution such as {% break %}
|
||||||
# or {% continue %}. These tags may also occur through Block or Include tags.
|
# or {% continue %}
|
||||||
break if context.interrupt? # might have happened in a for-block
|
context.push_interrupt(node.interrupt)
|
||||||
|
break
|
||||||
|
else # Other non-Block tags
|
||||||
|
render_node(context, output, node)
|
||||||
|
break if context.interrupt? # might have happened through an include
|
||||||
end
|
end
|
||||||
idx += 1
|
idx += 1
|
||||||
|
|
||||||
context.resource_limits.increment_write_score(output)
|
raise_if_resource_limits_reached(context, output.bytesize - previous_output_size)
|
||||||
end
|
end
|
||||||
|
|
||||||
output
|
output
|
||||||
@@ -216,7 +171,29 @@ module Liquid
|
|||||||
private
|
private
|
||||||
|
|
||||||
def render_node(context, output, node)
|
def render_node(context, output, node)
|
||||||
BlockBody.render_node(context, output, node)
|
if node.disabled?(context)
|
||||||
|
output << node.disabled_error_message
|
||||||
|
return
|
||||||
|
end
|
||||||
|
disable_tags(context, node.disabled_tags) do
|
||||||
|
node.render_to_output_buffer(context, output)
|
||||||
|
end
|
||||||
|
rescue UndefinedVariable, UndefinedDropMethod, UndefinedFilter => e
|
||||||
|
context.handle_error(e, node.line_number)
|
||||||
|
rescue ::StandardError => e
|
||||||
|
line_number = node.is_a?(String) ? nil : node.line_number
|
||||||
|
output << context.handle_error(e, line_number)
|
||||||
|
end
|
||||||
|
|
||||||
|
def disable_tags(context, tags, &block)
|
||||||
|
return yield if tags.empty?
|
||||||
|
context.registers[:disabled_tags].disable(tags, &block)
|
||||||
|
end
|
||||||
|
|
||||||
|
def raise_if_resource_limits_reached(context, length)
|
||||||
|
context.resource_limits.render_length += length
|
||||||
|
return unless context.resource_limits.reached?
|
||||||
|
raise MemoryError, "Memory limits exceeded"
|
||||||
end
|
end
|
||||||
|
|
||||||
def create_variable(token, parse_context)
|
def create_variable(token, parse_context)
|
||||||
@@ -224,17 +201,15 @@ module Liquid
|
|||||||
markup = content.first
|
markup = content.first
|
||||||
return Variable.new(markup, parse_context)
|
return Variable.new(markup, parse_context)
|
||||||
end
|
end
|
||||||
BlockBody.raise_missing_variable_terminator(token, parse_context)
|
raise_missing_variable_terminator(token, parse_context)
|
||||||
end
|
end
|
||||||
|
|
||||||
# @deprecated Use {.raise_missing_tag_terminator} instead
|
|
||||||
def raise_missing_tag_terminator(token, parse_context)
|
def raise_missing_tag_terminator(token, parse_context)
|
||||||
BlockBody.raise_missing_tag_terminator(token, parse_context)
|
raise SyntaxError, parse_context.locale.t("errors.syntax.tag_termination", token: token, tag_end: TagEnd.inspect)
|
||||||
end
|
end
|
||||||
|
|
||||||
# @deprecated Use {.raise_missing_variable_terminator} instead
|
|
||||||
def raise_missing_variable_terminator(token, parse_context)
|
def raise_missing_variable_terminator(token, parse_context)
|
||||||
BlockBody.raise_missing_variable_terminator(token, parse_context)
|
raise SyntaxError, parse_context.locale.t("errors.syntax.variable_termination", token: token, tag_end: VariableEnd.inspect)
|
||||||
end
|
end
|
||||||
|
|
||||||
def registered_tags
|
def registered_tags
|
||||||
|
|||||||
@@ -27,28 +27,10 @@ module Liquid
|
|||||||
end,
|
end,
|
||||||
}
|
}
|
||||||
|
|
||||||
class MethodLiteral
|
|
||||||
attr_reader :method_name, :to_s
|
|
||||||
|
|
||||||
def initialize(method_name, to_s)
|
|
||||||
@method_name = method_name
|
|
||||||
@to_s = to_s
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
@@method_literals = {
|
|
||||||
'blank' => MethodLiteral.new(:blank?, '').freeze,
|
|
||||||
'empty' => MethodLiteral.new(:empty?, '').freeze,
|
|
||||||
}
|
|
||||||
|
|
||||||
def self.operators
|
def self.operators
|
||||||
@@operators
|
@@operators
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.parse_expression(markup)
|
|
||||||
@@method_literals[markup] || Expression.parse(markup)
|
|
||||||
end
|
|
||||||
|
|
||||||
attr_reader :attachment, :child_condition
|
attr_reader :attachment, :child_condition
|
||||||
attr_accessor :left, :operator, :right
|
attr_accessor :left, :operator, :right
|
||||||
|
|
||||||
@@ -109,7 +91,7 @@ module Liquid
|
|||||||
private
|
private
|
||||||
|
|
||||||
def equal_variables(left, right)
|
def equal_variables(left, right)
|
||||||
if left.is_a?(MethodLiteral)
|
if left.is_a?(Liquid::Expression::MethodLiteral)
|
||||||
if right.respond_to?(left.method_name)
|
if right.respond_to?(left.method_name)
|
||||||
return right.send(left.method_name)
|
return right.send(left.method_name)
|
||||||
else
|
else
|
||||||
@@ -117,7 +99,7 @@ module Liquid
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if right.is_a?(MethodLiteral)
|
if right.is_a?(Liquid::Expression::MethodLiteral)
|
||||||
if left.respond_to?(right.method_name)
|
if left.respond_to?(right.method_name)
|
||||||
return left.send(right.method_name)
|
return left.send(right.method_name)
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -34,7 +34,6 @@ module Liquid
|
|||||||
@strict_variables = false
|
@strict_variables = false
|
||||||
@resource_limits = resource_limits || ResourceLimits.new(Template.default_resource_limits)
|
@resource_limits = resource_limits || ResourceLimits.new(Template.default_resource_limits)
|
||||||
@base_scope_depth = 0
|
@base_scope_depth = 0
|
||||||
squash_instance_assigns_with_environments
|
|
||||||
|
|
||||||
self.exception_renderer = Template.default_exception_renderer
|
self.exception_renderer = Template.default_exception_renderer
|
||||||
if rethrow_errors
|
if rethrow_errors
|
||||||
@@ -44,7 +43,6 @@ module Liquid
|
|||||||
@interrupts = []
|
@interrupts = []
|
||||||
@filters = []
|
@filters = []
|
||||||
@global_filter = nil
|
@global_filter = nil
|
||||||
@disabled_tags = {}
|
|
||||||
end
|
end
|
||||||
# rubocop:enable Metrics/ParameterLists
|
# rubocop:enable Metrics/ParameterLists
|
||||||
|
|
||||||
@@ -145,7 +143,6 @@ module Liquid
|
|||||||
subcontext.strainer = nil
|
subcontext.strainer = nil
|
||||||
subcontext.errors = errors
|
subcontext.errors = errors
|
||||||
subcontext.warnings = warnings
|
subcontext.warnings = warnings
|
||||||
subcontext.disabled_tags = @disabled_tags
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -210,24 +207,9 @@ module Liquid
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def with_disabled_tags(tag_names)
|
|
||||||
tag_names.each do |name|
|
|
||||||
@disabled_tags[name] = @disabled_tags.fetch(name, 0) + 1
|
|
||||||
end
|
|
||||||
yield
|
|
||||||
ensure
|
|
||||||
tag_names.each do |name|
|
|
||||||
@disabled_tags[name] -= 1
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def tag_disabled?(tag_name)
|
|
||||||
@disabled_tags.fetch(tag_name, 0) > 0
|
|
||||||
end
|
|
||||||
|
|
||||||
protected
|
protected
|
||||||
|
|
||||||
attr_writer :base_scope_depth, :warnings, :errors, :strainer, :filters, :disabled_tags
|
attr_writer :base_scope_depth, :warnings, :errors, :strainer, :filters
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
@@ -263,16 +245,5 @@ module Liquid
|
|||||||
rescue Liquid::InternalError => exc
|
rescue Liquid::InternalError => exc
|
||||||
exc
|
exc
|
||||||
end
|
end
|
||||||
|
|
||||||
def squash_instance_assigns_with_environments
|
|
||||||
@scopes.last.each_key do |k|
|
|
||||||
@environments.each do |env|
|
|
||||||
if env.key?(k)
|
|
||||||
scopes.last[k] = lookup_and_evaluate(env, k)
|
|
||||||
break
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end # squash_instance_assigns_with_environments
|
|
||||||
end # Context
|
end # Context
|
||||||
end # Liquid
|
end # Liquid
|
||||||
|
|||||||
@@ -1,33 +1,23 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module Liquid
|
module Liquid
|
||||||
class Document
|
class Document < BlockBody
|
||||||
def self.parse(tokens, parse_context)
|
def self.parse(tokens, parse_context)
|
||||||
doc = new(parse_context)
|
doc = new
|
||||||
doc.parse(tokens, parse_context)
|
doc.parse(tokens, parse_context)
|
||||||
doc
|
doc
|
||||||
end
|
end
|
||||||
|
|
||||||
attr_reader :parse_context, :body
|
def parse(tokens, parse_context)
|
||||||
|
super do |end_tag_name, _end_tag_params|
|
||||||
def initialize(parse_context)
|
unknown_tag(end_tag_name, parse_context) if end_tag_name
|
||||||
@parse_context = parse_context
|
|
||||||
@body = new_body
|
|
||||||
end
|
|
||||||
|
|
||||||
def nodelist
|
|
||||||
@body.nodelist
|
|
||||||
end
|
|
||||||
|
|
||||||
def parse(tokenizer, parse_context)
|
|
||||||
while parse_body(tokenizer)
|
|
||||||
end
|
end
|
||||||
rescue SyntaxError => e
|
rescue SyntaxError => e
|
||||||
e.line_number ||= parse_context.line_number
|
e.line_number ||= parse_context.line_number
|
||||||
raise
|
raise
|
||||||
end
|
end
|
||||||
|
|
||||||
def unknown_tag(tag, _markup, _tokenizer)
|
def unknown_tag(tag, parse_context)
|
||||||
case tag
|
case tag
|
||||||
when 'else', 'end'
|
when 'else', 'end'
|
||||||
raise SyntaxError, parse_context.locale.t("errors.syntax.unexpected_outer_tag", tag: tag)
|
raise SyntaxError, parse_context.locale.t("errors.syntax.unexpected_outer_tag", tag: tag)
|
||||||
@@ -35,30 +25,5 @@ module Liquid
|
|||||||
raise SyntaxError, parse_context.locale.t("errors.syntax.unknown_tag", tag: tag)
|
raise SyntaxError, parse_context.locale.t("errors.syntax.unknown_tag", tag: tag)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def render_to_output_buffer(context, output)
|
|
||||||
@body.render_to_output_buffer(context, output)
|
|
||||||
end
|
|
||||||
|
|
||||||
def render(context)
|
|
||||||
@body.render(context)
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def new_body
|
|
||||||
parse_context.new_block_body
|
|
||||||
end
|
|
||||||
|
|
||||||
def parse_body(tokenizer)
|
|
||||||
@body.parse(tokenizer, parse_context) do |unknown_tag_name, unknown_tag_markup|
|
|
||||||
if unknown_tag_name
|
|
||||||
unknown_tag(unknown_tag_name, unknown_tag_markup, tokenizer)
|
|
||||||
true
|
|
||||||
else
|
|
||||||
false
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -53,6 +53,5 @@ module Liquid
|
|||||||
UndefinedDropMethod = Class.new(Error)
|
UndefinedDropMethod = Class.new(Error)
|
||||||
UndefinedFilter = Class.new(Error)
|
UndefinedFilter = Class.new(Error)
|
||||||
MethodOverrideError = Class.new(Error)
|
MethodOverrideError = Class.new(Error)
|
||||||
DisabledError = Class.new(Error)
|
|
||||||
InternalError = Class.new(Error)
|
InternalError = Class.new(Error)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -2,12 +2,25 @@
|
|||||||
|
|
||||||
module Liquid
|
module Liquid
|
||||||
class Expression
|
class Expression
|
||||||
|
class MethodLiteral
|
||||||
|
attr_reader :method_name, :to_s
|
||||||
|
|
||||||
|
def initialize(method_name, to_s)
|
||||||
|
@method_name = method_name
|
||||||
|
@to_s = to_s
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_liquid
|
||||||
|
to_s
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
LITERALS = {
|
LITERALS = {
|
||||||
nil => nil, 'nil' => nil, 'null' => nil, '' => nil,
|
nil => nil, 'nil' => nil, 'null' => nil, '' => nil,
|
||||||
'true' => true,
|
'true' => true,
|
||||||
'false' => false,
|
'false' => false,
|
||||||
'blank' => '',
|
'blank' => MethodLiteral.new(:blank?, '').freeze,
|
||||||
'empty' => ''
|
'empty' => MethodLiteral.new(:empty?, '').freeze
|
||||||
}.freeze
|
}.freeze
|
||||||
|
|
||||||
SINGLE_QUOTED_STRING = /\A'(.*)'\z/m
|
SINGLE_QUOTED_STRING = /\A'(.*)'\z/m
|
||||||
|
|||||||
@@ -19,10 +19,6 @@ module Liquid
|
|||||||
@options[option_key]
|
@options[option_key]
|
||||||
end
|
end
|
||||||
|
|
||||||
def new_block_body
|
|
||||||
Liquid::BlockBody.new
|
|
||||||
end
|
|
||||||
|
|
||||||
def partial=(value)
|
def partial=(value)
|
||||||
@partial = value
|
@partial = value
|
||||||
@options = value ? partial_options : @template_options
|
@options = value ? partial_options : @template_options
|
||||||
|
|||||||
@@ -1,21 +1,25 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module Liquid
|
module Liquid
|
||||||
module BlockBodyProfilingHook
|
class BlockBody
|
||||||
def render_node(context, output, node)
|
def render_node_with_profiling(context, output, node)
|
||||||
Profiler.profile_node_render(node) do
|
Profiler.profile_node_render(node) do
|
||||||
super
|
render_node_without_profiling(context, output, node)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
|
||||||
BlockBody.prepend(BlockBodyProfilingHook)
|
|
||||||
|
|
||||||
module IncludeProfilingHook
|
alias_method :render_node_without_profiling, :render_node
|
||||||
def render_to_output_buffer(context, output)
|
alias_method :render_node, :render_node_with_profiling
|
||||||
|
end
|
||||||
|
|
||||||
|
class Include < Tag
|
||||||
|
def render_to_output_buffer_with_profiling(context, output)
|
||||||
Profiler.profile_children(context.evaluate(@template_name_expr).to_s) do
|
Profiler.profile_children(context.evaluate(@template_name_expr).to_s) do
|
||||||
super
|
render_to_output_buffer_without_profiling(context, output)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
alias_method :render_to_output_buffer_without_profiling, :render_to_output_buffer
|
||||||
|
alias_method :render_to_output_buffer, :render_to_output_buffer_with_profiling
|
||||||
end
|
end
|
||||||
Include.prepend(IncludeProfilingHook)
|
|
||||||
end
|
end
|
||||||
|
|||||||
32
lib/liquid/registers/disabled_tags.rb
Normal file
32
lib/liquid/registers/disabled_tags.rb
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
module Liquid
|
||||||
|
class DisabledTags < Register
|
||||||
|
def initialize
|
||||||
|
@disabled_tags = {}
|
||||||
|
end
|
||||||
|
|
||||||
|
def disabled?(tag)
|
||||||
|
@disabled_tags.key?(tag) && @disabled_tags[tag] > 0
|
||||||
|
end
|
||||||
|
|
||||||
|
def disable(tags)
|
||||||
|
tags.each(&method(:increment))
|
||||||
|
yield
|
||||||
|
ensure
|
||||||
|
tags.each(&method(:decrement))
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def increment(tag)
|
||||||
|
@disabled_tags[tag] ||= 0
|
||||||
|
@disabled_tags[tag] += 1
|
||||||
|
end
|
||||||
|
|
||||||
|
def decrement(tag)
|
||||||
|
@disabled_tags[tag] -= 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
Template.add_register(:disabled_tags, DisabledTags.new)
|
||||||
|
end
|
||||||
@@ -2,8 +2,8 @@
|
|||||||
|
|
||||||
module Liquid
|
module Liquid
|
||||||
class ResourceLimits
|
class ResourceLimits
|
||||||
attr_accessor :render_length_limit, :render_score_limit, :assign_score_limit
|
attr_accessor :render_length, :render_score, :assign_score,
|
||||||
attr_reader :render_score, :assign_score
|
:render_length_limit, :render_score_limit, :assign_score_limit
|
||||||
|
|
||||||
def initialize(limits)
|
def initialize(limits)
|
||||||
@render_length_limit = limits[:render_length_limit]
|
@render_length_limit = limits[:render_length_limit]
|
||||||
@@ -12,51 +12,14 @@ module Liquid
|
|||||||
reset
|
reset
|
||||||
end
|
end
|
||||||
|
|
||||||
def increment_render_score(amount)
|
|
||||||
@render_score += amount
|
|
||||||
raise_limits_reached if @render_score_limit && @render_score > @render_score_limit
|
|
||||||
end
|
|
||||||
|
|
||||||
def increment_assign_score(amount)
|
|
||||||
@assign_score += amount
|
|
||||||
raise_limits_reached if @assign_score_limit && @assign_score > @assign_score_limit
|
|
||||||
end
|
|
||||||
|
|
||||||
# update either render_length or assign_score based on whether or not the writes are captured
|
|
||||||
def increment_write_score(output)
|
|
||||||
if (last_captured = @last_capture_length)
|
|
||||||
captured = output.bytesize
|
|
||||||
increment = captured - last_captured
|
|
||||||
@last_capture_length = captured
|
|
||||||
increment_assign_score(increment)
|
|
||||||
elsif @render_length_limit && output.bytesize > @render_length_limit
|
|
||||||
raise_limits_reached
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def raise_limits_reached
|
|
||||||
@reached_limit = true
|
|
||||||
raise MemoryError, "Memory limits exceeded"
|
|
||||||
end
|
|
||||||
|
|
||||||
def reached?
|
def reached?
|
||||||
@reached_limit
|
(@render_length_limit && @render_length > @render_length_limit) ||
|
||||||
|
(@render_score_limit && @render_score > @render_score_limit) ||
|
||||||
|
(@assign_score_limit && @assign_score > @assign_score_limit)
|
||||||
end
|
end
|
||||||
|
|
||||||
def reset
|
def reset
|
||||||
@reached_limit = false
|
@render_length = @render_score = @assign_score = 0
|
||||||
@last_capture_length = nil
|
|
||||||
@render_score = @assign_score = 0
|
|
||||||
end
|
|
||||||
|
|
||||||
def with_capture
|
|
||||||
old_capture_length = @last_capture_length
|
|
||||||
begin
|
|
||||||
@last_capture_length = 0
|
|
||||||
yield
|
|
||||||
ensure
|
|
||||||
@last_capture_length = old_capture_length
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -13,13 +13,15 @@ module Liquid
|
|||||||
tag
|
tag
|
||||||
end
|
end
|
||||||
|
|
||||||
def disable_tags(*tag_names)
|
def disable_tags(*tags)
|
||||||
@disabled_tags ||= []
|
disabled_tags.push(*tags)
|
||||||
@disabled_tags.concat(tag_names)
|
|
||||||
prepend(Disabler)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
private :new
|
private :new
|
||||||
|
|
||||||
|
def disabled_tags
|
||||||
|
@disabled_tags ||= []
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def initialize(tag_name, markup, parse_context)
|
def initialize(tag_name, markup, parse_context)
|
||||||
@@ -44,6 +46,14 @@ module Liquid
|
|||||||
''
|
''
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def disabled?(context)
|
||||||
|
context.registers[:disabled_tags].disabled?(tag_name)
|
||||||
|
end
|
||||||
|
|
||||||
|
def disabled_error_message
|
||||||
|
"#{tag_name} #{options[:locale].t('errors.disabled.tag')}"
|
||||||
|
end
|
||||||
|
|
||||||
# For backwards compatibility with custom tags. In a future release, the semantics
|
# For backwards compatibility with custom tags. In a future release, the semantics
|
||||||
# of the `render_to_output_buffer` method will become the default and the `render`
|
# of the `render_to_output_buffer` method will become the default and the `render`
|
||||||
# method will be removed.
|
# method will be removed.
|
||||||
@@ -55,5 +65,9 @@ module Liquid
|
|||||||
def blank?
|
def blank?
|
||||||
false
|
false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def disabled_tags
|
||||||
|
self.class.disabled_tags
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,22 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
module Liquid
|
|
||||||
class Tag
|
|
||||||
module Disableable
|
|
||||||
def render_to_output_buffer(context, output)
|
|
||||||
if context.tag_disabled?(tag_name)
|
|
||||||
output << disabled_error(context)
|
|
||||||
return
|
|
||||||
end
|
|
||||||
super
|
|
||||||
end
|
|
||||||
|
|
||||||
def disabled_error(context)
|
|
||||||
# raise then rescue the exception so that the Context#exception_renderer can re-raise it
|
|
||||||
raise DisabledError, "#{tag_name} #{parse_context[:locale].t('errors.disabled.tag')}"
|
|
||||||
rescue DisabledError => exc
|
|
||||||
context.handle_error(exc, line_number)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
module Liquid
|
|
||||||
class Tag
|
|
||||||
module Disabler
|
|
||||||
module ClassMethods
|
|
||||||
attr_reader :disabled_tags
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.prepended(base)
|
|
||||||
base.extend(ClassMethods)
|
|
||||||
end
|
|
||||||
|
|
||||||
def render_to_output_buffer(context, output)
|
|
||||||
context.with_disabled_tags(self.class.disabled_tags) do
|
|
||||||
super
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -27,7 +27,7 @@ module Liquid
|
|||||||
def render_to_output_buffer(context, output)
|
def render_to_output_buffer(context, output)
|
||||||
val = @from.render(context)
|
val = @from.render(context)
|
||||||
context.scopes.last[@to] = val
|
context.scopes.last[@to] = val
|
||||||
context.resource_limits.increment_assign_score(assign_score_of(val))
|
context.resource_limits.assign_score += assign_score_of(val)
|
||||||
output
|
output
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -11,11 +11,8 @@ module Liquid
|
|||||||
# {% endfor %}
|
# {% endfor %}
|
||||||
#
|
#
|
||||||
class Break < Tag
|
class Break < Tag
|
||||||
INTERRUPT = BreakInterrupt.new.freeze
|
def interrupt
|
||||||
|
BreakInterrupt.new
|
||||||
def render_to_output_buffer(context, output)
|
|
||||||
context.push_interrupt(INTERRUPT)
|
|
||||||
output
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -25,10 +25,10 @@ module Liquid
|
|||||||
end
|
end
|
||||||
|
|
||||||
def render_to_output_buffer(context, output)
|
def render_to_output_buffer(context, output)
|
||||||
context.resource_limits.with_capture do
|
previous_output_size = output.bytesize
|
||||||
capture_output = render(context)
|
super
|
||||||
context.scopes.last[@to] = capture_output
|
context.scopes.last[@to] = output
|
||||||
end
|
context.resource_limits.assign_score += (output.bytesize - previous_output_size)
|
||||||
output
|
output
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -19,11 +19,8 @@ module Liquid
|
|||||||
end
|
end
|
||||||
|
|
||||||
def parse(tokens)
|
def parse(tokens)
|
||||||
body = new_body
|
body = BlockBody.new
|
||||||
body = @blocks.last.attachment while parse_body(body, tokens)
|
body = @blocks.last.attachment while parse_body(body, tokens)
|
||||||
if blank?
|
|
||||||
@blocks.each { |condition| condition.attachment.remove_blank_strings }
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def nodelist
|
def nodelist
|
||||||
@@ -59,7 +56,7 @@ module Liquid
|
|||||||
private
|
private
|
||||||
|
|
||||||
def record_when_condition(markup)
|
def record_when_condition(markup)
|
||||||
body = new_body
|
body = BlockBody.new
|
||||||
|
|
||||||
while markup
|
while markup
|
||||||
unless markup =~ WhenSyntax
|
unless markup =~ WhenSyntax
|
||||||
@@ -68,7 +65,7 @@ module Liquid
|
|||||||
|
|
||||||
markup = Regexp.last_match(2)
|
markup = Regexp.last_match(2)
|
||||||
|
|
||||||
block = Condition.new(@left, '==', Condition.parse_expression(Regexp.last_match(1)))
|
block = Condition.new(@left, '==', Expression.parse(Regexp.last_match(1)))
|
||||||
block.attach(body)
|
block.attach(body)
|
||||||
@blocks << block
|
@blocks << block
|
||||||
end
|
end
|
||||||
@@ -80,7 +77,7 @@ module Liquid
|
|||||||
end
|
end
|
||||||
|
|
||||||
block = ElseCondition.new
|
block = ElseCondition.new
|
||||||
block.attach(new_body)
|
block.attach(BlockBody.new)
|
||||||
@blocks << block
|
@blocks << block
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -11,11 +11,8 @@ module Liquid
|
|||||||
# {% endfor %}
|
# {% endfor %}
|
||||||
#
|
#
|
||||||
class Continue < Tag
|
class Continue < Tag
|
||||||
INTERRUPT = ContinueInterrupt.new.freeze
|
def interrupt
|
||||||
|
ContinueInterrupt.new
|
||||||
def render_to_output_buffer(context, output)
|
|
||||||
context.push_interrupt(INTERRUPT)
|
|
||||||
output
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -54,18 +54,13 @@ module Liquid
|
|||||||
super
|
super
|
||||||
@from = @limit = nil
|
@from = @limit = nil
|
||||||
parse_with_selected_parser(markup)
|
parse_with_selected_parser(markup)
|
||||||
@for_block = new_body
|
@for_block = BlockBody.new
|
||||||
@else_block = nil
|
@else_block = nil
|
||||||
end
|
end
|
||||||
|
|
||||||
def parse(tokens)
|
def parse(tokens)
|
||||||
if parse_body(@for_block, tokens)
|
return unless parse_body(@for_block, tokens)
|
||||||
parse_body(@else_block, tokens)
|
parse_body(@else_block, tokens)
|
||||||
end
|
|
||||||
if blank?
|
|
||||||
@for_block.remove_blank_strings
|
|
||||||
@else_block&.remove_blank_strings
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def nodelist
|
def nodelist
|
||||||
@@ -74,7 +69,7 @@ module Liquid
|
|||||||
|
|
||||||
def unknown_tag(tag, markup, tokens)
|
def unknown_tag(tag, markup, tokens)
|
||||||
return super unless tag == 'else'
|
return super unless tag == 'else'
|
||||||
@else_block = new_body
|
@else_block = BlockBody.new
|
||||||
end
|
end
|
||||||
|
|
||||||
def render_to_output_buffer(context, output)
|
def render_to_output_buffer(context, output)
|
||||||
|
|||||||
@@ -31,9 +31,6 @@ module Liquid
|
|||||||
def parse(tokens)
|
def parse(tokens)
|
||||||
while parse_body(@blocks.last.attachment, tokens)
|
while parse_body(@blocks.last.attachment, tokens)
|
||||||
end
|
end
|
||||||
if blank?
|
|
||||||
@blocks.each { |condition| condition.attachment.remove_blank_strings }
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def unknown_tag(tag, markup, tokens)
|
def unknown_tag(tag, markup, tokens)
|
||||||
@@ -64,25 +61,21 @@ module Liquid
|
|||||||
end
|
end
|
||||||
|
|
||||||
@blocks.push(block)
|
@blocks.push(block)
|
||||||
block.attach(new_body)
|
block.attach(BlockBody.new)
|
||||||
end
|
|
||||||
|
|
||||||
def parse_expression(markup)
|
|
||||||
Condition.parse_expression(markup)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def lax_parse(markup)
|
def lax_parse(markup)
|
||||||
expressions = markup.scan(ExpressionsAndOperators)
|
expressions = markup.scan(ExpressionsAndOperators)
|
||||||
raise SyntaxError, options[:locale].t("errors.syntax.if") unless expressions.pop =~ Syntax
|
raise SyntaxError, options[:locale].t("errors.syntax.if") unless expressions.pop =~ Syntax
|
||||||
|
|
||||||
condition = Condition.new(parse_expression(Regexp.last_match(1)), Regexp.last_match(2), parse_expression(Regexp.last_match(3)))
|
condition = Condition.new(Expression.parse(Regexp.last_match(1)), Regexp.last_match(2), Expression.parse(Regexp.last_match(3)))
|
||||||
|
|
||||||
until expressions.empty?
|
until expressions.empty?
|
||||||
operator = expressions.pop.to_s.strip
|
operator = expressions.pop.to_s.strip
|
||||||
|
|
||||||
raise SyntaxError, options[:locale].t("errors.syntax.if") unless expressions.pop.to_s =~ Syntax
|
raise SyntaxError, options[:locale].t("errors.syntax.if") unless expressions.pop.to_s =~ Syntax
|
||||||
|
|
||||||
new_condition = Condition.new(parse_expression(Regexp.last_match(1)), Regexp.last_match(2), parse_expression(Regexp.last_match(3)))
|
new_condition = Condition.new(Expression.parse(Regexp.last_match(1)), Regexp.last_match(2), Expression.parse(Regexp.last_match(3)))
|
||||||
raise SyntaxError, options[:locale].t("errors.syntax.if") unless BOOLEAN_OPERATORS.include?(operator)
|
raise SyntaxError, options[:locale].t("errors.syntax.if") unless BOOLEAN_OPERATORS.include?(operator)
|
||||||
new_condition.send(operator, condition)
|
new_condition.send(operator, condition)
|
||||||
condition = new_condition
|
condition = new_condition
|
||||||
@@ -110,9 +103,9 @@ module Liquid
|
|||||||
end
|
end
|
||||||
|
|
||||||
def parse_comparison(p)
|
def parse_comparison(p)
|
||||||
a = parse_expression(p.expression)
|
a = Expression.parse(p.expression)
|
||||||
if (op = p.consume?(:comparison))
|
if (op = p.consume?(:comparison))
|
||||||
b = parse_expression(p.expression)
|
b = Expression.parse(p.expression)
|
||||||
Condition.new(a, op, b)
|
Condition.new(a, op, b)
|
||||||
else
|
else
|
||||||
Condition.new(a)
|
Condition.new(a)
|
||||||
|
|||||||
@@ -16,8 +16,6 @@ module Liquid
|
|||||||
# {% include 'product' for products %}
|
# {% include 'product' for products %}
|
||||||
#
|
#
|
||||||
class Include < Tag
|
class Include < Tag
|
||||||
prepend Tag::Disableable
|
|
||||||
|
|
||||||
SYNTAX = /(#{QuotedFragment}+)(\s+(?:with|for)\s+(#{QuotedFragment}+))?(\s+(?:as)\s+(#{VariableSegment}+))?/o
|
SYNTAX = /(#{QuotedFragment}+)(\s+(?:with|for)\s+(#{QuotedFragment}+))?(\s+(?:as)\s+(#{VariableSegment}+))?/o
|
||||||
Syntax = SYNTAX
|
Syntax = SYNTAX
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ module Liquid
|
|||||||
#
|
#
|
||||||
class Template
|
class Template
|
||||||
attr_accessor :root
|
attr_accessor :root
|
||||||
attr_reader :resource_limits, :warnings
|
attr_reader :warnings
|
||||||
|
|
||||||
class TagRegistry
|
class TagRegistry
|
||||||
include Enumerable
|
include Enumerable
|
||||||
@@ -80,6 +80,14 @@ module Liquid
|
|||||||
tags[name.to_s] = klass
|
tags[name.to_s] = klass
|
||||||
end
|
end
|
||||||
|
|
||||||
|
attr_accessor :registers
|
||||||
|
Template.registers = {}
|
||||||
|
private :registers=
|
||||||
|
|
||||||
|
def add_register(name, klass)
|
||||||
|
registers[name.to_sym] = klass
|
||||||
|
end
|
||||||
|
|
||||||
# Pass a module with filter methods which should be available
|
# Pass a module with filter methods which should be available
|
||||||
# to all liquid views. Good for registering the standard library
|
# to all liquid views. Good for registering the standard library
|
||||||
def register_filter(mod)
|
def register_filter(mod)
|
||||||
@@ -98,11 +106,6 @@ module Liquid
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def initialize
|
|
||||||
@rethrow_errors = false
|
|
||||||
@resource_limits = ResourceLimits.new(Template.default_resource_limits)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Parse source code.
|
# Parse source code.
|
||||||
# Returns self for easy chaining
|
# Returns self for easy chaining
|
||||||
def parse(source, options = {})
|
def parse(source, options = {})
|
||||||
@@ -115,22 +118,6 @@ module Liquid
|
|||||||
self
|
self
|
||||||
end
|
end
|
||||||
|
|
||||||
def registers
|
|
||||||
@registers ||= {}
|
|
||||||
end
|
|
||||||
|
|
||||||
def assigns
|
|
||||||
@assigns ||= {}
|
|
||||||
end
|
|
||||||
|
|
||||||
def instance_assigns
|
|
||||||
@instance_assigns ||= {}
|
|
||||||
end
|
|
||||||
|
|
||||||
def errors
|
|
||||||
@errors ||= []
|
|
||||||
end
|
|
||||||
|
|
||||||
# Render takes a hash with local variables.
|
# Render takes a hash with local variables.
|
||||||
#
|
#
|
||||||
# if you use the same filters over and over again consider registering them globally
|
# if you use the same filters over and over again consider registering them globally
|
||||||
@@ -145,37 +132,18 @@ module Liquid
|
|||||||
# * <tt>registers</tt> : hash with register variables. Those can be accessed from
|
# * <tt>registers</tt> : hash with register variables. Those can be accessed from
|
||||||
# filters and tags and might be useful to integrate liquid more with its host application
|
# filters and tags and might be useful to integrate liquid more with its host application
|
||||||
#
|
#
|
||||||
def render(*args)
|
def render(assigns_or_context = nil, options = nil)
|
||||||
return '' if @root.nil?
|
return '' if @root.nil?
|
||||||
|
|
||||||
context = case args.first
|
context = coerce_context(assigns_or_context)
|
||||||
when Liquid::Context
|
|
||||||
c = args.shift
|
|
||||||
|
|
||||||
if @rethrow_errors
|
|
||||||
c.exception_renderer = ->(_e) { raise }
|
|
||||||
end
|
|
||||||
|
|
||||||
c
|
|
||||||
when Liquid::Drop
|
|
||||||
drop = args.shift
|
|
||||||
drop.context = Context.new([drop, assigns], instance_assigns, registers, @rethrow_errors, @resource_limits)
|
|
||||||
when Hash
|
|
||||||
Context.new([args.shift, assigns], instance_assigns, registers, @rethrow_errors, @resource_limits)
|
|
||||||
when nil
|
|
||||||
Context.new(assigns, instance_assigns, registers, @rethrow_errors, @resource_limits)
|
|
||||||
else
|
|
||||||
raise ArgumentError, "Expected Hash or Liquid::Context as parameter"
|
|
||||||
end
|
|
||||||
|
|
||||||
output = nil
|
output = nil
|
||||||
|
|
||||||
context_register = context.registers.is_a?(StaticRegisters) ? context.registers.static : context.registers
|
context_register = context.registers.is_a?(StaticRegisters) ? context.registers.static : context.registers
|
||||||
|
|
||||||
case args.last
|
case options
|
||||||
when Hash
|
when Hash
|
||||||
options = args.pop
|
output = options[:output] if options[:output]
|
||||||
output = options[:output] if options[:output]
|
|
||||||
|
|
||||||
options[:registers]&.each do |key, register|
|
options[:registers]&.each do |key, register|
|
||||||
context_register[key] = register
|
context_register[key] = register
|
||||||
@@ -183,7 +151,11 @@ module Liquid
|
|||||||
|
|
||||||
apply_options_to_context(context, options)
|
apply_options_to_context(context, options)
|
||||||
when Module, Array
|
when Module, Array
|
||||||
context.add_filters(args.pop)
|
context.add_filters(options)
|
||||||
|
end
|
||||||
|
|
||||||
|
Template.registers.each do |key, register|
|
||||||
|
context_register[key] = register unless context_register.key?(key)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Retrying a render resets resource usage
|
# Retrying a render resets resource usage
|
||||||
@@ -197,14 +169,15 @@ module Liquid
|
|||||||
end
|
end
|
||||||
rescue Liquid::MemoryError => e
|
rescue Liquid::MemoryError => e
|
||||||
context.handle_error(e)
|
context.handle_error(e)
|
||||||
ensure
|
|
||||||
@errors = context.errors
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def render!(*args)
|
def render!(assigns_or_context = nil, options = nil)
|
||||||
@rethrow_errors = true
|
context = coerce_context(assigns_or_context)
|
||||||
render(*args)
|
# rethrow errors
|
||||||
|
context.exception_renderer = ->(_e) { raise }
|
||||||
|
|
||||||
|
render(context, options)
|
||||||
end
|
end
|
||||||
|
|
||||||
def render_to_output_buffer(context, output)
|
def render_to_output_buffer(context, output)
|
||||||
@@ -213,6 +186,22 @@ module Liquid
|
|||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
def coerce_context(assigns_or_context)
|
||||||
|
case assigns_or_context
|
||||||
|
when Liquid::Context
|
||||||
|
assigns_or_context
|
||||||
|
when Liquid::Drop
|
||||||
|
drop = assigns_or_context
|
||||||
|
drop.context = Context.build(environments: [drop])
|
||||||
|
when Hash
|
||||||
|
Context.build(environments: [assigns_or_context])
|
||||||
|
when nil
|
||||||
|
Context.build
|
||||||
|
else
|
||||||
|
raise ArgumentError, "Expected Hash or Liquid::Context as parameter"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def tokenize(source)
|
def tokenize(source)
|
||||||
Tokenizer.new(source, @line_numbers)
|
Tokenizer.new(source, @line_numbers)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -73,26 +73,29 @@ class ThemeRunner
|
|||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def compile_and_render(template, layout, assigns, page_template, template_file)
|
def compile_and_render(template, layout, assigns, page_template)
|
||||||
compiled_test = compile_test(template, layout, assigns, page_template, template_file)
|
assigns = assigns.merge(
|
||||||
|
'page_title' => 'Page title',
|
||||||
|
'template' => page_template,
|
||||||
|
)
|
||||||
|
compiled_test = compile_test(template, layout, assigns)
|
||||||
assigns['content_for_layout'] = compiled_test[:tmpl].render!(assigns)
|
assigns['content_for_layout'] = compiled_test[:tmpl].render!(assigns)
|
||||||
compiled_test[:layout].render!(assigns) if layout
|
compiled_test[:layout].render!(assigns) if layout
|
||||||
end
|
end
|
||||||
|
|
||||||
def compile_all_tests
|
def compile_all_tests
|
||||||
@compiled_tests = []
|
@compiled_tests = []
|
||||||
each_test do |liquid, layout, assigns, page_template, template_name|
|
each_test do |liquid, layout, assigns, _page_template, _template_name|
|
||||||
@compiled_tests << compile_test(liquid, layout, assigns, page_template, template_name)
|
@compiled_tests << compile_test(liquid, layout, assigns)
|
||||||
end
|
end
|
||||||
@compiled_tests
|
@compiled_tests
|
||||||
end
|
end
|
||||||
|
|
||||||
def compile_test(template, layout, assigns, page_template, template_file)
|
def compile_test(template, layout, assigns)
|
||||||
tmpl = init_template(page_template, template_file)
|
parsed_template = Liquid::Template.parse(template).dup
|
||||||
parsed_template = tmpl.parse(template).dup
|
|
||||||
|
|
||||||
if layout
|
if layout
|
||||||
parsed_layout = tmpl.parse(layout)
|
parsed_layout = Liquid::Template.parse(layout)
|
||||||
{ tmpl: parsed_template, assigns: assigns, layout: parsed_layout }
|
{ tmpl: parsed_template, assigns: assigns, layout: parsed_layout }
|
||||||
else
|
else
|
||||||
{ tmpl: parsed_template, assigns: assigns }
|
{ tmpl: parsed_template, assigns: assigns }
|
||||||
@@ -107,16 +110,7 @@ class ThemeRunner
|
|||||||
@tests.each do |test_hash|
|
@tests.each do |test_hash|
|
||||||
# Compute page_template outside of profiler run, uninteresting to profiler
|
# Compute page_template outside of profiler run, uninteresting to profiler
|
||||||
page_template = File.basename(test_hash[:template_name], File.extname(test_hash[:template_name]))
|
page_template = File.basename(test_hash[:template_name], File.extname(test_hash[:template_name]))
|
||||||
yield(test_hash[:liquid], test_hash[:layout], assigns, page_template, test_hash[:template_name])
|
yield(test_hash[:liquid], test_hash[:layout], assigns, page_template)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# set up a new Liquid::Template object for use in `compile_and_render` and `compile_test`
|
|
||||||
def init_template(page_template, template_file)
|
|
||||||
tmpl = Liquid::Template.new
|
|
||||||
tmpl.assigns['page_title'] = 'Page title'
|
|
||||||
tmpl.assigns['template'] = page_template
|
|
||||||
tmpl.registers[:file_system] = ThemeRunner::FileSystem.new(File.dirname(template_file))
|
|
||||||
tmpl
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -11,48 +11,4 @@ class BlockTest < Minitest::Test
|
|||||||
end
|
end
|
||||||
assert_equal(exc.message, "Liquid syntax error: 'endunless' is not a valid delimiter for if tags. use endif")
|
assert_equal(exc.message, "Liquid syntax error: 'endunless' is not a valid delimiter for if tags. use endif")
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_with_custom_tag
|
|
||||||
with_custom_tag('testtag', Block) do
|
|
||||||
assert Liquid::Template.parse("{% testtag %} {% endtesttag %}")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_custom_block_tags_have_a_default_render_to_output_buffer_method_for_backwards_compatibility
|
|
||||||
klass1 = Class.new(Block) do
|
|
||||||
def render(*)
|
|
||||||
'hello'
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
with_custom_tag('blabla', klass1) do
|
|
||||||
template = Liquid::Template.parse("{% blabla %} bla {% endblabla %}")
|
|
||||||
|
|
||||||
assert_equal 'hello', template.render
|
|
||||||
|
|
||||||
buf = +''
|
|
||||||
output = template.render({}, output: buf)
|
|
||||||
assert_equal 'hello', output
|
|
||||||
assert_equal 'hello', buf
|
|
||||||
assert_equal buf.object_id, output.object_id
|
|
||||||
end
|
|
||||||
|
|
||||||
klass2 = Class.new(klass1) do
|
|
||||||
def render(*)
|
|
||||||
'foo' + super + 'bar'
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
with_custom_tag('blabla', klass2) do
|
|
||||||
template = Liquid::Template.parse("{% blabla %} foo {% endblabla %}")
|
|
||||||
|
|
||||||
assert_equal 'foohellobar', template.render
|
|
||||||
|
|
||||||
buf = +''
|
|
||||||
output = template.render({}, output: buf)
|
|
||||||
assert_equal 'foohellobar', output
|
|
||||||
assert_equal 'foohellobar', buf
|
|
||||||
assert_equal buf.object_id, output.object_id
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -40,26 +40,29 @@ class ErrorHandlingTest < Minitest::Test
|
|||||||
|
|
||||||
def test_standard_error
|
def test_standard_error
|
||||||
template = Liquid::Template.parse(' {{ errors.standard_error }} ')
|
template = Liquid::Template.parse(' {{ errors.standard_error }} ')
|
||||||
assert_equal(' Liquid error: standard error ', template.render('errors' => ErrorDrop.new))
|
context = Liquid::Context.new('errors' => ErrorDrop.new)
|
||||||
|
assert_equal(' Liquid error: standard error ', template.render(context))
|
||||||
|
|
||||||
assert_equal(1, template.errors.size)
|
assert_equal(1, context.errors.size)
|
||||||
assert_equal(StandardError, template.errors.first.class)
|
assert_equal(StandardError, context.errors.first.class)
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_syntax
|
def test_syntax
|
||||||
template = Liquid::Template.parse(' {{ errors.syntax_error }} ')
|
template = Liquid::Template.parse(' {{ errors.syntax_error }} ')
|
||||||
assert_equal(' Liquid syntax error: syntax error ', template.render('errors' => ErrorDrop.new))
|
context = Liquid::Context.new('errors' => ErrorDrop.new)
|
||||||
|
assert_equal(' Liquid syntax error: syntax error ', template.render(context))
|
||||||
|
|
||||||
assert_equal(1, template.errors.size)
|
assert_equal(1, context.errors.size)
|
||||||
assert_equal(SyntaxError, template.errors.first.class)
|
assert_equal(SyntaxError, context.errors.first.class)
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_argument
|
def test_argument
|
||||||
template = Liquid::Template.parse(' {{ errors.argument_error }} ')
|
template = Liquid::Template.parse(' {{ errors.argument_error }} ')
|
||||||
assert_equal(' Liquid error: argument error ', template.render('errors' => ErrorDrop.new))
|
context = Liquid::Context.new('errors' => ErrorDrop.new)
|
||||||
|
assert_equal(' Liquid error: argument error ', template.render(context))
|
||||||
|
|
||||||
assert_equal(1, template.errors.size)
|
assert_equal(1, context.errors.size)
|
||||||
assert_equal(ArgumentError, template.errors.first.class)
|
assert_equal(ArgumentError, context.errors.first.class)
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_missing_endtag_parse_time_error
|
def test_missing_endtag_parse_time_error
|
||||||
@@ -78,9 +81,10 @@ class ErrorHandlingTest < Minitest::Test
|
|||||||
|
|
||||||
def test_lax_unrecognized_operator
|
def test_lax_unrecognized_operator
|
||||||
template = Liquid::Template.parse(' {% if 1 =! 2 %}ok{% endif %} ', error_mode: :lax)
|
template = Liquid::Template.parse(' {% if 1 =! 2 %}ok{% endif %} ', error_mode: :lax)
|
||||||
assert_equal(' Liquid error: Unknown operator =! ', template.render)
|
context = Liquid::Context.new('errors' => ErrorDrop.new)
|
||||||
assert_equal(1, template.errors.size)
|
assert_equal(' Liquid error: Unknown operator =! ', template.render(context))
|
||||||
assert_equal(Liquid::ArgumentError, template.errors.first.class)
|
assert_equal(1, context.errors.size)
|
||||||
|
assert_equal(Liquid::ArgumentError, context.errors.first.class)
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_with_line_numbers_adds_numbers_to_parser_errors
|
def test_with_line_numbers_adds_numbers_to_parser_errors
|
||||||
@@ -202,10 +206,11 @@ class ErrorHandlingTest < Minitest::Test
|
|||||||
def test_default_exception_renderer_with_internal_error
|
def test_default_exception_renderer_with_internal_error
|
||||||
template = Liquid::Template.parse('This is a runtime error: {{ errors.runtime_error }}', line_numbers: true)
|
template = Liquid::Template.parse('This is a runtime error: {{ errors.runtime_error }}', line_numbers: true)
|
||||||
|
|
||||||
output = template.render('errors' => ErrorDrop.new)
|
context = Liquid::Context.new('errors' => ErrorDrop.new)
|
||||||
|
output = template.render(context)
|
||||||
|
|
||||||
assert_equal('This is a runtime error: Liquid error (line 1): internal', output)
|
assert_equal('This is a runtime error: Liquid error (line 1): internal', output)
|
||||||
assert_equal([Liquid::InternalError], template.errors.map(&:class))
|
assert_equal([Liquid::InternalError], context.errors.map(&:class))
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_setting_default_exception_renderer
|
def test_setting_default_exception_renderer
|
||||||
@@ -217,10 +222,11 @@ class ErrorHandlingTest < Minitest::Test
|
|||||||
}
|
}
|
||||||
template = Liquid::Template.parse('This is a runtime error: {{ errors.argument_error }}')
|
template = Liquid::Template.parse('This is a runtime error: {{ errors.argument_error }}')
|
||||||
|
|
||||||
output = template.render('errors' => ErrorDrop.new)
|
context = Liquid::Context.new('errors' => ErrorDrop.new)
|
||||||
|
output = template.render(context)
|
||||||
|
|
||||||
assert_equal('This is a runtime error: ', output)
|
assert_equal('This is a runtime error: ', output)
|
||||||
assert_equal([Liquid::ArgumentError], template.errors.map(&:class))
|
assert_equal([Liquid::ArgumentError], context.errors.map(&:class))
|
||||||
ensure
|
ensure
|
||||||
Liquid::Template.default_exception_renderer = old_exception_renderer if old_exception_renderer
|
Liquid::Template.default_exception_renderer = old_exception_renderer if old_exception_renderer
|
||||||
end
|
end
|
||||||
@@ -233,11 +239,12 @@ class ErrorHandlingTest < Minitest::Test
|
|||||||
e.cause
|
e.cause
|
||||||
}
|
}
|
||||||
|
|
||||||
output = template.render({ 'errors' => ErrorDrop.new }, exception_renderer: handler)
|
context = Liquid::Context.new('errors' => ErrorDrop.new)
|
||||||
|
output = template.render(context, exception_renderer: handler)
|
||||||
|
|
||||||
assert_equal('This is a runtime error: runtime error', output)
|
assert_equal('This is a runtime error: runtime error', output)
|
||||||
assert_equal([Liquid::InternalError], exceptions.map(&:class))
|
assert_equal([Liquid::InternalError], exceptions.map(&:class))
|
||||||
assert_equal(exceptions, template.errors)
|
assert_equal(exceptions, context.errors)
|
||||||
assert_equal('#<RuntimeError: runtime error>', exceptions.first.cause.inspect)
|
assert_equal('#<RuntimeError: runtime error>', exceptions.first.cause.inspect)
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -250,23 +257,16 @@ class ErrorHandlingTest < Minitest::Test
|
|||||||
def test_included_template_name_with_line_numbers
|
def test_included_template_name_with_line_numbers
|
||||||
old_file_system = Liquid::Template.file_system
|
old_file_system = Liquid::Template.file_system
|
||||||
|
|
||||||
|
context = Liquid::Context.new('errors' => ErrorDrop.new)
|
||||||
begin
|
begin
|
||||||
Liquid::Template.file_system = TestFileSystem.new
|
Liquid::Template.file_system = TestFileSystem.new
|
||||||
|
|
||||||
template = Liquid::Template.parse("Argument error:\n{% include 'product' %}", line_numbers: true)
|
template = Liquid::Template.parse("Argument error:\n{% include 'product' %}", line_numbers: true)
|
||||||
page = template.render('errors' => ErrorDrop.new)
|
page = template.render(context)
|
||||||
ensure
|
ensure
|
||||||
Liquid::Template.file_system = old_file_system
|
Liquid::Template.file_system = old_file_system
|
||||||
end
|
end
|
||||||
assert_equal("Argument error:\nLiquid error (product line 1): argument error", page)
|
assert_equal("Argument error:\nLiquid error (product line 1): argument error", page)
|
||||||
assert_equal("product", template.errors.first.template_name)
|
assert_equal("product", context.errors.first.template_name)
|
||||||
end
|
|
||||||
|
|
||||||
def test_bug_compatible_silencing_of_errors_in_blank_nodes
|
|
||||||
output = Liquid::Template.parse("{% assign x = 0 %}{% if 1 < '2' %}not blank{% assign x = 3 %}{% endif %}{{ x }}").render
|
|
||||||
assert_equal("Liquid error: comparison of Integer with String failed0", output)
|
|
||||||
|
|
||||||
output = Liquid::Template.parse("{% assign x = 0 %}{% if 1 < '2' %}{% assign x = 3 %}{% endif %}{{ x }}").render
|
|
||||||
assert_equal("0", output)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
27
test/integration/registers/disabled_tags_test.rb
Normal file
27
test/integration/registers/disabled_tags_test.rb
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'test_helper'
|
||||||
|
|
||||||
|
class DisabledTagsTest < Minitest::Test
|
||||||
|
include Liquid
|
||||||
|
|
||||||
|
class DisableRaw < Block
|
||||||
|
disable_tags "raw"
|
||||||
|
end
|
||||||
|
|
||||||
|
class DisableRawEcho < Block
|
||||||
|
disable_tags "raw", "echo"
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_disables_raw
|
||||||
|
with_custom_tag('disable', DisableRaw) do
|
||||||
|
assert_template_result 'raw usage is not allowed in this contextfoo', '{% disable %}{% raw %}Foobar{% endraw %}{% echo "foo" %}{% enddisable %}'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_disables_echo_and_raw
|
||||||
|
with_custom_tag('disable', DisableRawEcho) do
|
||||||
|
assert_template_result 'raw usage is not allowed in this contextecho usage is not allowed in this context', '{% disable %}{% raw %}Foobar{% endraw %}{% echo "foo" %}{% enddisable %}'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -43,22 +43,15 @@ class SecurityTest < Minitest::Test
|
|||||||
assert_equal(expected, Template.parse(text).render!(@assigns, filters: SecurityFilter))
|
assert_equal(expected, Template.parse(text).render!(@assigns, filters: SecurityFilter))
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_does_not_permanently_add_filters_to_symbol_table
|
def test_does_not_add_filters_to_symbol_table
|
||||||
current_symbols = Symbol.all_symbols
|
current_symbols = Symbol.all_symbols
|
||||||
|
|
||||||
# MRI imprecisely marks objects found on the C stack, which can result
|
test = %( {{ "some_string" | a_bad_filter }} )
|
||||||
# in uninitialized memory being marked. This can even result in the test failing
|
|
||||||
# deterministically for a given compilation of ruby. Using a separate thread will
|
|
||||||
# keep these writes of the symbol pointer on a separate stack that will be garbage
|
|
||||||
# collected after Thread#join.
|
|
||||||
Thread.new do
|
|
||||||
test = %( {{ "some_string" | a_bad_filter }} )
|
|
||||||
Template.parse(test).render!
|
|
||||||
nil
|
|
||||||
end.join
|
|
||||||
|
|
||||||
GC.start
|
template = Template.parse(test)
|
||||||
|
assert_equal([], (Symbol.all_symbols - current_symbols))
|
||||||
|
|
||||||
|
template.render!
|
||||||
assert_equal([], (Symbol.all_symbols - current_symbols))
|
assert_equal([], (Symbol.all_symbols - current_symbols))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -1,51 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
require 'test_helper'
|
|
||||||
|
|
||||||
class TagDisableableTest < Minitest::Test
|
|
||||||
include Liquid
|
|
||||||
|
|
||||||
class DisableRaw < Block
|
|
||||||
disable_tags "raw"
|
|
||||||
end
|
|
||||||
|
|
||||||
class DisableRawEcho < Block
|
|
||||||
disable_tags "raw", "echo"
|
|
||||||
end
|
|
||||||
|
|
||||||
class DisableableRaw < Liquid::Raw
|
|
||||||
prepend Liquid::Tag::Disableable
|
|
||||||
end
|
|
||||||
|
|
||||||
class DisableableEcho < Liquid::Echo
|
|
||||||
prepend Liquid::Tag::Disableable
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_disables_raw
|
|
||||||
with_disableable_tags do
|
|
||||||
with_custom_tag('disable', DisableRaw) do
|
|
||||||
output = Template.parse('{% disable %}{% raw %}Foobar{% endraw %}{% echo "foo" %}{% enddisable %}').render
|
|
||||||
assert_equal('Liquid error: raw usage is not allowed in this contextfoo', output)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_disables_echo_and_raw
|
|
||||||
with_disableable_tags do
|
|
||||||
with_custom_tag('disable', DisableRawEcho) do
|
|
||||||
output = Template.parse('{% disable %}{% raw %}Foobar{% endraw %}{% echo "foo" %}{% enddisable %}').render
|
|
||||||
assert_equal('Liquid error: raw usage is not allowed in this contextLiquid error: echo usage is not allowed in this context', output)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def with_disableable_tags
|
|
||||||
with_custom_tag('raw', DisableableRaw) do
|
|
||||||
with_custom_tag('echo', DisableableEcho) do
|
|
||||||
yield
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
require 'test_helper'
|
|
||||||
|
|
||||||
class TagTest < Minitest::Test
|
|
||||||
include Liquid
|
|
||||||
|
|
||||||
def test_custom_tags_have_a_default_render_to_output_buffer_method_for_backwards_compatibility
|
|
||||||
klass1 = Class.new(Tag) do
|
|
||||||
def render(*)
|
|
||||||
'hello'
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
with_custom_tag('blabla', klass1) do
|
|
||||||
template = Liquid::Template.parse("{% blabla %}")
|
|
||||||
|
|
||||||
assert_equal 'hello', template.render
|
|
||||||
|
|
||||||
buf = +''
|
|
||||||
output = template.render({}, output: buf)
|
|
||||||
assert_equal 'hello', output
|
|
||||||
assert_equal 'hello', buf
|
|
||||||
assert_equal buf.object_id, output.object_id
|
|
||||||
end
|
|
||||||
|
|
||||||
klass2 = Class.new(klass1) do
|
|
||||||
def render(*)
|
|
||||||
'foo' + super + 'bar'
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
with_custom_tag('blabla', klass2) do
|
|
||||||
template = Liquid::Template.parse("{% blabla %}")
|
|
||||||
|
|
||||||
assert_equal 'foohellobar', template.render
|
|
||||||
|
|
||||||
buf = +''
|
|
||||||
output = template.render({}, output: buf)
|
|
||||||
assert_equal 'foohellobar', output
|
|
||||||
assert_equal 'foohellobar', buf
|
|
||||||
assert_equal buf.object_id, output.object_id
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -217,8 +217,9 @@ class IncludeTagTest < Minitest::Test
|
|||||||
Liquid::Template.file_system = TestFileSystem.new
|
Liquid::Template.file_system = TestFileSystem.new
|
||||||
|
|
||||||
a = Liquid::Template.parse(' {% include "nested_template" %}')
|
a = Liquid::Template.parse(' {% include "nested_template" %}')
|
||||||
a.render!
|
context = Liquid::Context.new
|
||||||
assert_empty(a.errors)
|
a.render!(context)
|
||||||
|
assert_empty(context.errors)
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_passing_options_to_included_templates
|
def test_passing_options_to_included_templates
|
||||||
@@ -257,9 +258,10 @@ class IncludeTagTest < Minitest::Test
|
|||||||
|
|
||||||
def test_including_with_strict_variables
|
def test_including_with_strict_variables
|
||||||
template = Liquid::Template.parse("{% include 'simple' %}", error_mode: :warn)
|
template = Liquid::Template.parse("{% include 'simple' %}", error_mode: :warn)
|
||||||
template.render(nil, strict_variables: true)
|
context = Liquid::Context.new
|
||||||
|
template.render(context, strict_variables: true)
|
||||||
|
|
||||||
assert_equal([], template.errors)
|
assert_equal([], context.errors)
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_break_through_include
|
def test_break_through_include
|
||||||
|
|||||||
@@ -82,13 +82,15 @@ class LiquidTagTest < Minitest::Test
|
|||||||
end
|
end
|
||||||
|
|
||||||
def test_nested_liquid_tag
|
def test_nested_liquid_tag
|
||||||
assert_template_result('good', <<~LIQUID)
|
assert_usage_increment("liquid_tag_contains_outer_tag", times: 0) do
|
||||||
{%- if true %}
|
assert_template_result('good', <<~LIQUID)
|
||||||
{%- liquid
|
{%- if true %}
|
||||||
echo "good"
|
{%- liquid
|
||||||
%}
|
echo "good"
|
||||||
{%- endif -%}
|
%}
|
||||||
LIQUID
|
{%- endif -%}
|
||||||
|
LIQUID
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_cannot_open_blocks_living_past_a_liquid_tag
|
def test_cannot_open_blocks_living_past_a_liquid_tag
|
||||||
@@ -100,12 +102,14 @@ class LiquidTagTest < Minitest::Test
|
|||||||
LIQUID
|
LIQUID
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_cannot_close_blocks_created_before_a_liquid_tag
|
def test_quirk_can_close_blocks_created_before_a_liquid_tag
|
||||||
assert_match_syntax_error("syntax error (line 3): 'endif' is not a valid delimiter for liquid tags. use %}", <<~LIQUID)
|
assert_usage_increment("liquid_tag_contains_outer_tag") do
|
||||||
{%- if true -%}
|
assert_template_result("42", <<~LIQUID)
|
||||||
42
|
{%- if true -%}
|
||||||
{%- liquid endif -%}
|
42
|
||||||
LIQUID
|
{%- liquid endif -%}
|
||||||
|
LIQUID
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_liquid_tag_in_raw
|
def test_liquid_tag_in_raw
|
||||||
|
|||||||
@@ -127,10 +127,7 @@ class RenderTagTest < Minitest::Test
|
|||||||
'test_include' => '{% include "foo" %}'
|
'test_include' => '{% include "foo" %}'
|
||||||
)
|
)
|
||||||
|
|
||||||
exc = assert_raises(Liquid::DisabledError) do
|
assert_template_result('include usage is not allowed in this context', '{% render "test_include" %}')
|
||||||
Liquid::Template.parse('{% render "test_include" %}').render!
|
|
||||||
end
|
|
||||||
assert_equal('Liquid error: include usage is not allowed in this context', exc.message)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_includes_will_not_render_inside_nested_sibling_tags
|
def test_includes_will_not_render_inside_nested_sibling_tags
|
||||||
@@ -140,8 +137,7 @@ class RenderTagTest < Minitest::Test
|
|||||||
'test_include' => '{% include "foo" %}'
|
'test_include' => '{% include "foo" %}'
|
||||||
)
|
)
|
||||||
|
|
||||||
output = Liquid::Template.parse('{% render "nested_render_with_sibling_include" %}').render
|
assert_template_result('include usage is not allowed in this contextinclude usage is not allowed in this context', '{% render "nested_render_with_sibling_include" %}')
|
||||||
assert_equal('Liquid error: include usage is not allowed in this contextLiquid error: include usage is not allowed in this context', output)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_render_tag_with
|
def test_render_tag_with
|
||||||
|
|||||||
@@ -38,12 +38,6 @@ end
|
|||||||
class TemplateTest < Minitest::Test
|
class TemplateTest < Minitest::Test
|
||||||
include Liquid
|
include Liquid
|
||||||
|
|
||||||
def test_instance_assigns_persist_on_same_template_object_between_parses
|
|
||||||
t = Template.new
|
|
||||||
assert_equal('from instance assigns', t.parse("{% assign foo = 'from instance assigns' %}{{ foo }}").render!)
|
|
||||||
assert_equal('from instance assigns', t.parse("{{ foo }}").render!)
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_warnings_is_not_exponential_time
|
def test_warnings_is_not_exponential_time
|
||||||
str = "false"
|
str = "false"
|
||||||
100.times do
|
100.times do
|
||||||
@@ -54,43 +48,15 @@ class TemplateTest < Minitest::Test
|
|||||||
assert_equal([], Timeout.timeout(1) { t.warnings })
|
assert_equal([], Timeout.timeout(1) { t.warnings })
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_instance_assigns_persist_on_same_template_parsing_between_renders
|
|
||||||
t = Template.new.parse("{{ foo }}{% assign foo = 'foo' %}{{ foo }}")
|
|
||||||
assert_equal('foo', t.render!)
|
|
||||||
assert_equal('foofoo', t.render!)
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_custom_assigns_do_not_persist_on_same_template
|
def test_custom_assigns_do_not_persist_on_same_template
|
||||||
t = Template.new
|
t = Template.new
|
||||||
assert_equal('from custom assigns', t.parse("{{ foo }}").render!('foo' => 'from custom assigns'))
|
assert_equal('from custom assigns', t.parse("{{ foo }}").render!('foo' => 'from custom assigns'))
|
||||||
assert_equal('', t.parse("{{ foo }}").render!)
|
assert_equal('', t.parse("{{ foo }}").render!)
|
||||||
end
|
|
||||||
|
|
||||||
def test_custom_assigns_squash_instance_assigns
|
|
||||||
t = Template.new
|
|
||||||
assert_equal('from instance assigns', t.parse("{% assign foo = 'from instance assigns' %}{{ foo }}").render!)
|
assert_equal('from instance assigns', t.parse("{% assign foo = 'from instance assigns' %}{{ foo }}").render!)
|
||||||
assert_equal('from custom assigns', t.parse("{{ foo }}").render!('foo' => 'from custom assigns'))
|
assert_equal('from custom assigns', t.parse("{{ foo }}").render!('foo' => 'from custom assigns'))
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_persistent_assigns_squash_instance_assigns
|
|
||||||
t = Template.new
|
|
||||||
assert_equal('from instance assigns', t.parse("{% assign foo = 'from instance assigns' %}{{ foo }}").render!)
|
|
||||||
t.assigns['foo'] = 'from persistent assigns'
|
|
||||||
assert_equal('from persistent assigns', t.parse("{{ foo }}").render!)
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_lambda_is_called_once_from_persistent_assigns_over_multiple_parses_and_renders
|
|
||||||
t = Template.new
|
|
||||||
t.assigns['number'] = -> {
|
|
||||||
@global ||= 0
|
|
||||||
@global += 1
|
|
||||||
}
|
|
||||||
assert_equal('1', t.parse("{{number}}").render!)
|
|
||||||
assert_equal('1', t.parse("{{number}}").render!)
|
|
||||||
assert_equal('1', t.render!)
|
|
||||||
@global = nil
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_lambda_is_called_once_from_custom_assigns_over_multiple_parses_and_renders
|
def test_lambda_is_called_once_from_custom_assigns_over_multiple_parses_and_renders
|
||||||
t = Template.new
|
t = Template.new
|
||||||
assigns = { 'number' => -> {
|
assigns = { 'number' => -> {
|
||||||
@@ -105,125 +71,143 @@ class TemplateTest < Minitest::Test
|
|||||||
|
|
||||||
def test_resource_limits_works_with_custom_length_method
|
def test_resource_limits_works_with_custom_length_method
|
||||||
t = Template.parse("{% assign foo = bar %}")
|
t = Template.parse("{% assign foo = bar %}")
|
||||||
t.resource_limits.render_length_limit = 42
|
context = Liquid::Context.new("bar" => SomethingWithLength.new)
|
||||||
assert_equal("", t.render!("bar" => SomethingWithLength.new))
|
context.resource_limits.render_length_limit = 42
|
||||||
|
assert_equal("", t.render!(context))
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_resource_limits_render_length
|
def test_resource_limits_render_length
|
||||||
t = Template.parse("0123456789")
|
t = Template.parse("0123456789")
|
||||||
t.resource_limits.render_length_limit = 9
|
context = Liquid::Context.new
|
||||||
assert_equal("Liquid error: Memory limits exceeded", t.render)
|
context.resource_limits.render_length_limit = 5
|
||||||
assert(t.resource_limits.reached?)
|
assert_equal("Liquid error: Memory limits exceeded", t.render(context))
|
||||||
|
assert(context.resource_limits.reached?)
|
||||||
|
|
||||||
t.resource_limits.render_length_limit = 10
|
context.resource_limits.render_length_limit = 10
|
||||||
assert_equal("0123456789", t.render!)
|
assert_equal("0123456789", t.render!)
|
||||||
|
refute_nil(context.resource_limits.render_length)
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_resource_limits_render_score
|
def test_resource_limits_render_score
|
||||||
t = Template.parse("{% for a in (1..10) %} {% for a in (1..10) %} foo {% endfor %} {% endfor %}")
|
t = Template.parse("{% for a in (1..10) %} {% for a in (1..10) %} foo {% endfor %} {% endfor %}")
|
||||||
t.resource_limits.render_score_limit = 50
|
context = Liquid::Context.new
|
||||||
assert_equal("Liquid error: Memory limits exceeded", t.render)
|
context.resource_limits.render_score_limit = 50
|
||||||
assert(t.resource_limits.reached?)
|
assert_equal("Liquid error: Memory limits exceeded", t.render(context))
|
||||||
|
assert(context.resource_limits.reached?)
|
||||||
|
|
||||||
t = Template.parse("{% for a in (1..100) %} foo {% endfor %}")
|
t = Template.parse("{% for a in (1..100) %} foo {% endfor %}")
|
||||||
t.resource_limits.render_score_limit = 50
|
context.resource_limits.render_score_limit = 50
|
||||||
assert_equal("Liquid error: Memory limits exceeded", t.render)
|
assert_equal("Liquid error: Memory limits exceeded", t.render(context))
|
||||||
assert(t.resource_limits.reached?)
|
assert(context.resource_limits.reached?)
|
||||||
|
|
||||||
t.resource_limits.render_score_limit = 200
|
context.resource_limits.render_score_limit = 200
|
||||||
assert_equal((" foo " * 100), t.render!)
|
assert_equal((" foo " * 100), t.render!(context))
|
||||||
refute_nil(t.resource_limits.render_score)
|
refute_nil(context.resource_limits.render_score)
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_resource_limits_assign_score
|
def test_resource_limits_assign_score
|
||||||
t = Template.parse("{% assign foo = 42 %}{% assign bar = 23 %}")
|
t = Template.parse("{% assign foo = 42 %}{% assign bar = 23 %}")
|
||||||
t.resource_limits.assign_score_limit = 1
|
context = Liquid::Context.new
|
||||||
assert_equal("Liquid error: Memory limits exceeded", t.render)
|
context.resource_limits.assign_score_limit = 1
|
||||||
assert(t.resource_limits.reached?)
|
assert_equal("Liquid error: Memory limits exceeded", t.render(context))
|
||||||
|
assert(context.resource_limits.reached?)
|
||||||
|
|
||||||
t.resource_limits.assign_score_limit = 2
|
context.resource_limits.assign_score_limit = 2
|
||||||
assert_equal("", t.render!)
|
assert_equal("", t.render!(context))
|
||||||
refute_nil(t.resource_limits.assign_score)
|
refute_nil(context.resource_limits.assign_score)
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_resource_limits_assign_score_counts_bytes_not_characters
|
def test_resource_limits_assign_score_counts_bytes_not_characters
|
||||||
t = Template.parse("{% assign foo = 'すごい' %}")
|
t = Template.parse("{% assign foo = 'すごい' %}")
|
||||||
t.render
|
context = Liquid::Context.new
|
||||||
assert_equal(9, t.resource_limits.assign_score)
|
t.render(context)
|
||||||
|
assert_equal(9, context.resource_limits.assign_score)
|
||||||
|
|
||||||
t = Template.parse("{% capture foo %}すごい{% endcapture %}")
|
t = Template.parse("{% capture foo %}すごい{% endcapture %}")
|
||||||
t.render
|
t.render(context)
|
||||||
assert_equal(9, t.resource_limits.assign_score)
|
assert_equal(9, context.resource_limits.assign_score)
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_resource_limits_assign_score_nested
|
def test_resource_limits_assign_score_nested
|
||||||
t = Template.parse("{% assign foo = 'aaaa' | reverse %}")
|
t = Template.parse("{% assign foo = 'aaaa' | reverse %}")
|
||||||
|
|
||||||
t.resource_limits.assign_score_limit = 3
|
context = Liquid::Context.new
|
||||||
assert_equal("Liquid error: Memory limits exceeded", t.render)
|
context.resource_limits.assign_score_limit = 3
|
||||||
assert(t.resource_limits.reached?)
|
assert_equal("Liquid error: Memory limits exceeded", t.render(context))
|
||||||
|
assert(context.resource_limits.reached?)
|
||||||
|
|
||||||
t.resource_limits.assign_score_limit = 5
|
context.resource_limits.assign_score_limit = 5
|
||||||
assert_equal("", t.render!)
|
assert_equal("", t.render!(context))
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_resource_limits_aborts_rendering_after_first_error
|
def test_resource_limits_aborts_rendering_after_first_error
|
||||||
t = Template.parse("{% for a in (1..100) %} foo1 {% endfor %} bar {% for a in (1..100) %} foo2 {% endfor %}")
|
t = Template.parse("{% for a in (1..100) %} foo1 {% endfor %} bar {% for a in (1..100) %} foo2 {% endfor %}")
|
||||||
t.resource_limits.render_score_limit = 50
|
context = Liquid::Context.new
|
||||||
assert_equal("Liquid error: Memory limits exceeded", t.render)
|
context.resource_limits.render_score_limit = 50
|
||||||
assert(t.resource_limits.reached?)
|
assert_equal("Liquid error: Memory limits exceeded", t.render(context))
|
||||||
|
assert(context.resource_limits.reached?)
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_resource_limits_hash_in_template_gets_updated_even_if_no_limits_are_set
|
def test_resource_limits_hash_in_template_gets_updated_even_if_no_limits_are_set
|
||||||
t = Template.parse("{% for a in (1..100) %}x{% assign foo = 1 %} {% endfor %}")
|
t = Template.parse("{% for a in (1..100) %} {% assign foo = 1 %} {% endfor %}")
|
||||||
t.render!
|
context = Liquid::Context.new
|
||||||
assert(t.resource_limits.assign_score > 0)
|
t.render!(context)
|
||||||
assert(t.resource_limits.render_score > 0)
|
assert(context.resource_limits.assign_score > 0)
|
||||||
|
assert(context.resource_limits.render_score > 0)
|
||||||
|
assert(context.resource_limits.render_length > 0)
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_render_length_persists_between_blocks
|
def test_render_length_persists_between_blocks
|
||||||
t = Template.parse("{% if true %}aaaa{% endif %}")
|
t = Template.parse("{% if true %}aaaa{% endif %}")
|
||||||
t.resource_limits.render_length_limit = 3
|
context = Liquid::Context.new
|
||||||
assert_equal("Liquid error: Memory limits exceeded", t.render)
|
context.resource_limits.render_length_limit = 7
|
||||||
t.resource_limits.render_length_limit = 4
|
assert_equal("Liquid error: Memory limits exceeded", t.render(context))
|
||||||
assert_equal("aaaa", t.render)
|
context.resource_limits.render_length_limit = 8
|
||||||
|
assert_equal("aaaa", t.render(context))
|
||||||
|
|
||||||
t = Template.parse("{% if true %}aaaa{% endif %}{% if true %}bbb{% endif %}")
|
t = Template.parse("{% if true %}aaaa{% endif %}{% if true %}bbb{% endif %}")
|
||||||
t.resource_limits.render_length_limit = 6
|
context = Liquid::Context.new
|
||||||
assert_equal("Liquid error: Memory limits exceeded", t.render)
|
context.resource_limits.render_length_limit = 13
|
||||||
t.resource_limits.render_length_limit = 7
|
assert_equal("Liquid error: Memory limits exceeded", t.render(context))
|
||||||
assert_equal("aaaabbb", t.render)
|
context.resource_limits.render_length_limit = 14
|
||||||
|
assert_equal("aaaabbb", t.render(context))
|
||||||
|
|
||||||
t = Template.parse("{% if true %}a{% endif %}{% if true %}b{% endif %}{% if true %}a{% endif %}{% if true %}b{% endif %}{% if true %}a{% endif %}{% if true %}b{% endif %}")
|
t = Template.parse("{% if true %}a{% endif %}{% if true %}b{% endif %}{% if true %}a{% endif %}{% if true %}b{% endif %}{% if true %}a{% endif %}{% if true %}b{% endif %}")
|
||||||
t.resource_limits.render_length_limit = 5
|
context = Liquid::Context.new
|
||||||
assert_equal("Liquid error: Memory limits exceeded", t.render)
|
context.resource_limits.render_length_limit = 5
|
||||||
t.resource_limits.render_length_limit = 6
|
assert_equal("Liquid error: Memory limits exceeded", t.render(context))
|
||||||
assert_equal("ababab", t.render)
|
context.resource_limits.render_length_limit = 11
|
||||||
|
assert_equal("Liquid error: Memory limits exceeded", t.render(context))
|
||||||
|
context.resource_limits.render_length_limit = 12
|
||||||
|
assert_equal("ababab", t.render(context))
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_render_length_uses_number_of_bytes_not_characters
|
def test_render_length_uses_number_of_bytes_not_characters
|
||||||
t = Template.parse("{% if true %}すごい{% endif %}")
|
t = Template.parse("{% if true %}すごい{% endif %}")
|
||||||
t.resource_limits.render_length_limit = 8
|
context = Liquid::Context.new
|
||||||
assert_equal("Liquid error: Memory limits exceeded", t.render)
|
context.resource_limits.render_length_limit = 10
|
||||||
t.resource_limits.render_length_limit = 9
|
assert_equal("Liquid error: Memory limits exceeded", t.render(context))
|
||||||
assert_equal("すごい", t.render)
|
context.resource_limits.render_length_limit = 18
|
||||||
|
assert_equal("すごい", t.render(context))
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_default_resource_limits_unaffected_by_render_with_context
|
def test_default_resource_limits_unaffected_by_render_with_context
|
||||||
context = Context.new
|
context = Context.new
|
||||||
t = Template.parse("{% for a in (1..100) %}x{% assign foo = 1 %} {% endfor %}")
|
t = Template.parse("{% for a in (1..100) %} {% assign foo = 1 %} {% endfor %}")
|
||||||
t.render!(context)
|
t.render!(context)
|
||||||
assert(context.resource_limits.assign_score > 0)
|
assert(context.resource_limits.assign_score > 0)
|
||||||
assert(context.resource_limits.render_score > 0)
|
assert(context.resource_limits.render_score > 0)
|
||||||
|
assert(context.resource_limits.render_length > 0)
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_can_use_drop_as_context
|
def test_can_use_drop_as_context
|
||||||
t = Template.new
|
t = Template.new
|
||||||
t.registers['lulz'] = 'haha'
|
|
||||||
drop = TemplateContextDrop.new
|
drop = TemplateContextDrop.new
|
||||||
assert_equal('fizzbuzz', t.parse('{{foo}}').render!(drop))
|
context = Liquid::Context.build(environments: drop, registers: { 'lulz' => 'haha' })
|
||||||
assert_equal('bar', t.parse('{{bar}}').render!(drop))
|
drop.context = context
|
||||||
assert_equal('haha', t.parse("{{baz}}").render!(drop))
|
assert_equal('fizzbuzz', t.parse('{{foo}}').render!(context))
|
||||||
|
assert_equal('bar', t.parse('{{bar}}').render!(context))
|
||||||
|
assert_equal('haha', t.parse("{{baz}}").render!(context))
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_render_bang_force_rethrow_errors_on_passed_context
|
def test_render_bang_force_rethrow_errors_on_passed_context
|
||||||
@@ -275,25 +259,27 @@ class TemplateTest < Minitest::Test
|
|||||||
end
|
end
|
||||||
|
|
||||||
def test_undefined_variables
|
def test_undefined_variables
|
||||||
t = Template.parse("{{x}} {{y}} {{z.a}} {{z.b}} {{z.c.d}}")
|
t = Template.parse("{{x}} {{y}} {{z.a}} {{z.b}} {{z.c.d}}")
|
||||||
result = t.render({ 'x' => 33, 'z' => { 'a' => 32, 'c' => { 'e' => 31 } } }, strict_variables: true)
|
context = Liquid::Context.new('x' => 33, 'z' => { 'a' => 32, 'c' => { 'e' => 31 } })
|
||||||
|
result = t.render(context, strict_variables: true)
|
||||||
|
|
||||||
assert_equal('33 32 ', result)
|
assert_equal('33 32 ', result)
|
||||||
assert_equal(3, t.errors.count)
|
assert_equal(3, context.errors.count)
|
||||||
assert_instance_of(Liquid::UndefinedVariable, t.errors[0])
|
assert_instance_of(Liquid::UndefinedVariable, context.errors[0])
|
||||||
assert_equal('Liquid error: undefined variable y', t.errors[0].message)
|
assert_equal('Liquid error: undefined variable y', context.errors[0].message)
|
||||||
assert_instance_of(Liquid::UndefinedVariable, t.errors[1])
|
assert_instance_of(Liquid::UndefinedVariable, context.errors[1])
|
||||||
assert_equal('Liquid error: undefined variable b', t.errors[1].message)
|
assert_equal('Liquid error: undefined variable b', context.errors[1].message)
|
||||||
assert_instance_of(Liquid::UndefinedVariable, t.errors[2])
|
assert_instance_of(Liquid::UndefinedVariable, context.errors[2])
|
||||||
assert_equal('Liquid error: undefined variable d', t.errors[2].message)
|
assert_equal('Liquid error: undefined variable d', context.errors[2].message)
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_nil_value_does_not_raise
|
def test_nil_value_does_not_raise
|
||||||
Liquid::Template.error_mode = :strict
|
Liquid::Template.error_mode = :strict
|
||||||
t = Template.parse("some{{x}}thing")
|
t = Template.parse("some{{x}}thing")
|
||||||
result = t.render!({ 'x' => nil }, strict_variables: true)
|
context = Liquid::Context.new('x' => nil)
|
||||||
|
result = t.render!(context, strict_variables: true)
|
||||||
|
|
||||||
assert_equal(0, t.errors.count)
|
assert_equal(0, context.errors.count)
|
||||||
assert_equal('something', result)
|
assert_equal('something', result)
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -308,11 +294,13 @@ class TemplateTest < Minitest::Test
|
|||||||
def test_undefined_drop_methods
|
def test_undefined_drop_methods
|
||||||
d = DropWithUndefinedMethod.new
|
d = DropWithUndefinedMethod.new
|
||||||
t = Template.new.parse('{{ foo }} {{ woot }}')
|
t = Template.new.parse('{{ foo }} {{ woot }}')
|
||||||
result = t.render(d, strict_variables: true)
|
context = Liquid::Context.new(d)
|
||||||
|
d.context = context
|
||||||
|
result = t.render(context, strict_variables: true)
|
||||||
|
|
||||||
assert_equal('foo ', result)
|
assert_equal('foo ', result)
|
||||||
assert_equal(1, t.errors.count)
|
assert_equal(1, context.errors.count)
|
||||||
assert_instance_of(Liquid::UndefinedDropMethod, t.errors[0])
|
assert_instance_of(Liquid::UndefinedDropMethod, context.errors[0])
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_undefined_drop_methods_raise
|
def test_undefined_drop_methods_raise
|
||||||
@@ -331,12 +319,13 @@ class TemplateTest < Minitest::Test
|
|||||||
"-#{v}-"
|
"-#{v}-"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
result = t.render({ 'a' => 123, 'x' => 'foo' }, filters: [filters], strict_filters: true)
|
context = Liquid::Context.new('a' => 123, 'x' => 'foo')
|
||||||
|
result = t.render(context, filters: [filters], strict_filters: true)
|
||||||
|
|
||||||
assert_equal('123 ', result)
|
assert_equal('123 ', result)
|
||||||
assert_equal(1, t.errors.count)
|
assert_equal(1, context.errors.count)
|
||||||
assert_instance_of(Liquid::UndefinedFilter, t.errors[0])
|
assert_instance_of(Liquid::UndefinedFilter, context.errors[0])
|
||||||
assert_equal('Liquid error: undefined filter somefilter1', t.errors[0].message)
|
assert_equal('Liquid error: undefined filter somefilter1', context.errors[0].message)
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_undefined_filters_raise
|
def test_undefined_filters_raise
|
||||||
@@ -356,4 +345,48 @@ class TemplateTest < Minitest::Test
|
|||||||
result = t.render('x' => 1, 'y' => 5)
|
result = t.render('x' => 1, 'y' => 5)
|
||||||
assert_equal('12345', result)
|
assert_equal('12345', result)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_render_uses_correct_disabled_tags_instance
|
||||||
|
Liquid::Template.file_system = StubFileSystem.new(
|
||||||
|
'foo' => 'bar',
|
||||||
|
'test_include' => '{% include "foo" %}'
|
||||||
|
)
|
||||||
|
|
||||||
|
disabled_tags = DisabledTags.new
|
||||||
|
context = Context.build(registers: { disabled_tags: disabled_tags })
|
||||||
|
|
||||||
|
source = "{% render 'test_include' %}"
|
||||||
|
parse_context = Liquid::ParseContext.new(line_numbers: true, error_mode: :strict)
|
||||||
|
document = Document.parse(Liquid::Tokenizer.new(source, true), parse_context)
|
||||||
|
|
||||||
|
assert_equal("include usage is not allowed in this context", document.render(context))
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_render_sets_context_static_register_when_register_key_does_exist
|
||||||
|
disabled_tags_for_test = DisabledTags.new
|
||||||
|
Template.add_register(:disabled_tags, disabled_tags_for_test)
|
||||||
|
|
||||||
|
t = Template.parse("{% if true %} Test Template {% endif %}")
|
||||||
|
|
||||||
|
context = Context.new
|
||||||
|
refute(context.registers.key?(:disabled_tags))
|
||||||
|
|
||||||
|
t.render(context)
|
||||||
|
|
||||||
|
assert(context.registers.key?(:disabled_tags))
|
||||||
|
assert_equal(disabled_tags_for_test, context.registers[:disabled_tags])
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_render_does_not_override_context_static_register_when_register_key_exists
|
||||||
|
context = Context.new
|
||||||
|
context.registers[:random_register] = nil
|
||||||
|
Template.add_register(:random_register, {})
|
||||||
|
|
||||||
|
t = Template.parse("{% if true %} Test Template {% endif %}")
|
||||||
|
|
||||||
|
t.render(context)
|
||||||
|
|
||||||
|
assert_nil(context.registers[:random_register])
|
||||||
|
assert(context.registers.key?(:random_register))
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -528,32 +528,4 @@ class TrimModeTest < Minitest::Test
|
|||||||
END_EXPECTED
|
END_EXPECTED
|
||||||
assert_template_result(expected, text)
|
assert_template_result(expected, text)
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_pre_trim_blank_preceding_text
|
|
||||||
template = Liquid::Template.parse("\n{%- raw %}{% endraw %}")
|
|
||||||
assert_equal("", template.render)
|
|
||||||
|
|
||||||
template = Liquid::Template.parse("\n{%- if true %}{% endif %}")
|
|
||||||
assert_equal("", template.render)
|
|
||||||
|
|
||||||
template = Liquid::Template.parse("{{ 'B' }} \n{%- if true %}C{% endif %}")
|
|
||||||
assert_equal("BC", template.render)
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_bug_compatible_pre_trim
|
|
||||||
template = Liquid::Template.parse("\n {%- raw %}{% endraw %}", bug_compatible_whitespace_trimming: true)
|
|
||||||
assert_equal("\n", template.render)
|
|
||||||
|
|
||||||
template = Liquid::Template.parse("\n {%- if true %}{% endif %}", bug_compatible_whitespace_trimming: true)
|
|
||||||
assert_equal("\n", template.render)
|
|
||||||
|
|
||||||
template = Liquid::Template.parse("{{ 'B' }} \n{%- if true %}C{% endif %}", bug_compatible_whitespace_trimming: true)
|
|
||||||
assert_equal("B C", template.render)
|
|
||||||
|
|
||||||
template = Liquid::Template.parse("B\n {%- raw %}{% endraw %}", bug_compatible_whitespace_trimming: true)
|
|
||||||
assert_equal("B", template.render)
|
|
||||||
|
|
||||||
template = Liquid::Template.parse("B\n {%- if true %}{% endif %}", bug_compatible_whitespace_trimming: true)
|
|
||||||
assert_equal("B", template.render)
|
|
||||||
end
|
|
||||||
end # TrimModeTest
|
end # TrimModeTest
|
||||||
|
|||||||
@@ -51,29 +51,10 @@ class VariableTest < Minitest::Test
|
|||||||
assert_equal('cat', Template.parse("{{ nil | append: 'cat' }}").render!)
|
assert_equal('cat', Template.parse("{{ nil | append: 'cat' }}").render!)
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_preset_assigns
|
|
||||||
template = Template.parse(%({{ test }}))
|
|
||||||
template.assigns['test'] = 'worked'
|
|
||||||
assert_equal('worked', template.render!)
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_reuse_parsed_template
|
def test_reuse_parsed_template
|
||||||
template = Template.parse(%({{ greeting }} {{ name }}))
|
template = Template.parse(%({{ greeting }} {{ name }}))
|
||||||
template.assigns['greeting'] = 'Goodbye'
|
|
||||||
assert_equal('Hello Tobi', template.render!('greeting' => 'Hello', 'name' => 'Tobi'))
|
assert_equal('Hello Tobi', template.render!('greeting' => 'Hello', 'name' => 'Tobi'))
|
||||||
assert_equal('Hello ', template.render!('greeting' => 'Hello', 'unknown' => 'Tobi'))
|
assert_equal('Goodbye Brian', template.render!('greeting' => 'Goodbye', 'name' => 'Brian'))
|
||||||
assert_equal('Hello Brian', template.render!('greeting' => 'Hello', 'name' => 'Brian'))
|
|
||||||
assert_equal('Goodbye Brian', template.render!('name' => 'Brian'))
|
|
||||||
assert_equal({ 'greeting' => 'Goodbye' }, template.assigns)
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_assigns_not_polluted_from_template
|
|
||||||
template = Template.parse(%({{ test }}{% assign test = 'bar' %}{{ test }}))
|
|
||||||
template.assigns['test'] = 'baz'
|
|
||||||
assert_equal('bazbar', template.render!)
|
|
||||||
assert_equal('bazbar', template.render!)
|
|
||||||
assert_equal('foobar', template.render!('test' => 'foo'))
|
|
||||||
assert_equal('bazbar', template.render!)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_hash_with_default_proc
|
def test_hash_with_default_proc
|
||||||
|
|||||||
@@ -98,17 +98,10 @@ module Minitest
|
|||||||
end
|
end
|
||||||
|
|
||||||
def with_custom_tag(tag_name, tag_class)
|
def with_custom_tag(tag_name, tag_class)
|
||||||
old_tag = Liquid::Template.tags[tag_name]
|
Liquid::Template.register_tag(tag_name, tag_class)
|
||||||
begin
|
yield
|
||||||
Liquid::Template.register_tag(tag_name, tag_class)
|
ensure
|
||||||
yield
|
Liquid::Template.tags.delete(tag_name)
|
||||||
ensure
|
|
||||||
if old_tag
|
|
||||||
Liquid::Template.tags[tag_name] = old_tag
|
|
||||||
else
|
|
||||||
Liquid::Template.tags.delete(tag_name)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -45,9 +45,53 @@ class BlockUnitTest < Minitest::Test
|
|||||||
assert_equal(3, template.root.nodelist.size)
|
assert_equal(3, template.root.nodelist.size)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_with_custom_tag
|
||||||
|
with_custom_tag('testtag', Block) do
|
||||||
|
assert Liquid::Template.parse("{% testtag %} {% endtesttag %}")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_custom_block_tags_have_a_default_render_to_output_buffer_method_for_backwards_compatibility
|
||||||
|
klass1 = Class.new(Block) do
|
||||||
|
def render(*)
|
||||||
|
'hello'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
with_custom_tag('blabla', klass1) do
|
||||||
|
template = Liquid::Template.parse("{% blabla %} bla {% endblabla %}")
|
||||||
|
|
||||||
|
assert_equal 'hello', template.render
|
||||||
|
|
||||||
|
buf = +''
|
||||||
|
output = template.render({}, output: buf)
|
||||||
|
assert_equal 'hello', output
|
||||||
|
assert_equal 'hello', buf
|
||||||
|
assert_equal buf.object_id, output.object_id
|
||||||
|
end
|
||||||
|
|
||||||
|
klass2 = Class.new(klass1) do
|
||||||
|
def render(*)
|
||||||
|
'foo' + super + 'bar'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
with_custom_tag('blabla', klass2) do
|
||||||
|
template = Liquid::Template.parse("{% blabla %} foo {% endblabla %}")
|
||||||
|
|
||||||
|
assert_equal 'foohellobar', template.render
|
||||||
|
|
||||||
|
buf = +''
|
||||||
|
output = template.render({}, output: buf)
|
||||||
|
assert_equal 'foohellobar', output
|
||||||
|
assert_equal 'foohellobar', buf
|
||||||
|
assert_equal buf.object_id, output.object_id
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def block_types(nodelist)
|
def block_types(nodelist)
|
||||||
nodelist.collect(&:class)
|
nodelist.collect(&:class)
|
||||||
end
|
end
|
||||||
end
|
end # VariableTest
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ class ArrayLike
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
class ContextTest < Minitest::Test
|
class ContextUnitTest < Minitest::Test
|
||||||
include Liquid
|
include Liquid
|
||||||
|
|
||||||
def setup
|
def setup
|
||||||
@@ -563,35 +563,6 @@ class ContextTest < Minitest::Test
|
|||||||
assert_equal('my filter result', template.render(subcontext))
|
assert_equal('my filter result', template.render(subcontext))
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_disables_tag_specified
|
|
||||||
context = Context.new
|
|
||||||
context.with_disabled_tags(%w(foo bar)) do
|
|
||||||
assert_equal true, context.tag_disabled?("foo")
|
|
||||||
assert_equal true, context.tag_disabled?("bar")
|
|
||||||
assert_equal false, context.tag_disabled?("unknown")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_disables_nested_tags
|
|
||||||
context = Context.new
|
|
||||||
context.with_disabled_tags(["foo"]) do
|
|
||||||
context.with_disabled_tags(["foo"]) do
|
|
||||||
assert_equal true, context.tag_disabled?("foo")
|
|
||||||
assert_equal false, context.tag_disabled?("bar")
|
|
||||||
end
|
|
||||||
context.with_disabled_tags(["bar"]) do
|
|
||||||
assert_equal true, context.tag_disabled?("foo")
|
|
||||||
assert_equal true, context.tag_disabled?("bar")
|
|
||||||
context.with_disabled_tags(["foo"]) do
|
|
||||||
assert_equal true, context.tag_disabled?("foo")
|
|
||||||
assert_equal true, context.tag_disabled?("bar")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
assert_equal true, context.tag_disabled?("foo")
|
|
||||||
assert_equal false, context.tag_disabled?("bar")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def assert_no_object_allocations
|
def assert_no_object_allocations
|
||||||
36
test/unit/registers/disabled_tags_unit_test.rb
Normal file
36
test/unit/registers/disabled_tags_unit_test.rb
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'test_helper'
|
||||||
|
|
||||||
|
class DisabledTagsUnitTest < Minitest::Test
|
||||||
|
include Liquid
|
||||||
|
|
||||||
|
def test_disables_tag_specified
|
||||||
|
register = DisabledTags.new
|
||||||
|
register.disable(%w(foo bar)) do
|
||||||
|
assert_equal true, register.disabled?("foo")
|
||||||
|
assert_equal true, register.disabled?("bar")
|
||||||
|
assert_equal false, register.disabled?("unknown")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_disables_nested_tags
|
||||||
|
register = DisabledTags.new
|
||||||
|
register.disable(["foo"]) do
|
||||||
|
register.disable(["foo"]) do
|
||||||
|
assert_equal true, register.disabled?("foo")
|
||||||
|
assert_equal false, register.disabled?("bar")
|
||||||
|
end
|
||||||
|
register.disable(["bar"]) do
|
||||||
|
assert_equal true, register.disabled?("foo")
|
||||||
|
assert_equal true, register.disabled?("bar")
|
||||||
|
register.disable(["foo"]) do
|
||||||
|
assert_equal true, register.disabled?("foo")
|
||||||
|
assert_equal true, register.disabled?("bar")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
assert_equal true, register.disabled?("foo")
|
||||||
|
assert_equal false, register.disabled?("bar")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -20,4 +20,42 @@ class TagUnitTest < Minitest::Test
|
|||||||
tag = Tag.parse("some_tag", "", Tokenizer.new(""), ParseContext.new)
|
tag = Tag.parse("some_tag", "", Tokenizer.new(""), ParseContext.new)
|
||||||
assert_equal('some_tag', tag.tag_name)
|
assert_equal('some_tag', tag.tag_name)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_custom_tags_have_a_default_render_to_output_buffer_method_for_backwards_compatibility
|
||||||
|
klass1 = Class.new(Tag) do
|
||||||
|
def render(*)
|
||||||
|
'hello'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
with_custom_tag('blabla', klass1) do
|
||||||
|
template = Liquid::Template.parse("{% blabla %}")
|
||||||
|
|
||||||
|
assert_equal 'hello', template.render
|
||||||
|
|
||||||
|
buf = +''
|
||||||
|
output = template.render({}, output: buf)
|
||||||
|
assert_equal 'hello', output
|
||||||
|
assert_equal 'hello', buf
|
||||||
|
assert_equal buf.object_id, output.object_id
|
||||||
|
end
|
||||||
|
|
||||||
|
klass2 = Class.new(klass1) do
|
||||||
|
def render(*)
|
||||||
|
'foo' + super + 'bar'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
with_custom_tag('blabla', klass2) do
|
||||||
|
template = Liquid::Template.parse("{% blabla %}")
|
||||||
|
|
||||||
|
assert_equal 'foohellobar', template.render
|
||||||
|
|
||||||
|
buf = +''
|
||||||
|
output = template.render({}, output: buf)
|
||||||
|
assert_equal 'foohellobar', output
|
||||||
|
assert_equal 'foohellobar', buf
|
||||||
|
assert_equal buf.object_id, output.object_id
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
Reference in New Issue
Block a user