mirror of
https://github.com/kemko/liquid.git
synced 2026-01-02 00:05:42 +03:00
Compare commits
3 Commits
benchmark-
...
warning-li
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
10173c3315 | ||
|
|
1c1aa4094a | ||
|
|
627ef9e29d |
@@ -3,8 +3,6 @@
|
||||
## 3.0.0 / not yet released / branch "master"
|
||||
|
||||
* ...
|
||||
* Fixed condition with wrong data types, see #423 [Bogdan Gusiev]
|
||||
* Add url_encode to standard filters, see #421 [Derrick Reimer, djreimer]
|
||||
* Add uniq to standard filters [Florian Weingarten, fw42]
|
||||
* 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]
|
||||
|
||||
@@ -15,9 +15,7 @@ module Liquid
|
||||
'>'.freeze => :>,
|
||||
'>='.freeze => :>=,
|
||||
'<='.freeze => :<=,
|
||||
'contains'.freeze => lambda { |cond, left, right|
|
||||
left && right && left.respond_to?(:include?) ? left.include?(right) : false
|
||||
}
|
||||
'contains'.freeze => lambda { |cond, left, right| left && right ? left.include?(right) : false }
|
||||
}
|
||||
|
||||
def self.operators
|
||||
|
||||
@@ -16,14 +16,12 @@ module Liquid
|
||||
attr_reader :scopes, :errors, :registers, :environments, :resource_limits
|
||||
attr_accessor :exception_handler
|
||||
|
||||
def initialize(environments = {}, outer_scope = {}, registers = {}, rethrow_errors = false, resource_limits = nil)
|
||||
def initialize(environments = {}, outer_scope = {}, registers = {}, rethrow_errors = false, resource_limits = {})
|
||||
@environments = [environments].flatten
|
||||
@scopes = [(outer_scope || {})]
|
||||
@registers = registers
|
||||
@errors = []
|
||||
@resource_limits = resource_limits || Template.default_resource_limits
|
||||
@resource_limits[:render_score_current] = 0
|
||||
@resource_limits[:assign_score_current] = 0
|
||||
@resource_limits = (resource_limits || {}).merge!({ :render_score_current => 0, :assign_score_current => 0 })
|
||||
@parsed_expression = Hash.new{ |cache, markup| cache[markup] = Expression.parse(markup) }
|
||||
squash_instance_assigns_with_environments
|
||||
|
||||
@@ -94,11 +92,8 @@ module Liquid
|
||||
end
|
||||
|
||||
|
||||
def handle_error(e, token=nil)
|
||||
if e.is_a?(Liquid::Error)
|
||||
e.set_line_number_from_token(token)
|
||||
end
|
||||
|
||||
def handle_error(e, token)
|
||||
e = Liquid::Error.error_with_line_number(e, token)
|
||||
errors.push(e)
|
||||
raise if exception_handler && exception_handler.call(e)
|
||||
Liquid::Error.render(e)
|
||||
|
||||
@@ -1,51 +1,34 @@
|
||||
module Liquid
|
||||
class Error < ::StandardError
|
||||
attr_accessor :line_number
|
||||
attr_accessor :markup_context
|
||||
|
||||
def to_s(with_prefix=true)
|
||||
str = ""
|
||||
str << message_prefix if with_prefix
|
||||
str << super()
|
||||
|
||||
if markup_context
|
||||
str << " "
|
||||
str << markup_context
|
||||
def self.render(e)
|
||||
msg = if e.is_a?(Liquid::Error) && e.line_number
|
||||
"#{e.line_number}: #{e.message}"
|
||||
else
|
||||
e.message
|
||||
end
|
||||
|
||||
str
|
||||
case e
|
||||
when SyntaxError
|
||||
"Liquid syntax error: #{msg}"
|
||||
else
|
||||
"Liquid error: #{msg}"
|
||||
end
|
||||
end
|
||||
|
||||
def self.error_with_line_number(e, token)
|
||||
if e.is_a?(Liquid::Error)
|
||||
e.set_line_number_from_token(token)
|
||||
end
|
||||
|
||||
e
|
||||
end
|
||||
|
||||
def set_line_number_from_token(token)
|
||||
return unless token.respond_to?(:line_number)
|
||||
self.line_number = token.line_number
|
||||
end
|
||||
|
||||
def self.render(e)
|
||||
if e.is_a?(Liquid::Error)
|
||||
e.to_s
|
||||
else
|
||||
"Liquid error: #{e.to_s}"
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def message_prefix
|
||||
str = ""
|
||||
if is_a?(SyntaxError)
|
||||
str << "Liquid syntax error"
|
||||
else
|
||||
str << "Liquid error"
|
||||
end
|
||||
|
||||
if line_number
|
||||
str << " (line #{line_number})"
|
||||
end
|
||||
|
||||
str << ": "
|
||||
str
|
||||
end
|
||||
end
|
||||
|
||||
class ArgumentError < Error; end
|
||||
|
||||
@@ -8,7 +8,7 @@ module Liquid
|
||||
begin
|
||||
return strict_parse_with_error_context(markup)
|
||||
rescue SyntaxError => e
|
||||
e.set_line_number_from_token(markup)
|
||||
e.line_number = markup.line_number if markup.is_a?(Token)
|
||||
@warnings ||= []
|
||||
@warnings << e
|
||||
return lax_parse(markup)
|
||||
@@ -20,12 +20,12 @@ module Liquid
|
||||
def strict_parse_with_error_context(markup)
|
||||
strict_parse(markup)
|
||||
rescue SyntaxError => e
|
||||
e.markup_context = markup_context(markup)
|
||||
e.message << markup_context(markup)
|
||||
raise e
|
||||
end
|
||||
|
||||
def markup_context(markup)
|
||||
"in \"#{markup.strip}\""
|
||||
" in \"#{markup.strip}\""
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -42,10 +42,6 @@ module Liquid
|
||||
input.to_s.gsub(HTML_ESCAPE_ONCE_REGEXP, HTML_ESCAPE)
|
||||
end
|
||||
|
||||
def url_encode(input)
|
||||
CGI.escape(input) rescue input
|
||||
end
|
||||
|
||||
def slice(input, offset, length=nil)
|
||||
offset = Integer(offset)
|
||||
length = length ? Integer(length) : 1
|
||||
|
||||
@@ -69,7 +69,7 @@ module Liquid
|
||||
return cached
|
||||
end
|
||||
source = read_template_from_file_system(context)
|
||||
partial = Liquid::Template.parse(source, pass_options)
|
||||
partial = Liquid::Template.parse(source)
|
||||
cached_partials[template_name] = partial
|
||||
context.registers[:cached_partials] = cached_partials
|
||||
partial
|
||||
@@ -88,16 +88,6 @@ module Liquid
|
||||
raise ArgumentError, "file_system.read_template_file expects two parameters: (template_name, context)"
|
||||
end
|
||||
end
|
||||
|
||||
def pass_options
|
||||
dont_pass = @options[:include_options_blacklist]
|
||||
return {locale: @options[:locale]} if dont_pass == true
|
||||
opts = @options.merge(included: true, include_options_blacklist: false)
|
||||
if dont_pass.is_a?(Array)
|
||||
dont_pass.each {|o| opts.delete(o)}
|
||||
end
|
||||
opts
|
||||
end
|
||||
end
|
||||
|
||||
Template.register_tag('include'.freeze, Include)
|
||||
|
||||
@@ -86,10 +86,6 @@ module Liquid
|
||||
Strainer.global_filter(mod)
|
||||
end
|
||||
|
||||
def default_resource_limits
|
||||
@default_resource_limits ||= {}
|
||||
end
|
||||
|
||||
# creates a new <tt>Template</tt> object from liquid source code
|
||||
# To enable profiling, pass in <tt>profile: true</tt> as an option.
|
||||
# See Liquid::Profiler for more information
|
||||
@@ -99,16 +95,16 @@ module Liquid
|
||||
end
|
||||
end
|
||||
|
||||
# creates a new <tt>Template</tt> from an array of tokens. Use <tt>Template.parse</tt> instead
|
||||
def initialize
|
||||
@resource_limits = self.class.default_resource_limits.dup
|
||||
@resource_limits = {}
|
||||
end
|
||||
|
||||
# Parse source code.
|
||||
# Returns self for easy chaining
|
||||
def parse(source, options = {})
|
||||
@options = options
|
||||
@profiling = options[:profile]
|
||||
@line_numbers = options[:line_numbers] || @profiling
|
||||
@profiling = options.delete(:profile)
|
||||
@line_numbers = options.delete(:line_numbers) || @profiling
|
||||
@root = Document.parse(tokenize(source), DEFAULT_OPTIONS.merge(options))
|
||||
@warnings = nil
|
||||
self
|
||||
@@ -201,7 +197,7 @@ module Liquid
|
||||
end
|
||||
result.respond_to?(:join) ? result.join : result
|
||||
rescue Liquid::MemoryError => e
|
||||
context.handle_error(e)
|
||||
context.handle_error(e, nil)
|
||||
ensure
|
||||
@errors = context.errors
|
||||
end
|
||||
@@ -239,7 +235,7 @@ module Liquid
|
||||
end
|
||||
|
||||
def with_profiling
|
||||
if @profiling && !@options[:included]
|
||||
if @profiling
|
||||
@profiler = Profiler.new
|
||||
@profiler.start
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ module Liquid
|
||||
end
|
||||
|
||||
def markup_context(markup)
|
||||
"in \"{{#{markup}}}\""
|
||||
" in \"{{#{markup}}}\""
|
||||
end
|
||||
|
||||
def lax_parse(markup)
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
require 'benchmark'
|
||||
require 'benchmark/ips'
|
||||
require File.dirname(__FILE__) + '/theme_runner'
|
||||
|
||||
Liquid::Template.error_mode = ARGV.first.to_sym if ARGV.first
|
||||
profiler = ThemeRunner.new
|
||||
|
||||
N = 100
|
||||
Benchmark.bmbm do |x|
|
||||
x.report("parse:") { N.times { profiler.parse } }
|
||||
x.report("marshal load:") { N.times { profiler.marshal_load } }
|
||||
x.report("render:") { N.times { profiler.render } }
|
||||
x.report("marshal load & render:") { N.times { profiler.load_and_render } }
|
||||
x.report("parse & render:") { N.times { profiler.parse_and_render } }
|
||||
Benchmark.ips do |x|
|
||||
x.time = 60
|
||||
x.warmup = 5
|
||||
|
||||
puts
|
||||
puts "Running benchmark for #{x.time} seconds (with #{x.warmup} seconds warmup)."
|
||||
puts
|
||||
|
||||
x.report("parse:") { profiler.compile }
|
||||
x.report("parse & run:") { profiler.run }
|
||||
end
|
||||
|
||||
@@ -3,13 +3,13 @@ require File.dirname(__FILE__) + '/theme_runner'
|
||||
|
||||
Liquid::Template.error_mode = ARGV.first.to_sym if ARGV.first
|
||||
profiler = ThemeRunner.new
|
||||
profiler.parse_and_render
|
||||
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.parse_and_render
|
||||
profiler.run
|
||||
end
|
||||
end
|
||||
StackProf::Report.new(results).print_text(false, 20)
|
||||
|
||||
@@ -32,59 +32,45 @@ class ThemeRunner
|
||||
|
||||
[File.read(test), (File.file?(theme_path) ? File.read(theme_path) : nil), test]
|
||||
end.compact
|
||||
@parsed = @tests.map do |liquid, layout, template_name|
|
||||
[Liquid::Template.parse(liquid), Liquid::Template.parse(layout), template_name]
|
||||
end
|
||||
@marshaled = @parsed.map do |liquid, layout, template_name|
|
||||
[Marshal.dump(liquid), Marshal.dump(layout), template_name]
|
||||
end
|
||||
end
|
||||
|
||||
def parse
|
||||
def compile
|
||||
# Dup assigns because will make some changes to them
|
||||
|
||||
@tests.each do |liquid, layout, template_name|
|
||||
Liquid::Template.parse(liquid)
|
||||
Liquid::Template.parse(layout)
|
||||
|
||||
tmpl = Liquid::Template.new
|
||||
tmpl.parse(liquid)
|
||||
tmpl = Liquid::Template.new
|
||||
tmpl.parse(layout)
|
||||
end
|
||||
end
|
||||
|
||||
def marshal_load
|
||||
@marshaled.each do |liquid, layout, template_name|
|
||||
Marshal.load(liquid)
|
||||
Marshal.load(layout)
|
||||
end
|
||||
end
|
||||
|
||||
def render
|
||||
@parsed.each do |liquid, layout, template_name|
|
||||
render_once(liquid, layout, template_name)
|
||||
end
|
||||
end
|
||||
|
||||
def load_and_render
|
||||
@marshaled.each do |liquid, layout, template_name|
|
||||
render_once(Marshal.load(liquid), Marshal.load(layout), template_name)
|
||||
end
|
||||
end
|
||||
|
||||
def parse_and_render
|
||||
@tests.each do |liquid, layout, template_name|
|
||||
render_once(Liquid::Template.parse(liquid), Liquid::Template.parse(layout), template_name)
|
||||
end
|
||||
end
|
||||
|
||||
def render_once(template, layout, template_name)
|
||||
def run
|
||||
# Dup assigns because will make some changes to them
|
||||
assigns = Database.tables.dup
|
||||
|
||||
assigns['page_title'] = 'Page title'
|
||||
assigns['template'] = File.basename(template_name, File.extname(template_name))
|
||||
template.registers[:file_system] = ThemeRunner::FileSystem.new(File.dirname(template_name))
|
||||
@tests.each do |liquid, layout, template_name|
|
||||
|
||||
content_for_layout = template.render!(assigns)
|
||||
# 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, template_name)
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def compile_and_render(template, layout, assigns, page_template, template_file)
|
||||
tmpl = Liquid::Template.new
|
||||
tmpl.assigns['page_title'] = 'Page title'
|
||||
tmpl.assigns['template'] = page_template
|
||||
tmpl.registers[:file_system] = ThemeRunner::FileSystem.new(File.dirname(template_file))
|
||||
|
||||
content_for_layout = tmpl.parse(template).render!(assigns)
|
||||
|
||||
if layout
|
||||
assigns['content_for_layout'] = content_for_layout
|
||||
layout.render!(assigns)
|
||||
tmpl.parse(layout).render!(assigns)
|
||||
else
|
||||
content_for_layout
|
||||
end
|
||||
|
||||
@@ -40,13 +40,13 @@ class ErrorHandlingTest < Minitest::Test
|
||||
expected = <<-TEXT
|
||||
Hello,
|
||||
|
||||
Liquid error (line 3): standard error will raise a standard error.
|
||||
Liquid error: 3: standard error will raise a standard error.
|
||||
|
||||
Bla bla test.
|
||||
|
||||
Liquid syntax error (line 7): syntax error will raise a syntax error.
|
||||
Liquid syntax error: 7: syntax error will raise a syntax error.
|
||||
|
||||
This is an argument error: Liquid error (line 9): argument error
|
||||
This is an argument error: Liquid error: 9: argument error
|
||||
|
||||
Bla.
|
||||
TEXT
|
||||
@@ -104,28 +104,25 @@ class ErrorHandlingTest < Minitest::Test
|
||||
err = assert_raises(SyntaxError) do
|
||||
Liquid::Template.parse(' {% if 1 =! 2 %}ok{% endif %} ', :error_mode => :strict)
|
||||
end
|
||||
assert_equal 'Liquid syntax error: Unexpected character = in "1 =! 2"', err.message
|
||||
assert_equal 'Unexpected character = in "1 =! 2"', err.message
|
||||
|
||||
err = assert_raises(SyntaxError) do
|
||||
Liquid::Template.parse('{{%%%}}', :error_mode => :strict)
|
||||
end
|
||||
assert_equal 'Liquid syntax error: Unexpected character % in "{{%%%}}"', err.message
|
||||
assert_equal 'Unexpected character % in "{{%%%}}"', err.message
|
||||
end
|
||||
|
||||
def test_warnings
|
||||
template = Liquid::Template.parse('{% if ~~~ %}{{%%%}}{% else %}{{ hello. }}{% endif %}', :error_mode => :warn)
|
||||
assert_equal 3, template.warnings.size
|
||||
assert_equal 'Unexpected character ~ in "~~~"', template.warnings[0].to_s(false)
|
||||
assert_equal 'Unexpected character % in "{{%%%}}"', template.warnings[1].to_s(false)
|
||||
assert_equal 'Expected id but found end_of_string in "{{ hello. }}"', template.warnings[2].to_s(false)
|
||||
assert_equal 'Unexpected character ~ in "~~~"', template.warnings[0].message
|
||||
assert_equal 'Unexpected character % in "{{%%%}}"', template.warnings[1].message
|
||||
assert_equal 'Expected id but found end_of_string in "{{ hello. }}"', template.warnings[2].message
|
||||
assert_equal '', template.render
|
||||
end
|
||||
|
||||
def test_warning_line_numbers
|
||||
template = Liquid::Template.parse("{% if ~~~ %}\n{{%%%}}{% else %}\n{{ hello. }}{% endif %}", :error_mode => :warn, :line_numbers => true)
|
||||
assert_equal 'Liquid syntax error (line 1): Unexpected character ~ in "~~~"', template.warnings[0].message
|
||||
assert_equal 'Liquid syntax error (line 2): Unexpected character % in "{{%%%}}"', template.warnings[1].message
|
||||
assert_equal 'Liquid syntax error (line 3): Expected id but found end_of_string in "{{ hello. }}"', template.warnings[2].message
|
||||
assert_equal 3, template.warnings.size
|
||||
assert_equal [1,2,3], template.warnings.map(&:line_number)
|
||||
end
|
||||
|
||||
@@ -48,18 +48,6 @@ class RenderProfilingTest < Minitest::Test
|
||||
assert_equal 2, t.profiler[1].line_number
|
||||
end
|
||||
|
||||
def test_profiling_includes_line_numbers_of_included_partials
|
||||
t = Template.parse("{% include 'a_template' %}", :profile => true)
|
||||
t.render!
|
||||
|
||||
included_children = t.profiler[0].children
|
||||
|
||||
# {% assign template_name = 'a_template' %}
|
||||
assert_equal 1, included_children[0].line_number
|
||||
# {{ template_name }}
|
||||
assert_equal 2, included_children[1].line_number
|
||||
end
|
||||
|
||||
def test_profiling_times_the_rendering_of_tokens
|
||||
t = Template.parse("{% include 'a_template' %}", :profile => true)
|
||||
t.render!
|
||||
|
||||
@@ -118,11 +118,6 @@ class StandardFiltersTest < Minitest::Test
|
||||
assert_equal '<strong>Hulk</strong>', @filters.escape_once('<strong>Hulk</strong>')
|
||||
end
|
||||
|
||||
def test_url_encode
|
||||
assert_equal 'foo%2B1%40example.com', @filters.url_encode('foo+1@example.com')
|
||||
assert_equal nil, @filters.url_encode(nil)
|
||||
end
|
||||
|
||||
def test_truncatewords
|
||||
assert_equal 'one two three', @filters.truncatewords('one two three', 4)
|
||||
assert_equal 'one two...', @filters.truncatewords('one two three', 2)
|
||||
|
||||
@@ -132,7 +132,7 @@ class IncludeTagTest < Minitest::Test
|
||||
|
||||
Liquid::Template.file_system = infinite_file_system.new
|
||||
|
||||
assert_raises(Liquid::StackLevelError, SystemStackError) do
|
||||
assert_raises(Liquid::StackLevelError) do
|
||||
Template.parse("{% include 'loop' %}").render!
|
||||
end
|
||||
|
||||
@@ -209,19 +209,4 @@ class IncludeTagTest < Minitest::Test
|
||||
a.render!
|
||||
assert_empty a.errors
|
||||
end
|
||||
|
||||
def test_passing_options_to_included_templates
|
||||
assert_raises(Liquid::SyntaxError) do
|
||||
Template.parse("{% include template %}", error_mode: :strict).render!("template" => '{{ "X" || downcase }}')
|
||||
end
|
||||
with_error_mode(:lax) do
|
||||
assert_equal 'x', Template.parse("{% include template %}", error_mode: :strict, include_options_blacklist: true).render!("template" => '{{ "X" || downcase }}')
|
||||
end
|
||||
assert_raises(Liquid::SyntaxError) do
|
||||
Template.parse("{% include template %}", error_mode: :strict, include_options_blacklist: [:locale]).render!("template" => '{{ "X" || downcase }}')
|
||||
end
|
||||
with_error_mode(:lax) do
|
||||
assert_equal 'x', Template.parse("{% include template %}", error_mode: :strict, include_options_blacklist: [:error_mode]).render!("template" => '{{ "X" || downcase }}')
|
||||
end
|
||||
end
|
||||
end # IncludeTagTest
|
||||
|
||||
@@ -80,10 +80,6 @@ class ConditionUnitTest < Minitest::Test
|
||||
assert_evalutes_false "0", 'contains', 'not_assigned'
|
||||
end
|
||||
|
||||
def test_contains_return_false_on_wrong_data_type
|
||||
assert_evalutes_false "1", 'contains', '0'
|
||||
end
|
||||
|
||||
def test_or_condition
|
||||
condition = Condition.new('1', '==', '2')
|
||||
|
||||
|
||||
Reference in New Issue
Block a user