mirror of
https://github.com/kemko/liquid.git
synced 2026-01-02 08:15:41 +03:00
Compare commits
16 Commits
v2.4.0
...
hotfix_bla
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9559d69e11 | ||
|
|
50bd34fd78 | ||
|
|
ee41b3f4a3 | ||
|
|
05d9976e16 | ||
|
|
6c2fde5eea | ||
|
|
ce76dbf8d9 | ||
|
|
661ff2ccdf | ||
|
|
9c183bea83 | ||
|
|
484fd18612 | ||
|
|
bf86459456 | ||
|
|
d2827c561b | ||
|
|
16c34595a4 | ||
|
|
6e091909ee | ||
|
|
d7cb39ccb3 | ||
|
|
f8d46804fd | ||
|
|
5c6de2d919 |
@@ -8,7 +8,7 @@
|
||||
* Ruby 1.8, 1.9, and Rubinius compatibility fixes
|
||||
* Add support for `quoted['references']` in `tablerow`
|
||||
* Add support for Enumerable to `tablerow`
|
||||
* `strip\_html` filter removes html comments
|
||||
* `strip_html` filter removes html comments
|
||||
|
||||
|
||||
## 2.3.0 / 2011-10-16
|
||||
|
||||
8
Rakefile
8
Rakefile
@@ -9,7 +9,7 @@ task :default => 'test'
|
||||
|
||||
Rake::TestTask.new(:test) do |t|
|
||||
t.libs << '.' << 'lib' << 'test'
|
||||
t.pattern = 'test/liquid/**/*_test.rb'
|
||||
t.test_files = FileList['test/liquid/**/*_test.rb']
|
||||
t.verbose = false
|
||||
end
|
||||
|
||||
@@ -27,7 +27,7 @@ namespace :benchmark do
|
||||
|
||||
desc "Run the liquid benchmark"
|
||||
task :run do
|
||||
ruby "performance/benchmark.rb"
|
||||
ruby "./performance/benchmark.rb"
|
||||
end
|
||||
|
||||
end
|
||||
@@ -37,12 +37,12 @@ namespace :profile do
|
||||
|
||||
desc "Run the liquid profile/performance coverage"
|
||||
task :run do
|
||||
ruby "performance/profile.rb"
|
||||
ruby "./performance/profile.rb"
|
||||
end
|
||||
|
||||
desc "Run KCacheGrind"
|
||||
task :grind => :run do
|
||||
system "kcachegrind /tmp/liquid.rubyprof_calltreeprinter.txt"
|
||||
system "qcachegrind /tmp/liquid.rubyprof_calltreeprinter.txt"
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@@ -48,6 +48,7 @@ end
|
||||
require 'liquid/drop'
|
||||
require 'liquid/extensions'
|
||||
require 'liquid/errors'
|
||||
require 'liquid/interrupts'
|
||||
require 'liquid/strainer'
|
||||
require 'liquid/context'
|
||||
require 'liquid/tag'
|
||||
|
||||
@@ -89,13 +89,27 @@ module Liquid
|
||||
end
|
||||
|
||||
def render_all(list, context)
|
||||
list.collect do |token|
|
||||
output = []
|
||||
list.each do |token|
|
||||
# Break out if we have any unhanded interrupts.
|
||||
break if context.has_interrupt?
|
||||
|
||||
begin
|
||||
token.respond_to?(:render) ? token.render(context) : token
|
||||
# If we get an Interrupt that means the block must stop processing. An
|
||||
# Interrupt is any command that stops block execution such as {% break %}
|
||||
# or {% continue %}
|
||||
if token.is_a? Continue or token.is_a? Break
|
||||
context.push_interrupt(token.interrupt)
|
||||
break
|
||||
end
|
||||
|
||||
output << (token.respond_to?(:render) ? token.render(context) : token)
|
||||
rescue ::StandardError => e
|
||||
context.handle_error(e)
|
||||
output << (context.handle_error(e))
|
||||
end
|
||||
end.join
|
||||
end
|
||||
|
||||
output.join
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -22,6 +22,8 @@ module Liquid
|
||||
@errors = []
|
||||
@rethrow_errors = rethrow_errors
|
||||
squash_instance_assigns_with_environments
|
||||
|
||||
@interrupts = []
|
||||
end
|
||||
|
||||
def strainer
|
||||
@@ -41,6 +43,21 @@ module Liquid
|
||||
end
|
||||
end
|
||||
|
||||
# are there any not handled interrupts?
|
||||
def has_interrupt?
|
||||
!@interrupts.empty?
|
||||
end
|
||||
|
||||
# push an interrupt to the stack. this interrupt is considered not handled.
|
||||
def push_interrupt(e)
|
||||
@interrupts.push(e)
|
||||
end
|
||||
|
||||
# pop an interrupt from the stack
|
||||
def pop_interrupt
|
||||
@interrupts.pop
|
||||
end
|
||||
|
||||
def handle_error(e)
|
||||
errors.push(e)
|
||||
raise if @rethrow_errors
|
||||
|
||||
@@ -20,6 +20,9 @@ module Liquid
|
||||
# Your drop can either implement the methods sans any parameters or implement the before_method(name) method which is a
|
||||
# catch all.
|
||||
class Drop
|
||||
EMPTY_STRING = ''.freeze
|
||||
METHOD_BLACKLIST = [:dup, :clone, :singleton_class, :eval, :class_eval, :`, :inspect]
|
||||
|
||||
attr_writer :context
|
||||
|
||||
# Catch all for the method
|
||||
@@ -29,7 +32,7 @@ module Liquid
|
||||
|
||||
# called by liquid to invoke a drop
|
||||
def invoke_drop(method_or_key)
|
||||
if method_or_key && method_or_key != '' && self.class.public_method_defined?(method_or_key.to_s.to_sym)
|
||||
if method_or_key && method_or_key != EMPTY_STRING && self.class.public_method_defined?(method_or_key.to_s.to_sym)
|
||||
send(method_or_key.to_s.to_sym)
|
||||
else
|
||||
before_method(method_or_key)
|
||||
@@ -45,5 +48,9 @@ module Liquid
|
||||
end
|
||||
|
||||
alias :[] :invoke_drop
|
||||
|
||||
METHOD_BLACKLIST.each do |blacklisted|
|
||||
define_method(blacklisted) {nil}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -8,4 +8,4 @@ module Liquid
|
||||
class StandardError < Error; end
|
||||
class SyntaxError < Error; end
|
||||
class StackLevelError < Error; end
|
||||
end
|
||||
end
|
||||
|
||||
17
lib/liquid/interrupts.rb
Normal file
17
lib/liquid/interrupts.rb
Normal file
@@ -0,0 +1,17 @@
|
||||
module Liquid
|
||||
|
||||
# An interrupt is any command that breaks processing of a block (ex: a for loop).
|
||||
class Interrupt
|
||||
attr_reader :message
|
||||
|
||||
def initialize(message=nil)
|
||||
@message = message || "interrupt"
|
||||
end
|
||||
end
|
||||
|
||||
# Interrupt that is thrown whenever a {% break %} is called.
|
||||
class BreakInterrupt < Interrupt; end
|
||||
|
||||
# Interrupt that is thrown whenever a {% continue %} is called.
|
||||
class ContinueInterrupt < Interrupt; end
|
||||
end
|
||||
21
lib/liquid/tags/break.rb
Normal file
21
lib/liquid/tags/break.rb
Normal file
@@ -0,0 +1,21 @@
|
||||
module Liquid
|
||||
|
||||
# Break tag to be used to break out of a for loop.
|
||||
#
|
||||
# == Basic Usage:
|
||||
# {% for item in collection %}
|
||||
# {% if item.condition %}
|
||||
# {% break %}
|
||||
# {% endif %}
|
||||
# {% endfor %}
|
||||
#
|
||||
class Break < Tag
|
||||
|
||||
def interrupt
|
||||
BreakInterrupt.new
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
Template.register_tag('break', Break)
|
||||
end
|
||||
21
lib/liquid/tags/continue.rb
Normal file
21
lib/liquid/tags/continue.rb
Normal file
@@ -0,0 +1,21 @@
|
||||
module Liquid
|
||||
|
||||
# Continue tag to be used to break out of a for loop.
|
||||
#
|
||||
# == Basic Usage:
|
||||
# {% for item in collection %}
|
||||
# {% if item.condition %}
|
||||
# {% continue %}
|
||||
# {% endif %}
|
||||
# {% endfor %}
|
||||
#
|
||||
class Continue < Tag
|
||||
|
||||
def interrupt
|
||||
ContinueInterrupt.new
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
Template.register_tag('continue', Continue)
|
||||
end
|
||||
@@ -69,7 +69,7 @@ module Liquid
|
||||
@nodelist = @else_block = []
|
||||
end
|
||||
|
||||
def render(context)
|
||||
def render(context)
|
||||
context.registers[:for] ||= Hash.new(0)
|
||||
|
||||
collection = context[@collection_name]
|
||||
@@ -101,8 +101,8 @@ module Liquid
|
||||
# Store our progress through the collection for the continue flag
|
||||
context.registers[:for][@name] = from + segment.length
|
||||
|
||||
context.stack do
|
||||
segment.each_with_index do |item, index|
|
||||
context.stack do
|
||||
segment.each_with_index do |item, index|
|
||||
context[@variable_name] = item
|
||||
context['forloop'] = {
|
||||
'name' => @name,
|
||||
@@ -115,6 +115,13 @@ module Liquid
|
||||
'last' => (index == length - 1) }
|
||||
|
||||
result << render_all(@for_block, context)
|
||||
|
||||
# Handle any interrupts if they exist.
|
||||
if context.has_interrupt?
|
||||
interrupt = context.pop_interrupt
|
||||
break if interrupt.is_a? BreakInterrupt
|
||||
next if interrupt.is_a? ContinueInterrupt
|
||||
end
|
||||
end
|
||||
end
|
||||
result
|
||||
|
||||
@@ -11,7 +11,7 @@ module Liquid
|
||||
# {{ user | link }}
|
||||
#
|
||||
class Variable
|
||||
FilterParser = /(?:#{FilterSeparator}|(?:\s*(?!(?:#{FilterSeparator}))(?:#{QuotedFragment}|\S+)\s*)+)/o
|
||||
FilterParser = /(?:#{FilterSeparator}|(?:\s*(?:#{QuotedFragment}|#{ArgumentSeparator})\s*)+)/o
|
||||
attr_accessor :filters, :name
|
||||
|
||||
def initialize(markup)
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
Gem::Specification.new do |s|
|
||||
s.name = "liquid"
|
||||
s.version = "2.4.0"
|
||||
s.version = "2.4.1"
|
||||
s.platform = Gem::Platform::RUBY
|
||||
s.summary = "A secure, non-evaling end user template engine with aesthetic markup."
|
||||
s.authors = ["Tobias Luetke"]
|
||||
|
||||
@@ -5,6 +5,7 @@ require File.dirname(__FILE__) + '/theme_runner'
|
||||
profiler = ThemeRunner.new
|
||||
|
||||
Benchmark.bmbm do |x|
|
||||
x.report("parse & run:") { 10.times { profiler.run(false) } }
|
||||
x.report("parse:") { 100.times { profiler.compile } }
|
||||
x.report("parse & run:") { 100.times { profiler.run } }
|
||||
end
|
||||
|
||||
|
||||
@@ -6,14 +6,14 @@ profiler = ThemeRunner.new
|
||||
|
||||
puts 'Running profiler...'
|
||||
|
||||
results = profiler.run(true)
|
||||
results = profiler.run
|
||||
|
||||
puts 'Success'
|
||||
puts
|
||||
|
||||
[RubyProf::FlatPrinter, RubyProf::GraphPrinter, RubyProf::GraphHtmlPrinter, RubyProf::CallTreePrinter].each do |klass|
|
||||
filename = (ENV['TMP'] || '/tmp') + (klass.name.include?('Html') ? "/liquid.#{klass.name.downcase}.html" : "/liquid.#{klass.name.downcase}.txt")
|
||||
filename = (ENV['TMP'] || '/tmp') + (klass.name.include?('Html') ? "/liquid.#{klass.name.downcase}.html" : "/callgrind.liquid.#{klass.name.downcase}.txt")
|
||||
filename.gsub!(/:+/, '_')
|
||||
File.open(filename, "w+") { |fp| klass.new(results).print(fp) }
|
||||
File.open(filename, "w+") { |fp| klass.new(results).print(fp, :print_file => true) }
|
||||
$stderr.puts "wrote #{klass.name} output to #{filename}"
|
||||
end
|
||||
|
||||
@@ -27,9 +27,34 @@ class ThemeRunner
|
||||
end.compact
|
||||
end
|
||||
|
||||
def compile
|
||||
# Dup assigns because will make some changes to them
|
||||
|
||||
def run(profile = false)
|
||||
RubyProf.measure_mode = RubyProf::WALL_TIME if profile
|
||||
@tests.each do |liquid, layout, template_name|
|
||||
|
||||
tmpl = Liquid::Template.new
|
||||
tmpl.parse(liquid)
|
||||
tmpl = Liquid::Template.new
|
||||
tmpl.parse(layout)
|
||||
end
|
||||
end
|
||||
|
||||
def run
|
||||
# Dup assigns because will make some changes to them
|
||||
assigns = Database.tables.dup
|
||||
|
||||
@tests.each do |liquid, layout, template_name|
|
||||
|
||||
# Compute page_tempalte outside of profiler run, uninteresting to profiler
|
||||
page_template = File.basename(template_name, File.extname(template_name))
|
||||
compile_and_render(liquid, layout, assigns, page_template)
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def run_profile
|
||||
RubyProf.measure_mode = RubyProf::WALL_TIME
|
||||
|
||||
# Dup assigns because will make some changes to them
|
||||
assigns = Database.tables.dup
|
||||
@@ -40,26 +65,27 @@ class ThemeRunner
|
||||
html = nil
|
||||
page_template = File.basename(template_name, File.extname(template_name))
|
||||
|
||||
# Profile compiling and rendering both
|
||||
|
||||
if profile
|
||||
|
||||
RubyProf.resume do
|
||||
html = compile_and_render(liquid, layout, assigns, page_template)
|
||||
end
|
||||
|
||||
else
|
||||
html = compile_and_render(liquid, layout, assigns, page_template)
|
||||
unless @started
|
||||
RubyProf.start
|
||||
RubyProf.pause
|
||||
@started = true
|
||||
end
|
||||
|
||||
html = nil
|
||||
|
||||
RubyProf.resume
|
||||
html = compile_and_render(liquid, layout, assigns, page_template)
|
||||
RubyProf.pause
|
||||
|
||||
|
||||
# return the result and the MD5 of the content, this can be used to detect regressions between liquid version
|
||||
$stdout.puts "* rendered template %s, content: %s" % [template_name, Digest::MD5.hexdigest(html)] if profile
|
||||
$stdout.puts "* rendered template %s, content: %s" % [template_name, Digest::MD5.hexdigest(html)]
|
||||
|
||||
# Uncomment to dump html files to /tmp so that you can inspect for errors
|
||||
# File.open("/tmp/#{File.basename(template_name)}.html", "w+") { |fp| fp <<html}
|
||||
end
|
||||
|
||||
RubyProf.stop if profile
|
||||
RubyProf.stop
|
||||
end
|
||||
|
||||
def compile_and_render(template, layout, assigns, page_template)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
require 'test_helper'
|
||||
|
||||
class VariableTest < Test::Unit::TestCase
|
||||
class BlockTest < Test::Unit::TestCase
|
||||
include Liquid
|
||||
|
||||
def test_blankspace
|
||||
|
||||
16
test/liquid/tags/break_tag_test.rb
Normal file
16
test/liquid/tags/break_tag_test.rb
Normal file
@@ -0,0 +1,16 @@
|
||||
require 'test_helper'
|
||||
|
||||
class BreakTagTest < Test::Unit::TestCase
|
||||
include Liquid
|
||||
|
||||
# tests that no weird errors are raised if break is called outside of a
|
||||
# block
|
||||
def test_break_with_no_block
|
||||
assigns = {'i' => 1}
|
||||
markup = '{% break %}'
|
||||
expected = ''
|
||||
|
||||
assert_template_result(expected, markup, assigns)
|
||||
end
|
||||
|
||||
end
|
||||
16
test/liquid/tags/continue_tag_test.rb
Normal file
16
test/liquid/tags/continue_tag_test.rb
Normal file
@@ -0,0 +1,16 @@
|
||||
require 'test_helper'
|
||||
|
||||
class ContinueTagTest < Test::Unit::TestCase
|
||||
include Liquid
|
||||
|
||||
# tests that no weird errors are raised if continue is called outside of a
|
||||
# block
|
||||
def test_continue_with_no_block
|
||||
assigns = {}
|
||||
markup = '{% continue %}'
|
||||
expected = ''
|
||||
|
||||
assert_template_result(expected, markup, assigns)
|
||||
end
|
||||
|
||||
end
|
||||
@@ -168,6 +168,88 @@ HERE
|
||||
assert_template_result(expected,markup,assigns)
|
||||
end
|
||||
|
||||
def test_for_with_break
|
||||
assigns = {'array' => {'items' => [1,2,3,4,5,6,7,8,9,10]}}
|
||||
|
||||
markup = '{% for i in array.items %}{% break %}{% endfor %}'
|
||||
expected = ""
|
||||
assert_template_result(expected,markup,assigns)
|
||||
|
||||
markup = '{% for i in array.items %}{{ i }}{% break %}{% endfor %}'
|
||||
expected = "1"
|
||||
assert_template_result(expected,markup,assigns)
|
||||
|
||||
markup = '{% for i in array.items %}{% break %}{{ i }}{% endfor %}'
|
||||
expected = ""
|
||||
assert_template_result(expected,markup,assigns)
|
||||
|
||||
markup = '{% for i in array.items %}{{ i }}{% if i > 3 %}{% break %}{% endif %}{% endfor %}'
|
||||
expected = "1234"
|
||||
assert_template_result(expected,markup,assigns)
|
||||
|
||||
# tests to ensure it only breaks out of the local for loop
|
||||
# and not all of them.
|
||||
assigns = {'array' => [[1,2],[3,4],[5,6]] }
|
||||
markup = '{% for item in array %}' +
|
||||
'{% for i in item %}' +
|
||||
'{% if i == 1 %}' +
|
||||
'{% break %}' +
|
||||
'{% endif %}' +
|
||||
'{{ i }}' +
|
||||
'{% endfor %}' +
|
||||
'{% endfor %}'
|
||||
expected = '3456'
|
||||
assert_template_result(expected, markup, assigns)
|
||||
|
||||
# test break does nothing when unreached
|
||||
assigns = {'array' => {'items' => [1,2,3,4,5]}}
|
||||
markup = '{% for i in array.items %}{% if i == 9999 %}{% break %}{% endif %}{{ i }}{% endfor %}'
|
||||
expected = '12345'
|
||||
assert_template_result(expected, markup, assigns)
|
||||
end
|
||||
|
||||
def test_for_with_continue
|
||||
assigns = {'array' => {'items' => [1,2,3,4,5]}}
|
||||
|
||||
markup = '{% for i in array.items %}{% continue %}{% endfor %}'
|
||||
expected = ""
|
||||
assert_template_result(expected,markup,assigns)
|
||||
|
||||
markup = '{% for i in array.items %}{{ i }}{% continue %}{% endfor %}'
|
||||
expected = "12345"
|
||||
assert_template_result(expected,markup,assigns)
|
||||
|
||||
markup = '{% for i in array.items %}{% continue %}{{ i }}{% endfor %}'
|
||||
expected = ""
|
||||
assert_template_result(expected,markup,assigns)
|
||||
|
||||
markup = '{% for i in array.items %}{% if i > 3 %}{% continue %}{% endif %}{{ i }}{% endfor %}'
|
||||
expected = "123"
|
||||
assert_template_result(expected,markup,assigns)
|
||||
|
||||
markup = '{% for i in array.items %}{% if i == 3 %}{% continue %}{% else %}{{ i }}{% endif %}{% endfor %}'
|
||||
expected = "1245"
|
||||
assert_template_result(expected,markup,assigns)
|
||||
|
||||
# tests to ensure it only continues the local for loop and not all of them.
|
||||
assigns = {'array' => [[1,2],[3,4],[5,6]] }
|
||||
markup = '{% for item in array %}' +
|
||||
'{% for i in item %}' +
|
||||
'{% if i == 1 %}' +
|
||||
'{% continue %}' +
|
||||
'{% endif %}' +
|
||||
'{{ i }}' +
|
||||
'{% endfor %}' +
|
||||
'{% endfor %}'
|
||||
expected = '23456'
|
||||
assert_template_result(expected, markup, assigns)
|
||||
|
||||
# test continue does nothing when unreached
|
||||
assigns = {'array' => {'items' => [1,2,3,4,5]}}
|
||||
markup = '{% for i in array.items %}{% if i == 9999 %}{% continue %}{% endif %}{{ i }}{% endfor %}'
|
||||
expected = '12345'
|
||||
assert_template_result(expected, markup, assigns)
|
||||
end
|
||||
|
||||
def test_for_tag_string
|
||||
# ruby 1.8.7 "String".each => Enumerator with single "String" element.
|
||||
|
||||
@@ -66,6 +66,10 @@ class VariableTest < Test::Unit::TestCase
|
||||
var = Variable.new('hello|textileze|paragraph')
|
||||
assert_equal 'hello', var.name
|
||||
assert_equal [[:textileze,[]], [:paragraph,[]]], var.filters
|
||||
|
||||
var = Variable.new("hello|replace:'foo','bar'|textileze")
|
||||
assert_equal 'hello', var.name
|
||||
assert_equal [[:replace, ["'foo'", "'bar'"]], [:textileze, []]], var.filters
|
||||
end
|
||||
|
||||
def test_symbol
|
||||
|
||||
Reference in New Issue
Block a user