Compare commits

..

3 Commits

Author SHA1 Message Date
Tristan Hume
10173c3315 Add line numbers to warnings 2014-08-14 16:23:38 -04:00
Tristan Hume
1c1aa4094a Remove parser switching duplication 2014-08-14 15:06:54 -04:00
Florian Weingarten
627ef9e29d Optional line numbers for liquid errors 2014-08-14 16:11:21 +00:00
42 changed files with 243 additions and 679 deletions

View File

@@ -2,8 +2,6 @@ rvm:
- 1.9
- 2.0
- 2.1
- 2.2
- ruby-head
- jruby-19mode
- jruby-head
- rbx-2
@@ -11,7 +9,6 @@ matrix:
allow_failures:
- rvm: rbx-2
- rvm: jruby-head
- rvm: ruby-head
script: "rake test"

View File

@@ -1,19 +1,8 @@
# Liquid Version History
## 3.0.2 / 2015-04-24 / branch "3-0-stable"
## 3.0.0 / not yet released / branch "master"
* 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
* 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]
* ...
* Add uniq to standard filters [Florian Weingarten, fw42]
* Add exception_handler feature, see #397 and #254 [Bogdan Gusiev, bogdan and Florian Weingarten, fw42]
* Optimize variable parsing to avoid repeated regex evaluation during template rendering #383 [Jason Hiltz-Laforge, jasonhl]

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

@@ -11,8 +11,7 @@ class LiquidServlet < WEBrick::HTTPServlet::AbstractServlet
private
def handle(type, req, res)
@request = req
@response = res
@request, @response = req, res
@request.path_info =~ /(\w+)\z/
@action = $1 || 'index'

View File

@@ -14,45 +14,48 @@ module Liquid
@nodelist ||= []
@nodelist.clear
# All child tags of the current block.
@children = []
while token = tokens.shift
begin
unless token.empty?
case
when token.start_with?(TAGSTART)
if token =~ FullToken
unless token.empty?
case
when token.start_with?(TAGSTART)
if token =~ FullToken
# if we found the proper block delimiter just end parsing here and let the outer block
# proceed
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))
# if we found the proper block delimiter just end parsing here and let the outer block
# proceed
if block_delimiter == $1
end_tag
return
end
# fetch the tag from registered blocks
if tag = Template.tags[$1]
markup = token.is_a?(Token) ? token.child($2) : $2
new_tag = tag.parse($1, markup, tokens, @options)
new_tag.line_number = token.line_number if token.is_a?(Token)
@blank &&= new_tag.blank?
@nodelist << new_tag
@children << new_tag
else
# this tag is not registered with the system
# pass it to the current block for special handling or error reporting
unknown_tag($1, $2, tokens)
end
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/)
raise SyntaxError.new(options[:locale].t("errors.syntax.tag_termination".freeze, :token => token, :tag_end => TagEnd.inspect))
end
when token.start_with?(VARSTART)
new_var = create_variable(token)
new_var.line_number = token.line_number if token.is_a?(Token)
@nodelist << new_var
@children << new_var
@blank = false
else
@nodelist << token
@blank &&= (token =~ /\A\s*\z/)
end
rescue SyntaxError => e
e.set_line_number_from_token(token)
raise
end
end
@@ -67,13 +70,16 @@ module Liquid
all_warnings = []
all_warnings.concat(@warnings) if @warnings
(nodelist || []).each do |node|
all_warnings.concat(node.warnings || []) if node.respond_to?(:warnings)
(@children || []).each do |node|
all_warnings.concat(node.warnings || [])
end
all_warnings
end
def end_tag
end
def unknown_tag(tag, params, tokens)
case tag
when 'else'.freeze

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

@@ -15,9 +15,7 @@ module Liquid
'>'.freeze => :>,
'>='.freeze => :>=,
'<='.freeze => :<=,
'contains'.freeze => lambda { |cond, left, right|
left && right && left.respond_to?(:include?) ? left.include?(right) : false
}
'contains'.freeze => lambda { |cond, left, right| left && right ? left.include?(right) : false }
}
def self.operators
@@ -28,9 +26,7 @@ module Liquid
attr_accessor :left, :operator, :right
def initialize(left = nil, operator = nil, right = nil)
@left = left
@operator = operator
@right = right
@left, @operator, @right = left, operator, right
@child_relation = nil
@child_condition = nil
end
@@ -49,13 +45,11 @@ module Liquid
end
def or(condition)
@child_relation = :or
@child_condition = condition
@child_relation, @child_condition = :or, condition
end
def and(condition)
@child_relation = :and
@child_condition = condition
@child_relation, @child_condition = :and, condition
end
def attach(attachment)
@@ -98,8 +92,7 @@ module Liquid
# return this as the result.
return context[left] if op == nil
left = context[left]
right = context[right]
left, right = context[left], context[right]
operation = self.class.operators[op] || raise(Liquid::ArgumentError.new("Unknown operator #{op}"))

View File

@@ -16,14 +16,12 @@ module Liquid
attr_reader :scopes, :errors, :registers, :environments, :resource_limits
attr_accessor :exception_handler
def initialize(environments = {}, outer_scope = {}, registers = {}, rethrow_errors = false, resource_limits = nil)
def initialize(environments = {}, outer_scope = {}, registers = {}, rethrow_errors = false, resource_limits = {})
@environments = [environments].flatten
@scopes = [(outer_scope || {})]
@registers = registers
@errors = []
@resource_limits = resource_limits || Template.default_resource_limits.dup
@resource_limits[:render_score_current] = 0
@resource_limits[:assign_score_current] = 0
@resource_limits = (resource_limits || {}).merge!({ :render_score_current => 0, :assign_score_current => 0 })
@parsed_expression = Hash.new{ |cache, markup| cache[markup] = Expression.parse(markup) }
squash_instance_assigns_with_environments
@@ -61,8 +59,21 @@ module Liquid
# for that
def add_filters(filters)
filters = [filters].flatten.compact
@filters += filters
@strainer = nil
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
end
# are there any not handled interrupts?
@@ -81,11 +92,8 @@ module Liquid
end
def handle_error(e, token=nil)
if e.is_a?(Liquid::Error)
e.set_line_number_from_token(token)
end
def handle_error(e, token)
e = Liquid::Error.error_with_line_number(e, token)
errors.push(e)
raise if exception_handler && exception_handler.call(e)
Liquid::Error.render(e)

View File

@@ -1,60 +1,42 @@
module Liquid
class Error < ::StandardError
attr_accessor :line_number
attr_accessor :markup_context
def to_s(with_prefix=true)
str = ""
str << message_prefix if with_prefix
str << super()
if markup_context
str << " "
str << markup_context
def self.render(e)
msg = if e.is_a?(Liquid::Error) && e.line_number
"#{e.line_number}: #{e.message}"
else
e.message
end
str
case e
when SyntaxError
"Liquid syntax error: #{msg}"
else
"Liquid error: #{msg}"
end
end
def self.error_with_line_number(e, token)
if e.is_a?(Liquid::Error)
e.set_line_number_from_token(token)
end
e
end
def set_line_number_from_token(token)
return unless token.respond_to?(:line_number)
return if self.line_number
self.line_number = token.line_number
end
def self.render(e)
if e.is_a?(Liquid::Error)
e.to_s
else
"Liquid error: #{e.to_s}"
end
end
private
def message_prefix
str = ""
if is_a?(SyntaxError)
str << "Liquid syntax error"
else
str << "Liquid error"
end
if line_number
str << " (line #{line_number})"
end
str << ": "
str
end
end
class ArgumentError < Error; end
class ContextError < Error; end
class FilterNotFound < Error; end
class FileSystemError < Error; end
class StandardError < Error; end
class SyntaxError < Error; end
class StackLevelError < Error; end
class TaintedError < Error; end
class MemoryError < Error; end
end

View File

@@ -8,7 +8,7 @@ module Liquid
begin
return strict_parse_with_error_context(markup)
rescue SyntaxError => e
e.set_line_number_from_token(markup)
e.line_number = markup.line_number if markup.is_a?(Token)
@warnings ||= []
@warnings << e
return lax_parse(markup)
@@ -20,12 +20,12 @@ module Liquid
def strict_parse_with_error_context(markup)
strict_parse(markup)
rescue SyntaxError => e
e.markup_context = markup_context(markup)
e.message << markup_context(markup)
raise e
end
def markup_context(markup)
"in \"#{markup.strip}\""
" in \"#{markup.strip}\""
end
end
end
end

View File

@@ -34,7 +34,7 @@ module Liquid
end
def escape(input)
CGI.escapeHTML(input).untaint rescue input
CGI.escapeHTML(input) rescue input
end
alias_method :h, :escape
@@ -42,10 +42,6 @@ module Liquid
input.to_s.gsub(HTML_ESCAPE_ONCE_REGEXP, HTML_ESCAPE)
end
def url_encode(input)
CGI.escape(input) rescue input
end
def slice(input, offset, length=nil)
offset = Integer(offset)
length = length ? Integer(length) : 1

View File

@@ -8,13 +8,12 @@ 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:
@@global_strainer = Class.new(Strainer) do
@filter_methods = Set.new
end
@@filters = []
@@known_filters = Set.new
@@known_methods = Set.new
@@strainer_class_cache = Hash.new do |hash, filters|
hash[filters] = Class.new(@@global_strainer) do
@filter_methods = @@global_strainer.filter_methods.dup
filters.each { |f| add_filter(f) }
hash[filters] = Class.new(Strainer) do
filters.each { |f| include f }
end
end
@@ -22,32 +21,33 @@ module Liquid
@context = context
end
def self.filter_methods
@filter_methods
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)
end
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))
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)
end
end
def self.global_filter(filter)
@@global_strainer.add_filter(filter)
end
def self.invokable?(method)
@filter_methods.include?(method.to_s)
def self.strainer_class_cache
@@strainer_class_cache
end
def self.create(context, filters = [])
@@strainer_class_cache[filters].new(context)
filters = @@filters + filters
strainer_class_cache[filters].new(context)
end
def invoke(method, *args)
if self.class.invokable?(method)
if invokable?(method)
send(method, *args)
else
args.first
@@ -55,5 +55,9 @@ 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

View File

@@ -11,7 +11,7 @@ module Liquid
# in a sidebar or footer.
#
class Capture < Block
Syntax = /(#{VariableSignature}+)/o
Syntax = /(\w+)/
def initialize(tag_name, markup, options)
super

View File

@@ -15,7 +15,7 @@ module Liquid
end
def nodelist
@blocks.flat_map(&:attachment)
@blocks.map(&:attachment).flatten
end
def unknown_tag(tag, markup, tokens)

View File

@@ -21,7 +21,7 @@ module Liquid
end
def nodelist
@blocks.flat_map(&:attachment)
@blocks.map(&:attachment).flatten
end
def unknown_tag(tag, markup, tokens)
@@ -57,15 +57,15 @@ module Liquid
end
def lax_parse(markup)
expressions = markup.scan(ExpressionsAndOperators)
raise(SyntaxError.new(options[:locale].t("errors.syntax.if".freeze))) unless expressions.pop =~ Syntax
expressions = markup.scan(ExpressionsAndOperators).reverse
raise(SyntaxError.new(options[:locale].t("errors.syntax.if".freeze))) unless expressions.shift =~ Syntax
condition = Condition.new($1, $2, $3)
while not expressions.empty?
operator = expressions.pop.to_s.strip
operator = (expressions.shift).to_s.strip
raise(SyntaxError.new(options[:locale].t("errors.syntax.if".freeze))) unless expressions.pop.to_s =~ Syntax
raise(SyntaxError.new(options[:locale].t("errors.syntax.if".freeze))) unless expressions.shift.to_s =~ Syntax
new_condition = Condition.new($1, $2, $3)
raise(SyntaxError.new(options[:locale].t("errors.syntax.if".freeze))) unless BOOLEAN_OPERATORS.include?(operator)

View File

@@ -4,7 +4,7 @@ module Liquid
def render(context)
context.stack do
output = super
output = render_all(@nodelist, context)
if output != context.registers[:ifchanged]
context.registers[:ifchanged] = output

View File

@@ -69,7 +69,7 @@ module Liquid
return cached
end
source = read_template_from_file_system(context)
partial = Liquid::Template.parse(source, pass_options)
partial = Liquid::Template.parse(source)
cached_partials[template_name] = partial
context.registers[:cached_partials] = cached_partials
partial
@@ -88,16 +88,6 @@ module Liquid
raise ArgumentError, "file_system.read_template_file expects two parameters: (template_name, context)"
end
end
def pass_options
dont_pass = @options[:include_options_blacklist]
return {locale: @options[:locale]} if dont_pass == true
opts = @options.merge(included: true, include_options_blacklist: false)
if dont_pass.is_a?(Array)
dont_pass.each {|o| opts.delete(o)}
end
opts
end
end
Template.register_tag('include'.freeze, Include)

View File

@@ -8,7 +8,10 @@ module Liquid
while token = tokens.shift
if token =~ FullTokenPossiblyInvalid
@nodelist << $1 if $1 != "".freeze
return if block_delimiter == $2
if block_delimiter == $2
end_tag
return
end
end
@nodelist << token if not token.empty?
end

View File

@@ -42,6 +42,7 @@ 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),
@@ -53,7 +54,7 @@ module Liquid
col += 1
result << "<td class=\"col#{col}\">" << super << '</td>'
result << "<td class=\"col#{col}\">" << render_all(@nodelist, context) << '</td>'
if col == cols and (index != length - 1)
col = 0

View File

@@ -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 {% endunless %}
# {% unless x < 0 %} x is greater than zero {% end %}
#
class Unless < If
def render(context)

View File

@@ -60,12 +60,6 @@ module Liquid
# :strict will enforce correct syntax.
attr_writer :error_mode
# Sets how strict the taint checker should be.
# :lax is the default, and ignores the taint flag completely
# :warn adds a warning, but does not interrupt the rendering
# :error raises an error when tainted output is used
attr_writer :taint_mode
def file_system
@@file_system
end
@@ -86,20 +80,12 @@ module Liquid
@error_mode || :lax
end
def taint_mode
@taint_mode || :lax
end
# Pass a module with filter methods which should be available
# to all liquid views. Good for registering the standard library
def register_filter(mod)
Strainer.global_filter(mod)
end
def default_resource_limits
@default_resource_limits ||= {}
end
# creates a new <tt>Template</tt> object from liquid source code
# To enable profiling, pass in <tt>profile: true</tt> as an option.
# See Liquid::Profiler for more information
@@ -109,16 +95,16 @@ module Liquid
end
end
# creates a new <tt>Template</tt> from an array of tokens. Use <tt>Template.parse</tt> instead
def initialize
@resource_limits = self.class.default_resource_limits.dup
@resource_limits = {}
end
# Parse source code.
# Returns self for easy chaining
def parse(source, options = {})
@options = options
@profiling = options[:profile]
@line_numbers = options[:line_numbers] || @profiling
@profiling = options.delete(:profile)
@line_numbers = options.delete(:line_numbers) || @profiling
@root = Document.parse(tokenize(source), DEFAULT_OPTIONS.merge(options))
@warnings = nil
self
@@ -211,7 +197,7 @@ module Liquid
end
result.respond_to?(:join) ? result.join : result
rescue Liquid::MemoryError => e
context.handle_error(e)
context.handle_error(e, nil)
ensure
@errors = context.errors
end
@@ -249,7 +235,7 @@ module Liquid
end
def with_profiling
if @profiling && !@options[:included]
if @profiling
@profiler = Profiler.new
@profiler.start

View File

@@ -30,22 +30,20 @@ module Liquid
end
def markup_context(markup)
"in \"{{#{markup}}}\""
" in \"{{#{markup}}}\""
end
def lax_parse(markup)
@filters = []
if markup =~ /(#{QuotedFragment})(.*)/om
name_markup = $1
filter_markup = $2
@name = Expression.parse(name_markup)
if filter_markup =~ /#{FilterSeparator}\s*(.*)/om
filters = $1.scan(FilterParser)
if markup =~ /\s*(#{QuotedFragment})(.*)/om
@name = Regexp.last_match(1)
if Regexp.last_match(2) =~ /#{FilterSeparator}\s*(.*)/om
filters = Regexp.last_match(1).scan(FilterParser)
filters.each do |f|
if f =~ /\w+/
filtername = Regexp.last_match(0)
filterargs = f.scan(/(?:#{FilterArgumentSeparator}|#{ArgumentSeparator})\s*((?:\w+\s*\:\s*)?#{QuotedFragment})/o).flatten
@filters << parse_filter_expressions(filtername, filterargs)
@filters << [filtername, filterargs]
end
end
end
@@ -55,7 +53,7 @@ module Liquid
def strict_parse(markup)
# Very simple valid cases
if markup =~ EasyParse
@name = Expression.parse($1)
@name = $1
@filters = []
return
end
@@ -63,11 +61,11 @@ module Liquid
@filters = []
p = Parser.new(markup)
# Could be just filters with no input
@name = p.look(:pipe) ? nil : Expression.parse(p.expression)
@name = p.look(:pipe) ? ''.freeze : p.expression
while p.consume?(:pipe)
filtername = p.consume(:id)
filterargs = p.consume?(:colon) ? parse_filterargs(p) : []
@filters << parse_filter_expressions(filtername, filterargs)
@filters << [filtername, filterargs]
end
p.consume(:end_of_string)
end
@@ -83,51 +81,22 @@ module Liquid
end
def render(context)
@filters.inject(context.evaluate(@name)) do |output, (filter_name, filter_args, filter_kwargs)|
filter_args = evaluate_filter_expressions(context, filter_args, filter_kwargs)
output = context.invoke(filter_name, output, *filter_args)
end.tap{ |obj| taint_check(obj) }
end
private
def parse_filter_expressions(filter_name, unparsed_args)
filter_args = []
keyword_args = {}
unparsed_args.each do |a|
if matches = a.match(/\A#{TagAttributes}\z/o)
keyword_args[matches[1]] = Expression.parse(matches[2])
else
filter_args << Expression.parse(a)
return ''.freeze if @name.nil?
@filters.inject(context[@name]) do |output, filter|
filterargs = []
keyword_args = {}
filter[1].to_a.each do |a|
if matches = a.match(/\A#{TagAttributes}\z/o)
keyword_args[matches[1]] = context[matches[2]]
else
filterargs << context[a]
end
end
end
result = [filter_name, filter_args]
result << keyword_args unless keyword_args.empty?
result
end
def evaluate_filter_expressions(context, filter_args, filter_kwargs)
parsed_args = filter_args.map{ |expr| context.evaluate(expr) }
if filter_kwargs
parsed_kwargs = {}
filter_kwargs.each do |key, expr|
parsed_kwargs[key] = context.evaluate(expr)
end
parsed_args << parsed_kwargs
end
parsed_args
end
def taint_check(obj)
if obj.tainted?
@markup =~ QuotedFragment
name = Regexp.last_match(0)
case Template.taint_mode
when :warn
@warnings ||= []
@warnings << "variable '#{name}' is tainted and was not escaped"
when :error
raise TaintedError, "Error - variable '#{name}' is tainted and was not escaped"
filterargs << keyword_args unless keyword_args.empty?
begin
output = context.invoke(filter[0], output, *filterargs)
rescue FilterNotFound
raise FilterNotFound, "Error - filter '#{filter[0]}' in '#{@markup.strip}' could not be found."
end
end
end

View File

@@ -3,8 +3,6 @@ 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
@@ -66,15 +64,5 @@ module Liquid
object
end
def ==(other)
self.class == other.class && self.state == other.state
end
protected
def state
[@name, @lookups, @command_flags]
end
end
end

View File

@@ -1,4 +1,4 @@
# encoding: utf-8
module Liquid
VERSION = "3.0.2"
VERSION = "3.0.0"
end

View File

@@ -4,6 +4,8 @@ class Paginate < Liquid::Block
def initialize(tag_name, markup, options)
super
@nodelist = []
if markup =~ Syntax
@collection_name = $1
@page_size = if $2
@@ -71,7 +73,7 @@ class Paginate < Liquid::Block
end
end
super
render_all(@nodelist, context)
end
end

View File

@@ -3,16 +3,6 @@ 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] }}.',

View File

@@ -7,16 +7,6 @@ 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 = '' %}

View File

@@ -23,10 +23,12 @@ class ContextTest < Minitest::Test
end
def test_has_key_will_not_add_an_error_for_missing_keys
with_error_mode :strict do
context = Context.new
context.has_key?('unknown')
assert_empty context.errors
end
Template.error_mode = :strict
context = Context.new
context.has_key?('unknown')
assert_empty context.errors
end
end

View File

@@ -48,10 +48,6 @@ class ProductDrop < Liquid::Drop
ContextDrop.new
end
def user_input
"foo".taint
end
protected
def callmenot
"protected"
@@ -112,30 +108,6 @@ class DropsTest < Minitest::Test
assert_equal ' ', tpl.render!('product' => ProductDrop.new)
end
def test_rendering_raises_on_tainted_attr
with_taint_mode(:error) do
tpl = Liquid::Template.parse('{{ product.user_input }}')
assert_raises TaintedError do
tpl.render!('product' => ProductDrop.new)
end
end
end
def test_rendering_warns_on_tainted_attr
with_taint_mode(:warn) do
tpl = Liquid::Template.parse('{{ product.user_input }}')
tpl.render!('product' => ProductDrop.new)
assert_match /tainted/, tpl.warnings.first
end
end
def test_rendering_doesnt_raise_on_escaped_tainted_attr
with_taint_mode(:error) do
tpl = Liquid::Template.parse('{{ product.user_input | escape }}')
tpl.render!('product' => ProductDrop.new)
end
end
def test_drop_does_only_respond_to_whitelisted_methods
assert_equal "", Liquid::Template.parse("{{ product.inspect }}").render!('product' => ProductDrop.new)
assert_equal "", Liquid::Template.parse("{{ product.pretty_inspect }}").render!('product' => ProductDrop.new)

View File

@@ -40,13 +40,13 @@ class ErrorHandlingTest < Minitest::Test
expected = <<-TEXT
Hello,
Liquid error (line 3): standard error will raise a standard error.
Liquid error: 3: standard error will raise a standard error.
Bla bla test.
Liquid syntax error (line 7): syntax error will raise a syntax error.
Liquid syntax error: 7: syntax error will raise a syntax error.
This is an argument error: Liquid error (line 9): argument error
This is an argument error: Liquid error: 9: argument error
Bla.
TEXT
@@ -100,99 +100,29 @@ class ErrorHandlingTest < Minitest::Test
assert_equal Liquid::ArgumentError, template.errors.first.class
end
def test_with_line_numbers_adds_numbers_to_parser_errors
err = assert_raises(SyntaxError) do
template = Liquid::Template.parse(%q{
foobar
{% "cat" | foobar %}
bla
},
:line_numbers => true
)
end
assert_match /Liquid syntax error \(line 4\)/, err.message
end
def test_parsing_warn_with_line_numbers_adds_numbers_to_lexer_errors
template = Liquid::Template.parse(%q{
foobar
{% if 1 =! 2 %}ok{% endif %}
bla
},
:error_mode => :warn,
:line_numbers => true
)
assert_equal ['Liquid syntax error (line 4): Unexpected character = in "1 =! 2"'],
template.warnings.map(&:message)
end
def test_parsing_strict_with_line_numbers_adds_numbers_to_lexer_errors
err = assert_raises(SyntaxError) do
Liquid::Template.parse(%q{
foobar
{% if 1 =! 2 %}ok{% endif %}
bla
},
:error_mode => :strict,
:line_numbers => true
)
end
assert_equal 'Liquid syntax error (line 4): Unexpected character = in "1 =! 2"', err.message
end
def test_syntax_errors_in_nested_blocks_have_correct_line_number
err = assert_raises(SyntaxError) do
Liquid::Template.parse(%q{
foobar
{% if 1 != 2 %}
{% foo %}
{% endif %}
bla
},
:line_numbers => true
)
end
assert_equal "Liquid syntax error (line 5): Unknown tag 'foo'", err.message
end
def test_strict_error_messages
err = assert_raises(SyntaxError) do
Liquid::Template.parse(' {% if 1 =! 2 %}ok{% endif %} ', :error_mode => :strict)
end
assert_equal 'Liquid syntax error: Unexpected character = in "1 =! 2"', err.message
assert_equal 'Unexpected character = in "1 =! 2"', err.message
err = assert_raises(SyntaxError) do
Liquid::Template.parse('{{%%%}}', :error_mode => :strict)
end
assert_equal 'Liquid syntax error: Unexpected character % in "{{%%%}}"', err.message
assert_equal 'Unexpected character % in "{{%%%}}"', err.message
end
def test_warnings
template = Liquid::Template.parse('{% if ~~~ %}{{%%%}}{% else %}{{ hello. }}{% endif %}', :error_mode => :warn)
assert_equal 3, template.warnings.size
assert_equal 'Unexpected character ~ in "~~~"', template.warnings[0].to_s(false)
assert_equal 'Unexpected character % in "{{%%%}}"', template.warnings[1].to_s(false)
assert_equal 'Expected id but found end_of_string in "{{ hello. }}"', template.warnings[2].to_s(false)
assert_equal 'Unexpected character ~ in "~~~"', template.warnings[0].message
assert_equal 'Unexpected character % in "{{%%%}}"', template.warnings[1].message
assert_equal 'Expected id but found end_of_string in "{{ hello. }}"', template.warnings[2].message
assert_equal '', template.render
end
def test_warning_line_numbers
template = Liquid::Template.parse("{% if ~~~ %}\n{{%%%}}{% else %}\n{{ hello. }}{% endif %}", :error_mode => :warn, :line_numbers => true)
assert_equal 'Liquid syntax error (line 1): Unexpected character ~ in "~~~"', template.warnings[0].message
assert_equal 'Liquid syntax error (line 2): Unexpected character % in "{{%%%}}"', template.warnings[1].message
assert_equal 'Liquid syntax error (line 3): Expected id but found end_of_string in "{{ hello. }}"', template.warnings[2].message
assert_equal 3, template.warnings.size
assert_equal [1,2,3], template.warnings.map(&:line_number)
end

View File

@@ -25,12 +25,6 @@ end
class FiltersTest < Minitest::Test
include Liquid
module OverrideObjectMethodFilter
def tap(input)
"tap overridden"
end
end
def setup
@context = Context.new
end
@@ -111,13 +105,6 @@ 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

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

@@ -48,18 +48,6 @@ class RenderProfilingTest < Minitest::Test
assert_equal 2, t.profiler[1].line_number
end
def test_profiling_includes_line_numbers_of_included_partials
t = Template.parse("{% include 'a_template' %}", :profile => true)
t.render!
included_children = t.profiler[0].children
# {% assign template_name = 'a_template' %}
assert_equal 1, included_children[0].line_number
# {{ template_name }}
assert_equal 2, included_children[1].line_number
end
def test_profiling_times_the_rendering_of_tokens
t = Template.parse("{% include 'a_template' %}", :profile => true)
t.render!
@@ -72,7 +60,7 @@ class RenderProfilingTest < Minitest::Test
t = Template.parse("{% include 'a_template' %}", :profile => true)
t.render!
assert t.profiler.total_render_time >= 0, "Total render time was not calculated"
assert t.profiler.total_render_time > 0, "Total render time was not calculated"
end
def test_profiling_uses_include_to_mark_children

View File

@@ -118,11 +118,6 @@ class StandardFiltersTest < Minitest::Test
assert_equal '&lt;strong&gt;Hulk&lt;/strong&gt;', @filters.escape_once('&lt;strong&gt;Hulk</strong>')
end
def test_url_encode
assert_equal 'foo%2B1%40example.com', @filters.url_encode('foo+1@example.com')
assert_equal nil, @filters.url_encode(nil)
end
def test_truncatewords
assert_equal 'one two three', @filters.truncatewords('one two three', 4)
assert_equal 'one two...', @filters.truncatewords('one two three', 2)

View File

@@ -10,11 +10,6 @@ class IfElseTagTest < Minitest::Test
assert_template_result(' you rock ?','{% if false %} you suck {% endif %} {% if true %} you rock {% endif %}?')
end
def test_literal_comparisons
assert_template_result(' NO ','{% assign v = false %}{% if v %} YES {% else %} NO {% endif %}')
assert_template_result(' YES ','{% assign v = nil %}{% if v == nil %} YES {% else %} NO {% endif %}')
end
def test_if_else
assert_template_result(' YES ','{% if false %} NO {% else %} YES {% endif %}')
assert_template_result(' YES ','{% if true %} YES {% else %} NO {% endif %}')

View File

@@ -27,9 +27,6 @@ class TestFileSystem
when "pick_a_source"
"from TestFileSystem"
when 'assignments'
"{% assign foo = 'bar' %}"
else
template_path
end
@@ -111,10 +108,6 @@ class IncludeTagTest < Minitest::Test
'echo1' => 'test123', 'more_echos' => { "echo2" => 'test321'}
end
def test_included_templates_assigns_variables
assert_template_result "bar", "{% include 'assignments' %}{{ foo }}"
end
def test_nested_include_tag
assert_template_result "body body_detail", "{% include 'body' %}"
@@ -139,7 +132,7 @@ class IncludeTagTest < Minitest::Test
Liquid::Template.file_system = infinite_file_system.new
assert_raises(Liquid::StackLevelError, SystemStackError) do
assert_raises(Liquid::StackLevelError) do
Template.parse("{% include 'loop' %}").render!
end
@@ -216,19 +209,4 @@ class IncludeTagTest < Minitest::Test
a.render!
assert_empty a.errors
end
def test_passing_options_to_included_templates
assert_raises(Liquid::SyntaxError) do
Template.parse("{% include template %}", error_mode: :strict).render!("template" => '{{ "X" || downcase }}')
end
with_error_mode(:lax) do
assert_equal 'x', Template.parse("{% include template %}", error_mode: :strict, include_options_blacklist: true).render!("template" => '{{ "X" || downcase }}')
end
assert_raises(Liquid::SyntaxError) do
Template.parse("{% include template %}", error_mode: :strict, include_options_blacklist: [:locale]).render!("template" => '{{ "X" || downcase }}')
end
with_error_mode(:lax) do
assert_equal 'x', Template.parse("{% include template %}", error_mode: :strict, include_options_blacklist: [:error_mode]).render!("template" => '{{ "X" || downcase }}')
end
end
end # IncludeTagTest

View File

@@ -135,18 +135,6 @@ class TemplateTest < Minitest::Test
assert t.resource_limits[:render_length_current] > 0
end
def test_default_resource_limits_unaffected_by_render_with_context
context = Context.new
t = Template.parse("{% for a in (1..100) %} {% assign foo = 1 %} {% endfor %}")
t.render!(context)
assert context.resource_limits[:assign_score_current] > 0
assert context.resource_limits[:render_score_current] > 0
assert context.resource_limits[:render_length_current] > 0
refute Template.default_resource_limits.key?(:assign_score_current)
refute Template.default_resource_limits.key?(:render_score_current)
refute Template.default_resource_limits.key?(:render_length_current)
end
def test_can_use_drop_as_context
t = Template.new
t.registers['lulz'] = 'haha'

View File

@@ -31,12 +31,6 @@ class VariableTest < Minitest::Test
def test_false_renders_as_false
assert_equal 'false', Template.parse("{{ foo }}").render!('foo' => false)
assert_equal 'false', Template.parse("{{ false }}").render!
end
def test_nil_renders_as_empty_string
assert_equal '', Template.parse("{{ nil }}").render!
assert_equal 'cat', Template.parse("{{ nil | append: 'cat' }}").render!
end
def test_preset_assigns

View File

@@ -1,6 +1,5 @@
#!/usr/bin/env ruby
ENV["MT_NO_EXPECTATIONS"] = "1"
require 'minitest/autorun'
require 'spy/integration'
@@ -49,27 +48,13 @@ module Minitest
end
def with_global_filter(*globals)
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
original_filters = Array.new(Liquid::Strainer.class_variable_get(:@@filters))
globals.each do |global|
Liquid::Template.register_filter(global)
end
yield
ensure
Liquid::Strainer.class_variable_get(:@@strainer_class_cache).clear
Liquid::Strainer.class_variable_set(:@@global_strainer, original_global_strainer)
end
def with_taint_mode(mode)
old_mode = Liquid::Template.taint_mode
Liquid::Template.taint_mode = mode
yield
ensure
Liquid::Template.taint_mode = old_mode
Liquid::Strainer.class_variable_set(:@@filters, original_filters)
end
def with_error_mode(mode)

View File

@@ -80,10 +80,6 @@ class ConditionUnitTest < Minitest::Test
assert_evalutes_false "0", 'contains', 'not_assigned'
end
def test_contains_return_false_on_wrong_data_type
assert_evalutes_false "1", 'contains', '0'
end
def test_or_condition
condition = Condition.new('1', '==', '2')

View File

@@ -31,11 +31,11 @@ class StrainerUnitTest < Minitest::Test
def test_strainer_only_invokes_public_filter_methods
strainer = Strainer.create(nil)
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
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
end
def test_strainer_returns_nil_if_no_filter_method_found
@@ -57,13 +57,14 @@ class StrainerUnitTest < Minitest::Test
end
def test_strainer_uses_a_class_cache_to_avoid_method_cache_invalidation
a = Module.new
b = Module.new
a, b = Module.new, Module.new
strainer = Strainer.create(nil, [a,b])
assert_kind_of Strainer, strainer
assert_kind_of a, strainer
assert_kind_of b, strainer
assert_kind_of Liquid::StandardFilters, strainer
Strainer.class_variable_get(:@@filters).each do |m|
assert_kind_of m, strainer
end
end
end # StrainerTest

View File

@@ -5,123 +5,125 @@ class VariableUnitTest < Minitest::Test
def test_variable
var = Variable.new('hello')
assert_equal VariableLookup.new('hello'), var.name
assert_equal 'hello', var.name
end
def test_filters
var = Variable.new('hello | textileze')
assert_equal VariableLookup.new('hello'), var.name
assert_equal [['textileze',[]]], var.filters
assert_equal 'hello', var.name
assert_equal [["textileze",[]]], var.filters
var = Variable.new('hello | textileze | paragraph')
assert_equal VariableLookup.new('hello'), var.name
assert_equal [['textileze',[]], ['paragraph',[]]], var.filters
assert_equal 'hello', var.name
assert_equal [["textileze",[]], ["paragraph",[]]], var.filters
var = Variable.new(%! hello | strftime: '%Y'!)
assert_equal VariableLookup.new('hello'), var.name
assert_equal [['strftime',['%Y']]], var.filters
assert_equal 'hello', var.name
assert_equal [["strftime",["'%Y'"]]], var.filters
var = Variable.new(%! 'typo' | link_to: 'Typo', true !)
assert_equal 'typo', var.name
assert_equal [['link_to',['Typo', true]]], var.filters
assert_equal %!'typo'!, var.name
assert_equal [["link_to",["'Typo'", "true"]]], var.filters
var = Variable.new(%! 'typo' | link_to: 'Typo', false !)
assert_equal 'typo', var.name
assert_equal [['link_to',['Typo', false]]], var.filters
assert_equal %!'typo'!, var.name
assert_equal [["link_to",["'Typo'", "false"]]], var.filters
var = Variable.new(%! 'foo' | repeat: 3 !)
assert_equal 'foo', var.name
assert_equal [['repeat',[3]]], var.filters
assert_equal %!'foo'!, var.name
assert_equal [["repeat",["3"]]], var.filters
var = Variable.new(%! 'foo' | repeat: 3, 3 !)
assert_equal 'foo', var.name
assert_equal [['repeat',[3,3]]], var.filters
assert_equal %!'foo'!, var.name
assert_equal [["repeat",["3","3"]]], var.filters
var = Variable.new(%! 'foo' | repeat: 3, 3, 3 !)
assert_equal 'foo', var.name
assert_equal [['repeat',[3,3,3]]], var.filters
assert_equal %!'foo'!, var.name
assert_equal [["repeat",["3","3","3"]]], var.filters
var = Variable.new(%! hello | strftime: '%Y, okay?'!)
assert_equal VariableLookup.new('hello'), var.name
assert_equal [['strftime',['%Y, okay?']]], var.filters
assert_equal 'hello', var.name
assert_equal [["strftime",["'%Y, okay?'"]]], var.filters
var = Variable.new(%! hello | things: "%Y, okay?", 'the other one'!)
assert_equal VariableLookup.new('hello'), var.name
assert_equal [['things',['%Y, okay?','the other one']]], var.filters
assert_equal 'hello', var.name
assert_equal [["things",["\"%Y, okay?\"","'the other one'"]]], var.filters
end
def test_filter_with_date_parameter
var = Variable.new(%! '2006-06-06' | date: "%m/%d/%Y"!)
assert_equal '2006-06-06', var.name
assert_equal [['date',['%m/%d/%Y']]], var.filters
assert_equal "'2006-06-06'", var.name
assert_equal [["date",["\"%m/%d/%Y\""]]], var.filters
end
def test_filters_without_whitespace
var = Variable.new('hello | textileze | paragraph')
assert_equal VariableLookup.new('hello'), var.name
assert_equal [['textileze',[]], ['paragraph',[]]], var.filters
assert_equal 'hello', var.name
assert_equal [["textileze",[]], ["paragraph",[]]], var.filters
var = Variable.new('hello|textileze|paragraph')
assert_equal VariableLookup.new('hello'), var.name
assert_equal [['textileze',[]], ['paragraph',[]]], var.filters
assert_equal 'hello', var.name
assert_equal [["textileze",[]], ["paragraph",[]]], var.filters
var = Variable.new("hello|replace:'foo','bar'|textileze")
assert_equal VariableLookup.new('hello'), var.name
assert_equal [['replace', ['foo', 'bar']], ['textileze', []]], var.filters
assert_equal 'hello', var.name
assert_equal [["replace", ["'foo'", "'bar'"]], ["textileze", []]], var.filters
end
def test_symbol
var = Variable.new("http://disney.com/logo.gif | image: 'med' ", :error_mode => :lax)
assert_equal VariableLookup.new('http://disney.com/logo.gif'), var.name
assert_equal [['image',['med']]], var.filters
assert_equal "http://disney.com/logo.gif", var.name
assert_equal [["image",["'med'"]]], var.filters
end
def test_string_to_filter
var = Variable.new("'http://disney.com/logo.gif' | image: 'med' ")
assert_equal 'http://disney.com/logo.gif', var.name
assert_equal [['image',['med']]], var.filters
assert_equal "'http://disney.com/logo.gif'", var.name
assert_equal [["image",["'med'"]]], var.filters
end
def test_string_single_quoted
var = Variable.new(%| "hello" |)
assert_equal 'hello', var.name
assert_equal '"hello"', var.name
end
def test_string_double_quoted
var = Variable.new(%| 'hello' |)
assert_equal 'hello', var.name
assert_equal "'hello'", var.name
end
def test_integer
var = Variable.new(%| 1000 |)
assert_equal 1000, var.name
assert_equal "1000", var.name
end
def test_float
var = Variable.new(%| 1000.01 |)
assert_equal 1000.01, var.name
assert_equal "1000.01", var.name
end
def test_string_with_special_chars
var = Variable.new(%| 'hello! $!@.;"ddasd" ' |)
assert_equal 'hello! $!@.;"ddasd" ', var.name
assert_equal %|'hello! $!@.;"ddasd" '|, var.name
end
def test_string_dot
var = Variable.new(%| test.test |)
assert_equal VariableLookup.new('test.test'), var.name
assert_equal 'test.test', var.name
end
def test_filter_with_keyword_arguments
var = Variable.new(%! hello | things: greeting: "world", farewell: 'goodbye'!)
assert_equal VariableLookup.new('hello'), var.name
assert_equal [['things', [], { 'greeting' => 'world', 'farewell' => 'goodbye' }]], var.filters
assert_equal 'hello', var.name
assert_equal [['things',["greeting: \"world\"","farewell: 'goodbye'"]]], var.filters
end
def test_lax_filter_argument_parsing
var = Variable.new(%! number_of_comments | pluralize: 'comment': 'comments' !, :error_mode => :lax)
assert_equal VariableLookup.new('number_of_comments'), var.name
assert_equal [['pluralize',['comment','comments']]], var.filters
assert_equal 'number_of_comments', var.name
assert_equal [['pluralize',["'comment'","'comments'"]]], var.filters
end
def test_strict_filter_argument_parsing
@@ -136,10 +138,4 @@ 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