mirror of
https://github.com/kemko/liquid.git
synced 2026-01-01 15:55:40 +03:00
Compare commits
21 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ebbfb54de4 | ||
|
|
8f84ddb5ce | ||
|
|
09de50dcb1 | ||
|
|
49f2af4209 | ||
|
|
5d7c00a202 | ||
|
|
9bd05110dc | ||
|
|
9dd24824f9 | ||
|
|
291b58bc91 | ||
|
|
8c193e203f | ||
|
|
47dbcd93a5 | ||
|
|
000d0c911b | ||
|
|
95b340a7cf | ||
|
|
36a8696c07 | ||
|
|
cbc163ba1c | ||
|
|
9faf8f9a56 | ||
|
|
d6db28c854 | ||
|
|
475ea51f1f | ||
|
|
9c33e9601b | ||
|
|
b242a7273a | ||
|
|
4b1835e3c0 | ||
|
|
2fe3a21a5d |
@@ -2,6 +2,8 @@ rvm:
|
||||
- 1.9
|
||||
- 2.0
|
||||
- 2.1
|
||||
- 2.2
|
||||
- ruby-head
|
||||
- jruby-19mode
|
||||
- jruby-head
|
||||
- rbx-2
|
||||
@@ -9,6 +11,7 @@ matrix:
|
||||
allow_failures:
|
||||
- rvm: rbx-2
|
||||
- rvm: jruby-head
|
||||
- rvm: ruby-head
|
||||
|
||||
script: "rake test"
|
||||
|
||||
|
||||
17
History.md
17
History.md
@@ -1,9 +1,20 @@
|
||||
# Liquid Version History
|
||||
|
||||
## 3.0.0 / not yet released / branch "master"
|
||||
## 3.0.3 / 2015-05-28 / branch "3-0-stable"
|
||||
|
||||
* Fix condition parse order in strict mode (#569) [Justin Li, pushrax]
|
||||
|
||||
## 3.0.2 / 2015-04-24
|
||||
|
||||
* Expose VariableLookup private members (#551) [Justin Li, pushrax]
|
||||
* Documentation fixes
|
||||
|
||||
## 3.0.1 / 2015-01-23
|
||||
|
||||
* Remove duplicate `index0` key in TableRow tag (#502) [Alfred Xing]
|
||||
|
||||
## 3.0.0 / 2014-11-12
|
||||
|
||||
* ...
|
||||
* Block parsing moved to BlockBody class, see #458 [Dylan Thacker-Smith, dylanahsmith]
|
||||
* Removed Block#end_tag. Instead, override parse with `super` followed by your code. See #446 [Dylan Thacker-Smith, dylanahsmith]
|
||||
* Fixed condition with wrong data types, see #423 [Bogdan Gusiev]
|
||||
* Add url_encode to standard filters, see #421 [Derrick Reimer, djreimer]
|
||||
|
||||
@@ -57,7 +57,6 @@ require 'liquid/context'
|
||||
require 'liquid/parser_switching'
|
||||
require 'liquid/tag'
|
||||
require 'liquid/block'
|
||||
require 'liquid/block_body'
|
||||
require 'liquid/document'
|
||||
require 'liquid/variable'
|
||||
require 'liquid/variable_lookup'
|
||||
|
||||
@@ -1,26 +1,65 @@
|
||||
module Liquid
|
||||
class Block < Tag
|
||||
def initialize(tag_name, markup, options)
|
||||
super
|
||||
@blank = true
|
||||
end
|
||||
|
||||
def parse(tokens)
|
||||
@body = BlockBody.new
|
||||
while more = parse_body(@body, tokens)
|
||||
end
|
||||
end
|
||||
|
||||
def render(context)
|
||||
@body.render(context)
|
||||
end
|
||||
FullToken = /\A#{TagStart}\s*(\w+)\s*(.*)?#{TagEnd}\z/om
|
||||
ContentOfVariable = /\A#{VariableStart}(.*)#{VariableEnd}\z/om
|
||||
TAGSTART = "{%".freeze
|
||||
VARSTART = "{{".freeze
|
||||
|
||||
def blank?
|
||||
@blank
|
||||
end
|
||||
|
||||
def nodelist
|
||||
@body.nodelist
|
||||
def parse(tokens)
|
||||
@blank = true
|
||||
@nodelist ||= []
|
||||
@nodelist.clear
|
||||
|
||||
while token = tokens.shift
|
||||
begin
|
||||
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
|
||||
return if block_delimiter == $1
|
||||
|
||||
# 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
|
||||
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
|
||||
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
|
||||
@blank = false
|
||||
else
|
||||
@nodelist << token
|
||||
@blank &&= (token =~ /\A\s*\z/)
|
||||
end
|
||||
end
|
||||
rescue SyntaxError => e
|
||||
e.set_line_number_from_token(token)
|
||||
raise
|
||||
end
|
||||
end
|
||||
|
||||
# Make sure that it's ok to end parsing in the current block.
|
||||
# Effectively this method will throw an exception unless the current block is
|
||||
# of type Document
|
||||
assert_missing_delimitation!
|
||||
end
|
||||
|
||||
# warnings of this block and all sub-tags
|
||||
@@ -57,23 +96,65 @@ module Liquid
|
||||
@block_delimiter ||= "end#{block_name}"
|
||||
end
|
||||
|
||||
def create_variable(token)
|
||||
token.scan(ContentOfVariable) do |content|
|
||||
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
|
||||
|
||||
def render(context)
|
||||
render_all(@nodelist, context)
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def parse_body(body, tokens)
|
||||
body.parse(tokens, options) do |end_tag_name, end_tag_params|
|
||||
@blank &&= body.blank?
|
||||
def assert_missing_delimitation!
|
||||
raise SyntaxError.new(options[:locale].t("errors.syntax.tag_never_closed".freeze, :block_name => block_name))
|
||||
end
|
||||
|
||||
return false if end_tag_name == block_delimiter
|
||||
unless end_tag_name
|
||||
raise SyntaxError.new(@options[:locale].t("errors.syntax.tag_never_closed".freeze, :block_name => block_name))
|
||||
def render_all(list, context)
|
||||
output = []
|
||||
context.resource_limits[:render_length_current] = 0
|
||||
context.resource_limits[:render_score_current] += list.length
|
||||
|
||||
list.each do |token|
|
||||
# Break out if we have any unhanded interrupts.
|
||||
break if context.has_interrupt?
|
||||
|
||||
begin
|
||||
# If we get an Interrupt that means the block must stop processing. An
|
||||
# Interrupt is any command that stops block execution such as {% break %}
|
||||
# or {% continue %}
|
||||
if token.is_a? Continue or token.is_a? Break
|
||||
context.push_interrupt(token.interrupt)
|
||||
break
|
||||
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, token))
|
||||
end
|
||||
|
||||
# this tag is not registered with the system
|
||||
# pass it to the current block for special handling or error reporting
|
||||
unknown_tag(end_tag_name, end_tag_params, tokens)
|
||||
end
|
||||
|
||||
true
|
||||
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
|
||||
|
||||
@@ -34,7 +34,7 @@ module Liquid
|
||||
return yield tag_name, markup
|
||||
end
|
||||
else
|
||||
raise_missing_tag_terminator(token, options)
|
||||
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, options)
|
||||
@@ -117,14 +117,6 @@ module Liquid
|
||||
markup = token.is_a?(Token) ? token.child(content.first) : content.first
|
||||
return Variable.new(markup, options)
|
||||
end
|
||||
raise_missing_variable_terminator(token, options)
|
||||
end
|
||||
|
||||
def raise_missing_tag_terminator(token, options)
|
||||
raise SyntaxError.new(options[:locale].t("errors.syntax.tag_termination".freeze, :token => token, :tag_end => TagEnd.inspect))
|
||||
end
|
||||
|
||||
def raise_missing_variable_terminator(token, options)
|
||||
raise SyntaxError.new(options[:locale].t("errors.syntax.variable_termination".freeze, :token => token, :tag_end => VariableEnd.inspect))
|
||||
end
|
||||
end
|
||||
|
||||
@@ -3,7 +3,7 @@ module Liquid
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# c = Condition.new(1, '==', 1)
|
||||
# c = Condition.new('1', '==', '1')
|
||||
# c.evaluate #=> true
|
||||
#
|
||||
class Condition #:nodoc:
|
||||
@@ -96,10 +96,10 @@ module Liquid
|
||||
# If the operator is empty this means that the decision statement is just
|
||||
# a single variable. We can just poll this variable from the context and
|
||||
# return this as the result.
|
||||
return context.evaluate(left) if op == nil
|
||||
return context[left] if op == nil
|
||||
|
||||
left = context.evaluate(left)
|
||||
right = context.evaluate(right)
|
||||
left = context[left]
|
||||
right = context[right]
|
||||
|
||||
operation = self.class.operators[op] || raise(Liquid::ArgumentError.new("Unknown operator #{op}"))
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ module Liquid
|
||||
@resource_limits = resource_limits || Template.default_resource_limits.dup
|
||||
@resource_limits[:render_score_current] = 0
|
||||
@resource_limits[:assign_score_current] = 0
|
||||
@parsed_expression = Hash.new{ |cache, markup| cache[markup] = Expression.parse(markup) }
|
||||
squash_instance_assigns_with_environments
|
||||
|
||||
@this_stack_used = false
|
||||
@@ -60,21 +61,8 @@ module Liquid
|
||||
# for that
|
||||
def add_filters(filters)
|
||||
filters = [filters].flatten.compact
|
||||
filters.each do |f|
|
||||
raise ArgumentError, "Expected module but got: #{f.class}" unless f.is_a?(Module)
|
||||
Strainer.add_known_filter(f)
|
||||
end
|
||||
|
||||
# If strainer is already setup then there's no choice but to use a runtime
|
||||
# extend call. If strainer is not yet created, we can utilize strainers
|
||||
# cached class based API, which avoids busting the method cache.
|
||||
if @strainer
|
||||
filters.each do |f|
|
||||
strainer.extend(f)
|
||||
end
|
||||
else
|
||||
@filters.concat filters
|
||||
end
|
||||
@filters += filters
|
||||
@strainer = nil
|
||||
end
|
||||
|
||||
# are there any not handled interrupts?
|
||||
@@ -169,7 +157,7 @@ module Liquid
|
||||
# Example:
|
||||
# products == empty #=> products.empty?
|
||||
def [](expression)
|
||||
evaluate(Expression.parse(expression))
|
||||
evaluate(@parsed_expression[expression])
|
||||
end
|
||||
|
||||
def has_key?(key)
|
||||
|
||||
@@ -1,24 +1,17 @@
|
||||
module Liquid
|
||||
class Document < BlockBody
|
||||
def self.parse(tokens, options)
|
||||
doc = new
|
||||
doc.parse(tokens, options)
|
||||
doc
|
||||
class Document < Block
|
||||
def self.parse(tokens, options={})
|
||||
# we don't need markup to open this block
|
||||
super(nil, nil, tokens, options)
|
||||
end
|
||||
|
||||
def parse(tokens, options)
|
||||
super do |end_tag_name, end_tag_params|
|
||||
unknown_tag(end_tag_name, options) if end_tag_name
|
||||
end
|
||||
# There isn't a real delimiter
|
||||
def block_delimiter
|
||||
[]
|
||||
end
|
||||
|
||||
def unknown_tag(tag, options)
|
||||
case tag
|
||||
when 'else'.freeze, 'end'.freeze
|
||||
raise SyntaxError.new(options[:locale].t("errors.syntax.unexpected_outer_tag".freeze, :tag => tag))
|
||||
else
|
||||
raise SyntaxError.new(options[:locale].t("errors.syntax.unknown_tag".freeze, :tag => tag))
|
||||
end
|
||||
# Document blocks don't need to be terminated since they are not actually opened
|
||||
def assert_missing_delimitation!
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -9,11 +9,9 @@ module Liquid
|
||||
'['.freeze => :open_square,
|
||||
']'.freeze => :close_square,
|
||||
'('.freeze => :open_round,
|
||||
')'.freeze => :close_round,
|
||||
'?'.freeze => :question,
|
||||
'-'.freeze => :dash
|
||||
')'.freeze => :close_round
|
||||
}
|
||||
IDENTIFIER = /[a-zA-Z_][\w-]*\??/
|
||||
IDENTIFIER = /[\w\-?!]+/
|
||||
SINGLE_STRING_LITERAL = /'[^\']*'/
|
||||
DOUBLE_STRING_LITERAL = /"[^\"]*"/
|
||||
NUMBER_LITERAL = /-?\d+(\.\d+)?/
|
||||
|
||||
@@ -14,8 +14,7 @@
|
||||
include: "Error in tag 'include' - Valid syntax: include '[template]' (with|for) [object|collection]"
|
||||
unknown_tag: "Unknown tag '%{tag}'"
|
||||
invalid_delimiter: "'end' is not a valid delimiter for %{block_name} tags. use %{block_delimiter}"
|
||||
unexpected_else: "%{block_name} tag does not expect 'else' tag"
|
||||
unexpected_outer_tag: "Unexpected outer '%{tag}' tag"
|
||||
unexpected_else: "%{block_name} tag does not expect else tag"
|
||||
tag_termination: "Tag '%{token}' was not properly terminated with regexp: %{tag_end}"
|
||||
variable_termination: "Variable '%{token}' was not properly terminated with regexp: %{tag_end}"
|
||||
tag_never_closed: "'%{block_name}' tag was never closed"
|
||||
|
||||
@@ -75,7 +75,7 @@ module Liquid
|
||||
|
||||
def variable_signature
|
||||
str = consume(:id)
|
||||
if look(:open_square)
|
||||
while look(:open_square)
|
||||
str << consume
|
||||
str << expression
|
||||
str << consume(:close_square)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
module Liquid
|
||||
class BlockBody
|
||||
class Block < Tag
|
||||
def render_token_with_profiling(token, context)
|
||||
Profiler.profile_token_render(token) do
|
||||
render_token_without_profiling(token, context)
|
||||
@@ -12,7 +12,7 @@ module Liquid
|
||||
|
||||
class Include < Tag
|
||||
def render_with_profiling(context)
|
||||
Profiler.profile_children(context.evaluate(@template_name).to_s) do
|
||||
Profiler.profile_children(@template_name) do
|
||||
render_without_profiling(context)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -8,12 +8,13 @@ module Liquid
|
||||
# The Strainer only allows method calls defined in filters given to it via Strainer.global_filter,
|
||||
# Context#add_filters or Template.register_filter
|
||||
class Strainer #:nodoc:
|
||||
@@filters = []
|
||||
@@known_filters = Set.new
|
||||
@@known_methods = Set.new
|
||||
@@global_strainer = Class.new(Strainer) do
|
||||
@filter_methods = Set.new
|
||||
end
|
||||
@@strainer_class_cache = Hash.new do |hash, filters|
|
||||
hash[filters] = Class.new(Strainer) do
|
||||
filters.each { |f| include f }
|
||||
hash[filters] = Class.new(@@global_strainer) do
|
||||
@filter_methods = @@global_strainer.filter_methods.dup
|
||||
filters.each { |f| add_filter(f) }
|
||||
end
|
||||
end
|
||||
|
||||
@@ -21,33 +22,32 @@ module Liquid
|
||||
@context = context
|
||||
end
|
||||
|
||||
def self.global_filter(filter)
|
||||
raise ArgumentError, "Passed filter is not a module" unless filter.is_a?(Module)
|
||||
add_known_filter(filter)
|
||||
@@filters << filter unless @@filters.include?(filter)
|
||||
def self.filter_methods
|
||||
@filter_methods
|
||||
end
|
||||
|
||||
def self.add_known_filter(filter)
|
||||
unless @@known_filters.include?(filter)
|
||||
@@method_blacklist ||= Set.new(Strainer.instance_methods.map(&:to_s))
|
||||
new_methods = filter.instance_methods.map(&:to_s)
|
||||
new_methods.reject!{ |m| @@method_blacklist.include?(m) }
|
||||
@@known_methods.merge(new_methods)
|
||||
@@known_filters.add(filter)
|
||||
def self.add_filter(filter)
|
||||
raise ArgumentError, "Expected module but got: #{f.class}" unless filter.is_a?(Module)
|
||||
unless self.class.include?(filter)
|
||||
self.send(:include, filter)
|
||||
@filter_methods.merge(filter.public_instance_methods.map(&:to_s))
|
||||
end
|
||||
end
|
||||
|
||||
def self.strainer_class_cache
|
||||
@@strainer_class_cache
|
||||
def self.global_filter(filter)
|
||||
@@global_strainer.add_filter(filter)
|
||||
end
|
||||
|
||||
def self.invokable?(method)
|
||||
@filter_methods.include?(method.to_s)
|
||||
end
|
||||
|
||||
def self.create(context, filters = [])
|
||||
filters = @@filters + filters
|
||||
strainer_class_cache[filters].new(context)
|
||||
@@strainer_class_cache[filters].new(context)
|
||||
end
|
||||
|
||||
def invoke(method, *args)
|
||||
if invokable?(method)
|
||||
if self.class.invokable?(method)
|
||||
send(method, *args)
|
||||
else
|
||||
args.first
|
||||
@@ -55,9 +55,5 @@ module Liquid
|
||||
rescue ::ArgumentError => e
|
||||
raise Liquid::ArgumentError.new(e.message)
|
||||
end
|
||||
|
||||
def invokable?(method)
|
||||
@@known_methods.include?(method.to_s) && respond_to?(method)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -11,7 +11,7 @@ module Liquid
|
||||
# in a sidebar or footer.
|
||||
#
|
||||
class Capture < Block
|
||||
Syntax = /(\w+)/
|
||||
Syntax = /(#{VariableSignature}+)/o
|
||||
|
||||
def initialize(tag_name, markup, options)
|
||||
super
|
||||
|
||||
@@ -8,24 +8,18 @@ module Liquid
|
||||
@blocks = []
|
||||
|
||||
if markup =~ Syntax
|
||||
@left = Expression.parse($1)
|
||||
@left = $1
|
||||
else
|
||||
raise SyntaxError.new(options[:locale].t("errors.syntax.case".freeze))
|
||||
end
|
||||
end
|
||||
|
||||
def parse(tokens)
|
||||
body = BlockBody.new
|
||||
while more = parse_body(body, tokens)
|
||||
body = @blocks.last.attachment
|
||||
end
|
||||
end
|
||||
|
||||
def nodelist
|
||||
@blocks.map(&:attachment)
|
||||
@blocks.flat_map(&:attachment)
|
||||
end
|
||||
|
||||
def unknown_tag(tag, markup, tokens)
|
||||
@nodelist = []
|
||||
case tag
|
||||
when 'when'.freeze
|
||||
record_when_condition(markup)
|
||||
@@ -43,10 +37,10 @@ module Liquid
|
||||
output = ''
|
||||
@blocks.each do |block|
|
||||
if block.else?
|
||||
return block.attachment.render(context) if execute_else_block
|
||||
return render_all(block.attachment, context) if execute_else_block
|
||||
elsif block.evaluate(context)
|
||||
execute_else_block = false
|
||||
output << block.attachment.render(context)
|
||||
output << render_all(block.attachment, context)
|
||||
end
|
||||
end
|
||||
output
|
||||
@@ -56,18 +50,17 @@ module Liquid
|
||||
private
|
||||
|
||||
def record_when_condition(markup)
|
||||
body = BlockBody.new
|
||||
|
||||
while markup
|
||||
# Create a new nodelist and assign it to the new block
|
||||
if not markup =~ WhenSyntax
|
||||
raise SyntaxError.new(options[:locale].t("errors.syntax.case_invalid_when".freeze))
|
||||
end
|
||||
|
||||
markup = $2
|
||||
|
||||
block = Condition.new(@left, '=='.freeze, Expression.parse($1))
|
||||
block.attach(body)
|
||||
@blocks << block
|
||||
block = Condition.new(@left, '=='.freeze, $1)
|
||||
block.attach(@nodelist)
|
||||
@blocks.push(block)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -77,7 +70,7 @@ module Liquid
|
||||
end
|
||||
|
||||
block = ElseCondition.new
|
||||
block.attach(BlockBody.new)
|
||||
block.attach(@nodelist)
|
||||
@blocks << block
|
||||
end
|
||||
end
|
||||
|
||||
@@ -20,10 +20,10 @@ module Liquid
|
||||
case markup
|
||||
when NamedSyntax
|
||||
@variables = variables_from_string($2)
|
||||
@name = Expression.parse($1)
|
||||
@name = $1
|
||||
when SimpleSyntax
|
||||
@variables = variables_from_string(markup)
|
||||
@name = @variables.to_s
|
||||
@name = "'#{@variables.to_s}'"
|
||||
else
|
||||
raise SyntaxError.new(options[:locale].t("errors.syntax.cycle".freeze))
|
||||
end
|
||||
@@ -33,9 +33,9 @@ module Liquid
|
||||
context.registers[:cycle] ||= Hash.new(0)
|
||||
|
||||
context.stack do
|
||||
key = context.evaluate(@name)
|
||||
key = context[@name]
|
||||
iteration = context.registers[:cycle][key]
|
||||
result = context.evaluate(@variables[iteration])
|
||||
result = context[@variables[iteration]]
|
||||
iteration += 1
|
||||
iteration = 0 if iteration >= @variables.size
|
||||
context.registers[:cycle][key] = iteration
|
||||
@@ -48,7 +48,7 @@ module Liquid
|
||||
def variables_from_string(markup)
|
||||
markup.split(',').collect do |var|
|
||||
var =~ /\s*(#{QuotedFragment})\s*/o
|
||||
$1 ? Expression.parse($1) : nil
|
||||
$1 ? $1 : nil
|
||||
end.compact
|
||||
end
|
||||
end
|
||||
|
||||
@@ -49,40 +49,38 @@ module Liquid
|
||||
def initialize(tag_name, markup, options)
|
||||
super
|
||||
parse_with_selected_parser(markup)
|
||||
@for_block = BlockBody.new
|
||||
end
|
||||
|
||||
def parse(tokens)
|
||||
if more = parse_body(@for_block, tokens)
|
||||
parse_body(@else_block, tokens)
|
||||
end
|
||||
@nodelist = @for_block = []
|
||||
end
|
||||
|
||||
def nodelist
|
||||
@else_block ? [@for_block, @else_block] : [@for_block]
|
||||
if @else_block
|
||||
@for_block + @else_block
|
||||
else
|
||||
@for_block
|
||||
end
|
||||
end
|
||||
|
||||
def unknown_tag(tag, markup, tokens)
|
||||
return super unless tag == 'else'.freeze
|
||||
@else_block = BlockBody.new
|
||||
@nodelist = @else_block = []
|
||||
end
|
||||
|
||||
def render(context)
|
||||
context.registers[:for] ||= Hash.new(0)
|
||||
|
||||
collection = context.evaluate(@collection_name)
|
||||
collection = context[@collection_name]
|
||||
collection = collection.to_a if collection.is_a?(Range)
|
||||
|
||||
# Maintains Ruby 1.8.7 String#each behaviour on 1.9
|
||||
return render_else(context) unless iterable?(collection)
|
||||
|
||||
from = if @from == :continue
|
||||
from = if @attributes['offset'.freeze] == 'continue'.freeze
|
||||
context.registers[:for][@name].to_i
|
||||
else
|
||||
context.evaluate(@from).to_i
|
||||
context[@attributes['offset'.freeze]].to_i
|
||||
end
|
||||
|
||||
limit = context.evaluate(@limit)
|
||||
limit = context[@attributes['limit'.freeze]]
|
||||
to = limit ? limit.to_i + from : nil
|
||||
|
||||
segment = Utils.slice_collection(collection, from, to)
|
||||
@@ -112,7 +110,7 @@ module Liquid
|
||||
'last'.freeze => (index == length - 1)
|
||||
}
|
||||
|
||||
result << @for_block.render(context)
|
||||
result << render_all(@for_block, context)
|
||||
|
||||
# Handle any interrupts if they exist.
|
||||
if context.has_interrupt?
|
||||
@@ -130,12 +128,12 @@ module Liquid
|
||||
def lax_parse(markup)
|
||||
if markup =~ Syntax
|
||||
@variable_name = $1
|
||||
collection_name = $2
|
||||
@collection_name = $2
|
||||
@name = "#{$1}-#{$2}"
|
||||
@reversed = $3
|
||||
@name = "#{@variable_name}-#{collection_name}"
|
||||
@collection_name = Expression.parse(collection_name)
|
||||
@attributes = {}
|
||||
markup.scan(TagAttributes) do |key, value|
|
||||
set_attribute(key, value)
|
||||
@attributes[key] = value
|
||||
end
|
||||
else
|
||||
raise SyntaxError.new(options[:locale].t("errors.syntax.for".freeze))
|
||||
@@ -146,38 +144,26 @@ module Liquid
|
||||
p = Parser.new(markup)
|
||||
@variable_name = p.consume(:id)
|
||||
raise SyntaxError.new(options[:locale].t("errors.syntax.for_invalid_in".freeze)) unless p.id?('in'.freeze)
|
||||
collection_name = p.expression
|
||||
@name = "#{@variable_name}-#{collection_name}"
|
||||
@collection_name = Expression.parse(collection_name)
|
||||
@collection_name = p.expression
|
||||
@name = "#{@variable_name}-#{@collection_name}"
|
||||
@reversed = p.id?('reversed'.freeze)
|
||||
|
||||
@attributes = {}
|
||||
while p.look(:id) && p.look(:colon, 1)
|
||||
unless attribute = p.id?('limit'.freeze) || p.id?('offset'.freeze)
|
||||
raise SyntaxError.new(options[:locale].t("errors.syntax.for_invalid_attribute".freeze))
|
||||
end
|
||||
p.consume
|
||||
set_attribute(attribute, p.expression)
|
||||
val = p.expression
|
||||
@attributes[attribute] = val
|
||||
end
|
||||
p.consume(:end_of_string)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_attribute(key, expr)
|
||||
case key
|
||||
when 'offset'.freeze
|
||||
@from = if expr == 'continue'.freeze
|
||||
:continue
|
||||
else
|
||||
Expression.parse(expr)
|
||||
end
|
||||
when 'limit'.freeze
|
||||
@limit = Expression.parse(expr)
|
||||
end
|
||||
end
|
||||
|
||||
def render_else(context)
|
||||
@else_block ? @else_block.render(context) : ''.freeze
|
||||
return @else_block ? [render_all(@else_block, context)] : ''.freeze
|
||||
end
|
||||
|
||||
def iterable?(collection)
|
||||
|
||||
@@ -20,13 +20,8 @@ module Liquid
|
||||
push_block('if'.freeze, markup)
|
||||
end
|
||||
|
||||
def parse(tokens)
|
||||
while more = parse_body(@blocks.last.attachment, tokens)
|
||||
end
|
||||
end
|
||||
|
||||
def nodelist
|
||||
@blocks.map(&:attachment)
|
||||
@blocks.flat_map(&:attachment)
|
||||
end
|
||||
|
||||
def unknown_tag(tag, markup, tokens)
|
||||
@@ -41,7 +36,7 @@ module Liquid
|
||||
context.stack do
|
||||
@blocks.each do |block|
|
||||
if block.evaluate(context)
|
||||
return block.attachment.render(context)
|
||||
return render_all(block.attachment, context)
|
||||
end
|
||||
end
|
||||
''.freeze
|
||||
@@ -58,21 +53,21 @@ module Liquid
|
||||
end
|
||||
|
||||
@blocks.push(block)
|
||||
block.attach(BlockBody.new)
|
||||
@nodelist = block.attach(Array.new)
|
||||
end
|
||||
|
||||
def lax_parse(markup)
|
||||
expressions = markup.scan(ExpressionsAndOperators)
|
||||
raise(SyntaxError.new(options[:locale].t("errors.syntax.if".freeze))) unless expressions.pop =~ Syntax
|
||||
|
||||
condition = Condition.new(Expression.parse($1), $2, Expression.parse($3))
|
||||
condition = Condition.new($1, $2, $3)
|
||||
|
||||
while not expressions.empty?
|
||||
operator = expressions.pop.to_s.strip
|
||||
|
||||
raise(SyntaxError.new(options[:locale].t("errors.syntax.if".freeze))) unless expressions.pop.to_s =~ Syntax
|
||||
|
||||
new_condition = Condition.new(Expression.parse($1), $2, Expression.parse($3))
|
||||
new_condition = Condition.new($1, $2, $3)
|
||||
raise(SyntaxError.new(options[:locale].t("errors.syntax.if".freeze))) unless BOOLEAN_OPERATORS.include?(operator)
|
||||
new_condition.send(operator, condition)
|
||||
condition = new_condition
|
||||
@@ -83,23 +78,23 @@ module Liquid
|
||||
|
||||
def strict_parse(markup)
|
||||
p = Parser.new(markup)
|
||||
|
||||
condition = parse_comparison(p)
|
||||
|
||||
while op = (p.id?('and'.freeze) || p.id?('or'.freeze))
|
||||
new_cond = parse_comparison(p)
|
||||
new_cond.send(op, condition)
|
||||
condition = new_cond
|
||||
end
|
||||
condition = parse_binary_comparison(p)
|
||||
p.consume(:end_of_string)
|
||||
condition
|
||||
end
|
||||
|
||||
def parse_binary_comparison(p)
|
||||
condition = parse_comparison(p)
|
||||
if op = (p.id?('and'.freeze) || p.id?('or'.freeze))
|
||||
condition.send(op, parse_binary_comparison(p))
|
||||
end
|
||||
condition
|
||||
end
|
||||
|
||||
def parse_comparison(p)
|
||||
a = Expression.parse(p.expression)
|
||||
a = p.expression
|
||||
if op = p.consume?(:comparison)
|
||||
b = Expression.parse(p.expression)
|
||||
b = p.expression
|
||||
Condition.new(a, op, b)
|
||||
else
|
||||
Condition.new(a)
|
||||
|
||||
@@ -22,16 +22,12 @@ module Liquid
|
||||
|
||||
if markup =~ Syntax
|
||||
|
||||
template_name = $1
|
||||
variable_name = $3
|
||||
|
||||
@variable_name = Expression.parse(variable_name || template_name[1..-2])
|
||||
@context_variable_name = template_name[1..-2].split('/'.freeze).last
|
||||
@template_name = Expression.parse(template_name)
|
||||
@template_name = $1
|
||||
@variable_name = $3
|
||||
@attributes = {}
|
||||
|
||||
markup.scan(TagAttributes) do |key, value|
|
||||
@attributes[key] = Expression.parse(value)
|
||||
@attributes[key] = value
|
||||
end
|
||||
|
||||
else
|
||||
@@ -44,20 +40,21 @@ module Liquid
|
||||
|
||||
def render(context)
|
||||
partial = load_cached_partial(context)
|
||||
variable = context.evaluate(@variable_name)
|
||||
variable = context[@variable_name || @template_name[1..-2]]
|
||||
|
||||
context.stack do
|
||||
@attributes.each do |key, value|
|
||||
context[key] = context.evaluate(value)
|
||||
context[key] = context[value]
|
||||
end
|
||||
|
||||
context_variable_name = @template_name[1..-2].split('/'.freeze).last
|
||||
if variable.is_a?(Array)
|
||||
variable.collect do |var|
|
||||
context[@context_variable_name] = var
|
||||
context[context_variable_name] = var
|
||||
partial.render(context)
|
||||
end
|
||||
else
|
||||
context[@context_variable_name] = variable
|
||||
context[context_variable_name] = variable
|
||||
partial.render(context)
|
||||
end
|
||||
end
|
||||
@@ -66,7 +63,7 @@ module Liquid
|
||||
private
|
||||
def load_cached_partial(context)
|
||||
cached_partials = context.registers[:cached_partials] || {}
|
||||
template_name = context.evaluate(@template_name)
|
||||
template_name = context[@template_name]
|
||||
|
||||
if cached = cached_partials[template_name]
|
||||
return cached
|
||||
@@ -84,9 +81,9 @@ module Liquid
|
||||
# make read_template_file call backwards-compatible.
|
||||
case file_system.method(:read_template_file).arity
|
||||
when 1
|
||||
file_system.read_template_file(context.evaluate(@template_name))
|
||||
file_system.read_template_file(context[@template_name])
|
||||
when 2
|
||||
file_system.read_template_file(context.evaluate(@template_name), context)
|
||||
file_system.read_template_file(context[@template_name], context)
|
||||
else
|
||||
raise ArgumentError, "file_system.read_template_file expects two parameters: (template_name, context)"
|
||||
end
|
||||
|
||||
@@ -3,27 +3,16 @@ module Liquid
|
||||
FullTokenPossiblyInvalid = /\A(.*)#{TagStart}\s*(\w+)\s*(.*)?#{TagEnd}\z/om
|
||||
|
||||
def parse(tokens)
|
||||
@body = ''
|
||||
@nodelist ||= []
|
||||
@nodelist.clear
|
||||
while token = tokens.shift
|
||||
if token =~ FullTokenPossiblyInvalid
|
||||
@body << $1 if $1 != "".freeze
|
||||
@nodelist << $1 if $1 != "".freeze
|
||||
return if block_delimiter == $2
|
||||
end
|
||||
@body << token if not token.empty?
|
||||
@nodelist << token if not token.empty?
|
||||
end
|
||||
end
|
||||
|
||||
def render(context)
|
||||
@body
|
||||
end
|
||||
|
||||
def nodelist
|
||||
[@body]
|
||||
end
|
||||
|
||||
def blank?
|
||||
@body.empty?
|
||||
end
|
||||
end
|
||||
|
||||
Template.register_tag('raw'.freeze, Raw)
|
||||
|
||||
@@ -6,10 +6,10 @@ module Liquid
|
||||
super
|
||||
if markup =~ Syntax
|
||||
@variable_name = $1
|
||||
@collection_name = Expression.parse($2)
|
||||
@collection_name = $2
|
||||
@attributes = {}
|
||||
markup.scan(TagAttributes) do |key, value|
|
||||
@attributes[key] = Expression.parse(value)
|
||||
@attributes[key] = value
|
||||
end
|
||||
else
|
||||
raise SyntaxError.new(options[:locale].t("errors.syntax.table_row".freeze))
|
||||
@@ -17,16 +17,16 @@ module Liquid
|
||||
end
|
||||
|
||||
def render(context)
|
||||
collection = context.evaluate(@collection_name) or return ''.freeze
|
||||
collection = context[@collection_name] or return ''.freeze
|
||||
|
||||
from = @attributes.key?('offset'.freeze) ? context.evaluate(@attributes['offset'.freeze]).to_i : 0
|
||||
to = @attributes.key?('limit'.freeze) ? from + context.evaluate(@attributes['limit'.freeze]).to_i : nil
|
||||
from = @attributes['offset'.freeze] ? context[@attributes['offset'.freeze]].to_i : 0
|
||||
to = @attributes['limit'.freeze] ? from + context[@attributes['limit'.freeze]].to_i : nil
|
||||
|
||||
collection = Utils.slice_collection(collection, from, to)
|
||||
|
||||
length = collection.length
|
||||
|
||||
cols = context.evaluate(@attributes['cols'.freeze]).to_i
|
||||
cols = context[@attributes['cols'.freeze]].to_i
|
||||
|
||||
row = 1
|
||||
col = 0
|
||||
@@ -42,7 +42,6 @@ module Liquid
|
||||
'index0'.freeze => index,
|
||||
'col'.freeze => col + 1,
|
||||
'col0'.freeze => col,
|
||||
'index0'.freeze => index,
|
||||
'rindex'.freeze => length - index,
|
||||
'rindex0'.freeze => length - index - 1,
|
||||
'first'.freeze => (index == 0),
|
||||
|
||||
@@ -3,7 +3,7 @@ require File.dirname(__FILE__) + '/if'
|
||||
module Liquid
|
||||
# Unless is a conditional just like 'if' but works on the inverse logic.
|
||||
#
|
||||
# {% unless x < 0 %} x is greater than zero {% end %}
|
||||
# {% unless x < 0 %} x is greater than zero {% endunless %}
|
||||
#
|
||||
class Unless < If
|
||||
def render(context)
|
||||
@@ -12,13 +12,13 @@ module Liquid
|
||||
# First condition is interpreted backwards ( if not )
|
||||
first_block = @blocks.first
|
||||
unless first_block.evaluate(context)
|
||||
return first_block.attachment.render(context)
|
||||
return render_all(first_block.attachment, context)
|
||||
end
|
||||
|
||||
# After the first condition unless works just like if
|
||||
@blocks[1..-1].each do |block|
|
||||
if block.evaluate(context)
|
||||
return block.attachment.render(context)
|
||||
return render_all(block.attachment, context)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ module Liquid
|
||||
#
|
||||
class Variable
|
||||
FilterParser = /(?:\s+|#{QuotedFragment}|#{ArgumentSeparator})+/o
|
||||
EasyParse = /\A *(\w+(?:\.\w+)*) *\z/
|
||||
attr_accessor :filters, :name, :warnings
|
||||
attr_accessor :line_number
|
||||
include ParserSwitching
|
||||
@@ -52,10 +53,17 @@ module Liquid
|
||||
end
|
||||
|
||||
def strict_parse(markup)
|
||||
# Very simple valid cases
|
||||
if markup =~ EasyParse
|
||||
@name = Expression.parse($1)
|
||||
@filters = []
|
||||
return
|
||||
end
|
||||
|
||||
@filters = []
|
||||
p = Parser.new(markup)
|
||||
|
||||
@name = Expression.parse(p.expression)
|
||||
# Could be just filters with no input
|
||||
@name = p.look(:pipe) ? nil : Expression.parse(p.expression)
|
||||
while p.consume?(:pipe)
|
||||
filtername = p.consume(:id)
|
||||
filterargs = p.consume?(:colon) ? parse_filterargs(p) : []
|
||||
|
||||
@@ -3,6 +3,8 @@ module Liquid
|
||||
SQUARE_BRACKETED = /\A\[(.*)\]\z/m
|
||||
COMMAND_METHODS = ['size'.freeze, 'first'.freeze, 'last'.freeze]
|
||||
|
||||
attr_reader :name, :lookups
|
||||
|
||||
def self.parse(markup)
|
||||
new(markup)
|
||||
end
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# encoding: utf-8
|
||||
module Liquid
|
||||
VERSION = "3.0.0"
|
||||
VERSION = "3.0.4"
|
||||
end
|
||||
|
||||
@@ -8,17 +8,10 @@ profiler.run
|
||||
[:cpu, :object].each do |profile_type|
|
||||
puts "Profiling in #{profile_type.to_s} mode..."
|
||||
results = StackProf.run(mode: profile_type) do
|
||||
200.times do
|
||||
100.times do
|
||||
profiler.run
|
||||
end
|
||||
end
|
||||
|
||||
if profile_type == :cpu && graph_filename = ENV['GRAPH_FILENAME']
|
||||
File.open(graph_filename, 'w') do |f|
|
||||
StackProf::Report.new(results).print_graphviz(nil, f)
|
||||
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
|
||||
|
||||
@@ -3,6 +3,16 @@ require 'test_helper'
|
||||
class AssignTest < Minitest::Test
|
||||
include Liquid
|
||||
|
||||
def test_assign_with_hyphen_in_variable_name
|
||||
template_source = <<-END_TEMPLATE
|
||||
{% assign this-thing = 'Print this-thing' %}
|
||||
{{ this-thing }}
|
||||
END_TEMPLATE
|
||||
template = Template.parse(template_source)
|
||||
rendered = template.render!
|
||||
assert_equal "Print this-thing", rendered.strip
|
||||
end
|
||||
|
||||
def test_assigned_variable
|
||||
assert_template_result('.foo.',
|
||||
'{% assign foo = values %}.{{ foo[0] }}.',
|
||||
|
||||
@@ -7,6 +7,16 @@ class CaptureTest < Minitest::Test
|
||||
assert_template_result("test string", "{% capture 'var' %}test string{% endcapture %}{{var}}", {})
|
||||
end
|
||||
|
||||
def test_capture_with_hyphen_in_variable_name
|
||||
template_source = <<-END_TEMPLATE
|
||||
{% capture this-thing %}Print this-thing{% endcapture %}
|
||||
{{ this-thing }}
|
||||
END_TEMPLATE
|
||||
template = Template.parse(template_source)
|
||||
rendered = template.render!
|
||||
assert_equal "Print this-thing", rendered.strip
|
||||
end
|
||||
|
||||
def test_capture_to_variable_from_outer_scope_if_existing
|
||||
template_source = <<-END_TEMPLATE
|
||||
{% assign var = '' %}
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
require 'test_helper'
|
||||
|
||||
class DocumentTest < Minitest::Test
|
||||
include Liquid
|
||||
|
||||
def test_unexpected_outer_tag
|
||||
exc = assert_raises(SyntaxError) do
|
||||
Template.parse("{% else %}")
|
||||
end
|
||||
assert_equal exc.message, "Liquid syntax error: Unexpected outer 'else' tag"
|
||||
end
|
||||
|
||||
def test_unknown_tag
|
||||
exc = assert_raises(SyntaxError) do
|
||||
Template.parse("{% foo %}")
|
||||
end
|
||||
assert_equal exc.message, "Liquid syntax error: Unknown tag 'foo'"
|
||||
end
|
||||
end
|
||||
@@ -25,6 +25,12 @@ end
|
||||
class FiltersTest < Minitest::Test
|
||||
include Liquid
|
||||
|
||||
module OverrideObjectMethodFilter
|
||||
def tap(input)
|
||||
"tap overridden"
|
||||
end
|
||||
end
|
||||
|
||||
def setup
|
||||
@context = Context.new
|
||||
end
|
||||
@@ -105,6 +111,13 @@ class FiltersTest < Minitest::Test
|
||||
output = Variable.new(%! 'hello %{first_name}, %{last_name}' | substitute: first_name: surname, last_name: 'doe' !).render(@context)
|
||||
assert_equal 'hello john, doe', output
|
||||
end
|
||||
|
||||
def test_override_object_method_in_filter
|
||||
assert_equal "tap overridden", Template.parse("{{var | tap}}").render!({ 'var' => 1000 }, :filters => [OverrideObjectMethodFilter])
|
||||
|
||||
# tap still treated as a non-existent filter
|
||||
assert_equal "1000", Template.parse("{{var | tap}}").render!({ 'var' => 1000 })
|
||||
end
|
||||
end
|
||||
|
||||
class FiltersInTemplate < Minitest::Test
|
||||
|
||||
@@ -44,6 +44,14 @@ class OutputTest < Minitest::Test
|
||||
assert_equal expected, Template.parse(text).render!(@assigns)
|
||||
end
|
||||
|
||||
def test_variable_traversing_with_two_brackets
|
||||
text = %({{ site.data.menu[include.menu][include.locale] }})
|
||||
assert_equal "it works!", Template.parse(text).render!(
|
||||
"site" => { "data" => { "menu" => { "foo" => { "bar" => "it works!" } } } },
|
||||
"include" => { "menu" => "foo", "locale" => "bar" }
|
||||
)
|
||||
end
|
||||
|
||||
def test_variable_traversing
|
||||
text = %| {{car.bmw}} {{car.gm}} {{car.bmw}} |
|
||||
|
||||
|
||||
@@ -28,14 +28,11 @@ class ParsingQuirksTest < Minitest::Test
|
||||
|
||||
def test_error_on_empty_filter
|
||||
assert Template.parse("{{test}}")
|
||||
|
||||
with_error_mode(:lax) do
|
||||
assert Template.parse("{{|test}}")
|
||||
end
|
||||
|
||||
assert Template.parse("{{|test}}")
|
||||
with_error_mode(:strict) do
|
||||
assert_raises(SyntaxError) { Template.parse("{{|test}}") }
|
||||
assert_raises(SyntaxError) { Template.parse("{{test |a|b|}}") }
|
||||
assert_raises(SyntaxError) do
|
||||
Template.parse("{{test |a|b|}}")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -89,7 +89,7 @@ class RenderProfilingTest < Minitest::Test
|
||||
|
||||
include_node = t.profiler[1]
|
||||
include_node.children.each do |child|
|
||||
assert_equal "a_template", child.partial
|
||||
assert_equal "'a_template'", child.partial
|
||||
end
|
||||
end
|
||||
|
||||
@@ -99,12 +99,12 @@ class RenderProfilingTest < Minitest::Test
|
||||
|
||||
a_template = t.profiler[1]
|
||||
a_template.children.each do |child|
|
||||
assert_equal "a_template", child.partial
|
||||
assert_equal "'a_template'", child.partial
|
||||
end
|
||||
|
||||
b_template = t.profiler[2]
|
||||
b_template.children.each do |child|
|
||||
assert_equal "b_template", child.partial
|
||||
assert_equal "'b_template'", child.partial
|
||||
end
|
||||
end
|
||||
|
||||
@@ -114,12 +114,12 @@ class RenderProfilingTest < Minitest::Test
|
||||
|
||||
a_template1 = t.profiler[1]
|
||||
a_template1.children.each do |child|
|
||||
assert_equal "a_template", child.partial
|
||||
assert_equal "'a_template'", child.partial
|
||||
end
|
||||
|
||||
a_template2 = t.profiler[2]
|
||||
a_template2.children.each do |child|
|
||||
assert_equal "a_template", child.partial
|
||||
assert_equal "'a_template'", child.partial
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -166,4 +166,25 @@ class IfElseTagTest < Minitest::Test
|
||||
assert_template_result('', %({% if 1 or throw or or 1 %}yes{% endif %}))
|
||||
end
|
||||
end
|
||||
|
||||
def test_multiple_conditions
|
||||
tpl = "{% if a or b and c %}true{% else %}false{% endif %}"
|
||||
|
||||
tests = {
|
||||
[true, true, true] => true,
|
||||
[true, true, false] => true,
|
||||
[true, false, true] => true,
|
||||
[true, false, false] => true,
|
||||
[false, true, true] => true,
|
||||
[false, true, false] => false,
|
||||
[false, false, true] => false,
|
||||
[false, false, false] => false,
|
||||
}
|
||||
|
||||
tests.each do |vals, expected|
|
||||
a, b, c = vals
|
||||
assigns = { 'a' => a, 'b' => b, 'c' => c }
|
||||
assert_template_result expected.to_s, tpl, assigns, assigns.to_s
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#!/usr/bin/env ruby
|
||||
|
||||
ENV["MT_NO_EXPECTATIONS"] = "1"
|
||||
require 'minitest/autorun'
|
||||
require 'spy/integration'
|
||||
|
||||
@@ -31,13 +32,13 @@ module Minitest
|
||||
include Liquid
|
||||
|
||||
def assert_template_result(expected, template, assigns = {}, message = nil)
|
||||
assert_equal expected, Template.parse(template).render!(assigns)
|
||||
assert_equal expected, Template.parse(template).render!(assigns), message
|
||||
end
|
||||
|
||||
def assert_template_result_matches(expected, template, assigns = {}, message = nil)
|
||||
return assert_template_result(expected, template, assigns, message) unless expected.is_a? Regexp
|
||||
|
||||
assert_match expected, Template.parse(template).render!(assigns)
|
||||
assert_match expected, Template.parse(template).render!(assigns), message
|
||||
end
|
||||
|
||||
def assert_match_syntax_error(match, template, registers = {})
|
||||
@@ -48,13 +49,19 @@ module Minitest
|
||||
end
|
||||
|
||||
def with_global_filter(*globals)
|
||||
original_filters = Array.new(Liquid::Strainer.class_variable_get(:@@filters))
|
||||
original_global_strainer = Liquid::Strainer.class_variable_get(:@@global_strainer)
|
||||
Liquid::Strainer.class_variable_set(:@@global_strainer, Class.new(Liquid::Strainer) do
|
||||
@filter_methods = Set.new
|
||||
end)
|
||||
Liquid::Strainer.class_variable_get(:@@strainer_class_cache).clear
|
||||
|
||||
globals.each do |global|
|
||||
Liquid::Template.register_filter(global)
|
||||
end
|
||||
yield
|
||||
ensure
|
||||
Liquid::Strainer.class_variable_set(:@@filters, original_filters)
|
||||
Liquid::Strainer.class_variable_get(:@@strainer_class_cache).clear
|
||||
Liquid::Strainer.class_variable_set(:@@global_strainer, original_global_strainer)
|
||||
end
|
||||
|
||||
def with_taint_mode(mode)
|
||||
|
||||
@@ -4,111 +4,110 @@ class ConditionUnitTest < Minitest::Test
|
||||
include Liquid
|
||||
|
||||
def test_basic_condition
|
||||
assert_equal false, Condition.new(1, '==', 2).evaluate
|
||||
assert_equal true, Condition.new(1, '==', 1).evaluate
|
||||
assert_equal false, Condition.new('1', '==', '2').evaluate
|
||||
assert_equal true, Condition.new('1', '==', '1').evaluate
|
||||
end
|
||||
|
||||
def test_default_operators_evalute_true
|
||||
assert_evalutes_true 1, '==', 1
|
||||
assert_evalutes_true 1, '!=', 2
|
||||
assert_evalutes_true 1, '<>', 2
|
||||
assert_evalutes_true 1, '<', 2
|
||||
assert_evalutes_true 2, '>', 1
|
||||
assert_evalutes_true 1, '>=', 1
|
||||
assert_evalutes_true 2, '>=', 1
|
||||
assert_evalutes_true 1, '<=', 2
|
||||
assert_evalutes_true 1, '<=', 1
|
||||
assert_evalutes_true '1', '==', '1'
|
||||
assert_evalutes_true '1', '!=', '2'
|
||||
assert_evalutes_true '1', '<>', '2'
|
||||
assert_evalutes_true '1', '<', '2'
|
||||
assert_evalutes_true '2', '>', '1'
|
||||
assert_evalutes_true '1', '>=', '1'
|
||||
assert_evalutes_true '2', '>=', '1'
|
||||
assert_evalutes_true '1', '<=', '2'
|
||||
assert_evalutes_true '1', '<=', '1'
|
||||
# negative numbers
|
||||
assert_evalutes_true 1, '>', -1
|
||||
assert_evalutes_true -1, '<', 1
|
||||
assert_evalutes_true 1.0, '>', -1.0
|
||||
assert_evalutes_true -1.0, '<', 1.0
|
||||
assert_evalutes_true '1', '>', '-1'
|
||||
assert_evalutes_true '-1', '<', '1'
|
||||
assert_evalutes_true '1.0', '>', '-1.0'
|
||||
assert_evalutes_true '-1.0', '<', '1.0'
|
||||
end
|
||||
|
||||
def test_default_operators_evalute_false
|
||||
assert_evalutes_false 1, '==', 2
|
||||
assert_evalutes_false 1, '!=', 1
|
||||
assert_evalutes_false 1, '<>', 1
|
||||
assert_evalutes_false 1, '<', 0
|
||||
assert_evalutes_false 2, '>', 4
|
||||
assert_evalutes_false 1, '>=', 3
|
||||
assert_evalutes_false 2, '>=', 4
|
||||
assert_evalutes_false 1, '<=', 0
|
||||
assert_evalutes_false 1, '<=', 0
|
||||
assert_evalutes_false '1', '==', '2'
|
||||
assert_evalutes_false '1', '!=', '1'
|
||||
assert_evalutes_false '1', '<>', '1'
|
||||
assert_evalutes_false '1', '<', '0'
|
||||
assert_evalutes_false '2', '>', '4'
|
||||
assert_evalutes_false '1', '>=', '3'
|
||||
assert_evalutes_false '2', '>=', '4'
|
||||
assert_evalutes_false '1', '<=', '0'
|
||||
assert_evalutes_false '1', '<=', '0'
|
||||
end
|
||||
|
||||
def test_contains_works_on_strings
|
||||
assert_evalutes_true 'bob', 'contains', 'o'
|
||||
assert_evalutes_true 'bob', 'contains', 'b'
|
||||
assert_evalutes_true 'bob', 'contains', 'bo'
|
||||
assert_evalutes_true 'bob', 'contains', 'ob'
|
||||
assert_evalutes_true 'bob', 'contains', 'bob'
|
||||
assert_evalutes_true "'bob'", 'contains', "'o'"
|
||||
assert_evalutes_true "'bob'", 'contains', "'b'"
|
||||
assert_evalutes_true "'bob'", 'contains', "'bo'"
|
||||
assert_evalutes_true "'bob'", 'contains', "'ob'"
|
||||
assert_evalutes_true "'bob'", 'contains', "'bob'"
|
||||
|
||||
assert_evalutes_false 'bob', 'contains', 'bob2'
|
||||
assert_evalutes_false 'bob', 'contains', 'a'
|
||||
assert_evalutes_false 'bob', 'contains', '---'
|
||||
assert_evalutes_false "'bob'", 'contains', "'bob2'"
|
||||
assert_evalutes_false "'bob'", 'contains', "'a'"
|
||||
assert_evalutes_false "'bob'", 'contains', "'---'"
|
||||
end
|
||||
|
||||
def test_invalid_comparation_operator
|
||||
assert_evaluates_argument_error 1, '~~', 0
|
||||
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
|
||||
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]
|
||||
array_expr = VariableLookup.new("array")
|
||||
|
||||
assert_evalutes_false array_expr, 'contains', 0
|
||||
assert_evalutes_true array_expr, 'contains', 1
|
||||
assert_evalutes_true array_expr, 'contains', 2
|
||||
assert_evalutes_true array_expr, 'contains', 3
|
||||
assert_evalutes_true array_expr, 'contains', 4
|
||||
assert_evalutes_true array_expr, 'contains', 5
|
||||
assert_evalutes_false array_expr, 'contains', 6
|
||||
assert_evalutes_false array_expr, 'contains', "1"
|
||||
assert_evalutes_false "array", 'contains', '0'
|
||||
assert_evalutes_true "array", 'contains', '1'
|
||||
assert_evalutes_true "array", 'contains', '2'
|
||||
assert_evalutes_true "array", 'contains', '3'
|
||||
assert_evalutes_true "array", 'contains', '4'
|
||||
assert_evalutes_true "array", 'contains', '5'
|
||||
assert_evalutes_false "array", 'contains', '6'
|
||||
assert_evalutes_false "array", 'contains', '"1"'
|
||||
end
|
||||
|
||||
def test_contains_returns_false_for_nil_operands
|
||||
@context = Liquid::Context.new
|
||||
assert_evalutes_false VariableLookup.new('not_assigned'), 'contains', '0'
|
||||
assert_evalutes_false 0, 'contains', VariableLookup.new('not_assigned')
|
||||
assert_evalutes_false "not_assigned", 'contains', '0'
|
||||
assert_evalutes_false "0", 'contains', 'not_assigned'
|
||||
end
|
||||
|
||||
def test_contains_return_false_on_wrong_data_type
|
||||
assert_evalutes_false 1, 'contains', 0
|
||||
assert_evalutes_false "1", 'contains', '0'
|
||||
end
|
||||
|
||||
def test_or_condition
|
||||
condition = Condition.new(1, '==', 2)
|
||||
condition = Condition.new('1', '==', '2')
|
||||
|
||||
assert_equal false, condition.evaluate
|
||||
|
||||
condition.or Condition.new(2, '==', 1)
|
||||
condition.or Condition.new('2', '==', '1')
|
||||
|
||||
assert_equal false, condition.evaluate
|
||||
|
||||
condition.or Condition.new(1, '==', 1)
|
||||
condition.or Condition.new('1', '==', '1')
|
||||
|
||||
assert_equal true, condition.evaluate
|
||||
end
|
||||
|
||||
def test_and_condition
|
||||
condition = Condition.new(1, '==', 1)
|
||||
condition = Condition.new('1', '==', '1')
|
||||
|
||||
assert_equal true, condition.evaluate
|
||||
|
||||
condition.and Condition.new(2, '==', 2)
|
||||
condition.and Condition.new('2', '==', '2')
|
||||
|
||||
assert_equal true, condition.evaluate
|
||||
|
||||
condition.and Condition.new(2, '==', 1)
|
||||
condition.and Condition.new('2', '==', '1')
|
||||
|
||||
assert_equal false, condition.evaluate
|
||||
end
|
||||
@@ -116,17 +115,18 @@ class ConditionUnitTest < Minitest::Test
|
||||
def test_should_allow_custom_proc_operator
|
||||
Condition.operators['starts_with'] = Proc.new { |cond, left, right| left =~ %r{^#{right}} }
|
||||
|
||||
assert_evalutes_true 'bob', 'starts_with', 'b'
|
||||
assert_evalutes_false 'bob', 'starts_with', 'o'
|
||||
ensure
|
||||
Condition.operators.delete 'starts_with'
|
||||
assert_evalutes_true "'bob'", 'starts_with', "'b'"
|
||||
assert_evalutes_false "'bob'", 'starts_with', "'o'"
|
||||
|
||||
ensure
|
||||
Condition.operators.delete 'starts_with'
|
||||
end
|
||||
|
||||
def test_left_or_right_may_contain_operators
|
||||
@context = Liquid::Context.new
|
||||
@context['one'] = @context['another'] = "gnomeslab-and-or-liquid"
|
||||
|
||||
assert_evalutes_true VariableLookup.new("one"), '==', VariableLookup.new("another")
|
||||
assert_evalutes_true "one", '==', "another"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
@@ -469,6 +469,16 @@ class ContextUnitTest < Minitest::Test
|
||||
|
||||
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_context_initialization_with_a_proc_in_environment
|
||||
|
||||
@@ -31,11 +31,8 @@ class LexerUnitTest < Minitest::Test
|
||||
end
|
||||
|
||||
def test_fancy_identifiers
|
||||
tokens = Lexer.new('hi five?').tokenize
|
||||
assert_equal [[:id, 'hi'], [:id, 'five?'], [:end_of_string]], tokens
|
||||
|
||||
tokens = Lexer.new('2foo').tokenize
|
||||
assert_equal [[:number, '2'], [:id, 'foo'], [:end_of_string]], tokens
|
||||
tokens = Lexer.new('hi! five?').tokenize
|
||||
assert_equal [[:id,'hi!'], [:id, 'five?'], [:end_of_string]], tokens
|
||||
end
|
||||
|
||||
def test_whitespace
|
||||
|
||||
@@ -44,9 +44,9 @@ class ParserUnitTest < Minitest::Test
|
||||
end
|
||||
|
||||
def test_expressions
|
||||
p = Parser.new("hi.there hi?[5].there? hi.there.bob")
|
||||
p = Parser.new("hi.there hi[5].! hi.there.bob")
|
||||
assert_equal 'hi.there', p.expression
|
||||
assert_equal 'hi?[5].there?', p.expression
|
||||
assert_equal 'hi[5].!', p.expression
|
||||
assert_equal 'hi.there.bob', p.expression
|
||||
|
||||
p = Parser.new("567 6.0 'lol' \"wut\"")
|
||||
|
||||
@@ -31,11 +31,11 @@ class StrainerUnitTest < Minitest::Test
|
||||
|
||||
def test_strainer_only_invokes_public_filter_methods
|
||||
strainer = Strainer.create(nil)
|
||||
assert_equal false, strainer.invokable?('__test__')
|
||||
assert_equal false, strainer.invokable?('test')
|
||||
assert_equal false, strainer.invokable?('instance_eval')
|
||||
assert_equal false, strainer.invokable?('__send__')
|
||||
assert_equal true, strainer.invokable?('size') # from the standard lib
|
||||
assert_equal false, strainer.class.invokable?('__test__')
|
||||
assert_equal false, strainer.class.invokable?('test')
|
||||
assert_equal false, strainer.class.invokable?('instance_eval')
|
||||
assert_equal false, strainer.class.invokable?('__send__')
|
||||
assert_equal true, strainer.class.invokable?('size') # from the standard lib
|
||||
end
|
||||
|
||||
def test_strainer_returns_nil_if_no_filter_method_found
|
||||
@@ -63,9 +63,7 @@ class StrainerUnitTest < Minitest::Test
|
||||
assert_kind_of Strainer, strainer
|
||||
assert_kind_of a, strainer
|
||||
assert_kind_of b, strainer
|
||||
Strainer.class_variable_get(:@@filters).each do |m|
|
||||
assert_kind_of m, strainer
|
||||
end
|
||||
assert_kind_of Liquid::StandardFilters, strainer
|
||||
end
|
||||
|
||||
end # StrainerTest
|
||||
|
||||
@@ -5,6 +5,6 @@ class CaseTagUnitTest < Minitest::Test
|
||||
|
||||
def test_case_nodelist
|
||||
template = Liquid::Template.parse('{% case var %}{% when true %}WHEN{% else %}ELSE{% endcase %}')
|
||||
assert_equal ['WHEN', 'ELSE'], template.root.nodelist[0].nodelist.map(&:nodelist).flatten
|
||||
assert_equal ['WHEN', 'ELSE'], template.root.nodelist[0].nodelist
|
||||
end
|
||||
end
|
||||
|
||||
@@ -3,11 +3,11 @@ require 'test_helper'
|
||||
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.map(&:nodelist).flatten
|
||||
assert_equal ['FOR'], template.root.nodelist[0].nodelist
|
||||
end
|
||||
|
||||
def test_for_else_nodelist
|
||||
template = Liquid::Template.parse('{% for item in items %}FOR{% else %}ELSE{% endfor %}')
|
||||
assert_equal ['FOR', 'ELSE'], template.root.nodelist[0].nodelist.map(&:nodelist).flatten
|
||||
assert_equal ['FOR', 'ELSE'], template.root.nodelist[0].nodelist
|
||||
end
|
||||
end
|
||||
|
||||
@@ -3,6 +3,6 @@ require 'test_helper'
|
||||
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.map(&:nodelist).flatten
|
||||
assert_equal ['IF', 'ELSE'], template.root.nodelist[0].nodelist
|
||||
end
|
||||
end
|
||||
|
||||
@@ -5,17 +5,16 @@ class TemplateUnitTest < Minitest::Test
|
||||
|
||||
def test_sets_default_localization_in_document
|
||||
t = Template.new
|
||||
t.parse('{%comment%}{%endcomment%}')
|
||||
assert_instance_of I18n, t.root.nodelist[0].options[:locale]
|
||||
t.parse('')
|
||||
assert_instance_of I18n, t.root.options[:locale]
|
||||
end
|
||||
|
||||
def test_sets_default_localization_in_context_with_quick_initialization
|
||||
t = Template.new
|
||||
t.parse('{%comment%}{%endcomment%}', :locale => I18n.new(fixture("en_locale.yml")))
|
||||
t.parse('{{foo}}', :locale => I18n.new(fixture("en_locale.yml")))
|
||||
|
||||
locale = t.root.nodelist[0].options[:locale]
|
||||
assert_instance_of I18n, locale
|
||||
assert_equal fixture("en_locale.yml"), locale.path
|
||||
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
|
||||
|
||||
@@ -102,17 +102,6 @@ class VariableUnitTest < Minitest::Test
|
||||
assert_equal 1000.01, var.name
|
||||
end
|
||||
|
||||
def test_dashes
|
||||
assert_equal VariableLookup.new('foo-bar'), Variable.new('foo-bar').name
|
||||
assert_equal VariableLookup.new('foo-bar-2'), Variable.new('foo-bar-2').name
|
||||
|
||||
with_error_mode :strict do
|
||||
assert_raises(Liquid::SyntaxError) { Variable.new('foo - bar') }
|
||||
assert_raises(Liquid::SyntaxError) { Variable.new('-foo') }
|
||||
assert_raises(Liquid::SyntaxError) { Variable.new('2foo') }
|
||||
end
|
||||
end
|
||||
|
||||
def test_string_with_special_chars
|
||||
var = Variable.new(%| 'hello! $!@.;"ddasd" ' |)
|
||||
assert_equal 'hello! $!@.;"ddasd" ', var.name
|
||||
@@ -147,4 +136,10 @@ class VariableUnitTest < Minitest::Test
|
||||
var = Variable.new(%! name_of_variable | upcase !)
|
||||
assert_equal " name_of_variable | upcase ", var.raw
|
||||
end
|
||||
|
||||
def test_variable_lookup_interface
|
||||
lookup = VariableLookup.new('a.b.c')
|
||||
assert_equal 'a', lookup.name
|
||||
assert_equal ['b', 'c'], lookup.lookups
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user