mirror of
https://github.com/kemko/liquid.git
synced 2026-01-02 16:25:42 +03:00
Compare commits
59 Commits
range-to_l
...
stack-leve
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1707980a48 | ||
|
|
1370a102c9 | ||
|
|
c9bac9befe | ||
|
|
210a0616f3 | ||
|
|
5149cde5c3 | ||
|
|
22f2cec5de | ||
|
|
4318240ae0 | ||
|
|
aa79c33dda | ||
|
|
b1ef28566e | ||
|
|
41bcc48222 | ||
|
|
27d5106dc9 | ||
|
|
7334073be2 | ||
|
|
5dcefd7d77 | ||
|
|
25c7b05916 | ||
|
|
d17f86ba4d | ||
|
|
384e4313ff | ||
|
|
23f2af8ff5 | ||
|
|
a93eac0268 | ||
|
|
2cc7493cb0 | ||
|
|
85463e1753 | ||
|
|
52ff9b0e84 | ||
|
|
0c58328a40 | ||
|
|
2bb3552033 | ||
|
|
8b751ddf46 | ||
|
|
e5cbdb2b27 | ||
|
|
ffb0ace303 | ||
|
|
ad00998ef8 | ||
|
|
869dbc7ebf | ||
|
|
fae3a2de7b | ||
|
|
f27bd619b9 | ||
|
|
a9b84b7806 | ||
|
|
6cc2c567c5 | ||
|
|
812e3c51b9 | ||
|
|
9dd0801f5c | ||
|
|
b146b49f46 | ||
|
|
86944fe7b7 | ||
|
|
a549d289d7 | ||
|
|
b2feeacbce | ||
|
|
143ba39a08 | ||
|
|
43e59796f6 | ||
|
|
bb3624b799 | ||
|
|
64fca66ef5 | ||
|
|
e9d7486758 | ||
|
|
2bb98c1431 | ||
|
|
95d5c24bfc | ||
|
|
b7ee1a2176 | ||
|
|
0eca61a977 | ||
|
|
9bfd04da2d | ||
|
|
302185a7fc | ||
|
|
6ed6e7e12f | ||
|
|
f41ed78378 | ||
|
|
50c85afc35 | ||
|
|
5876dff326 | ||
|
|
f25185631d | ||
|
|
283f1bad18 | ||
|
|
e1d40c7d89 | ||
|
|
19c6eb426a | ||
|
|
f87b06095d | ||
|
|
b81d54e789 |
@@ -1,9 +1,9 @@
|
|||||||
language: ruby
|
language: ruby
|
||||||
|
|
||||||
rvm:
|
rvm:
|
||||||
- 2.0
|
|
||||||
- 2.1
|
- 2.1
|
||||||
- 2.2
|
- 2.2
|
||||||
|
- 2.3.3
|
||||||
- ruby-head
|
- ruby-head
|
||||||
- jruby-head
|
- jruby-head
|
||||||
# - rbx-2
|
# - rbx-2
|
||||||
@@ -19,6 +19,10 @@ matrix:
|
|||||||
allow_failures:
|
allow_failures:
|
||||||
- rvm: jruby-head
|
- rvm: jruby-head
|
||||||
|
|
||||||
|
install:
|
||||||
|
- gem install rainbow -v 2.2.1
|
||||||
|
- bundle install
|
||||||
|
|
||||||
script: "bundle exec rake"
|
script: "bundle exec rake"
|
||||||
|
|
||||||
notifications:
|
notifications:
|
||||||
|
|||||||
7
Gemfile
7
Gemfile
@@ -3,12 +3,15 @@ source 'https://rubygems.org'
|
|||||||
gemspec
|
gemspec
|
||||||
gem 'stackprof', platforms: :mri_21
|
gem 'stackprof', platforms: :mri_21
|
||||||
|
|
||||||
|
group :benchmark, :test do
|
||||||
|
gem 'benchmark-ips'
|
||||||
|
end
|
||||||
|
|
||||||
group :test do
|
group :test do
|
||||||
gem 'spy', '0.4.1'
|
gem 'spy', '0.4.1'
|
||||||
gem 'benchmark-ips'
|
|
||||||
gem 'rubocop', '0.34.2'
|
gem 'rubocop', '0.34.2'
|
||||||
|
|
||||||
platform :mri do
|
platform :mri do
|
||||||
gem 'liquid-c', github: 'Shopify/liquid-c', ref: '2570693d8d03faa0df9160ec74348a7149436df3'
|
gem 'liquid-c', github: 'Shopify/liquid-c', ref: 'bd53db95de3d44d631e7c5a267c3d934e66107dd'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
# Liquid Change Log
|
# Liquid Change Log
|
||||||
|
|
||||||
## 4.0.0 / not yet released / branch "master"
|
## 4.0.0 / 2016-12-14 / branch "4-0-stable"
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
* Render an opaque internal error by default for non-Liquid::Error (#835) [Dylan Thacker-Smith]
|
||||||
|
* Ruby 2.0 support dropped (#832) [Dylan Thacker-Smith]
|
||||||
* Add to_number Drop method to allow custom drops to work with number filters (#731)
|
* Add to_number Drop method to allow custom drops to work with number filters (#731)
|
||||||
* Add strict_variables and strict_filters options to detect undefined references (#691)
|
* Add strict_variables and strict_filters options to detect undefined references (#691)
|
||||||
* Improve loop performance (#681) [Florian Weingarten]
|
* Improve loop performance (#681) [Florian Weingarten]
|
||||||
@@ -18,10 +20,13 @@
|
|||||||
* Add concat filter to concatenate arrays (#429) [Diogo Beato]
|
* Add concat filter to concatenate arrays (#429) [Diogo Beato]
|
||||||
* Ruby 1.9 support dropped (#491) [Justin Li]
|
* Ruby 1.9 support dropped (#491) [Justin Li]
|
||||||
* Liquid::Template.file_system's read_template_file method is no longer passed the context. (#441) [James Reid-Smith]
|
* Liquid::Template.file_system's read_template_file method is no longer passed the context. (#441) [James Reid-Smith]
|
||||||
* Remove support for `liquid_methods`
|
* Remove `liquid_methods` (See https://github.com/Shopify/liquid/pull/568 for replacement)
|
||||||
* Liquid::Template.register_filter raises when the module overrides registered public methods as private or protected (#705) [Gaurav Chande]
|
* Liquid::Template.register_filter raises when the module overrides registered public methods as private or protected (#705) [Gaurav Chande]
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
|
* Fix variable names being detected as an operator when starting with contains (#788) [Michael Angell]
|
||||||
|
* Fix include tag used with strict_variables (#828) [QuickPay]
|
||||||
* Fix map filter when value is a Proc (#672) [Guillaume Malette]
|
* Fix map filter when value is a Proc (#672) [Guillaume Malette]
|
||||||
* Fix truncate filter when value is not a string (#672) [Guillaume Malette]
|
* Fix truncate filter when value is not a string (#672) [Guillaume Malette]
|
||||||
* Fix behaviour of escape filter when input is nil (#665) [Tanel Jakobsoo]
|
* Fix behaviour of escape filter when input is nil (#665) [Tanel Jakobsoo]
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
machine:
|
machine:
|
||||||
ruby:
|
ruby:
|
||||||
version: ruby-2.0
|
version: ruby-2.1
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ module Liquid
|
|||||||
ArgumentSeparator = ','.freeze
|
ArgumentSeparator = ','.freeze
|
||||||
FilterArgumentSeparator = ':'.freeze
|
FilterArgumentSeparator = ':'.freeze
|
||||||
VariableAttributeSeparator = '.'.freeze
|
VariableAttributeSeparator = '.'.freeze
|
||||||
|
WhitespaceControl = '-'.freeze
|
||||||
TagStart = /\{\%/
|
TagStart = /\{\%/
|
||||||
TagEnd = /\%\}/
|
TagEnd = /\%\}/
|
||||||
VariableSignature = /\(?[\w\-\.\[\]]\)?/
|
VariableSignature = /\(?[\w\-\.\[\]]\)?/
|
||||||
@@ -34,7 +35,7 @@ module Liquid
|
|||||||
QuotedString = /"[^"]*"|'[^']*'/
|
QuotedString = /"[^"]*"|'[^']*'/
|
||||||
QuotedFragment = /#{QuotedString}|(?:[^\s,\|'"]|#{QuotedString})+/o
|
QuotedFragment = /#{QuotedString}|(?:[^\s,\|'"]|#{QuotedString})+/o
|
||||||
TagAttributes = /(\w+)\s*\:\s*(#{QuotedFragment})/o
|
TagAttributes = /(\w+)\s*\:\s*(#{QuotedFragment})/o
|
||||||
AnyStartingTag = /\{\{|\{\%/
|
AnyStartingTag = /#{TagStart}|#{VariableStart}/o
|
||||||
PartialTemplateParser = /#{TagStart}.*?#{TagEnd}|#{VariableStart}.*?#{VariableIncompleteEnd}/om
|
PartialTemplateParser = /#{TagStart}.*?#{TagEnd}|#{VariableStart}.*?#{VariableIncompleteEnd}/om
|
||||||
TemplateParser = /(#{PartialTemplateParser}|#{AnyStartingTag})/om
|
TemplateParser = /(#{PartialTemplateParser}|#{AnyStartingTag})/om
|
||||||
VariableParser = /\[[^\]]+\]|#{VariableSegment}+\??/o
|
VariableParser = /\[[^\]]+\]|#{VariableSegment}+\??/o
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
module Liquid
|
module Liquid
|
||||||
class BlockBody
|
class BlockBody
|
||||||
FullToken = /\A#{TagStart}\s*(\w+)\s*(.*)?#{TagEnd}\z/om
|
FullToken = /\A#{TagStart}#{WhitespaceControl}?\s*(\w+)\s*(.*?)#{WhitespaceControl}?#{TagEnd}\z/om
|
||||||
ContentOfVariable = /\A#{VariableStart}(.*)#{VariableEnd}\z/om
|
ContentOfVariable = /\A#{VariableStart}#{WhitespaceControl}?(.*?)#{WhitespaceControl}?#{VariableEnd}\z/om
|
||||||
TAGSTART = "{%".freeze
|
TAGSTART = "{%".freeze
|
||||||
VARSTART = "{{".freeze
|
VARSTART = "{{".freeze
|
||||||
|
|
||||||
@@ -18,6 +18,7 @@ module Liquid
|
|||||||
unless token.empty?
|
unless token.empty?
|
||||||
case
|
case
|
||||||
when token.start_with?(TAGSTART)
|
when token.start_with?(TAGSTART)
|
||||||
|
whitespace_handler(token, parse_context)
|
||||||
if token =~ FullToken
|
if token =~ FullToken
|
||||||
tag_name = $1
|
tag_name = $1
|
||||||
markup = $2
|
markup = $2
|
||||||
@@ -35,9 +36,14 @@ module Liquid
|
|||||||
raise_missing_tag_terminator(token, parse_context)
|
raise_missing_tag_terminator(token, parse_context)
|
||||||
end
|
end
|
||||||
when token.start_with?(VARSTART)
|
when token.start_with?(VARSTART)
|
||||||
|
whitespace_handler(token, parse_context)
|
||||||
@nodelist << create_variable(token, parse_context)
|
@nodelist << create_variable(token, parse_context)
|
||||||
@blank = false
|
@blank = false
|
||||||
else
|
else
|
||||||
|
if parse_context.trim_whitespace
|
||||||
|
token.lstrip!
|
||||||
|
end
|
||||||
|
parse_context.trim_whitespace = false
|
||||||
@nodelist << token
|
@nodelist << token
|
||||||
@blank &&= !!(token =~ /\A\s*\z/)
|
@blank &&= !!(token =~ /\A\s*\z/)
|
||||||
end
|
end
|
||||||
@@ -48,6 +54,16 @@ module Liquid
|
|||||||
yield nil, nil
|
yield nil, nil
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def whitespace_handler(token, parse_context)
|
||||||
|
if token[2] == WhitespaceControl
|
||||||
|
previous_token = @nodelist.last
|
||||||
|
if previous_token.is_a? String
|
||||||
|
previous_token.rstrip!
|
||||||
|
end
|
||||||
|
end
|
||||||
|
parse_context.trim_whitespace = (token[-3] == WhitespaceControl)
|
||||||
|
end
|
||||||
|
|
||||||
def blank?
|
def blank?
|
||||||
@blank
|
@blank
|
||||||
end
|
end
|
||||||
@@ -80,7 +96,8 @@ module Liquid
|
|||||||
context.handle_error(e, token.line_number)
|
context.handle_error(e, token.line_number)
|
||||||
output << nil
|
output << nil
|
||||||
rescue ::StandardError => e
|
rescue ::StandardError => e
|
||||||
output << context.handle_error(e, token.line_number)
|
line_number = token.is_a?(String) ? nil : token.line_number
|
||||||
|
output << context.handle_error(e, line_number)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -90,7 +107,7 @@ module Liquid
|
|||||||
private
|
private
|
||||||
|
|
||||||
def render_node(node, context)
|
def render_node(node, context)
|
||||||
node_output = (node.respond_to?(:render) ? node.render(context) : node)
|
node_output = node.is_a?(String) ? node : node.render(context)
|
||||||
node_output = node_output.is_a?(Array) ? node_output.join : node_output.to_s
|
node_output = node_output.is_a?(Array) ? node_output.join : node_output.to_s
|
||||||
|
|
||||||
context.resource_limits.render_length += node_output.length
|
context.resource_limits.render_length += node_output.length
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ module Liquid
|
|||||||
# c.evaluate #=> true
|
# c.evaluate #=> true
|
||||||
#
|
#
|
||||||
class Condition #:nodoc:
|
class Condition #:nodoc:
|
||||||
|
@@depth = 0
|
||||||
@@operators = {
|
@@operators = {
|
||||||
'=='.freeze => ->(cond, left, right) { cond.send(:equal_variables, left, right) },
|
'=='.freeze => ->(cond, left, right) { cond.send(:equal_variables, left, right) },
|
||||||
'!='.freeze => ->(cond, left, right) { !cond.send(:equal_variables, left, right) },
|
'!='.freeze => ->(cond, left, right) { !cond.send(:equal_variables, left, right) },
|
||||||
@@ -47,6 +48,11 @@ module Liquid
|
|||||||
when :or
|
when :or
|
||||||
result || @child_condition.evaluate(context)
|
result || @child_condition.evaluate(context)
|
||||||
when :and
|
when :and
|
||||||
|
@@depth += 1
|
||||||
|
if @@depth >= 500
|
||||||
|
@@depth = 0
|
||||||
|
raise StackLevelError, "Nesting too deep".freeze
|
||||||
|
end
|
||||||
result && @child_condition.evaluate(context)
|
result && @child_condition.evaluate(context)
|
||||||
else
|
else
|
||||||
result
|
result
|
||||||
@@ -110,7 +116,7 @@ module Liquid
|
|||||||
|
|
||||||
if operation.respond_to?(:call)
|
if operation.respond_to?(:call)
|
||||||
operation.call(self, left, right)
|
operation.call(self, left, right)
|
||||||
elsif left.respond_to?(operation) && right.respond_to?(operation)
|
elsif left.respond_to?(operation) && right.respond_to?(operation) && !left.is_a?(Hash) && !right.is_a?(Hash)
|
||||||
begin
|
begin
|
||||||
left.send(operation, right)
|
left.send(operation, right)
|
||||||
rescue ::ArgumentError => e
|
rescue ::ArgumentError => e
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ module Liquid
|
|||||||
# context['bob'] #=> nil class Context
|
# context['bob'] #=> nil class Context
|
||||||
class Context
|
class Context
|
||||||
attr_reader :scopes, :errors, :registers, :environments, :resource_limits
|
attr_reader :scopes, :errors, :registers, :environments, :resource_limits
|
||||||
attr_accessor :exception_handler, :template_name, :partial, :global_filter, :strict_variables, :strict_filters
|
attr_accessor :exception_renderer, :template_name, :partial, :global_filter, :strict_variables, :strict_filters
|
||||||
|
|
||||||
def initialize(environments = {}, outer_scope = {}, registers = {}, rethrow_errors = false, resource_limits = nil)
|
def initialize(environments = {}, outer_scope = {}, registers = {}, rethrow_errors = false, resource_limits = nil)
|
||||||
@environments = [environments].flatten
|
@environments = [environments].flatten
|
||||||
@@ -27,8 +27,9 @@ module Liquid
|
|||||||
|
|
||||||
@this_stack_used = false
|
@this_stack_used = false
|
||||||
|
|
||||||
|
self.exception_renderer = Template.default_exception_renderer
|
||||||
if rethrow_errors
|
if rethrow_errors
|
||||||
self.exception_handler = ->(e) { raise }
|
self.exception_renderer = ->(e) { raise }
|
||||||
end
|
end
|
||||||
|
|
||||||
@interrupts = []
|
@interrupts = []
|
||||||
@@ -74,30 +75,11 @@ module Liquid
|
|||||||
end
|
end
|
||||||
|
|
||||||
def handle_error(e, line_number = nil)
|
def handle_error(e, line_number = nil)
|
||||||
if e.is_a?(Liquid::Error)
|
e = internal_error unless e.is_a?(Liquid::Error)
|
||||||
e.template_name ||= template_name
|
e.template_name ||= template_name
|
||||||
e.line_number ||= line_number
|
e.line_number ||= line_number
|
||||||
end
|
|
||||||
|
|
||||||
output = nil
|
|
||||||
|
|
||||||
if exception_handler
|
|
||||||
result = exception_handler.call(e)
|
|
||||||
case result
|
|
||||||
when Exception
|
|
||||||
e = result
|
|
||||||
if e.is_a?(Liquid::Error)
|
|
||||||
e.template_name ||= template_name
|
|
||||||
e.line_number ||= line_number
|
|
||||||
end
|
|
||||||
when String
|
|
||||||
output = result
|
|
||||||
else
|
|
||||||
raise if result
|
|
||||||
end
|
|
||||||
end
|
|
||||||
errors.push(e)
|
errors.push(e)
|
||||||
output || Liquid::Error.render(e)
|
exception_renderer.call(e).to_s
|
||||||
end
|
end
|
||||||
|
|
||||||
def invoke(method, *args)
|
def invoke(method, *args)
|
||||||
@@ -178,7 +160,7 @@ module Liquid
|
|||||||
end
|
end
|
||||||
|
|
||||||
# Fetches an object starting at the local scope and then moving up the hierachy
|
# Fetches an object starting at the local scope and then moving up the hierachy
|
||||||
def find_variable(key)
|
def find_variable(key, raise_on_not_found: true)
|
||||||
# This was changed from find() to find_index() because this is a very hot
|
# This was changed from find() to find_index() because this is a very hot
|
||||||
# path and find_index() is optimized in MRI to reduce object allocation
|
# path and find_index() is optimized in MRI to reduce object allocation
|
||||||
index = @scopes.find_index { |s| s.key?(key) }
|
index = @scopes.find_index { |s| s.key?(key) }
|
||||||
@@ -188,7 +170,7 @@ module Liquid
|
|||||||
|
|
||||||
if scope.nil?
|
if scope.nil?
|
||||||
@environments.each do |e|
|
@environments.each do |e|
|
||||||
variable = lookup_and_evaluate(e, key)
|
variable = lookup_and_evaluate(e, key, raise_on_not_found: raise_on_not_found)
|
||||||
unless variable.nil?
|
unless variable.nil?
|
||||||
scope = e
|
scope = e
|
||||||
break
|
break
|
||||||
@@ -197,7 +179,7 @@ module Liquid
|
|||||||
end
|
end
|
||||||
|
|
||||||
scope ||= @environments.last || @scopes.last
|
scope ||= @environments.last || @scopes.last
|
||||||
variable ||= lookup_and_evaluate(scope, key)
|
variable ||= lookup_and_evaluate(scope, key, raise_on_not_found: raise_on_not_found)
|
||||||
|
|
||||||
variable = variable.to_liquid
|
variable = variable.to_liquid
|
||||||
variable.context = self if variable.respond_to?(:context=)
|
variable.context = self if variable.respond_to?(:context=)
|
||||||
@@ -205,8 +187,8 @@ module Liquid
|
|||||||
variable
|
variable
|
||||||
end
|
end
|
||||||
|
|
||||||
def lookup_and_evaluate(obj, key)
|
def lookup_and_evaluate(obj, key, raise_on_not_found: true)
|
||||||
if @strict_variables && obj.respond_to?(:key?) && !obj.key?(key)
|
if @strict_variables && raise_on_not_found && obj.respond_to?(:key?) && !obj.key?(key)
|
||||||
raise Liquid::UndefinedVariable, "undefined variable #{key}"
|
raise Liquid::UndefinedVariable, "undefined variable #{key}"
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -221,6 +203,13 @@ module Liquid
|
|||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
def internal_error
|
||||||
|
# raise and catch to set backtrace and cause on exception
|
||||||
|
raise Liquid::InternalError, 'internal'
|
||||||
|
rescue Liquid::InternalError => exc
|
||||||
|
exc
|
||||||
|
end
|
||||||
|
|
||||||
def squash_instance_assigns_with_environments
|
def squash_instance_assigns_with_environments
|
||||||
@scopes.last.each_key do |k|
|
@scopes.last.each_key do |k|
|
||||||
@environments.each do |env|
|
@environments.each do |env|
|
||||||
|
|||||||
@@ -17,14 +17,6 @@ module Liquid
|
|||||||
str
|
str
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.render(e)
|
|
||||||
if e.is_a?(Liquid::Error)
|
|
||||||
e.to_s
|
|
||||||
else
|
|
||||||
"Liquid error: #{e}"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def message_prefix
|
def message_prefix
|
||||||
@@ -60,4 +52,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)
|
||||||
|
InternalError = Class.new(Error)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ module Liquid
|
|||||||
#
|
#
|
||||||
# Example:
|
# Example:
|
||||||
#
|
#
|
||||||
# Liquid::Template.file_system = Liquid::LocalFileSystem.new(template_path)
|
# Liquid::Template.file_system = Liquid::LocalFileSystem.new(template_path)
|
||||||
# liquid = Liquid::Template.parse(template)
|
# liquid = Liquid::Template.parse(template)
|
||||||
#
|
#
|
||||||
# This will parse the template with a LocalFileSystem implementation rooted at 'template_path'.
|
# This will parse the template with a LocalFileSystem implementation rooted at 'template_path'.
|
||||||
class BlankFileSystem
|
class BlankFileSystem
|
||||||
@@ -26,10 +26,10 @@ module Liquid
|
|||||||
#
|
#
|
||||||
# Example:
|
# Example:
|
||||||
#
|
#
|
||||||
# file_system = Liquid::LocalFileSystem.new("/some/path")
|
# file_system = Liquid::LocalFileSystem.new("/some/path")
|
||||||
#
|
#
|
||||||
# file_system.full_path("mypartial") # => "/some/path/_mypartial.liquid"
|
# file_system.full_path("mypartial") # => "/some/path/_mypartial.liquid"
|
||||||
# file_system.full_path("dir/mypartial") # => "/some/path/dir/_mypartial.liquid"
|
# file_system.full_path("dir/mypartial") # => "/some/path/dir/_mypartial.liquid"
|
||||||
#
|
#
|
||||||
# Optionally in the second argument you can specify a custom pattern for template filenames.
|
# Optionally in the second argument you can specify a custom pattern for template filenames.
|
||||||
# The Kernel::sprintf format specification is used.
|
# The Kernel::sprintf format specification is used.
|
||||||
@@ -37,9 +37,9 @@ module Liquid
|
|||||||
#
|
#
|
||||||
# Example:
|
# Example:
|
||||||
#
|
#
|
||||||
# file_system = Liquid::LocalFileSystem.new("/some/path", "%s.html")
|
# file_system = Liquid::LocalFileSystem.new("/some/path", "%s.html")
|
||||||
#
|
#
|
||||||
# file_system.full_path("index") # => "/some/path/index.html"
|
# file_system.full_path("index") # => "/some/path/index.html"
|
||||||
#
|
#
|
||||||
class LocalFileSystem
|
class LocalFileSystem
|
||||||
attr_accessor :root
|
attr_accessor :root
|
||||||
|
|||||||
@@ -18,10 +18,10 @@ module Liquid
|
|||||||
DOUBLE_STRING_LITERAL = /"[^\"]*"/
|
DOUBLE_STRING_LITERAL = /"[^\"]*"/
|
||||||
NUMBER_LITERAL = /-?\d+(\.\d+)?/
|
NUMBER_LITERAL = /-?\d+(\.\d+)?/
|
||||||
DOTDOT = /\.\./
|
DOTDOT = /\.\./
|
||||||
COMPARISON_OPERATOR = /==|!=|<>|<=?|>=?|contains/
|
COMPARISON_OPERATOR = /==|!=|<>|<=?|>=?|contains(?=\s)/
|
||||||
|
|
||||||
def initialize(input)
|
def initialize(input)
|
||||||
@ss = StringScanner.new(input.rstrip)
|
@ss = StringScanner.new(input)
|
||||||
end
|
end
|
||||||
|
|
||||||
def tokenize
|
def tokenize
|
||||||
@@ -29,6 +29,7 @@ module Liquid
|
|||||||
|
|
||||||
until @ss.eos?
|
until @ss.eos?
|
||||||
@ss.skip(/\s*/)
|
@ss.skip(/\s*/)
|
||||||
|
break if @ss.eos?
|
||||||
tok = case
|
tok = case
|
||||||
when t = @ss.scan(COMPARISON_OPERATOR) then [:comparison, t]
|
when t = @ss.scan(COMPARISON_OPERATOR) then [:comparison, t]
|
||||||
when t = @ss.scan(SINGLE_STRING_LITERAL) then [:string, t]
|
when t = @ss.scan(SINGLE_STRING_LITERAL) then [:string, t]
|
||||||
|
|||||||
@@ -22,3 +22,5 @@
|
|||||||
tag_never_closed: "'%{block_name}' tag was never closed"
|
tag_never_closed: "'%{block_name}' tag was never closed"
|
||||||
meta_syntax_error: "Liquid syntax error: #{e.message}"
|
meta_syntax_error: "Liquid syntax error: #{e.message}"
|
||||||
table_row: "Syntax Error in 'table_row loop' - Valid syntax: table_row [item] in [collection] cols=3"
|
table_row: "Syntax Error in 'table_row loop' - Valid syntax: table_row [item] in [collection] cols=3"
|
||||||
|
argument:
|
||||||
|
include: "Argument error in tag 'include' - Illegal template name"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
module Liquid
|
module Liquid
|
||||||
class ParseContext
|
class ParseContext
|
||||||
attr_accessor :locale, :line_number
|
attr_accessor :locale, :line_number, :trim_whitespace
|
||||||
attr_reader :partial, :warnings, :error_mode
|
attr_reader :partial, :warnings, :error_mode
|
||||||
|
|
||||||
def initialize(options = {})
|
def initialize(options = {})
|
||||||
|
|||||||
@@ -65,9 +65,10 @@ module Liquid
|
|||||||
return if input.nil?
|
return if input.nil?
|
||||||
input_str = input.to_s
|
input_str = input.to_s
|
||||||
length = Utils.to_integer(length)
|
length = Utils.to_integer(length)
|
||||||
l = length - truncate_string.length
|
truncate_string_str = truncate_string.to_s
|
||||||
|
l = length - truncate_string_str.length
|
||||||
l = 0 if l < 0
|
l = 0 if l < 0
|
||||||
input_str.length > length ? input_str[0...l] + truncate_string : input_str
|
input_str.length > length ? input_str[0...l] + truncate_string_str : input_str
|
||||||
end
|
end
|
||||||
|
|
||||||
def truncatewords(input, words = 15, truncate_string = "...".freeze)
|
def truncatewords(input, words = 15, truncate_string = "...".freeze)
|
||||||
@@ -76,7 +77,7 @@ module Liquid
|
|||||||
words = Utils.to_integer(words)
|
words = Utils.to_integer(words)
|
||||||
l = words - 1
|
l = words - 1
|
||||||
l = 0 if l < 0
|
l = 0 if l < 0
|
||||||
wordlist.length > l ? wordlist[0..l].join(" ".freeze) + truncate_string : input
|
wordlist.length > l ? wordlist[0..l].join(" ".freeze) + truncate_string.to_s : input
|
||||||
end
|
end
|
||||||
|
|
||||||
# Split input string into an array of substrings separated by given pattern.
|
# Split input string into an array of substrings separated by given pattern.
|
||||||
@@ -85,7 +86,7 @@ module Liquid
|
|||||||
# <div class="summary">{{ post | split '//' | first }}</div>
|
# <div class="summary">{{ post | split '//' | first }}</div>
|
||||||
#
|
#
|
||||||
def split(input, pattern)
|
def split(input, pattern)
|
||||||
input.to_s.split(pattern)
|
input.to_s.split(pattern.to_s)
|
||||||
end
|
end
|
||||||
|
|
||||||
def strip(input)
|
def strip(input)
|
||||||
@@ -124,7 +125,15 @@ module Liquid
|
|||||||
elsif ary.empty? # The next two cases assume a non-empty array.
|
elsif ary.empty? # The next two cases assume a non-empty array.
|
||||||
[]
|
[]
|
||||||
elsif ary.first.respond_to?(:[]) && !ary.first[property].nil?
|
elsif ary.first.respond_to?(:[]) && !ary.first[property].nil?
|
||||||
ary.sort { |a, b| a[property] <=> b[property] }
|
ary.sort do |a, b|
|
||||||
|
a = a[property]
|
||||||
|
b = b[property]
|
||||||
|
if a && b
|
||||||
|
a <=> b
|
||||||
|
else
|
||||||
|
a ? -1 : 1
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -375,7 +384,7 @@ module Liquid
|
|||||||
end
|
end
|
||||||
|
|
||||||
def join(glue)
|
def join(glue)
|
||||||
to_a.join(glue)
|
to_a.join(glue.to_s)
|
||||||
end
|
end
|
||||||
|
|
||||||
def concat(args)
|
def concat(args)
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ module Liquid
|
|||||||
|
|
||||||
def self.add_filter(filter)
|
def self.add_filter(filter)
|
||||||
raise ArgumentError, "Expected module but got: #{filter.class}" unless filter.is_a?(Module)
|
raise ArgumentError, "Expected module but got: #{filter.class}" unless filter.is_a?(Module)
|
||||||
unless self.class.include?(filter)
|
unless self.include?(filter)
|
||||||
invokable_non_public_methods = (filter.private_instance_methods + filter.protected_instance_methods).select { |m| invokable?(m) }
|
invokable_non_public_methods = (filter.private_instance_methods + filter.protected_instance_methods).select { |m| invokable?(m) }
|
||||||
if invokable_non_public_methods.any?
|
if invokable_non_public_methods.any?
|
||||||
raise MethodOverrideError, "Filter overrides registered public methods as non public: #{invokable_non_public_methods.join(', ')}"
|
raise MethodOverrideError, "Filter overrides registered public methods as non public: #{invokable_non_public_methods.join(', ')}"
|
||||||
@@ -39,6 +39,7 @@ module Liquid
|
|||||||
end
|
end
|
||||||
|
|
||||||
def self.global_filter(filter)
|
def self.global_filter(filter)
|
||||||
|
@@strainer_class_cache.clear
|
||||||
@@global_strainer.add_filter(filter)
|
@@global_strainer.add_filter(filter)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ module Liquid
|
|||||||
# {{ item.name }}
|
# {{ item.name }}
|
||||||
# {% end %}
|
# {% end %}
|
||||||
#
|
#
|
||||||
# To reverse the for loop simply use {% for item in collection reversed %}
|
# To reverse the for loop simply use {% for item in collection reversed %} (note that the flag's spelling is different to the filter `reverse`)
|
||||||
#
|
#
|
||||||
# == Available variables:
|
# == Available variables:
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -42,14 +42,15 @@ module Liquid
|
|||||||
|
|
||||||
def render(context)
|
def render(context)
|
||||||
template_name = context.evaluate(@template_name_expr)
|
template_name = context.evaluate(@template_name_expr)
|
||||||
partial = load_cached_partial(template_name, context)
|
raise ArgumentError.new(options[:locale].t("errors.argument.include")) unless template_name
|
||||||
|
|
||||||
|
partial = load_cached_partial(template_name, context)
|
||||||
context_variable_name = template_name.split('/'.freeze).last
|
context_variable_name = template_name.split('/'.freeze).last
|
||||||
|
|
||||||
variable = if @variable_name_expr
|
variable = if @variable_name_expr
|
||||||
context.evaluate(@variable_name_expr)
|
context.evaluate(@variable_name_expr)
|
||||||
else
|
else
|
||||||
context.find_variable(template_name)
|
context.find_variable(template_name, raise_on_not_found: false)
|
||||||
end
|
end
|
||||||
|
|
||||||
old_template_name = context.template_name
|
old_template_name = context.template_name
|
||||||
|
|||||||
@@ -69,6 +69,11 @@ module Liquid
|
|||||||
# :error raises an error when tainted output is used
|
# :error raises an error when tainted output is used
|
||||||
attr_writer :taint_mode
|
attr_writer :taint_mode
|
||||||
|
|
||||||
|
attr_accessor :default_exception_renderer
|
||||||
|
Template.default_exception_renderer = lambda do |exception|
|
||||||
|
exception
|
||||||
|
end
|
||||||
|
|
||||||
def file_system
|
def file_system
|
||||||
@@file_system
|
@@file_system
|
||||||
end
|
end
|
||||||
@@ -167,7 +172,7 @@ module Liquid
|
|||||||
c = args.shift
|
c = args.shift
|
||||||
|
|
||||||
if @rethrow_errors
|
if @rethrow_errors
|
||||||
c.exception_handler = ->(e) { raise }
|
c.exception_renderer = ->(e) { raise }
|
||||||
end
|
end
|
||||||
|
|
||||||
c
|
c
|
||||||
@@ -241,7 +246,7 @@ module Liquid
|
|||||||
def apply_options_to_context(context, options)
|
def apply_options_to_context(context, options)
|
||||||
context.add_filters(options[:filters]) if options[:filters]
|
context.add_filters(options[:filters]) if options[:filters]
|
||||||
context.global_filter = options[:global_filter] if options[:global_filter]
|
context.global_filter = options[:global_filter] if options[:global_filter]
|
||||||
context.exception_handler = options[:exception_handler] if options[:exception_handler]
|
context.exception_renderer = options[:exception_renderer] if options[:exception_renderer]
|
||||||
context.strict_variables = options[:strict_variables] if options[:strict_variables]
|
context.strict_variables = options[:strict_variables] if options[:strict_variables]
|
||||||
context.strict_filters = options[:strict_filters] if options[:strict_filters]
|
context.strict_filters = options[:strict_filters] if options[:strict_filters]
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# encoding: utf-8
|
# encoding: utf-8
|
||||||
module Liquid
|
module Liquid
|
||||||
VERSION = "4.0.0.rc2"
|
VERSION = "4.0.0"
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ Gem::Specification.new do |s|
|
|||||||
s.license = "MIT"
|
s.license = "MIT"
|
||||||
# s.description = "A secure, non-evaling end user template engine with aesthetic markup."
|
# s.description = "A secure, non-evaling end user template engine with aesthetic markup."
|
||||||
|
|
||||||
|
s.required_ruby_version = ">= 2.1.0"
|
||||||
s.required_rubygems_version = ">= 1.3.7"
|
s.required_rubygems_version = ">= 1.3.7"
|
||||||
|
|
||||||
s.test_files = Dir.glob("{test}/**/*")
|
s.test_files = Dir.glob("{test}/**/*")
|
||||||
@@ -24,6 +25,6 @@ Gem::Specification.new do |s|
|
|||||||
|
|
||||||
s.require_path = "lib"
|
s.require_path = "lib"
|
||||||
|
|
||||||
s.add_development_dependency 'rake'
|
s.add_development_dependency 'rake', '~> 11.3'
|
||||||
s.add_development_dependency 'minitest'
|
s.add_development_dependency 'minitest'
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ Liquid::Template.error_mode = ARGV.first.to_sym if ARGV.first
|
|||||||
profiler = ThemeRunner.new
|
profiler = ThemeRunner.new
|
||||||
|
|
||||||
Benchmark.ips do |x|
|
Benchmark.ips do |x|
|
||||||
x.time = 60
|
x.time = 10
|
||||||
x.warmup = 5
|
x.warmup = 5
|
||||||
|
|
||||||
puts
|
puts
|
||||||
@@ -13,5 +13,6 @@ Benchmark.ips do |x|
|
|||||||
puts
|
puts
|
||||||
|
|
||||||
x.report("parse:") { profiler.compile }
|
x.report("parse:") { profiler.compile }
|
||||||
x.report("parse & run:") { profiler.run }
|
x.report("render:") { profiler.render }
|
||||||
|
x.report("parse & render:") { profiler.run }
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -21,53 +21,100 @@ class ThemeRunner
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Load all templates into memory, do this now so that
|
# Initialize a new liquid ThemeRunner instance
|
||||||
# we don't profile IO.
|
# Will load all templates into memory, do this now so that we don't profile IO.
|
||||||
def initialize
|
def initialize
|
||||||
@tests = Dir[__dir__ + '/tests/**/*.liquid'].collect do |test|
|
@tests = Dir[__dir__ + '/tests/**/*.liquid'].collect do |test|
|
||||||
next if File.basename(test) == 'theme.liquid'
|
next if File.basename(test) == 'theme.liquid'
|
||||||
|
|
||||||
theme_path = File.dirname(test) + '/theme.liquid'
|
theme_path = File.dirname(test) + '/theme.liquid'
|
||||||
|
{
|
||||||
[File.read(test), (File.file?(theme_path) ? File.read(theme_path) : nil), test]
|
liquid: File.read(test),
|
||||||
|
layout: (File.file?(theme_path) ? File.read(theme_path) : nil),
|
||||||
|
template_name: test
|
||||||
|
}
|
||||||
end.compact
|
end.compact
|
||||||
|
|
||||||
|
compile_all_tests
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# `compile` will test just the compilation portion of liquid without any templates
|
||||||
def compile
|
def compile
|
||||||
# Dup assigns because will make some changes to them
|
@tests.each do |test_hash|
|
||||||
|
Liquid::Template.new.parse(test_hash[:liquid])
|
||||||
@tests.each do |liquid, layout, template_name|
|
Liquid::Template.new.parse(test_hash[:layout])
|
||||||
tmpl = Liquid::Template.new
|
|
||||||
tmpl.parse(liquid)
|
|
||||||
tmpl = Liquid::Template.new
|
|
||||||
tmpl.parse(layout)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# `run` is called to benchmark rendering and compiling at the same time
|
||||||
def run
|
def run
|
||||||
# Dup assigns because will make some changes to them
|
each_test do |liquid, layout, assigns, page_template, template_name|
|
||||||
assigns = Database.tables.dup
|
|
||||||
|
|
||||||
@tests.each do |liquid, layout, template_name|
|
|
||||||
# Compute page_tempalte outside of profiler run, uninteresting to profiler
|
|
||||||
page_template = File.basename(template_name, File.extname(template_name))
|
|
||||||
compile_and_render(liquid, layout, assigns, page_template, template_name)
|
compile_and_render(liquid, layout, assigns, page_template, template_name)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# `render` is called to benchmark just the render portion of liquid
|
||||||
|
def render
|
||||||
|
@compiled_tests.each do |test|
|
||||||
|
tmpl = test[:tmpl]
|
||||||
|
assigns = test[:assigns]
|
||||||
|
layout = test[:layout]
|
||||||
|
|
||||||
|
if layout
|
||||||
|
assigns['content_for_layout'] = tmpl.render!(assigns)
|
||||||
|
layout.render!(assigns)
|
||||||
|
else
|
||||||
|
tmpl.render!(assigns)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
def compile_and_render(template, layout, assigns, page_template, template_file)
|
def compile_and_render(template, layout, assigns, page_template, template_file)
|
||||||
|
compiled_test = compile_test(template, layout, assigns, page_template, template_file)
|
||||||
|
assigns['content_for_layout'] = compiled_test[:tmpl].render!(assigns)
|
||||||
|
compiled_test[:layout].render!(assigns) if layout
|
||||||
|
end
|
||||||
|
|
||||||
|
def compile_all_tests
|
||||||
|
@compiled_tests = []
|
||||||
|
each_test do |liquid, layout, assigns, page_template, template_name|
|
||||||
|
@compiled_tests << compile_test(liquid, layout, assigns, page_template, template_name)
|
||||||
|
end
|
||||||
|
@compiled_tests
|
||||||
|
end
|
||||||
|
|
||||||
|
def compile_test(template, layout, assigns, page_template, template_file)
|
||||||
|
tmpl = init_template(page_template, template_file)
|
||||||
|
parsed_template = tmpl.parse(template).dup
|
||||||
|
|
||||||
|
if layout
|
||||||
|
parsed_layout = tmpl.parse(layout)
|
||||||
|
{ tmpl: parsed_template, assigns: assigns, layout: parsed_layout }
|
||||||
|
else
|
||||||
|
{ tmpl: parsed_template, assigns: assigns }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# utility method with similar functionality needed in `compile_all_tests` and `run`
|
||||||
|
def each_test
|
||||||
|
# Dup assigns because will make some changes to them
|
||||||
|
assigns = Database.tables.dup
|
||||||
|
|
||||||
|
@tests.each do |test_hash|
|
||||||
|
# Compute page_template outside of profiler run, uninteresting to profiler
|
||||||
|
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])
|
||||||
|
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 = Liquid::Template.new
|
||||||
tmpl.assigns['page_title'] = 'Page title'
|
tmpl.assigns['page_title'] = 'Page title'
|
||||||
tmpl.assigns['template'] = page_template
|
tmpl.assigns['template'] = page_template
|
||||||
tmpl.registers[:file_system] = ThemeRunner::FileSystem.new(File.dirname(template_file))
|
tmpl.registers[:file_system] = ThemeRunner::FileSystem.new(File.dirname(template_file))
|
||||||
|
tmpl
|
||||||
content_for_layout = tmpl.parse(template).render!(assigns)
|
|
||||||
|
|
||||||
if layout
|
|
||||||
assigns['content_for_layout'] = content_for_layout
|
|
||||||
tmpl.parse(layout).render!(assigns)
|
|
||||||
else
|
|
||||||
content_for_layout
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -97,6 +97,22 @@ class ErrorHandlingTest < Minitest::Test
|
|||||||
assert_match(/Liquid syntax error \(line 4\)/, err.message)
|
assert_match(/Liquid syntax error \(line 4\)/, err.message)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_with_line_numbers_adds_numbers_to_parser_errors_with_whitespace_trim
|
||||||
|
err = assert_raises(SyntaxError) do
|
||||||
|
Liquid::Template.parse(%q(
|
||||||
|
foobar
|
||||||
|
|
||||||
|
{%- "cat" | foobar -%}
|
||||||
|
|
||||||
|
bla
|
||||||
|
),
|
||||||
|
line_numbers: true
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
assert_match(/Liquid syntax error \(line 4\)/, err.message)
|
||||||
|
end
|
||||||
|
|
||||||
def test_parsing_warn_with_line_numbers_adds_numbers_to_lexer_errors
|
def test_parsing_warn_with_line_numbers_adds_numbers_to_lexer_errors
|
||||||
template = Liquid::Template.parse('
|
template = Liquid::Template.parse('
|
||||||
foobar
|
foobar
|
||||||
@@ -186,22 +202,40 @@ class ErrorHandlingTest < Minitest::Test
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_exception_handler_with_string_result
|
def test_default_exception_renderer_with_internal_error
|
||||||
template = Liquid::Template.parse('This is an argument error: {{ errors.argument_error }}')
|
|
||||||
output = template.render({ 'errors' => ErrorDrop.new }, exception_handler: ->(e) { '' })
|
|
||||||
assert_equal 'This is an argument error: ', output
|
|
||||||
assert_equal [ArgumentError], template.errors.map(&:class)
|
|
||||||
end
|
|
||||||
|
|
||||||
class InternalError < Liquid::Error
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_exception_handler_with_exception_result
|
|
||||||
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)
|
||||||
handler = ->(e) { e.is_a?(Liquid::Error) ? e : InternalError.new('internal') }
|
|
||||||
output = template.render({ 'errors' => ErrorDrop.new }, exception_handler: handler)
|
output = template.render({ 'errors' => ErrorDrop.new })
|
||||||
|
|
||||||
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 [InternalError], template.errors.map(&:class)
|
assert_equal [Liquid::InternalError], template.errors.map(&:class)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_setting_default_exception_renderer
|
||||||
|
old_exception_renderer = Liquid::Template.default_exception_renderer
|
||||||
|
exceptions = []
|
||||||
|
Liquid::Template.default_exception_renderer = ->(e) { exceptions << e; '' }
|
||||||
|
template = Liquid::Template.parse('This is a runtime error: {{ errors.argument_error }}')
|
||||||
|
|
||||||
|
output = template.render({ 'errors' => ErrorDrop.new })
|
||||||
|
|
||||||
|
assert_equal 'This is a runtime error: ', output
|
||||||
|
assert_equal [Liquid::ArgumentError], template.errors.map(&:class)
|
||||||
|
ensure
|
||||||
|
Liquid::Template.default_exception_renderer = old_exception_renderer if old_exception_renderer
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_exception_renderer_exposing_non_liquid_error
|
||||||
|
template = Liquid::Template.parse('This is a runtime error: {{ errors.runtime_error }}', line_numbers: true)
|
||||||
|
exceptions = []
|
||||||
|
handler = ->(e) { exceptions << e; e.cause }
|
||||||
|
|
||||||
|
output = template.render({ 'errors' => ErrorDrop.new }, exception_renderer: handler)
|
||||||
|
|
||||||
|
assert_equal 'This is a runtime error: runtime error', output
|
||||||
|
assert_equal [Liquid::InternalError], exceptions.map(&:class)
|
||||||
|
assert_equal exceptions, template.errors
|
||||||
|
assert_equal '#<RuntimeError: runtime error>', exceptions.first.cause.inspect
|
||||||
end
|
end
|
||||||
|
|
||||||
class TestFileSystem
|
class TestFileSystem
|
||||||
|
|||||||
@@ -115,4 +115,8 @@ class ParsingQuirksTest < Minitest::Test
|
|||||||
assert_template_result('12345', "{% for i in (1...5) %}{{ i }}{% endfor %}")
|
assert_template_result('12345', "{% for i in (1...5) %}{{ i }}{% endfor %}")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_contains_in_id
|
||||||
|
assert_template_result(' YES ', '{% if containsallshipments == true %} YES {% endif %}', 'containsallshipments' => true)
|
||||||
|
end
|
||||||
end # ParsingQuirksTest
|
end # ParsingQuirksTest
|
||||||
|
|||||||
@@ -115,15 +115,15 @@ class StandardFiltersTest < Minitest::Test
|
|||||||
assert_equal '...', @filters.truncate('1234567890', 0)
|
assert_equal '...', @filters.truncate('1234567890', 0)
|
||||||
assert_equal '1234567890', @filters.truncate('1234567890')
|
assert_equal '1234567890', @filters.truncate('1234567890')
|
||||||
assert_equal "测试...", @filters.truncate("测试测试测试测试", 5)
|
assert_equal "测试...", @filters.truncate("测试测试测试测试", 5)
|
||||||
|
assert_equal '12341', @filters.truncate("1234567890", 5, 1)
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_split
|
def test_split
|
||||||
assert_equal ['12', '34'], @filters.split('12~34', '~')
|
assert_equal ['12', '34'], @filters.split('12~34', '~')
|
||||||
assert_equal ['A? ', ' ,Z'], @filters.split('A? ~ ~ ~ ,Z', '~ ~ ~')
|
assert_equal ['A? ', ' ,Z'], @filters.split('A? ~ ~ ~ ,Z', '~ ~ ~')
|
||||||
assert_equal ['A?Z'], @filters.split('A?Z', '~')
|
assert_equal ['A?Z'], @filters.split('A?Z', '~')
|
||||||
# Regexp works although Liquid does not support.
|
|
||||||
assert_equal ['A', 'Z'], @filters.split('AxZ', /x/)
|
|
||||||
assert_equal [], @filters.split(nil, ' ')
|
assert_equal [], @filters.split(nil, ' ')
|
||||||
|
assert_equal ['A', 'Z'], @filters.split('A1Z', 1)
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_escape
|
def test_escape
|
||||||
@@ -154,6 +154,7 @@ class StandardFiltersTest < Minitest::Test
|
|||||||
assert_equal 'one two three', @filters.truncatewords('one two three')
|
assert_equal 'one two three', @filters.truncatewords('one two three')
|
||||||
assert_equal 'Two small (13” x 5.5” x 10” high) baskets fit inside one large basket (13”...', @filters.truncatewords('Two small (13” x 5.5” x 10” high) baskets fit inside one large basket (13” x 16” x 10.5” high) with cover.', 15)
|
assert_equal 'Two small (13” x 5.5” x 10” high) baskets fit inside one large basket (13”...', @filters.truncatewords('Two small (13” x 5.5” x 10” high) baskets fit inside one large basket (13” x 16” x 10.5” high) with cover.', 15)
|
||||||
assert_equal "测试测试测试测试", @filters.truncatewords('测试测试测试测试', 5)
|
assert_equal "测试测试测试测试", @filters.truncatewords('测试测试测试测试', 5)
|
||||||
|
assert_equal 'one two1', @filters.truncatewords("one two three", 2, 1)
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_strip_html
|
def test_strip_html
|
||||||
@@ -169,6 +170,7 @@ class StandardFiltersTest < Minitest::Test
|
|||||||
def test_join
|
def test_join
|
||||||
assert_equal '1 2 3 4', @filters.join([1, 2, 3, 4])
|
assert_equal '1 2 3 4', @filters.join([1, 2, 3, 4])
|
||||||
assert_equal '1 - 2 - 3 - 4', @filters.join([1, 2, 3, 4], ' - ')
|
assert_equal '1 - 2 - 3 - 4', @filters.join([1, 2, 3, 4], ' - ')
|
||||||
|
assert_equal '1121314', @filters.join([1, 2, 3, 4], 1)
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_sort
|
def test_sort
|
||||||
@@ -176,6 +178,24 @@ class StandardFiltersTest < Minitest::Test
|
|||||||
assert_equal [{ "a" => 1 }, { "a" => 2 }, { "a" => 3 }, { "a" => 4 }], @filters.sort([{ "a" => 4 }, { "a" => 3 }, { "a" => 1 }, { "a" => 2 }], "a")
|
assert_equal [{ "a" => 1 }, { "a" => 2 }, { "a" => 3 }, { "a" => 4 }], @filters.sort([{ "a" => 4 }, { "a" => 3 }, { "a" => 1 }, { "a" => 2 }], "a")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_sort_when_property_is_sometimes_missing_puts_nils_last
|
||||||
|
input = [
|
||||||
|
{ "price" => 4, "handle" => "alpha" },
|
||||||
|
{ "handle" => "beta" },
|
||||||
|
{ "price" => 1, "handle" => "gamma" },
|
||||||
|
{ "handle" => "delta" },
|
||||||
|
{ "price" => 2, "handle" => "epsilon" }
|
||||||
|
]
|
||||||
|
expectation = [
|
||||||
|
{ "price" => 1, "handle" => "gamma" },
|
||||||
|
{ "price" => 2, "handle" => "epsilon" },
|
||||||
|
{ "price" => 4, "handle" => "alpha" },
|
||||||
|
{ "handle" => "delta" },
|
||||||
|
{ "handle" => "beta" }
|
||||||
|
]
|
||||||
|
assert_equal expectation, @filters.sort(input, "price")
|
||||||
|
end
|
||||||
|
|
||||||
def test_sort_empty_array
|
def test_sort_empty_array
|
||||||
assert_equal [], @filters.sort([], "a")
|
assert_equal [], @filters.sort([], "a")
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -217,6 +217,17 @@ class IncludeTagTest < Minitest::Test
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_render_raise_argument_error_when_template_is_undefined
|
||||||
|
assert_raises(Liquid::ArgumentError) do
|
||||||
|
template = Liquid::Template.parse('{% include undefined_variable %}')
|
||||||
|
template.render!
|
||||||
|
end
|
||||||
|
assert_raises(Liquid::ArgumentError) do
|
||||||
|
template = Liquid::Template.parse('{% include nil %}')
|
||||||
|
template.render!
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def test_including_via_variable_value
|
def test_including_via_variable_value
|
||||||
assert_template_result "from TestFileSystem", "{% assign page = 'pick_a_source' %}{% include page %}"
|
assert_template_result "from TestFileSystem", "{% assign page = 'pick_a_source' %}{% include page %}"
|
||||||
|
|
||||||
@@ -224,4 +235,11 @@ class IncludeTagTest < Minitest::Test
|
|||||||
|
|
||||||
assert_template_result "Product: Draft 151cm ", "{% assign page = 'product' %}{% include page for foo %}", "foo" => { 'title' => 'Draft 151cm' }
|
assert_template_result "Product: Draft 151cm ", "{% assign page = 'product' %}{% include page for foo %}", "foo" => { 'title' => 'Draft 151cm' }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_including_with_strict_variables
|
||||||
|
template = Liquid::Template.parse("{% include 'simple' %}", error_mode: :warn)
|
||||||
|
template.render(nil, strict_variables: true)
|
||||||
|
|
||||||
|
assert_equal [], template.errors
|
||||||
|
end
|
||||||
end # IncludeTagTest
|
end # IncludeTagTest
|
||||||
|
|||||||
@@ -215,16 +215,20 @@ class TemplateTest < Minitest::Test
|
|||||||
assert_equal 'ruby error in drop', e.message
|
assert_equal 'ruby error in drop', e.message
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_exception_handler_doesnt_reraise_if_it_returns_false
|
def test_exception_renderer_that_returns_string
|
||||||
exception = nil
|
exception = nil
|
||||||
Template.parse("{{ 1 | divided_by: 0 }}").render({}, exception_handler: ->(e) { exception = e; false })
|
handler = ->(e) { exception = e; '<!-- error -->' }
|
||||||
|
|
||||||
|
output = Template.parse("{{ 1 | divided_by: 0 }}").render({}, exception_renderer: handler)
|
||||||
|
|
||||||
assert exception.is_a?(Liquid::ZeroDivisionError)
|
assert exception.is_a?(Liquid::ZeroDivisionError)
|
||||||
|
assert_equal '<!-- error -->', output
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_exception_handler_does_reraise_if_it_returns_true
|
def test_exception_renderer_that_raises
|
||||||
exception = nil
|
exception = nil
|
||||||
assert_raises(Liquid::ZeroDivisionError) do
|
assert_raises(Liquid::ZeroDivisionError) do
|
||||||
Template.parse("{{ 1 | divided_by: 0 }}").render({}, exception_handler: ->(e) { exception = e; true })
|
Template.parse("{{ 1 | divided_by: 0 }}").render({}, exception_renderer: ->(e) { exception = e; raise })
|
||||||
end
|
end
|
||||||
assert exception.is_a?(Liquid::ZeroDivisionError)
|
assert exception.is_a?(Liquid::ZeroDivisionError)
|
||||||
end
|
end
|
||||||
|
|||||||
525
test/integration/trim_mode_test.rb
Normal file
525
test/integration/trim_mode_test.rb
Normal file
@@ -0,0 +1,525 @@
|
|||||||
|
require 'test_helper'
|
||||||
|
|
||||||
|
class TrimModeTest < Minitest::Test
|
||||||
|
include Liquid
|
||||||
|
|
||||||
|
# Make sure the trim isn't applied to standard output
|
||||||
|
def test_standard_output
|
||||||
|
text = <<-END_TEMPLATE
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
{{ 'John' }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
END_TEMPLATE
|
||||||
|
expected = <<-END_EXPECTED
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
John
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
END_EXPECTED
|
||||||
|
assert_template_result(expected, text)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_variable_output_with_multiple_blank_lines
|
||||||
|
text = <<-END_TEMPLATE
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
|
||||||
|
|
||||||
|
{{- 'John' -}}
|
||||||
|
|
||||||
|
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
END_TEMPLATE
|
||||||
|
expected = <<-END_EXPECTED
|
||||||
|
<div>
|
||||||
|
<p>John</p>
|
||||||
|
</div>
|
||||||
|
END_EXPECTED
|
||||||
|
assert_template_result(expected, text)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_tag_output_with_multiple_blank_lines
|
||||||
|
text = <<-END_TEMPLATE
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
|
||||||
|
|
||||||
|
{%- if true -%}
|
||||||
|
yes
|
||||||
|
{%- endif -%}
|
||||||
|
|
||||||
|
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
END_TEMPLATE
|
||||||
|
expected = <<-END_EXPECTED
|
||||||
|
<div>
|
||||||
|
<p>yes</p>
|
||||||
|
</div>
|
||||||
|
END_EXPECTED
|
||||||
|
assert_template_result(expected, text)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Make sure the trim isn't applied to standard tags
|
||||||
|
def test_standard_tags
|
||||||
|
whitespace = ' '
|
||||||
|
text = <<-END_TEMPLATE
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
{% if true %}
|
||||||
|
yes
|
||||||
|
{% endif %}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
END_TEMPLATE
|
||||||
|
expected = <<-END_EXPECTED
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
#{whitespace}
|
||||||
|
yes
|
||||||
|
#{whitespace}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
END_EXPECTED
|
||||||
|
assert_template_result(expected, text)
|
||||||
|
|
||||||
|
text = <<-END_TEMPLATE
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
{% if false %}
|
||||||
|
no
|
||||||
|
{% endif %}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
END_TEMPLATE
|
||||||
|
expected = <<-END_EXPECTED
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
#{whitespace}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
END_EXPECTED
|
||||||
|
assert_template_result(expected, text)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Make sure the trim isn't too agressive
|
||||||
|
def test_no_trim_output
|
||||||
|
text = '<p>{{- \'John\' -}}</p>'
|
||||||
|
expected = '<p>John</p>'
|
||||||
|
assert_template_result(expected, text)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Make sure the trim isn't too agressive
|
||||||
|
def test_no_trim_tags
|
||||||
|
text = '<p>{%- if true -%}yes{%- endif -%}</p>'
|
||||||
|
expected = '<p>yes</p>'
|
||||||
|
assert_template_result(expected, text)
|
||||||
|
|
||||||
|
text = '<p>{%- if false -%}no{%- endif -%}</p>'
|
||||||
|
expected = '<p></p>'
|
||||||
|
assert_template_result(expected, text)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_single_line_outer_tag
|
||||||
|
text = '<p> {%- if true %} yes {% endif -%} </p>'
|
||||||
|
expected = '<p> yes </p>'
|
||||||
|
assert_template_result(expected, text)
|
||||||
|
|
||||||
|
text = '<p> {%- if false %} no {% endif -%} </p>'
|
||||||
|
expected = '<p></p>'
|
||||||
|
assert_template_result(expected, text)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_single_line_inner_tag
|
||||||
|
text = '<p> {% if true -%} yes {%- endif %} </p>'
|
||||||
|
expected = '<p> yes </p>'
|
||||||
|
assert_template_result(expected, text)
|
||||||
|
|
||||||
|
text = '<p> {% if false -%} no {%- endif %} </p>'
|
||||||
|
expected = '<p> </p>'
|
||||||
|
assert_template_result(expected, text)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_single_line_post_tag
|
||||||
|
text = '<p> {% if true -%} yes {% endif -%} </p>'
|
||||||
|
expected = '<p> yes </p>'
|
||||||
|
assert_template_result(expected, text)
|
||||||
|
|
||||||
|
text = '<p> {% if false -%} no {% endif -%} </p>'
|
||||||
|
expected = '<p> </p>'
|
||||||
|
assert_template_result(expected, text)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_single_line_pre_tag
|
||||||
|
text = '<p> {%- if true %} yes {%- endif %} </p>'
|
||||||
|
expected = '<p> yes </p>'
|
||||||
|
assert_template_result(expected, text)
|
||||||
|
|
||||||
|
text = '<p> {%- if false %} no {%- endif %} </p>'
|
||||||
|
expected = '<p> </p>'
|
||||||
|
assert_template_result(expected, text)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_pre_trim_output
|
||||||
|
text = <<-END_TEMPLATE
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
{{- 'John' }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
END_TEMPLATE
|
||||||
|
expected = <<-END_EXPECTED
|
||||||
|
<div>
|
||||||
|
<p>John
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
END_EXPECTED
|
||||||
|
assert_template_result(expected, text)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_pre_trim_tags
|
||||||
|
text = <<-END_TEMPLATE
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
{%- if true %}
|
||||||
|
yes
|
||||||
|
{%- endif %}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
END_TEMPLATE
|
||||||
|
expected = <<-END_EXPECTED
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
yes
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
END_EXPECTED
|
||||||
|
assert_template_result(expected, text)
|
||||||
|
|
||||||
|
text = <<-END_TEMPLATE
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
{%- if false %}
|
||||||
|
no
|
||||||
|
{%- endif %}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
END_TEMPLATE
|
||||||
|
expected = <<-END_EXPECTED
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
END_EXPECTED
|
||||||
|
assert_template_result(expected, text)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_post_trim_output
|
||||||
|
text = <<-END_TEMPLATE
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
{{ 'John' -}}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
END_TEMPLATE
|
||||||
|
expected = <<-END_EXPECTED
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
John</p>
|
||||||
|
</div>
|
||||||
|
END_EXPECTED
|
||||||
|
assert_template_result(expected, text)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_post_trim_tags
|
||||||
|
text = <<-END_TEMPLATE
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
{% if true -%}
|
||||||
|
yes
|
||||||
|
{% endif -%}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
END_TEMPLATE
|
||||||
|
expected = <<-END_EXPECTED
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
yes
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
END_EXPECTED
|
||||||
|
assert_template_result(expected, text)
|
||||||
|
|
||||||
|
text = <<-END_TEMPLATE
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
{% if false -%}
|
||||||
|
no
|
||||||
|
{% endif -%}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
END_TEMPLATE
|
||||||
|
expected = <<-END_EXPECTED
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
END_EXPECTED
|
||||||
|
assert_template_result(expected, text)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_pre_and_post_trim_tags
|
||||||
|
text = <<-END_TEMPLATE
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
{%- if true %}
|
||||||
|
yes
|
||||||
|
{% endif -%}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
END_TEMPLATE
|
||||||
|
expected = <<-END_EXPECTED
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
yes
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
END_EXPECTED
|
||||||
|
assert_template_result(expected, text)
|
||||||
|
|
||||||
|
text = <<-END_TEMPLATE
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
{%- if false %}
|
||||||
|
no
|
||||||
|
{% endif -%}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
END_TEMPLATE
|
||||||
|
expected = <<-END_EXPECTED
|
||||||
|
<div>
|
||||||
|
<p></p>
|
||||||
|
</div>
|
||||||
|
END_EXPECTED
|
||||||
|
assert_template_result(expected, text)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_post_and_pre_trim_tags
|
||||||
|
text = <<-END_TEMPLATE
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
{% if true -%}
|
||||||
|
yes
|
||||||
|
{%- endif %}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
END_TEMPLATE
|
||||||
|
expected = <<-END_EXPECTED
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
yes
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
END_EXPECTED
|
||||||
|
assert_template_result(expected, text)
|
||||||
|
|
||||||
|
whitespace = ' '
|
||||||
|
text = <<-END_TEMPLATE
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
{% if false -%}
|
||||||
|
no
|
||||||
|
{%- endif %}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
END_TEMPLATE
|
||||||
|
expected = <<-END_EXPECTED
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
#{whitespace}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
END_EXPECTED
|
||||||
|
assert_template_result(expected, text)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_trim_output
|
||||||
|
text = <<-END_TEMPLATE
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
{{- 'John' -}}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
END_TEMPLATE
|
||||||
|
expected = <<-END_EXPECTED
|
||||||
|
<div>
|
||||||
|
<p>John</p>
|
||||||
|
</div>
|
||||||
|
END_EXPECTED
|
||||||
|
assert_template_result(expected, text)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_trim_tags
|
||||||
|
text = <<-END_TEMPLATE
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
{%- if true -%}
|
||||||
|
yes
|
||||||
|
{%- endif -%}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
END_TEMPLATE
|
||||||
|
expected = <<-END_EXPECTED
|
||||||
|
<div>
|
||||||
|
<p>yes</p>
|
||||||
|
</div>
|
||||||
|
END_EXPECTED
|
||||||
|
assert_template_result(expected, text)
|
||||||
|
|
||||||
|
text = <<-END_TEMPLATE
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
{%- if false -%}
|
||||||
|
no
|
||||||
|
{%- endif -%}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
END_TEMPLATE
|
||||||
|
expected = <<-END_EXPECTED
|
||||||
|
<div>
|
||||||
|
<p></p>
|
||||||
|
</div>
|
||||||
|
END_EXPECTED
|
||||||
|
assert_template_result(expected, text)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_whitespace_trim_output
|
||||||
|
text = <<-END_TEMPLATE
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
{{- 'John' -}},
|
||||||
|
{{- '30' -}}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
END_TEMPLATE
|
||||||
|
expected = <<-END_EXPECTED
|
||||||
|
<div>
|
||||||
|
<p>John,30</p>
|
||||||
|
</div>
|
||||||
|
END_EXPECTED
|
||||||
|
assert_template_result(expected, text)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_whitespace_trim_tags
|
||||||
|
text = <<-END_TEMPLATE
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
{%- if true -%}
|
||||||
|
yes
|
||||||
|
{%- endif -%}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
END_TEMPLATE
|
||||||
|
expected = <<-END_EXPECTED
|
||||||
|
<div>
|
||||||
|
<p>yes</p>
|
||||||
|
</div>
|
||||||
|
END_EXPECTED
|
||||||
|
assert_template_result(expected, text)
|
||||||
|
|
||||||
|
text = <<-END_TEMPLATE
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
{%- if false -%}
|
||||||
|
no
|
||||||
|
{%- endif -%}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
END_TEMPLATE
|
||||||
|
expected = <<-END_EXPECTED
|
||||||
|
<div>
|
||||||
|
<p></p>
|
||||||
|
</div>
|
||||||
|
END_EXPECTED
|
||||||
|
assert_template_result(expected, text)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_complex_trim_output
|
||||||
|
text = <<-END_TEMPLATE
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
{{- 'John' -}}
|
||||||
|
{{- '30' -}}
|
||||||
|
</p>
|
||||||
|
<b>
|
||||||
|
{{ 'John' -}}
|
||||||
|
{{- '30' }}
|
||||||
|
</b>
|
||||||
|
<i>
|
||||||
|
{{- 'John' }}
|
||||||
|
{{ '30' -}}
|
||||||
|
</i>
|
||||||
|
</div>
|
||||||
|
END_TEMPLATE
|
||||||
|
expected = <<-END_EXPECTED
|
||||||
|
<div>
|
||||||
|
<p>John30</p>
|
||||||
|
<b>
|
||||||
|
John30
|
||||||
|
</b>
|
||||||
|
<i>John
|
||||||
|
30</i>
|
||||||
|
</div>
|
||||||
|
END_EXPECTED
|
||||||
|
assert_template_result(expected, text)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_complex_trim
|
||||||
|
text = <<-END_TEMPLATE
|
||||||
|
<div>
|
||||||
|
{%- if true -%}
|
||||||
|
{%- if true -%}
|
||||||
|
<p>
|
||||||
|
{{- 'John' -}}
|
||||||
|
</p>
|
||||||
|
{%- endif -%}
|
||||||
|
{%- endif -%}
|
||||||
|
</div>
|
||||||
|
END_TEMPLATE
|
||||||
|
expected = <<-END_EXPECTED
|
||||||
|
<div><p>John</p></div>
|
||||||
|
END_EXPECTED
|
||||||
|
assert_template_result(expected, text)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_raw_output
|
||||||
|
whitespace = ' '
|
||||||
|
text = <<-END_TEMPLATE
|
||||||
|
<div>
|
||||||
|
{% raw %}
|
||||||
|
{%- if true -%}
|
||||||
|
<p>
|
||||||
|
{{- 'John' -}}
|
||||||
|
</p>
|
||||||
|
{%- endif -%}
|
||||||
|
{% endraw %}
|
||||||
|
</div>
|
||||||
|
END_TEMPLATE
|
||||||
|
expected = <<-END_EXPECTED
|
||||||
|
<div>
|
||||||
|
#{whitespace}
|
||||||
|
{%- if true -%}
|
||||||
|
<p>
|
||||||
|
{{- 'John' -}}
|
||||||
|
</p>
|
||||||
|
{%- endif -%}
|
||||||
|
#{whitespace}
|
||||||
|
</div>
|
||||||
|
END_EXPECTED
|
||||||
|
assert_template_result(expected, text)
|
||||||
|
end
|
||||||
|
end # TrimModeTest
|
||||||
@@ -64,6 +64,14 @@ class ConditionUnitTest < Minitest::Test
|
|||||||
assert_evaluates_argument_error '1', '<=', 0
|
assert_evaluates_argument_error '1', '<=', 0
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_hash_compare_backwards_compatibility
|
||||||
|
assert_equal nil, Condition.new({}, '>', 2).evaluate
|
||||||
|
assert_equal nil, Condition.new(2, '>', {}).evaluate
|
||||||
|
assert_equal false, Condition.new({}, '==', 2).evaluate
|
||||||
|
assert_equal true, Condition.new({ 'a' => 1 }, '==', { 'a' => 1 }).evaluate
|
||||||
|
assert_equal true, Condition.new({ 'a' => 2 }, 'contains', 'a').evaluate
|
||||||
|
end
|
||||||
|
|
||||||
def test_contains_works_on_arrays
|
def test_contains_works_on_arrays
|
||||||
@context = Liquid::Context.new
|
@context = Liquid::Context.new
|
||||||
@context['array'] = [1, 2, 3, 4, 5]
|
@context['array'] = [1, 2, 3, 4, 5]
|
||||||
@@ -122,6 +130,17 @@ class ConditionUnitTest < Minitest::Test
|
|||||||
assert_equal false, condition.evaluate
|
assert_equal false, condition.evaluate
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_maximum_recursion_depth
|
||||||
|
condition = Condition.new(1, '==', 1)
|
||||||
|
|
||||||
|
assert_raises(Liquid::StackLevelError) do
|
||||||
|
(1..510).each do
|
||||||
|
condition.evaluate
|
||||||
|
condition.and Condition.new(2, '==', 2)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def test_should_allow_custom_proc_operator
|
def test_should_allow_custom_proc_operator
|
||||||
Condition.operators['starts_with'] = proc { |cond, left, right| left =~ %r{^#{right}} }
|
Condition.operators['starts_with'] = proc { |cond, left, right| left =~ %r{^#{right}} }
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ class LexerUnitTest < Minitest::Test
|
|||||||
end
|
end
|
||||||
|
|
||||||
def test_comparison
|
def test_comparison
|
||||||
tokens = Lexer.new('== <> contains').tokenize
|
tokens = Lexer.new('== <> contains ').tokenize
|
||||||
assert_equal [[:comparison, '=='], [:comparison, '<>'], [:comparison, 'contains'], [:end_of_string]], tokens
|
assert_equal [[:comparison, '=='], [:comparison, '<>'], [:comparison, 'contains'], [:end_of_string]], tokens
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -133,4 +133,32 @@ class StrainerUnitTest < Minitest::Test
|
|||||||
strainer.class.add_filter(PublicMethodOverrideFilter)
|
strainer.class.add_filter(PublicMethodOverrideFilter)
|
||||||
assert strainer.class.filter_methods.include?('public_filter')
|
assert strainer.class.filter_methods.include?('public_filter')
|
||||||
end
|
end
|
||||||
|
|
||||||
|
module LateAddedFilter
|
||||||
|
def late_added_filter(input)
|
||||||
|
"filtered"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_global_filter_clears_cache
|
||||||
|
assert_equal 'input', Strainer.create(nil).invoke('late_added_filter', 'input')
|
||||||
|
Strainer.global_filter(LateAddedFilter)
|
||||||
|
assert_equal 'filtered', Strainer.create(nil).invoke('late_added_filter', 'input')
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_add_filter_does_not_include_already_included_module
|
||||||
|
mod = Module.new do
|
||||||
|
class << self
|
||||||
|
attr_accessor :include_count
|
||||||
|
def included(mod)
|
||||||
|
self.include_count += 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
self.include_count = 0
|
||||||
|
end
|
||||||
|
strainer = Context.new.strainer
|
||||||
|
strainer.class.add_filter(mod)
|
||||||
|
strainer.class.add_filter(mod)
|
||||||
|
assert_equal 1, mod.include_count
|
||||||
|
end
|
||||||
end # StrainerTest
|
end # StrainerTest
|
||||||
|
|||||||
Reference in New Issue
Block a user