Compare commits

..

6 Commits

Author SHA1 Message Date
Arthur Neves
a41a60d294 Bump version to rc1 2013-10-10 10:22:35 -04:00
Florian Weingarten
307cab2eaa History and gemspec
Conflicts:
	History.md
	liquid.gemspec
2013-10-09 17:04:47 -04:00
Arthur Neves
a64360d148 Update history
Conflicts:
	History.md
2013-10-09 16:33:44 -04:00
Florian Weingarten
6cb5a9b7cc Merge pull request #234 from Shopify/fix_mapping_procs
Fix mapping over procs
2013-10-09 16:32:53 -04:00
Florian Weingarten
4207d1f086 Merge pull request #232 from Shopify/to_liquid_stuff
Always call 'to_liquid' on stuff in map filter and allow to_liquid to be...
2013-10-09 16:32:45 -04:00
Florian Weingarten
988a1694fd Use invoke_drop in map filter 2013-10-09 16:32:38 -04:00
45 changed files with 124 additions and 1064 deletions

View File

@@ -3,16 +3,9 @@
IMPORTANT: Liquid 2.6 is going to be the last version of Liquid which maintains explicit Ruby 1.8 compatability.
The following releases will only be tested against Ruby 1.9 and Ruby 2.0 and are likely to break on Ruby 1.8.
## 2.6.0 / Master branch (not yet released)
## 2.6.0 (not yet released)
* ...
* Add optional strict parsing and warn parsing, see #235 [Tristan Hume, trishume]
* Add I18n syntax error translation, see #241 [Simon Hørup Eskildsen, Sirupsen]
* Make sort filter work on enumerable drops, see #239 [Florian Weingarten, fw42]
* Fix clashing method names in enumerable drops, see #238 [Florian Weingarten, fw42]
* Make map filter work on enumerable drops, see #233 [Florian Weingarten, fw42]
* Fix security issue with map filter, see #230, #232, #234, #237 [Florian Weingarten, fw42]
* Improved whitespace stripping for blank blocks, related to #216 [Florian Weingarten, fw42]
* Bugfix for #106: fix example servlet [gnowoel]
* Bugfix for #97: strip_html filter supports multi-line tags [Jo Liss, joliss]
* Bugfix for #114: strip_html filter supports style tags [James Allardice, jamesallardice]
@@ -31,6 +24,17 @@ The following releases will only be tested against Ruby 1.9 and Ruby 2.0 and are
* Better documentation for 'include' tag (closes #163) [Peter Schröder, phoet]
* Use of BigDecimal on filters to have better precision (closes #155) [Arthur Nogueira Neves, arthurnn]
## 2.5.3 / branch "2.5-stable"
* #232, #234, #237: Fix map filter bugs [Florian Weingarten, fw42]
## 2.5.2 / 2013-09-03 / deleted
Yanked from rubygems, as it contained too many changes that broke compatibility. Those changes will be on following major releases.
## 2.5.1 / 2013-07-24
* #230: Fix security issue with map filter, Use invoke_drop in map filter [Florian Weingarten, fw42]
## 2.5.0 / 2013-03-06

View File

@@ -4,7 +4,6 @@
* [Version history](History.md)
* [Liquid documentation from Shopify](http://docs.shopify.com/themes/liquid-basics)
* [Liquid Wiki from Shopify](http://wiki.shopify.com/Liquid)
* [Liquid Wiki at GitHub](https://github.com/Shopify/liquid/wiki)
* [Website](http://liquidmarkup.org/)
## Introduction
@@ -48,28 +47,4 @@ For standard use you can just pass it the content of a file and call render with
@template.render('name' => 'tobi') # => "hi tobi"
```
### Error Modes
Setting the error mode of Liquid lets you specify how strictly you want your templates to be interpreted.
Normally the parser is very lax and will accept almost anything without error. Unfortunately this can make
it very hard to debug and can lead to unexpected behaviour.
Liquid also comes with a stricter parser that can be used when editing templates to give better error messages
when templates are invalid. You can enable this new parser like this:
```ruby
Liquid::Template.error_mode = :strict # Raises a SyntaxError when invalid syntax is used
Liquid::Template.error_mode = :warn # Adds errors to template.errors but continues as normal
Liquid::Template.error_mode = :lax # The default mode, accepts almost anything.
```
If you want to set the error mode only on specific templates you can pass `:error_mode` as an option to `parse`:
```ruby
Liquid::Template.parse(source, :error_mode => :strict)
```
This is useful for doing things like enabling strict mode only in the theme editor.
It is recommended that you enable `:strict` or `:warn` mode on new apps to stop invalid templates from being created.
It is also recommended that you use it in the template editors of existing apps to give editors better error messages.
[![Build Status](https://secure.travis-ci.org/Shopify/liquid.png)](http://travis-ci.org/Shopify/liquid)

View File

@@ -7,28 +7,12 @@ require 'rubygems/package_task'
task :default => 'test'
desc 'run test suite with default parser'
Rake::TestTask.new(:base_test) do |t|
Rake::TestTask.new(:test) do |t|
t.libs << '.' << 'lib' << 'test'
t.test_files = FileList['test/liquid/**/*_test.rb']
t.verbose = false
end
desc 'run test suite with warn error mode'
task :warn_test do
ENV['LIQUID_PARSER_MODE'] = 'warn'
Rake::Task['base_test'].invoke
end
desc 'runs test suite with both strict and lax parsers'
task :test do
ENV['LIQUID_PARSER_MODE'] = 'lax'
Rake::Task['base_test'].invoke
ENV['LIQUID_PARSER_MODE'] = 'strict'
Rake::Task['base_test'].reenable
Rake::Task['base_test'].invoke
end
gemspec = eval(File.read('liquid.gemspec'))
Gem::PackageTask.new(gemspec) do |pkg|
pkg.gem_spec = gemspec
@@ -41,15 +25,11 @@ end
namespace :benchmark do
desc "Run the liquid benchmark with lax parsing"
desc "Run the liquid benchmark"
task :run do
ruby "./performance/benchmark.rb lax"
ruby "./performance/benchmark.rb"
end
desc "Run the liquid benchmark with strict parsing"
task :strict do
ruby "./performance/benchmark.rb strict"
end
end

View File

@@ -46,9 +46,6 @@ module Liquid
end
require "liquid/version"
require 'liquid/lexer'
require 'liquid/parser'
require 'liquid/i18n'
require 'liquid/drop'
require 'liquid/extensions'
require 'liquid/errors'

View File

@@ -1,23 +1,17 @@
module Liquid
class Block < Tag
IsTag = /^#{TagStart}/o
IsVariable = /^#{VariableStart}/o
FullToken = /^#{TagStart}\s*(\w+)\s*(.*)?#{TagEnd}$/o
ContentOfVariable = /^#{VariableStart}(.*)#{VariableEnd}$/o
def blank?
@blank || false
end
def parse(tokens)
@blank = true
@nodelist ||= []
@nodelist.clear
# All child tags of the current block.
@children = []
while token = tokens.shift
case token
when IsTag
if token =~ FullToken
@@ -31,28 +25,21 @@ module Liquid
# fetch the tag from registered blocks
if tag = Template.tags[$1]
new_tag = tag.new_with_options($1, $2, tokens, @options || {})
@blank &&= new_tag.blank?
@nodelist << new_tag
@children << new_tag
@nodelist << tag.new($1, $2, tokens)
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", :token => token, :tag_end => TagEnd.inspect))
raise SyntaxError, "Tag '#{token}' was not properly terminated with regexp: #{TagEnd.inspect} "
end
when IsVariable
new_var = create_variable(token)
@nodelist << new_var
@children << new_var
@blank = false
@nodelist << create_variable(token)
when ''
# pass
else
@nodelist << token
@blank &&= (token =~ /\A\s*\z/)
end
end
@@ -62,32 +49,17 @@ module Liquid
assert_missing_delimitation!
end
# warnings of this block and all sub-tags
def warnings
all_warnings = []
all_warnings.concat(@warnings) if @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'
raise SyntaxError.new(options[:locale].t("errors.syntax.unexpected_else",
:block_name => block_name))
raise SyntaxError, "#{block_name} tag does not expect else tag"
when 'end'
raise SyntaxError.new(options[:locale].t("errors.syntax.invalid_delimiter",
:block_name => block_name,
:block_delimiter => block_delimiter))
raise SyntaxError, "'end' is not a valid delimiter for #{block_name} tags. use #{block_delimiter}"
else
raise SyntaxError.new(options[:locale].t("errors.syntax.unknown_tag", :tag => tag))
raise SyntaxError, "Unknown tag '#{tag}'"
end
end
@@ -101,9 +73,9 @@ module Liquid
def create_variable(token)
token.scan(ContentOfVariable) do |content|
return Variable.new(content.first, @options)
return Variable.new(content.first)
end
raise SyntaxError.new(options[:locale].t("errors.syntax.variable_termination", :token => token, :tag_end => VariableEnd.inspect))
raise SyntaxError.new("Variable '#{token}' was not properly terminated with regexp: #{VariableEnd.inspect} ")
end
def render(context)
@@ -113,7 +85,7 @@ module Liquid
protected
def assert_missing_delimitation!
raise SyntaxError.new(options[:locale].t("errors.syntax.tag_never_closed", :block_name => block_name))
raise SyntaxError.new("#{block_name} tag was never closed")
end
def render_all(list, context)
@@ -140,9 +112,7 @@ module Liquid
context.resource_limits[:reached] = true
raise MemoryError.new("Memory limits exceeded")
end
unless token.is_a?(Block) && token.blank?
output << token_output
end
output << token_output
rescue MemoryError => e
raise e
rescue ::StandardError => e

View File

@@ -1,8 +1,7 @@
module Liquid
class Document < Block
# we don't need markup to open this block
def initialize(tokens, options = {})
@options = options
def initialize(tokens)
parse(tokens)
end

View File

@@ -44,10 +44,6 @@ module Liquid
true
end
def inspect
self.class.to_s
end
def to_liquid
self
end
@@ -58,16 +54,7 @@ module Liquid
# Check for method existence without invoking respond_to?, which creates symbols
def self.invokable?(method_name)
unless @invokable_methods
# Ruby 1.8 compatibility: call to_s on method names (which are strings in 1.8, but already symbols in 1.9)
blacklist = (Liquid::Drop.public_instance_methods + [:each]).map(&:to_s)
if include?(Enumerable)
blacklist += Enumerable.public_instance_methods.map(&:to_s)
blacklist -= [:sort, :count, :first, :min, :max, :include?].map(&:to_s)
end
whitelist = [:to_liquid] + (public_instance_methods.map(&:to_s) - blacklist.map(&:to_s))
@invokable_methods = Set.new(whitelist.map(&:to_s))
end
@invokable_methods ||= Set.new(["to_liquid"] + (public_instance_methods - Liquid::Drop.public_instance_methods).map(&:to_s))
@invokable_methods.include?(method_name.to_s)
end
end

View File

@@ -11,7 +11,7 @@ module Liquid
@attributes[key] = value
end
else
raise SyntaxError.new(options[:locale].t("errors.syntax.table_row"))
raise SyntaxError.new("Syntax Error in 'table_row loop' - Valid syntax: table_row [item] in [collection] cols=3")
end
super

View File

@@ -1,39 +0,0 @@
require 'yaml'
module Liquid
class I18n
DEFAULT_LOCALE = File.join(File.expand_path(File.dirname(__FILE__)), "locales", "en.yml")
class TranslationError < StandardError
end
attr_reader :path
def initialize(path = DEFAULT_LOCALE)
@path = path
end
def translate(name, vars = {})
interpolate(deep_fetch_translation(name), vars)
end
alias_method :t, :translate
def locale
@locale ||= YAML.load_file(@path)
end
private
def interpolate(name, vars)
name.gsub(/%{(\w+)}/) {
# raise TranslationError, "Undefined key #{$1} for interpolation in translation #{name}" unless vars[$1.to_sym]
"#{vars[$1.to_sym]}"
}
end
def deep_fetch_translation(name)
name.split('.').reduce(locale) do |level, cur|
level[cur] or raise TranslationError, "Translation for #{name} does not exist in locale #{path}"
end
end
end
end

View File

@@ -1,49 +0,0 @@
require "strscan"
module Liquid
class Lexer
SPECIALS = {
'|' => :pipe,
'.' => :dot,
':' => :colon,
',' => :comma,
'[' => :open_square,
']' => :close_square,
'(' => :open_round,
')' => :close_round
}
IDENTIFIER = /[\w\-?!]+/
SINGLE_STRING_LITERAL = /'[^\']*'/
DOUBLE_STRING_LITERAL = /"[^\"]*"/
NUMBER_LITERAL = /-?\d+(\.\d+)?/
COMPARISON_OPERATOR = /==|!=|<>|<=?|>=?|contains/
def initialize(input)
@ss = StringScanner.new(input.rstrip)
end
def tokenize
@output = []
while !@ss.eos?
@ss.skip(/\s*/)
tok = case
when t = @ss.scan(COMPARISON_OPERATOR) then [:comparison, t]
when t = @ss.scan(SINGLE_STRING_LITERAL) then [:string, t]
when t = @ss.scan(DOUBLE_STRING_LITERAL) then [:string, t]
when t = @ss.scan(NUMBER_LITERAL) then [:number, t]
when t = @ss.scan(IDENTIFIER) then [:id, t]
else
c = @ss.getch
if s = SPECIALS[c]
[s,c]
else
raise SyntaxError, "Unexpected character #{c}"
end
end
@output << tok
end
@output << [:end_of_string]
end
end
end

View File

@@ -1,22 +0,0 @@
---
errors:
syntax:
assign: "Syntax Error in 'assign' - Valid syntax: assign [var] = [source]"
capture: "Syntax Error in 'capture' - Valid syntax: capture [var]"
case: "Syntax Error in 'case' - Valid syntax: case [condition]"
case_invalid_when: "Syntax Error in tag 'case' - Valid when condition: {% when [condition] [or condition2...] %}"
case_invalid_else: "Syntax Error in tag 'case' - Valid else condition: {% else %} (no parameters) "
cycle: "Syntax Error in 'cycle' - Valid syntax: cycle [name :] var [, var2, var3 ...]"
for: "Syntax Error in 'for loop' - Valid syntax: for [item] in [collection]"
for_invalid_in: "For loops require an 'in' clause"
for_invalid_attribute: "Invalid attribute in for loop. Valid attributes are limit and offset"
if: "Syntax Error in tag 'if' - Valid syntax: if [expression]"
include: "Error in tag 'include' - Valid syntax: include '[template]' (with|for) [object|collection]"
unknown_tag: "Unknown tag '%{tag}'"
invalid_delimiter: "'end' is not a valid delimiter for %{block_name} tags. use %{block_delimiter}"
unexpected_else: "%{block_name} tag does not expect else tag"
tag_termination: "Tag '%{token}' was not properly terminated with regexp: %{tag_end}"
variable_termination: "Variable '%{token}' was not properly terminated with regexp: %{tag_end}"
tag_never_closed: "'%{block_name}' tag was never closed"
meta_syntax_error: "Liquid syntax error: #{e.message}"
table_row: "Syntax Error in 'table_row loop' - Valid syntax: table_row [item] in [collection] cols=3"

View File

@@ -1,90 +0,0 @@
module Liquid
class Parser
def initialize(input)
l = Lexer.new(input)
@tokens = l.tokenize
@p = 0 # pointer to current location
end
def jump(point)
@p = point
end
def consume(type = nil)
token = @tokens[@p]
if type && token[0] != type
raise SyntaxError, "Expected #{type} but found #{@tokens[@p].first}"
end
@p += 1
token[1]
end
# Only consumes the token if it matches the type
# Returns the token's contents if it was consumed
# or false otherwise.
def consume?(type)
token = @tokens[@p]
return false unless token && token[0] == type
@p += 1
token[1]
end
# Like consume? Except for an :id token of a certain name
def id?(str)
token = @tokens[@p]
return false unless token && token[0] == :id
return false unless token[1] == str
@p += 1
token[1]
end
def look(type, ahead = 0)
tok = @tokens[@p + ahead]
return false unless tok
tok[0] == type
end
def expression
token = @tokens[@p]
if token[0] == :id
variable_signature
elsif [:string, :number].include? token[0]
consume
elsif token.first == :open_round
consume
first = expression
consume(:dot)
consume(:dot)
last = expression
consume(:close_round)
"(#{first}..#{last})"
else
raise SyntaxError, "#{token} is not a valid expression"
end
end
def argument
str = ""
# might be a keyword argument (identifier: expression)
if look(:id) && look(:colon, 1)
str << consume << consume << ' '
end
str << expression
end
def variable_signature
str = consume(:id)
if look(:open_square)
str << consume
str << expression
str << consume(:close_square)
end
if look(:dot)
str << consume
str << variable_signature
end
str
end
end
end

View File

@@ -81,7 +81,7 @@ module Liquid
# Sort elements of the array
# provide optional property with which to sort an array of hashes or drops
def sort(input, property = nil)
ary = flatten_if_necessary(input)
ary = [input].flatten
if property.nil?
ary.sort
elsif ary.first.respond_to?('[]') and !ary.first[property].nil?
@@ -99,8 +99,10 @@ module Liquid
# map/collect on a given property
def map(input, property)
flatten_if_necessary(input).map do |e|
ary = [input].flatten
ary.map do |e|
e = e.call if e.is_a?(Proc)
e = e.to_liquid if e.respond_to?(:to_liquid)
if property == "to_liquid"
e
@@ -247,17 +249,6 @@ module Liquid
private
def flatten_if_necessary(input)
ary = if input.is_a?(Array)
input.flatten
elsif input.kind_of?(Enumerable)
input
else
[input].flatten
end
ary.map{ |e| e.respond_to?(:to_liquid) ? e.to_liquid : e }
end
def to_number(obj)
case obj
when Float

View File

@@ -1,21 +1,12 @@
module Liquid
class Tag
attr_accessor :nodelist, :options
attr_reader :warnings
def self.new_with_options(tag_name, markup, tokens, options)
# Forgive me Matz for I have sinned. I know this code is weird
# but it was necessary to maintain API compatibility.
new_tag = self.allocate
new_tag.options = options
new_tag.send(:initialize, tag_name, markup, tokens)
new_tag
end
class Tag
attr_accessor :nodelist
def initialize(tag_name, markup, tokens)
@tag_name = tag_name
@markup = markup
@options ||= {} # needs || because might be set before initialize
parse(tokens)
end
@@ -30,31 +21,6 @@ module Liquid
''
end
def blank?
@blank || true
end
def parse_with_selected_parser(markup)
case @options[:error_mode] || Template.error_mode
when :strict then strict_parse_with_error_context(markup)
when :lax then lax_parse(markup)
when :warn
begin
return strict_parse_with_error_context(markup)
rescue SyntaxError => e
@warnings ||= []
@warnings << e
return lax_parse(markup)
end
end
end
private
def strict_parse_with_error_context(markup)
strict_parse(markup)
rescue SyntaxError => e
e.message << " in \"#{markup.strip}\""
raise e
end
end # Tag
end # Liquid

View File

@@ -16,7 +16,7 @@ module Liquid
@to = $1
@from = Variable.new($2)
else
raise SyntaxError.new options[:locale].t("errors.syntax.assign")
raise SyntaxError.new("Syntax Error in 'assign' - Valid syntax: assign [var] = [source]")
end
super

View File

@@ -18,7 +18,7 @@ module Liquid
if markup =~ Syntax
@to = $1
else
raise SyntaxError.new(options[:locale].t("errors.syntax.capture"))
raise SyntaxError.new("Syntax Error in 'capture' - Valid syntax: capture [var]")
end
super
@@ -30,10 +30,6 @@ module Liquid
context.resource_limits[:assign_score_current] += (output.respond_to?(:length) ? output.length : 1)
''
end
def blank?
true
end
end
Template.register_tag('capture', Capture)

View File

@@ -9,7 +9,7 @@ module Liquid
if markup =~ Syntax
@left = $1
else
raise SyntaxError.new(options[:locale].t("errors.syntax.case"))
raise SyntaxError.new("Syntax Error in tag 'case' - Valid syntax: case [condition]")
end
super
@@ -50,7 +50,7 @@ module Liquid
while markup
# Create a new nodelist and assign it to the new block
if not markup =~ WhenSyntax
raise SyntaxError.new(options[:locale].t("errors.syntax.case_invalid_when"))
raise SyntaxError.new("Syntax Error in tag 'case' - Valid when condition: {% when [condition] [or condition2...] %} ")
end
markup = $2
@@ -62,14 +62,17 @@ module Liquid
end
def record_else_condition(markup)
if not markup.strip.empty?
raise SyntaxError.new(options[:locale].t("errors.syntax.case_invalid_else"))
raise SyntaxError.new("Syntax Error in tag 'case' - Valid else condition: {% else %} (no parameters) ")
end
block = ElseCondition.new
block.attach(@nodelist)
@blocks << block
end
end
Template.register_tag('case', Case)

View File

@@ -3,10 +3,6 @@ module Liquid
def render(context)
''
end
def blank?
true
end
end
Template.register_tag('comment', Comment)

View File

@@ -1,4 +1,5 @@
module Liquid
# Cycle is usually used within a loop to alternate between values, like colors or DOM classes.
#
# {% for item in items %}
@@ -24,7 +25,7 @@ module Liquid
@variables = variables_from_string(markup)
@name = "'#{@variables.to_s}'"
else
raise SyntaxError.new(options[:locale].t("errors.syntax.cycle"))
raise SyntaxError.new("Syntax Error in 'cycle' - Valid syntax: cycle [name :] var [, var2, var3 ...]")
end
super
end
@@ -43,17 +44,15 @@ module Liquid
end
end
def blank?
false
end
private
def variables_from_string(markup)
markup.split(',').collect do |var|
var =~ /\s*(#{QuotedFragment})\s*/o
$1 ? $1 : nil
end.compact
end
end
Template.register_tag('cycle', Cycle)

View File

@@ -44,10 +44,22 @@ module Liquid
# forloop.last:: Returns true if the item is the last item.
#
class For < Block
Syntax = /\A(#{VariableSegment}+)\s+in\s+(#{QuotedFragment}+)\s*(reversed)?/o
Syntax = /\A(\w+)\s+in\s+(#{QuotedFragment}+)\s*(reversed)?/o
def initialize(tag_name, markup, tokens)
parse_with_selected_parser(markup)
if markup =~ Syntax
@variable_name = $1
@collection_name = $2
@name = "#{$1}-#{$2}"
@reversed = $3
@attributes = {}
markup.scan(TagAttributes) do |key, value|
@attributes[key] = value
end
else
raise SyntaxError.new("Syntax Error in 'for loop' - Valid syntax: for [item] in [collection]")
end
@nodelist = @for_block = []
super
end
@@ -115,43 +127,6 @@ module Liquid
result
end
protected
def lax_parse(markup)
if markup =~ Syntax
@variable_name = $1
@collection_name = $2
@name = "#{$1}-#{$2}"
@reversed = $3
@attributes = {}
markup.scan(TagAttributes) do |key, value|
@attributes[key] = value
end
else
raise SyntaxError.new(options[:locale].t("errors.syntax.for"))
end
end
def strict_parse(markup)
p = Parser.new(markup)
@variable_name = p.consume(:id)
raise SyntaxError.new(options[:locale].t("errors.syntax.for_invalid_in")) unless p.id?('in')
@collection_name = p.expression
@name = "#{@variable_name}-#{@collection_name}"
@reversed = p.id?('reversed')
@attributes = {}
while p.look(:id) && p.look(:colon, 1)
unless attribute = p.id?('limit') || p.id?('offset')
raise SyntaxError.new(options[:locale].t("errors.syntax.for_invalid_attribute"))
end
p.consume
val = p.expression
@attributes[attribute] = val
end
p.consume(:end_of_string)
end
private
def render_else(context)

View File

@@ -1,4 +1,5 @@
module Liquid
# If is the conditional block
#
# {% if user.admin %}
@@ -9,13 +10,17 @@ module Liquid
#
# There are {% if count < 5 %} less {% else %} more {% endif %} items than you need.
#
#
class If < Block
SyntaxHelp = "Syntax Error in tag 'if' - Valid syntax: if [expression]"
Syntax = /(#{QuotedFragment})\s*([=!<>a-z_]+)?\s*(#{QuotedFragment})?/o
ExpressionsAndOperators = /(?:\b(?:\s?and\s?|\s?or\s?)\b|(?:\s*(?!\b(?:\s?and\s?|\s?or\s?)\b)(?:#{QuotedFragment}|\S+)\s*)+)/o
def initialize(tag_name, markup, tokens)
@blocks = []
push_block('if', markup)
super
end
@@ -44,56 +49,30 @@ module Liquid
block = if tag == 'else'
ElseCondition.new
else
parse_with_selected_parser(markup)
expressions = markup.scan(ExpressionsAndOperators).reverse
raise(SyntaxError, SyntaxHelp) unless expressions.shift =~ Syntax
condition = Condition.new($1, $2, $3)
while not expressions.empty?
operator = (expressions.shift).to_s.strip
raise(SyntaxError, SyntaxHelp) unless expressions.shift.to_s =~ Syntax
new_condition = Condition.new($1, $2, $3)
new_condition.send(operator.to_sym, condition)
condition = new_condition
end
condition
end
@blocks.push(block)
@nodelist = block.attach(Array.new)
end
def lax_parse(markup)
expressions = markup.scan(ExpressionsAndOperators).reverse
raise(SyntaxError.new(options[:locale].t("errors.syntax.if"))) unless expressions.shift =~ Syntax
condition = Condition.new($1, $2, $3)
while not expressions.empty?
operator = (expressions.shift).to_s.strip
raise(SyntaxError.new(options[:locale].t("errors.syntax.if"))) unless expressions.shift.to_s =~ Syntax
new_condition = Condition.new($1, $2, $3)
new_condition.send(operator.to_sym, condition)
condition = new_condition
end
condition
end
def strict_parse(markup)
p = Parser.new(markup)
condition = parse_comparison(p)
while op = (p.id?('and') || p.id?('or'))
new_cond = parse_comparison(p)
new_cond.send(op, condition)
condition = new_cond
end
p.consume(:end_of_string)
condition
end
def parse_comparison(p)
a = p.expression
if op = p.consume?(:comparison)
b = p.expression
Condition.new(a, op, b)
else
Condition.new(a)
end
end
end
Template.register_tag('if', If)

View File

@@ -29,7 +29,7 @@ module Liquid
end
else
raise SyntaxError.new(options[:locale].t("errors.syntax.include"))
raise SyntaxError.new("Error in tag 'include' - Valid syntax: include '[template]' (with|for) [object|collection]")
end
super
@@ -38,10 +38,6 @@ module Liquid
def parse(tokens)
end
def blank?
false
end
def render(context)
partial = load_cached_partial(context)
variable = context[@variable_name || @template_name[1..-2]]

View File

@@ -1,11 +1,12 @@
module Liquid
# increment is used in a place where one needs to insert a counter
# into a template, and needs the counter to survive across
# multiple instantiations of the template.
# (To achieve the survival, the application must keep the context)
#
# if the variable does not exist, it is created with value 0.
#
# Hello: {% increment variable %}
#
# gives you:
@@ -17,6 +18,7 @@ module Liquid
class Increment < Tag
def initialize(tag_name, markup, tokens)
@variable = markup.strip
super
end
@@ -26,9 +28,7 @@ module Liquid
value.to_s
end
def blank?
false
end
private
end
Template.register_tag('increment', Increment)

View File

@@ -14,10 +14,6 @@ module Liquid
# template.render('user_name' => 'bob')
#
class Template
DEFAULT_OPTIONS = {
:locale => I18n.new
}
attr_accessor :root, :resource_limits
@@file_system = BlankFileSystem.new
@@ -38,18 +34,6 @@ module Liquid
@tags ||= {}
end
# Sets how strict the parser should be.
# :lax acts like liquid 2.5 and silently ignores malformed tags in most cases.
# :warn is the default and will give deprecation warnings when invalid syntax is used.
# :strict will enforce correct syntax.
def error_mode=(mode)
@error_mode = mode
end
def error_mode
@error_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)
@@ -57,9 +41,9 @@ module Liquid
end
# creates a new <tt>Template</tt> object from liquid source code
def parse(source, options = {})
def parse(source)
template = Template.new
template.parse(source, options)
template.parse(source)
template
end
end
@@ -71,17 +55,11 @@ module Liquid
# Parse source code.
# Returns self for easy chaining
def parse(source, options = {})
@root = Document.new(tokenize(source), DEFAULT_OPTIONS.merge(options))
@warnings = nil
def parse(source)
@root = Document.new(tokenize(source))
self
end
def warnings
return [] unless @root
@warnings ||= @root.warnings
end
def registers
@registers ||= {}
end
@@ -123,7 +101,7 @@ module Liquid
when nil
Context.new(assigns, instance_assigns, registers, @rethrow_errors, @resource_limits)
else
raise ArgumentError, "Expected Hash or Liquid::Context as parameter"
raise ArgumentError, "Expect Hash or Liquid::Context as parameter"
end
case args.last

View File

@@ -12,30 +12,11 @@ module Liquid
#
class Variable
FilterParser = /(?:#{FilterSeparator}|(?:\s*(?:#{QuotedFragment}|#{ArgumentSeparator})\s*)+)/o
EasyParse = /^ *(\w+(?:\.\w+)*) *$/
attr_accessor :filters, :name, :warnings
attr_accessor :filters, :name
def initialize(markup, options = {})
def initialize(markup)
@markup = markup
@name = nil
@options = options || {}
case @options[:error_mode] || Template.error_mode
when :strict then strict_parse(markup)
when :lax then lax_parse(markup)
when :warn
begin
strict_parse(markup)
rescue SyntaxError => e
@warnings ||= []
@warnings << e
lax_parse(markup)
end
end
end
def lax_parse(markup)
@filters = []
if match = markup.match(/\s*(#{QuotedFragment})(.*)/o)
@name = match[1]
@@ -52,39 +33,6 @@ module Liquid
end
end
def strict_parse(markup)
# Very simple valid cases
if markup =~ EasyParse
@name = $1
@filters = []
return
end
@filters = []
p = Parser.new(markup)
# Could be just filters with no input
@name = p.look(:pipe) ? '' : p.expression
while p.consume?(:pipe)
filtername = p.consume(:id)
filterargs = p.consume?(:colon) ? parse_filterargs(p) : []
@filters << [filtername, filterargs]
end
p.consume(:end_of_string)
rescue SyntaxError => e
e.message << " in \"{{#{markup}}}\""
raise e
end
def parse_filterargs(p)
# first argument
filterargs = [p.argument]
# followed by comma separated others
while p.consume?(:comma)
filterargs << p.argument
end
filterargs
end
def render(context)
return '' if @name.nil?
@filters.inject(context[@name]) do |output, filter|

View File

@@ -1,4 +1,4 @@
# encoding: utf-8
module Liquid
VERSION = "2.6.0"
VERSION = "2.6.0.rc1"
end

View File

@@ -12,7 +12,6 @@ Gem::Specification.new do |s|
s.authors = ["Tobias Luetke"]
s.email = ["tobi@leetsoft.com"]
s.homepage = "http://www.liquidmarkup.org"
s.license = "MIT"
#s.description = "A secure, non-evaling end user template engine with aesthetic markup."
s.required_rubygems_version = ">= 1.3.7"

View File

@@ -2,7 +2,6 @@ require 'rubygems'
require 'benchmark'
require File.dirname(__FILE__) + '/theme_runner'
Liquid::Template.error_mode = ARGV.first.to_sym if ARGV.first
profiler = ThemeRunner.new
Benchmark.bmbm do |x|

View File

@@ -6,14 +6,14 @@ profiler = ThemeRunner.new
puts 'Running profiler...'
results = profiler.run_profile
results = profiler.run
puts 'Success'
puts
[RubyProf::FlatPrinter, RubyProf::GraphHtmlPrinter, RubyProf::CallTreePrinter, RubyProf::DotPrinter].each do |klass|
[RubyProf::FlatPrinter, RubyProf::GraphPrinter, RubyProf::GraphHtmlPrinter, RubyProf::CallTreePrinter].each do |klass|
filename = (ENV['TMP'] || '/tmp') + (klass.name.include?('Html') ? "/liquid.#{klass.name.downcase}.html" : "/callgrind.liquid.#{klass.name.downcase}.txt")
filename.gsub!(/:+/, '_')
File.open(filename, "w+") { |fp| klass.new(results).print(fp, :print_file => true, :min_percent => 3) }
File.open(filename, "w+") { |fp| klass.new(results).print(fp, :print_file => true) }
$stderr.puts "wrote #{klass.name} output to #{filename}"
end

View File

@@ -28,7 +28,7 @@
{% else %}
<div class="article-body textile">
In <em>Admin &gt; Blogs &amp; Pages</em>, create a page with the handle <strong><code>frontpage</code></strong> and it will show up here.<br />
{{ "Learn more about handles" | link_to: "http://wiki.shopify.com/Handle" }}
{{ "Learn more about handles" | link_to "http://wiki.shopify.com/Handle" }}
</div>
{% endif %}

View File

@@ -26,7 +26,7 @@
{{ article.content }}
{% else %}
In <em>Admin &gt; Blogs &amp; Pages</em>, create a page with the handle <strong><code>frontpage</code></strong> and it will show up here.<br />
{{ "Learn more about handles" | link_to: "http://wiki.shopify.com/Handle" }}
{{ "Learn more about handles" | link_to "http://wiki.shopify.com/Handle" }}
{% endif %}
</div>
</div>

View File

@@ -5,7 +5,7 @@
{{ article.content }}
{% else %}
In <em>Admin &gt; Blogs &amp; Pages</em>, create a page with the handle <strong><code>frontpage</code></strong> and it will show up here.<br />
{{ "Learn more about handles" | link_to: "http://wiki.shopify.com/Handle" }}
{{ "Learn more about handles" | link_to "http://wiki.shopify.com/Handle" }}
{% endif %}
</div>

View File

@@ -1,9 +0,0 @@
---
simple: "less is more"
whatever: "something %{something}"
errors:
i18n:
undefined_interpolation: "undefined key %{key}"
unknown_translation: "translation '%{name}' wasn't found"
syntax:
oops: "something wasn't right"

View File

@@ -18,10 +18,4 @@ class AssignTest < Test::Unit::TestCase
'{% assign foo = values | split: "," %}.{{ foo[1] }}.',
'values' => "foo,bar,baz")
end
def test_assign_syntax_error
assert_match_syntax_error(/assign/,
'{% assign foo not values %}.',
'values' => "foo,bar,baz")
end
end # AssignTest

View File

@@ -1,94 +0,0 @@
require 'test_helper'
class BlankTestFileSystem
def read_template_file(template_path, context)
template_path
end
end
class BlankTest < Test::Unit::TestCase
include Liquid
N = 10
def wrap_in_for(body)
"{% for i in (1..#{N}) %}#{body}{% endfor %}"
end
def wrap_in_if(body)
"{% if true %}#{body}{% endif %}"
end
def wrap(body)
wrap_in_for(body) + wrap_in_if(body)
end
def test_loops_are_blank
assert_template_result("", wrap_in_for(" "))
end
def test_if_else_are_blank
assert_template_result("", "{% if true %} {% elsif false %} {% else %} {% endif %}")
end
def test_unless_is_blank
assert_template_result("", wrap("{% unless true %} {% endunless %}"))
end
def test_mark_as_blank_only_during_parsing
assert_template_result(" "*(N+1), wrap(" {% if false %} this never happens, but still, this block is not blank {% endif %}"))
end
def test_comments_are_blank
assert_template_result("", wrap(" {% comment %} whatever {% endcomment %} "))
end
def test_captures_are_blank
assert_template_result("", wrap(" {% capture foo %} whatever {% endcapture %} "))
end
def test_nested_blocks_are_blank_but_only_if_all_children_are
assert_template_result("", wrap(wrap(" ")))
assert_template_result("\n but this is not "*(N+1),
wrap(%q{{% if true %} {% comment %} this is blank {% endcomment %} {% endif %}
{% if true %} but this is not {% endif %}}))
end
def test_assigns_are_blank
assert_template_result("", wrap(' {% assign foo = "bar" %} '))
end
def test_whitespace_is_blank
assert_template_result("", wrap(" "))
assert_template_result("", wrap("\t"))
end
def test_whitespace_is_not_blank_if_other_stuff_is_present
body = " x "
assert_template_result(body*(N+1), wrap(body))
end
def test_increment_is_not_blank
assert_template_result(" 0"*2*(N+1), wrap("{% assign foo = 0 %} {% increment foo %} {% decrement foo %}"))
end
def test_cycle_is_not_blank
assert_template_result(" "*((N+1)/2)+" ", wrap("{% cycle ' ', ' ' %}"))
end
def test_raw_is_not_blank
assert_template_result(" "*(N+1), wrap(" {% raw %} {% endraw %}"))
end
def test_include_is_blank
Liquid::Template.file_system = BlankTestFileSystem.new
assert_equal "foobar"*(N+1), Template.parse(wrap("{% include 'foobar' %}")).render()
assert_equal " foobar "*(N+1), Template.parse(wrap("{% include ' foobar ' %}")).render()
assert_equal " ", Template.parse(" {% include ' ' %} ").render()
end
def test_case_is_blank
assert_template_result("", wrap(" {% assign foo = 'bar' %} {% case foo %} {% when 'bar' %} {% when 'whatever' %} {% else %} {% endcase %} "))
assert_template_result("", wrap(" {% assign foo = 'else' %} {% case foo %} {% when 'bar' %} {% when 'whatever' %} {% else %} {% endcase %} "))
assert_template_result(" x "*(N+1), wrap(" {% assign foo = 'else' %} {% case foo %} {% when 'bar' %} {% when 'whatever' %} {% else %} x {% endcase %} "))
end
end

View File

@@ -55,44 +55,11 @@ class ProductDrop < Liquid::Drop
end
class EnumerableDrop < Liquid::Drop
def before_method(method)
method
end
def size
3
end
def first
1
end
def count
3
end
def min
1
end
def max
3
end
def each
yield 1
yield 2
yield 3
end
end
class RealEnumerableDrop < Liquid::Drop
include Enumerable
def before_method(method)
method
end
def each
yield 1
yield 2
@@ -203,33 +170,6 @@ class DropsTest < Test::Unit::TestCase
assert_equal '3', Liquid::Template.parse( '{{collection.size}}').render('collection' => EnumerableDrop.new)
end
def test_enumerable_drop_will_invoke_before_method_for_clashing_method_names
["select", "each", "map", "cycle"].each do |method|
assert_equal method.to_s, Liquid::Template.parse("{{collection.#{method}}}").render('collection' => EnumerableDrop.new)
assert_equal method.to_s, Liquid::Template.parse("{{collection[\"#{method}\"]}}").render('collection' => EnumerableDrop.new)
assert_equal method.to_s, Liquid::Template.parse("{{collection.#{method}}}").render('collection' => RealEnumerableDrop.new)
assert_equal method.to_s, Liquid::Template.parse("{{collection[\"#{method}\"]}}").render('collection' => RealEnumerableDrop.new)
end
end
def test_some_enumerable_methods_still_get_invoked
[ :count, :max ].each do |method|
assert_equal "3", Liquid::Template.parse("{{collection.#{method}}}").render('collection' => RealEnumerableDrop.new)
assert_equal "3", Liquid::Template.parse("{{collection[\"#{method}\"]}}").render('collection' => RealEnumerableDrop.new)
assert_equal "3", Liquid::Template.parse("{{collection.#{method}}}").render('collection' => EnumerableDrop.new)
assert_equal "3", Liquid::Template.parse("{{collection[\"#{method}\"]}}").render('collection' => EnumerableDrop.new)
end
assert_equal "yes", Liquid::Template.parse("{% if collection contains 3 %}yes{% endif %}").render('collection' => RealEnumerableDrop.new)
[ :min, :first ].each do |method|
assert_equal "1", Liquid::Template.parse("{{collection.#{method}}}").render('collection' => RealEnumerableDrop.new)
assert_equal "1", Liquid::Template.parse("{{collection[\"#{method}\"]}}").render('collection' => RealEnumerableDrop.new)
assert_equal "1", Liquid::Template.parse("{{collection.#{method}}}").render('collection' => EnumerableDrop.new)
assert_equal "1", Liquid::Template.parse("{{collection[\"#{method}\"]}}").render('collection' => EnumerableDrop.new)
end
end
def test_empty_string_value_access
assert_equal '', Liquid::Template.parse('{{ product[value] }}').render('product' => ProductDrop.new, 'value' => '')
end

View File

@@ -58,48 +58,19 @@ class ErrorHandlingTest < Test::Unit::TestCase
def test_missing_endtag_parse_time_error
assert_raise(Liquid::SyntaxError) do
Liquid::Template.parse(' {% for a in b %} ... ')
template = Liquid::Template.parse(' {% for a in b %} ... ')
end
end
def test_unrecognized_operator
with_error_mode(:strict) do
assert_raise(SyntaxError) do
Liquid::Template.parse(' {% if 1 =! 2 %}ok{% endif %} ')
end
end
end
def test_lax_unrecognized_operator
assert_nothing_raised do
template = Liquid::Template.parse(' {% if 1 =! 2 %}ok{% endif %} ', :error_mode => :lax)
template = Liquid::Template.parse(' {% if 1 =! 2 %}ok{% endif %} ')
assert_equal ' Liquid error: Unknown operator =! ', template.render
assert_equal 1, template.errors.size
assert_equal Liquid::ArgumentError, template.errors.first.class
end
end
def test_strict_error_messages
err = assert_raise(SyntaxError) do
Liquid::Template.parse(' {% if 1 =! 2 %}ok{% endif %} ', :error_mode => :strict)
end
assert_equal 'Unexpected character = in "1 =! 2"', err.message
err = assert_raise(SyntaxError) do
Liquid::Template.parse('{{%%%}}', :error_mode => :strict)
end
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].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
# Liquid should not catch Exceptions that are not subclasses of StandardError, like Interrupt and NoMemoryError
def test_exceptions_propagate
assert_raise Exception do

View File

@@ -1,37 +0,0 @@
require 'test_helper'
class I18nTest < Test::Unit::TestCase
include Liquid
def setup
@i18n = I18n.new(fixture("en_locale.yml"))
end
def test_simple_translate_string
assert_equal "less is more", @i18n.translate("simple")
end
def test_nested_translate_string
assert_equal "something wasn't right", @i18n.translate("errors.syntax.oops")
end
def test_single_string_interpolation
assert_equal "something different", @i18n.translate("whatever", :something => "different")
end
# def test_raises_translation_error_on_undefined_interpolation_key
# assert_raise I18n::TranslationError do
# @i18n.translate("whatever", :oopstypos => "yes")
# end
# end
def test_raises_unknown_translation
assert_raise I18n::TranslationError do
@i18n.translate("doesnt_exist")
end
end
def test_sets_default_path_to_en
assert_equal I18n::DEFAULT_LOCALE, I18n.new.path
end
end

View File

@@ -1,48 +0,0 @@
require 'test_helper'
class LexerTest < Test::Unit::TestCase
include Liquid
def test_strings
tokens = Lexer.new(%! 'this is a test""' "wat 'lol'"!).tokenize
assert_equal [[:string,%!'this is a test""'!], [:string, %!"wat 'lol'"!], [:end_of_string]], tokens
end
def test_integer
tokens = Lexer.new('hi 50').tokenize
assert_equal [[:id,'hi'], [:number, '50'], [:end_of_string]], tokens
end
def test_float
tokens = Lexer.new('hi 5.0').tokenize
assert_equal [[:id,'hi'], [:number, '5.0'], [:end_of_string]], tokens
end
def test_comparison
tokens = Lexer.new('== <> contains').tokenize
assert_equal [[:comparison,'=='], [:comparison, '<>'], [:comparison, 'contains'], [:end_of_string]], tokens
end
def test_specials
tokens = Lexer.new('| .:').tokenize
assert_equal [[:pipe, '|'], [:dot, '.'], [:colon, ':'], [:end_of_string]], tokens
tokens = Lexer.new('[,]').tokenize
assert_equal [[:open_square, '['], [:comma, ','], [:close_square, ']'], [:end_of_string]], tokens
end
def test_fancy_identifiers
tokens = Lexer.new('hi! five?').tokenize
assert_equal [[:id,'hi!'], [:id, 'five?'], [:end_of_string]], tokens
end
def test_whitespace
tokens = Lexer.new("five|\n\t ==").tokenize
assert_equal [[:id,'five'], [:pipe, '|'], [:comparison, '=='], [:end_of_string]], tokens
end
def test_unexpected_character
assert_raises(SyntaxError) do
Lexer.new("%").tokenize
end
end
end

View File

@@ -1,74 +0,0 @@
require 'test_helper'
class ParserTest < Test::Unit::TestCase
include Liquid
def test_consume
p = Parser.new("wat: 7")
assert_equal 'wat', p.consume(:id)
assert_equal ':', p.consume(:colon)
assert_equal '7', p.consume(:number)
end
def test_jump
p = Parser.new("wat: 7")
p.jump(2)
assert_equal '7', p.consume(:number)
end
def test_consume?
p = Parser.new("wat: 7")
assert_equal 'wat', p.consume?(:id)
assert_equal false, p.consume?(:dot)
assert_equal ':', p.consume(:colon)
assert_equal '7', p.consume?(:number)
end
def test_id?
p = Parser.new("wat 6 Peter Hegemon")
assert_equal 'wat', p.id?('wat')
assert_equal false, p.id?('endgame')
assert_equal '6', p.consume(:number)
assert_equal 'Peter', p.id?('Peter')
assert_equal false, p.id?('Achilles')
end
def test_look
p = Parser.new("wat 6 Peter Hegemon")
assert_equal true, p.look(:id)
assert_equal 'wat', p.consume(:id)
assert_equal false, p.look(:comparison)
assert_equal true, p.look(:number)
assert_equal true, p.look(:id, 1)
assert_equal false, p.look(:number, 1)
end
def test_expressions
p = Parser.new("hi.there hi[5].! hi.there.bob")
assert_equal 'hi.there', p.expression
assert_equal 'hi[5].!', p.expression
assert_equal 'hi.there.bob', p.expression
p = Parser.new("567 6.0 'lol' \"wut\"")
assert_equal '567', p.expression
assert_equal '6.0', p.expression
assert_equal "'lol'", p.expression
assert_equal '"wut"', p.expression
end
def test_arguments
p = Parser.new("filter: hi.there[5], keyarg: 7")
assert_equal 'filter', p.consume(:id)
assert_equal ':', p.consume(:colon)
assert_equal 'hi.there[5]', p.argument
assert_equal ',', p.consume(:comma)
assert_equal 'keyarg: 7', p.argument
end
def test_invalid_expression
assert_raises(SyntaxError) do
p = Parser.new("==")
p.expression
end
end
end

View File

@@ -31,60 +31,22 @@ class ParsingQuirksTest < Test::Unit::TestCase
def test_error_on_empty_filter
assert_nothing_raised do
Template.parse("{{test |a|b|}}")
Template.parse("{{test}}")
Template.parse("{{|test}}")
end
with_error_mode(:strict) do
assert_raise(SyntaxError) do
Template.parse("{{test |a|b|}}")
end
Template.parse("{{|test|}}")
end
end
def test_meaningless_parens_error
with_error_mode(:strict) do
assert_raise(SyntaxError) do
markup = "a == 'foo' or (b == 'bar' and c == 'baz') or false"
Template.parse("{% if #{markup} %} YES {% endif %}")
end
end
def test_meaningless_parens
assigns = {'b' => 'bar', 'c' => 'baz'}
markup = "a == 'foo' or (b == 'bar' and c == 'baz') or false"
assert_template_result(' YES ',"{% if #{markup} %} YES {% endif %}", assigns)
end
def test_unexpected_characters_syntax_error
with_error_mode(:strict) do
assert_raise(SyntaxError) do
markup = "true && false"
Template.parse("{% if #{markup} %} YES {% endif %}")
end
assert_raise(SyntaxError) do
markup = "false || true"
Template.parse("{% if #{markup} %} YES {% endif %}")
end
end
end
def test_no_error_on_lax_empty_filter
assert_nothing_raised do
Template.parse("{{test |a|b|}}", :error_mode => :lax)
Template.parse("{{test}}", :error_mode => :lax)
Template.parse("{{|test|}}", :error_mode => :lax)
end
end
def test_meaningless_parens_lax
with_error_mode(:lax) do
assigns = {'b' => 'bar', 'c' => 'baz'}
markup = "a == 'foo' or (b == 'bar' and c == 'baz') or false"
assert_template_result(' YES ',"{% if #{markup} %} YES {% endif %}", assigns)
end
end
def test_unexpected_characters_silently_eat_logic_lax
with_error_mode(:lax) do
markup = "true && false"
assert_template_result(' YES ',"{% if #{markup} %} YES {% endif %}")
markup = "false || true"
assert_template_result('',"{% if #{markup} %} YES {% endif %}")
end
def test_unexpected_characters_silently_eat_logic
markup = "true && false"
assert_template_result(' YES ',"{% if #{markup} %} YES {% endif %}")
markup = "false || true"
assert_template_result('',"{% if #{markup} %} YES {% endif %}")
end
end # ParsingQuirksTest

View File

@@ -15,10 +15,6 @@ class TestThing
"woot: #{@foo}"
end
def [](whatever)
to_s
end
def to_liquid
@foo += 1
self
@@ -31,14 +27,6 @@ class TestDrop < Liquid::Drop
end
end
class TestEnumerable < Liquid::Drop
include Enumerable
def each(&block)
[ { "foo" => 1, "bar" => 2 }, { "foo" => 2, "bar" => 1 }, { "foo" => 3, "bar" => 3 } ].each(&block)
end
end
class StandardFiltersTest < Test::Unit::TestCase
include Liquid
@@ -137,12 +125,7 @@ class StandardFiltersTest < Test::Unit::TestCase
def test_map_calls_to_liquid
t = TestThing.new
assert_equal "woot: 1", Liquid::Template.parse('{{ foo | map: "whatever" }}').render("foo" => [t])
end
def test_sort_calls_to_liquid
t = TestThing.new
assert_equal "woot: 1", Liquid::Template.parse('{{ foo | sort: "whatever" }}').render("foo" => [t])
assert_equal "woot: 1", Liquid::Template.parse('{{ foo }}').render("foo" => t)
end
def test_map_over_proc
@@ -152,14 +135,6 @@ class StandardFiltersTest < Test::Unit::TestCase
assert_equal "testfoo", Liquid::Template.parse(templ).render("procs" => [p])
end
def test_map_works_on_enumerables
assert_equal "123", Liquid::Template.parse('{{ foo | map: "foo" }}').render!("foo" => TestEnumerable.new)
end
def test_sort_works_on_enumerables
assert_equal "213", Liquid::Template.parse('{{ foo | sort: "bar" | map: "foo" }}').render!("foo" => TestEnumerable.new)
end
def test_date
assert_equal 'May', @filters.date(Time.parse("2006-05-05 10:00:00"), "%B")
assert_equal 'June', @filters.date(Time.parse("2006-06-05 10:00:00"), "%B")

View File

@@ -143,18 +143,4 @@ class TemplateTest < Test::Unit::TestCase
assert_equal 'bar', t.parse('{{bar}}').render(drop)
assert_equal 'haha', t.parse("{{baz}}").render(drop)
end
def test_sets_default_localization_in_document
t = Template.new
t.parse('')
assert_instance_of I18n, t.root.options[:locale]
end
def test_sets_default_localization_in_context_with_quick_initialization
t = Template.new
t.parse('{{foo}}', :locale => I18n.new(fixture("en_locale.yml")))
assert_instance_of I18n, t.root.options[:locale]
assert_equal fixture("en_locale.yml"), t.root.options[:locale].path
end
end
end # TemplateTest

View File

@@ -73,14 +73,8 @@ class VariableTest < Test::Unit::TestCase
end
def test_symbol
var = Variable.new("http://disney.com/logo.gif | image: 'med' ", :error_mode => :lax)
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
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
end
@@ -121,18 +115,10 @@ class VariableTest < Test::Unit::TestCase
end
def test_lax_filter_argument_parsing
var = Variable.new(%! number_of_comments | pluralize: 'comment': 'comments' !, :error_mode => :lax)
var = Variable.new(%! number_of_comments | pluralize: 'comment': 'comments' !)
assert_equal 'number_of_comments', var.name
assert_equal [['pluralize',["'comment'","'comments'"]]], var.filters
end
def test_strict_filter_argument_parsing
with_error_mode(:strict) do
assert_raises(SyntaxError) do
Variable.new(%! number_of_comments | pluralize: 'comment': 'comments' !)
end
end
end
end

View File

@@ -7,26 +7,11 @@ begin
rescue LoadError
puts "Couldn't load ruby-debug. gem install ruby-debug if you need it."
end
$:.unshift(File.join(File.expand_path(File.dirname(__FILE__)), '..', 'lib'))
require 'liquid.rb'
mode = :strict
if env_mode = ENV['LIQUID_PARSER_MODE']
puts "-- #{env_mode.upcase} ERROR MODE"
mode = env_mode.to_sym
end
Liquid::Template.error_mode = mode
require File.join(File.dirname(__FILE__), '..', 'lib', 'liquid')
module Test
module Unit
class TestCase
def fixture(name)
File.join(File.expand_path(File.dirname(__FILE__)), "fixtures", name)
end
end
module Assertions
include Liquid
@@ -39,20 +24,6 @@ module Test
assert_match expected, Template.parse(template).render(assigns)
end
def assert_match_syntax_error(match, template, registers = {})
exception = assert_raise(Liquid::SyntaxError) {
Template.parse(template).render(assigns)
}
assert_match match, exception.message
end
def with_error_mode(mode)
old_mode = Liquid::Template.error_mode
Liquid::Template.error_mode = mode
yield
Liquid::Template.error_mode = old_mode
end
end # Assertions
end # Unit
end # Test