mirror of
https://github.com/kemko/liquid.git
synced 2026-01-07 10:45:42 +03:00
Compare commits
20 Commits
objects-op
...
formatter
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f5ba7ae4b2 | ||
|
|
9e666d7528 | ||
|
|
5ee8a32cb8 | ||
|
|
d2cbc5610e | ||
|
|
4a17d1a4ad | ||
|
|
8a8996387b | ||
|
|
9310640bdd | ||
|
|
4c3381a523 | ||
|
|
261aa2e726 | ||
|
|
247c51ac70 | ||
|
|
37dbec3610 | ||
|
|
ff253a04c6 | ||
|
|
25ef0df671 | ||
|
|
32460c255b | ||
|
|
724d625f47 | ||
|
|
f658dcee8b | ||
|
|
fa6cd6287e | ||
|
|
068791d698 | ||
|
|
3a082ddbbd | ||
|
|
03b3446119 |
1
Gemfile
1
Gemfile
@@ -2,7 +2,6 @@ source 'https://rubygems.org'
|
|||||||
|
|
||||||
gemspec
|
gemspec
|
||||||
gem 'stackprof', platforms: :mri_21
|
gem 'stackprof', platforms: :mri_21
|
||||||
gem 'allocation_tracer', platforms: :mri_21
|
|
||||||
|
|
||||||
group :test do
|
group :test do
|
||||||
gem 'spy', '0.4.1'
|
gem 'spy', '0.4.1'
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
## 4.0.0 / not yet released / branch "master"
|
## 4.0.0 / not yet released / branch "master"
|
||||||
### Changed
|
### Changed
|
||||||
|
* Add sort_natural filter (#554) [Martin Hanzel, arthanzel]
|
||||||
* Add forloop.parentloop as a reference to the parent loop (#520) [Justin Li, pushrax]
|
* Add forloop.parentloop as a reference to the parent loop (#520) [Justin Li, pushrax]
|
||||||
* Block parsing moved to BlockBody class (#458) [Dylan Thacker-Smith, dylanahsmith]
|
* Block parsing moved to BlockBody class (#458) [Dylan Thacker-Smith, dylanahsmith]
|
||||||
* Add concat filter to concatenate arrays (#429) [Diogo Beato, dvbeato]
|
* Add concat filter to concatenate arrays (#429) [Diogo Beato, dvbeato]
|
||||||
@@ -9,6 +10,8 @@
|
|||||||
* Liquid::Template.file_system's read_template_file method is no longer passed the context. (#441) [James Reid-Smith, sunblaze]
|
* Liquid::Template.file_system's read_template_file method is no longer passed the context. (#441) [James Reid-Smith, sunblaze]
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
* Fix naming of the "context variable" when dynamically including a template (#559) [Justin Li, pushrax]
|
||||||
|
* Gracefully accept empty strings in the date filter (#555) [Loren Hale, boobooninja]
|
||||||
* Fix capturing into variables with a hyphen in the name (#505) [Florian Weingarten, fw42]
|
* Fix capturing into variables with a hyphen in the name (#505) [Florian Weingarten, fw42]
|
||||||
* Fix case sensitivity regression in date standard filter (#499) [Kelley Reynolds, kreynolds]
|
* Fix case sensitivity regression in date standard filter (#499) [Kelley Reynolds, kreynolds]
|
||||||
* Disallow filters with no variable in strict mode (#475) [Justin Li, pushrax]
|
* Disallow filters with no variable in strict mode (#475) [Justin Li, pushrax]
|
||||||
|
|||||||
14
Rakefile
14
Rakefile
@@ -71,20 +71,6 @@ namespace :profile do
|
|||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
namespace :memory do
|
|
||||||
|
|
||||||
desc "Run the liquid memory tracer"
|
|
||||||
task :run do
|
|
||||||
ruby "./performance/memory.rb"
|
|
||||||
end
|
|
||||||
|
|
||||||
desc "Run the liquid memory tracer with strict parsing"
|
|
||||||
task :strict do
|
|
||||||
ruby "./performance/memory.rb strict"
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
desc "Run example"
|
desc "Run example"
|
||||||
task :example do
|
task :example do
|
||||||
ruby "-w -d -Ilib example/server/server.rb"
|
ruby "-w -d -Ilib example/server/server.rb"
|
||||||
|
|||||||
@@ -57,6 +57,10 @@ module Liquid
|
|||||||
@block_delimiter ||= "end#{block_name}"
|
@block_delimiter ||= "end#{block_name}"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def format
|
||||||
|
"{% #{raw} %}#{@body.format}{% #{block_delimiter} %}"
|
||||||
|
end
|
||||||
|
|
||||||
protected
|
protected
|
||||||
|
|
||||||
def parse_body(body, tokens)
|
def parse_body(body, tokens)
|
||||||
|
|||||||
@@ -68,8 +68,7 @@ module Liquid
|
|||||||
end
|
end
|
||||||
|
|
||||||
def render(context)
|
def render(context)
|
||||||
@output ||= []
|
output = []
|
||||||
@output.clear
|
|
||||||
context.resource_limits.render_score += @nodelist.length
|
context.resource_limits.render_score += @nodelist.length
|
||||||
|
|
||||||
@nodelist.each do |token|
|
@nodelist.each do |token|
|
||||||
@@ -88,16 +87,28 @@ module Liquid
|
|||||||
token_output = render_token(token, context)
|
token_output = render_token(token, context)
|
||||||
|
|
||||||
unless token.is_a?(Block) && token.blank?
|
unless token.is_a?(Block) && token.blank?
|
||||||
@output << token_output
|
output << token_output
|
||||||
end
|
end
|
||||||
rescue MemoryError => e
|
rescue MemoryError => e
|
||||||
raise e
|
raise e
|
||||||
rescue ::StandardError => e
|
rescue ::StandardError => e
|
||||||
@output << context.handle_error(e, token)
|
output << context.handle_error(e, token)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@output.join
|
output.join
|
||||||
|
end
|
||||||
|
|
||||||
|
def format
|
||||||
|
@nodelist.map do |node|
|
||||||
|
if node.is_a?(Variable)
|
||||||
|
"{{ #{node.format} }}"
|
||||||
|
elsif node.is_a?(Tag)
|
||||||
|
node.format
|
||||||
|
else
|
||||||
|
node
|
||||||
|
end
|
||||||
|
end.join
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|||||||
@@ -33,6 +33,12 @@ module Liquid
|
|||||||
@right = right
|
@right = right
|
||||||
@child_relation = nil
|
@child_relation = nil
|
||||||
@child_condition = nil
|
@child_condition = nil
|
||||||
|
|
||||||
|
if right && right.is_a?(VariableLookup) && right.name.nil?
|
||||||
|
# Strip blank variable lookups resulting from cases like:
|
||||||
|
# {% if foo && bar %}
|
||||||
|
@right = nil
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def evaluate(context = Context.new)
|
def evaluate(context = Context.new)
|
||||||
@@ -70,6 +76,20 @@ module Liquid
|
|||||||
"#<Condition #{[@left, @operator, @right].compact.join(' '.freeze)}>"
|
"#<Condition #{[@left, @operator, @right].compact.join(' '.freeze)}>"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def format
|
||||||
|
out = if operator.nil?
|
||||||
|
Expression.format(left)
|
||||||
|
else
|
||||||
|
"#{Expression.format(left)} #{operator} #{Expression.format(right)}"
|
||||||
|
end
|
||||||
|
|
||||||
|
if @child_relation
|
||||||
|
"#{out} #{@child_relation.to_s} #{@child_condition.format}"
|
||||||
|
else
|
||||||
|
out
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def equal_variables(left, right)
|
def equal_variables(left, right)
|
||||||
|
|||||||
@@ -13,13 +13,14 @@ module Liquid
|
|||||||
#
|
#
|
||||||
# context['bob'] #=> nil class Context
|
# context['bob'] #=> nil class Context
|
||||||
class Context
|
class Context
|
||||||
attr_reader :scopes, :registers, :environments, :resource_limits
|
attr_reader :scopes, :errors, :registers, :environments, :resource_limits
|
||||||
attr_accessor :exception_handler
|
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 = nil)
|
||||||
@environments = [environments].flatten
|
@environments = [environments].flatten
|
||||||
@scopes = [(outer_scope || {})]
|
@scopes = [(outer_scope || {})]
|
||||||
@registers = registers
|
@registers = registers
|
||||||
|
@errors = []
|
||||||
@resource_limits = resource_limits || ResourceLimits.new(Template.default_resource_limits)
|
@resource_limits = resource_limits || ResourceLimits.new(Template.default_resource_limits)
|
||||||
squash_instance_assigns_with_environments
|
squash_instance_assigns_with_environments
|
||||||
|
|
||||||
@@ -29,14 +30,10 @@ module Liquid
|
|||||||
self.exception_handler = ->(e) { true }
|
self.exception_handler = ->(e) { true }
|
||||||
end
|
end
|
||||||
|
|
||||||
@interrupts = nil
|
@interrupts = []
|
||||||
@filters = []
|
@filters = []
|
||||||
end
|
end
|
||||||
|
|
||||||
def errors
|
|
||||||
@errors ||= []
|
|
||||||
end
|
|
||||||
|
|
||||||
def strainer
|
def strainer
|
||||||
@strainer ||= Strainer.create(self, @filters)
|
@strainer ||= Strainer.create(self, @filters)
|
||||||
end
|
end
|
||||||
@@ -53,17 +50,17 @@ module Liquid
|
|||||||
|
|
||||||
# are there any not handled interrupts?
|
# are there any not handled interrupts?
|
||||||
def has_interrupt?
|
def has_interrupt?
|
||||||
@interrupts && !@interrupts.empty?
|
!@interrupts.empty?
|
||||||
end
|
end
|
||||||
|
|
||||||
# push an interrupt to the stack. this interrupt is considered not handled.
|
# push an interrupt to the stack. this interrupt is considered not handled.
|
||||||
def push_interrupt(e)
|
def push_interrupt(e)
|
||||||
(@interrupts ||= []).push(e)
|
@interrupts.push(e)
|
||||||
end
|
end
|
||||||
|
|
||||||
# pop an interrupt from the stack
|
# pop an interrupt from the stack
|
||||||
def pop_interrupt
|
def pop_interrupt
|
||||||
@interrupts.pop if @interrupts
|
@interrupts.pop
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ module Liquid
|
|||||||
'empty'.freeze => :empty?
|
'empty'.freeze => :empty?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
INVERTED_LITERALS = LITERALS.invert
|
||||||
|
|
||||||
def self.parse(markup)
|
def self.parse(markup)
|
||||||
if LITERALS.key?(markup)
|
if LITERALS.key?(markup)
|
||||||
LITERALS[markup]
|
LITERALS[markup]
|
||||||
@@ -29,5 +31,18 @@ module Liquid
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def self.format(value)
|
||||||
|
if INVERTED_LITERALS.key?(value)
|
||||||
|
INVERTED_LITERALS[value].dup
|
||||||
|
elsif value.is_a?(VariableLookup) || value.is_a?(RangeLookup)
|
||||||
|
value.format
|
||||||
|
elsif value.is_a?(String)
|
||||||
|
"\"#{value}\""
|
||||||
|
elsif value.is_a?(Range)
|
||||||
|
"(#{value.to_s})"
|
||||||
|
else
|
||||||
|
value.to_s
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ module Liquid
|
|||||||
|
|
||||||
class Include < Tag
|
class Include < Tag
|
||||||
def render_with_profiling(context)
|
def render_with_profiling(context)
|
||||||
Profiler.profile_children(context.evaluate(@template_name).to_s) do
|
Profiler.profile_children(context.evaluate(@template_name_expr).to_s) do
|
||||||
render_without_profiling(context)
|
render_without_profiling(context)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -18,5 +18,9 @@ module Liquid
|
|||||||
def evaluate(context)
|
def evaluate(context)
|
||||||
context.evaluate(@start_obj).to_i..context.evaluate(@end_obj).to_i
|
context.evaluate(@start_obj).to_i..context.evaluate(@end_obj).to_i
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def format
|
||||||
|
"(#{Expression.format(@start_obj)}..#{Expression.format(@end_obj)})"
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -122,6 +122,20 @@ module Liquid
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Sort elements of an array ignoring case if strings
|
||||||
|
# provide optional property with which to sort an array of hashes or drops
|
||||||
|
def sort_natural(input, property = nil)
|
||||||
|
ary = InputIterator.new(input)
|
||||||
|
|
||||||
|
if property.nil?
|
||||||
|
ary.sort {|a,b| a.casecmp(b) }
|
||||||
|
elsif ary.first.respond_to?(:[]) && !ary.first[property].nil?
|
||||||
|
ary.sort {|a,b| a[property].casecmp(b[property]) }
|
||||||
|
elsif ary.first.respond_to?(property)
|
||||||
|
ary.sort {|a,b| a.send(property).casecmp(b.send(property)) }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
# 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)
|
||||||
@@ -310,7 +324,10 @@ module Liquid
|
|||||||
def to_date(obj)
|
def to_date(obj)
|
||||||
return obj if obj.respond_to?(:strftime)
|
return obj if obj.respond_to?(:strftime)
|
||||||
|
|
||||||
obj = obj.downcase if obj.is_a?(String)
|
if obj.is_a?(String)
|
||||||
|
return nil if obj.empty?
|
||||||
|
obj = obj.downcase
|
||||||
|
end
|
||||||
|
|
||||||
case obj
|
case obj
|
||||||
when 'now'.freeze, 'today'.freeze
|
when 'now'.freeze, 'today'.freeze
|
||||||
|
|||||||
@@ -24,7 +24,10 @@ module Liquid
|
|||||||
end
|
end
|
||||||
|
|
||||||
def raw
|
def raw
|
||||||
"#{@tag_name} #{@markup}"
|
tag = @tag_name.strip
|
||||||
|
markup = @markup.strip
|
||||||
|
tag << " #{markup}" unless markup.empty?
|
||||||
|
tag
|
||||||
end
|
end
|
||||||
|
|
||||||
def name
|
def name
|
||||||
@@ -38,5 +41,9 @@ module Liquid
|
|||||||
def blank?
|
def blank?
|
||||||
false
|
false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def format
|
||||||
|
"{% #{raw} %}"
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -35,6 +35,10 @@ module Liquid
|
|||||||
def blank?
|
def blank?
|
||||||
true
|
true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def format
|
||||||
|
"{% assign #{@to} = #{@from.format} %}"
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
Template.register_tag('assign'.freeze, Assign)
|
Template.register_tag('assign'.freeze, Assign)
|
||||||
|
|||||||
@@ -32,6 +32,10 @@ module Liquid
|
|||||||
def blank?
|
def blank?
|
||||||
true
|
true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def format
|
||||||
|
"{% #{block_name} #{@to} %}#{@body.format}{% #{block_delimiter} %}"
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
Template.register_tag('capture'.freeze, Capture)
|
Template.register_tag('capture'.freeze, Capture)
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ module Liquid
|
|||||||
end
|
end
|
||||||
|
|
||||||
def parse(tokens)
|
def parse(tokens)
|
||||||
body = BlockBody.new
|
@initial_body = body = BlockBody.new
|
||||||
while more = parse_body(body, tokens)
|
while more = parse_body(body, tokens)
|
||||||
body = @blocks.last.attachment
|
body = @blocks.last.attachment
|
||||||
end
|
end
|
||||||
@@ -53,6 +53,21 @@ module Liquid
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def format
|
||||||
|
out = "{% case #{Expression.format(@left)} %}#{@initial_body.format}"
|
||||||
|
|
||||||
|
@blocks.each do |block|
|
||||||
|
if block.else?
|
||||||
|
out << "{% else %}"
|
||||||
|
else
|
||||||
|
out << "{% when #{Expression.format(block.right)} %}"
|
||||||
|
end
|
||||||
|
out << block.attachment.format
|
||||||
|
end
|
||||||
|
|
||||||
|
out + "{% endcase %}"
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def record_when_condition(markup)
|
def record_when_condition(markup)
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ module Liquid
|
|||||||
when NamedSyntax
|
when NamedSyntax
|
||||||
@variables = variables_from_string($2)
|
@variables = variables_from_string($2)
|
||||||
@name = Expression.parse($1)
|
@name = Expression.parse($1)
|
||||||
|
@named = true
|
||||||
when SimpleSyntax
|
when SimpleSyntax
|
||||||
@variables = variables_from_string(markup)
|
@variables = variables_from_string(markup)
|
||||||
@name = @variables.to_s
|
@name = @variables.to_s
|
||||||
@@ -43,6 +44,15 @@ module Liquid
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def format
|
||||||
|
formatted_vars = @variables.map { |v| Expression.format(v) }.join(', ')
|
||||||
|
if @named
|
||||||
|
"{% cycle #{Expression.format(@name)}: #{formatted_vars} %}"
|
||||||
|
else
|
||||||
|
"{% cycle #{formatted_vars} %}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def variables_from_string(markup)
|
def variables_from_string(markup)
|
||||||
|
|||||||
@@ -129,6 +129,21 @@ module Liquid
|
|||||||
result
|
result
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def format
|
||||||
|
args = [Expression.format(@collection_name)]
|
||||||
|
args << "reversed" if @reversed
|
||||||
|
args << "limit: #{Expression.format(@limit)}" if @limit
|
||||||
|
args << "offset: " << (@from == :continue ? 'continue' : Expression.format(@from)) if @from
|
||||||
|
|
||||||
|
out = "{% for #{@variable_name} in #{args.join(' ')} %}"
|
||||||
|
out << @for_block.format
|
||||||
|
if @else_block
|
||||||
|
out << "{% else %}"
|
||||||
|
out << @else_block.format
|
||||||
|
end
|
||||||
|
out + "{% endfor %}"
|
||||||
|
end
|
||||||
|
|
||||||
protected
|
protected
|
||||||
|
|
||||||
def lax_parse(markup)
|
def lax_parse(markup)
|
||||||
|
|||||||
@@ -48,6 +48,25 @@ module Liquid
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def format
|
||||||
|
out = ""
|
||||||
|
first_condition = true
|
||||||
|
|
||||||
|
@blocks.each do |block|
|
||||||
|
if block.else?
|
||||||
|
out << "{% else %}"
|
||||||
|
elsif first_condition
|
||||||
|
out << "{% #{block_name} #{block.format} %}"
|
||||||
|
first_condition = false
|
||||||
|
else
|
||||||
|
out << "{% elsif #{block.format} %}"
|
||||||
|
end
|
||||||
|
out << block.attachment.format
|
||||||
|
end
|
||||||
|
|
||||||
|
out + "{% #{block_delimiter} %}"
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def push_block(tag, markup)
|
def push_block(tag, markup)
|
||||||
|
|||||||
@@ -25,9 +25,8 @@ module Liquid
|
|||||||
template_name = $1
|
template_name = $1
|
||||||
variable_name = $3
|
variable_name = $3
|
||||||
|
|
||||||
@variable_name = Expression.parse(variable_name || template_name[1..-2])
|
@variable_name_expr = variable_name ? Expression.parse(variable_name) : nil
|
||||||
@context_variable_name = template_name[1..-2].split('/'.freeze).last
|
@template_name_expr = Expression.parse(template_name)
|
||||||
@template_name = Expression.parse(template_name)
|
|
||||||
@attributes = {}
|
@attributes = {}
|
||||||
|
|
||||||
markup.scan(TagAttributes) do |key, value|
|
markup.scan(TagAttributes) do |key, value|
|
||||||
@@ -44,7 +43,15 @@ module Liquid
|
|||||||
|
|
||||||
def render(context)
|
def render(context)
|
||||||
partial = load_cached_partial(context)
|
partial = load_cached_partial(context)
|
||||||
variable = context.evaluate(@variable_name)
|
|
||||||
|
template_name = context.evaluate(@template_name_expr)
|
||||||
|
context_variable_name = template_name.split('/'.freeze).last
|
||||||
|
|
||||||
|
variable = if @variable_name_expr
|
||||||
|
context.evaluate(@variable_name_expr)
|
||||||
|
else
|
||||||
|
context.find_variable(template_name)
|
||||||
|
end
|
||||||
|
|
||||||
context.stack do
|
context.stack do
|
||||||
@attributes.each do |key, value|
|
@attributes.each do |key, value|
|
||||||
@@ -53,20 +60,35 @@ module Liquid
|
|||||||
|
|
||||||
if variable.is_a?(Array)
|
if variable.is_a?(Array)
|
||||||
variable.collect do |var|
|
variable.collect do |var|
|
||||||
context[@context_variable_name] = var
|
context[context_variable_name] = var
|
||||||
partial.render(context)
|
partial.render(context)
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
context[@context_variable_name] = variable
|
context[context_variable_name] = variable
|
||||||
partial.render(context)
|
partial.render(context)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def format
|
||||||
|
segments = [@tag_name, Expression.format(@template_name_expr)]
|
||||||
|
|
||||||
|
if @variable_name_expr
|
||||||
|
segments << "with"
|
||||||
|
segments << Expression.format(@variable_name_expr)
|
||||||
|
end
|
||||||
|
|
||||||
|
unless @attributes.empty?
|
||||||
|
segments << @attributes.map { |k, v| "#{k}: #{Expression.format(v)}" }.join(", ")
|
||||||
|
end
|
||||||
|
|
||||||
|
"{% #{segments.join(' ')} %}"
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
def load_cached_partial(context)
|
def load_cached_partial(context)
|
||||||
cached_partials = context.registers[:cached_partials] || {}
|
cached_partials = context.registers[:cached_partials] || {}
|
||||||
template_name = context.evaluate(@template_name)
|
template_name = context.evaluate(@template_name_expr)
|
||||||
|
|
||||||
if cached = cached_partials[template_name]
|
if cached = cached_partials[template_name]
|
||||||
return cached
|
return cached
|
||||||
@@ -81,7 +103,7 @@ module Liquid
|
|||||||
def read_template_from_file_system(context)
|
def read_template_from_file_system(context)
|
||||||
file_system = context.registers[:file_system] || Liquid::Template.file_system
|
file_system = context.registers[:file_system] || Liquid::Template.file_system
|
||||||
|
|
||||||
file_system.read_template_file(context.evaluate(@template_name))
|
file_system.read_template_file(context.evaluate(@template_name_expr))
|
||||||
end
|
end
|
||||||
|
|
||||||
def pass_options
|
def pass_options
|
||||||
|
|||||||
@@ -24,6 +24,10 @@ module Liquid
|
|||||||
def blank?
|
def blank?
|
||||||
@body.empty?
|
@body.empty?
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def format
|
||||||
|
"{% #{block_name} %}#{@body}{% #{block_delimiter} %}"
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
Template.register_tag('raw'.freeze, Raw)
|
Template.register_tag('raw'.freeze, Raw)
|
||||||
|
|||||||
@@ -66,6 +66,12 @@ module Liquid
|
|||||||
result << "</tr>\n"
|
result << "</tr>\n"
|
||||||
result
|
result
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def format
|
||||||
|
tag_main = "#{block_name} #{@variable_name} in #{Expression.format(@collection_name)}"
|
||||||
|
args = @attributes.map { |k, v| "#{k}: #{Expression.format(v)}" }.join(", ")
|
||||||
|
"{% #{tag_main} #{args} %}#{@body.format}{% #{block_delimiter} %}"
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
Template.register_tag('tablerow'.freeze, TableRow)
|
Template.register_tag('tablerow'.freeze, TableRow)
|
||||||
|
|||||||
@@ -28,13 +28,34 @@ module Liquid
|
|||||||
@markup
|
@markup
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def format
|
||||||
|
out = Expression.format(@name)
|
||||||
|
|
||||||
|
@filters.each do |filter|
|
||||||
|
out << " | "
|
||||||
|
out << filter[0]
|
||||||
|
|
||||||
|
args = filter[1].map { |arg| Expression.format(arg) }
|
||||||
|
|
||||||
|
if filter.size > 2
|
||||||
|
args += filter[2].map { |key, arg| "#{key}: #{Expression.format(arg)}" }
|
||||||
|
end
|
||||||
|
|
||||||
|
unless args.empty?
|
||||||
|
out << ": "
|
||||||
|
out << args.join(', ')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
out
|
||||||
|
end
|
||||||
|
|
||||||
def markup_context(markup)
|
def markup_context(markup)
|
||||||
"in \"{{#{markup}}}\""
|
"in \"{{#{markup}}}\""
|
||||||
end
|
end
|
||||||
|
|
||||||
def lax_parse(markup)
|
def lax_parse(markup)
|
||||||
@filters ||= []
|
@filters = []
|
||||||
@filters.clear
|
|
||||||
if markup =~ /(#{QuotedFragment})(.*)/om
|
if markup =~ /(#{QuotedFragment})(.*)/om
|
||||||
name_markup = $1
|
name_markup = $1
|
||||||
filter_markup = $2
|
filter_markup = $2
|
||||||
@@ -53,8 +74,7 @@ module Liquid
|
|||||||
end
|
end
|
||||||
|
|
||||||
def strict_parse(markup)
|
def strict_parse(markup)
|
||||||
@filters ||= []
|
@filters = []
|
||||||
@filters.clear
|
|
||||||
p = Parser.new(markup)
|
p = Parser.new(markup)
|
||||||
|
|
||||||
@name = Expression.parse(p.expression)
|
@name = Expression.parse(p.expression)
|
||||||
|
|||||||
@@ -10,20 +10,22 @@ module Liquid
|
|||||||
end
|
end
|
||||||
|
|
||||||
def initialize(markup)
|
def initialize(markup)
|
||||||
@lookups = markup.scan(VariableParser)
|
lookups = markup.scan(VariableParser)
|
||||||
|
|
||||||
name = @lookups.shift
|
name = lookups.shift
|
||||||
if name =~ SQUARE_BRACKETED
|
if name =~ SQUARE_BRACKETED
|
||||||
|
@name_expression = true
|
||||||
name = Expression.parse($1)
|
name = Expression.parse($1)
|
||||||
end
|
end
|
||||||
@name = name
|
@name = name
|
||||||
|
|
||||||
|
@lookups = lookups
|
||||||
@command_flags = 0
|
@command_flags = 0
|
||||||
|
|
||||||
@lookups.each_index do |i|
|
@lookups.each_index do |i|
|
||||||
lookup = @lookups[i]
|
lookup = lookups[i]
|
||||||
if lookup =~ SQUARE_BRACKETED
|
if lookup =~ SQUARE_BRACKETED
|
||||||
@lookups[i] = Expression.parse($1)
|
lookups[i] = Expression.parse($1)
|
||||||
elsif COMMAND_METHODS.include?(lookup)
|
elsif COMMAND_METHODS.include?(lookup)
|
||||||
@command_flags |= 1 << i
|
@command_flags |= 1 << i
|
||||||
end
|
end
|
||||||
@@ -70,6 +72,20 @@ module Liquid
|
|||||||
self.class == other.class && self.state == other.state
|
self.class == other.class && self.state == other.state
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def format
|
||||||
|
out = @name_expression ? Expression.format(@name) : @name.dup
|
||||||
|
|
||||||
|
@lookups.each do |lookup|
|
||||||
|
if lookup.is_a?(String) && lookup =~ /^#{VariableSegment}+$/
|
||||||
|
out << ".#{lookup}"
|
||||||
|
else
|
||||||
|
out << "[#{Expression.format(lookup)}]"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
out
|
||||||
|
end
|
||||||
|
|
||||||
protected
|
protected
|
||||||
|
|
||||||
def state
|
def state
|
||||||
|
|||||||
@@ -1,19 +0,0 @@
|
|||||||
at_exit do
|
|
||||||
p 'Objects distribution:'
|
|
||||||
require 'pp'
|
|
||||||
pp ObjectSpace.count_objects
|
|
||||||
end
|
|
||||||
|
|
||||||
require 'allocation_tracer' rescue fail("install allocation_tracer extension/gem")
|
|
||||||
require File.dirname(__FILE__) + '/theme_runner'
|
|
||||||
|
|
||||||
Liquid::Template.error_mode = ARGV.first.to_sym if ARGV.first
|
|
||||||
profiler = ThemeRunner.new
|
|
||||||
|
|
||||||
require 'allocation_tracer/trace'
|
|
||||||
|
|
||||||
puts "Profiling memory usage..."
|
|
||||||
|
|
||||||
200.times do
|
|
||||||
profiler.run
|
|
||||||
end
|
|
||||||
@@ -74,11 +74,34 @@ class FiltersTest < Minitest::Test
|
|||||||
@context['numbers'] = [2,1,4,3]
|
@context['numbers'] = [2,1,4,3]
|
||||||
@context['words'] = ['expected', 'as', 'alphabetic']
|
@context['words'] = ['expected', 'as', 'alphabetic']
|
||||||
@context['arrays'] = ['flower', 'are']
|
@context['arrays'] = ['flower', 'are']
|
||||||
|
@context['case_sensitive'] = ['sensitive', 'Expected', 'case']
|
||||||
|
|
||||||
assert_equal [1,2,3,4], Variable.new("numbers | sort").render(@context)
|
assert_equal [1,2,3,4], Variable.new("numbers | sort").render(@context)
|
||||||
assert_equal ['alphabetic', 'as', 'expected'], Variable.new("words | sort").render(@context)
|
assert_equal ['alphabetic', 'as', 'expected'], Variable.new("words | sort").render(@context)
|
||||||
assert_equal [3], Variable.new("value | sort").render(@context)
|
assert_equal [3], Variable.new("value | sort").render(@context)
|
||||||
assert_equal ['are', 'flower'], Variable.new("arrays | sort").render(@context)
|
assert_equal ['are', 'flower'], Variable.new("arrays | sort").render(@context)
|
||||||
|
assert_equal ['Expected', 'case', 'sensitive'], Variable.new("case_sensitive | sort").render(@context)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_sort_natural
|
||||||
|
@context['words'] = ['case', 'Assert', 'Insensitive']
|
||||||
|
@context['hashes'] = [{ 'a' => 'A'}, { 'a' => 'b'}, { 'a' => 'C' }]
|
||||||
|
@context['objects'] = [TestObject.new('A'), TestObject.new('b'), TestObject.new('C')]
|
||||||
|
|
||||||
|
# Test strings
|
||||||
|
assert_equal ['Assert', 'case', 'Insensitive'], Variable.new("words | sort_natural").render(@context)
|
||||||
|
|
||||||
|
# Test hashes
|
||||||
|
sorted = Variable.new("hashes | sort_natural: 'a'").render(@context)
|
||||||
|
assert_equal sorted[0]['a'], 'A'
|
||||||
|
assert_equal sorted[1]['a'], 'b'
|
||||||
|
assert_equal sorted[2]['a'], 'C'
|
||||||
|
|
||||||
|
# Test objects
|
||||||
|
sorted = Variable.new("objects | sort_natural: 'a'").render(@context)
|
||||||
|
assert_equal sorted[0].a, 'A'
|
||||||
|
assert_equal sorted[1].a, 'b'
|
||||||
|
assert_equal sorted[2].a, 'C'
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_strip_html
|
def test_strip_html
|
||||||
@@ -136,3 +159,10 @@ class FiltersInTemplate < Minitest::Test
|
|||||||
assert_equal " 1000$ CAD ", Template.parse("{{1000 | money}}").render!(nil, [CanadianMoneyFilter])
|
assert_equal " 1000$ CAD ", Template.parse("{{1000 | money}}").render!(nil, [CanadianMoneyFilter])
|
||||||
end
|
end
|
||||||
end # FiltersTest
|
end # FiltersTest
|
||||||
|
|
||||||
|
class TestObject
|
||||||
|
attr_accessor :a
|
||||||
|
def initialize(a)
|
||||||
|
@a = a
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|||||||
172
test/integration/formatter_test.rb
Normal file
172
test/integration/formatter_test.rb
Normal file
@@ -0,0 +1,172 @@
|
|||||||
|
require 'test_helper'
|
||||||
|
|
||||||
|
class FormatterTest < Minitest::Test
|
||||||
|
def assert_format(expected, src)
|
||||||
|
with_error_mode(:lax) do
|
||||||
|
src_ast = Liquid::Template.parse(src).root
|
||||||
|
assert_equal expected, src_ast.format
|
||||||
|
|
||||||
|
fmt_ast = Liquid::Template.parse(src_ast.format).root
|
||||||
|
assert_equal expected, fmt_ast.format
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_filters
|
||||||
|
assert_format '{{ a | b: foo, c: "foo" }}', '{{a|b:foo,c:"foo"}}'
|
||||||
|
assert_format '{{ page.attribs.title | downcase }}', "{{page.attribs['title' ]| downcase}}"
|
||||||
|
assert_format '{{ page.attribs["t.i.t.l.e"] | downcase }}', "{{page.attribs['t.i.t.l.e'] | downcase }}"
|
||||||
|
assert_format '{{ page.attribs["t&tle"] | downcase }}', "{{page.attribs['t&tle'] | downcase }}"
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_conditionals
|
||||||
|
src = <<-eof
|
||||||
|
{% if true && !!%}
|
||||||
|
cats
|
||||||
|
{% elsif a or (b and c) && d%}
|
||||||
|
dogs
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{%unless something%}
|
||||||
|
cats
|
||||||
|
{% endunless%}
|
||||||
|
eof
|
||||||
|
|
||||||
|
expected = <<-eof
|
||||||
|
{% if true %}
|
||||||
|
cats
|
||||||
|
{% elsif a or b and c %}
|
||||||
|
dogs
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% unless something %}
|
||||||
|
cats
|
||||||
|
{% endunless %}
|
||||||
|
eof
|
||||||
|
|
||||||
|
assert_format expected, src
|
||||||
|
|
||||||
|
src = <<-eof
|
||||||
|
{%case var asdf $$^$ %}
|
||||||
|
{% when true%}
|
||||||
|
w
|
||||||
|
{% else%}
|
||||||
|
e
|
||||||
|
{%endcase %}
|
||||||
|
eof
|
||||||
|
|
||||||
|
expected = <<-eof
|
||||||
|
{% case var %}
|
||||||
|
{% when true %}
|
||||||
|
w
|
||||||
|
{% else %}
|
||||||
|
e
|
||||||
|
{% endcase %}
|
||||||
|
eof
|
||||||
|
|
||||||
|
assert_format expected, src
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_comments
|
||||||
|
assert_format "{% comment %} hunter2 {% endcomment %}", "{%comment %} hunter2 {% endcomment ^ %}"
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_assigns
|
||||||
|
assert_format '{% assign foo = "monkey" %}', "{%assign foo ='monkey' ^ %}"
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_looping
|
||||||
|
src = <<-eof
|
||||||
|
{% for i in (1..10) %}
|
||||||
|
cat
|
||||||
|
{%ifchanged%}{{i}}{% endifchanged %}
|
||||||
|
{% continue%}
|
||||||
|
{% else %}
|
||||||
|
dog
|
||||||
|
{%break %}
|
||||||
|
{% endfor %}
|
||||||
|
eof
|
||||||
|
|
||||||
|
expected = <<-eof
|
||||||
|
{% for i in (1..10) %}
|
||||||
|
cat
|
||||||
|
{% ifchanged %}{{ i }}{% endifchanged %}
|
||||||
|
{% continue %}
|
||||||
|
{% else %}
|
||||||
|
dog
|
||||||
|
{% break %}
|
||||||
|
{% endfor %}
|
||||||
|
eof
|
||||||
|
|
||||||
|
assert_format expected, src
|
||||||
|
|
||||||
|
src = "{% tablerow n in numbers cols:3 offset : 1 limit:6%} {{n}} {% endtablerow %}"
|
||||||
|
expected = "{% tablerow n in numbers cols: 3, offset: 1, limit: 6 %} {{ n }} {% endtablerow %}"
|
||||||
|
assert_format expected, src
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_capture
|
||||||
|
assert_format "{% capture foo %} foo {% endcapture %}", "{%capture foo %} foo {%endcapture%}"
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_cycle
|
||||||
|
assert_format '{% cycle "red", 2.8, "green", 1 %}', "{% cycle 'red',2.8,'green',1 %}"
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_augment
|
||||||
|
assert_format "{% decrement foo %}{% increment foo %}", "{% decrement foo%}{%increment foo %}"
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_raw
|
||||||
|
assert_format "{% raw %} foo {% endraw %}", "{%raw !!%} foo {%endraw foo%}"
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_include
|
||||||
|
src = <<-eof
|
||||||
|
{% include 'foo' %}
|
||||||
|
{% include 'foo' !!! why! %}
|
||||||
|
{% include 'foo' with bar %}
|
||||||
|
{% include 'foo' with bar baz: z qux:f %}
|
||||||
|
{% include 'foo' baz: z qux:f %}
|
||||||
|
eof
|
||||||
|
|
||||||
|
expected = <<-eof
|
||||||
|
{% include "foo" %}
|
||||||
|
{% include "foo" %}
|
||||||
|
{% include "foo" with bar %}
|
||||||
|
{% include "foo" with bar baz: z, qux: f %}
|
||||||
|
{% include "foo" baz: z, qux: f %}
|
||||||
|
eof
|
||||||
|
|
||||||
|
assert_format expected, src
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_quirks
|
||||||
|
src = <<-eof
|
||||||
|
{% if a == 'foo' or (b == 'bar' and c == 'baz') or false %} YES {% endif %}
|
||||||
|
{% if true && false %} YES {% endif %}
|
||||||
|
{% if false || true %} YES {% endif %}
|
||||||
|
{{ 'hi there' | split$$$:' ' | first }}""
|
||||||
|
{{ 'X' | downcase) }}
|
||||||
|
{{ 'hi there' | split:"t"" | reverse | first}}
|
||||||
|
{{ 'hi there' | split:"t"" | remove:"i" | first}}
|
||||||
|
{% for i in (1...5) %}{{ i }}{% endfor %}
|
||||||
|
{{test |a|b|}}
|
||||||
|
{{|test|}}
|
||||||
|
eof
|
||||||
|
|
||||||
|
expected = <<-eof
|
||||||
|
{% if a == "foo" or b == "bar" and c == "baz" or false %} YES {% endif %}
|
||||||
|
{% if true %} YES {% endif %}
|
||||||
|
{% if false %} YES {% endif %}
|
||||||
|
{{ "hi there" | split: " " | first }}""
|
||||||
|
{{ "X" | downcase }}
|
||||||
|
{{ "hi there" | split: "t" | reverse | first }}
|
||||||
|
{{ "hi there" | split: "t" | first }}
|
||||||
|
{% for i in (1..5) %}{{ i }}{% endfor %}
|
||||||
|
{{ test | a | b }}
|
||||||
|
{{ test }}
|
||||||
|
eof
|
||||||
|
|
||||||
|
assert_format expected, src
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -253,6 +253,8 @@ class StandardFiltersTest < Minitest::Test
|
|||||||
|
|
||||||
assert_equal nil, @filters.date(nil, "%B")
|
assert_equal nil, @filters.date(nil, "%B")
|
||||||
|
|
||||||
|
assert_equal '', @filters.date('', "%B")
|
||||||
|
|
||||||
assert_equal "07/05/2006", @filters.date(1152098955, "%m/%d/%Y")
|
assert_equal "07/05/2006", @filters.date(1152098955, "%m/%d/%Y")
|
||||||
assert_equal "07/05/2006", @filters.date("1152098955", "%m/%d/%Y")
|
assert_equal "07/05/2006", @filters.date("1152098955", "%m/%d/%Y")
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -219,4 +219,12 @@ class IncludeTagTest < Minitest::Test
|
|||||||
assert_equal 'x', Template.parse("{% include template %}", error_mode: :strict, include_options_blacklist: [:error_mode]).render!("template" => '{{ "X" || downcase }}')
|
assert_equal 'x', Template.parse("{% include template %}", error_mode: :strict, include_options_blacklist: [:error_mode]).render!("template" => '{{ "X" || downcase }}')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_including_via_variable_value
|
||||||
|
assert_template_result "from TestFileSystem", "{% assign page = 'pick_a_source' %}{% include page %}"
|
||||||
|
|
||||||
|
assert_template_result "Product: Draft 151cm ", "{% assign page = 'product' %}{% include page %}", "product" => {'title' => 'Draft 151cm'}
|
||||||
|
|
||||||
|
assert_template_result "Product: Draft 151cm ", "{% assign page = 'product' %}{% include page for foo %}", "foo" => {'title' => 'Draft 151cm'}
|
||||||
|
end
|
||||||
end # IncludeTagTest
|
end # IncludeTagTest
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ class TableRowTest < Minitest::Test
|
|||||||
|
|
||||||
def test_offset_and_limit
|
def test_offset_and_limit
|
||||||
assert_template_result("<tr class=\"row1\">\n<td class=\"col1\"> 1 </td><td class=\"col2\"> 2 </td><td class=\"col3\"> 3 </td></tr>\n<tr class=\"row2\"><td class=\"col1\"> 4 </td><td class=\"col2\"> 5 </td><td class=\"col3\"> 6 </td></tr>\n",
|
assert_template_result("<tr class=\"row1\">\n<td class=\"col1\"> 1 </td><td class=\"col2\"> 2 </td><td class=\"col3\"> 3 </td></tr>\n<tr class=\"row2\"><td class=\"col1\"> 4 </td><td class=\"col2\"> 5 </td><td class=\"col3\"> 6 </td></tr>\n",
|
||||||
'{% tablerow n in numbers cols:3 offset:1 limit:6%} {{n}} {% endtablerow %}',
|
'{% tablerow n in numbers cols:3, offset:1, limit:6%} {{n}} {% endtablerow %}',
|
||||||
'numbers' => [0,1,2,3,4,5,6,7])
|
'numbers' => [0,1,2,3,4,5,6,7])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -465,7 +465,6 @@ class ContextUnitTest < Minitest::Test
|
|||||||
mock_empty = Spy.on_instance_method(Array, :empty?)
|
mock_empty = Spy.on_instance_method(Array, :empty?)
|
||||||
mock_has_interrupt = Spy.on(@context, :has_interrupt?).and_call_through
|
mock_has_interrupt = Spy.on(@context, :has_interrupt?).and_call_through
|
||||||
|
|
||||||
@context.push_interrupt(StandardError.new)
|
|
||||||
@context.has_interrupt?
|
@context.has_interrupt?
|
||||||
|
|
||||||
refute mock_any.has_been_called?
|
refute mock_any.has_been_called?
|
||||||
|
|||||||
Reference in New Issue
Block a user