Merge remote-tracking branch 'upstream/master' into correct-if-nodelists

Conflicts:
	test/liquid/tags/for_tag_test.rb
	test/liquid/tags/if_else_tag_test.rb
This commit is contained in:
Nicholas Jones 2014-01-13 12:43:43 -08:00
commit 8f9f12e542
36 changed files with 344 additions and 89 deletions

View File

@ -1,11 +1,14 @@
rvm:
- 1.8.7
- 1.9.3
- ree
- jruby-18mode
- 2.0.0
- 2.1.0
- jruby-19mode
- rbx-18mode
- jruby-head
- rbx-19mode
matrix:
allow_failures:
- rvm: rbx-19mode
- rvm: jruby-head
script: "rake test"

View File

@ -1,18 +1,29 @@
# Liquid Version History
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)
## 3.0.0 / not yet released / branch "master"
* ...
* Prevent arbitrary method invocation on condition objects, see #274 [Dylan Thacker-Smith, dylanahsmith]
* Don't call to_sym when creating conditions for security reasons, see #273 [Bouke van der Bijl, bouk]
* Fix resource counting bug with respond_to?(:length), see #263 [Florian Weingarten, fw42]
* Allow specifying custom patterns for template filenames, see #284 [Andrei Gladkyi, agladkyi]
* Allow drops to optimize loading a slice of elements, see #282 [Tom Burns, boourns]
* Support for passing variables to snippets in subdirs, see #271 [Joost Hietbrink, joost]
* Add a class cache to avoid runtime extend calls, see #249 [James Tucker, raggi]
* Remove some legacy Ruby 1.8 compatibility code, see #276 [Florian Weingarten, fw42]
* Add default filter to standard filters, see #267 [Derrick Reimer, djreimer]
* 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]
## 2.6.0 / 2013-11-25 / branch "2.6-stable"
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.
* 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]
@ -21,6 +32,7 @@ The following releases will only be tested against Ruby 1.9 and Ruby 2.0 and are
* Bugfix for #204: 'raw' parsing bug [Florian Weingarten, fw42]
* Bugfix for #150: 'for' parsing bug [Peter Schröder, phoet]
* Bugfix for #126: Strip CRLF in strip_newline [Peter Schröder, phoet]
* Bugfix for #174, "can't convert Fixnum into String" for "replace" [wǒ_is神仙, jsw0528]
* Allow a Liquid::Drop to be passed into Template#render [Daniel Huckstep, darkhelmet]
* Resource limits [Florian Weingarten, fw42]
* Add reverse filter [Jay Strybis, unreal]
@ -31,6 +43,21 @@ 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.4 / 2013-11-11 / branch "2.5-stable"
* Fix "can't convert Fixnum into String" for "replace", see #173, [wǒ_is神仙, jsw0528]
## 2.5.3 / 2013-10-09
* #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

@ -1,9 +1,9 @@
[![Build Status](https://secure.travis-ci.org/Shopify/liquid.png?branch=master)](http://travis-ci.org/Shopify/liquid)
# Liquid template engine
* [Contributing guidelines](CONTRIBUTING.md)
* [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/)
@ -71,5 +71,3 @@ This is useful for doing things like enabling strict mode only in the theme edit
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

@ -1,9 +1,7 @@
#!/usr/bin/env ruby
require 'rubygems'
require 'rake'
require 'rake/testtask'
require 'rubygems/package_task'
$LOAD_PATH.unshift File.expand_path("../lib", __FILE__)
require "liquid/version"
task :default => 'test'
@ -29,14 +27,20 @@ task :test do
Rake::Task['base_test'].invoke
end
gemspec = eval(File.read('liquid.gemspec'))
Gem::PackageTask.new(gemspec) do |pkg|
pkg.gem_spec = gemspec
task :gem => :build
task :build do
system "gem build liquid.gemspec"
end
desc "Build the gem and release it to rubygems.org"
task :release => :gem do
sh "gem push pkg/liquid-#{gemspec.version}.gem"
task :install => :build do
system "gem install liquid-#{Liquid::VERSION}.gem"
end
task :release => :build do
system "git tag -a v#{Liquid::VERSION} -m 'Tagging #{Liquid::VERSION}'"
system "git push --tags"
system "gem push liquid-#{Liquid::VERSION}.gem"
system "rm liquid-#{Liquid::VERSION}.gem"
end
namespace :benchmark do

View File

@ -135,7 +135,7 @@ module Liquid
end
token_output = (token.respond_to?(:render) ? token.render(context) : token)
context.resource_limits[:render_length_current] += (token_output.respond_to?(:length) ? token_output.length : 1)
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")

View File

@ -25,6 +25,15 @@ module Liquid
squash_instance_assigns_with_environments
@interrupts = []
@filters = []
end
def increment_used_resources(key, obj)
@resource_limits[key] += if obj.class.ancestors & [ String, Array, Hash ] != []
obj.length
else
1
end
end
def resource_limits_reached?
@ -34,7 +43,7 @@ module Liquid
end
def strainer
@strainer ||= Strainer.create(self)
@strainer ||= Strainer.create(self, @filters)
end
# Adds filters to this context.
@ -43,11 +52,20 @@ module Liquid
# for that
def add_filters(filters)
filters = [filters].flatten.compact
filters.each do |f|
raise ArgumentError, "Expected module but got: #{f.class}" unless f.is_a?(Module)
Strainer.add_known_filter(f)
strainer.extend(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

View File

@ -59,13 +59,12 @@ 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)
blacklist = Liquid::Drop.public_instance_methods + [:each]
if include?(Enumerable)
blacklist += Enumerable.public_instance_methods.map(&:to_s)
blacklist -= [:sort, :count, :first, :min, :max, :include?].map(&:to_s)
blacklist += Enumerable.public_instance_methods
blacklist -= [:sort, :count, :first, :min, :max, :include?]
end
whitelist = [:to_liquid] + (public_instance_methods.map(&:to_s) - blacklist.map(&:to_s))
whitelist = [:to_liquid] + (public_instance_methods - blacklist)
@invokable_methods = Set.new(whitelist.map(&:to_s))
end
@invokable_methods.include?(method_name.to_s)

View File

@ -31,11 +31,22 @@ module Liquid
# file_system.full_path("mypartial") # => "/some/path/_mypartial.liquid"
# file_system.full_path("dir/mypartial") # => "/some/path/dir/_mypartial.liquid"
#
# Optionally in the second argument you can specify a custom pattern for template filenames.
# The Kernel::sprintf format specification is used.
# Default pattern is "_%s.liquid".
#
# Example:
#
# file_system = Liquid::LocalFileSystem.new("/some/path", "%s.html")
#
# file_system.full_path("index") # => "/some/path/index.html"
#
class LocalFileSystem
attr_accessor :root
def initialize(root)
def initialize(root, pattern = "_%s.liquid")
@root = root
@pattern = pattern
end
def read_template_file(template_path, context)
@ -49,9 +60,9 @@ module Liquid
raise FileSystemError, "Illegal template name '#{template_path}'" unless template_path =~ /^[^.\/][a-zA-Z0-9_\/]+$/
full_path = if template_path.include?('/')
File.join(root, File.dirname(template_path), "_#{File.basename(template_path)}.liquid")
File.join(root, File.dirname(template_path), @pattern % File.basename(template_path))
else
File.join(root, "_#{template_path}.liquid")
File.join(root, @pattern % template_path)
end
raise FileSystemError, "Illegal template path '#{File.expand_path(full_path)}'" unless File.expand_path(full_path) =~ /^#{File.expand_path(root)}/

View File

@ -23,7 +23,7 @@ module Liquid
from = @attributes['offset'] ? context[@attributes['offset']].to_i : 0
to = @attributes['limit'] ? from + context[@attributes['limit']].to_i : nil
collection = Utils.slice_collection_using_each(collection, from, to)
collection = Utils.slice_collection(collection, from, to)
length = collection.length

View File

@ -24,7 +24,7 @@ module Liquid
private
def interpolate(name, vars)
name.gsub(/%{(\w+)}/) {
name.gsub(/%\{(\w+)\}/) {
# raise TranslationError, "Undefined key #{$1} for interpolation in translation #{name}" unless vars[$1.to_sym]
"#{vars[$1.to_sym]}"
}

View File

@ -15,6 +15,7 @@ module Liquid
SINGLE_STRING_LITERAL = /'[^\']*'/
DOUBLE_STRING_LITERAL = /"[^\"]*"/
NUMBER_LITERAL = /-?\d+(\.\d+)?/
DOTDOT = /\.\./
COMPARISON_OPERATOR = /==|!=|<>|<=?|>=?|contains/
def initialize(input)
@ -32,6 +33,7 @@ module Liquid
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]
when t = @ss.scan(DOTDOT) then [:dotdot, t]
else
c = @ss.getch
if s = SPECIALS[c]

View File

@ -53,8 +53,7 @@ module Liquid
elsif token.first == :open_round
consume
first = expression
consume(:dot)
consume(:dot)
consume(:dotdot)
last = expression
consume(:close_round)
"(#{first}..#{last})"

View File

@ -4,6 +4,8 @@ require 'bigdecimal'
module Liquid
module StandardFilters
HTML_ESCAPE = { '&' => '&amp;', '>' => '&gt;', '<' => '&lt;', '"' => '&quot;', "'" => '&#39;' }
HTML_ESCAPE_ONCE_REGEXP = /["><']|&(?!([a-zA-Z]+|(#\d+));)/
# Return the size of an array or of an string
def size(input)
@ -31,9 +33,7 @@ module Liquid
end
def escape_once(input)
ActionView::Helpers::TagHelper.escape_once(input)
rescue NameError
input
input.to_s.gsub(HTML_ESCAPE_ONCE_REGEXP, HTML_ESCAPE)
end
alias_method :h, :escape
@ -43,8 +43,7 @@ module Liquid
if input.nil? then return end
l = length.to_i - truncate_string.length
l = 0 if l < 0
truncated = RUBY_VERSION[0,3] == "1.8" ? input.scan(/./mu)[0...l].to_s : input[0...l]
input.length > length.to_i ? truncated + truncate_string : input
input.length > length.to_i ? input[0...l] + truncate_string : input
end
def truncatewords(input, words = 15, truncate_string = "...")
@ -245,12 +244,17 @@ module Liquid
apply_operation(input, operand, :%)
end
def default(input, default_value = "")
is_blank = input.respond_to?(:empty?) ? input.empty? : !input
is_blank ? default_value : input
end
private
def flatten_if_necessary(input)
ary = if input.is_a?(Array)
input.flatten
elsif input.kind_of?(Enumerable)
elsif input.is_a?(Enumerable) && !input.is_a?(Hash)
input
else
[input].flatten

View File

@ -11,6 +11,11 @@ module Liquid
@@filters = []
@@known_filters = Set.new
@@known_methods = Set.new
@@strainer_class_cache = Hash.new do |hash, filters|
hash[filters] = Class.new(Strainer) do
filters.each { |f| include f }
end
end
def initialize(context)
@context = context
@ -32,10 +37,13 @@ module Liquid
end
end
def self.create(context)
strainer = Strainer.new(context)
@@filters.each { |m| strainer.extend(m) }
strainer
def self.strainer_class_cache
@@strainer_class_cache
end
def self.create(context, filters = [])
filters = @@filters + filters
strainer_class_cache[filters].new(context)
end
def invoke(method, *args)

View File

@ -31,7 +31,7 @@ module Liquid
end
def blank?
@blank || true
@blank || false
end
def parse_with_selected_parser(markup)

View File

@ -25,10 +25,13 @@ module Liquid
def render(context)
val = @from.render(context)
context.scopes.last[@to] = val
context.resource_limits[:assign_score_current] += (val.respond_to?(:length) ? val.length : 1)
context.increment_used_resources(:assign_score_current, val)
''
end
def blank?
true
end
end
Template.register_tag('assign', Assign)

View File

@ -27,7 +27,7 @@ module Liquid
def render(context)
output = super
context.scopes.last[@to] = output
context.resource_limits[:assign_score_current] += (output.respond_to?(:length) ? output.length : 1)
context.increment_used_resources(:assign_score_current, output)
''
end

View File

@ -4,6 +4,9 @@ module Liquid
''
end
def unknown_tag(tag, markup, tokens)
end
def blank?
true
end

View File

@ -83,8 +83,7 @@ module Liquid
limit = context[@attributes['limit']]
to = limit ? limit.to_i + from : nil
segment = Utils.slice_collection_using_each(collection, from, to)
segment = Utils.slice_collection(collection, from, to)
return render_else(context) if segment.empty?

View File

@ -12,6 +12,7 @@ module Liquid
class If < Block
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
BOOLEAN_OPERATORS = %w(and or)
def initialize(tag_name, markup, tokens)
@blocks = []
@ -67,7 +68,8 @@ module Liquid
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)
raise(SyntaxError.new(options[:locale].t("errors.syntax.if"))) unless BOOLEAN_OPERATORS.include?(operator)
new_condition.send(operator, condition)
condition = new_condition
end

View File

@ -51,13 +51,14 @@ module Liquid
context[key] = context[value]
end
context_variable_name = @template_name[1..-2].split('/').last
if variable.is_a?(Array)
variable.collect do |var|
context[@template_name[1..-2]] = var
context[context_variable_name] = var
partial.render(context)
end
else
context[@template_name[1..-2]] = variable
context[context_variable_name] = variable
partial.render(context)
end
end

View File

@ -1,5 +1,18 @@
module Liquid
module Utils
def self.slice_collection(collection, from, to)
if (from != 0 || to != nil) && collection.respond_to?(:load_slice)
collection.load_slice(from, to)
else
slice_collection_using_each(collection, from, to)
end
end
def self.non_blank_string?(collection)
collection.is_a?(String) && collection != ''
end
def self.slice_collection_using_each(collection, from, to)
segments = []
index = 0
@ -22,9 +35,5 @@ module Liquid
segments
end
def self.non_blank_string?(collection)
collection.is_a?(String) && collection != ''
end
end
end

View File

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

View File

@ -1,5 +1,13 @@
require 'test_helper'
class FoobarTag < Liquid::Tag
def render(*args)
" "
end
Liquid::Template.register_tag('foobar', FoobarTag)
end
class BlankTestFileSystem
def read_template_file(template_path, context)
template_path
@ -22,6 +30,10 @@ class BlankTest < Test::Unit::TestCase
wrap_in_for(body) + wrap_in_if(body)
end
def test_new_tags_are_not_blank_by_default
assert_template_result(" "*N, wrap_in_for("{% foobar %}"))
end
def test_loops_are_blank
assert_template_result("", wrap_in_for(" "))
end

View File

@ -26,4 +26,10 @@ class FileSystemTest < Test::Unit::TestCase
file_system.full_path("/etc/passwd")
end
end
def test_custom_template_filename_patterns
file_system = Liquid::LocalFileSystem.new("/some/path", "%s.html")
assert_equal "/some/path/mypartial.html" , file_system.full_path("mypartial")
assert_equal "/some/path/dir/mypartial.html", file_system.full_path("dir/mypartial")
end
end # FileSystemTest

View File

@ -86,7 +86,7 @@ class OutputTest < Test::Unit::TestCase
assert_equal expected, Template.parse(text).render(@assigns, :filters => [FunnyFilter])
end
def test_variable_piping_with_args
def test_variable_piping_with_multiple_args
text = %! {{ car.gm | add_tag : 'span', 'bar'}} !
expected = %| <span id="bar">bad</span> |

View File

@ -56,6 +56,14 @@ class ParserTest < Test::Unit::TestCase
assert_equal '"wut"', p.expression
end
def test_ranges
p = Parser.new("(5..7) (1.5..9.6) (young..old) (hi[5].wat..old)")
assert_equal '(5..7)', p.expression
assert_equal '(1.5..9.6)', p.expression
assert_equal '(young..old)', p.expression
assert_equal '(hi[5].wat..old)', p.expression
end
def test_arguments
p = Parser.new("filter: hi.there[5], keyarg: 7")
assert_equal 'filter', p.consume(:id)

View File

@ -21,11 +21,11 @@ class RegexpTest < Test::Unit::TestCase
assert_equal ['<style', 'class="hello">', '</style>'], %|<style class="hello">' </style>|.scan(QuotedFragment)
end
def test_quoted_words
def test_double_quoted_words
assert_equal ['arg1', 'arg2', '"arg 3"'], 'arg1 arg2 "arg 3"'.scan(QuotedFragment)
end
def test_quoted_words
def test_single_quoted_words
assert_equal ['arg1', 'arg2', "'arg 3'"], 'arg1 arg2 \'arg 3\''.scan(QuotedFragment)
end

View File

@ -62,11 +62,6 @@ class StandardFiltersTest < Test::Unit::TestCase
assert_equal '', @filters.upcase(nil)
end
def test_upcase
assert_equal 'TESTING', @filters.upcase("Testing")
assert_equal '', @filters.upcase(nil)
end
def test_truncate
assert_equal '1234...', @filters.truncate('1234567890', 7)
assert_equal '1234567890', @filters.truncate('1234567890', 20)
@ -89,7 +84,7 @@ class StandardFiltersTest < Test::Unit::TestCase
end
def test_escape_once
assert_equal '&lt;strong&gt;', @filters.escape_once(@filters.escape('<strong>'))
assert_equal '&lt;strong&gt;Hulk&lt;/strong&gt;', @filters.escape_once('&lt;strong&gt;Hulk</strong>')
end
def test_truncatewords
@ -140,6 +135,10 @@ class StandardFiltersTest < Test::Unit::TestCase
assert_equal "woot: 1", Liquid::Template.parse('{{ foo | map: "whatever" }}').render("foo" => [t])
end
def test_map_on_hashes
assert_equal "4217", Liquid::Template.parse('{{ thing | map: "foo" | map: "bar" }}').render("thing" => { "foo" => [ { "bar" => 42 }, { "bar" => 17 } ] })
end
def test_sort_calls_to_liquid
t = TestThing.new
assert_equal "woot: 1", Liquid::Template.parse('{{ foo | sort: "whatever" }}').render("foo" => [t])
@ -233,9 +232,6 @@ class StandardFiltersTest < Test::Unit::TestCase
assert_template_result "12", "{{ 3 | times:4 }}"
assert_template_result "0", "{{ 'foo' | times:4 }}"
# Ruby v1.9.2-rc1, or higher, backwards compatible Float test
assert_match(/(6\.3)|(6\.(0{13})1)/, Template.parse("{{ '2.1' | times:3 }}").render)
assert_template_result "6", "{{ '2.1' | times:3 | replace: '.','-' | plus:0}}"
assert_template_result "7.25", "{{ 0.0725 | times:100 }}"
@ -245,9 +241,6 @@ class StandardFiltersTest < Test::Unit::TestCase
assert_template_result "4", "{{ 12 | divided_by:3 }}"
assert_template_result "4", "{{ 14 | divided_by:3 }}"
# Ruby v1.9.2-rc1, or higher, backwards compatible Float test
assert_match(/4\.(6{13,14})7/, Template.parse("{{ 14 | divided_by:'3.0' }}").render)
assert_template_result "5", "{{ 15 | divided_by:3 }}"
assert_template_result "Liquid error: divided by 0", "{{ 5 | divided_by:0 }}"
@ -270,6 +263,15 @@ class StandardFiltersTest < Test::Unit::TestCase
assert_template_result('abc',"{{ a | prepend: b}}",assigns)
end
def test_default
assert_equal "foo", @filters.default("foo", "bar")
assert_equal "bar", @filters.default(nil, "bar")
assert_equal "bar", @filters.default("", "bar")
assert_equal "bar", @filters.default(false, "bar")
assert_equal "bar", @filters.default([], "bar")
assert_equal "bar", @filters.default({}, "bar")
end
def test_cannot_access_private_methods
assert_template_result('a',"{{ 'a' | to_number }}")
end

View File

@ -49,4 +49,15 @@ class StrainerTest < Test::Unit::TestCase
assert_equal "has_method?", strainer.invoke("invoke", "has_method?", "invoke")
end
def test_strainer_uses_a_class_cache_to_avoid_method_cache_invalidation
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
Strainer.class_variable_get(:@@filters).each do |m|
assert_kind_of m, strainer
end
end
end # StrainerTest

View File

@ -304,4 +304,62 @@ HERE
template = Liquid::Template.parse('{% for item in items %}FOR{% else %}ELSE{% endfor %}')
assert_equal ['FOR', 'ELSE'], template.root.nodelist[0].nodelist
end
class LoaderDrop < Liquid::Drop
attr_accessor :each_called, :load_slice_called
def initialize(data)
@data = data
end
def each
@each_called = true
@data.each { |el| yield el }
end
def load_slice(from, to)
@load_slice_called = true
@data[(from..to-1)]
end
end
def test_iterate_with_each_when_no_limit_applied
loader = LoaderDrop.new([1,2,3,4,5])
assigns = {'items' => loader}
expected = '12345'
template = '{% for item in items %}{{item}}{% endfor %}'
assert_template_result(expected, template, assigns)
assert loader.each_called
assert !loader.load_slice_called
end
def test_iterate_with_load_slice_when_limit_applied
loader = LoaderDrop.new([1,2,3,4,5])
assigns = {'items' => loader}
expected = '1'
template = '{% for item in items limit:1 %}{{item}}{% endfor %}'
assert_template_result(expected, template, assigns)
assert !loader.each_called
assert loader.load_slice_called
end
def test_iterate_with_load_slice_when_limit_and_offset_applied
loader = LoaderDrop.new([1,2,3,4,5])
assigns = {'items' => loader}
expected = '34'
template = '{% for item in items offset:2 limit:2 %}{{item}}{% endfor %}'
assert_template_result(expected, template, assigns)
assert !loader.each_called
assert loader.load_slice_called
end
def test_iterate_with_load_slice_returns_same_results_as_without
loader = LoaderDrop.new([1,2,3,4,5])
loader_assigns = {'items' => loader}
array_assigns = {'items' => [1,2,3,4,5]}
expected = '34'
template = '{% for item in items offset:2 limit:2 %}{{item}}{% endfor %}'
assert_template_result(expected, template, loader_assigns)
assert_template_result(expected, template, array_assigns)
end
end

View File

@ -162,4 +162,10 @@ class IfElseTagTest < Test::Unit::TestCase
template = Liquid::Template.parse('{% if true %}IF{% else %}ELSE{% endif %}')
assert_equal ['IF', 'ELSE'], template.root.nodelist[0].nodelist
end
def test_operators_are_whitelisted
assert_raise(SyntaxError) do
assert_template_result('', %({% if 1 or throw or or 1 %}yes{% endif %}))
end
end
end # IfElseTest

View File

@ -48,6 +48,27 @@ class CountingFileSystem
end
end
class CustomInclude < Liquid::Tag
Syntax = /(#{Liquid::QuotedFragment}+)(\s+(?:with|for)\s+(#{Liquid::QuotedFragment}+))?/o
def initialize(tag_name, markup, tokens)
markup =~ Syntax
@template_name = $1
super
end
def parse(tokens)
end
def blank?
false
end
def render(context)
@template_name[1..-2]
end
end
class IncludeTagTest < Test::Unit::TestCase
include Liquid
@ -163,4 +184,31 @@ class IncludeTagTest < Test::Unit::TestCase
Template.parse("{% include 'pick_a_source' %}").render({}, :registers => {:file_system => file_system})
assert_equal 2, file_system.count
end
def test_include_tag_within_if_statement
assert_equal "foo_if_true",
Template.parse("{% if true %}{% include 'foo_if_true' %}{% endif %}").render
end
def test_custom_include_tag
original_tag = Liquid::Template.tags['include']
Liquid::Template.tags['include'] = CustomInclude
begin
assert_equal "custom_foo",
Template.parse("{% include 'custom_foo' %}").render
ensure
Liquid::Template.tags['include'] = original_tag
end
end
def test_custom_include_tag_within_if_statement
original_tag = Liquid::Template.tags['include']
Liquid::Template.tags['include'] = CustomInclude
begin
assert_equal "custom_foo_if_true",
Template.parse("{% if true %}{% include 'custom_foo_if_true' %}{% endif %}").render
ensure
Liquid::Template.tags['include'] = original_tag
end
end
end # IncludeTagTest

View File

@ -33,6 +33,13 @@ class StandardTagTest < Test::Unit::TestCase
assert_template_result('','{% comment %}{% endcomment %}')
assert_template_result('','{%comment%}comment{%endcomment%}')
assert_template_result('','{% comment %}comment{% endcomment %}')
assert_template_result('','{% comment %} 1 {% comment %} 2 {% endcomment %} 3 {% endcomment %}')
assert_template_result('','{%comment%}{%blabla%}{%endcomment%}')
assert_template_result('','{% comment %}{% blabla %}{% endcomment %}')
assert_template_result('','{%comment%}{% endif %}{%endcomment%}')
assert_template_result('','{% comment %}{% endwhatever %}{% endcomment %}')
assert_template_result('','{% comment %}{% raw %} {{%%%%}} }} { {% endcomment %} {% comment {% endraw %} {% endcomment %}')
assert_template_result('foobar','foo{%comment%}comment{%endcomment%}bar')
assert_template_result('foobar','foo{% comment %}comment{% endcomment %}bar')
@ -47,16 +54,9 @@ class StandardTagTest < Test::Unit::TestCase
{%endcomment%}bar')
end
def test_assign
assigns = {'var' => 'content' }
assert_template_result('var2: var2:content', 'var2:{{var2}} {%assign var2 = var%} var2:{{var2}}', assigns)
end
def test_hyphenated_assign
assigns = {'a-b' => '1' }
assert_template_result('a-b:1 a-b:2', 'a-b:{{a-b}} {%assign a-b = 2 %}a-b:{{a-b}}', assigns)
end
def test_assign_with_colon_and_spaces
@ -218,7 +218,12 @@ class StandardTagTest < Test::Unit::TestCase
end
def test_assign
assert_equal 'variable', Liquid::Template.parse( '{% assign a = "variable"%}{{a}}' ).render
assert_equal 'variable', Liquid::Template.parse( '{% assign a = "variable"%}{{a}}').render
end
def test_assign_unassigned
assigns = { 'var' => 'content' }
assert_template_result('var2: var2:content', 'var2:{{var2}} {%assign var2 = var%} var2:{{var2}}', assigns)
end
def test_assign_an_empty_string

View File

@ -14,6 +14,14 @@ class TemplateContextDrop < Liquid::Drop
end
end
class SomethingWithLength
def length
nil
end
liquid_methods :length
end
class TemplateTest < Test::Unit::TestCase
include Liquid
@ -86,6 +94,12 @@ class TemplateTest < Test::Unit::TestCase
@global = nil
end
def test_resource_limits_works_with_custom_length_method
t = Template.parse("{% assign foo = bar %}")
t.resource_limits = { :render_length_limit => 42 }
assert_equal "", t.render("bar" => SomethingWithLength.new)
end
def test_resource_limits_render_length
t = Template.parse("0123456789")
t.resource_limits = { :render_length_limit => 5 }

View File

@ -2,11 +2,6 @@
require 'test/unit'
require 'test/unit/assertions'
begin
require 'ruby-debug'
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'