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 @@ +[![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) 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'