Compare commits

...

27 Commits

Author SHA1 Message Date
Florian Weingarten
d6db28c854 Revert "Merge pull request #463 from Shopify/stricter-identifiers"
This reverts commit a056f6521c, reversing
changes made to 7843bcca8d.
2014-11-07 01:49:01 +00:00
Florian Weingarten
475ea51f1f Revert "Merge pull request #466 from Shopify/remove-expression-cache"
This reverts commit d9ae36ec40, reversing
changes made to 2da9d49478.
2014-11-07 01:48:51 +00:00
Florian Weingarten
9c33e9601b Revert "Merge pull request #476 from Shopify/missing-variable-name-error"
This reverts commit 4dc682313f, reversing
changes made to a8f60ff6b1.
2014-11-07 01:48:16 +00:00
Florian Weingarten
b242a7273a Revert "Merge pull request #478 from Shopify/numbers-in-identifiers"
This reverts commit 263e90e772, reversing
changes made to 4dc682313f.
2014-11-07 01:48:05 +00:00
Florian Weingarten
4b1835e3c0 Revert "Merge pull request #458 from Shopify/block-body"
This reverts commit 12d526a05c, reversing
changes made to 263e90e772.

Conflicts:
	lib/liquid/block_body.rb
2014-11-07 01:47:47 +00:00
Florian Weingarten
2fe3a21a5d Revert "Merge pull request #479 from Shopify/tweaks-for-c"
This reverts commit aa182f64b4, reversing
changes made to 70c45f8cd8.
2014-11-07 01:46:59 +00:00
Justin Li
76ef675eb2 Merge pull request #481 from Shopify/fix-nil-blank
Coerce regex @blank output to a boolean
2014-11-06 13:03:15 -05:00
Justin Li
e5fd4d929f Coerce regex @blank output to a boolean 2014-11-05 20:44:06 -05:00
Justin Li
2e42c7be1f Merge pull request #480 from Shopify/number_variables
Add quirks test for variables with number prefixes
2014-11-05 12:05:21 -05:00
Justin Li
95b031ee04 Add quirks test for extra dots in ranges 2014-11-05 11:41:12 -05:00
Justin Li
4d97a714a9 Add quirks test for variables with number prefixes 2014-11-05 10:56:58 -05:00
Justin Li
aa182f64b4 Merge pull request #479 from Shopify/tweaks-for-c
Tweaks for C
2014-11-04 14:02:14 -05:00
Justin Li
4e870302b1 Add env var for saving stackprof graphviz output 2014-11-04 18:38:14 +00:00
Justin Li
098c89b5f5 Convenience methods for raising terminator syntax errors 2014-11-04 18:38:08 +00:00
Justin Li
70c45f8cd8 Use SVG badge URLs
[ci skip]
2014-11-03 17:41:42 -05:00
Justin Li
12d526a05c Merge pull request #458 from Shopify/block-body
Create a BlockBody class to decouple block body parsing from tags.
2014-11-03 17:34:39 -05:00
Dylan Thacker-Smith
2fd8ad08c0 Remove unused local variable that was accidentally added. 2014-11-03 17:07:42 -05:00
Dylan Thacker-Smith
15e1d46125 Avoid storing options instance variable in BlockBody.
There is no need to pass parse options to the BlockBody initializer, since
it does all the parsing in the parse method, unlike tags which parse the
tag markup in the initializer.
2014-11-03 17:07:42 -05:00
Dylan Thacker-Smith
73fcd42403 Create a BlockBody class to decouple block body parsing from tags. 2014-11-03 17:07:42 -05:00
Justin Li
263e90e772 Merge pull request #478 from Shopify/numbers-in-identifiers
Use a single token for identifiers
2014-10-30 21:59:26 -04:00
Justin Li
81770f094d Remove unnecessary + 2014-10-29 13:39:43 -04:00
Justin Li
dd5ee81089 Disallow number and dash identifier prefixes 2014-10-29 12:08:00 -04:00
Justin Li
a07e382617 Use a single token for identifiers 2014-10-29 11:28:41 -04:00
Justin Li
4dc682313f Merge pull request #476 from Shopify/missing-variable-name-error
Disallow filters with no variable in strict mode
2014-10-27 13:56:11 -04:00
Justin Li
5616ddf00e Remove obsolete comment 2014-10-27 13:44:14 -04:00
Justin Li
fcb23a4cd2 Disallow filters with no variable in strict mode 2014-10-27 13:34:27 -04:00
Justin Li
a8f60ff6b1 Merge pull request #472 from Shopify/fix-leaky-test
Fix test leaking error_mode, fix equality check for VariableLookup
2014-10-23 10:12:41 -04:00
19 changed files with 267 additions and 144 deletions

View File

@@ -1,5 +1,5 @@
[![Build Status](https://secure.travis-ci.org/Shopify/liquid.png?branch=master)](http://travis-ci.org/Shopify/liquid)
[![Inline docs](http://inch-ci.org/github/Shopify/liquid.png)](http://inch-ci.org/github/Shopify/liquid)
[![Build Status](https://api.travis-ci.org/Shopify/liquid.svg?branch=master)](http://travis-ci.org/Shopify/liquid)
[![Inline docs](http://inch-ci.org/github/Shopify/liquid.svg?branch=master)](http://inch-ci.org/github/Shopify/liquid)
# Liquid template engine

123
lib/liquid/block_body.rb Normal file
View File

@@ -0,0 +1,123 @@
module Liquid
class BlockBody
FullToken = /\A#{TagStart}\s*(\w+)\s*(.*)?#{TagEnd}\z/om
ContentOfVariable = /\A#{VariableStart}(.*)#{VariableEnd}\z/om
TAGSTART = "{%".freeze
VARSTART = "{{".freeze
attr_reader :nodelist
def initialize
@nodelist = []
@blank = true
end
def parse(tokens, options)
while token = tokens.shift
begin
unless token.empty?
case
when token.start_with?(TAGSTART)
if token =~ FullToken
tag_name = $1
markup = $2
# fetch the tag from registered blocks
if tag = Template.tags[tag_name]
markup = token.child(markup) if token.is_a?(Token)
new_tag = tag.parse(tag_name, markup, tokens, options)
new_tag.line_number = token.line_number if token.is_a?(Token)
@blank &&= new_tag.blank?
@nodelist << new_tag
else
# end parsing if we reach an unknown tag and let the caller decide
# determine how to proceed
return yield tag_name, markup
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, options)
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
yield nil, nil
end
def blank?
@blank
end
def warnings
all_warnings = []
nodelist.each do |node|
all_warnings.concat(node.warnings) if node.respond_to?(:warnings) && node.warnings
end
all_warnings
end
def render(context)
output = []
context.resource_limits[:render_length_current] = 0
context.resource_limits[:render_score_current] += @nodelist.length
@nodelist.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
end
output.join
end
private
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
def create_variable(token, options)
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
end
end

View File

@@ -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}"))

View File

@@ -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
@@ -169,7 +170,7 @@ module Liquid
# Example:
# products == empty #=> products.empty?
def [](expression)
evaluate(Expression.parse(expression))
evaluate(@parsed_expression[expression])
end
def has_key?(key)

View File

@@ -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 = /\w+/
IDENTIFIER = /[\w\-?!]+/
SINGLE_STRING_LITERAL = /'[^\']*'/
DOUBLE_STRING_LITERAL = /"[^\"]*"/
NUMBER_LITERAL = /-?\d+(\.\d+)?/

View File

@@ -75,13 +75,6 @@ module Liquid
def variable_signature
str = consume(:id)
while consume?(:dash)
str << "-".freeze
str << consume(:id)
end
if consume?(:question)
str << "?".freeze
end
if look(:open_square)
str << consume
str << expression

View File

@@ -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

View File

@@ -8,7 +8,7 @@ module Liquid
@blocks = []
if markup =~ Syntax
@left = Expression.parse($1)
@left = $1
else
raise SyntaxError.new(options[:locale].t("errors.syntax.case".freeze))
end
@@ -58,7 +58,7 @@ module Liquid
markup = $2
block = Condition.new(@left, '=='.freeze, Expression.parse($1))
block = Condition.new(@left, '=='.freeze, $1)
block.attach(@nodelist)
@blocks.push(block)
end

View File

@@ -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

View File

@@ -68,19 +68,19 @@ module Liquid
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)
@@ -128,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))
@@ -144,36 +144,24 @@ 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)
return @else_block ? [render_all(@else_block, context)] : ''.freeze
end

View File

@@ -60,14 +60,14 @@ module Liquid
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
@@ -92,9 +92,9 @@ module Liquid
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)

View File

@@ -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

View File

@@ -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

View File

@@ -100,4 +100,17 @@ class ParsingQuirksTest < Minitest::Test
end
end
def test_invalid_variables_work
with_error_mode(:lax) do
assert_template_result('bar', "{% assign 123foo = 'bar' %}{{ 123foo }}")
assert_template_result('123', "{% assign 123 = 'bar' %}{{ 123 }}")
end
end
def test_extra_dots_in_ranges
with_error_mode(:lax) do
assert_template_result('12345', "{% for i in (1...5) %}{{ i }}{% endfor %}")
end
end
end # ParsingQuirksTest

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -31,8 +31,8 @@ class LexerUnitTest < Minitest::Test
end
def test_fancy_identifiers
tokens = Lexer.new('hi five?').tokenize
assert_equal [[:id,'hi'], [:id, 'five'], [:question, '?'], [:end_of_string]], tokens
tokens = Lexer.new('hi! five?').tokenize
assert_equal [[:id,'hi!'], [:id, 'five?'], [:end_of_string]], tokens
end
def test_whitespace

View File

@@ -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\"")