Compare commits

..

47 Commits

Author SHA1 Message Date
Florian Weingarten
17db93cf6f bump version to v3.0.0.rc1 2014-07-24 15:04:39 +00:00
Arthur Neves
d07b12dc7d Update History log
Bring latest History from 2-6-stable and 2-5-stable
2014-07-24 11:01:19 -04:00
Arthur Nogueira Neves
32e4f2d3b1 Merge pull request #240 from Shopify/remove_flatten
remove .flatten on standard filters
2014-07-24 10:54:28 -04:00
Arthur Nogueira Neves
2cb1483d54 Merge pull request #397 from Shopify/bogdan-excetion-handling-for-humans
Excetion handling for humans (2)
2014-07-24 10:51:02 -04:00
Florian Weingarten
6c6350f18b Exception handling for humans
Ability to pass exception_handler as a block to #render
and provide whatever behavior you want on handling exceptions

https://github.com/Shopify/liquid/pull/254
2014-07-24 14:44:02 +00:00
Florian Weingarten
eae24373e6 remove unnecessary flatten filter 2014-07-24 02:56:57 +00:00
Jason Hiltz-Laforge
034a47a6cf Merge pull request #395 from Shopify/fix_block_delimiter
Forgot an error message case
2014-07-23 22:35:13 -04:00
Jason Hiltz-Laforge
51c1165f26 Forgot an error message case 2014-07-24 02:27:26 +00:00
Florian Weingarten
0b45ffeada add more legacy tests 2014-07-24 00:33:39 +00:00
Arthur Neves
b7b243a13d Fix regression on map 2014-07-23 17:16:21 -04:00
Arthur Neves
18e8ce1eb0 add flatten filter 2014-07-23 17:16:20 -04:00
Florian Weingarten
994f309465 Fix broken standardfilter test 2014-07-23 17:15:39 -04:00
Arthur Neves
02d42a1475 Array is a Enumerable 2014-07-23 17:14:27 -04:00
Arthur Neves
d099878385 add a input iterator to standard filter 2014-07-23 17:14:27 -04:00
Arthur Neves
6a061cbe81 remove .flatten on standard filters 2014-07-23 17:14:26 -04:00
Arthur Nogueira Neves
c864a75903 Merge pull request #341 from curebit/comparation_argument_error
Raise Liquid::ArugmentError when condition has wrong usage
2014-07-23 17:03:31 -04:00
Jason Hiltz-Laforge
d6fdf86acd Merge pull request #393 from Shopify/fix_block_delimiter
Fixing regression from block delimiter enhancement
2014-07-23 16:24:24 -04:00
Jason Hiltz-Laforge
55597b8398 Fixing regression from block delimiter enhancement 2014-07-23 19:18:02 +00:00
Florian Weingarten
c75522026b Merge pull request #389 from Shopify/remove_unnecessary_blank_stuf
Remove unnecessary blank? code
2014-07-23 15:07:26 +02:00
Florian Weingarten
1e0e9f1f31 Remove unnecessary blank? code 2014-07-22 21:19:12 +00:00
Jason Hiltz-Laforge
5fc1929b73 Merge pull request #384 from Shopify/optimize_block_parsing
Optimize block parsing -- don't recreate delimiter, use strings instead of regex
2014-07-22 12:59:05 -04:00
Jason Hiltz-Laforge
746a800475 Merge pull request #386 from Shopify/optimize_variable_parsing
Reduce temporary objects during variable/filter parsing
2014-07-22 11:22:57 -04:00
Jason Hiltz-Laforge
85dc7ef610 Merge pull request #385 from Shopify/optimize_scope_variable_resolution
Remove block in favour of for loop to reduce temporary object allocation during variable context resolution
2014-07-22 11:22:33 -04:00
Jason Hiltz-Laforge
bc3b066ba8 Remove block in favour of for loop to reduce temporary object allocation during variable context resolution 2014-07-22 14:54:50 +00:00
Jason Hiltz-Laforge
3c2de7737d Optimize block parsing -- don't recreate delimiter, use strings instead of regex 2014-07-22 02:43:20 +00:00
Jason Hiltz-Laforge
adb7d2bbb8 Reduce temporary objects during variable/filter parsing 2014-07-21 21:13:59 +00:00
Jason Hiltz-Laforge
0e56cf99ab Merge pull request #383 from Shopify/optimize_variable_lookup
Cache parsed markup parts to avoid repeated calls during template render
2014-07-21 12:29:58 -04:00
Jason Hiltz-Laforge
0df3f1c372 Cache parsed markup parts to avoid repeated calls during template render 2014-07-21 15:55:06 +00:00
Jason Hiltz-Laforge
44b9ad604f Merge pull request #381 from Shopify/add_object_profiling
Add object profiling in addition to cpu profiling
2014-07-16 16:41:31 -04:00
Jason Hiltz-Laforge
535d549978 Merge pull request #380 from Shopify/optimize_interrupt_handling
Optimize checking for interrupts by replacing any? with NOT empty?
2014-07-16 11:36:32 -04:00
Jason Hiltz-Laforge
32349033a9 Add object profiling in addition to cpu profiling 2014-07-16 15:32:04 +00:00
Jason Hiltz-Laforge
fd8c30070a Adding tests, spy dependency 2014-07-16 15:05:45 +00:00
Jason Hiltz-Laforge
4cfc05e32a Optimize checking for interrupts by replacing any? with NOT empty? 2014-07-15 18:31:40 +00:00
Florian Weingarten
c4bc6cf3db Merge pull request #378 from Shopify/revert_352
Revert: Add error messages for missing variables when :strict
2014-07-08 17:31:19 +02:00
Florian Weingarten
0ac3ec7834 Revert "Merge pull request #352 from gaiottino/master"
This reverts commit 553b0926ae, reversing
changes made to 628ab3dc6a.
2014-07-08 14:48:19 +00:00
Florian Weingarten
8909c9f27a add regression tests for #377 2014-07-08 14:47:39 +00:00
David Cornu
51c708c8f8 Merge pull request #376 from Shopify/tag-class-reloading
Avoid holding on to stale tag classes
2014-07-03 12:01:50 -04:00
David Cornu
f57383af37 Allow tag classes to be reloaded when using Liquid.cache_classes is false
Because Liquid keeps a reference to tag classes, Rails class reloading may
cause problems with custom tags. This commit introduces a setting that
allows these classes to be resolved when required.
2014-07-02 20:03:18 +00:00
Jean Boussier
d007c50856 Merge pull request #374 from Shopify/remove-duplicate-ruby-version
Give some ❤️ to travis.yml
2014-07-02 11:04:38 -04:00
Tobias Lütke
101f125a69 Merge pull request #375 from Shopify/relative-link
Fixed relative link
2014-06-30 17:19:37 -04:00
Tony Zou
5110ca906c Fixed relative link 2014-06-30 16:43:37 -04:00
Jean Boussier
ac0f63eda9 rbx-19mode is not supported anymore 2014-06-28 16:21:08 -04:00
Jean Boussier
1372274fca Do not intall stackprof under jruby nor rubinius 2014-06-28 16:09:14 -04:00
Jean Boussier
69951be173 Canonicalize travis ruby versions 2014-06-28 16:02:37 -04:00
Arthur Nogueira Neves
c9863836cd Merge pull request #371 from Shopify/unfreeze-version-string
Unfreeze version string
2014-06-24 09:59:46 -05:00
Jean Boussier
14b8d824d7 Unfreeze version string
It breaks gem installation on ruby 1.9.x (older rubygem?)
2014-06-23 10:47:10 -04:00
Bogdan Gusiev
fa14fd02e7 Raise Liquid::ArugmentError when condition has wrong usage
Condition now raises ::ArgumentError when built wrongly.
This patch make it raise Liquid::ArgumentError instead
to indicate a liquid markup error instead of ruby error.
2014-04-21 16:42:37 +03:00
29 changed files with 349 additions and 135 deletions

View File

@@ -1,17 +1,16 @@
rvm:
- 1.9.3
- 2.0.0
- 1.9
- 2.0
- 2.1
- 2.1.1
- jruby-19mode
- jruby-head
- rbx-19mode
- rbx-2
matrix:
allow_failures:
- rvm: rbx-19mode
- rvm: rbx-2
- rvm: jruby-head
script: "gem build liquid.gemspec && gem install liquid-3.0.0.gem"
script: "rake test"
notifications:
disable: true

View File

@@ -1,3 +1,8 @@
source 'https://rubygems.org'
gemspec
gem 'stackprof', platforms: :mri_21
group :test do
gem 'spy', '0.4.1'
end

View File

@@ -3,7 +3,9 @@
## 3.0.0 / not yet released / branch "master"
* ...
* Add error messages for missing variables when :strict, see #352 [Daniel Gaiottino]
* Add exception_handler feature, see #397 and #254 [Bogdan Gusiev, bogdan and Florian Weingarten, fw42]
* Optimize variable parsing to avoid repeated regex evaluation during template rendering #383 [Jason Hiltz-Laforge, jasonhl]
* Optimize checking for block interrupts to reduce object allocation #380 [Jason Hiltz-Laforge, jasonhl]
* Properly set context rethrow_errors on render! #349 [Thierry Joyal, tjoyal]
* Fix broken rendering of variables which are equal to false, see #345 [Florian Weingarten, fw42]
* Remove ActionView template handler [Dylan Thacker-Smith, dylanahsmith]
@@ -30,7 +32,13 @@
* Make map filter work on enumerable drops, see #233 [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"
## 2.6.1 / 2014-01-10 / branch "2-6-stable"
Security fix, cherry-picked from master (4e14a65):
* Don't call to_sym when creating conditions for security reasons, see #273 [Bouke van der Bijl, bouk]
* Prevent arbitrary method invocation on condition objects, see #274 [Dylan Thacker-Smith, dylanahsmith]
## 2.6.0 / 2013-11-25
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.
@@ -54,7 +62,13 @@ 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"
## 2.5.5 / 2014-01-10 / branch "2-5-stable"
Security fix, cherry-picked from master (4e14a65):
* Don't call to_sym when creating conditions for security reasons, see #273 [Bouke van der Bijl, bouk]
* Prevent arbitrary method invocation on condition objects, see #274 [Dylan Thacker-Smith, dylanahsmith]
## 2.5.4 / 2013-11-11
* Fix "can't convert Fixnum into String" for "replace", see #173, [wǒ_is神仙, jsw0528]

View File

@@ -3,4 +3,4 @@
<p>It is {{date}}</p>
<p>Check out the <a href="http://localhost:3000/products">Products</a> screen </p>
<p>Check out the <a href="/products">Products</a> screen </p>

View File

@@ -38,6 +38,9 @@ module Liquid
PartialTemplateParser = /#{TagStart}.*?#{TagEnd}|#{VariableStart}.*?#{VariableIncompleteEnd}/om
TemplateParser = /(#{PartialTemplateParser}|#{AnyStartingTag})/om
VariableParser = /\[[^\]]+\]|#{VariableSegment}+\??/o
singleton_class.send(:attr_accessor, :cache_classes)
self.cache_classes = true
end
require "liquid/version"

View File

@@ -1,12 +1,12 @@
module Liquid
class Block < Tag
IsTag = /\A#{TagStart}/o
IsVariable = /\A#{VariableStart}/o
FullToken = /\A#{TagStart}\s*(\w+)\s*(.*)?#{TagEnd}\z/om
ContentOfVariable = /\A#{VariableStart}(.*)#{VariableEnd}\z/om
TAGSTART = "{%".freeze
VARSTART = "{{".freeze
def blank?
@blank || false
@blank
end
def parse(tokens)
@@ -18,41 +18,41 @@ module Liquid
@children = []
while token = tokens.shift
case token
when IsTag
if token =~ FullToken
unless token.empty?
case
when token.start_with?(TAGSTART)
if token =~ FullToken
# if we found the proper block delimiter just end parsing here and let the outer block
# proceed
if block_delimiter == $1
end_tag
return
end
# if we found the proper block delimiter just end parsing here and let the outer block
# proceed
if block_delimiter == $1
end_tag
return
end
# fetch the tag from registered blocks
if tag = Template.tags[$1]
new_tag = tag.parse($1, $2, tokens, @options)
@blank &&= new_tag.blank?
@nodelist << new_tag
@children << new_tag
# fetch the tag from registered blocks
if tag = Template.tags[$1]
new_tag = tag.parse($1, $2, tokens, @options)
@blank &&= new_tag.blank?
@nodelist << new_tag
@children << new_tag
else
# this tag is not registered with the system
# pass it to the current block for special handling or error reporting
unknown_tag($1, $2, tokens)
end
else
# this tag is not registered with the system
# pass it to the current block for special handling or error reporting
unknown_tag($1, $2, tokens)
raise SyntaxError.new(options[:locale].t("errors.syntax.tag_termination".freeze, :token => token, :tag_end => TagEnd.inspect))
end
when token.start_with?(VARSTART)
new_var = create_variable(token)
@nodelist << new_var
@children << new_var
@blank = false
else
raise SyntaxError.new(options[:locale].t("errors.syntax.tag_termination".freeze, :token => token, :tag_end => TagEnd.inspect))
@nodelist << token
@blank &&= (token =~ /\A\s*\z/)
end
when IsVariable
new_var = create_variable(token)
@nodelist << new_var
@children << new_var
@blank = false
when ''.freeze
# pass
else
@nodelist << token
@blank &&= (token =~ /\A\s*\z/)
end
end
@@ -91,14 +91,14 @@ module Liquid
end
end
def block_delimiter
"end#{block_name}"
end
def block_name
@tag_name
end
def block_delimiter
@block_delimiter ||= "end#{block_name}"
end
def create_variable(token)
token.scan(ContentOfVariable) do |content|
return Variable.new(content.first, @options)

View File

@@ -94,12 +94,16 @@ module Liquid
left, right = context[left], context[right]
operation = self.class.operators[op] || raise(ArgumentError.new("Unknown operator #{op}"))
operation = self.class.operators[op] || raise(Liquid::ArgumentError.new("Unknown operator #{op}"))
if operation.respond_to?(:call)
operation.call(self, left, right)
elsif left.respond_to?(operation) and right.respond_to?(operation)
left.send(operation, right)
begin
left.send(operation, right)
rescue ::ArgumentError => e
raise Liquid::ArgumentError.new(e.message)
end
else
nil
end

View File

@@ -14,20 +14,25 @@ module Liquid
# context['bob'] #=> nil class Context
class Context
attr_reader :scopes, :errors, :registers, :environments, :resource_limits
attr_accessor :exception_handler
attr_accessor :rethrow_errors
SQUARE_BRACKETED = /\A\[(.*)\]\z/m
def initialize(environments = {}, outer_scope = {}, registers = {}, rethrow_errors = false, resource_limits = {})
@environments = [environments].flatten
@scopes = [(outer_scope || {})]
@registers = registers
@errors = []
@rethrow_errors = rethrow_errors
@resource_limits = (resource_limits || {}).merge!({ :render_score_current => 0, :assign_score_current => 0 })
squash_instance_assigns_with_environments
if rethrow_errors
self.exception_handler = ->(e) { true }
end
@interrupts = []
@filters = []
@parsed_variables = Hash.new{ |cache, markup| cache[markup] = variable_parse(markup) }
end
def increment_used_resources(key, obj)
@@ -73,7 +78,7 @@ module Liquid
# are there any not handled interrupts?
def has_interrupt?
@interrupts.any?
!@interrupts.empty?
end
# push an interrupt to the stack. this interrupt is considered not handled.
@@ -88,7 +93,8 @@ module Liquid
def handle_error(e)
errors.push(e)
raise if @rethrow_errors
raise if exception_handler && exception_handler.call(e)
case e
when SyntaxError
@@ -191,7 +197,12 @@ module Liquid
# Fetches an object starting at the local scope and then moving up the hierachy
def find_variable(key)
scope = @scopes.find { |s| s.has_key?(key) }
# This was changed from find() to find_index() because this is a very hot
# path and find_index() is optimized in MRI to reduce object allocation
index = @scopes.find_index { |s| s.has_key?(key) }
scope = @scopes[index] if index
variable = nil
if scope.nil?
@@ -205,7 +216,6 @@ module Liquid
end
scope ||= @environments.last || @scopes.last
handle_not_found(key) unless scope.has_key?(key)
variable ||= lookup_and_evaluate(scope, key)
variable = variable.to_liquid
@@ -214,6 +224,16 @@ module Liquid
return variable
end
def variable_parse(markup)
parts = markup.scan(VariableParser)
needs_resolution = false
if parts.first =~ SQUARE_BRACKETED
needs_resolution = true
parts[0] = $1
end
{:first => parts.shift, :needs_resolution => needs_resolution, :rest => parts}
end
# Resolves namespaced queries gracefully.
#
# Example
@@ -221,19 +241,17 @@ module Liquid
# assert_equal 'tobi', @context['hash.name']
# assert_equal 'tobi', @context['hash["name"]']
def variable(markup)
parts = markup.scan(VariableParser)
square_bracketed = /\A\[(.*)\]\z/m
parts = @parsed_variables[markup]
first_part = parts.shift
if first_part =~ square_bracketed
first_part = resolve($1)
first_part = parts[:first]
if parts[:needs_resolution]
first_part = resolve(parts[:first])
end
if object = find_variable(first_part)
parts.each do |part|
part = resolve($1) if part_resolved = (part =~ square_bracketed)
parts[:rest].each do |part|
part = resolve($1) if part_resolved = (part =~ SQUARE_BRACKETED)
# If object is a hash- or array-like object we look for the
# presence of the key and if its available we return it
@@ -255,7 +273,6 @@ module Liquid
# No key was present with the desired value and it wasn't one of the directly supported
# keywords either. The only thing we got left is to return nil
else
handle_not_found(markup)
return nil
end
@@ -285,10 +302,5 @@ module Liquid
end
end
end # squash_instance_assigns_with_environments
def handle_not_found(variable)
@errors << "Variable {{#{variable}}} not found" if Template.error_mode == :strict
end
end # Context
end # Liquid

View File

@@ -92,16 +92,16 @@ module Liquid
# Join elements of the array with certain character between them
def join(input, glue = ' '.freeze)
[input].flatten.join(glue)
InputIterator.new(input).join(glue)
end
# Sort elements of the array
# provide optional property with which to sort an array of hashes or drops
def sort(input, property = nil)
ary = flatten_if_necessary(input)
ary = InputIterator.new(input)
if property.nil?
ary.sort
elsif ary.first.respond_to?('[]'.freeze) and !ary.first[property].nil?
elsif ary.first.respond_to?('[]'.freeze) && !ary.first[property].nil?
ary.sort {|a,b| a[property] <=> b[property] }
elsif ary.first.respond_to?(property)
ary.sort {|a,b| a.send(property) <=> b.send(property) }
@@ -110,13 +110,13 @@ module Liquid
# Reverse the elements of an array
def reverse(input)
ary = [input].flatten
ary = InputIterator.new(input)
ary.reverse
end
# map/collect on a given property
def map(input, property)
flatten_if_necessary(input).map do |e|
InputIterator.new(input).map do |e|
e = e.call if e.is_a?(Proc)
if property == "to_liquid".freeze
@@ -265,17 +265,6 @@ module Liquid
private
def flatten_if_necessary(input)
ary = if input.is_a?(Array)
input.flatten
elsif input.is_a?(Enumerable) && !input.is_a?(Hash)
input
else
[input].flatten
end
ary.map{ |e| e.respond_to?(:to_liquid) ? e.to_liquid : e }
end
def to_number(obj)
case obj
when Float
@@ -310,6 +299,36 @@ module Liquid
result = to_number(input).send(operation, to_number(operand))
result.is_a?(BigDecimal) ? result.to_f : result
end
class InputIterator
include Enumerable
def initialize(input)
@input = if input.is_a?(Array)
input.flatten
elsif input.is_a?(Hash)
[input]
elsif input.is_a?(Enumerable)
input
else
Array(input)
end
end
def join(glue)
to_a.join(glue)
end
def reverse
reverse_each.to_a
end
def each
@input.each do |e|
yield(e.respond_to?(:to_liquid) ? e.to_liquid : e)
end
end
end
end
Template.register_filter(StandardFilters)

View File

@@ -31,7 +31,7 @@ module Liquid
end
def blank?
@blank || false
false
end
def parse_with_selected_parser(markup)
@@ -50,11 +50,12 @@ module Liquid
end
private
def strict_parse_with_error_context(markup)
strict_parse(markup)
rescue SyntaxError => e
e.message << " in \"#{markup.strip}\""
raise e
end
end # Tag
end # Liquid
end
end

View File

@@ -1,5 +1,4 @@
module Liquid
# Capture stores the result of a block into a variable without rendering it inplace.
#
# {% capture heading %}

View File

@@ -43,11 +43,8 @@ module Liquid
end
end
def blank?
false
end
private
def variables_from_string(markup)
markup.split(',').collect do |var|
var =~ /\s*(#{QuotedFragment})\s*/o

View File

@@ -38,10 +38,6 @@ module Liquid
def parse(tokens)
end
def blank?
false
end
def render(context)
partial = load_cached_partial(context)
variable = context[@variable_name || @template_name[1..-2]]

View File

@@ -25,10 +25,6 @@ module Liquid
context.environments.first[@variable] = value + 1
value.to_s
end
def blank?
false
end
end
Template.register_tag('increment'.freeze, Increment)

View File

@@ -21,6 +21,36 @@ module Liquid
attr_accessor :root, :resource_limits
@@file_system = BlankFileSystem.new
class TagRegistry
def initialize
@tags = {}
@cache = {}
end
def [](tag_name)
return nil unless @tags.has_key?(tag_name)
return @cache[tag_name] if Liquid.cache_classes
lookup_class(@tags[tag_name]).tap { |o| @cache[tag_name] = o }
end
def []=(tag_name, klass)
@tags[tag_name] = klass.name
@cache[tag_name] = klass
end
def delete(tag_name)
@tags.delete(tag_name)
@cache.delete(tag_name)
end
private
def lookup_class(name)
name.split("::").reject(&:empty?).reduce(Object) { |scope, const| scope.const_get(const) }
end
end
class << self
# Sets how strict the parser should be.
# :lax acts like liquid 2.5 and silently ignores malformed tags in most cases.
@@ -41,7 +71,7 @@ module Liquid
end
def tags
@tags ||= {}
@tags ||= TagRegistry.new
end
def error_mode
@@ -112,7 +142,11 @@ module Liquid
context = case args.first
when Liquid::Context
c = args.shift
c.rethrow_errors = true if @rethrow_errors
if @rethrow_errors
c.exception_handler = ->(e) { true }
end
c
when Liquid::Drop
drop = args.shift
@@ -137,6 +171,9 @@ module Liquid
context.add_filters(options[:filters])
end
if options[:exception_handler]
context.exception_handler = options[:exception_handler]
end
when Module
context.add_filters(args.pop)
when Array

View File

@@ -36,13 +36,13 @@ module Liquid
def lax_parse(markup)
@filters = []
if match = markup.match(/\s*(#{QuotedFragment})(.*)/om)
@name = match[1]
if match[2].match(/#{FilterSeparator}\s*(.*)/om)
if markup =~ /\s*(#{QuotedFragment})(.*)/om
@name = Regexp.last_match(1)
if Regexp.last_match(2) =~ /#{FilterSeparator}\s*(.*)/om
filters = Regexp.last_match(1).scan(FilterParser)
filters.each do |f|
if matches = f.match(/\s*(\w+)/)
filtername = matches[1]
if f =~ /\s*(\w+)/
filtername = Regexp.last_match(1)
filterargs = f.scan(/(?:#{FilterArgumentSeparator}|#{ArgumentSeparator})\s*((?:\w+\s*\:\s*)?#{QuotedFragment})/o).flatten
@filters << [filtername, filterargs]
end

View File

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

View File

@@ -24,6 +24,5 @@ Gem::Specification.new do |s|
s.require_path = "lib"
s.add_development_dependency 'stackprof' if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("2.1.0")
s.add_development_dependency 'rake'
end

View File

@@ -4,10 +4,14 @@ require File.dirname(__FILE__) + '/theme_runner'
Liquid::Template.error_mode = ARGV.first.to_sym if ARGV.first
profiler = ThemeRunner.new
profiler.run
results = StackProf.run(mode: :cpu) do
100.times do
profiler.run
[:cpu, :object].each do |profile_type|
puts "Profiling in #{profile_type.to_s} mode..."
results = StackProf.run(mode: profile_type) do
100.times do
profiler.run
end
end
StackProf::Report.new(results).print_text(false, 20)
File.write(ENV['FILENAME'] + "." + profile_type.to_s, Marshal.dump(results)) if ENV['FILENAME']
end
StackProf::Report.new(results).print_text(false, 20)
File.write(ENV['FILENAME'], Marshal.dump(results)) if ENV['FILENAME']

View File

@@ -20,4 +20,14 @@ class ContextTest < Test::Unit::TestCase
assert_equal 'Global test', Template.parse("{{'test' | notice }}").render!
assert_equal 'Local test', Template.parse("{{'test' | notice }}").render!({}, :filters => [local])
end
def test_has_key_will_not_add_an_error_for_missing_keys
Template.error_mode = :strict
context = Context.new
context.has_key?('unknown')
assert_empty context.errors
end
end

View File

@@ -67,12 +67,12 @@ class FiltersTest < Test::Unit::TestCase
@context['value'] = 3
@context['numbers'] = [2,1,4,3]
@context['words'] = ['expected', 'as', 'alphabetic']
@context['arrays'] = [['flattened'], ['are']]
@context['arrays'] = ['flower', 'are']
assert_equal [1,2,3,4], Variable.new("numbers | sort").render(@context)
assert_equal ['alphabetic', 'as', 'expected'], Variable.new("words | sort").render(@context)
assert_equal [3], Variable.new("value | sort").render(@context)
assert_equal ['are', 'flattened'], Variable.new("arrays | sort").render(@context)
assert_equal ['are', 'flower'], Variable.new("arrays | sort").render(@context)
end
def test_strip_html

View File

@@ -84,4 +84,11 @@ class ParsingQuirksTest < Test::Unit::TestCase
assert_template_result('',"{% if #{markup} %} YES {% endif %}")
end
end
def test_raise_on_invalid_tag_delimiter
assert_raise(Liquid::SyntaxError) do
Template.new.parse('{% end %}')
end
end
end # ParsingQuirksTest

View File

@@ -7,6 +7,8 @@ class Filters
end
class TestThing
attr_reader :foo
def initialize
@foo = 0
end
@@ -115,6 +117,10 @@ class StandardFiltersTest < Test::Unit::TestCase
assert_equal [{"a" => 1}, {"a" => 2}, {"a" => 3}, {"a" => 4}], @filters.sort([{"a" => 4}, {"a" => 3}, {"a" => 1}, {"a" => 2}], "a")
end
def test_legacy_sort_hash
assert_equal [{a:1, b:2}], @filters.sort({a:1, b:2})
end
def test_numerical_vs_lexicographical_sort
assert_equal [2, 10], @filters.sort([10, 2])
assert_equal [{"a" => 2}, {"a" => 10}], @filters.sort([{"a" => 10}, {"a" => 2}], "a")
@@ -126,6 +132,10 @@ class StandardFiltersTest < Test::Unit::TestCase
assert_equal [4,3,2,1], @filters.reverse([1,2,3,4])
end
def test_legacy_reverse_hash
assert_equal [{a:1, b:2}], @filters.reverse(a:1, b:2)
end
def test_map
assert_equal [1,2,3,4], @filters.map([{"a" => 1}, {"a" => 2}, {"a" => 3}, {"a" => 4}], 'a')
assert_template_result 'abc', "{{ ary | map:'foo' | map:'bar' }}",
@@ -147,9 +157,16 @@ class StandardFiltersTest < Test::Unit::TestCase
"thing" => { "foo" => [ { "bar" => 42 }, { "bar" => 17 } ] }
end
def test_legacy_map_on_hashes_with_dynamic_key
template = "{% assign key = 'foo' %}{{ thing | map: key | map: 'bar' }}"
hash = { "foo" => { "bar" => 42 } }
assert_template_result "42", template, "thing" => hash
end
def test_sort_calls_to_liquid
t = TestThing.new
assert_template_result "woot: 1", '{{ foo | sort: "whatever" }}', "foo" => [t]
Liquid::Template.parse('{{ foo | sort: "whatever" }}').render("foo" => [t])
assert t.foo > 0
end
def test_map_over_proc

View File

@@ -60,10 +60,6 @@ class CustomInclude < Liquid::Tag
def parse(tokens)
end
def blank?
false
end
def render(context)
@template_name[1..-2]
end
@@ -205,4 +201,12 @@ class IncludeTagTest < Test::Unit::TestCase
Liquid::Template.tags['include'] = original_tag
end
end
def test_does_not_add_error_in_strict_mode_for_missing_variable
Liquid::Template.file_system = TestFileSystem.new
a = Liquid::Template.parse(' {% include "nested_template" %}')
a.render!
assert_empty a.errors
end
end # IncludeTagTest

View File

@@ -153,4 +153,18 @@ class TemplateTest < Test::Unit::TestCase
end
assert_equal 'ruby error in drop', e.message
end
def test_exception_handler_doesnt_reraise_if_it_returns_false
exception = nil
Template.parse("{{ 1 | divided_by: 0 }}").render({}, exception_handler: ->(e) { exception = e; false })
assert exception.is_a?(ZeroDivisionError)
end
def test_exception_handler_does_reraise_if_it_returns_true
exception = nil
assert_raises(ZeroDivisionError) do
Template.parse("{{ 1 | divided_by: 0 }}").render({}, exception_handler: ->(e) { exception = e; true })
end
assert exception.is_a?(ZeroDivisionError)
end
end

View File

@@ -2,6 +2,7 @@
require 'test/unit'
require 'test/unit/assertions'
require 'spy/integration'
$:.unshift(File.join(File.expand_path(File.dirname(__FILE__)), '..', 'lib'))
require 'liquid.rb'

View File

@@ -49,6 +49,17 @@ class ConditionUnitTest < Test::Unit::TestCase
assert_evalutes_false "'bob'", 'contains', "'---'"
end
def test_invalid_comparation_operator
assert_evaluates_argument_error "1", '~~', '0'
end
def test_comparation_of_int_and_str
assert_evaluates_argument_error "'1'", '>', '0'
assert_evaluates_argument_error "'1'", '<', '0'
assert_evaluates_argument_error "'1'", '>=', '0'
assert_evaluates_argument_error "'1'", '<=', '0'
end
def test_contains_works_on_arrays
@context = Liquid::Context.new
@context['array'] = [1,2,3,4,5]
@@ -124,4 +135,11 @@ class ConditionUnitTest < Test::Unit::TestCase
assert !Condition.new(left, op, right).evaluate(@context || Liquid::Context.new),
"Evaluated true: #{left} #{op} #{right}"
end
def assert_evaluates_argument_error(left, op, right)
assert_raises(Liquid::ArgumentError) do
Condition.new(left, op, right).evaluate(@context || Liquid::Context.new)
end
end
end # ConditionTest

View File

@@ -70,6 +70,10 @@ class ContextUnitTest < Test::Unit::TestCase
@context = Liquid::Context.new
end
def teardown
Spy.teardown
end
def test_variables
@context['string'] = 'string'
assert_equal 'string', @context['string']
@@ -458,21 +462,25 @@ class ContextUnitTest < Test::Unit::TestCase
assert_equal @context, @context['category'].context
end
def test_strict_variables_not_found
with_error_mode(:strict) do
@context['does_not_exist']
assert(@context.errors.length == 1)
assert_equal(@context.errors[0], 'Variable {{does_not_exist}} not found')
end
end
def test_use_empty_instead_of_any_in_interrupt_handling_to_avoid_lots_of_unnecessary_object_allocations
mock_any = Spy.on_instance_method(Array, :any?)
mock_empty = Spy.on_instance_method(Array, :empty?)
mock_has_interrupt = Spy.on(@context, :has_interrupt?).and_call_through
def test_strict_nested_variables_not_found
with_error_mode(:strict) do
@context['hash'] = {'this' => 'exists'}
@context['hash.does_not_exist']
assert(@context.errors.length == 1)
assert_equal(@context.errors[0], 'Variable {{hash.does_not_exist}} not found')
end
@context.has_interrupt?
refute mock_any.has_been_called?
assert mock_empty.has_been_called?
end
def test_variable_lookup_caches_markup
mock_scan = Spy.on_instance_method(String, :scan).and_return(["string"])
@context['string'] = 'string'
@context['string']
@context['string']
assert_equal 1, mock_scan.calls.size
end
end # ContextTest

View File

@@ -16,4 +16,54 @@ class TemplateUnitTest < Test::Unit::TestCase
assert_instance_of I18n, t.root.options[:locale]
assert_equal fixture("en_locale.yml"), t.root.options[:locale].path
end
def test_with_cache_classes_tags_returns_the_same_class
original_cache_setting = Liquid.cache_classes
Liquid.cache_classes = true
original_klass = Class.new
Object.send(:const_set, :CustomTag, original_klass)
Template.register_tag('custom', CustomTag)
Object.send(:remove_const, :CustomTag)
new_klass = Class.new
Object.send(:const_set, :CustomTag, new_klass)
assert Template.tags['custom'].equal?(original_klass)
ensure
Object.send(:remove_const, :CustomTag)
Template.tags.delete('custom')
Liquid.cache_classes = original_cache_setting
end
def test_without_cache_classes_tags_reloads_the_class
original_cache_setting = Liquid.cache_classes
Liquid.cache_classes = false
original_klass = Class.new
Object.send(:const_set, :CustomTag, original_klass)
Template.register_tag('custom', CustomTag)
Object.send(:remove_const, :CustomTag)
new_klass = Class.new
Object.send(:const_set, :CustomTag, new_klass)
assert Template.tags['custom'].equal?(new_klass)
ensure
Object.send(:remove_const, :CustomTag)
Template.tags.delete('custom')
Liquid.cache_classes = original_cache_setting
end
class FakeTag; end
def test_tags_delete
Template.register_tag('fake', FakeTag)
assert_equal FakeTag, Template.tags['fake']
Template.tags.delete('fake')
assert_nil Template.tags['fake']
end
end