Compare commits

..

4 Commits

Author SHA1 Message Date
Mike Angell
273a9f7a97 Prefer attr_reader 2019-10-09 03:57:06 +11:00
Mike Angell
fbe14cd0b7 Resolve InputIterator dropping context 2019-10-09 03:28:09 +11:00
uchoudh
ffadc64f28 Merge pull request #1172 from Shopify/add-liquid-profiling-attr
Add liquid profile attributes
2019-10-08 10:49:54 -04:00
uchoudh
fefee4c675 Add liquid profile attributes
Attribute testing

Add partial name support
2019-10-03 10:12:39 -04:00
9 changed files with 60 additions and 91 deletions

View File

@@ -46,7 +46,7 @@ module Liquid
include Enumerable include Enumerable
class Timing class Timing
attr_reader :code, :partial, :line_number, :children attr_reader :code, :partial, :line_number, :children, :total_time, :self_time
def initialize(node, partial) def initialize(node, partial)
@code = node.respond_to?(:raw) ? node.raw : node @code = node.respond_to?(:raw) ? node.raw : node
@@ -65,6 +65,17 @@ module Liquid
def finish def finish
@end_time = Time.now @end_time = Time.now
@total_time = @end_time - @start_time
if @children.empty?
@self_time = @total_time
else
total_children_time = 0
@children.each do |child|
total_children_time += child.total_time
end
@self_time = @total_time - total_children_time
end
end end
def render_time def render_time
@@ -98,8 +109,8 @@ module Liquid
Thread.current[:liquid_profiler] Thread.current[:liquid_profiler]
end end
def initialize def initialize(partial_name = "<root>")
@partial_stack = ["<root>"] @partial_stack = [partial_name]
@root_timing = Timing.new("", current_partial) @root_timing = Timing.new("", current_partial)
@timing_stack = [@root_timing] @timing_stack = [@root_timing]

View File

@@ -128,13 +128,13 @@ module Liquid
# Join elements of the array with certain character between them # Join elements of the array with certain character between them
def join(input, glue = ' ') def join(input, glue = ' ')
InputIterator.new(input).join(glue) InputIterator.new(input, context).join(glue)
end end
# Sort elements of the array # Sort elements of the array
# provide optional property with which to sort an array of hashes or drops # provide optional property with which to sort an array of hashes or drops
def sort(input, property = nil) def sort(input, property = nil)
ary = InputIterator.new(input) ary = InputIterator.new(input, context)
return [] if ary.empty? return [] if ary.empty?
@@ -154,7 +154,7 @@ module Liquid
# Sort elements of an array ignoring case if strings # Sort elements of an array ignoring case if strings
# provide optional property with which to sort an array of hashes or drops # provide optional property with which to sort an array of hashes or drops
def sort_natural(input, property = nil) def sort_natural(input, property = nil)
ary = InputIterator.new(input) ary = InputIterator.new(input, context)
return [] if ary.empty? return [] if ary.empty?
@@ -174,7 +174,7 @@ module Liquid
# Filter the elements of an array to those with a certain property value. # Filter the elements of an array to those with a certain property value.
# By default the target is any truthy value. # By default the target is any truthy value.
def where(input, property, target_value = nil) def where(input, property, target_value = nil)
ary = InputIterator.new(input) ary = InputIterator.new(input, context)
if ary.empty? if ary.empty?
[] []
@@ -196,7 +196,7 @@ module Liquid
# Remove duplicate elements from an array # Remove duplicate elements from an array
# provide optional property with which to determine uniqueness # provide optional property with which to determine uniqueness
def uniq(input, property = nil) def uniq(input, property = nil)
ary = InputIterator.new(input) ary = InputIterator.new(input, context)
if property.nil? if property.nil?
ary.uniq ary.uniq
@@ -213,13 +213,13 @@ module Liquid
# Reverse the elements of an array # Reverse the elements of an array
def reverse(input) def reverse(input)
ary = InputIterator.new(input) ary = InputIterator.new(input, context)
ary.reverse ary.reverse
end end
# map/collect on a given property # map/collect on a given property
def map(input, property) def map(input, property)
InputIterator.new(input).map do |e| InputIterator.new(input, context).map do |e|
e = e.call if e.is_a?(Proc) e = e.call if e.is_a?(Proc)
if property == "to_liquid" if property == "to_liquid"
@@ -236,7 +236,7 @@ module Liquid
# Remove nils within an array # Remove nils within an array
# provide optional property with which to check for nil # provide optional property with which to check for nil
def compact(input, property = nil) def compact(input, property = nil)
ary = InputIterator.new(input) ary = InputIterator.new(input, context)
if property.nil? if property.nil?
ary.compact ary.compact
@@ -280,7 +280,7 @@ module Liquid
unless array.respond_to?(:to_ary) unless array.respond_to?(:to_ary)
raise ArgumentError, "concat filter requires an array argument" raise ArgumentError, "concat filter requires an array argument"
end end
InputIterator.new(input).concat(array) InputIterator.new(input, context).concat(array)
end end
# prepend a string to another # prepend a string to another
@@ -432,6 +432,8 @@ module Liquid
private private
attr_reader :context
def raise_property_error(property) def raise_property_error(property)
raise Liquid::ArgumentError, "cannot select the property '#{property}'" raise Liquid::ArgumentError, "cannot select the property '#{property}'"
end end
@@ -460,7 +462,8 @@ module Liquid
class InputIterator class InputIterator
include Enumerable include Enumerable
def initialize(input) def initialize(input, context)
@context = context
@input = if input.is_a?(Array) @input = if input.is_a?(Array)
input.flatten input.flatten
elsif input.is_a?(Hash) elsif input.is_a?(Hash)
@@ -499,6 +502,7 @@ module Liquid
def each def each
@input.each do |e| @input.each do |e|
e.context = @context if e.respond_to?(:context=)
yield(e.respond_to?(:to_liquid) ? e.to_liquid : e) yield(e.respond_to?(:to_liquid) ? e.to_liquid : e)
end end
end end

View File

@@ -16,20 +16,18 @@ module Liquid
# {% include 'product' for products %} # {% include 'product' for products %}
# #
class Include < Tag class Include < Tag
SYNTAX = /(#{QuotedFragment}+)(\s+(?:with|for)\s+(#{QuotedFragment}+))?(\s+(?:as)\s+(#{VariableSegment}+))?/o Syntax = /(#{QuotedFragment}+)(\s+(?:with|for)\s+(#{QuotedFragment}+))?/o
Syntax = SYNTAX
attr_reader :template_name_expr, :variable_name_expr, :attributes attr_reader :template_name_expr, :variable_name_expr, :attributes
def initialize(tag_name, markup, options) def initialize(tag_name, markup, options)
super super
if markup =~ SYNTAX if markup =~ Syntax
template_name = Regexp.last_match(1) template_name = Regexp.last_match(1)
variable_name = Regexp.last_match(3) variable_name = Regexp.last_match(3)
@alias_name = Regexp.last_match(5)
@variable_name_expr = variable_name ? Expression.parse(variable_name) : nil @variable_name_expr = variable_name ? Expression.parse(variable_name) : nil
@template_name_expr = Expression.parse(template_name) @template_name_expr = Expression.parse(template_name)
@attributes = {} @attributes = {}
@@ -56,7 +54,7 @@ module Liquid
parse_context: parse_context parse_context: parse_context
) )
context_variable_name = @alias_name || template_name.split('/').last context_variable_name = template_name.split('/').last
variable = if @variable_name_expr variable = if @variable_name_expr
context.evaluate(@variable_name_expr) context.evaluate(@variable_name_expr)

View File

@@ -2,7 +2,7 @@
module Liquid module Liquid
class Render < Tag class Render < Tag
SYNTAX = /(#{QuotedString}+)(\s+(?:with|for)\s+(#{QuotedFragment}+))?(\s+(?:as)\s+(#{VariableSegment}+))?/o SYNTAX = /(#{QuotedString})#{QuotedFragment}*/o
disable_tags "include" disable_tags "include"
@@ -14,10 +14,7 @@ module Liquid
raise SyntaxError, options[:locale].t("errors.syntax.render") unless markup =~ SYNTAX raise SyntaxError, options[:locale].t("errors.syntax.render") unless markup =~ SYNTAX
template_name = Regexp.last_match(1) template_name = Regexp.last_match(1)
variable_name = Regexp.last_match(3)
@alias_name = Regexp.last_match(5)
@variable_name_expr = variable_name ? Expression.parse(variable_name) : nil
@template_name_expr = Expression.parse(template_name) @template_name_expr = Expression.parse(template_name)
@attributes = {} @attributes = {}
@@ -41,21 +38,13 @@ module Liquid
parse_context: parse_context parse_context: parse_context
) )
context_variable_name = @alias_name || template_name.split('/').last inner_context = context.new_isolated_subcontext
inner_context.template_name = template_name
render_partial_func = ->(var) { inner_context.partial = true
inner_context = context.new_isolated_subcontext @attributes.each do |key, value|
inner_context.template_name = template_name inner_context[key] = context.evaluate(value)
inner_context.partial = true end
@attributes.each do |key, value| partial.render_to_output_buffer(inner_context, output)
inner_context[key] = context.evaluate(value)
end
inner_context[context_variable_name] = var unless var.nil?
partial.render_to_output_buffer(inner_context, output)
}
variable = @variable_name_expr ? context.evaluate(@variable_name_expr) : nil
variable.is_a?(Array) ? variable.each(&render_partial_func) : render_partial_func.call(variable)
output output
end end

View File

@@ -254,7 +254,7 @@ module Liquid
if @profiling && !context.partial if @profiling && !context.partial
raise "Profiler not loaded, require 'liquid/profiler' first" unless defined?(Liquid::Profiler) raise "Profiler not loaded, require 'liquid/profiler' first" unless defined?(Liquid::Profiler)
@profiler = Profiler.new @profiler = Profiler.new(context.template_name)
@profiler.start @profiler.start
begin begin

View File

@@ -179,6 +179,11 @@ class DropsTest < Minitest::Test
assert_equal(' carrot ', output) assert_equal(' carrot ', output)
end end
def test_context_drop_array_with_map
output = Liquid::Template.parse(' {{ contexts | map: "bar" }} ').render!('contexts' => [ContextDrop.new, ContextDrop.new], 'bar' => "carrot")
assert_equal(' carrotcarrot ', output)
end
def test_nested_context_drop def test_nested_context_drop
output = Liquid::Template.parse(' {{ product.context.foo }} ').render!('product' => ProductDrop.new, 'foo' => "monkey") output = Liquid::Template.parse(' {{ product.context.foo }} ').render!('product' => ProductDrop.new, 'foo' => "monkey")
assert_equal(' monkey ', output) assert_equal(' monkey ', output)

View File

@@ -153,4 +153,19 @@ class RenderProfilingTest < Minitest::Test
# Will profile each invocation of the for block # Will profile each invocation of the for block
assert_equal(2, t.profiler[0].children.length) assert_equal(2, t.profiler[0].children.length)
end end
def test_total_time_equals_self_time_in_leaf
t = Template.parse("{% for item in collection %} {{ item }} {% endfor %}", profile: true)
t.render!("collection" => ["one", "two"])
leaf = t.profiler[0].children[0]
assert_equal leaf.total_time, leaf.self_time
end
def test_total_time_greater_than_self_time_in_node
t = Template.parse("{% if true %} {% increment test %} {{ test }} {% endif %}", profile: true)
t.render!
assert_operator t.profiler[0].total_time, :>, t.profiler[0].self_time
end
end end

View File

@@ -8,9 +8,6 @@ class TestFileSystem
when "product" when "product"
"Product: {{ product.title }} " "Product: {{ product.title }} "
when "product_alias"
"Product: {{ product.title }} "
when "locale_variables" when "locale_variables"
"Locale: {{echo1}} {{echo2}}" "Locale: {{echo1}} {{echo2}}"
@@ -94,16 +91,6 @@ class IncludeTagTest < Minitest::Test
"{% include 'product' with products[0] %}", "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }]) "{% include 'product' with products[0] %}", "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }])
end end
def test_include_tag_with_alias
assert_template_result("Product: Draft 151cm ",
"{% include 'product_alias' with products[0] as product %}", "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }])
end
def test_include_tag_for_alias
assert_template_result("Product: Draft 151cm Product: Element 155cm ",
"{% include 'product_alias' for products as product %}", "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }])
end
def test_include_tag_with_default_name def test_include_tag_with_default_name
assert_template_result("Product: Draft 151cm ", assert_template_result("Product: Draft 151cm ",
"{% include 'product' %}", "product" => { 'title' => 'Draft 151cm' }) "{% include 'product' %}", "product" => { 'title' => 'Draft 151cm' })

View File

@@ -165,44 +165,4 @@ class RenderTagTest < Minitest::Test
assert_template_result('include usage is not allowed in this contextinclude usage is not allowed in this context', '{% render "nested_render_with_sibling_include" %}') assert_template_result('include usage is not allowed in this contextinclude usage is not allowed in this context', '{% render "nested_render_with_sibling_include" %}')
end end
def test_render_tag_with
Liquid::Template.file_system = StubFileSystem.new(
'product' => "Product: {{ product.title }} ",
'product_alias' => "Product: {{ product.title }} ",
)
assert_template_result("Product: Draft 151cm ",
"{% render 'product' with products[0] %}", "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }])
end
def test_render_tag_with_alias
Liquid::Template.file_system = StubFileSystem.new(
'product' => "Product: {{ product.title }} ",
'product_alias' => "Product: {{ product.title }} ",
)
assert_template_result("Product: Draft 151cm ",
"{% render 'product_alias' with products[0] as product %}", "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }])
end
def test_render_tag_for_alias
Liquid::Template.file_system = StubFileSystem.new(
'product' => "Product: {{ product.title }} ",
'product_alias' => "Product: {{ product.title }} ",
)
assert_template_result("Product: Draft 151cm Product: Element 155cm ",
"{% render 'product_alias' for products as product %}", "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }])
end
def test_render_tag_for
Liquid::Template.file_system = StubFileSystem.new(
'product' => "Product: {{ product.title }} ",
'product_alias' => "Product: {{ product.title }} ",
)
assert_template_result("Product: Draft 151cm Product: Element 155cm ",
"{% render 'product' for products %}", "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }])
end
end end