mirror of
https://github.com/kemko/liquid.git
synced 2026-01-01 15:55:40 +03:00
Compare commits
92 Commits
test-gem-i
...
warning-li
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
10173c3315 | ||
|
|
1c1aa4094a | ||
|
|
627ef9e29d | ||
|
|
c2663258be | ||
|
|
d4654d0062 | ||
|
|
ffd4f9d959 | ||
|
|
292161865d | ||
|
|
35808390ee | ||
|
|
1678c07548 | ||
|
|
173a58d36a | ||
|
|
f31e309770 | ||
|
|
ffe1036e15 | ||
|
|
d3b113d2e1 | ||
|
|
2aa9bbbac2 | ||
|
|
d5e57a8ea4 | ||
|
|
5c0e0be639 | ||
|
|
a74d40f1e5 | ||
|
|
79d4ec1a48 | ||
|
|
4db22be8ba | ||
|
|
dc58a4d648 | ||
|
|
2809ec780a | ||
|
|
2d98392bf5 | ||
|
|
df6b442816 | ||
|
|
4b22fc8d1b | ||
|
|
fb6f9c1c13 | ||
|
|
66ae7f3ec0 | ||
|
|
0bea31d2ef | ||
|
|
e5b0487fef | ||
|
|
9117722740 | ||
|
|
baea0a6bf7 | ||
|
|
17347d43de | ||
|
|
794ca9f604 | ||
|
|
15f6cabf83 | ||
|
|
e53d102a2c | ||
|
|
33e7b8e373 | ||
|
|
9b8e3d437e | ||
|
|
a2f0f2547d | ||
|
|
57d5426eed | ||
|
|
3e3a415457 | ||
|
|
deba039d6d | ||
|
|
ee4295c889 | ||
|
|
f5e67a12f9 | ||
|
|
6b56bdd74f | ||
|
|
ba6e3e3da6 | ||
|
|
a8e63ff03d | ||
|
|
052ef9fcb8 | ||
|
|
d07b12dc7d | ||
|
|
32e4f2d3b1 | ||
|
|
2cb1483d54 | ||
|
|
6c6350f18b | ||
|
|
eae24373e6 | ||
|
|
034a47a6cf | ||
|
|
51c1165f26 | ||
|
|
0b45ffeada | ||
|
|
b7b243a13d | ||
|
|
18e8ce1eb0 | ||
|
|
994f309465 | ||
|
|
02d42a1475 | ||
|
|
d099878385 | ||
|
|
6a061cbe81 | ||
|
|
c864a75903 | ||
|
|
d6fdf86acd | ||
|
|
55597b8398 | ||
|
|
c75522026b | ||
|
|
1e0e9f1f31 | ||
|
|
5fc1929b73 | ||
|
|
746a800475 | ||
|
|
85dc7ef610 | ||
|
|
bc3b066ba8 | ||
|
|
3c2de7737d | ||
|
|
adb7d2bbb8 | ||
|
|
0e56cf99ab | ||
|
|
0df3f1c372 | ||
|
|
44b9ad604f | ||
|
|
535d549978 | ||
|
|
32349033a9 | ||
|
|
fd8c30070a | ||
|
|
4cfc05e32a | ||
|
|
c4bc6cf3db | ||
|
|
0ac3ec7834 | ||
|
|
8909c9f27a | ||
|
|
51c708c8f8 | ||
|
|
f57383af37 | ||
|
|
d007c50856 | ||
|
|
101f125a69 | ||
|
|
5110ca906c | ||
|
|
ac0f63eda9 | ||
|
|
1372274fca | ||
|
|
69951be173 | ||
|
|
c9863836cd | ||
|
|
14b8d824d7 | ||
|
|
fa14fd02e7 |
@@ -1,14 +1,13 @@
|
||||
rvm:
|
||||
- 1.9.3
|
||||
- 2.0.0
|
||||
- 1.9
|
||||
- 2.0
|
||||
- 2.1
|
||||
- 2.1.1
|
||||
- jruby-19mode
|
||||
- jruby-head
|
||||
- rbx-19mode
|
||||
- rbx-2
|
||||
matrix:
|
||||
allow_failures:
|
||||
- rvm: rbx-19mode
|
||||
- rvm: rbx-2
|
||||
- rvm: jruby-head
|
||||
|
||||
script: "rake test"
|
||||
|
||||
6
Gemfile
6
Gemfile
@@ -1,3 +1,9 @@
|
||||
source 'https://rubygems.org'
|
||||
|
||||
gemspec
|
||||
gem 'stackprof', platforms: :mri_21
|
||||
|
||||
group :test do
|
||||
gem 'spy', '0.4.1'
|
||||
gem 'benchmark-ips'
|
||||
end
|
||||
|
||||
21
History.md
21
History.md
@@ -3,7 +3,10 @@
|
||||
## 3.0.0 / not yet released / branch "master"
|
||||
|
||||
* ...
|
||||
* Add error messages for missing variables when :strict, see #352 [Daniel Gaiottino]
|
||||
* Add uniq to standard filters [Florian Weingarten, fw42]
|
||||
* Add exception_handler feature, see #397 and #254 [Bogdan Gusiev, bogdan and Florian Weingarten, fw42]
|
||||
* Optimize variable parsing to avoid repeated regex evaluation during template rendering #383 [Jason Hiltz-Laforge, jasonhl]
|
||||
* Optimize checking for block interrupts to reduce object allocation #380 [Jason Hiltz-Laforge, jasonhl]
|
||||
* Properly set context rethrow_errors on render! #349 [Thierry Joyal, tjoyal]
|
||||
* Fix broken rendering of variables which are equal to false, see #345 [Florian Weingarten, fw42]
|
||||
* Remove ActionView template handler [Dylan Thacker-Smith, dylanahsmith]
|
||||
@@ -30,7 +33,13 @@
|
||||
* Make map filter work on enumerable drops, see #233 [Florian Weingarten, fw42]
|
||||
* Improved whitespace stripping for blank blocks, related to #216 [Florian Weingarten, fw42]
|
||||
|
||||
## 2.6.0 / 2013-11-25 / branch "2.6-stable"
|
||||
## 2.6.1 / 2014-01-10 / branch "2-6-stable"
|
||||
|
||||
Security fix, cherry-picked from master (4e14a65):
|
||||
* Don't call to_sym when creating conditions for security reasons, see #273 [Bouke van der Bijl, bouk]
|
||||
* Prevent arbitrary method invocation on condition objects, see #274 [Dylan Thacker-Smith, dylanahsmith]
|
||||
|
||||
## 2.6.0 / 2013-11-25
|
||||
|
||||
IMPORTANT: Liquid 2.6 is going to be the last version of Liquid which maintains explicit Ruby 1.8 compatability.
|
||||
The following releases will only be tested against Ruby 1.9 and Ruby 2.0 and are likely to break on Ruby 1.8.
|
||||
@@ -54,7 +63,13 @@ The following releases will only be tested against Ruby 1.9 and Ruby 2.0 and are
|
||||
* Better documentation for 'include' tag (closes #163) [Peter Schröder, phoet]
|
||||
* Use of BigDecimal on filters to have better precision (closes #155) [Arthur Nogueira Neves, arthurnn]
|
||||
|
||||
## 2.5.4 / 2013-11-11 / branch "2.5-stable"
|
||||
## 2.5.5 / 2014-01-10 / branch "2-5-stable"
|
||||
|
||||
Security fix, cherry-picked from master (4e14a65):
|
||||
* Don't call to_sym when creating conditions for security reasons, see #273 [Bouke van der Bijl, bouk]
|
||||
* Prevent arbitrary method invocation on condition objects, see #274 [Dylan Thacker-Smith, dylanahsmith]
|
||||
|
||||
## 2.5.4 / 2013-11-11
|
||||
|
||||
* Fix "can't convert Fixnum into String" for "replace", see #173, [wǒ_is神仙, jsw0528]
|
||||
|
||||
|
||||
@@ -3,4 +3,4 @@
|
||||
<p>It is {{date}}</p>
|
||||
|
||||
|
||||
<p>Check out the <a href="http://localhost:3000/products">Products</a> screen </p>
|
||||
<p>Check out the <a href="/products">Products</a> screen </p>
|
||||
|
||||
@@ -38,6 +38,9 @@ module Liquid
|
||||
PartialTemplateParser = /#{TagStart}.*?#{TagEnd}|#{VariableStart}.*?#{VariableIncompleteEnd}/om
|
||||
TemplateParser = /(#{PartialTemplateParser}|#{AnyStartingTag})/om
|
||||
VariableParser = /\[[^\]]+\]|#{VariableSegment}+\??/o
|
||||
|
||||
singleton_class.send(:attr_accessor, :cache_classes)
|
||||
self.cache_classes = true
|
||||
end
|
||||
|
||||
require "liquid/version"
|
||||
@@ -49,18 +52,26 @@ require 'liquid/extensions'
|
||||
require 'liquid/errors'
|
||||
require 'liquid/interrupts'
|
||||
require 'liquid/strainer'
|
||||
require 'liquid/expression'
|
||||
require 'liquid/context'
|
||||
require 'liquid/parser_switching'
|
||||
require 'liquid/tag'
|
||||
require 'liquid/block'
|
||||
require 'liquid/document'
|
||||
require 'liquid/variable'
|
||||
require 'liquid/variable_lookup'
|
||||
require 'liquid/range_lookup'
|
||||
require 'liquid/file_system'
|
||||
require 'liquid/template'
|
||||
require 'liquid/standardfilters'
|
||||
require 'liquid/condition'
|
||||
require 'liquid/module_ex'
|
||||
require 'liquid/utils'
|
||||
require 'liquid/token'
|
||||
|
||||
# Load all the tags of the standard library
|
||||
#
|
||||
Dir[File.dirname(__FILE__) + '/liquid/tags/*.rb'].each { |f| require f }
|
||||
|
||||
require 'liquid/profiler'
|
||||
require 'liquid/profiler/hooks'
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
module Liquid
|
||||
class Block < Tag
|
||||
IsTag = /\A#{TagStart}/o
|
||||
IsVariable = /\A#{VariableStart}/o
|
||||
FullToken = /\A#{TagStart}\s*(\w+)\s*(.*)?#{TagEnd}\z/om
|
||||
ContentOfVariable = /\A#{VariableStart}(.*)#{VariableEnd}\z/om
|
||||
TAGSTART = "{%".freeze
|
||||
VARSTART = "{{".freeze
|
||||
|
||||
def blank?
|
||||
@blank || false
|
||||
@blank
|
||||
end
|
||||
|
||||
def parse(tokens)
|
||||
@@ -18,41 +18,44 @@ module Liquid
|
||||
@children = []
|
||||
|
||||
while token = tokens.shift
|
||||
case token
|
||||
when IsTag
|
||||
if token =~ FullToken
|
||||
unless token.empty?
|
||||
case
|
||||
when token.start_with?(TAGSTART)
|
||||
if token =~ FullToken
|
||||
|
||||
# if we found the proper block delimiter just end parsing here and let the outer block
|
||||
# proceed
|
||||
if block_delimiter == $1
|
||||
end_tag
|
||||
return
|
||||
end
|
||||
# if we found the proper block delimiter just end parsing here and let the outer block
|
||||
# proceed
|
||||
if block_delimiter == $1
|
||||
end_tag
|
||||
return
|
||||
end
|
||||
|
||||
# fetch the tag from registered blocks
|
||||
if tag = Template.tags[$1]
|
||||
new_tag = tag.parse($1, $2, tokens, @options)
|
||||
@blank &&= new_tag.blank?
|
||||
@nodelist << new_tag
|
||||
@children << new_tag
|
||||
# fetch the tag from registered blocks
|
||||
if tag = Template.tags[$1]
|
||||
markup = token.is_a?(Token) ? token.child($2) : $2
|
||||
new_tag = tag.parse($1, markup, tokens, @options)
|
||||
new_tag.line_number = token.line_number if token.is_a?(Token)
|
||||
@blank &&= new_tag.blank?
|
||||
@nodelist << new_tag
|
||||
@children << new_tag
|
||||
else
|
||||
# this tag is not registered with the system
|
||||
# pass it to the current block for special handling or error reporting
|
||||
unknown_tag($1, $2, tokens)
|
||||
end
|
||||
else
|
||||
# this tag is not registered with the system
|
||||
# pass it to the current block for special handling or error reporting
|
||||
unknown_tag($1, $2, tokens)
|
||||
raise SyntaxError.new(options[:locale].t("errors.syntax.tag_termination".freeze, :token => token, :tag_end => TagEnd.inspect))
|
||||
end
|
||||
when token.start_with?(VARSTART)
|
||||
new_var = create_variable(token)
|
||||
new_var.line_number = token.line_number if token.is_a?(Token)
|
||||
@nodelist << new_var
|
||||
@children << new_var
|
||||
@blank = false
|
||||
else
|
||||
raise SyntaxError.new(options[:locale].t("errors.syntax.tag_termination".freeze, :token => token, :tag_end => TagEnd.inspect))
|
||||
@nodelist << token
|
||||
@blank &&= (token =~ /\A\s*\z/)
|
||||
end
|
||||
when IsVariable
|
||||
new_var = create_variable(token)
|
||||
@nodelist << new_var
|
||||
@children << new_var
|
||||
@blank = false
|
||||
when ''.freeze
|
||||
# pass
|
||||
else
|
||||
@nodelist << token
|
||||
@blank &&= (token =~ /\A\s*\z/)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -91,17 +94,18 @@ module Liquid
|
||||
end
|
||||
end
|
||||
|
||||
def block_delimiter
|
||||
"end#{block_name}"
|
||||
end
|
||||
|
||||
def block_name
|
||||
@tag_name
|
||||
end
|
||||
|
||||
def block_delimiter
|
||||
@block_delimiter ||= "end#{block_name}"
|
||||
end
|
||||
|
||||
def create_variable(token)
|
||||
token.scan(ContentOfVariable) do |content|
|
||||
return Variable.new(content.first, @options)
|
||||
markup = token.is_a?(Token) ? token.child(content.first) : content.first
|
||||
return Variable.new(markup, @options)
|
||||
end
|
||||
raise SyntaxError.new(options[:locale].t("errors.syntax.variable_termination".freeze, :token => token, :tag_end => VariableEnd.inspect))
|
||||
end
|
||||
@@ -134,23 +138,29 @@ module Liquid
|
||||
break
|
||||
end
|
||||
|
||||
token_output = (token.respond_to?(:render) ? token.render(context) : token)
|
||||
context.increment_used_resources(:render_length_current, token_output)
|
||||
if context.resource_limits_reached?
|
||||
context.resource_limits[:reached] = true
|
||||
raise MemoryError.new("Memory limits exceeded".freeze)
|
||||
end
|
||||
token_output = render_token(token, context)
|
||||
|
||||
unless token.is_a?(Block) && token.blank?
|
||||
output << token_output
|
||||
end
|
||||
rescue MemoryError => e
|
||||
raise e
|
||||
rescue ::StandardError => e
|
||||
output << (context.handle_error(e))
|
||||
output << (context.handle_error(e, token))
|
||||
end
|
||||
end
|
||||
|
||||
output.join
|
||||
end
|
||||
|
||||
def render_token(token, context)
|
||||
token_output = (token.respond_to?(:render) ? token.render(context) : token)
|
||||
context.increment_used_resources(:render_length_current, token_output)
|
||||
if context.resource_limits_reached?
|
||||
context.resource_limits[:reached] = true
|
||||
raise MemoryError.new("Memory limits exceeded".freeze)
|
||||
end
|
||||
token_output
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -94,12 +94,16 @@ module Liquid
|
||||
|
||||
left, right = context[left], context[right]
|
||||
|
||||
operation = self.class.operators[op] || raise(ArgumentError.new("Unknown operator #{op}"))
|
||||
operation = self.class.operators[op] || raise(Liquid::ArgumentError.new("Unknown operator #{op}"))
|
||||
|
||||
if operation.respond_to?(:call)
|
||||
operation.call(self, left, right)
|
||||
elsif left.respond_to?(operation) and right.respond_to?(operation)
|
||||
left.send(operation, right)
|
||||
begin
|
||||
left.send(operation, right)
|
||||
rescue ::ArgumentError => e
|
||||
raise Liquid::ArgumentError.new(e.message)
|
||||
end
|
||||
else
|
||||
nil
|
||||
end
|
||||
|
||||
@@ -14,18 +14,23 @@ module Liquid
|
||||
# context['bob'] #=> nil class Context
|
||||
class Context
|
||||
attr_reader :scopes, :errors, :registers, :environments, :resource_limits
|
||||
|
||||
attr_accessor :rethrow_errors
|
||||
attr_accessor :exception_handler
|
||||
|
||||
def initialize(environments = {}, outer_scope = {}, registers = {}, rethrow_errors = false, resource_limits = {})
|
||||
@environments = [environments].flatten
|
||||
@scopes = [(outer_scope || {})]
|
||||
@registers = registers
|
||||
@errors = []
|
||||
@rethrow_errors = rethrow_errors
|
||||
@resource_limits = (resource_limits || {}).merge!({ :render_score_current => 0, :assign_score_current => 0 })
|
||||
@environments = [environments].flatten
|
||||
@scopes = [(outer_scope || {})]
|
||||
@registers = registers
|
||||
@errors = []
|
||||
@resource_limits = (resource_limits || {}).merge!({ :render_score_current => 0, :assign_score_current => 0 })
|
||||
@parsed_expression = Hash.new{ |cache, markup| cache[markup] = Expression.parse(markup) }
|
||||
squash_instance_assigns_with_environments
|
||||
|
||||
@this_stack_used = false
|
||||
|
||||
if rethrow_errors
|
||||
self.exception_handler = ->(e) { true }
|
||||
end
|
||||
|
||||
@interrupts = []
|
||||
@filters = []
|
||||
end
|
||||
@@ -73,7 +78,7 @@ module Liquid
|
||||
|
||||
# are there any not handled interrupts?
|
||||
def has_interrupt?
|
||||
@interrupts.any?
|
||||
!@interrupts.empty?
|
||||
end
|
||||
|
||||
# push an interrupt to the stack. this interrupt is considered not handled.
|
||||
@@ -86,20 +91,16 @@ module Liquid
|
||||
@interrupts.pop
|
||||
end
|
||||
|
||||
def handle_error(e)
|
||||
errors.push(e)
|
||||
raise if @rethrow_errors
|
||||
|
||||
case e
|
||||
when SyntaxError
|
||||
"Liquid syntax error: #{e.message}"
|
||||
else
|
||||
"Liquid error: #{e.message}"
|
||||
end
|
||||
def handle_error(e, token)
|
||||
e = Liquid::Error.error_with_line_number(e, token)
|
||||
errors.push(e)
|
||||
raise if exception_handler && exception_handler.call(e)
|
||||
Liquid::Error.render(e)
|
||||
end
|
||||
|
||||
def invoke(method, *args)
|
||||
strainer.invoke(method, *args)
|
||||
strainer.invoke(method, *args).to_liquid
|
||||
end
|
||||
|
||||
# Push new local scope on the stack. use <tt>Context#stack</tt> instead
|
||||
@@ -127,11 +128,19 @@ module Liquid
|
||||
# end
|
||||
#
|
||||
# context['var] #=> nil
|
||||
def stack(new_scope={})
|
||||
push(new_scope)
|
||||
def stack(new_scope=nil)
|
||||
old_stack_used = @this_stack_used
|
||||
if new_scope
|
||||
push(new_scope)
|
||||
@this_stack_used = true
|
||||
else
|
||||
@this_stack_used = false
|
||||
end
|
||||
|
||||
yield
|
||||
ensure
|
||||
pop
|
||||
pop if @this_stack_used
|
||||
@this_stack_used = old_stack_used
|
||||
end
|
||||
|
||||
def clear_instance_assigns
|
||||
@@ -140,141 +149,71 @@ module Liquid
|
||||
|
||||
# Only allow String, Numeric, Hash, Array, Proc, Boolean or <tt>Liquid::Drop</tt>
|
||||
def []=(key, value)
|
||||
unless @this_stack_used
|
||||
@this_stack_used = true
|
||||
push({})
|
||||
end
|
||||
@scopes[0][key] = value
|
||||
end
|
||||
|
||||
def [](key)
|
||||
resolve(key)
|
||||
# Look up variable, either resolve directly after considering the name. We can directly handle
|
||||
# Strings, digits, floats and booleans (true,false).
|
||||
# If no match is made we lookup the variable in the current scope and
|
||||
# later move up to the parent blocks to see if we can resolve the variable somewhere up the tree.
|
||||
# Some special keywords return symbols. Those symbols are to be called on the rhs object in expressions
|
||||
#
|
||||
# Example:
|
||||
# products == empty #=> products.empty?
|
||||
def [](expression)
|
||||
evaluate(@parsed_expression[expression])
|
||||
end
|
||||
|
||||
def has_key?(key)
|
||||
resolve(key) != nil
|
||||
self[key] != nil
|
||||
end
|
||||
|
||||
def evaluate(object)
|
||||
object.respond_to?(:evaluate) ? object.evaluate(self) : object
|
||||
end
|
||||
|
||||
# Fetches an object starting at the local scope and then moving up the hierachy
|
||||
def find_variable(key)
|
||||
|
||||
# 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
|
||||
index = @scopes.find_index { |s| s.has_key?(key) }
|
||||
scope = @scopes[index] if index
|
||||
|
||||
variable = nil
|
||||
|
||||
if scope.nil?
|
||||
@environments.each do |e|
|
||||
variable = lookup_and_evaluate(e, key)
|
||||
unless variable.nil?
|
||||
scope = e
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
scope ||= @environments.last || @scopes.last
|
||||
variable ||= lookup_and_evaluate(scope, key)
|
||||
|
||||
variable = variable.to_liquid
|
||||
variable.context = self if variable.respond_to?(:context=)
|
||||
|
||||
return variable
|
||||
end
|
||||
|
||||
def lookup_and_evaluate(obj, key)
|
||||
if (value = obj[key]).is_a?(Proc) && obj.respond_to?(:[]=)
|
||||
obj[key] = (value.arity == 0) ? value.call : value.call(self)
|
||||
else
|
||||
value
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
LITERALS = {
|
||||
nil => nil, 'nil'.freeze => nil, 'null'.freeze => nil, ''.freeze => nil,
|
||||
'true'.freeze => true,
|
||||
'false'.freeze => false,
|
||||
'blank'.freeze => :blank?,
|
||||
'empty'.freeze => :empty?
|
||||
}
|
||||
|
||||
# Look up variable, either resolve directly after considering the name. We can directly handle
|
||||
# Strings, digits, floats and booleans (true,false).
|
||||
# If no match is made we lookup the variable in the current scope and
|
||||
# later move up to the parent blocks to see if we can resolve the variable somewhere up the tree.
|
||||
# Some special keywords return symbols. Those symbols are to be called on the rhs object in expressions
|
||||
#
|
||||
# Example:
|
||||
# products == empty #=> products.empty?
|
||||
def resolve(key)
|
||||
if LITERALS.key?(key)
|
||||
LITERALS[key]
|
||||
else
|
||||
case key
|
||||
when /\A'(.*)'\z/m # Single quoted strings
|
||||
$1
|
||||
when /\A"(.*)"\z/m # Double quoted strings
|
||||
$1
|
||||
when /\A(-?\d+)\z/ # Integer and floats
|
||||
$1.to_i
|
||||
when /\A\((\S+)\.\.(\S+)\)\z/ # Ranges
|
||||
(resolve($1).to_i..resolve($2).to_i)
|
||||
when /\A(-?\d[\d\.]+)\z/ # Floats
|
||||
$1.to_f
|
||||
else
|
||||
variable(key)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Fetches an object starting at the local scope and then moving up the hierachy
|
||||
def find_variable(key)
|
||||
scope = @scopes.find { |s| s.has_key?(key) }
|
||||
variable = nil
|
||||
|
||||
if scope.nil?
|
||||
@environments.each do |e|
|
||||
variable = lookup_and_evaluate(e, key)
|
||||
unless variable.nil?
|
||||
scope = e
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
scope ||= @environments.last || @scopes.last
|
||||
handle_not_found(key) unless scope.has_key?(key)
|
||||
variable ||= lookup_and_evaluate(scope, key)
|
||||
|
||||
variable = variable.to_liquid
|
||||
variable.context = self if variable.respond_to?(:context=)
|
||||
|
||||
return variable
|
||||
end
|
||||
|
||||
# Resolves namespaced queries gracefully.
|
||||
#
|
||||
# Example
|
||||
# @context['hash'] = {"name" => 'tobi'}
|
||||
# assert_equal 'tobi', @context['hash.name']
|
||||
# assert_equal 'tobi', @context['hash["name"]']
|
||||
def variable(markup)
|
||||
parts = markup.scan(VariableParser)
|
||||
square_bracketed = /\A\[(.*)\]\z/m
|
||||
|
||||
first_part = parts.shift
|
||||
|
||||
if first_part =~ square_bracketed
|
||||
first_part = resolve($1)
|
||||
end
|
||||
|
||||
if object = find_variable(first_part)
|
||||
|
||||
parts.each do |part|
|
||||
part = resolve($1) if part_resolved = (part =~ square_bracketed)
|
||||
|
||||
# If object is a hash- or array-like object we look for the
|
||||
# presence of the key and if its available we return it
|
||||
if object.respond_to?(:[]) and
|
||||
((object.respond_to?(:has_key?) and object.has_key?(part)) or
|
||||
(object.respond_to?(:fetch) and part.is_a?(Integer)))
|
||||
|
||||
# if its a proc we will replace the entry with the proc
|
||||
res = lookup_and_evaluate(object, part)
|
||||
object = res.to_liquid
|
||||
|
||||
# Some special cases. If the part wasn't in square brackets and
|
||||
# no key with the same name was found we interpret following calls
|
||||
# as commands and call them on the current object
|
||||
elsif !part_resolved and object.respond_to?(part) and ['size'.freeze, 'first'.freeze, 'last'.freeze].include?(part)
|
||||
|
||||
object = object.send(part.intern).to_liquid
|
||||
|
||||
# No key was present with the desired value and it wasn't one of the directly supported
|
||||
# keywords either. The only thing we got left is to return nil
|
||||
else
|
||||
handle_not_found(markup)
|
||||
return nil
|
||||
end
|
||||
|
||||
# If we are dealing with a drop here we have to
|
||||
object.context = self if object.respond_to?(:context=)
|
||||
end
|
||||
end
|
||||
|
||||
object
|
||||
end # variable
|
||||
|
||||
def lookup_and_evaluate(obj, key)
|
||||
if (value = obj[key]).is_a?(Proc) && obj.respond_to?(:[]=)
|
||||
obj[key] = (value.arity == 0) ? value.call : value.call(self)
|
||||
else
|
||||
value
|
||||
end
|
||||
end # lookup_and_evaluate
|
||||
|
||||
def squash_instance_assigns_with_environments
|
||||
@scopes.last.each_key do |k|
|
||||
@environments.each do |env|
|
||||
@@ -285,10 +224,5 @@ module Liquid
|
||||
end
|
||||
end
|
||||
end # squash_instance_assigns_with_environments
|
||||
|
||||
def handle_not_found(variable)
|
||||
@errors << "Variable {{#{variable}}} not found" if Template.error_mode == :strict
|
||||
end
|
||||
end # Context
|
||||
|
||||
end # Liquid
|
||||
|
||||
@@ -1,5 +1,35 @@
|
||||
module Liquid
|
||||
class Error < ::StandardError; end
|
||||
class Error < ::StandardError
|
||||
attr_accessor :line_number
|
||||
|
||||
def self.render(e)
|
||||
msg = if e.is_a?(Liquid::Error) && e.line_number
|
||||
"#{e.line_number}: #{e.message}"
|
||||
else
|
||||
e.message
|
||||
end
|
||||
|
||||
case e
|
||||
when SyntaxError
|
||||
"Liquid syntax error: #{msg}"
|
||||
else
|
||||
"Liquid error: #{msg}"
|
||||
end
|
||||
end
|
||||
|
||||
def self.error_with_line_number(e, token)
|
||||
if e.is_a?(Liquid::Error)
|
||||
e.set_line_number_from_token(token)
|
||||
end
|
||||
|
||||
e
|
||||
end
|
||||
|
||||
def set_line_number_from_token(token)
|
||||
return unless token.respond_to?(:line_number)
|
||||
self.line_number = token.line_number
|
||||
end
|
||||
end
|
||||
|
||||
class ArgumentError < Error; end
|
||||
class ContextError < Error; end
|
||||
|
||||
33
lib/liquid/expression.rb
Normal file
33
lib/liquid/expression.rb
Normal file
@@ -0,0 +1,33 @@
|
||||
module Liquid
|
||||
class Expression
|
||||
LITERALS = {
|
||||
nil => nil, 'nil'.freeze => nil, 'null'.freeze => nil, ''.freeze => nil,
|
||||
'true'.freeze => true,
|
||||
'false'.freeze => false,
|
||||
'blank'.freeze => :blank?,
|
||||
'empty'.freeze => :empty?
|
||||
}
|
||||
|
||||
def self.parse(markup)
|
||||
if LITERALS.key?(markup)
|
||||
LITERALS[markup]
|
||||
else
|
||||
case markup
|
||||
when /\A'(.*)'\z/m # Single quoted strings
|
||||
$1
|
||||
when /\A"(.*)"\z/m # Double quoted strings
|
||||
$1
|
||||
when /\A(-?\d+)\z/ # Integer and floats
|
||||
$1.to_i
|
||||
when /\A\((\S+)\.\.(\S+)\)\z/ # Ranges
|
||||
RangeLookup.parse($1, $2)
|
||||
when /\A(-?\d[\d\.]+)\z/ # Floats
|
||||
$1.to_f
|
||||
else
|
||||
VariableLookup.parse(markup)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
31
lib/liquid/parser_switching.rb
Normal file
31
lib/liquid/parser_switching.rb
Normal file
@@ -0,0 +1,31 @@
|
||||
module Liquid
|
||||
module ParserSwitching
|
||||
def parse_with_selected_parser(markup)
|
||||
case @options[:error_mode] || Template.error_mode
|
||||
when :strict then strict_parse_with_error_context(markup)
|
||||
when :lax then lax_parse(markup)
|
||||
when :warn
|
||||
begin
|
||||
return strict_parse_with_error_context(markup)
|
||||
rescue SyntaxError => e
|
||||
e.line_number = markup.line_number if markup.is_a?(Token)
|
||||
@warnings ||= []
|
||||
@warnings << e
|
||||
return lax_parse(markup)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def strict_parse_with_error_context(markup)
|
||||
strict_parse(markup)
|
||||
rescue SyntaxError => e
|
||||
e.message << markup_context(markup)
|
||||
raise e
|
||||
end
|
||||
|
||||
def markup_context(markup)
|
||||
" in \"#{markup.strip}\""
|
||||
end
|
||||
end
|
||||
end
|
||||
159
lib/liquid/profiler.rb
Normal file
159
lib/liquid/profiler.rb
Normal file
@@ -0,0 +1,159 @@
|
||||
module Liquid
|
||||
|
||||
# Profiler enables support for profiling template rendering to help track down performance issues.
|
||||
#
|
||||
# To enable profiling, pass the <tt>profile: true</tt> option to <tt>Liquid::Template.parse</tt>. Then, after
|
||||
# <tt>Liquid::Template#render</tt> is called, the template object makes available an instance of this
|
||||
# class via the <tt>Liquid::Template#profiler</tt> method.
|
||||
#
|
||||
# template = Liquid::Template.parse(template_content, profile: true)
|
||||
# output = template.render
|
||||
# profile = template.profiler
|
||||
#
|
||||
# This object contains all profiling information, containing information on what tags were rendered,
|
||||
# where in the templates these tags live, and how long each tag took to render.
|
||||
#
|
||||
# This is a tree structure that is Enumerable all the way down, and keeps track of tags and rendering times
|
||||
# inside of <tt>{% include %}</tt> tags.
|
||||
#
|
||||
# profile.each do |node|
|
||||
# # Access to the token itself
|
||||
# node.code
|
||||
#
|
||||
# # Which template and line number of this node.
|
||||
# # If top level, this will be "<root>".
|
||||
# node.partial
|
||||
# node.line_number
|
||||
#
|
||||
# # Render time in seconds of this node
|
||||
# node.render_time
|
||||
#
|
||||
# # If the template used {% include %}, this node will also have children.
|
||||
# node.children.each do |child2|
|
||||
# # ...
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# Profiler also exposes the total time of the template's render in <tt>Liquid::Profiler#total_render_time</tt>.
|
||||
#
|
||||
# All render times are in seconds. There is a small performance hit when profiling is enabled.
|
||||
#
|
||||
class Profiler
|
||||
include Enumerable
|
||||
|
||||
class Timing
|
||||
attr_reader :code, :partial, :line_number, :children
|
||||
|
||||
def initialize(token, partial)
|
||||
@code = token.respond_to?(:raw) ? token.raw : token
|
||||
@partial = partial
|
||||
@line_number = token.respond_to?(:line_number) ? token.line_number : nil
|
||||
@children = []
|
||||
end
|
||||
|
||||
def self.start(token, partial)
|
||||
new(token, partial).tap do |t|
|
||||
t.start
|
||||
end
|
||||
end
|
||||
|
||||
def start
|
||||
@start_time = Time.now
|
||||
end
|
||||
|
||||
def finish
|
||||
@end_time = Time.now
|
||||
end
|
||||
|
||||
def render_time
|
||||
@end_time - @start_time
|
||||
end
|
||||
end
|
||||
|
||||
def self.profile_token_render(token)
|
||||
if Profiler.current_profile && token.respond_to?(:render)
|
||||
Profiler.current_profile.start_token(token)
|
||||
output = yield
|
||||
Profiler.current_profile.end_token(token)
|
||||
output
|
||||
else
|
||||
yield
|
||||
end
|
||||
end
|
||||
|
||||
def self.profile_children(template_name)
|
||||
if Profiler.current_profile
|
||||
Profiler.current_profile.push_partial(template_name)
|
||||
output = yield
|
||||
Profiler.current_profile.pop_partial
|
||||
output
|
||||
else
|
||||
yield
|
||||
end
|
||||
end
|
||||
|
||||
def self.current_profile
|
||||
Thread.current[:liquid_profiler]
|
||||
end
|
||||
|
||||
def initialize
|
||||
@partial_stack = ["<root>"]
|
||||
|
||||
@root_timing = Timing.new("", current_partial)
|
||||
@timing_stack = [@root_timing]
|
||||
|
||||
@render_start_at = Time.now
|
||||
@render_end_at = @render_start_at
|
||||
end
|
||||
|
||||
def start
|
||||
Thread.current[:liquid_profiler] = self
|
||||
@render_start_at = Time.now
|
||||
end
|
||||
|
||||
def stop
|
||||
Thread.current[:liquid_profiler] = nil
|
||||
@render_end_at = Time.now
|
||||
end
|
||||
|
||||
def total_render_time
|
||||
@render_end_at - @render_start_at
|
||||
end
|
||||
|
||||
def each(&block)
|
||||
@root_timing.children.each(&block)
|
||||
end
|
||||
|
||||
def [](idx)
|
||||
@root_timing.children[idx]
|
||||
end
|
||||
|
||||
def length
|
||||
@root_timing.children.length
|
||||
end
|
||||
|
||||
def start_token(token)
|
||||
@timing_stack.push(Timing.start(token, current_partial))
|
||||
end
|
||||
|
||||
def end_token(token)
|
||||
timing = @timing_stack.pop
|
||||
timing.finish
|
||||
|
||||
@timing_stack.last.children << timing
|
||||
end
|
||||
|
||||
def current_partial
|
||||
@partial_stack.last
|
||||
end
|
||||
|
||||
def push_partial(partial_name)
|
||||
@partial_stack.push(partial_name)
|
||||
end
|
||||
|
||||
def pop_partial
|
||||
@partial_stack.pop
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
23
lib/liquid/profiler/hooks.rb
Normal file
23
lib/liquid/profiler/hooks.rb
Normal file
@@ -0,0 +1,23 @@
|
||||
module Liquid
|
||||
class Block < Tag
|
||||
def render_token_with_profiling(token, context)
|
||||
Profiler.profile_token_render(token) do
|
||||
render_token_without_profiling(token, context)
|
||||
end
|
||||
end
|
||||
|
||||
alias_method :render_token_without_profiling, :render_token
|
||||
alias_method :render_token, :render_token_with_profiling
|
||||
end
|
||||
|
||||
class Include < Tag
|
||||
def render_with_profiling(context)
|
||||
Profiler.profile_children(@template_name) do
|
||||
render_without_profiling(context)
|
||||
end
|
||||
end
|
||||
|
||||
alias_method :render_without_profiling, :render
|
||||
alias_method :render, :render_with_profiling
|
||||
end
|
||||
end
|
||||
22
lib/liquid/range_lookup.rb
Normal file
22
lib/liquid/range_lookup.rb
Normal file
@@ -0,0 +1,22 @@
|
||||
module Liquid
|
||||
class RangeLookup
|
||||
def self.parse(start_markup, end_markup)
|
||||
start_obj = Expression.parse(start_markup)
|
||||
end_obj = Expression.parse(end_markup)
|
||||
if start_obj.respond_to?(:evaluate) || end_obj.respond_to?(:evaluate)
|
||||
new(start_obj, end_obj)
|
||||
else
|
||||
start_obj.to_i..end_obj.to_i
|
||||
end
|
||||
end
|
||||
|
||||
def initialize(start_obj, end_obj)
|
||||
@start_obj = start_obj
|
||||
@end_obj = end_obj
|
||||
end
|
||||
|
||||
def evaluate(context)
|
||||
context.evaluate(@start_obj).to_i..context.evaluate(@end_obj).to_i
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -36,12 +36,22 @@ module Liquid
|
||||
def escape(input)
|
||||
CGI.escapeHTML(input) rescue input
|
||||
end
|
||||
alias_method :h, :escape
|
||||
|
||||
def escape_once(input)
|
||||
input.to_s.gsub(HTML_ESCAPE_ONCE_REGEXP, HTML_ESCAPE)
|
||||
end
|
||||
|
||||
alias_method :h, :escape
|
||||
def slice(input, offset, length=nil)
|
||||
offset = Integer(offset)
|
||||
length = length ? Integer(length) : 1
|
||||
|
||||
if input.is_a?(Array)
|
||||
input.slice(offset, length) || []
|
||||
else
|
||||
input.to_s.slice(offset, length) || ''
|
||||
end
|
||||
end
|
||||
|
||||
# Truncate a string down to x characters
|
||||
def truncate(input, length = 50, truncate_string = "...".freeze)
|
||||
@@ -65,7 +75,7 @@ module Liquid
|
||||
# <div class="summary">{{ post | split '//' | first }}</div>
|
||||
#
|
||||
def split(input, pattern)
|
||||
input.split(pattern)
|
||||
input.to_s.split(pattern)
|
||||
end
|
||||
|
||||
def strip(input)
|
||||
@@ -92,31 +102,42 @@ module Liquid
|
||||
|
||||
# Join elements of the array with certain character between them
|
||||
def join(input, glue = ' '.freeze)
|
||||
[input].flatten.join(glue)
|
||||
InputIterator.new(input).join(glue)
|
||||
end
|
||||
|
||||
# Sort elements of the array
|
||||
# provide optional property with which to sort an array of hashes or drops
|
||||
def sort(input, property = nil)
|
||||
ary = flatten_if_necessary(input)
|
||||
ary = InputIterator.new(input)
|
||||
if property.nil?
|
||||
ary.sort
|
||||
elsif ary.first.respond_to?('[]'.freeze) and !ary.first[property].nil?
|
||||
elsif ary.first.respond_to?(:[]) && !ary.first[property].nil?
|
||||
ary.sort {|a,b| a[property] <=> b[property] }
|
||||
elsif ary.first.respond_to?(property)
|
||||
ary.sort {|a,b| a.send(property) <=> b.send(property) }
|
||||
end
|
||||
end
|
||||
|
||||
# Remove duplicate elements from an array
|
||||
# provide optional property with which to determine uniqueness
|
||||
def uniq(input, property = nil)
|
||||
ary = InputIterator.new(input)
|
||||
if property.nil?
|
||||
input.uniq
|
||||
elsif input.first.respond_to?(:[])
|
||||
input.uniq{ |a| a[property] }
|
||||
end
|
||||
end
|
||||
|
||||
# Reverse the elements of an array
|
||||
def reverse(input)
|
||||
ary = [input].flatten
|
||||
ary = InputIterator.new(input)
|
||||
ary.reverse
|
||||
end
|
||||
|
||||
# map/collect on a given property
|
||||
def map(input, property)
|
||||
flatten_if_necessary(input).map do |e|
|
||||
InputIterator.new(input).map do |e|
|
||||
e = e.call if e.is_a?(Proc)
|
||||
|
||||
if property == "to_liquid".freeze
|
||||
@@ -265,17 +286,6 @@ module Liquid
|
||||
|
||||
private
|
||||
|
||||
def flatten_if_necessary(input)
|
||||
ary = if input.is_a?(Array)
|
||||
input.flatten
|
||||
elsif input.is_a?(Enumerable) && !input.is_a?(Hash)
|
||||
input
|
||||
else
|
||||
[input].flatten
|
||||
end
|
||||
ary.map{ |e| e.respond_to?(:to_liquid) ? e.to_liquid : e }
|
||||
end
|
||||
|
||||
def to_number(obj)
|
||||
case obj
|
||||
when Float
|
||||
@@ -310,6 +320,36 @@ module Liquid
|
||||
result = to_number(input).send(operation, to_number(operand))
|
||||
result.is_a?(BigDecimal) ? result.to_f : result
|
||||
end
|
||||
|
||||
class InputIterator
|
||||
include Enumerable
|
||||
|
||||
def initialize(input)
|
||||
@input = if input.is_a?(Array)
|
||||
input.flatten
|
||||
elsif input.is_a?(Hash)
|
||||
[input]
|
||||
elsif input.is_a?(Enumerable)
|
||||
input
|
||||
else
|
||||
Array(input)
|
||||
end
|
||||
end
|
||||
|
||||
def join(glue)
|
||||
to_a.join(glue)
|
||||
end
|
||||
|
||||
def reverse
|
||||
reverse_each.to_a
|
||||
end
|
||||
|
||||
def each
|
||||
@input.each do |e|
|
||||
yield(e.respond_to?(:to_liquid) ? e.to_liquid : e)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Template.register_filter(StandardFilters)
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
module Liquid
|
||||
class Tag
|
||||
attr_accessor :options
|
||||
attr_accessor :options, :line_number
|
||||
attr_reader :nodelist, :warnings
|
||||
include ParserSwitching
|
||||
|
||||
class << self
|
||||
def parse(tag_name, markup, tokens, options)
|
||||
@@ -22,6 +23,10 @@ module Liquid
|
||||
def parse(tokens)
|
||||
end
|
||||
|
||||
def raw
|
||||
"#{@tag_name} #{@markup}"
|
||||
end
|
||||
|
||||
def name
|
||||
self.class.name.downcase
|
||||
end
|
||||
@@ -31,30 +36,7 @@ module Liquid
|
||||
end
|
||||
|
||||
def blank?
|
||||
@blank || false
|
||||
false
|
||||
end
|
||||
|
||||
def parse_with_selected_parser(markup)
|
||||
case @options[:error_mode] || Template.error_mode
|
||||
when :strict then strict_parse_with_error_context(markup)
|
||||
when :lax then lax_parse(markup)
|
||||
when :warn
|
||||
begin
|
||||
return strict_parse_with_error_context(markup)
|
||||
rescue SyntaxError => e
|
||||
@warnings ||= []
|
||||
@warnings << e
|
||||
return lax_parse(markup)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def strict_parse_with_error_context(markup)
|
||||
strict_parse(markup)
|
||||
rescue SyntaxError => e
|
||||
e.message << " in \"#{markup.strip}\""
|
||||
raise e
|
||||
end
|
||||
end # Tag
|
||||
end # Liquid
|
||||
end
|
||||
end
|
||||
|
||||
@@ -15,7 +15,8 @@ module Liquid
|
||||
super
|
||||
if markup =~ Syntax
|
||||
@to = $1
|
||||
@from = Variable.new($2)
|
||||
@from = Variable.new($2,options)
|
||||
@from.line_number = line_number
|
||||
else
|
||||
raise SyntaxError.new options[:locale].t("errors.syntax.assign".freeze)
|
||||
end
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
module Liquid
|
||||
|
||||
# Capture stores the result of a block into a variable without rendering it inplace.
|
||||
#
|
||||
# {% capture heading %}
|
||||
|
||||
@@ -43,11 +43,8 @@ module Liquid
|
||||
end
|
||||
end
|
||||
|
||||
def blank?
|
||||
false
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def variables_from_string(markup)
|
||||
markup.split(',').collect do |var|
|
||||
var =~ /\s*(#{QuotedFragment})\s*/o
|
||||
|
||||
@@ -38,10 +38,6 @@ module Liquid
|
||||
def parse(tokens)
|
||||
end
|
||||
|
||||
def blank?
|
||||
false
|
||||
end
|
||||
|
||||
def render(context)
|
||||
partial = load_cached_partial(context)
|
||||
variable = context[@variable_name || @template_name[1..-2]]
|
||||
|
||||
@@ -25,10 +25,6 @@ module Liquid
|
||||
context.environments.first[@variable] = value + 1
|
||||
value.to_s
|
||||
end
|
||||
|
||||
def blank?
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
Template.register_tag('increment'.freeze, Increment)
|
||||
|
||||
@@ -21,6 +21,38 @@ module Liquid
|
||||
attr_accessor :root, :resource_limits
|
||||
@@file_system = BlankFileSystem.new
|
||||
|
||||
class TagRegistry
|
||||
def initialize
|
||||
@tags = {}
|
||||
@cache = {}
|
||||
end
|
||||
|
||||
def [](tag_name)
|
||||
return nil unless @tags.has_key?(tag_name)
|
||||
return @cache[tag_name] if Liquid.cache_classes
|
||||
|
||||
lookup_class(@tags[tag_name]).tap { |o| @cache[tag_name] = o }
|
||||
end
|
||||
|
||||
def []=(tag_name, klass)
|
||||
@tags[tag_name] = klass.name
|
||||
@cache[tag_name] = klass
|
||||
end
|
||||
|
||||
def delete(tag_name)
|
||||
@tags.delete(tag_name)
|
||||
@cache.delete(tag_name)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def lookup_class(name)
|
||||
name.split("::").reject(&:empty?).reduce(Object) { |scope, const| scope.const_get(const) }
|
||||
end
|
||||
end
|
||||
|
||||
attr_reader :profiler
|
||||
|
||||
class << self
|
||||
# Sets how strict the parser should be.
|
||||
# :lax acts like liquid 2.5 and silently ignores malformed tags in most cases.
|
||||
@@ -41,7 +73,7 @@ module Liquid
|
||||
end
|
||||
|
||||
def tags
|
||||
@tags ||= {}
|
||||
@tags ||= TagRegistry.new
|
||||
end
|
||||
|
||||
def error_mode
|
||||
@@ -55,6 +87,8 @@ module Liquid
|
||||
end
|
||||
|
||||
# creates a new <tt>Template</tt> object from liquid source code
|
||||
# To enable profiling, pass in <tt>profile: true</tt> as an option.
|
||||
# See Liquid::Profiler for more information
|
||||
def parse(source, options = {})
|
||||
template = Template.new
|
||||
template.parse(source, options)
|
||||
@@ -69,6 +103,8 @@ module Liquid
|
||||
# Parse source code.
|
||||
# Returns self for easy chaining
|
||||
def parse(source, options = {})
|
||||
@profiling = options.delete(:profile)
|
||||
@line_numbers = options.delete(:line_numbers) || @profiling
|
||||
@root = Document.parse(tokenize(source), DEFAULT_OPTIONS.merge(options))
|
||||
@warnings = nil
|
||||
self
|
||||
@@ -100,6 +136,9 @@ module Liquid
|
||||
# if you use the same filters over and over again consider registering them globally
|
||||
# with <tt>Template.register_filter</tt>
|
||||
#
|
||||
# if profiling was enabled in <tt>Template#parse</tt> then the resulting profiling information
|
||||
# will be available via <tt>Template#profiler</tt>
|
||||
#
|
||||
# Following options can be passed:
|
||||
#
|
||||
# * <tt>filters</tt> : array with local filters
|
||||
@@ -112,7 +151,11 @@ module Liquid
|
||||
context = case args.first
|
||||
when Liquid::Context
|
||||
c = args.shift
|
||||
c.rethrow_errors = true if @rethrow_errors
|
||||
|
||||
if @rethrow_errors
|
||||
c.exception_handler = ->(e) { true }
|
||||
end
|
||||
|
||||
c
|
||||
when Liquid::Drop
|
||||
drop = args.shift
|
||||
@@ -137,6 +180,9 @@ module Liquid
|
||||
context.add_filters(options[:filters])
|
||||
end
|
||||
|
||||
if options[:exception_handler]
|
||||
context.exception_handler = options[:exception_handler]
|
||||
end
|
||||
when Module
|
||||
context.add_filters(args.pop)
|
||||
when Array
|
||||
@@ -146,10 +192,12 @@ module Liquid
|
||||
begin
|
||||
# render the nodelist.
|
||||
# for performance reasons we get an array back here. join will make a string out of it.
|
||||
result = @root.render(context)
|
||||
result = with_profiling do
|
||||
@root.render(context)
|
||||
end
|
||||
result.respond_to?(:join) ? result.join : result
|
||||
rescue Liquid::MemoryError => e
|
||||
context.handle_error(e)
|
||||
context.handle_error(e, nil)
|
||||
ensure
|
||||
@errors = context.errors
|
||||
end
|
||||
@@ -166,7 +214,8 @@ module Liquid
|
||||
def tokenize(source)
|
||||
source = source.source if source.respond_to?(:source)
|
||||
return [] if source.to_s.empty?
|
||||
tokens = source.split(TemplateParser)
|
||||
|
||||
tokens = calculate_line_numbers(source.split(TemplateParser))
|
||||
|
||||
# removes the rogue empty element at the beginning of the array
|
||||
tokens.shift if tokens[0] and tokens[0].empty?
|
||||
@@ -174,5 +223,30 @@ module Liquid
|
||||
tokens
|
||||
end
|
||||
|
||||
def calculate_line_numbers(raw_tokens)
|
||||
return raw_tokens unless @line_numbers
|
||||
|
||||
current_line = 1
|
||||
raw_tokens.map do |token|
|
||||
Token.new(token, current_line).tap do
|
||||
current_line += token.count("\n")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def with_profiling
|
||||
if @profiling
|
||||
@profiler = Profiler.new
|
||||
@profiler.start
|
||||
|
||||
begin
|
||||
yield
|
||||
ensure
|
||||
@profiler.stop
|
||||
end
|
||||
else
|
||||
yield
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
18
lib/liquid/token.rb
Normal file
18
lib/liquid/token.rb
Normal file
@@ -0,0 +1,18 @@
|
||||
module Liquid
|
||||
class Token < String
|
||||
attr_reader :line_number
|
||||
|
||||
def initialize(content, line_number)
|
||||
super(content)
|
||||
@line_number = line_number
|
||||
end
|
||||
|
||||
def raw
|
||||
"<raw>"
|
||||
end
|
||||
|
||||
def child(string)
|
||||
Token.new(string, @line_number)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -11,38 +11,37 @@ module Liquid
|
||||
# {{ user | link }}
|
||||
#
|
||||
class Variable
|
||||
FilterParser = /(?:#{FilterSeparator}|(?:\s*(?:#{QuotedFragment}|#{ArgumentSeparator})\s*)+)/o
|
||||
FilterParser = /(?:\s+|#{QuotedFragment}|#{ArgumentSeparator})+/o
|
||||
EasyParse = /\A *(\w+(?:\.\w+)*) *\z/
|
||||
attr_accessor :filters, :name, :warnings
|
||||
attr_accessor :line_number
|
||||
include ParserSwitching
|
||||
|
||||
def initialize(markup, options = {})
|
||||
@markup = markup
|
||||
@name = nil
|
||||
@options = options || {}
|
||||
|
||||
case @options[:error_mode] || Template.error_mode
|
||||
when :strict then strict_parse(markup)
|
||||
when :lax then lax_parse(markup)
|
||||
when :warn
|
||||
begin
|
||||
strict_parse(markup)
|
||||
rescue SyntaxError => e
|
||||
@warnings ||= []
|
||||
@warnings << e
|
||||
lax_parse(markup)
|
||||
end
|
||||
end
|
||||
parse_with_selected_parser(markup)
|
||||
end
|
||||
|
||||
def raw
|
||||
@markup
|
||||
end
|
||||
|
||||
def markup_context(markup)
|
||||
" in \"{{#{markup}}}\""
|
||||
end
|
||||
|
||||
def lax_parse(markup)
|
||||
@filters = []
|
||||
if match = markup.match(/\s*(#{QuotedFragment})(.*)/om)
|
||||
@name = match[1]
|
||||
if match[2].match(/#{FilterSeparator}\s*(.*)/om)
|
||||
if markup =~ /\s*(#{QuotedFragment})(.*)/om
|
||||
@name = Regexp.last_match(1)
|
||||
if Regexp.last_match(2) =~ /#{FilterSeparator}\s*(.*)/om
|
||||
filters = Regexp.last_match(1).scan(FilterParser)
|
||||
filters.each do |f|
|
||||
if matches = f.match(/\s*(\w+)/)
|
||||
filtername = matches[1]
|
||||
if f =~ /\w+/
|
||||
filtername = Regexp.last_match(0)
|
||||
filterargs = f.scan(/(?:#{FilterArgumentSeparator}|#{ArgumentSeparator})\s*((?:\w+\s*\:\s*)?#{QuotedFragment})/o).flatten
|
||||
@filters << [filtername, filterargs]
|
||||
end
|
||||
@@ -69,9 +68,6 @@ module Liquid
|
||||
@filters << [filtername, filterargs]
|
||||
end
|
||||
p.consume(:end_of_string)
|
||||
rescue SyntaxError => e
|
||||
e.message << " in \"{{#{markup}}}\""
|
||||
raise e
|
||||
end
|
||||
|
||||
def parse_filterargs(p)
|
||||
|
||||
68
lib/liquid/variable_lookup.rb
Normal file
68
lib/liquid/variable_lookup.rb
Normal file
@@ -0,0 +1,68 @@
|
||||
module Liquid
|
||||
class VariableLookup
|
||||
SQUARE_BRACKETED = /\A\[(.*)\]\z/m
|
||||
COMMAND_METHODS = ['size'.freeze, 'first'.freeze, 'last'.freeze]
|
||||
|
||||
def self.parse(markup)
|
||||
new(markup)
|
||||
end
|
||||
|
||||
def initialize(markup)
|
||||
lookups = markup.scan(VariableParser)
|
||||
|
||||
name = lookups.shift
|
||||
if name =~ SQUARE_BRACKETED
|
||||
name = Expression.parse($1)
|
||||
end
|
||||
@name = name
|
||||
|
||||
@lookups = lookups
|
||||
@command_flags = 0
|
||||
|
||||
@lookups.each_index do |i|
|
||||
lookup = lookups[i]
|
||||
if lookup =~ SQUARE_BRACKETED
|
||||
lookups[i] = Expression.parse($1)
|
||||
elsif COMMAND_METHODS.include?(lookup)
|
||||
@command_flags |= 1 << i
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def evaluate(context)
|
||||
name = context.evaluate(@name)
|
||||
object = context.find_variable(name)
|
||||
|
||||
@lookups.each_index do |i|
|
||||
key = context.evaluate(@lookups[i])
|
||||
|
||||
# If object is a hash- or array-like object we look for the
|
||||
# presence of the key and if its available we return it
|
||||
if object.respond_to?(:[]) &&
|
||||
((object.respond_to?(:has_key?) && object.has_key?(key)) ||
|
||||
(object.respond_to?(:fetch) && key.is_a?(Integer)))
|
||||
|
||||
# if its a proc we will replace the entry with the proc
|
||||
res = context.lookup_and_evaluate(object, key)
|
||||
object = res.to_liquid
|
||||
|
||||
# Some special cases. If the part wasn't in square brackets and
|
||||
# no key with the same name was found we interpret following calls
|
||||
# as commands and call them on the current object
|
||||
elsif @command_flags & (1 << i) != 0 && object.respond_to?(key)
|
||||
object = object.send(key).to_liquid
|
||||
|
||||
# No key was present with the desired value and it wasn't one of the directly supported
|
||||
# keywords either. The only thing we got left is to return nil
|
||||
else
|
||||
return nil
|
||||
end
|
||||
|
||||
# If we are dealing with a drop here we have to
|
||||
object.context = context if object.respond_to?(:context=)
|
||||
end
|
||||
|
||||
object
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,4 +1,4 @@
|
||||
# encoding: utf-8
|
||||
module Liquid
|
||||
VERSION = "3.0.0".freeze
|
||||
VERSION = "3.0.0"
|
||||
end
|
||||
|
||||
@@ -24,6 +24,6 @@ Gem::Specification.new do |s|
|
||||
|
||||
s.require_path = "lib"
|
||||
|
||||
s.add_development_dependency 'stackprof' if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("2.1.0")
|
||||
s.add_development_dependency 'rake'
|
||||
s.add_development_dependency 'minitest'
|
||||
end
|
||||
|
||||
@@ -1,11 +1,17 @@
|
||||
require 'benchmark'
|
||||
require 'benchmark/ips'
|
||||
require File.dirname(__FILE__) + '/theme_runner'
|
||||
|
||||
Liquid::Template.error_mode = ARGV.first.to_sym if ARGV.first
|
||||
profiler = ThemeRunner.new
|
||||
|
||||
Benchmark.bmbm do |x|
|
||||
x.report("parse:") { 100.times { profiler.compile } }
|
||||
x.report("parse & run:") { 100.times { profiler.run } }
|
||||
end
|
||||
Benchmark.ips do |x|
|
||||
x.time = 60
|
||||
x.warmup = 5
|
||||
|
||||
puts
|
||||
puts "Running benchmark for #{x.time} seconds (with #{x.warmup} seconds warmup)."
|
||||
puts
|
||||
|
||||
x.report("parse:") { profiler.compile }
|
||||
x.report("parse & run:") { profiler.run }
|
||||
end
|
||||
|
||||
@@ -4,10 +4,14 @@ require File.dirname(__FILE__) + '/theme_runner'
|
||||
Liquid::Template.error_mode = ARGV.first.to_sym if ARGV.first
|
||||
profiler = ThemeRunner.new
|
||||
profiler.run
|
||||
results = StackProf.run(mode: :cpu) do
|
||||
100.times do
|
||||
profiler.run
|
||||
|
||||
[:cpu, :object].each do |profile_type|
|
||||
puts "Profiling in #{profile_type.to_s} mode..."
|
||||
results = StackProf.run(mode: profile_type) do
|
||||
100.times do
|
||||
profiler.run
|
||||
end
|
||||
end
|
||||
StackProf::Report.new(results).print_text(false, 20)
|
||||
File.write(ENV['FILENAME'] + "." + profile_type.to_s, Marshal.dump(results)) if ENV['FILENAME']
|
||||
end
|
||||
StackProf::Report.new(results).print_text(false, 20)
|
||||
File.write(ENV['FILENAME'], Marshal.dump(results)) if ENV['FILENAME']
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
require 'test_helper'
|
||||
|
||||
class AssignTest < Test::Unit::TestCase
|
||||
class AssignTest < Minitest::Test
|
||||
include Liquid
|
||||
|
||||
def test_assigned_variable
|
||||
@@ -24,4 +24,15 @@ class AssignTest < Test::Unit::TestCase
|
||||
'{% assign foo not values %}.',
|
||||
'values' => "foo,bar,baz")
|
||||
end
|
||||
|
||||
def test_assign_uses_error_mode
|
||||
with_error_mode(:strict) do
|
||||
assert_raises(SyntaxError) do
|
||||
Template.parse("{% assign foo = ('X' | downcase) %}")
|
||||
end
|
||||
end
|
||||
with_error_mode(:lax) do
|
||||
assert Template.parse("{% assign foo = ('X' | downcase) %}")
|
||||
end
|
||||
end
|
||||
end # AssignTest
|
||||
|
||||
@@ -14,7 +14,7 @@ class BlankTestFileSystem
|
||||
end
|
||||
end
|
||||
|
||||
class BlankTest < Test::Unit::TestCase
|
||||
class BlankTest < Minitest::Test
|
||||
include Liquid
|
||||
N = 10
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
require 'test_helper'
|
||||
|
||||
class CaptureTest < Test::Unit::TestCase
|
||||
class CaptureTest < Minitest::Test
|
||||
include Liquid
|
||||
|
||||
def test_captures_block_content_in_variable
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
require 'test_helper'
|
||||
|
||||
class ContextTest < Test::Unit::TestCase
|
||||
class ContextTest < Minitest::Test
|
||||
include Liquid
|
||||
|
||||
def test_override_global_filter
|
||||
@@ -16,8 +16,19 @@ class ContextTest < Test::Unit::TestCase
|
||||
end
|
||||
end
|
||||
|
||||
Template.register_filter(global)
|
||||
assert_equal 'Global test', Template.parse("{{'test' | notice }}").render!
|
||||
assert_equal 'Local test', Template.parse("{{'test' | notice }}").render!({}, :filters => [local])
|
||||
with_global_filter(global) do
|
||||
assert_equal 'Global test', Template.parse("{{'test' | notice }}").render!
|
||||
assert_equal 'Local test', Template.parse("{{'test' | notice }}").render!({}, :filters => [local])
|
||||
end
|
||||
end
|
||||
|
||||
def test_has_key_will_not_add_an_error_for_missing_keys
|
||||
Template.error_mode = :strict
|
||||
|
||||
context = Context.new
|
||||
|
||||
context.has_key?('unknown')
|
||||
|
||||
assert_empty context.errors
|
||||
end
|
||||
end
|
||||
|
||||
@@ -100,14 +100,12 @@ class RealEnumerableDrop < Liquid::Drop
|
||||
end
|
||||
end
|
||||
|
||||
class DropsTest < Test::Unit::TestCase
|
||||
class DropsTest < Minitest::Test
|
||||
include Liquid
|
||||
|
||||
def test_product_drop
|
||||
assert_nothing_raised do
|
||||
tpl = Liquid::Template.parse( ' ' )
|
||||
tpl.render!('product' => ProductDrop.new)
|
||||
end
|
||||
tpl = Liquid::Template.parse(' ')
|
||||
assert_equal ' ', tpl.render!('product' => ProductDrop.new)
|
||||
end
|
||||
|
||||
def test_drop_does_only_respond_to_whitelisted_methods
|
||||
|
||||
@@ -19,73 +19,94 @@ class ErrorDrop < Liquid::Drop
|
||||
|
||||
end
|
||||
|
||||
class ErrorHandlingTest < Test::Unit::TestCase
|
||||
class ErrorHandlingTest < Minitest::Test
|
||||
include Liquid
|
||||
|
||||
def test_standard_error
|
||||
assert_nothing_raised do
|
||||
template = Liquid::Template.parse( ' {{ errors.standard_error }} ' )
|
||||
assert_equal ' Liquid error: standard error ', template.render('errors' => ErrorDrop.new)
|
||||
def test_templates_parsed_with_line_numbers_renders_them_in_errors
|
||||
template = <<-LIQUID
|
||||
Hello,
|
||||
|
||||
assert_equal 1, template.errors.size
|
||||
assert_equal StandardError, template.errors.first.class
|
||||
end
|
||||
{{ errors.standard_error }} will raise a standard error.
|
||||
|
||||
Bla bla test.
|
||||
|
||||
{{ errors.syntax_error }} will raise a syntax error.
|
||||
|
||||
This is an argument error: {{ errors.argument_error }}
|
||||
|
||||
Bla.
|
||||
LIQUID
|
||||
|
||||
expected = <<-TEXT
|
||||
Hello,
|
||||
|
||||
Liquid error: 3: standard error will raise a standard error.
|
||||
|
||||
Bla bla test.
|
||||
|
||||
Liquid syntax error: 7: syntax error will raise a syntax error.
|
||||
|
||||
This is an argument error: Liquid error: 9: argument error
|
||||
|
||||
Bla.
|
||||
TEXT
|
||||
|
||||
output = Liquid::Template.parse(template, line_numbers: true).render('errors' => ErrorDrop.new)
|
||||
assert_equal expected, output
|
||||
end
|
||||
|
||||
def test_standard_error
|
||||
template = Liquid::Template.parse( ' {{ errors.standard_error }} ' )
|
||||
assert_equal ' Liquid error: standard error ', template.render('errors' => ErrorDrop.new)
|
||||
|
||||
assert_equal 1, template.errors.size
|
||||
assert_equal StandardError, template.errors.first.class
|
||||
end
|
||||
|
||||
def test_syntax
|
||||
template = Liquid::Template.parse( ' {{ errors.syntax_error }} ' )
|
||||
assert_equal ' Liquid syntax error: syntax error ', template.render('errors' => ErrorDrop.new)
|
||||
|
||||
assert_nothing_raised do
|
||||
|
||||
template = Liquid::Template.parse( ' {{ errors.syntax_error }} ' )
|
||||
assert_equal ' Liquid syntax error: syntax error ', template.render('errors' => ErrorDrop.new)
|
||||
|
||||
assert_equal 1, template.errors.size
|
||||
assert_equal SyntaxError, template.errors.first.class
|
||||
|
||||
end
|
||||
assert_equal 1, template.errors.size
|
||||
assert_equal SyntaxError, template.errors.first.class
|
||||
end
|
||||
|
||||
def test_argument
|
||||
assert_nothing_raised do
|
||||
template = Liquid::Template.parse( ' {{ errors.argument_error }} ' )
|
||||
assert_equal ' Liquid error: argument error ', template.render('errors' => ErrorDrop.new)
|
||||
|
||||
template = Liquid::Template.parse( ' {{ errors.argument_error }} ' )
|
||||
assert_equal ' Liquid error: argument error ', template.render('errors' => ErrorDrop.new)
|
||||
|
||||
assert_equal 1, template.errors.size
|
||||
assert_equal ArgumentError, template.errors.first.class
|
||||
end
|
||||
assert_equal 1, template.errors.size
|
||||
assert_equal ArgumentError, template.errors.first.class
|
||||
end
|
||||
|
||||
def test_missing_endtag_parse_time_error
|
||||
assert_raise(Liquid::SyntaxError) do
|
||||
assert_raises(Liquid::SyntaxError) do
|
||||
Liquid::Template.parse(' {% for a in b %} ... ')
|
||||
end
|
||||
end
|
||||
|
||||
def test_unrecognized_operator
|
||||
with_error_mode(:strict) do
|
||||
assert_raise(SyntaxError) do
|
||||
assert_raises(SyntaxError) do
|
||||
Liquid::Template.parse(' {% if 1 =! 2 %}ok{% endif %} ')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def test_lax_unrecognized_operator
|
||||
assert_nothing_raised do
|
||||
template = Liquid::Template.parse(' {% if 1 =! 2 %}ok{% endif %} ', :error_mode => :lax)
|
||||
assert_equal ' Liquid error: Unknown operator =! ', template.render
|
||||
assert_equal 1, template.errors.size
|
||||
assert_equal Liquid::ArgumentError, template.errors.first.class
|
||||
end
|
||||
template = Liquid::Template.parse(' {% if 1 =! 2 %}ok{% endif %} ', :error_mode => :lax)
|
||||
assert_equal ' Liquid error: Unknown operator =! ', template.render
|
||||
assert_equal 1, template.errors.size
|
||||
assert_equal Liquid::ArgumentError, template.errors.first.class
|
||||
end
|
||||
|
||||
def test_strict_error_messages
|
||||
err = assert_raise(SyntaxError) do
|
||||
err = assert_raises(SyntaxError) do
|
||||
Liquid::Template.parse(' {% if 1 =! 2 %}ok{% endif %} ', :error_mode => :strict)
|
||||
end
|
||||
assert_equal 'Unexpected character = in "1 =! 2"', err.message
|
||||
|
||||
err = assert_raise(SyntaxError) do
|
||||
err = assert_raises(SyntaxError) do
|
||||
Liquid::Template.parse('{{%%%}}', :error_mode => :strict)
|
||||
end
|
||||
assert_equal 'Unexpected character % in "{{%%%}}"', err.message
|
||||
@@ -100,11 +121,17 @@ class ErrorHandlingTest < Test::Unit::TestCase
|
||||
assert_equal '', template.render
|
||||
end
|
||||
|
||||
def test_warning_line_numbers
|
||||
template = Liquid::Template.parse("{% if ~~~ %}\n{{%%%}}{% else %}\n{{ hello. }}{% endif %}", :error_mode => :warn, :line_numbers => true)
|
||||
assert_equal 3, template.warnings.size
|
||||
assert_equal [1,2,3], template.warnings.map(&:line_number)
|
||||
end
|
||||
|
||||
# Liquid should not catch Exceptions that are not subclasses of StandardError, like Interrupt and NoMemoryError
|
||||
def test_exceptions_propagate
|
||||
assert_raise Exception do
|
||||
template = Liquid::Template.parse( ' {{ errors.exception }} ' )
|
||||
assert_raises Exception do
|
||||
template = Liquid::Template.parse('{{ errors.exception }}')
|
||||
template.render('errors' => ErrorDrop.new)
|
||||
end
|
||||
end
|
||||
end # ErrorHandlingTest
|
||||
end
|
||||
|
||||
@@ -22,7 +22,7 @@ module SubstituteFilter
|
||||
end
|
||||
end
|
||||
|
||||
class FiltersTest < Test::Unit::TestCase
|
||||
class FiltersTest < Minitest::Test
|
||||
include Liquid
|
||||
|
||||
def setup
|
||||
@@ -67,12 +67,12 @@ class FiltersTest < Test::Unit::TestCase
|
||||
@context['value'] = 3
|
||||
@context['numbers'] = [2,1,4,3]
|
||||
@context['words'] = ['expected', 'as', 'alphabetic']
|
||||
@context['arrays'] = [['flattened'], ['are']]
|
||||
@context['arrays'] = ['flower', 'are']
|
||||
|
||||
assert_equal [1,2,3,4], Variable.new("numbers | sort").render(@context)
|
||||
assert_equal ['alphabetic', 'as', 'expected'], Variable.new("words | sort").render(@context)
|
||||
assert_equal [3], Variable.new("value | sort").render(@context)
|
||||
assert_equal ['are', 'flattened'], Variable.new("arrays | sort").render(@context)
|
||||
assert_equal ['are', 'flower'], Variable.new("arrays | sort").render(@context)
|
||||
end
|
||||
|
||||
def test_strip_html
|
||||
@@ -107,15 +107,15 @@ class FiltersTest < Test::Unit::TestCase
|
||||
end
|
||||
end
|
||||
|
||||
class FiltersInTemplate < Test::Unit::TestCase
|
||||
class FiltersInTemplate < Minitest::Test
|
||||
include Liquid
|
||||
|
||||
def test_local_global
|
||||
Template.register_filter(MoneyFilter)
|
||||
|
||||
assert_equal " 1000$ ", Template.parse("{{1000 | money}}").render!(nil, nil)
|
||||
assert_equal " 1000$ CAD ", Template.parse("{{1000 | money}}").render!(nil, :filters => CanadianMoneyFilter)
|
||||
assert_equal " 1000$ CAD ", Template.parse("{{1000 | money}}").render!(nil, :filters => [CanadianMoneyFilter])
|
||||
with_global_filter(MoneyFilter) do
|
||||
assert_equal " 1000$ ", Template.parse("{{1000 | money}}").render!(nil, nil)
|
||||
assert_equal " 1000$ CAD ", Template.parse("{{1000 | money}}").render!(nil, :filters => CanadianMoneyFilter)
|
||||
assert_equal " 1000$ CAD ", Template.parse("{{1000 | money}}").render!(nil, :filters => [CanadianMoneyFilter])
|
||||
end
|
||||
end
|
||||
|
||||
def test_local_filter_with_deprecated_syntax
|
||||
|
||||
@@ -12,14 +12,12 @@ module CanadianMoneyFilter
|
||||
end
|
||||
end
|
||||
|
||||
class HashOrderingTest < Test::Unit::TestCase
|
||||
class HashOrderingTest < Minitest::Test
|
||||
include Liquid
|
||||
|
||||
def test_global_register_order
|
||||
Template.register_filter(MoneyFilter)
|
||||
Template.register_filter(CanadianMoneyFilter)
|
||||
|
||||
assert_equal " 1000$ CAD ", Template.parse("{{1000 | money}}").render(nil, nil)
|
||||
end
|
||||
|
||||
def test_global_register_order
|
||||
with_global_filter(MoneyFilter, CanadianMoneyFilter) do
|
||||
assert_equal " 1000$ CAD ", Template.parse("{{1000 | money}}").render(nil, nil)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -27,7 +27,7 @@ module FunnyFilter
|
||||
|
||||
end
|
||||
|
||||
class OutputTest < Test::Unit::TestCase
|
||||
class OutputTest < Minitest::Test
|
||||
include Liquid
|
||||
|
||||
def setup
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
require 'test_helper'
|
||||
|
||||
class ParsingQuirksTest < Test::Unit::TestCase
|
||||
class ParsingQuirksTest < Minitest::Test
|
||||
include Liquid
|
||||
|
||||
def test_parsing_css
|
||||
@@ -9,30 +9,28 @@ class ParsingQuirksTest < Test::Unit::TestCase
|
||||
end
|
||||
|
||||
def test_raise_on_single_close_bracet
|
||||
assert_raise(SyntaxError) do
|
||||
assert_raises(SyntaxError) do
|
||||
Template.parse("text {{method} oh nos!")
|
||||
end
|
||||
end
|
||||
|
||||
def test_raise_on_label_and_no_close_bracets
|
||||
assert_raise(SyntaxError) do
|
||||
assert_raises(SyntaxError) do
|
||||
Template.parse("TEST {{ ")
|
||||
end
|
||||
end
|
||||
|
||||
def test_raise_on_label_and_no_close_bracets_percent
|
||||
assert_raise(SyntaxError) do
|
||||
assert_raises(SyntaxError) do
|
||||
Template.parse("TEST {% ")
|
||||
end
|
||||
end
|
||||
|
||||
def test_error_on_empty_filter
|
||||
assert_nothing_raised do
|
||||
Template.parse("{{test}}")
|
||||
Template.parse("{{|test}}")
|
||||
end
|
||||
assert Template.parse("{{test}}")
|
||||
assert Template.parse("{{|test}}")
|
||||
with_error_mode(:strict) do
|
||||
assert_raise(SyntaxError) do
|
||||
assert_raises(SyntaxError) do
|
||||
Template.parse("{{test |a|b|}}")
|
||||
end
|
||||
end
|
||||
@@ -40,7 +38,7 @@ class ParsingQuirksTest < Test::Unit::TestCase
|
||||
|
||||
def test_meaningless_parens_error
|
||||
with_error_mode(:strict) do
|
||||
assert_raise(SyntaxError) do
|
||||
assert_raises(SyntaxError) do
|
||||
markup = "a == 'foo' or (b == 'bar' and c == 'baz') or false"
|
||||
Template.parse("{% if #{markup} %} YES {% endif %}")
|
||||
end
|
||||
@@ -49,11 +47,11 @@ class ParsingQuirksTest < Test::Unit::TestCase
|
||||
|
||||
def test_unexpected_characters_syntax_error
|
||||
with_error_mode(:strict) do
|
||||
assert_raise(SyntaxError) do
|
||||
assert_raises(SyntaxError) do
|
||||
markup = "true && false"
|
||||
Template.parse("{% if #{markup} %} YES {% endif %}")
|
||||
end
|
||||
assert_raise(SyntaxError) do
|
||||
assert_raises(SyntaxError) do
|
||||
markup = "false || true"
|
||||
Template.parse("{% if #{markup} %} YES {% endif %}")
|
||||
end
|
||||
@@ -61,11 +59,9 @@ class ParsingQuirksTest < Test::Unit::TestCase
|
||||
end
|
||||
|
||||
def test_no_error_on_lax_empty_filter
|
||||
assert_nothing_raised do
|
||||
Template.parse("{{test |a|b|}}", :error_mode => :lax)
|
||||
Template.parse("{{test}}", :error_mode => :lax)
|
||||
Template.parse("{{|test|}}", :error_mode => :lax)
|
||||
end
|
||||
assert Template.parse("{{test |a|b|}}", :error_mode => :lax)
|
||||
assert Template.parse("{{test}}", :error_mode => :lax)
|
||||
assert Template.parse("{{|test|}}", :error_mode => :lax)
|
||||
end
|
||||
|
||||
def test_meaningless_parens_lax
|
||||
@@ -84,4 +80,24 @@ class ParsingQuirksTest < Test::Unit::TestCase
|
||||
assert_template_result('',"{% if #{markup} %} YES {% endif %}")
|
||||
end
|
||||
end
|
||||
|
||||
def test_raise_on_invalid_tag_delimiter
|
||||
assert_raises(Liquid::SyntaxError) do
|
||||
Template.new.parse('{% end %}')
|
||||
end
|
||||
end
|
||||
|
||||
def test_unanchored_filter_arguments
|
||||
with_error_mode(:lax) do
|
||||
assert_template_result('hi',"{{ 'hi there' | split$$$:' ' | first }}")
|
||||
|
||||
assert_template_result('x', "{{ 'X' | downcase) }}")
|
||||
|
||||
# After the messed up quotes a filter without parameters (reverse) should work
|
||||
# but one with parameters (remove) shouldn't be detected.
|
||||
assert_template_result('here', "{{ 'hi there' | split:\"t\"\" | reverse | first}}")
|
||||
assert_template_result('hi ', "{{ 'hi there' | split:\"t\"\" | remove:\"i\" | first}}")
|
||||
end
|
||||
end
|
||||
|
||||
end # ParsingQuirksTest
|
||||
|
||||
142
test/integration/render_profiling_test.rb
Normal file
142
test/integration/render_profiling_test.rb
Normal file
@@ -0,0 +1,142 @@
|
||||
require 'test_helper'
|
||||
|
||||
class RenderProfilingTest < Minitest::Test
|
||||
include Liquid
|
||||
|
||||
class ProfilingFileSystem
|
||||
def read_template_file(template_path, context)
|
||||
"Rendering template {% assign template_name = '#{template_path}'%}\n{{ template_name }}"
|
||||
end
|
||||
end
|
||||
|
||||
def setup
|
||||
Liquid::Template.file_system = ProfilingFileSystem.new
|
||||
end
|
||||
|
||||
def test_template_allows_flagging_profiling
|
||||
t = Template.parse("{{ 'a string' | upcase }}")
|
||||
t.render!
|
||||
|
||||
assert_nil t.profiler
|
||||
end
|
||||
|
||||
def test_parse_makes_available_simple_profiling
|
||||
t = Template.parse("{{ 'a string' | upcase }}", :profile => true)
|
||||
t.render!
|
||||
|
||||
assert_equal 1, t.profiler.length
|
||||
|
||||
node = t.profiler[0]
|
||||
assert_equal " 'a string' | upcase ", node.code
|
||||
end
|
||||
|
||||
def test_render_ignores_raw_strings_when_profiling
|
||||
t = Template.parse("This is raw string\nstuff\nNewline", :profile => true)
|
||||
t.render!
|
||||
|
||||
assert_equal 0, t.profiler.length
|
||||
end
|
||||
|
||||
def test_profiling_includes_line_numbers_of_liquid_nodes
|
||||
t = Template.parse("{{ 'a string' | upcase }}\n{% increment test %}", :profile => true)
|
||||
t.render!
|
||||
assert_equal 2, t.profiler.length
|
||||
|
||||
# {{ 'a string' | upcase }}
|
||||
assert_equal 1, t.profiler[0].line_number
|
||||
# {{ increment test }}
|
||||
assert_equal 2, t.profiler[1].line_number
|
||||
end
|
||||
|
||||
def test_profiling_times_the_rendering_of_tokens
|
||||
t = Template.parse("{% include 'a_template' %}", :profile => true)
|
||||
t.render!
|
||||
|
||||
node = t.profiler[0]
|
||||
refute_nil node.render_time
|
||||
end
|
||||
|
||||
def test_profiling_times_the_entire_render
|
||||
t = Template.parse("{% include 'a_template' %}", :profile => true)
|
||||
t.render!
|
||||
|
||||
assert t.profiler.total_render_time > 0, "Total render time was not calculated"
|
||||
end
|
||||
|
||||
def test_profiling_uses_include_to_mark_children
|
||||
t = Template.parse("{{ 'a string' | upcase }}\n{% include 'a_template' %}", :profile => true)
|
||||
t.render!
|
||||
|
||||
include_node = t.profiler[1]
|
||||
assert_equal 2, include_node.children.length
|
||||
end
|
||||
|
||||
def test_profiling_marks_children_with_the_name_of_included_partial
|
||||
t = Template.parse("{{ 'a string' | upcase }}\n{% include 'a_template' %}", :profile => true)
|
||||
t.render!
|
||||
|
||||
include_node = t.profiler[1]
|
||||
include_node.children.each do |child|
|
||||
assert_equal "'a_template'", child.partial
|
||||
end
|
||||
end
|
||||
|
||||
def test_profiling_supports_multiple_templates
|
||||
t = Template.parse("{{ 'a string' | upcase }}\n{% include 'a_template' %}\n{% include 'b_template' %}", :profile => true)
|
||||
t.render!
|
||||
|
||||
a_template = t.profiler[1]
|
||||
a_template.children.each do |child|
|
||||
assert_equal "'a_template'", child.partial
|
||||
end
|
||||
|
||||
b_template = t.profiler[2]
|
||||
b_template.children.each do |child|
|
||||
assert_equal "'b_template'", child.partial
|
||||
end
|
||||
end
|
||||
|
||||
def test_profiling_supports_rendering_the_same_partial_multiple_times
|
||||
t = Template.parse("{{ 'a string' | upcase }}\n{% include 'a_template' %}\n{% include 'a_template' %}", :profile => true)
|
||||
t.render!
|
||||
|
||||
a_template1 = t.profiler[1]
|
||||
a_template1.children.each do |child|
|
||||
assert_equal "'a_template'", child.partial
|
||||
end
|
||||
|
||||
a_template2 = t.profiler[2]
|
||||
a_template2.children.each do |child|
|
||||
assert_equal "'a_template'", child.partial
|
||||
end
|
||||
end
|
||||
|
||||
def test_can_iterate_over_each_profiling_entry
|
||||
t = Template.parse("{{ 'a string' | upcase }}\n{% increment test %}", :profile => true)
|
||||
t.render!
|
||||
|
||||
timing_count = 0
|
||||
t.profiler.each do |timing|
|
||||
timing_count += 1
|
||||
end
|
||||
|
||||
assert_equal 2, timing_count
|
||||
end
|
||||
|
||||
def test_profiling_marks_children_of_if_blocks
|
||||
t = Template.parse("{% if true %} {% increment test %} {{ test }} {% endif %}", :profile => true)
|
||||
t.render!
|
||||
|
||||
assert_equal 1, t.profiler.length
|
||||
assert_equal 2, t.profiler[0].children.length
|
||||
end
|
||||
|
||||
def test_profiling_marks_children_of_for_blocks
|
||||
t = Template.parse("{% for item in collection %} {{ item }} {% endfor %}", :profile => true)
|
||||
t.render!({"collection" => ["one", "two"]})
|
||||
|
||||
assert_equal 1, t.profiler.length
|
||||
# Will profile each invocation of the for block
|
||||
assert_equal 2, t.profiler[0].children.length
|
||||
end
|
||||
end
|
||||
@@ -6,7 +6,7 @@ module SecurityFilter
|
||||
end
|
||||
end
|
||||
|
||||
class SecurityTest < Test::Unit::TestCase
|
||||
class SecurityTest < Minitest::Test
|
||||
include Liquid
|
||||
|
||||
def test_no_instance_eval
|
||||
|
||||
@@ -7,6 +7,8 @@ class Filters
|
||||
end
|
||||
|
||||
class TestThing
|
||||
attr_reader :foo
|
||||
|
||||
def initialize
|
||||
@foo = 0
|
||||
end
|
||||
@@ -39,7 +41,7 @@ class TestEnumerable < Liquid::Drop
|
||||
end
|
||||
end
|
||||
|
||||
class StandardFiltersTest < Test::Unit::TestCase
|
||||
class StandardFiltersTest < Minitest::Test
|
||||
include Liquid
|
||||
|
||||
def setup
|
||||
@@ -62,6 +64,34 @@ class StandardFiltersTest < Test::Unit::TestCase
|
||||
assert_equal '', @filters.upcase(nil)
|
||||
end
|
||||
|
||||
def test_slice
|
||||
assert_equal 'oob', @filters.slice('foobar', 1, 3)
|
||||
assert_equal 'oobar', @filters.slice('foobar', 1, 1000)
|
||||
assert_equal '', @filters.slice('foobar', 1, 0)
|
||||
assert_equal 'o', @filters.slice('foobar', 1, 1)
|
||||
assert_equal 'bar', @filters.slice('foobar', 3, 3)
|
||||
assert_equal 'ar', @filters.slice('foobar', -2, 2)
|
||||
assert_equal 'ar', @filters.slice('foobar', -2, 1000)
|
||||
assert_equal 'r', @filters.slice('foobar', -1)
|
||||
assert_equal '', @filters.slice(nil, 0)
|
||||
assert_equal '', @filters.slice('foobar', 100, 10)
|
||||
assert_equal '', @filters.slice('foobar', -100, 10)
|
||||
end
|
||||
|
||||
def test_slice_on_arrays
|
||||
input = 'foobar'.split(//)
|
||||
assert_equal %w{o o b}, @filters.slice(input, 1, 3)
|
||||
assert_equal %w{o o b a r}, @filters.slice(input, 1, 1000)
|
||||
assert_equal %w{}, @filters.slice(input, 1, 0)
|
||||
assert_equal %w{o}, @filters.slice(input, 1, 1)
|
||||
assert_equal %w{b a r}, @filters.slice(input, 3, 3)
|
||||
assert_equal %w{a r}, @filters.slice(input, -2, 2)
|
||||
assert_equal %w{a r}, @filters.slice(input, -2, 1000)
|
||||
assert_equal %w{r}, @filters.slice(input, -1)
|
||||
assert_equal %w{}, @filters.slice(input, 100, 10)
|
||||
assert_equal %w{}, @filters.slice(input, -100, 10)
|
||||
end
|
||||
|
||||
def test_truncate
|
||||
assert_equal '1234...', @filters.truncate('1234567890', 7)
|
||||
assert_equal '1234567890', @filters.truncate('1234567890', 20)
|
||||
@@ -76,6 +106,7 @@ class StandardFiltersTest < Test::Unit::TestCase
|
||||
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, ' ')
|
||||
end
|
||||
|
||||
def test_escape
|
||||
@@ -115,6 +146,10 @@ class StandardFiltersTest < Test::Unit::TestCase
|
||||
assert_equal [{"a" => 1}, {"a" => 2}, {"a" => 3}, {"a" => 4}], @filters.sort([{"a" => 4}, {"a" => 3}, {"a" => 1}, {"a" => 2}], "a")
|
||||
end
|
||||
|
||||
def test_legacy_sort_hash
|
||||
assert_equal [{a:1, b:2}], @filters.sort({a:1, b:2})
|
||||
end
|
||||
|
||||
def test_numerical_vs_lexicographical_sort
|
||||
assert_equal [2, 10], @filters.sort([10, 2])
|
||||
assert_equal [{"a" => 2}, {"a" => 10}], @filters.sort([{"a" => 10}, {"a" => 2}], "a")
|
||||
@@ -122,10 +157,21 @@ class StandardFiltersTest < Test::Unit::TestCase
|
||||
assert_equal [{"a" => "10"}, {"a" => "2"}], @filters.sort([{"a" => "10"}, {"a" => "2"}], "a")
|
||||
end
|
||||
|
||||
def test_uniq
|
||||
assert_equal [1,3,2,4], @filters.uniq([1,1,3,2,3,1,4,3,2,1])
|
||||
assert_equal [{"a" => 1}, {"a" => 3}, {"a" => 2}], @filters.uniq([{"a" => 1}, {"a" => 3}, {"a" => 1}, {"a" => 2}], "a")
|
||||
testdrop = TestDrop.new
|
||||
assert_equal [testdrop], @filters.uniq([testdrop, TestDrop.new], 'test')
|
||||
end
|
||||
|
||||
def test_reverse
|
||||
assert_equal [4,3,2,1], @filters.reverse([1,2,3,4])
|
||||
end
|
||||
|
||||
def test_legacy_reverse_hash
|
||||
assert_equal [{a:1, b:2}], @filters.reverse(a:1, b:2)
|
||||
end
|
||||
|
||||
def test_map
|
||||
assert_equal [1,2,3,4], @filters.map([{"a" => 1}, {"a" => 2}, {"a" => 3}, {"a" => 4}], 'a')
|
||||
assert_template_result 'abc', "{{ ary | map:'foo' | map:'bar' }}",
|
||||
@@ -147,9 +193,16 @@ class StandardFiltersTest < Test::Unit::TestCase
|
||||
"thing" => { "foo" => [ { "bar" => 42 }, { "bar" => 17 } ] }
|
||||
end
|
||||
|
||||
def test_legacy_map_on_hashes_with_dynamic_key
|
||||
template = "{% assign key = 'foo' %}{{ thing | map: key | map: 'bar' }}"
|
||||
hash = { "foo" => { "bar" => 42 } }
|
||||
assert_template_result "42", template, "thing" => hash
|
||||
end
|
||||
|
||||
def test_sort_calls_to_liquid
|
||||
t = TestThing.new
|
||||
assert_template_result "woot: 1", '{{ foo | sort: "whatever" }}', "foo" => [t]
|
||||
Liquid::Template.parse('{{ foo | sort: "whatever" }}').render("foo" => [t])
|
||||
assert t.foo > 0
|
||||
end
|
||||
|
||||
def test_map_over_proc
|
||||
@@ -167,6 +220,11 @@ class StandardFiltersTest < Test::Unit::TestCase
|
||||
assert_template_result "213", '{{ foo | sort: "bar" | map: "foo" }}', "foo" => TestEnumerable.new
|
||||
end
|
||||
|
||||
def test_first_and_last_call_to_liquid
|
||||
assert_template_result 'foobar', '{{ foo | first }}', 'foo' => [ThingWithToLiquid.new]
|
||||
assert_template_result 'foobar', '{{ foo | last }}', 'foo' => [ThingWithToLiquid.new]
|
||||
end
|
||||
|
||||
def test_date
|
||||
assert_equal 'May', @filters.date(Time.parse("2006-05-05 10:00:00"), "%B")
|
||||
assert_equal 'June', @filters.date(Time.parse("2006-06-05 10:00:00"), "%B")
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
require 'test_helper'
|
||||
|
||||
class BreakTagTest < Test::Unit::TestCase
|
||||
class BreakTagTest < Minitest::Test
|
||||
include Liquid
|
||||
|
||||
# tests that no weird errors are raised if break is called outside of a
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
require 'test_helper'
|
||||
|
||||
class ContinueTagTest < Test::Unit::TestCase
|
||||
class ContinueTagTest < Minitest::Test
|
||||
include Liquid
|
||||
|
||||
# tests that no weird errors are raised if continue is called outside of a
|
||||
|
||||
@@ -6,7 +6,7 @@ class ThingWithValue < Liquid::Drop
|
||||
end
|
||||
end
|
||||
|
||||
class ForTagTest < Test::Unit::TestCase
|
||||
class ForTagTest < Minitest::Test
|
||||
include Liquid
|
||||
|
||||
def test_for
|
||||
@@ -303,7 +303,7 @@ HERE
|
||||
end
|
||||
|
||||
def test_bad_variable_naming_in_for_loop
|
||||
assert_raise(Liquid::SyntaxError) do
|
||||
assert_raises(Liquid::SyntaxError) do
|
||||
Liquid::Template.parse('{% for a/b in x %}{% endfor %}')
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
require 'test_helper'
|
||||
|
||||
class IfElseTagTest < Test::Unit::TestCase
|
||||
class IfElseTagTest < Minitest::Test
|
||||
include Liquid
|
||||
|
||||
def test_if
|
||||
@@ -37,25 +37,19 @@ class IfElseTagTest < Test::Unit::TestCase
|
||||
end
|
||||
|
||||
def test_comparison_of_strings_containing_and_or_or
|
||||
assert_nothing_raised do
|
||||
awful_markup = "a == 'and' and b == 'or' and c == 'foo and bar' and d == 'bar or baz' and e == 'foo' and foo and bar"
|
||||
assigns = {'a' => 'and', 'b' => 'or', 'c' => 'foo and bar', 'd' => 'bar or baz', 'e' => 'foo', 'foo' => true, 'bar' => true}
|
||||
assert_template_result(' YES ',"{% if #{awful_markup} %} YES {% endif %}", assigns)
|
||||
end
|
||||
awful_markup = "a == 'and' and b == 'or' and c == 'foo and bar' and d == 'bar or baz' and e == 'foo' and foo and bar"
|
||||
assigns = {'a' => 'and', 'b' => 'or', 'c' => 'foo and bar', 'd' => 'bar or baz', 'e' => 'foo', 'foo' => true, 'bar' => true}
|
||||
assert_template_result(' YES ',"{% if #{awful_markup} %} YES {% endif %}", assigns)
|
||||
end
|
||||
|
||||
def test_comparison_of_expressions_starting_with_and_or_or
|
||||
assigns = {'order' => {'items_count' => 0}, 'android' => {'name' => 'Roy'}}
|
||||
assert_nothing_raised do
|
||||
assert_template_result( "YES",
|
||||
"{% if android.name == 'Roy' %}YES{% endif %}",
|
||||
assigns)
|
||||
end
|
||||
assert_nothing_raised do
|
||||
assert_template_result( "YES",
|
||||
"{% if order.items_count == 0 %}YES{% endif %}",
|
||||
assigns)
|
||||
end
|
||||
assert_template_result( "YES",
|
||||
"{% if android.name == 'Roy' %}YES{% endif %}",
|
||||
assigns)
|
||||
assert_template_result( "YES",
|
||||
"{% if order.items_count == 0 %}YES{% endif %}",
|
||||
assigns)
|
||||
end
|
||||
|
||||
def test_if_and
|
||||
@@ -135,31 +129,35 @@ class IfElseTagTest < Test::Unit::TestCase
|
||||
end
|
||||
|
||||
def test_syntax_error_no_variable
|
||||
assert_raise(SyntaxError){ assert_template_result('', '{% if jerry == 1 %}')}
|
||||
assert_raises(SyntaxError){ assert_template_result('', '{% if jerry == 1 %}')}
|
||||
end
|
||||
|
||||
def test_syntax_error_no_expression
|
||||
assert_raise(SyntaxError) { assert_template_result('', '{% if %}') }
|
||||
assert_raises(SyntaxError) { assert_template_result('', '{% if %}') }
|
||||
end
|
||||
|
||||
def test_if_with_custom_condition
|
||||
original_op = Condition.operators['contains']
|
||||
Condition.operators['contains'] = :[]
|
||||
|
||||
assert_template_result('yes', %({% if 'bob' contains 'o' %}yes{% endif %}))
|
||||
assert_template_result('no', %({% if 'bob' contains 'f' %}yes{% else %}no{% endif %}))
|
||||
ensure
|
||||
Condition.operators.delete 'contains'
|
||||
Condition.operators['contains'] = original_op
|
||||
end
|
||||
|
||||
def test_operators_are_ignored_unless_isolated
|
||||
original_op = Condition.operators['contains']
|
||||
Condition.operators['contains'] = :[]
|
||||
|
||||
assert_template_result('yes',
|
||||
%({% if 'gnomeslab-and-or-liquid' contains 'gnomeslab-and-or-liquid' %}yes{% endif %}))
|
||||
ensure
|
||||
Condition.operators['contains'] = original_op
|
||||
end
|
||||
|
||||
def test_operators_are_whitelisted
|
||||
assert_raise(SyntaxError) do
|
||||
assert_raises(SyntaxError) do
|
||||
assert_template_result('', %({% if 1 or throw or or 1 %}yes{% endif %}))
|
||||
end
|
||||
end
|
||||
|
||||
@@ -60,16 +60,12 @@ class CustomInclude < Liquid::Tag
|
||||
def parse(tokens)
|
||||
end
|
||||
|
||||
def blank?
|
||||
false
|
||||
end
|
||||
|
||||
def render(context)
|
||||
@template_name[1..-2]
|
||||
end
|
||||
end
|
||||
|
||||
class IncludeTagTest < Test::Unit::TestCase
|
||||
class IncludeTagTest < Minitest::Test
|
||||
include Liquid
|
||||
|
||||
def setup
|
||||
@@ -136,7 +132,7 @@ class IncludeTagTest < Test::Unit::TestCase
|
||||
|
||||
Liquid::Template.file_system = infinite_file_system.new
|
||||
|
||||
assert_raise(Liquid::StackLevelError) do
|
||||
assert_raises(Liquid::StackLevelError) do
|
||||
Template.parse("{% include 'loop' %}").render!
|
||||
end
|
||||
|
||||
@@ -205,4 +201,12 @@ class IncludeTagTest < Test::Unit::TestCase
|
||||
Liquid::Template.tags['include'] = original_tag
|
||||
end
|
||||
end
|
||||
|
||||
def test_does_not_add_error_in_strict_mode_for_missing_variable
|
||||
Liquid::Template.file_system = TestFileSystem.new
|
||||
|
||||
a = Liquid::Template.parse(' {% include "nested_template" %}')
|
||||
a.render!
|
||||
assert_empty a.errors
|
||||
end
|
||||
end # IncludeTagTest
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
require 'test_helper'
|
||||
|
||||
class IncrementTagTest < Test::Unit::TestCase
|
||||
class IncrementTagTest < Minitest::Test
|
||||
include Liquid
|
||||
|
||||
def test_inc
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
require 'test_helper'
|
||||
|
||||
class RawTagTest < Test::Unit::TestCase
|
||||
class RawTagTest < Minitest::Test
|
||||
include Liquid
|
||||
|
||||
def test_tag_in_raw
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
require 'test_helper'
|
||||
|
||||
class StandardTagTest < Test::Unit::TestCase
|
||||
class StandardTagTest < Minitest::Test
|
||||
include Liquid
|
||||
|
||||
def test_no_transform
|
||||
@@ -66,7 +66,7 @@ class StandardTagTest < Test::Unit::TestCase
|
||||
end
|
||||
|
||||
def test_capture_detects_bad_syntax
|
||||
assert_raise(SyntaxError) do
|
||||
assert_raises(SyntaxError) do
|
||||
assert_template_result('content foo content foo ',
|
||||
'{{ var2 }}{% capture %}{{ var }} foo {% endcapture %}{{ var2 }}{{ var2 }}',
|
||||
{'var' => 'content' })
|
||||
@@ -229,11 +229,11 @@ class StandardTagTest < Test::Unit::TestCase
|
||||
end
|
||||
|
||||
def test_case_detects_bad_syntax
|
||||
assert_raise(SyntaxError) do
|
||||
assert_raises(SyntaxError) do
|
||||
assert_template_result('', '{% case false %}{% when %}true{% endcase %}', {})
|
||||
end
|
||||
|
||||
assert_raise(SyntaxError) do
|
||||
assert_raises(SyntaxError) do
|
||||
assert_template_result('', '{% case false %}{% huh %}true{% endcase %}', {})
|
||||
end
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
require 'test_helper'
|
||||
|
||||
class StatementsTest < Test::Unit::TestCase
|
||||
class StatementsTest < Minitest::Test
|
||||
include Liquid
|
||||
|
||||
def test_true_eql_true
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
require 'test_helper'
|
||||
|
||||
class TableRowTest < Test::Unit::TestCase
|
||||
class TableRowTest < Minitest::Test
|
||||
include Liquid
|
||||
|
||||
class ArrayDrop < Liquid::Drop
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
require 'test_helper'
|
||||
|
||||
class UnlessElseTagTest < Test::Unit::TestCase
|
||||
class UnlessElseTagTest < Minitest::Test
|
||||
include Liquid
|
||||
|
||||
def test_unless
|
||||
|
||||
@@ -28,7 +28,7 @@ class ErroneousDrop < Liquid::Drop
|
||||
end
|
||||
end
|
||||
|
||||
class TemplateTest < Test::Unit::TestCase
|
||||
class TemplateTest < Minitest::Test
|
||||
include Liquid
|
||||
|
||||
def test_instance_assigns_persist_on_same_template_object_between_parses
|
||||
@@ -93,7 +93,7 @@ class TemplateTest < Test::Unit::TestCase
|
||||
assert t.resource_limits[:reached]
|
||||
t.resource_limits = { :render_length_limit => 10 }
|
||||
assert_equal "0123456789", t.render!()
|
||||
assert_not_nil t.resource_limits[:render_length_current]
|
||||
refute_nil t.resource_limits[:render_length_current]
|
||||
end
|
||||
|
||||
def test_resource_limits_render_score
|
||||
@@ -107,7 +107,7 @@ class TemplateTest < Test::Unit::TestCase
|
||||
assert t.resource_limits[:reached]
|
||||
t.resource_limits = { :render_score_limit => 200 }
|
||||
assert_equal (" foo " * 100), t.render!()
|
||||
assert_not_nil t.resource_limits[:render_score_current]
|
||||
refute_nil t.resource_limits[:render_score_current]
|
||||
end
|
||||
|
||||
def test_resource_limits_assign_score
|
||||
@@ -117,7 +117,7 @@ class TemplateTest < Test::Unit::TestCase
|
||||
assert t.resource_limits[:reached]
|
||||
t.resource_limits = { :assign_score_limit => 2 }
|
||||
assert_equal "", t.render!()
|
||||
assert_not_nil t.resource_limits[:assign_score_current]
|
||||
refute_nil t.resource_limits[:assign_score_current]
|
||||
end
|
||||
|
||||
def test_resource_limits_aborts_rendering_after_first_error
|
||||
@@ -153,4 +153,18 @@ class TemplateTest < Test::Unit::TestCase
|
||||
end
|
||||
assert_equal 'ruby error in drop', e.message
|
||||
end
|
||||
|
||||
def test_exception_handler_doesnt_reraise_if_it_returns_false
|
||||
exception = nil
|
||||
Template.parse("{{ 1 | divided_by: 0 }}").render({}, exception_handler: ->(e) { exception = e; false })
|
||||
assert exception.is_a?(ZeroDivisionError)
|
||||
end
|
||||
|
||||
def test_exception_handler_does_reraise_if_it_returns_true
|
||||
exception = nil
|
||||
assert_raises(ZeroDivisionError) do
|
||||
Template.parse("{{ 1 | divided_by: 0 }}").render({}, exception_handler: ->(e) { exception = e; true })
|
||||
end
|
||||
assert exception.is_a?(ZeroDivisionError)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
require 'test_helper'
|
||||
|
||||
class VariableTest < Test::Unit::TestCase
|
||||
class VariableTest < Minitest::Test
|
||||
include Liquid
|
||||
|
||||
def test_simple_variable
|
||||
@@ -9,6 +9,10 @@ class VariableTest < Test::Unit::TestCase
|
||||
assert_equal 'worked wonderfully', template.render!('test' => 'worked wonderfully')
|
||||
end
|
||||
|
||||
def test_variable_render_calls_to_liquid
|
||||
assert_template_result 'foobar', '{{ foo }}', 'foo' => ThingWithToLiquid.new
|
||||
end
|
||||
|
||||
def test_simple_with_whitespaces
|
||||
template = Template.parse(%| {{ test }} |)
|
||||
assert_equal ' worked ', template.render!('test' => 'worked')
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env ruby
|
||||
|
||||
require 'test/unit'
|
||||
require 'test/unit/assertions'
|
||||
require 'minitest/autorun'
|
||||
require 'spy/integration'
|
||||
|
||||
$:.unshift(File.join(File.expand_path(File.dirname(__FILE__)), '..', 'lib'))
|
||||
require 'liquid.rb'
|
||||
@@ -13,41 +13,62 @@ if env_mode = ENV['LIQUID_PARSER_MODE']
|
||||
end
|
||||
Liquid::Template.error_mode = mode
|
||||
|
||||
if Minitest.const_defined?('Test')
|
||||
# We're on Minitest 5+. Nothing to do here.
|
||||
else
|
||||
# Minitest 4 doesn't have Minitest::Test yet.
|
||||
Minitest::Test = MiniTest::Unit::TestCase
|
||||
end
|
||||
|
||||
module Test
|
||||
module Unit
|
||||
class TestCase
|
||||
def fixture(name)
|
||||
File.join(File.expand_path(File.dirname(__FILE__)), "fixtures", name)
|
||||
end
|
||||
module Minitest
|
||||
class Test
|
||||
def fixture(name)
|
||||
File.join(File.expand_path(File.dirname(__FILE__)), "fixtures", name)
|
||||
end
|
||||
end
|
||||
|
||||
module Assertions
|
||||
include Liquid
|
||||
|
||||
def assert_template_result(expected, template, assigns = {}, message = nil)
|
||||
assert_equal expected, Template.parse(template).render!(assigns)
|
||||
end
|
||||
|
||||
module Assertions
|
||||
include Liquid
|
||||
def assert_template_result_matches(expected, template, assigns = {}, message = nil)
|
||||
return assert_template_result(expected, template, assigns, message) unless expected.is_a? Regexp
|
||||
|
||||
def assert_template_result(expected, template, assigns = {}, message = nil)
|
||||
assert_equal expected, Template.parse(template).render!(assigns)
|
||||
assert_match expected, Template.parse(template).render!(assigns)
|
||||
end
|
||||
|
||||
def assert_match_syntax_error(match, template, registers = {})
|
||||
exception = assert_raises(Liquid::SyntaxError) {
|
||||
Template.parse(template).render(assigns)
|
||||
}
|
||||
assert_match match, exception.message
|
||||
end
|
||||
|
||||
def with_global_filter(*globals)
|
||||
original_filters = Array.new(Liquid::Strainer.class_variable_get(:@@filters))
|
||||
globals.each do |global|
|
||||
Liquid::Template.register_filter(global)
|
||||
end
|
||||
yield
|
||||
ensure
|
||||
Liquid::Strainer.class_variable_set(:@@filters, original_filters)
|
||||
end
|
||||
|
||||
def assert_template_result_matches(expected, template, assigns = {}, message = nil)
|
||||
return assert_template_result(expected, template, assigns, message) unless expected.is_a? Regexp
|
||||
def with_error_mode(mode)
|
||||
old_mode = Liquid::Template.error_mode
|
||||
Liquid::Template.error_mode = mode
|
||||
yield
|
||||
ensure
|
||||
Liquid::Template.error_mode = old_mode
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
assert_match expected, Template.parse(template).render!(assigns)
|
||||
end
|
||||
|
||||
def assert_match_syntax_error(match, template, registers = {})
|
||||
exception = assert_raise(Liquid::SyntaxError) {
|
||||
Template.parse(template).render(assigns)
|
||||
}
|
||||
assert_match match, exception.message
|
||||
end
|
||||
|
||||
def with_error_mode(mode)
|
||||
old_mode = Liquid::Template.error_mode
|
||||
Liquid::Template.error_mode = mode
|
||||
yield
|
||||
Liquid::Template.error_mode = old_mode
|
||||
end
|
||||
end # Assertions
|
||||
end # Unit
|
||||
end # Test
|
||||
class ThingWithToLiquid
|
||||
def to_liquid
|
||||
'foobar'
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
require 'test_helper'
|
||||
|
||||
class BlockUnitTest < Test::Unit::TestCase
|
||||
class BlockUnitTest < Minitest::Test
|
||||
include Liquid
|
||||
|
||||
def test_blankspace
|
||||
@@ -45,10 +45,7 @@ class BlockUnitTest < Test::Unit::TestCase
|
||||
|
||||
def test_with_custom_tag
|
||||
Liquid::Template.register_tag("testtag", Block)
|
||||
|
||||
assert_nothing_thrown do
|
||||
template = Liquid::Template.parse( "{% testtag %} {% endtesttag %}")
|
||||
end
|
||||
assert Liquid::Template.parse( "{% testtag %} {% endtesttag %}")
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
require 'test_helper'
|
||||
|
||||
class ConditionUnitTest < Test::Unit::TestCase
|
||||
class ConditionUnitTest < Minitest::Test
|
||||
include Liquid
|
||||
|
||||
def test_basic_condition
|
||||
@@ -49,6 +49,17 @@ class ConditionUnitTest < Test::Unit::TestCase
|
||||
assert_evalutes_false "'bob'", 'contains', "'---'"
|
||||
end
|
||||
|
||||
def test_invalid_comparation_operator
|
||||
assert_evaluates_argument_error "1", '~~', '0'
|
||||
end
|
||||
|
||||
def test_comparation_of_int_and_str
|
||||
assert_evaluates_argument_error "'1'", '>', '0'
|
||||
assert_evaluates_argument_error "'1'", '<', '0'
|
||||
assert_evaluates_argument_error "'1'", '>=', '0'
|
||||
assert_evaluates_argument_error "'1'", '<=', '0'
|
||||
end
|
||||
|
||||
def test_contains_works_on_arrays
|
||||
@context = Liquid::Context.new
|
||||
@context['array'] = [1,2,3,4,5]
|
||||
@@ -124,4 +135,11 @@ class ConditionUnitTest < Test::Unit::TestCase
|
||||
assert !Condition.new(left, op, right).evaluate(@context || Liquid::Context.new),
|
||||
"Evaluated true: #{left} #{op} #{right}"
|
||||
end
|
||||
|
||||
def assert_evaluates_argument_error(left, op, right)
|
||||
assert_raises(Liquid::ArgumentError) do
|
||||
Condition.new(left, op, right).evaluate(@context || Liquid::Context.new)
|
||||
end
|
||||
end
|
||||
|
||||
end # ConditionTest
|
||||
|
||||
@@ -63,13 +63,17 @@ class ArrayLike
|
||||
end
|
||||
end
|
||||
|
||||
class ContextUnitTest < Test::Unit::TestCase
|
||||
class ContextUnitTest < Minitest::Test
|
||||
include Liquid
|
||||
|
||||
def setup
|
||||
@context = Liquid::Context.new
|
||||
end
|
||||
|
||||
def teardown
|
||||
Spy.teardown
|
||||
end
|
||||
|
||||
def test_variables
|
||||
@context['string'] = 'string'
|
||||
assert_equal 'string', @context['string']
|
||||
@@ -103,16 +107,14 @@ class ContextUnitTest < Test::Unit::TestCase
|
||||
end
|
||||
|
||||
def test_scoping
|
||||
assert_nothing_raised do
|
||||
@context.push
|
||||
@context.push
|
||||
@context.pop
|
||||
|
||||
assert_raises(Liquid::ContextError) do
|
||||
@context.pop
|
||||
end
|
||||
|
||||
assert_raise(Liquid::ContextError) do
|
||||
@context.pop
|
||||
end
|
||||
|
||||
assert_raise(Liquid::ContextError) do
|
||||
assert_raises(Liquid::ContextError) do
|
||||
@context.push
|
||||
@context.pop
|
||||
@context.pop
|
||||
@@ -458,21 +460,33 @@ class ContextUnitTest < Test::Unit::TestCase
|
||||
assert_equal @context, @context['category'].context
|
||||
end
|
||||
|
||||
def test_strict_variables_not_found
|
||||
with_error_mode(:strict) do
|
||||
@context['does_not_exist']
|
||||
assert(@context.errors.length == 1)
|
||||
assert_equal(@context.errors[0], 'Variable {{does_not_exist}} not found')
|
||||
end
|
||||
def test_use_empty_instead_of_any_in_interrupt_handling_to_avoid_lots_of_unnecessary_object_allocations
|
||||
mock_any = Spy.on_instance_method(Array, :any?)
|
||||
mock_empty = Spy.on_instance_method(Array, :empty?)
|
||||
mock_has_interrupt = Spy.on(@context, :has_interrupt?).and_call_through
|
||||
|
||||
@context.has_interrupt?
|
||||
|
||||
refute mock_any.has_been_called?
|
||||
assert mock_empty.has_been_called?
|
||||
end
|
||||
|
||||
def test_variable_lookup_caches_markup
|
||||
mock_scan = Spy.on_instance_method(String, :scan).and_return(["string"])
|
||||
|
||||
@context['string'] = 'string'
|
||||
@context['string']
|
||||
@context['string']
|
||||
|
||||
assert_equal 1, mock_scan.calls.size
|
||||
end
|
||||
|
||||
def test_strict_nested_variables_not_found
|
||||
with_error_mode(:strict) do
|
||||
@context['hash'] = {'this' => 'exists'}
|
||||
@context['hash.does_not_exist']
|
||||
assert(@context.errors.length == 1)
|
||||
assert_equal(@context.errors[0], 'Variable {{hash.does_not_exist}} not found')
|
||||
end
|
||||
def test_context_initialization_with_a_proc_in_environment
|
||||
contx = Context.new([:test => lambda { |c| c['poutine']}], {:test => :foo})
|
||||
|
||||
assert contx
|
||||
assert_nil contx['poutine']
|
||||
end
|
||||
|
||||
|
||||
end # ContextTest
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
require 'test_helper'
|
||||
|
||||
class FileSystemUnitTest < Test::Unit::TestCase
|
||||
class FileSystemUnitTest < Minitest::Test
|
||||
include Liquid
|
||||
|
||||
def test_default
|
||||
assert_raise(FileSystemError) do
|
||||
assert_raises(FileSystemError) do
|
||||
BlankFileSystem.new.read_template_file("dummy", {'dummy'=>'smarty'})
|
||||
end
|
||||
end
|
||||
@@ -14,15 +14,15 @@ class FileSystemUnitTest < Test::Unit::TestCase
|
||||
assert_equal "/some/path/_mypartial.liquid" , file_system.full_path("mypartial")
|
||||
assert_equal "/some/path/dir/_mypartial.liquid", file_system.full_path("dir/mypartial")
|
||||
|
||||
assert_raise(FileSystemError) do
|
||||
assert_raises(FileSystemError) do
|
||||
file_system.full_path("../dir/mypartial")
|
||||
end
|
||||
|
||||
assert_raise(FileSystemError) do
|
||||
assert_raises(FileSystemError) do
|
||||
file_system.full_path("/dir/../../dir/mypartial")
|
||||
end
|
||||
|
||||
assert_raise(FileSystemError) do
|
||||
assert_raises(FileSystemError) do
|
||||
file_system.full_path("/etc/passwd")
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
require 'test_helper'
|
||||
|
||||
class I18nUnitTest < Test::Unit::TestCase
|
||||
class I18nUnitTest < Minitest::Test
|
||||
include Liquid
|
||||
|
||||
def setup
|
||||
@@ -20,13 +20,13 @@ class I18nUnitTest < Test::Unit::TestCase
|
||||
end
|
||||
|
||||
# def test_raises_translation_error_on_undefined_interpolation_key
|
||||
# assert_raise I18n::TranslationError do
|
||||
# assert_raises I18n::TranslationError do
|
||||
# @i18n.translate("whatever", :oopstypos => "yes")
|
||||
# end
|
||||
# end
|
||||
|
||||
def test_raises_unknown_translation
|
||||
assert_raise I18n::TranslationError do
|
||||
assert_raises I18n::TranslationError do
|
||||
@i18n.translate("doesnt_exist")
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
require 'test_helper'
|
||||
|
||||
class LexerUnitTest < Test::Unit::TestCase
|
||||
class LexerUnitTest < Minitest::Test
|
||||
include Liquid
|
||||
|
||||
def test_strings
|
||||
|
||||
@@ -36,7 +36,7 @@ class TestClassC::LiquidDropClass
|
||||
end
|
||||
end
|
||||
|
||||
class ModuleExUnitTest < Test::Unit::TestCase
|
||||
class ModuleExUnitTest < Minitest::Test
|
||||
include Liquid
|
||||
|
||||
def setup
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
require 'test_helper'
|
||||
|
||||
class ParserUnitTest < Test::Unit::TestCase
|
||||
class ParserUnitTest < Minitest::Test
|
||||
include Liquid
|
||||
|
||||
def test_consume
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
require 'test_helper'
|
||||
|
||||
class RegexpUnitTest < Test::Unit::TestCase
|
||||
class RegexpUnitTest < Minitest::Test
|
||||
include Liquid
|
||||
|
||||
def test_empty
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
require 'test_helper'
|
||||
|
||||
class StrainerUnitTest < Test::Unit::TestCase
|
||||
class StrainerUnitTest < Minitest::Test
|
||||
include Liquid
|
||||
|
||||
module AccessScopeFilters
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
require 'test_helper'
|
||||
|
||||
class TagUnitTest < Test::Unit::TestCase
|
||||
class TagUnitTest < Minitest::Test
|
||||
include Liquid
|
||||
|
||||
def test_tag
|
||||
@@ -8,4 +8,9 @@ class TagUnitTest < Test::Unit::TestCase
|
||||
assert_equal 'liquid::tag', tag.name
|
||||
assert_equal '', tag.render(Context.new)
|
||||
end
|
||||
|
||||
def test_return_raw_text_of_tag
|
||||
tag = Tag.parse("long_tag", "param1, param2, param3", [], {})
|
||||
assert_equal("long_tag param1, param2, param3", tag.raw)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
require 'test_helper'
|
||||
|
||||
class CaseTagUnitTest < Test::Unit::TestCase
|
||||
class CaseTagUnitTest < Minitest::Test
|
||||
include Liquid
|
||||
|
||||
def test_case_nodelist
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
require 'test_helper'
|
||||
|
||||
class ForTagUnitTest < Test::Unit::TestCase
|
||||
class ForTagUnitTest < Minitest::Test
|
||||
def test_for_nodelist
|
||||
template = Liquid::Template.parse('{% for item in items %}FOR{% endfor %}')
|
||||
assert_equal ['FOR'], template.root.nodelist[0].nodelist
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
require 'test_helper'
|
||||
|
||||
class IfTagUnitTest < Test::Unit::TestCase
|
||||
class IfTagUnitTest < Minitest::Test
|
||||
def test_if_nodelist
|
||||
template = Liquid::Template.parse('{% if true %}IF{% else %}ELSE{% endif %}')
|
||||
assert_equal ['IF', 'ELSE'], template.root.nodelist[0].nodelist
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
require 'test_helper'
|
||||
|
||||
class TemplateUnitTest < Test::Unit::TestCase
|
||||
class TemplateUnitTest < Minitest::Test
|
||||
include Liquid
|
||||
|
||||
def test_sets_default_localization_in_document
|
||||
@@ -16,4 +16,54 @@ class TemplateUnitTest < Test::Unit::TestCase
|
||||
assert_instance_of I18n, t.root.options[:locale]
|
||||
assert_equal fixture("en_locale.yml"), t.root.options[:locale].path
|
||||
end
|
||||
|
||||
def test_with_cache_classes_tags_returns_the_same_class
|
||||
original_cache_setting = Liquid.cache_classes
|
||||
Liquid.cache_classes = true
|
||||
|
||||
original_klass = Class.new
|
||||
Object.send(:const_set, :CustomTag, original_klass)
|
||||
Template.register_tag('custom', CustomTag)
|
||||
|
||||
Object.send(:remove_const, :CustomTag)
|
||||
|
||||
new_klass = Class.new
|
||||
Object.send(:const_set, :CustomTag, new_klass)
|
||||
|
||||
assert Template.tags['custom'].equal?(original_klass)
|
||||
ensure
|
||||
Object.send(:remove_const, :CustomTag)
|
||||
Template.tags.delete('custom')
|
||||
Liquid.cache_classes = original_cache_setting
|
||||
end
|
||||
|
||||
def test_without_cache_classes_tags_reloads_the_class
|
||||
original_cache_setting = Liquid.cache_classes
|
||||
Liquid.cache_classes = false
|
||||
|
||||
original_klass = Class.new
|
||||
Object.send(:const_set, :CustomTag, original_klass)
|
||||
Template.register_tag('custom', CustomTag)
|
||||
|
||||
Object.send(:remove_const, :CustomTag)
|
||||
|
||||
new_klass = Class.new
|
||||
Object.send(:const_set, :CustomTag, new_klass)
|
||||
|
||||
assert Template.tags['custom'].equal?(new_klass)
|
||||
ensure
|
||||
Object.send(:remove_const, :CustomTag)
|
||||
Template.tags.delete('custom')
|
||||
Liquid.cache_classes = original_cache_setting
|
||||
end
|
||||
|
||||
class FakeTag; end
|
||||
|
||||
def test_tags_delete
|
||||
Template.register_tag('fake', FakeTag)
|
||||
assert_equal FakeTag, Template.tags['fake']
|
||||
|
||||
Template.tags.delete('fake')
|
||||
assert_nil Template.tags['fake']
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
require 'test_helper'
|
||||
|
||||
class TokenizerTest < Test::Unit::TestCase
|
||||
class TokenizerTest < Minitest::Test
|
||||
def test_tokenize_strings
|
||||
assert_equal [' '], tokenize(' ')
|
||||
assert_equal ['hello world'], tokenize('hello world')
|
||||
@@ -21,6 +21,15 @@ class TokenizerTest < Test::Unit::TestCase
|
||||
assert_equal [' ', '{% comment %}', ' ', '{% endcomment %}', ' '], tokenize(" {% comment %} {% endcomment %} ")
|
||||
end
|
||||
|
||||
def test_calculate_line_numbers_per_token_with_profiling
|
||||
template = Liquid::Template.parse("", :profile => true)
|
||||
|
||||
assert_equal [1], template.send(:tokenize, "{{funk}}").map(&:line_number)
|
||||
assert_equal [1, 1, 1], template.send(:tokenize, " {{funk}} ").map(&:line_number)
|
||||
assert_equal [1, 2, 2], template.send(:tokenize, "\n{{funk}}\n").map(&:line_number)
|
||||
assert_equal [1, 1, 3], template.send(:tokenize, " {{\n funk \n}} ").map(&:line_number)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def tokenize(source)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
require 'test_helper'
|
||||
|
||||
class VariableUnitTest < Test::Unit::TestCase
|
||||
class VariableUnitTest < Minitest::Test
|
||||
include Liquid
|
||||
|
||||
def test_variable
|
||||
@@ -133,4 +133,9 @@ class VariableUnitTest < Test::Unit::TestCase
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def test_output_raw_source_of_variable
|
||||
var = Variable.new(%! name_of_variable | upcase !)
|
||||
assert_equal " name_of_variable | upcase ", var.raw
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user