diff --git a/.travis.yml b/.travis.yml
index fb899f9..92a360e 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -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"
diff --git a/History.md b/History.md
index b4517e9..4af0dd9 100644
--- a/History.md
+++ b/History.md
@@ -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
diff --git a/README.md b/README.md
index 481f18e..6572520 100644
--- a/README.md
+++ b/README.md
@@ -1,9 +1,9 @@
+[](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.
-
-[](http://travis-ci.org/Shopify/liquid)
diff --git a/Rakefile b/Rakefile
index 137b8e3..219b7cc 100755
--- a/Rakefile
+++ b/Rakefile
@@ -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
diff --git a/lib/liquid/block.rb b/lib/liquid/block.rb
index 0281b91..d1356eb 100644
--- a/lib/liquid/block.rb
+++ b/lib/liquid/block.rb
@@ -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")
diff --git a/lib/liquid/context.rb b/lib/liquid/context.rb
index 452b750..ade5c6d 100644
--- a/lib/liquid/context.rb
+++ b/lib/liquid/context.rb
@@ -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
diff --git a/lib/liquid/drop.rb b/lib/liquid/drop.rb
index f1bf48a..d4e5941 100644
--- a/lib/liquid/drop.rb
+++ b/lib/liquid/drop.rb
@@ -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)
diff --git a/lib/liquid/file_system.rb b/lib/liquid/file_system.rb
index c7eeddc..b3efef6 100644
--- a/lib/liquid/file_system.rb
+++ b/lib/liquid/file_system.rb
@@ -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)}/
diff --git a/lib/liquid/htmltags.rb b/lib/liquid/htmltags.rb
index 05b0419..8ceab44 100644
--- a/lib/liquid/htmltags.rb
+++ b/lib/liquid/htmltags.rb
@@ -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
diff --git a/lib/liquid/i18n.rb b/lib/liquid/i18n.rb
index de90e99..205b740 100644
--- a/lib/liquid/i18n.rb
+++ b/lib/liquid/i18n.rb
@@ -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]}"
}
diff --git a/lib/liquid/lexer.rb b/lib/liquid/lexer.rb
index 32991c9..47a9fc4 100644
--- a/lib/liquid/lexer.rb
+++ b/lib/liquid/lexer.rb
@@ -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]
diff --git a/lib/liquid/parser.rb b/lib/liquid/parser.rb
index 9626001..26f235f 100644
--- a/lib/liquid/parser.rb
+++ b/lib/liquid/parser.rb
@@ -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})"
diff --git a/lib/liquid/standardfilters.rb b/lib/liquid/standardfilters.rb
index b7f6418..027c0b3 100644
--- a/lib/liquid/standardfilters.rb
+++ b/lib/liquid/standardfilters.rb
@@ -4,6 +4,8 @@ require 'bigdecimal'
module Liquid
module StandardFilters
+ HTML_ESCAPE = { '&' => '&', '>' => '>', '<' => '<', '"' => '"', "'" => ''' }
+ 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
diff --git a/lib/liquid/strainer.rb b/lib/liquid/strainer.rb
index 5e75cdd..bc429b6 100644
--- a/lib/liquid/strainer.rb
+++ b/lib/liquid/strainer.rb
@@ -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)
diff --git a/lib/liquid/tag.rb b/lib/liquid/tag.rb
index fd72652..83dc730 100644
--- a/lib/liquid/tag.rb
+++ b/lib/liquid/tag.rb
@@ -31,7 +31,7 @@ module Liquid
end
def blank?
- @blank || true
+ @blank || false
end
def parse_with_selected_parser(markup)
diff --git a/lib/liquid/tags/assign.rb b/lib/liquid/tags/assign.rb
index 45c6bed..4699a6a 100644
--- a/lib/liquid/tags/assign.rb
+++ b/lib/liquid/tags/assign.rb
@@ -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)
diff --git a/lib/liquid/tags/capture.rb b/lib/liquid/tags/capture.rb
index 92b9950..34036be 100644
--- a/lib/liquid/tags/capture.rb
+++ b/lib/liquid/tags/capture.rb
@@ -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
diff --git a/lib/liquid/tags/comment.rb b/lib/liquid/tags/comment.rb
index 1eaf71f..099310e 100644
--- a/lib/liquid/tags/comment.rb
+++ b/lib/liquid/tags/comment.rb
@@ -4,6 +4,9 @@ module Liquid
''
end
+ def unknown_tag(tag, markup, tokens)
+ end
+
def blank?
true
end
diff --git a/lib/liquid/tags/for.rb b/lib/liquid/tags/for.rb
index d343ed5..057658c 100644
--- a/lib/liquid/tags/for.rb
+++ b/lib/liquid/tags/for.rb
@@ -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?
diff --git a/lib/liquid/tags/if.rb b/lib/liquid/tags/if.rb
index 1202d58..e6175e8 100644
--- a/lib/liquid/tags/if.rb
+++ b/lib/liquid/tags/if.rb
@@ -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
diff --git a/lib/liquid/tags/include.rb b/lib/liquid/tags/include.rb
index 76448f8..d439fff 100644
--- a/lib/liquid/tags/include.rb
+++ b/lib/liquid/tags/include.rb
@@ -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
diff --git a/lib/liquid/utils.rb b/lib/liquid/utils.rb
index 0bf6df2..cb96524 100644
--- a/lib/liquid/utils.rb
+++ b/lib/liquid/utils.rb
@@ -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
diff --git a/lib/liquid/version.rb b/lib/liquid/version.rb
index ee4d213..d27c6d3 100644
--- a/lib/liquid/version.rb
+++ b/lib/liquid/version.rb
@@ -1,4 +1,4 @@
# encoding: utf-8
module Liquid
- VERSION = "2.6.0"
+ VERSION = "3.0.0"
end
diff --git a/test/liquid/blank_test.rb b/test/liquid/blank_test.rb
index 8c039f7..d2e2f73 100644
--- a/test/liquid/blank_test.rb
+++ b/test/liquid/blank_test.rb
@@ -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
diff --git a/test/liquid/file_system_test.rb b/test/liquid/file_system_test.rb
index e9abaa8..4b2a2f1 100644
--- a/test/liquid/file_system_test.rb
+++ b/test/liquid/file_system_test.rb
@@ -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
diff --git a/test/liquid/output_test.rb b/test/liquid/output_test.rb
index e545a41..d5be6f8 100644
--- a/test/liquid/output_test.rb
+++ b/test/liquid/output_test.rb
@@ -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 = %| bad |
diff --git a/test/liquid/parser_test.rb b/test/liquid/parser_test.rb
index c588f12..a9f848f 100644
--- a/test/liquid/parser_test.rb
+++ b/test/liquid/parser_test.rb
@@ -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)
diff --git a/test/liquid/regexp_test.rb b/test/liquid/regexp_test.rb
index 280d62b..667d5bb 100644
--- a/test/liquid/regexp_test.rb
+++ b/test/liquid/regexp_test.rb
@@ -21,11 +21,11 @@ class RegexpTest < Test::Unit::TestCase
assert_equal [''], %||.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
diff --git a/test/liquid/standard_filter_test.rb b/test/liquid/standard_filter_test.rb
index b317b68..3b58711 100644
--- a/test/liquid/standard_filter_test.rb
+++ b/test/liquid/standard_filter_test.rb
@@ -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 '<strong>', @filters.escape_once(@filters.escape(''))
+ assert_equal '<strong>Hulk</strong>', @filters.escape_once('<strong>Hulk')
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
diff --git a/test/liquid/strainer_test.rb b/test/liquid/strainer_test.rb
index 582ed7f..fd0a969 100644
--- a/test/liquid/strainer_test.rb
+++ b/test/liquid/strainer_test.rb
@@ -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
diff --git a/test/liquid/tags/for_tag_test.rb b/test/liquid/tags/for_tag_test.rb
index 99d9a5f..2f8d006 100644
--- a/test/liquid/tags/for_tag_test.rb
+++ b/test/liquid/tags/for_tag_test.rb
@@ -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
diff --git a/test/liquid/tags/if_else_tag_test.rb b/test/liquid/tags/if_else_tag_test.rb
index 6fc3792..61c4bc8 100644
--- a/test/liquid/tags/if_else_tag_test.rb
+++ b/test/liquid/tags/if_else_tag_test.rb
@@ -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
diff --git a/test/liquid/tags/include_tag_test.rb b/test/liquid/tags/include_tag_test.rb
index 8bdb19c..52970e1 100644
--- a/test/liquid/tags/include_tag_test.rb
+++ b/test/liquid/tags/include_tag_test.rb
@@ -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
diff --git a/test/liquid/tags/standard_tag_test.rb b/test/liquid/tags/standard_tag_test.rb
index 4d8bdd3..ddd365f 100644
--- a/test/liquid/tags/standard_tag_test.rb
+++ b/test/liquid/tags/standard_tag_test.rb
@@ -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
diff --git a/test/liquid/template_test.rb b/test/liquid/template_test.rb
index 5fd4d1e..f9afac1 100644
--- a/test/liquid/template_test.rb
+++ b/test/liquid/template_test.rb
@@ -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 }
diff --git a/test/test_helper.rb b/test/test_helper.rb
index e1f12f0..b3d2818 100644
--- a/test/test_helper.rb
+++ b/test/test_helper.rb
@@ -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'