mirror of
https://github.com/kemko/liquid.git
synced 2026-01-03 00:35:40 +03:00
Compare commits
21 Commits
truffle-na
...
context_si
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6fd5e4e6ce | ||
|
|
56c667d8f9 | ||
|
|
0ce8aef229 | ||
|
|
6eab595fae | ||
|
|
831355dfbd | ||
|
|
00702d8e63 | ||
|
|
197c058208 | ||
|
|
98dfe198e1 | ||
|
|
c2c1497ca8 | ||
|
|
d19967a79d | ||
|
|
248c54a386 | ||
|
|
2c42447659 | ||
|
|
ab698191b9 | ||
|
|
9ef6f9b642 | ||
|
|
4684478e94 | ||
|
|
9640e77805 | ||
|
|
2a1ca3152d | ||
|
|
7dc488a73b | ||
|
|
e6ed804ca5 | ||
|
|
951abb67ee | ||
|
|
8d1cd41453 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -7,3 +7,4 @@ pkg
|
|||||||
.ruby-version
|
.ruby-version
|
||||||
Gemfile.lock
|
Gemfile.lock
|
||||||
.bundle
|
.bundle
|
||||||
|
.byebug_history
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# This configuration was generated by
|
# This configuration was generated by
|
||||||
# `rubocop --auto-gen-config`
|
# `rubocop --auto-gen-config`
|
||||||
# on 2019-03-19 11:04:37 -0400 using RuboCop version 0.53.0.
|
# on 2019-04-22 19:11:24 -0400 using RuboCop version 0.53.0.
|
||||||
# The point is for the user to remove these configuration records
|
# The point is for the user to remove these configuration records
|
||||||
# one by one as the offenses are removed from the code base.
|
# one by one as the offenses are removed from the code base.
|
||||||
# Note that changes in the inspected code, or installation of new
|
# Note that changes in the inspected code, or installation of new
|
||||||
@@ -46,18 +46,18 @@ Lint/Void:
|
|||||||
Exclude:
|
Exclude:
|
||||||
- 'lib/liquid/parse_context.rb'
|
- 'lib/liquid/parse_context.rb'
|
||||||
|
|
||||||
# Offense count: 54
|
# Offense count: 53
|
||||||
Metrics/AbcSize:
|
Metrics/AbcSize:
|
||||||
Max: 56
|
Max: 56
|
||||||
|
|
||||||
# Offense count: 12
|
# Offense count: 12
|
||||||
Metrics/CyclomaticComplexity:
|
Metrics/CyclomaticComplexity:
|
||||||
Max: 12
|
Max: 13
|
||||||
|
|
||||||
# Offense count: 112
|
# Offense count: 112
|
||||||
# Configuration parameters: CountComments.
|
# Configuration parameters: CountComments.
|
||||||
Metrics/MethodLength:
|
Metrics/MethodLength:
|
||||||
Max: 37
|
Max: 38
|
||||||
|
|
||||||
# Offense count: 8
|
# Offense count: 8
|
||||||
Metrics/PerceivedComplexity:
|
Metrics/PerceivedComplexity:
|
||||||
@@ -90,7 +90,7 @@ Naming/UncommunicativeMethodParamName:
|
|||||||
- 'test/integration/template_test.rb'
|
- 'test/integration/template_test.rb'
|
||||||
- 'test/unit/condition_unit_test.rb'
|
- 'test/unit/condition_unit_test.rb'
|
||||||
|
|
||||||
# Offense count: 10
|
# Offense count: 12
|
||||||
# Cop supports --auto-correct.
|
# Cop supports --auto-correct.
|
||||||
# Configuration parameters: EnforcedStyle.
|
# Configuration parameters: EnforcedStyle.
|
||||||
# SupportedStyles: prefer_alias, prefer_alias_method
|
# SupportedStyles: prefer_alias, prefer_alias_method
|
||||||
@@ -253,7 +253,7 @@ Style/WhileUntilModifier:
|
|||||||
Exclude:
|
Exclude:
|
||||||
- 'lib/liquid/tags/case.rb'
|
- 'lib/liquid/tags/case.rb'
|
||||||
|
|
||||||
# Offense count: 640
|
# Offense count: 648
|
||||||
# Configuration parameters: AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns.
|
# Configuration parameters: AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns.
|
||||||
# URISchemes: http, https
|
# URISchemes: http, https
|
||||||
Metrics/LineLength:
|
Metrics/LineLength:
|
||||||
|
|||||||
@@ -6,18 +6,21 @@ rvm:
|
|||||||
- 2.3
|
- 2.3
|
||||||
- 2.4
|
- 2.4
|
||||||
- 2.5
|
- 2.5
|
||||||
|
- &latest_ruby 2.6
|
||||||
- ruby-head
|
- ruby-head
|
||||||
- jruby-head
|
- jruby-head
|
||||||
# - rbx-2
|
# - rbx-2
|
||||||
|
|
||||||
sudo: false
|
|
||||||
|
|
||||||
addons:
|
addons:
|
||||||
apt:
|
apt:
|
||||||
packages:
|
packages:
|
||||||
- libgmp3-dev
|
- libgmp3-dev
|
||||||
|
|
||||||
matrix:
|
matrix:
|
||||||
|
include:
|
||||||
|
- rvm: *latest_ruby
|
||||||
|
script: bundle exec rake memory_profile:run
|
||||||
|
name: Profiling Memory Usage
|
||||||
allow_failures:
|
allow_failures:
|
||||||
- rvm: ruby-head
|
- rvm: ruby-head
|
||||||
- rvm: jruby-head
|
- rvm: jruby-head
|
||||||
|
|||||||
3
Gemfile
3
Gemfile
@@ -8,6 +8,7 @@ gemspec
|
|||||||
group :benchmark, :test do
|
group :benchmark, :test do
|
||||||
gem 'benchmark-ips'
|
gem 'benchmark-ips'
|
||||||
gem 'memory_profiler'
|
gem 'memory_profiler'
|
||||||
|
gem 'terminal-table'
|
||||||
|
|
||||||
install_if -> { RUBY_PLATFORM !~ /mingw|mswin|java/ } do
|
install_if -> { RUBY_PLATFORM !~ /mingw|mswin|java/ } do
|
||||||
gem 'stackprof'
|
gem 'stackprof'
|
||||||
@@ -18,6 +19,6 @@ group :test do
|
|||||||
gem 'rubocop', '~> 0.53.0'
|
gem 'rubocop', '~> 0.53.0'
|
||||||
|
|
||||||
platform :mri do
|
platform :mri do
|
||||||
gem 'liquid-c', github: 'Shopify/liquid-c', ref: '9168659de45d6d576fce30c735f857e597fa26f6'
|
gem 'liquid-c', github: 'Shopify/liquid-c', ref: 'liquid-tag'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ module Liquid
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# For backwards compatibility
|
||||||
def render(context)
|
def render(context)
|
||||||
@body.render(context)
|
@body.render(context)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
module Liquid
|
module Liquid
|
||||||
class BlockBody
|
class BlockBody
|
||||||
FullToken = /\A#{TagStart}#{WhitespaceControl}?\s*(\w+)\s*(.*?)#{WhitespaceControl}?#{TagEnd}\z/om
|
LiquidTagToken = /\A\s*(\w+)\s*(.*?)\z/o
|
||||||
|
FullToken = /\A#{TagStart}#{WhitespaceControl}?(\s*)(\w+)(\s*)(.*?)#{WhitespaceControl}?#{TagEnd}\z/om
|
||||||
ContentOfVariable = /\A#{VariableStart}#{WhitespaceControl}?(.*?)#{WhitespaceControl}?#{VariableEnd}\z/om
|
ContentOfVariable = /\A#{VariableStart}#{WhitespaceControl}?(.*?)#{WhitespaceControl}?#{VariableEnd}\z/om
|
||||||
WhitespaceOrNothing = /\A\s*\z/
|
WhitespaceOrNothing = /\A\s*\z/
|
||||||
TAGSTART = "{%".freeze
|
TAGSTART = "{%".freeze
|
||||||
@@ -13,8 +14,42 @@ module Liquid
|
|||||||
@blank = true
|
@blank = true
|
||||||
end
|
end
|
||||||
|
|
||||||
def parse(tokenizer, parse_context)
|
def parse(tokenizer, parse_context, &block)
|
||||||
parse_context.line_number = tokenizer.line_number
|
parse_context.line_number = tokenizer.line_number
|
||||||
|
|
||||||
|
if tokenizer.for_liquid_tag
|
||||||
|
parse_for_liquid_tag(tokenizer, parse_context, &block)
|
||||||
|
else
|
||||||
|
parse_for_document(tokenizer, parse_context, &block)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
private def parse_for_liquid_tag(tokenizer, parse_context)
|
||||||
|
while token = tokenizer.shift
|
||||||
|
unless token.empty? || token =~ WhitespaceOrNothing
|
||||||
|
unless token =~ LiquidTagToken
|
||||||
|
# line isn't empty but didn't match tag syntax, yield and let the
|
||||||
|
# caller raise a syntax error
|
||||||
|
return yield token, token
|
||||||
|
end
|
||||||
|
tag_name = $1
|
||||||
|
markup = $2
|
||||||
|
unless tag = registered_tags[tag_name]
|
||||||
|
# end parsing if we reach an unknown tag and let the caller decide
|
||||||
|
# determine how to proceed
|
||||||
|
return yield tag_name, markup
|
||||||
|
end
|
||||||
|
new_tag = tag.parse(tag_name, markup, tokenizer, parse_context)
|
||||||
|
@blank &&= new_tag.blank?
|
||||||
|
@nodelist << new_tag
|
||||||
|
end
|
||||||
|
parse_context.line_number = tokenizer.line_number
|
||||||
|
end
|
||||||
|
|
||||||
|
yield nil, nil
|
||||||
|
end
|
||||||
|
|
||||||
|
private def parse_for_document(tokenizer, parse_context, &block)
|
||||||
while token = tokenizer.shift
|
while token = tokenizer.shift
|
||||||
next if token.empty?
|
next if token.empty?
|
||||||
case
|
case
|
||||||
@@ -23,9 +58,20 @@ module Liquid
|
|||||||
unless token =~ FullToken
|
unless token =~ FullToken
|
||||||
raise_missing_tag_terminator(token, parse_context)
|
raise_missing_tag_terminator(token, parse_context)
|
||||||
end
|
end
|
||||||
tag_name = $1
|
tag_name = $2
|
||||||
markup = $2
|
markup = $4
|
||||||
# fetch the tag from registered blocks
|
|
||||||
|
if parse_context.line_number
|
||||||
|
# newlines inside the tag should increase the line number,
|
||||||
|
# particularly important for multiline {% liquid %} tags
|
||||||
|
parse_context.line_number += $1.count("\n".freeze) + $3.count("\n".freeze)
|
||||||
|
end
|
||||||
|
|
||||||
|
if tag_name == 'liquid'.freeze
|
||||||
|
liquid_tag_tokenizer = Tokenizer.new(markup, line_number: parse_context.line_number, for_liquid_tag: true)
|
||||||
|
next parse_for_liquid_tag(liquid_tag_tokenizer, parse_context, &block)
|
||||||
|
end
|
||||||
|
|
||||||
unless tag = registered_tags[tag_name]
|
unless tag = registered_tags[tag_name]
|
||||||
# end parsing if we reach an unknown tag and let the caller decide
|
# end parsing if we reach an unknown tag and let the caller decide
|
||||||
# determine how to proceed
|
# determine how to proceed
|
||||||
@@ -67,19 +113,23 @@ module Liquid
|
|||||||
end
|
end
|
||||||
|
|
||||||
def render(context)
|
def render(context)
|
||||||
output = []
|
render_to_output_buffer(context, '')
|
||||||
|
end
|
||||||
|
|
||||||
|
def render_to_output_buffer(context, output)
|
||||||
context.resource_limits.render_score += @nodelist.length
|
context.resource_limits.render_score += @nodelist.length
|
||||||
|
|
||||||
idx = 0
|
idx = 0
|
||||||
while node = @nodelist[idx]
|
while node = @nodelist[idx]
|
||||||
|
previous_output_size = output.bytesize
|
||||||
|
|
||||||
case node
|
case node
|
||||||
when String
|
when String
|
||||||
check_resources(context, node)
|
|
||||||
output << node
|
output << node
|
||||||
when Variable
|
when Variable
|
||||||
render_node_to_output(node, output, context)
|
render_node(context, output, node)
|
||||||
when Block
|
when Block
|
||||||
render_node_to_output(node, output, context, node.blank?)
|
render_node(context, node.blank? ? '' : output, node)
|
||||||
break if context.interrupt? # might have happened in a for-block
|
break if context.interrupt? # might have happened in a for-block
|
||||||
when Continue, Break
|
when Continue, Break
|
||||||
# If we get an Interrupt that means the block must stop processing. An
|
# If we get an Interrupt that means the block must stop processing. An
|
||||||
@@ -88,34 +138,30 @@ module Liquid
|
|||||||
context.push_interrupt(node.interrupt)
|
context.push_interrupt(node.interrupt)
|
||||||
break
|
break
|
||||||
else # Other non-Block tags
|
else # Other non-Block tags
|
||||||
render_node_to_output(node, output, context)
|
render_node(context, output, node)
|
||||||
break if context.interrupt? # might have happened through an include
|
break if context.interrupt? # might have happened through an include
|
||||||
end
|
end
|
||||||
idx += 1
|
idx += 1
|
||||||
|
|
||||||
|
raise_if_resource_limits_reached(context, output.bytesize - previous_output_size)
|
||||||
end
|
end
|
||||||
|
|
||||||
output.join
|
output
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def render_node_to_output(node, output, context, skip_output = false)
|
def render_node(context, output, node)
|
||||||
node_output = node.render(context)
|
node.render_to_output_buffer(context, output)
|
||||||
node_output = node_output.is_a?(Array) ? node_output.join : node_output.to_s
|
|
||||||
check_resources(context, node_output)
|
|
||||||
output << node_output unless skip_output
|
|
||||||
rescue MemoryError => e
|
|
||||||
raise e
|
|
||||||
rescue UndefinedVariable, UndefinedDropMethod, UndefinedFilter => e
|
rescue UndefinedVariable, UndefinedDropMethod, UndefinedFilter => e
|
||||||
context.handle_error(e, node.line_number)
|
context.handle_error(e, node.line_number)
|
||||||
output << nil
|
|
||||||
rescue ::StandardError => e
|
rescue ::StandardError => e
|
||||||
line_number = node.is_a?(String) ? nil : node.line_number
|
line_number = node.is_a?(String) ? nil : node.line_number
|
||||||
output << context.handle_error(e, line_number)
|
output << context.handle_error(e, line_number)
|
||||||
end
|
end
|
||||||
|
|
||||||
def check_resources(context, node_output)
|
def raise_if_resource_limits_reached(context, length)
|
||||||
context.resource_limits.render_length += node_output.bytesize
|
context.resource_limits.render_length += length
|
||||||
return unless context.resource_limits.reached?
|
return unless context.resource_limits.reached?
|
||||||
raise MemoryError.new("Memory limits exceeded".freeze)
|
raise MemoryError.new("Memory limits exceeded".freeze)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -20,21 +20,17 @@ module Liquid
|
|||||||
@scopes = [(outer_scope || {})]
|
@scopes = [(outer_scope || {})]
|
||||||
@registers = registers
|
@registers = registers
|
||||||
@errors = []
|
@errors = []
|
||||||
@partial = false
|
|
||||||
@strict_variables = false
|
|
||||||
@resource_limits = resource_limits || ResourceLimits.new(Template.default_resource_limits)
|
|
||||||
squash_instance_assigns_with_environments
|
|
||||||
|
|
||||||
@this_stack_used = false
|
|
||||||
|
|
||||||
self.exception_renderer = Template.default_exception_renderer
|
|
||||||
if rethrow_errors
|
|
||||||
self.exception_renderer = ->(e) { raise }
|
|
||||||
end
|
|
||||||
|
|
||||||
@interrupts = []
|
@interrupts = []
|
||||||
@filters = []
|
@filters = []
|
||||||
@global_filter = nil
|
@global_filter = nil
|
||||||
|
@partial = false
|
||||||
|
@strict_variables = false
|
||||||
|
@resource_limits = resource_limits || ResourceLimits.new(Template.default_resource_limits)
|
||||||
|
|
||||||
|
self.exception_renderer = Template.default_exception_renderer
|
||||||
|
self.exception_renderer = ->(e) { raise } if rethrow_errors
|
||||||
|
|
||||||
|
squash_instance_assigns_with_environments
|
||||||
end
|
end
|
||||||
|
|
||||||
def warnings
|
def warnings
|
||||||
@@ -87,9 +83,9 @@ module Liquid
|
|||||||
end
|
end
|
||||||
|
|
||||||
# Push new local scope on the stack. use <tt>Context#stack</tt> instead
|
# Push new local scope on the stack. use <tt>Context#stack</tt> instead
|
||||||
def push(new_scope = {})
|
def push
|
||||||
@scopes.unshift(new_scope)
|
@scopes.unshift({})
|
||||||
raise StackLevelError, "Nesting too deep".freeze if @scopes.length > Block::MAX_DEPTH
|
raise StackLevelError, "Nesting too deep".freeze if @scopes.length > (Block::MAX_DEPTH + 1)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Merge a hash of variables in the current local scope
|
# Merge a hash of variables in the current local scope
|
||||||
@@ -111,31 +107,15 @@ module Liquid
|
|||||||
# end
|
# end
|
||||||
#
|
#
|
||||||
# context['var] #=> nil
|
# context['var] #=> nil
|
||||||
def stack(new_scope = nil)
|
def stack
|
||||||
old_stack_used = @this_stack_used
|
push
|
||||||
if new_scope
|
|
||||||
push(new_scope)
|
|
||||||
@this_stack_used = true
|
|
||||||
else
|
|
||||||
@this_stack_used = false
|
|
||||||
end
|
|
||||||
|
|
||||||
yield
|
yield
|
||||||
ensure
|
ensure
|
||||||
pop if @this_stack_used
|
pop
|
||||||
@this_stack_used = old_stack_used
|
|
||||||
end
|
|
||||||
|
|
||||||
def clear_instance_assigns
|
|
||||||
@scopes[0] = {}
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# Only allow String, Numeric, Hash, Array, Proc, Boolean or <tt>Liquid::Drop</tt>
|
# Only allow String, Numeric, Hash, Array, Proc, Boolean or <tt>Liquid::Drop</tt>
|
||||||
def []=(key, value)
|
def []=(key, value)
|
||||||
unless @this_stack_used
|
|
||||||
@this_stack_used = true
|
|
||||||
push({})
|
|
||||||
end
|
|
||||||
@scopes[0][key] = value
|
@scopes[0][key] = value
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -163,27 +143,12 @@ module Liquid
|
|||||||
def find_variable(key, raise_on_not_found: true)
|
def find_variable(key, raise_on_not_found: true)
|
||||||
# This was changed from find() to find_index() because this is a very hot
|
# 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
|
# path and find_index() is optimized in MRI to reduce object allocation
|
||||||
index = @scopes.find_index { |s| s.key?(key) }
|
|
||||||
scope = @scopes[index] if index
|
|
||||||
|
|
||||||
variable = nil
|
scope = (index = @scopes.find_index { |s| s.key?(key) }) && @scopes[index]
|
||||||
|
scope ||= (index = @environments.find_index { |s| s.key?(key) }) && @environments[index]
|
||||||
|
scope ||= {}
|
||||||
|
|
||||||
if scope.nil?
|
variable = lookup_and_evaluate(scope, key, raise_on_not_found: raise_on_not_found).to_liquid
|
||||||
@environments.each do |e|
|
|
||||||
variable = lookup_and_evaluate(e, key, raise_on_not_found: raise_on_not_found)
|
|
||||||
# When lookup returned a value OR there is no value but the lookup also did not raise
|
|
||||||
# then it is the value we are looking for.
|
|
||||||
if !variable.nil? || @strict_variables && raise_on_not_found
|
|
||||||
scope = e
|
|
||||||
break
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
scope ||= @environments.last || @scopes.last
|
|
||||||
variable ||= lookup_and_evaluate(scope, key, raise_on_not_found: raise_on_not_found)
|
|
||||||
|
|
||||||
variable = variable.to_liquid
|
|
||||||
variable.context = self if variable.respond_to?(:context=)
|
variable.context = self if variable.respond_to?(:context=)
|
||||||
|
|
||||||
variable
|
variable
|
||||||
|
|||||||
@@ -44,11 +44,14 @@ module Liquid
|
|||||||
tok[0] == type
|
tok[0] == type
|
||||||
end
|
end
|
||||||
|
|
||||||
|
SINGLE_TOKEN_EXPRESSION_TYPES = [:string, :number].freeze
|
||||||
|
private_constant :SINGLE_TOKEN_EXPRESSION_TYPES
|
||||||
|
|
||||||
def expression
|
def expression
|
||||||
token = @tokens[@p]
|
token = @tokens[@p]
|
||||||
if token[0] == :id
|
if token[0] == :id
|
||||||
variable_signature
|
variable_signature
|
||||||
elsif [:string, :number].include? token[0]
|
elsif SINGLE_TOKEN_EXPRESSION_TYPES.include? token[0]
|
||||||
consume
|
consume
|
||||||
elsif token.first == :open_round
|
elsif token.first == :open_round
|
||||||
consume
|
consume
|
||||||
|
|||||||
@@ -1,23 +1,23 @@
|
|||||||
module Liquid
|
module Liquid
|
||||||
class BlockBody
|
class BlockBody
|
||||||
def render_node_with_profiling(node, output, context, skip_output = false)
|
def render_node_with_profiling(context, output, node)
|
||||||
Profiler.profile_node_render(node) do
|
Profiler.profile_node_render(node) do
|
||||||
render_node_without_profiling(node, output, context, skip_output)
|
render_node_without_profiling(context, output, node)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
alias_method :render_node_without_profiling, :render_node_to_output
|
alias_method :render_node_without_profiling, :render_node
|
||||||
alias_method :render_node_to_output, :render_node_with_profiling
|
alias_method :render_node, :render_node_with_profiling
|
||||||
end
|
end
|
||||||
|
|
||||||
class Include < Tag
|
class Include < Tag
|
||||||
def render_with_profiling(context)
|
def render_to_output_buffer_with_profiling(context, output)
|
||||||
Profiler.profile_children(context.evaluate(@template_name_expr).to_s) do
|
Profiler.profile_children(context.evaluate(@template_name_expr).to_s) do
|
||||||
render_without_profiling(context)
|
render_to_output_buffer_without_profiling(context, output)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
alias_method :render_without_profiling, :render
|
alias_method :render_to_output_buffer_without_profiling, :render_to_output_buffer
|
||||||
alias_method :render, :render_with_profiling
|
alias_method :render_to_output_buffer, :render_to_output_buffer_with_profiling
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ module Liquid
|
|||||||
truncate_string_str = truncate_string.to_s
|
truncate_string_str = truncate_string.to_s
|
||||||
l = length - truncate_string_str.length
|
l = length - truncate_string_str.length
|
||||||
l = 0 if l < 0
|
l = 0 if l < 0
|
||||||
input_str.length > length ? input_str[0...l] + truncate_string_str : input_str
|
input_str.length > length ? input_str[0...l].concat(truncate_string_str) : input_str
|
||||||
end
|
end
|
||||||
|
|
||||||
def truncatewords(input, words = 15, truncate_string = "...".freeze)
|
def truncatewords(input, words = 15, truncate_string = "...".freeze)
|
||||||
@@ -88,7 +88,7 @@ module Liquid
|
|||||||
words = Utils.to_integer(words)
|
words = Utils.to_integer(words)
|
||||||
l = words - 1
|
l = words - 1
|
||||||
l = 0 if l < 0
|
l = 0 if l < 0
|
||||||
wordlist.length > l ? wordlist[0..l].join(" ".freeze) + truncate_string.to_s : input
|
wordlist.length > l ? wordlist[0..l].join(" ".freeze).concat(truncate_string.to_s) : input
|
||||||
end
|
end
|
||||||
|
|
||||||
# Split input string into an array of substrings separated by given pattern.
|
# Split input string into an array of substrings separated by given pattern.
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ module Liquid
|
|||||||
include ParserSwitching
|
include ParserSwitching
|
||||||
|
|
||||||
class << self
|
class << self
|
||||||
def parse(tag_name, markup, tokenizer, options)
|
def parse(tag_name, markup, tokenizer, parse_context)
|
||||||
tag = new(tag_name, markup, options)
|
tag = new(tag_name, markup, parse_context)
|
||||||
tag.parse(tokenizer)
|
tag.parse(tokenizer)
|
||||||
tag
|
tag
|
||||||
end
|
end
|
||||||
@@ -36,6 +36,14 @@ module Liquid
|
|||||||
''.freeze
|
''.freeze
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# For backwards compatibility with custom tags. In a future release, the semantics
|
||||||
|
# of the `render_to_output_buffer` method will become the default and the `render`
|
||||||
|
# method will be removed.
|
||||||
|
def render_to_output_buffer(context, output)
|
||||||
|
output << render(context)
|
||||||
|
output
|
||||||
|
end
|
||||||
|
|
||||||
def blank?
|
def blank?
|
||||||
false
|
false
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -10,6 +10,10 @@ module Liquid
|
|||||||
class Assign < Tag
|
class Assign < Tag
|
||||||
Syntax = /(#{VariableSignature}+)\s*=\s*(.*)\s*/om
|
Syntax = /(#{VariableSignature}+)\s*=\s*(.*)\s*/om
|
||||||
|
|
||||||
|
def self.syntax_error_translation_key
|
||||||
|
"errors.syntax.assign".freeze
|
||||||
|
end
|
||||||
|
|
||||||
attr_reader :to, :from
|
attr_reader :to, :from
|
||||||
|
|
||||||
def initialize(tag_name, markup, options)
|
def initialize(tag_name, markup, options)
|
||||||
@@ -18,15 +22,15 @@ module Liquid
|
|||||||
@to = $1
|
@to = $1
|
||||||
@from = Variable.new($2, options)
|
@from = Variable.new($2, options)
|
||||||
else
|
else
|
||||||
raise SyntaxError.new options[:locale].t("errors.syntax.assign".freeze)
|
raise SyntaxError.new(options[:locale].t(self.class.syntax_error_translation_key))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def render(context)
|
def render_to_output_buffer(context, output)
|
||||||
val = @from.render(context)
|
val = @from.render(context)
|
||||||
context.scopes.last[@to] = val
|
context.scopes.last[@to] = val
|
||||||
context.resource_limits.assign_score += assign_score_of(val)
|
context.resource_limits.assign_score += assign_score_of(val)
|
||||||
''.freeze
|
output
|
||||||
end
|
end
|
||||||
|
|
||||||
def blank?
|
def blank?
|
||||||
|
|||||||
@@ -22,11 +22,12 @@ module Liquid
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def render(context)
|
def render_to_output_buffer(context, output)
|
||||||
output = super
|
previous_output_size = output.bytesize
|
||||||
|
super
|
||||||
context.scopes.last[@to] = output
|
context.scopes.last[@to] = output
|
||||||
context.resource_limits.assign_score += output.bytesize
|
context.resource_limits.assign_score += (output.bytesize - previous_output_size)
|
||||||
''.freeze
|
output
|
||||||
end
|
end
|
||||||
|
|
||||||
def blank?
|
def blank?
|
||||||
|
|||||||
@@ -38,22 +38,22 @@ module Liquid
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def render(context)
|
def render_to_output_buffer(context, output)
|
||||||
context.stack do
|
context.stack do
|
||||||
execute_else_block = true
|
execute_else_block = true
|
||||||
|
|
||||||
output = ''
|
|
||||||
@blocks.each do |block|
|
@blocks.each do |block|
|
||||||
if block.else?
|
if block.else?
|
||||||
return block.attachment.render(context) if execute_else_block
|
block.attachment.render_to_output_buffer(context, output) if execute_else_block
|
||||||
elsif block.evaluate(context)
|
elsif block.evaluate(context)
|
||||||
execute_else_block = false
|
execute_else_block = false
|
||||||
output << block.attachment.render(context)
|
block.attachment.render_to_output_buffer(context, output)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
output
|
output
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
module Liquid
|
module Liquid
|
||||||
class Comment < Block
|
class Comment < Block
|
||||||
def render(_context)
|
def render_to_output_buffer(_context, output)
|
||||||
''.freeze
|
output
|
||||||
end
|
end
|
||||||
|
|
||||||
def unknown_tag(_tag, _markup, _tokens)
|
def unknown_tag(_tag, _markup, _tokens)
|
||||||
|
|||||||
@@ -31,18 +31,29 @@ module Liquid
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def render(context)
|
def render_to_output_buffer(context, output)
|
||||||
context.registers[:cycle] ||= {}
|
context.registers[:cycle] ||= {}
|
||||||
|
|
||||||
context.stack do
|
context.stack do
|
||||||
key = context.evaluate(@name)
|
key = context.evaluate(@name)
|
||||||
iteration = context.registers[:cycle][key].to_i
|
iteration = context.registers[:cycle][key].to_i
|
||||||
result = context.evaluate(@variables[iteration])
|
|
||||||
|
val = context.evaluate(@variables[iteration])
|
||||||
|
|
||||||
|
if val.is_a?(Array)
|
||||||
|
val = val.join
|
||||||
|
elsif !val.is_a?(String)
|
||||||
|
val = val.to_s
|
||||||
|
end
|
||||||
|
|
||||||
|
output << val
|
||||||
|
|
||||||
iteration += 1
|
iteration += 1
|
||||||
iteration = 0 if iteration >= @variables.size
|
iteration = 0 if iteration >= @variables.size
|
||||||
context.registers[:cycle][key] = iteration
|
context.registers[:cycle][key] = iteration
|
||||||
result
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
output
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|||||||
@@ -23,11 +23,12 @@ module Liquid
|
|||||||
@variable = markup.strip
|
@variable = markup.strip
|
||||||
end
|
end
|
||||||
|
|
||||||
def render(context)
|
def render_to_output_buffer(context, output)
|
||||||
value = context.environments.first[@variable] ||= 0
|
value = context.environments.first[@variable] ||= 0
|
||||||
value -= 1
|
value -= 1
|
||||||
context.environments.first[@variable] = value
|
context.environments.first[@variable] = value
|
||||||
value.to_s
|
output << value.to_s
|
||||||
|
output
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
24
lib/liquid/tags/echo.rb
Normal file
24
lib/liquid/tags/echo.rb
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
module Liquid
|
||||||
|
# Echo outputs an expression
|
||||||
|
#
|
||||||
|
# {% echo monkey %}
|
||||||
|
# {% echo user.name %}
|
||||||
|
#
|
||||||
|
# This is identical to variable output syntax, like {{ foo }}, but works
|
||||||
|
# inside {% liquid %} tags. The full syntax is supported, including filters:
|
||||||
|
#
|
||||||
|
# {% echo user | link %}
|
||||||
|
#
|
||||||
|
class Echo < Tag
|
||||||
|
def initialize(tag_name, markup, parse_context)
|
||||||
|
super
|
||||||
|
@variable = Variable.new(markup, parse_context)
|
||||||
|
end
|
||||||
|
|
||||||
|
def render(context)
|
||||||
|
@variable.render(context)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
Template.register_tag('echo'.freeze, Echo)
|
||||||
|
end
|
||||||
@@ -70,14 +70,16 @@ module Liquid
|
|||||||
@else_block = BlockBody.new
|
@else_block = BlockBody.new
|
||||||
end
|
end
|
||||||
|
|
||||||
def render(context)
|
def render_to_output_buffer(context, output)
|
||||||
segment = collection_segment(context)
|
segment = collection_segment(context)
|
||||||
|
|
||||||
if segment.empty?
|
if segment.empty?
|
||||||
render_else(context)
|
render_else(context, output)
|
||||||
else
|
else
|
||||||
render_segment(context, segment)
|
render_segment(context, output, segment)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
output
|
||||||
end
|
end
|
||||||
|
|
||||||
protected
|
protected
|
||||||
@@ -150,12 +152,10 @@ module Liquid
|
|||||||
segment
|
segment
|
||||||
end
|
end
|
||||||
|
|
||||||
def render_segment(context, segment)
|
def render_segment(context, output, segment)
|
||||||
for_stack = context.registers[:for_stack] ||= []
|
for_stack = context.registers[:for_stack] ||= []
|
||||||
length = segment.length
|
length = segment.length
|
||||||
|
|
||||||
result = ''
|
|
||||||
|
|
||||||
context.stack do
|
context.stack do
|
||||||
loop_vars = Liquid::ForloopDrop.new(@name, length, for_stack[-1])
|
loop_vars = Liquid::ForloopDrop.new(@name, length, for_stack[-1])
|
||||||
|
|
||||||
@@ -166,7 +166,7 @@ module Liquid
|
|||||||
|
|
||||||
segment.each do |item|
|
segment.each do |item|
|
||||||
context[@variable_name] = item
|
context[@variable_name] = item
|
||||||
result << @for_block.render(context)
|
@for_block.render_to_output_buffer(context, output)
|
||||||
loop_vars.send(:increment!)
|
loop_vars.send(:increment!)
|
||||||
|
|
||||||
# Handle any interrupts if they exist.
|
# Handle any interrupts if they exist.
|
||||||
@@ -181,7 +181,7 @@ module Liquid
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
result
|
output
|
||||||
end
|
end
|
||||||
|
|
||||||
def set_attribute(key, expr)
|
def set_attribute(key, expr)
|
||||||
@@ -197,8 +197,12 @@ module Liquid
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def render_else(context)
|
def render_else(context, output)
|
||||||
@else_block ? @else_block.render(context) : ''.freeze
|
if @else_block
|
||||||
|
@else_block.render_to_output_buffer(context, output)
|
||||||
|
else
|
||||||
|
output
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
class ParseTreeVisitor < Liquid::ParseTreeVisitor
|
class ParseTreeVisitor < Liquid::ParseTreeVisitor
|
||||||
|
|||||||
@@ -39,15 +39,16 @@ module Liquid
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def render(context)
|
def render_to_output_buffer(context, output)
|
||||||
context.stack do
|
context.stack do
|
||||||
@blocks.each do |block|
|
@blocks.each do |block|
|
||||||
if block.evaluate(context)
|
if block.evaluate(context)
|
||||||
return block.attachment.render(context)
|
return block.attachment.render_to_output_buffer(context, output)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
''.freeze
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
output
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|||||||
@@ -1,16 +1,17 @@
|
|||||||
module Liquid
|
module Liquid
|
||||||
class Ifchanged < Block
|
class Ifchanged < Block
|
||||||
def render(context)
|
def render_to_output_buffer(context, output)
|
||||||
context.stack do
|
context.stack do
|
||||||
output = super
|
block_output = ''
|
||||||
|
super(context, block_output)
|
||||||
|
|
||||||
|
if block_output != context.registers[:ifchanged]
|
||||||
|
context.registers[:ifchanged] = block_output
|
||||||
|
output << block_output
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
if output != context.registers[:ifchanged]
|
|
||||||
context.registers[:ifchanged] = output
|
|
||||||
output
|
output
|
||||||
else
|
|
||||||
''.freeze
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ module Liquid
|
|||||||
def parse(_tokens)
|
def parse(_tokens)
|
||||||
end
|
end
|
||||||
|
|
||||||
def render(context)
|
def render_to_output_buffer(context, output)
|
||||||
template_name = context.evaluate(@template_name_expr)
|
template_name = context.evaluate(@template_name_expr)
|
||||||
raise ArgumentError.new(options[:locale].t("errors.argument.include")) unless template_name
|
raise ArgumentError.new(options[:locale].t("errors.argument.include")) unless template_name
|
||||||
|
|
||||||
@@ -66,19 +66,21 @@ module Liquid
|
|||||||
end
|
end
|
||||||
|
|
||||||
if variable.is_a?(Array)
|
if variable.is_a?(Array)
|
||||||
variable.collect do |var|
|
variable.each do |var|
|
||||||
context[context_variable_name] = var
|
context[context_variable_name] = var
|
||||||
partial.render(context)
|
partial.render_to_output_buffer(context, output)
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
context[context_variable_name] = variable
|
context[context_variable_name] = variable
|
||||||
partial.render(context)
|
partial.render_to_output_buffer(context, output)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
ensure
|
ensure
|
||||||
context.template_name = old_template_name
|
context.template_name = old_template_name
|
||||||
context.partial = old_partial
|
context.partial = old_partial
|
||||||
end
|
end
|
||||||
|
|
||||||
|
output
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|||||||
@@ -20,10 +20,11 @@ module Liquid
|
|||||||
@variable = markup.strip
|
@variable = markup.strip
|
||||||
end
|
end
|
||||||
|
|
||||||
def render(context)
|
def render_to_output_buffer(context, output)
|
||||||
value = context.environments.first[@variable] ||= 0
|
value = context.environments.first[@variable] ||= 0
|
||||||
context.environments.first[@variable] = value + 1
|
context.environments.first[@variable] = value + 1
|
||||||
value.to_s
|
output << value.to_s
|
||||||
|
output
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -22,8 +22,9 @@ module Liquid
|
|||||||
raise SyntaxError.new(parse_context.locale.t("errors.syntax.tag_never_closed".freeze, block_name: block_name))
|
raise SyntaxError.new(parse_context.locale.t("errors.syntax.tag_never_closed".freeze, block_name: block_name))
|
||||||
end
|
end
|
||||||
|
|
||||||
def render(_context)
|
def render_to_output_buffer(_context, output)
|
||||||
@body
|
output << @body
|
||||||
|
output
|
||||||
end
|
end
|
||||||
|
|
||||||
def nodelist
|
def nodelist
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ module Liquid
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def render(context)
|
def render_to_output_buffer(context, output)
|
||||||
collection = context.evaluate(@collection_name) or return ''.freeze
|
collection = context.evaluate(@collection_name) or return ''.freeze
|
||||||
|
|
||||||
from = @attributes.key?('offset'.freeze) ? context.evaluate(@attributes['offset'.freeze]).to_i : 0
|
from = @attributes.key?('offset'.freeze) ? context.evaluate(@attributes['offset'.freeze]).to_i : 0
|
||||||
@@ -30,7 +30,7 @@ module Liquid
|
|||||||
|
|
||||||
cols = context.evaluate(@attributes['cols'.freeze]).to_i
|
cols = context.evaluate(@attributes['cols'.freeze]).to_i
|
||||||
|
|
||||||
result = "<tr class=\"row1\">\n"
|
output << "<tr class=\"row1\">\n"
|
||||||
context.stack do
|
context.stack do
|
||||||
tablerowloop = Liquid::TablerowloopDrop.new(length, cols)
|
tablerowloop = Liquid::TablerowloopDrop.new(length, cols)
|
||||||
context['tablerowloop'.freeze] = tablerowloop
|
context['tablerowloop'.freeze] = tablerowloop
|
||||||
@@ -38,17 +38,20 @@ module Liquid
|
|||||||
collection.each do |item|
|
collection.each do |item|
|
||||||
context[@variable_name] = item
|
context[@variable_name] = item
|
||||||
|
|
||||||
result << "<td class=\"col#{tablerowloop.col}\">" << super << '</td>'
|
output << "<td class=\"col#{tablerowloop.col}\">"
|
||||||
|
super
|
||||||
|
output << '</td>'
|
||||||
|
|
||||||
if tablerowloop.col_last && !tablerowloop.last
|
if tablerowloop.col_last && !tablerowloop.last
|
||||||
result << "</tr>\n<tr class=\"row#{tablerowloop.row + 1}\">"
|
output << "</tr>\n<tr class=\"row#{tablerowloop.row + 1}\">"
|
||||||
end
|
end
|
||||||
|
|
||||||
tablerowloop.send(:increment!)
|
tablerowloop.send(:increment!)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
result << "</tr>\n"
|
|
||||||
result
|
output << "</tr>\n"
|
||||||
|
output
|
||||||
end
|
end
|
||||||
|
|
||||||
class ParseTreeVisitor < Liquid::ParseTreeVisitor
|
class ParseTreeVisitor < Liquid::ParseTreeVisitor
|
||||||
|
|||||||
@@ -6,23 +6,23 @@ module Liquid
|
|||||||
# {% unless x < 0 %} x is greater than zero {% endunless %}
|
# {% unless x < 0 %} x is greater than zero {% endunless %}
|
||||||
#
|
#
|
||||||
class Unless < If
|
class Unless < If
|
||||||
def render(context)
|
def render_to_output_buffer(context, output)
|
||||||
context.stack do
|
context.stack do
|
||||||
# First condition is interpreted backwards ( if not )
|
# First condition is interpreted backwards ( if not )
|
||||||
first_block = @blocks.first
|
first_block = @blocks.first
|
||||||
unless first_block.evaluate(context)
|
unless first_block.evaluate(context)
|
||||||
return first_block.attachment.render(context)
|
return first_block.attachment.render_to_output_buffer(context, output)
|
||||||
end
|
end
|
||||||
|
|
||||||
# After the first condition unless works just like if
|
# After the first condition unless works just like if
|
||||||
@blocks[1..-1].each do |block|
|
@blocks[1..-1].each do |block|
|
||||||
if block.evaluate(context)
|
if block.evaluate(context)
|
||||||
return block.attachment.render(context)
|
return block.attachment.render_to_output_buffer(context, output)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
''.freeze
|
output
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ module Liquid
|
|||||||
private
|
private
|
||||||
|
|
||||||
def lookup_class(name)
|
def lookup_class(name)
|
||||||
name.split("::").reject(&:empty?).reduce(Object) { |scope, const| scope.const_get(const) }
|
Object.const_get(name)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -187,9 +187,12 @@ module Liquid
|
|||||||
raise ArgumentError, "Expected Hash or Liquid::Context as parameter"
|
raise ArgumentError, "Expected Hash or Liquid::Context as parameter"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
output = nil
|
||||||
|
|
||||||
case args.last
|
case args.last
|
||||||
when Hash
|
when Hash
|
||||||
options = args.pop
|
options = args.pop
|
||||||
|
output = options[:output] if options[:output]
|
||||||
|
|
||||||
registers.merge!(options[:registers]) if options[:registers].is_a?(Hash)
|
registers.merge!(options[:registers]) if options[:registers].is_a?(Hash)
|
||||||
|
|
||||||
@@ -204,10 +207,9 @@ module Liquid
|
|||||||
begin
|
begin
|
||||||
# render the nodelist.
|
# render the nodelist.
|
||||||
# for performance reasons we get an array back here. join will make a string out of it.
|
# for performance reasons we get an array back here. join will make a string out of it.
|
||||||
result = with_profiling(context) do
|
with_profiling(context) do
|
||||||
@root.render(context)
|
@root.render_to_output_buffer(context, output || '')
|
||||||
end
|
end
|
||||||
result.respond_to?(:join) ? result.join : result
|
|
||||||
rescue Liquid::MemoryError => e
|
rescue Liquid::MemoryError => e
|
||||||
context.handle_error(e)
|
context.handle_error(e)
|
||||||
ensure
|
ensure
|
||||||
@@ -220,6 +222,10 @@ module Liquid
|
|||||||
render(*args)
|
render(*args)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def render_to_output_buffer(context, output)
|
||||||
|
render(context, output: output)
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def tokenize(source)
|
def tokenize(source)
|
||||||
|
|||||||
@@ -1,25 +1,31 @@
|
|||||||
module Liquid
|
module Liquid
|
||||||
class Tokenizer
|
class Tokenizer
|
||||||
attr_reader :line_number
|
attr_reader :line_number, :for_liquid_tag
|
||||||
|
|
||||||
def initialize(source, line_numbers = false)
|
def initialize(source, line_numbers = false, line_number: nil, for_liquid_tag: false)
|
||||||
@source = source
|
@source = source
|
||||||
@line_number = line_numbers ? 1 : nil
|
@line_number = line_number || (line_numbers ? 1 : nil)
|
||||||
|
@for_liquid_tag = for_liquid_tag
|
||||||
@tokens = tokenize
|
@tokens = tokenize
|
||||||
end
|
end
|
||||||
|
|
||||||
def shift
|
def shift
|
||||||
token = @tokens.shift
|
token = @tokens.shift or return
|
||||||
@line_number += token.count("\n") if @line_number && token
|
|
||||||
|
if @line_number
|
||||||
|
@line_number += @for_liquid_tag ? 1 : token.count("\n")
|
||||||
|
end
|
||||||
|
|
||||||
token
|
token
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def tokenize
|
def tokenize
|
||||||
@source = @source.source if @source.respond_to?(:source)
|
|
||||||
return [] if @source.to_s.empty?
|
return [] if @source.to_s.empty?
|
||||||
|
|
||||||
|
return @source.split("\n") if @for_liquid_tag
|
||||||
|
|
||||||
tokens = @source.split(TemplateParser)
|
tokens = @source.split(TemplateParser)
|
||||||
|
|
||||||
# removes the rogue empty element at the beginning of the array
|
# removes the rogue empty element at the beginning of the array
|
||||||
|
|||||||
@@ -85,12 +85,23 @@ module Liquid
|
|||||||
end
|
end
|
||||||
|
|
||||||
obj = context.apply_global_filter(obj)
|
obj = context.apply_global_filter(obj)
|
||||||
|
|
||||||
taint_check(context, obj)
|
taint_check(context, obj)
|
||||||
|
|
||||||
obj
|
obj
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def render_to_output_buffer(context, output)
|
||||||
|
obj = render(context)
|
||||||
|
|
||||||
|
if obj.is_a?(Array)
|
||||||
|
output << obj.join
|
||||||
|
elsif obj.nil?
|
||||||
|
else
|
||||||
|
output << obj.to_s
|
||||||
|
end
|
||||||
|
|
||||||
|
output
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def parse_filter_expressions(filter_name, unparsed_args)
|
def parse_filter_expressions(filter_name, unparsed_args)
|
||||||
|
|||||||
@@ -2,25 +2,61 @@
|
|||||||
|
|
||||||
require 'benchmark/ips'
|
require 'benchmark/ips'
|
||||||
require 'memory_profiler'
|
require 'memory_profiler'
|
||||||
|
require 'terminal-table'
|
||||||
require_relative 'theme_runner'
|
require_relative 'theme_runner'
|
||||||
|
|
||||||
def profile(phase, &block)
|
class Profiler
|
||||||
puts
|
LOG_LABEL = "Profiling: ".rjust(14).freeze
|
||||||
puts "#{phase}:"
|
REPORTS_DIR = File.expand_path('.memprof', __dir__).freeze
|
||||||
puts
|
|
||||||
|
|
||||||
|
def self.run
|
||||||
|
puts
|
||||||
|
yield new
|
||||||
|
end
|
||||||
|
|
||||||
|
def initialize
|
||||||
|
@allocated = []
|
||||||
|
@retained = []
|
||||||
|
@headings = []
|
||||||
|
end
|
||||||
|
|
||||||
|
def profile(phase, &block)
|
||||||
|
print LOG_LABEL
|
||||||
|
print "#{phase}.. ".ljust(10)
|
||||||
report = MemoryProfiler.report(&block)
|
report = MemoryProfiler.report(&block)
|
||||||
|
puts 'Done.'
|
||||||
|
@headings << phase.capitalize
|
||||||
|
@allocated << "#{report.scale_bytes(report.total_allocated_memsize)} (#{report.total_allocated} objects)"
|
||||||
|
@retained << "#{report.scale_bytes(report.total_retained_memsize)} (#{report.total_retained} objects)"
|
||||||
|
|
||||||
report.pretty_print(
|
return if ENV['CI']
|
||||||
color_output: true,
|
require 'fileutils'
|
||||||
scale_bytes: true,
|
report_file = File.join(REPORTS_DIR, "#{sanitize(phase)}.txt")
|
||||||
detailed_report: true
|
FileUtils.mkdir_p(REPORTS_DIR)
|
||||||
)
|
report.pretty_print(to_file: report_file, scale_bytes: true)
|
||||||
|
end
|
||||||
|
|
||||||
|
def tabulate
|
||||||
|
table = Terminal::Table.new(headings: @headings.unshift('Phase')) do |t|
|
||||||
|
t << @allocated.unshift('Total allocated')
|
||||||
|
t << @retained.unshift('Total retained')
|
||||||
|
end
|
||||||
|
|
||||||
|
puts
|
||||||
|
puts table
|
||||||
|
puts "\nDetailed report(s) saved to #{REPORTS_DIR}/" unless ENV['CI']
|
||||||
|
end
|
||||||
|
|
||||||
|
def sanitize(string)
|
||||||
|
string.downcase.gsub(/[\W]/, '-').squeeze('-')
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
Liquid::Template.error_mode = ARGV.first.to_sym if ARGV.first
|
Liquid::Template.error_mode = ARGV.first.to_sym if ARGV.first
|
||||||
|
|
||||||
profiler = ThemeRunner.new
|
runner = ThemeRunner.new
|
||||||
|
Profiler.run do |x|
|
||||||
profile("Parsing") { profiler.compile }
|
x.profile('parse') { runner.compile }
|
||||||
profile("Rendering") { profiler.render }
|
x.profile('render') { runner.render }
|
||||||
|
x.tabulate
|
||||||
|
end
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ class CommentForm < Liquid::Block
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def render(context)
|
def render_to_output_buffer(context, output)
|
||||||
article = context[@variable_name]
|
article = context[@variable_name]
|
||||||
|
|
||||||
context.stack do
|
context.stack do
|
||||||
@@ -23,7 +23,9 @@ class CommentForm < Liquid::Block
|
|||||||
'email' => context['comment.email'],
|
'email' => context['comment.email'],
|
||||||
'body' => context['comment.body']
|
'body' => context['comment.body']
|
||||||
}
|
}
|
||||||
wrap_in_form(article, render_all(@nodelist, context))
|
|
||||||
|
output << wrap_in_form(article, render_all(@nodelist, context, output))
|
||||||
|
output
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ class Paginate < Liquid::Block
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def render(context)
|
def render_to_output_buffer(context, output)
|
||||||
@context = context
|
@context = context
|
||||||
|
|
||||||
context.stack do
|
context.stack do
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
require 'test_helper'
|
require 'test_helper'
|
||||||
|
|
||||||
class FoobarTag < Liquid::Tag
|
class FoobarTag < Liquid::Tag
|
||||||
def render(*args)
|
def render_to_output_buffer(context, output)
|
||||||
" "
|
output << ' '
|
||||||
|
output
|
||||||
end
|
end
|
||||||
|
|
||||||
Liquid::Template.register_tag('foobar', FoobarTag)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
class BlankTestFileSystem
|
class BlankTestFileSystem
|
||||||
@@ -31,8 +30,10 @@ class BlankTest < Minitest::Test
|
|||||||
end
|
end
|
||||||
|
|
||||||
def test_new_tags_are_not_blank_by_default
|
def test_new_tags_are_not_blank_by_default
|
||||||
|
with_custom_tag('foobar', FoobarTag) do
|
||||||
assert_template_result(" " * N, wrap_in_for("{% foobar %}"))
|
assert_template_result(" " * N, wrap_in_for("{% foobar %}"))
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def test_loops_are_blank
|
def test_loops_are_blank
|
||||||
assert_template_result("", wrap_in_for(" "))
|
assert_template_result("", wrap_in_for(" "))
|
||||||
|
|||||||
11
test/integration/tags/echo_test.rb
Normal file
11
test/integration/tags/echo_test.rb
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
require 'test_helper'
|
||||||
|
|
||||||
|
class EchoTest < Minitest::Test
|
||||||
|
include Liquid
|
||||||
|
|
||||||
|
def test_echo_outputs_its_input
|
||||||
|
assert_template_result('BAR', <<~LIQUID, { 'variable-name' => 'bar' })
|
||||||
|
{%- echo variable-name | upcase -%}
|
||||||
|
LIQUID
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -66,8 +66,9 @@ class CustomInclude < Liquid::Tag
|
|||||||
def parse(tokens)
|
def parse(tokens)
|
||||||
end
|
end
|
||||||
|
|
||||||
def render(context)
|
def render_to_output_buffer(context, output)
|
||||||
@template_name[1..-2]
|
output << @template_name[1..-2]
|
||||||
|
output
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
104
test/integration/tags/liquid_tag_test.rb
Normal file
104
test/integration/tags/liquid_tag_test.rb
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
require 'test_helper'
|
||||||
|
|
||||||
|
class LiquidTagTest < Minitest::Test
|
||||||
|
include Liquid
|
||||||
|
|
||||||
|
def test_liquid_tag
|
||||||
|
assert_template_result('1 2 3', <<~LIQUID, 'array' => [1, 2, 3])
|
||||||
|
{%- liquid
|
||||||
|
echo array | join: " "
|
||||||
|
-%}
|
||||||
|
LIQUID
|
||||||
|
|
||||||
|
assert_template_result('1 2 3', <<~LIQUID, 'array' => [1, 2, 3])
|
||||||
|
{%- liquid
|
||||||
|
for value in array
|
||||||
|
echo value
|
||||||
|
unless forloop.last
|
||||||
|
echo " "
|
||||||
|
endunless
|
||||||
|
endfor
|
||||||
|
-%}
|
||||||
|
LIQUID
|
||||||
|
|
||||||
|
assert_template_result('4 8 12 6', <<~LIQUID, 'array' => [1, 2, 3])
|
||||||
|
{%- liquid
|
||||||
|
for value in array
|
||||||
|
assign double_value = value | times: 2
|
||||||
|
echo double_value | times: 2
|
||||||
|
unless forloop.last
|
||||||
|
echo " "
|
||||||
|
endunless
|
||||||
|
endfor
|
||||||
|
|
||||||
|
echo " "
|
||||||
|
echo double_value
|
||||||
|
-%}
|
||||||
|
LIQUID
|
||||||
|
|
||||||
|
assert_template_result('abc', <<~LIQUID)
|
||||||
|
{%- liquid echo "a" -%}
|
||||||
|
b
|
||||||
|
{%- liquid echo "c" -%}
|
||||||
|
LIQUID
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_liquid_tag_errors
|
||||||
|
assert_match_syntax_error("syntax error (line 1): Unknown tag 'error'", <<~LIQUID)
|
||||||
|
{%- liquid error no such tag -%}
|
||||||
|
LIQUID
|
||||||
|
|
||||||
|
assert_match_syntax_error("syntax error (line 7): Unknown tag 'error'", <<~LIQUID)
|
||||||
|
{{ test }}
|
||||||
|
|
||||||
|
{%-
|
||||||
|
liquid
|
||||||
|
for value in array
|
||||||
|
|
||||||
|
error no such tag
|
||||||
|
endfor
|
||||||
|
-%}
|
||||||
|
LIQUID
|
||||||
|
|
||||||
|
assert_match_syntax_error("syntax error (line 2): Unknown tag '!!! the guards are vigilant'", <<~LIQUID)
|
||||||
|
{%- liquid
|
||||||
|
!!! the guards are vigilant
|
||||||
|
-%}
|
||||||
|
LIQUID
|
||||||
|
|
||||||
|
assert_match_syntax_error("syntax error (line 4): 'for' tag was never closed", <<~LIQUID)
|
||||||
|
{%- liquid
|
||||||
|
for value in array
|
||||||
|
echo 'forgot to close the for tag'
|
||||||
|
-%}
|
||||||
|
LIQUID
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_line_number_is_correct_after_a_blank_token
|
||||||
|
assert_match_syntax_error("syntax error (line 3): Unknown tag 'error'", "{% liquid echo ''\n\n error %}")
|
||||||
|
assert_match_syntax_error("syntax error (line 3): Unknown tag 'error'", "{% liquid echo ''\n \n error %}")
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_cannot_open_blocks_living_past_a_liquid_tag
|
||||||
|
assert_match_syntax_error("syntax error (line 3): 'if' tag was never closed", <<~LIQUID)
|
||||||
|
{%- liquid
|
||||||
|
if true
|
||||||
|
-%}
|
||||||
|
{%- endif -%}
|
||||||
|
LIQUID
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_quirk_can_close_blocks_created_before_a_liquid_tag
|
||||||
|
assert_template_result("42", <<~LIQUID)
|
||||||
|
{%- if true -%}
|
||||||
|
42
|
||||||
|
{%- liquid endif -%}
|
||||||
|
LIQUID
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_liquid_tag_in_raw
|
||||||
|
assert_template_result("{% liquid echo 'test' %}\n", <<~LIQUID)
|
||||||
|
{% raw %}{% liquid echo 'test' %}{% endraw %}
|
||||||
|
LIQUID
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -80,10 +80,7 @@ class VariableTest < Minitest::Test
|
|||||||
assigns['test'] = 'Tobi'
|
assigns['test'] = 'Tobi'
|
||||||
assert_equal 'Hello Tobi', template.render!(assigns)
|
assert_equal 'Hello Tobi', template.render!(assigns)
|
||||||
assigns.delete('test')
|
assigns.delete('test')
|
||||||
e = assert_raises(RuntimeError) do
|
assert_equal "Hello ", template.render!(assigns)
|
||||||
template.render!(assigns)
|
|
||||||
end
|
|
||||||
assert_equal "Unknown variable 'test'", e.message
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_multiline_variable
|
def test_multiline_variable
|
||||||
|
|||||||
@@ -37,18 +37,18 @@ module Minitest
|
|||||||
include Liquid
|
include Liquid
|
||||||
|
|
||||||
def assert_template_result(expected, template, assigns = {}, message = nil)
|
def assert_template_result(expected, template, assigns = {}, message = nil)
|
||||||
assert_equal expected, Template.parse(template).render!(assigns), message
|
assert_equal expected, Template.parse(template, line_numbers: true).render!(assigns), message
|
||||||
end
|
end
|
||||||
|
|
||||||
def assert_template_result_matches(expected, template, assigns = {}, message = nil)
|
def assert_template_result_matches(expected, template, assigns = {}, message = nil)
|
||||||
return assert_template_result(expected, template, assigns, message) unless expected.is_a? Regexp
|
return assert_template_result(expected, template, assigns, message) unless expected.is_a? Regexp
|
||||||
|
|
||||||
assert_match expected, Template.parse(template).render!(assigns), message
|
assert_match expected, Template.parse(template, line_numbers: true).render!(assigns), message
|
||||||
end
|
end
|
||||||
|
|
||||||
def assert_match_syntax_error(match, template, assigns = {})
|
def assert_match_syntax_error(match, template, assigns = {})
|
||||||
exception = assert_raises(Liquid::SyntaxError) do
|
exception = assert_raises(Liquid::SyntaxError) do
|
||||||
Template.parse(template).render(assigns)
|
Template.parse(template, line_numbers: true).render(assigns)
|
||||||
end
|
end
|
||||||
assert_match match, exception.message
|
assert_match match, exception.message
|
||||||
end
|
end
|
||||||
@@ -84,6 +84,13 @@ module Minitest
|
|||||||
ensure
|
ensure
|
||||||
Liquid::Template.error_mode = old_mode
|
Liquid::Template.error_mode = old_mode
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def with_custom_tag(tag_name, tag_class)
|
||||||
|
Liquid::Template.register_tag(tag_name, tag_class)
|
||||||
|
yield
|
||||||
|
ensure
|
||||||
|
Liquid::Template.tags.delete(tag_name)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -44,10 +44,47 @@ class BlockUnitTest < Minitest::Test
|
|||||||
end
|
end
|
||||||
|
|
||||||
def test_with_custom_tag
|
def test_with_custom_tag
|
||||||
Liquid::Template.register_tag("testtag", Block)
|
with_custom_tag('testtag', Block) do
|
||||||
assert Liquid::Template.parse("{% testtag %} {% endtesttag %}")
|
assert Liquid::Template.parse("{% testtag %} {% endtesttag %}")
|
||||||
ensure
|
end
|
||||||
Liquid::Template.tags.delete('testtag')
|
end
|
||||||
|
|
||||||
|
def test_custom_block_tags_have_a_default_render_to_output_buffer_method_for_backwards_compatibility
|
||||||
|
klass1 = Class.new(Block) do
|
||||||
|
def render(*)
|
||||||
|
'hello'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
with_custom_tag('blabla', klass1) do
|
||||||
|
template = Liquid::Template.parse("{% blabla %} bla {% endblabla %}")
|
||||||
|
|
||||||
|
assert_equal 'hello', template.render
|
||||||
|
|
||||||
|
buf = ''
|
||||||
|
output = template.render({}, output: buf)
|
||||||
|
assert_equal 'hello', output
|
||||||
|
assert_equal 'hello', buf
|
||||||
|
assert_equal buf.object_id, output.object_id
|
||||||
|
end
|
||||||
|
|
||||||
|
klass2 = Class.new(klass1) do
|
||||||
|
def render(*)
|
||||||
|
'foo' + super + 'bar'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
with_custom_tag('blabla', klass2) do
|
||||||
|
template = Liquid::Template.parse("{% blabla %} foo {% endblabla %}")
|
||||||
|
|
||||||
|
assert_equal 'foohellobar', template.render
|
||||||
|
|
||||||
|
buf = ''
|
||||||
|
output = template.render({}, output: buf)
|
||||||
|
assert_equal 'foohellobar', output
|
||||||
|
assert_equal 'foohellobar', buf
|
||||||
|
assert_equal buf.object_id, output.object_id
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|||||||
@@ -18,4 +18,42 @@ class TagUnitTest < Minitest::Test
|
|||||||
tag = Tag.parse("some_tag", "", Tokenizer.new(""), ParseContext.new)
|
tag = Tag.parse("some_tag", "", Tokenizer.new(""), ParseContext.new)
|
||||||
assert_equal 'some_tag', tag.tag_name
|
assert_equal 'some_tag', tag.tag_name
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_custom_tags_have_a_default_render_to_output_buffer_method_for_backwards_compatibility
|
||||||
|
klass1 = Class.new(Tag) do
|
||||||
|
def render(*)
|
||||||
|
'hello'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
with_custom_tag('blabla', klass1) do
|
||||||
|
template = Liquid::Template.parse("{% blabla %}")
|
||||||
|
|
||||||
|
assert_equal 'hello', template.render
|
||||||
|
|
||||||
|
buf = ''
|
||||||
|
output = template.render({}, output: buf)
|
||||||
|
assert_equal 'hello', output
|
||||||
|
assert_equal 'hello', buf
|
||||||
|
assert_equal buf.object_id, output.object_id
|
||||||
|
end
|
||||||
|
|
||||||
|
klass2 = Class.new(klass1) do
|
||||||
|
def render(*)
|
||||||
|
'foo' + super + 'bar'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
with_custom_tag('blabla', klass2) do
|
||||||
|
template = Liquid::Template.parse("{% blabla %}")
|
||||||
|
|
||||||
|
assert_equal 'foohellobar', template.render
|
||||||
|
|
||||||
|
buf = ''
|
||||||
|
output = template.render({}, output: buf)
|
||||||
|
assert_equal 'foohellobar', output
|
||||||
|
assert_equal 'foohellobar', buf
|
||||||
|
assert_equal buf.object_id, output.object_id
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
Reference in New Issue
Block a user