Compare commits

..

1 Commits

Author SHA1 Message Date
Justin Li
5153ad1a78 Add test for spaces in square brackets 2014-10-22 15:47:12 -04:00
20 changed files with 147 additions and 267 deletions

View File

@@ -1,5 +1,5 @@
[![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)
[![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)
# Liquid template engine

View File

@@ -1,123 +0,0 @@
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[left] if op == nil
return context.evaluate(left) if op == nil
left = context[left]
right = context[right]
left = context.evaluate(left)
right = context.evaluate(right)
operation = self.class.operators[op] || raise(Liquid::ArgumentError.new("Unknown operator #{op}"))

View File

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

View File

@@ -9,9 +9,11 @@ module Liquid
'['.freeze => :open_square,
']'.freeze => :close_square,
'('.freeze => :open_round,
')'.freeze => :close_round
')'.freeze => :close_round,
'?'.freeze => :question,
'-'.freeze => :dash
}
IDENTIFIER = /[\w\-?!]+/
IDENTIFIER = /\w+/
SINGLE_STRING_LITERAL = /'[^\']*'/
DOUBLE_STRING_LITERAL = /"[^\"]*"/
NUMBER_LITERAL = /-?\d+(\.\d+)?/

View File

@@ -75,6 +75,13 @@ 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(@template_name) do
Profiler.profile_children(context.evaluate(@template_name).to_s) do
render_without_profiling(context)
end
end

View File

@@ -8,7 +8,7 @@ module Liquid
@blocks = []
if markup =~ Syntax
@left = $1
@left = Expression.parse($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, $1)
block = Condition.new(@left, '=='.freeze, Expression.parse($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 = $1
@name = Expression.parse($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[@name]
key = context.evaluate(@name)
iteration = context.registers[:cycle][key]
result = context[@variables[iteration]]
result = context.evaluate(@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 ? $1 : nil
$1 ? Expression.parse($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[@collection_name]
collection = context.evaluate(@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 @attributes['offset'.freeze] == 'continue'.freeze
from = if @from == :continue
context.registers[:for][@name].to_i
else
context[@attributes['offset'.freeze]].to_i
context.evaluate(@from).to_i
end
limit = context[@attributes['limit'.freeze]]
limit = context.evaluate(@limit)
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
@name = "#{$1}-#{$2}"
collection_name = $2
@reversed = $3
@attributes = {}
@name = "#{@variable_name}-#{collection_name}"
@collection_name = Expression.parse(collection_name)
markup.scan(TagAttributes) do |key, value|
@attributes[key] = value
set_attribute(key, value)
end
else
raise SyntaxError.new(options[:locale].t("errors.syntax.for".freeze))
@@ -144,24 +144,36 @@ 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 = p.expression
@name = "#{@variable_name}-#{collection_name}"
@collection_name = Expression.parse(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
val = p.expression
@attributes[attribute] = val
set_attribute(attribute, p.expression)
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($1, $2, $3)
condition = Condition.new(Expression.parse($1), $2, Expression.parse($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($1, $2, $3)
new_condition = Condition.new(Expression.parse($1), $2, Expression.parse($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 = p.expression
a = Expression.parse(p.expression)
if op = p.consume?(:comparison)
b = p.expression
b = Expression.parse(p.expression)
Condition.new(a, op, b)
else
Condition.new(a)

View File

@@ -22,12 +22,16 @@ module Liquid
if markup =~ Syntax
@template_name = $1
@variable_name = $3
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)
@attributes = {}
markup.scan(TagAttributes) do |key, value|
@attributes[key] = value
@attributes[key] = Expression.parse(value)
end
else
@@ -40,21 +44,20 @@ module Liquid
def render(context)
partial = load_cached_partial(context)
variable = context[@variable_name || @template_name[1..-2]]
variable = context.evaluate(@variable_name)
context.stack do
@attributes.each do |key, value|
context[key] = context[value]
context[key] = context.evaluate(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
@@ -63,7 +66,7 @@ module Liquid
private
def load_cached_partial(context)
cached_partials = context.registers[:cached_partials] || {}
template_name = context[@template_name]
template_name = context.evaluate(@template_name)
if cached = cached_partials[template_name]
return cached
@@ -81,9 +84,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[@template_name])
file_system.read_template_file(context.evaluate(@template_name))
when 2
file_system.read_template_file(context[@template_name], context)
file_system.read_template_file(context.evaluate(@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 = $2
@collection_name = Expression.parse($2)
@attributes = {}
markup.scan(TagAttributes) do |key, value|
@attributes[key] = value
@attributes[key] = Expression.parse(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[@collection_name] or return ''.freeze
collection = context.evaluate(@collection_name) or return ''.freeze
from = @attributes['offset'.freeze] ? context[@attributes['offset'.freeze]].to_i : 0
to = @attributes['limit'.freeze] ? from + context[@attributes['limit'.freeze]].to_i : nil
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
collection = Utils.slice_collection(collection, from, to)
length = collection.length
cols = context[@attributes['cols'.freeze]].to_i
cols = context.evaluate(@attributes['cols'.freeze]).to_i
row = 1
col = 0

View File

@@ -100,17 +100,4 @@ 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,110 +4,111 @@ 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", '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"'
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"
end
def test_contains_returns_false_for_nil_operands
@context = Liquid::Context.new
assert_evalutes_false "not_assigned", 'contains', '0'
assert_evalutes_false "0", 'contains', 'not_assigned'
assert_evalutes_false VariableLookup.new('not_assigned'), 'contains', '0'
assert_evalutes_false 0, 'contains', VariableLookup.new('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
@@ -115,18 +116,17 @@ 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 "one", '==', "another"
assert_evalutes_true VariableLookup.new("one"), '==', VariableLookup.new("another")
end
private

View File

@@ -469,16 +469,6 @@ 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?'], [:end_of_string]], tokens
tokens = Lexer.new('hi five?').tokenize
assert_equal [[:id,'hi'], [:id, 'five'], [:question, '?'], [: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].! hi.there.bob")
p = Parser.new("hi.there hi?[5].there? hi.there.bob")
assert_equal 'hi.there', p.expression
assert_equal 'hi[5].!', p.expression
assert_equal 'hi?[5].there?', p.expression
assert_equal 'hi.there.bob', p.expression
p = Parser.new("567 6.0 'lol' \"wut\"")

View File

@@ -6,6 +6,9 @@ class VariableUnitTest < Minitest::Test
def test_variable
var = Variable.new('hello')
assert_equal VariableLookup.new('hello'), var.name
var = Variable.new('hello[goodbye ]')
assert_equal VariableLookup.new('hello[goodbye]'), var.name
end
def test_filters