mirror of
https://github.com/kemko/liquid.git
synced 2026-01-16 15:05:44 +03:00
Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
712d97e37d | ||
|
|
ca72282dff | ||
|
|
e8a3fd10d4 | ||
|
|
e77b1a09b6 | ||
|
|
73b39beef2 | ||
|
|
fc63219087 | ||
|
|
53b6db48e3 | ||
|
|
0bbc22b027 | ||
|
|
145920738b | ||
|
|
6eb1f174de | ||
|
|
2e71ce1efe | ||
|
|
8204c61e31 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -4,4 +4,3 @@
|
||||
pkg
|
||||
*.rbc
|
||||
.rvmrc
|
||||
.ruby-version
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
# How to contribute
|
||||
|
||||
## Things we will merge
|
||||
|
||||
* Bugfixes
|
||||
* Performance improvements
|
||||
* Features which are likely to be useful to the majority of Liquid users
|
||||
|
||||
## Things we won't merge
|
||||
|
||||
* Code which introduces considerable performance degrations
|
||||
* Code which touches performance critical parts of Liquid and comes without benchmarks
|
||||
* Features which are not important for most people (we want to keep the core Liquid code small and tidy)
|
||||
* Features which can easily be implemented on top of Liquid (for example as a custom filter or custom filesystem)
|
||||
* Code which comes without tests
|
||||
* Code which breaks existing tests
|
||||
|
||||
## Workflow
|
||||
|
||||
* Fork the Liquid repository
|
||||
* Create a new branch in your fork
|
||||
* If it makes sense, add tests for your code and run a performance benchmark
|
||||
* Make sure all tests pass
|
||||
* Create a pull request
|
||||
* In the description, ping one of [@boourns](https://github.com/boourns), [@fw42](https://github.com/fw42), [@camilo](https://github.com/camilo), [@dylanahsmith](https://github.com/dylanahsmith), or [@arthurnn](https://github.com/arthurnn) and ask for a code review.
|
||||
|
||||
30
History.md
30
History.md
@@ -1,37 +1,12 @@
|
||||
# Liquid Version History
|
||||
|
||||
IMPORTANT: Liquid 2.6 is going to be the last version of Liquid which maintains explicit Ruby 1.8 compatability.
|
||||
The following releases will only be tested against Ruby 1.9 and Ruby 2.0 and are likely to break on Ruby 1.8.
|
||||
|
||||
## 2.6.1 / 2014-01-10 / branch "2-6-stable"
|
||||
## 2.5.5 / 2014-01-10 / branch "2-5-stable"
|
||||
|
||||
Security fix, cherry-picked from master (4e14a65):
|
||||
* Don't call to_sym when creating conditions for security reasons, see #273 [Bouke van der Bijl, bouk]
|
||||
* Prevent arbitrary method invocation on condition objects, see #274 [Dylan Thacker-Smith, dylanahsmith]
|
||||
|
||||
## 2.6.0 / 2013-11-25
|
||||
|
||||
* ...
|
||||
* Bugfix for #106: fix example servlet [gnowoel]
|
||||
* Bugfix for #97: strip_html filter supports multi-line tags [Jo Liss, joliss]
|
||||
* Bugfix for #114: strip_html filter supports style tags [James Allardice, jamesallardice]
|
||||
* Bugfix for #117: 'now' support for date filter in Ruby 1.9 [Notre Dame Webgroup, ndwebgroup]
|
||||
* Bugfix for #166: truncate filter on UTF-8 strings with Ruby 1.8 [Florian Weingarten, fw42]
|
||||
* Bugfix for #204: 'raw' parsing bug [Florian Weingarten, fw42]
|
||||
* Bugfix for #150: 'for' parsing bug [Peter Schröder, phoet]
|
||||
* Bugfix for #126: Strip CRLF in strip_newline [Peter Schröder, phoet]
|
||||
* Bugfix for #174, "can't convert Fixnum into String" for "replace" [wǒ_is神仙, jsw0528]
|
||||
* Allow a Liquid::Drop to be passed into Template#render [Daniel Huckstep, darkhelmet]
|
||||
* Resource limits [Florian Weingarten, fw42]
|
||||
* Add reverse filter [Jay Strybis, unreal]
|
||||
* Add utf-8 support
|
||||
* Use array instead of Hash to keep the registered filters [Tasos Stathopoulos, astathopoulos]
|
||||
* Cache tokenized partial templates [Tom Burns, boourns]
|
||||
* Avoid warnings in Ruby 1.9.3 [Marcus Stollsteimer, stomar]
|
||||
* Better documentation for 'include' tag (closes #163) [Peter Schröder, phoet]
|
||||
* Use of BigDecimal on filters to have better precision (closes #155) [Arthur Nogueira Neves, arthurnn]
|
||||
|
||||
## 2.5.4 / 2013-11-11 / branch "2.5-stable"
|
||||
## 2.5.4 / 2013-11-11
|
||||
|
||||
* Fix "can't convert Fixnum into String" for "replace", see #173, [wǒ_is神仙, jsw0528]
|
||||
|
||||
@@ -55,7 +30,6 @@ Yanked from rubygems, as it contained too many changes that broke compatibility.
|
||||
* Fix filter parser for args without space separators
|
||||
* Add support for filter keyword arguments
|
||||
|
||||
|
||||
## 2.4.0 / 2012-08-03
|
||||
|
||||
* Performance improvements
|
||||
|
||||
@@ -1,11 +1,5 @@
|
||||
# Liquid template engine
|
||||
|
||||
* [Contributing guidelines](CONTRIBUTING.md)
|
||||
* [Version history](History.md)
|
||||
* [Liquid documentation from Shopify](http://docs.shopify.com/themes/liquid-basics)
|
||||
* [Liquid Wiki from Shopify](http://wiki.shopify.com/Liquid)
|
||||
* [Website](http://liquidmarkup.org/)
|
||||
|
||||
## Introduction
|
||||
|
||||
Liquid is a template engine which was written with very specific requirements:
|
||||
|
||||
24
Rakefile
24
Rakefile
@@ -1,7 +1,9 @@
|
||||
#!/usr/bin/env ruby
|
||||
|
||||
require 'rubygems'
|
||||
require 'rake'
|
||||
require 'rake/testtask'
|
||||
$LOAD_PATH.unshift File.expand_path("../lib", __FILE__)
|
||||
require "liquid/version"
|
||||
require 'rubygems/package_task'
|
||||
|
||||
task :default => 'test'
|
||||
|
||||
@@ -11,20 +13,14 @@ Rake::TestTask.new(:test) do |t|
|
||||
t.verbose = false
|
||||
end
|
||||
|
||||
task :gem => :build
|
||||
task :build do
|
||||
system "gem build liquid.gemspec"
|
||||
gemspec = eval(File.read('liquid.gemspec'))
|
||||
Gem::PackageTask.new(gemspec) do |pkg|
|
||||
pkg.gem_spec = gemspec
|
||||
end
|
||||
|
||||
task :install => :build do
|
||||
system "gem install liquid-#{Liquid::VERSION}.gem"
|
||||
end
|
||||
|
||||
task :release => :build do
|
||||
system "git tag -a v#{Liquid::VERSION} -m 'Tagging #{Liquid::VERSION}'"
|
||||
system "git push --tags"
|
||||
system "gem push liquid-#{Liquid::VERSION}.gem"
|
||||
system "rm liquid-#{Liquid::VERSION}.gem"
|
||||
desc "Build the gem and release it to rubygems.org"
|
||||
task :release => :gem do
|
||||
sh "gem push pkg/liquid-#{gemspec.version}.gem"
|
||||
end
|
||||
|
||||
namespace :benchmark do
|
||||
|
||||
@@ -23,7 +23,11 @@ class Servlet < LiquidServlet
|
||||
end
|
||||
|
||||
def products
|
||||
{ 'products' => products_list, 'description' => description, 'section' => 'Snowboards', 'cool_products' => true}
|
||||
{ 'products' => products_list, 'section' => 'Snowboards', 'cool_products' => true}
|
||||
end
|
||||
|
||||
def description
|
||||
"List of Products ~ This is a list of products with price and description."
|
||||
end
|
||||
|
||||
private
|
||||
@@ -34,8 +38,4 @@ class Servlet < LiquidServlet
|
||||
{'name' => 'Arbor Diamond', 'price' => 59900, 'description' => 'the *arbor diamond* is a made up product because im obsessed with arbor and have no creativity'}]
|
||||
end
|
||||
|
||||
def description
|
||||
"List of Products ~ This is a list of products with price and description."
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@@ -45,7 +45,6 @@ module Liquid
|
||||
VariableParser = /\[[^\]]+\]|#{VariableSegment}+\??/o
|
||||
end
|
||||
|
||||
require "liquid/version"
|
||||
require 'liquid/drop'
|
||||
require 'liquid/extensions'
|
||||
require 'liquid/errors'
|
||||
|
||||
@@ -16,7 +16,7 @@ module Liquid
|
||||
when IsTag
|
||||
if token =~ FullToken
|
||||
|
||||
# if we found the proper block delimiter just end parsing here and let the outer block
|
||||
# if we found the proper block delimitor just end parsing here and let the outer block
|
||||
# proceed
|
||||
if block_delimiter == $1
|
||||
end_tag
|
||||
@@ -43,8 +43,8 @@ module Liquid
|
||||
end
|
||||
end
|
||||
|
||||
# Make sure that it's ok to end parsing in the current block.
|
||||
# Effectively this method will throw an exception unless the current block is
|
||||
# Make sure that its ok to end parsing in the current block.
|
||||
# Effectively this method will throw and exception unless the current block is
|
||||
# of type Document
|
||||
assert_missing_delimitation!
|
||||
end
|
||||
@@ -90,9 +90,6 @@ module Liquid
|
||||
|
||||
def render_all(list, context)
|
||||
output = []
|
||||
context.resource_limits[:render_length_current] = 0
|
||||
context.resource_limits[:render_score_current] += list.length
|
||||
|
||||
list.each do |token|
|
||||
# Break out if we have any unhanded interrupts.
|
||||
break if context.has_interrupt?
|
||||
@@ -106,15 +103,7 @@ module Liquid
|
||||
break
|
||||
end
|
||||
|
||||
token_output = (token.respond_to?(:render) ? token.render(context) : token)
|
||||
context.resource_limits[:render_length_current] += (token_output.respond_to?(:length) ? token_output.length : 1)
|
||||
if context.resource_limits_reached?
|
||||
context.resource_limits[:reached] = true
|
||||
raise MemoryError.new("Memory limits exceeded")
|
||||
end
|
||||
output << token_output
|
||||
rescue MemoryError => e
|
||||
raise e
|
||||
output << (token.respond_to?(:render) ? token.render(context) : token)
|
||||
rescue ::StandardError => e
|
||||
output << (context.handle_error(e))
|
||||
end
|
||||
|
||||
@@ -13,26 +13,19 @@ module Liquid
|
||||
#
|
||||
# context['bob'] #=> nil class Context
|
||||
class Context
|
||||
attr_reader :scopes, :errors, :registers, :environments, :resource_limits
|
||||
attr_reader :scopes, :errors, :registers, :environments
|
||||
|
||||
def initialize(environments = {}, outer_scope = {}, registers = {}, rethrow_errors = false, resource_limits = {})
|
||||
def initialize(environments = {}, outer_scope = {}, registers = {}, rethrow_errors = false)
|
||||
@environments = [environments].flatten
|
||||
@scopes = [(outer_scope || {})]
|
||||
@registers = registers
|
||||
@errors = []
|
||||
@rethrow_errors = rethrow_errors
|
||||
@resource_limits = (resource_limits || {}).merge!({ :render_score_current => 0, :assign_score_current => 0 })
|
||||
squash_instance_assigns_with_environments
|
||||
|
||||
@interrupts = []
|
||||
end
|
||||
|
||||
def resource_limits_reached?
|
||||
(@resource_limits[:render_length_limit] && @resource_limits[:render_length_current] > @resource_limits[:render_length_limit]) ||
|
||||
(@resource_limits[:render_score_limit] && @resource_limits[:render_score_current] > @resource_limits[:render_score_limit] ) ||
|
||||
(@resource_limits[:assign_score_limit] && @resource_limits[:assign_score_current] > @resource_limits[:assign_score_limit] )
|
||||
end
|
||||
|
||||
def strainer
|
||||
@strainer ||= Strainer.create(self)
|
||||
end
|
||||
@@ -53,7 +46,7 @@ module Liquid
|
||||
|
||||
# are there any not handled interrupts?
|
||||
def has_interrupt?
|
||||
@interrupts.any?
|
||||
!@interrupts.empty?
|
||||
end
|
||||
|
||||
# push an interrupt to the stack. this interrupt is considered not handled.
|
||||
@@ -172,7 +165,6 @@ module Liquid
|
||||
# Fetches an object starting at the local scope and then moving up the hierachy
|
||||
def find_variable(key)
|
||||
scope = @scopes.find { |s| s.has_key?(key) }
|
||||
variable = nil
|
||||
|
||||
if scope.nil?
|
||||
@environments.each do |e|
|
||||
|
||||
@@ -5,7 +5,7 @@ module Liquid
|
||||
parse(tokens)
|
||||
end
|
||||
|
||||
# There isn't a real delimiter
|
||||
# There isn't a real delimter
|
||||
def block_delimiter
|
||||
[]
|
||||
end
|
||||
|
||||
@@ -8,5 +8,4 @@ module Liquid
|
||||
class StandardError < Error; end
|
||||
class SyntaxError < Error; end
|
||||
class StackLevelError < Error; end
|
||||
class MemoryError < Error; end
|
||||
end
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
module Liquid
|
||||
# A Liquid file system is a way to let your templates retrieve other templates for use with the include tag.
|
||||
# A Liquid file system is way to let your templates retrieve other templates for use with the include tag.
|
||||
#
|
||||
# You can implement subclasses that retrieve templates from the database, from the file system using a different
|
||||
# path structure, you can provide them as hard-coded inline strings, or any manner that you see fit.
|
||||
|
||||
@@ -43,6 +43,7 @@ module Liquid
|
||||
'index0' => index,
|
||||
'col' => col + 1,
|
||||
'col0' => col,
|
||||
'index0' => index,
|
||||
'rindex' => length - index,
|
||||
'rindex0' => length - index - 1,
|
||||
'first' => (index == 0),
|
||||
@@ -56,7 +57,7 @@ module Liquid
|
||||
|
||||
result << "<td class=\"col#{col}\">" << render_all(@nodelist, context) << '</td>'
|
||||
|
||||
if col == cols and (index != length - 1)
|
||||
if col == cols and not (index == length - 1)
|
||||
col = 0
|
||||
row += 1
|
||||
result << "</tr>\n<tr class=\"row#{row}\">"
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# This library is free software. It may be used, redistributed and/or modified
|
||||
# under the same terms as Ruby itself
|
||||
#
|
||||
# This extension is used in order to expose the object of the implementing class
|
||||
# This extension is usesd in order to expose the object of the implementing class
|
||||
# to liquid as it were a Drop. It also limits the liquid-callable methods of the instance
|
||||
# to the allowed method passed with the liquid_methods call
|
||||
# Example:
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
require 'cgi'
|
||||
require 'bigdecimal'
|
||||
|
||||
module Liquid
|
||||
|
||||
@@ -11,12 +10,12 @@ module Liquid
|
||||
input.respond_to?(:size) ? input.size : 0
|
||||
end
|
||||
|
||||
# convert an input string to DOWNCASE
|
||||
# convert a input string to DOWNCASE
|
||||
def downcase(input)
|
||||
input.to_s.downcase
|
||||
end
|
||||
|
||||
# convert an input string to UPCASE
|
||||
# convert a input string to UPCASE
|
||||
def upcase(input)
|
||||
input.to_s.upcase
|
||||
end
|
||||
@@ -43,8 +42,7 @@ module Liquid
|
||||
if input.nil? then return end
|
||||
l = length.to_i - truncate_string.length
|
||||
l = 0 if l < 0
|
||||
truncated = RUBY_VERSION[0,3] == "1.8" ? input.scan(/./mu)[0...l].to_s : input[0...l]
|
||||
input.length > length.to_i ? truncated + truncate_string : input
|
||||
input.length > length.to_i ? input[0...l] + truncate_string : input
|
||||
end
|
||||
|
||||
def truncatewords(input, words = 15, truncate_string = "...")
|
||||
@@ -65,14 +63,15 @@ module Liquid
|
||||
end
|
||||
|
||||
def strip_html(input)
|
||||
input.to_s.gsub(/<script.*?<\/script>/m, '').gsub(/<!--.*?-->/m, '').gsub(/<style.*?<\/style>/m, '').gsub(/<.*?>/m, '')
|
||||
input.to_s.gsub(/<script.*?<\/script>/, '').gsub(/<!--.*?-->/, '').gsub(/<.*?>/, '')
|
||||
end
|
||||
|
||||
# Remove all newlines from the string
|
||||
def strip_newlines(input)
|
||||
input.to_s.gsub(/\r?\n/, '')
|
||||
input.to_s.gsub(/\n/, '')
|
||||
end
|
||||
|
||||
|
||||
# Join elements of the array with certain character between them
|
||||
def join(input, glue = ' ')
|
||||
[input].flatten.join(glue)
|
||||
@@ -91,12 +90,6 @@ module Liquid
|
||||
end
|
||||
end
|
||||
|
||||
# Reverse the elements of an array
|
||||
def reverse(input)
|
||||
ary = [input].flatten
|
||||
ary.reverse
|
||||
end
|
||||
|
||||
# map/collect on a given property
|
||||
def map(input, property)
|
||||
ary = [input].flatten
|
||||
@@ -185,23 +178,14 @@ module Liquid
|
||||
input = Time.at(input.to_i)
|
||||
end
|
||||
|
||||
date = if input.is_a?(String)
|
||||
case input.downcase
|
||||
when 'now', 'today'
|
||||
Time.now
|
||||
else
|
||||
Time.parse(input)
|
||||
end
|
||||
else
|
||||
input
|
||||
end
|
||||
date = input.is_a?(String) ? Time.parse(input) : input
|
||||
|
||||
if date.respond_to?(:strftime)
|
||||
date.strftime(format.to_s)
|
||||
else
|
||||
input
|
||||
end
|
||||
rescue
|
||||
rescue => e
|
||||
input
|
||||
end
|
||||
|
||||
@@ -225,47 +209,41 @@ module Liquid
|
||||
|
||||
# addition
|
||||
def plus(input, operand)
|
||||
apply_operation(input, operand, :+)
|
||||
to_number(input) + to_number(operand)
|
||||
end
|
||||
|
||||
# subtraction
|
||||
def minus(input, operand)
|
||||
apply_operation(input, operand, :-)
|
||||
to_number(input) - to_number(operand)
|
||||
end
|
||||
|
||||
# multiplication
|
||||
def times(input, operand)
|
||||
apply_operation(input, operand, :*)
|
||||
to_number(input) * to_number(operand)
|
||||
end
|
||||
|
||||
# division
|
||||
def divided_by(input, operand)
|
||||
apply_operation(input, operand, :/)
|
||||
to_number(input) / to_number(operand)
|
||||
end
|
||||
|
||||
def modulo(input, operand)
|
||||
apply_operation(input, operand, :%)
|
||||
to_number(input) % to_number(operand)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def to_number(obj)
|
||||
case obj
|
||||
when Float
|
||||
BigDecimal.new(obj.to_s)
|
||||
when Numeric
|
||||
obj
|
||||
when String
|
||||
(obj.strip =~ /^\d+\.\d+$/) ? BigDecimal.new(obj) : obj.to_i
|
||||
(obj.strip =~ /^\d+\.\d+$/) ? obj.to_f : obj.to_i
|
||||
else
|
||||
0
|
||||
end
|
||||
end
|
||||
|
||||
def apply_operation(input, operand, operation)
|
||||
result = to_number(input).send(operation, to_number(operand))
|
||||
result.is_a?(BigDecimal) ? result.to_f : result
|
||||
end
|
||||
end
|
||||
|
||||
Template.register_filter(StandardFilters)
|
||||
|
||||
@@ -8,7 +8,7 @@ module Liquid
|
||||
# The Strainer only allows method calls defined in filters given to it via Strainer.global_filter,
|
||||
# Context#add_filters or Template.register_filter
|
||||
class Strainer #:nodoc:
|
||||
@@filters = []
|
||||
@@filters = {}
|
||||
@@known_filters = Set.new
|
||||
@@known_methods = Set.new
|
||||
|
||||
@@ -19,7 +19,7 @@ module Liquid
|
||||
def self.global_filter(filter)
|
||||
raise ArgumentError, "Passed filter is not a module" unless filter.is_a?(Module)
|
||||
add_known_filter(filter)
|
||||
@@filters << filter unless @@filters.include?(filter)
|
||||
@@filters[filter.name] = filter
|
||||
end
|
||||
|
||||
def self.add_known_filter(filter)
|
||||
@@ -34,7 +34,7 @@ module Liquid
|
||||
|
||||
def self.create(context)
|
||||
strainer = Strainer.new(context)
|
||||
@@filters.each { |m| strainer.extend(m) }
|
||||
@@filters.each { |k,m| strainer.extend(m) }
|
||||
strainer
|
||||
end
|
||||
|
||||
|
||||
@@ -23,4 +23,4 @@ module Liquid
|
||||
|
||||
end # Tag
|
||||
|
||||
end # Liquid
|
||||
end # Tag
|
||||
|
||||
@@ -23,9 +23,7 @@ module Liquid
|
||||
end
|
||||
|
||||
def render(context)
|
||||
val = @from.render(context)
|
||||
context.scopes.last[@to] = val
|
||||
context.resource_limits[:assign_score_current] += (val.respond_to?(:length) ? val.length : 1)
|
||||
context.scopes.last[@to] = @from.render(context)
|
||||
''
|
||||
end
|
||||
|
||||
|
||||
@@ -27,7 +27,6 @@ module Liquid
|
||||
def render(context)
|
||||
output = super
|
||||
context.scopes.last[@to] = output
|
||||
context.resource_limits[:assign_score_current] += (output.respond_to?(:length) ? output.length : 1)
|
||||
''
|
||||
end
|
||||
end
|
||||
|
||||
@@ -44,7 +44,7 @@ module Liquid
|
||||
# forloop.last:: Returns true if the item is the last item.
|
||||
#
|
||||
class For < Block
|
||||
Syntax = /\A(\w+)\s+in\s+(#{QuotedFragment}+)\s*(reversed)?/o
|
||||
Syntax = /(\w+)\s+in\s+(#{QuotedFragment}+)\s*(reversed)?/o
|
||||
|
||||
def initialize(tag_name, markup, tokens)
|
||||
if markup =~ Syntax
|
||||
|
||||
@@ -1,19 +1,4 @@
|
||||
module Liquid
|
||||
|
||||
# Include allows templates to relate with other templates
|
||||
#
|
||||
# Simply include another template:
|
||||
#
|
||||
# {% include 'product' %}
|
||||
#
|
||||
# Include a template with a local variable:
|
||||
#
|
||||
# {% include 'product' with products[0] %}
|
||||
#
|
||||
# Include a template for a collection:
|
||||
#
|
||||
# {% include 'product' for products %}
|
||||
#
|
||||
class Include < Tag
|
||||
Syntax = /(#{QuotedFragment}+)(\s+(?:with|for)\s+(#{QuotedFragment}+))?/o
|
||||
|
||||
@@ -39,7 +24,8 @@ module Liquid
|
||||
end
|
||||
|
||||
def render(context)
|
||||
partial = load_cached_partial(context)
|
||||
source = _read_template_from_file_system(context)
|
||||
partial = Liquid::Template.parse(source)
|
||||
variable = context[@variable_name || @template_name[1..-2]]
|
||||
|
||||
context.stack do
|
||||
@@ -48,8 +34,8 @@ module Liquid
|
||||
end
|
||||
|
||||
if variable.is_a?(Array)
|
||||
variable.collect do |var|
|
||||
context[@template_name[1..-2]] = var
|
||||
variable.collect do |variable|
|
||||
context[@template_name[1..-2]] = variable
|
||||
partial.render(context)
|
||||
end
|
||||
else
|
||||
@@ -60,21 +46,7 @@ module Liquid
|
||||
end
|
||||
|
||||
private
|
||||
def load_cached_partial(context)
|
||||
cached_partials = context.registers[:cached_partials] || {}
|
||||
template_name = context[@template_name]
|
||||
|
||||
if cached = cached_partials[template_name]
|
||||
return cached
|
||||
end
|
||||
source = read_template_from_file_system(context)
|
||||
partial = Liquid::Template.parse(source)
|
||||
cached_partials[template_name] = partial
|
||||
context.registers[:cached_partials] = cached_partials
|
||||
partial
|
||||
end
|
||||
|
||||
def read_template_from_file_system(context)
|
||||
def _read_template_from_file_system(context)
|
||||
file_system = context.registers[:file_system] || Liquid::Template.file_system
|
||||
|
||||
# make read_template_file call backwards-compatible.
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
module Liquid
|
||||
class Raw < Block
|
||||
FullTokenPossiblyInvalid = /^(.*)#{TagStart}\s*(\w+)\s*(.*)?#{TagEnd}$/o
|
||||
|
||||
def parse(tokens)
|
||||
@nodelist ||= []
|
||||
@nodelist.clear
|
||||
|
||||
while token = tokens.shift
|
||||
if token =~ FullTokenPossiblyInvalid
|
||||
@nodelist << $1 if $1 != ""
|
||||
if block_delimiter == $2
|
||||
if token =~ FullToken
|
||||
if block_delimiter == $1
|
||||
end_tag
|
||||
return
|
||||
end
|
||||
@@ -20,3 +18,4 @@ module Liquid
|
||||
|
||||
Template.register_tag('raw', Raw)
|
||||
end
|
||||
|
||||
|
||||
@@ -11,9 +11,9 @@ module Liquid
|
||||
context.stack do
|
||||
|
||||
# First condition is interpreted backwards ( if not )
|
||||
first_block = @blocks.first
|
||||
unless first_block.evaluate(context)
|
||||
return render_all(first_block.attachment, context)
|
||||
block = @blocks.first
|
||||
unless block.evaluate(context)
|
||||
return render_all(block.attachment, context)
|
||||
end
|
||||
|
||||
# After the first condition unless works just like if
|
||||
|
||||
@@ -14,7 +14,7 @@ module Liquid
|
||||
# template.render('user_name' => 'bob')
|
||||
#
|
||||
class Template
|
||||
attr_accessor :root, :resource_limits
|
||||
attr_accessor :root
|
||||
@@file_system = BlankFileSystem.new
|
||||
|
||||
class << self
|
||||
@@ -50,7 +50,6 @@ module Liquid
|
||||
|
||||
# creates a new <tt>Template</tt> from an array of tokens. Use <tt>Template.parse</tt> instead
|
||||
def initialize
|
||||
@resource_limits = {}
|
||||
end
|
||||
|
||||
# Parse source code.
|
||||
@@ -93,13 +92,10 @@ module Liquid
|
||||
context = case args.first
|
||||
when Liquid::Context
|
||||
args.shift
|
||||
when Liquid::Drop
|
||||
drop = args.shift
|
||||
drop.context = Context.new([drop, assigns], instance_assigns, registers, @rethrow_errors, @resource_limits)
|
||||
when Hash
|
||||
Context.new([args.shift, assigns], instance_assigns, registers, @rethrow_errors, @resource_limits)
|
||||
Context.new([args.shift, assigns], instance_assigns, registers, @rethrow_errors)
|
||||
when nil
|
||||
Context.new(assigns, instance_assigns, registers, @rethrow_errors, @resource_limits)
|
||||
Context.new(assigns, instance_assigns, registers, @rethrow_errors)
|
||||
else
|
||||
raise ArgumentError, "Expect Hash or Liquid::Context as parameter"
|
||||
end
|
||||
@@ -124,11 +120,9 @@ module Liquid
|
||||
|
||||
begin
|
||||
# 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 a array back here. join will make a string out of it
|
||||
result = @root.render(context)
|
||||
result.respond_to?(:join) ? result.join : result
|
||||
rescue Liquid::MemoryError => e
|
||||
context.handle_error(e)
|
||||
ensure
|
||||
@errors = context.errors
|
||||
end
|
||||
|
||||
@@ -3,6 +3,7 @@ module Liquid
|
||||
def self.slice_collection_using_each(collection, from, to)
|
||||
segments = []
|
||||
index = 0
|
||||
yielded = 0
|
||||
|
||||
# Maintains Ruby 1.8.7 String#each behaviour on 1.9
|
||||
return [collection] if non_blank_string?(collection)
|
||||
|
||||
@@ -23,9 +23,9 @@ module Liquid
|
||||
if match[2].match(/#{FilterSeparator}\s*(.*)/o)
|
||||
filters = Regexp.last_match(1).scan(FilterParser)
|
||||
filters.each do |f|
|
||||
if matches = f.match(/\s*(\w+)/)
|
||||
if matches = f.match(/\s*(\w+)(?:\s*#{FilterArgumentSeparator}(.*))?/)
|
||||
filtername = matches[1]
|
||||
filterargs = f.scan(/(?:#{FilterArgumentSeparator}|#{ArgumentSeparator})\s*((?:\w+\s*\:\s*)?#{QuotedFragment})/o).flatten
|
||||
filterargs = matches[2].to_s.scan(/(?:\A|#{ArgumentSeparator})\s*((?:\w+\s*\:\s*)?#{QuotedFragment})/o).flatten
|
||||
@filters << [filtername, filterargs]
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
# encoding: utf-8
|
||||
module Liquid
|
||||
VERSION = "2.6.2"
|
||||
end
|
||||
@@ -1,12 +1,8 @@
|
||||
# encoding: utf-8
|
||||
lib = File.expand_path('../lib/', __FILE__)
|
||||
$:.unshift lib unless $:.include?(lib)
|
||||
|
||||
require "liquid/version"
|
||||
|
||||
Gem::Specification.new do |s|
|
||||
s.name = "liquid"
|
||||
s.version = Liquid::VERSION
|
||||
s.version = "2.5.5"
|
||||
s.platform = Gem::Platform::RUBY
|
||||
s.summary = "A secure, non-evaling end user template engine with aesthetic markup."
|
||||
s.authors = ["Tobias Luetke"]
|
||||
|
||||
@@ -14,17 +14,6 @@ require File.dirname(__FILE__) + '/shopify/liquid'
|
||||
require File.dirname(__FILE__) + '/shopify/database.rb'
|
||||
|
||||
class ThemeRunner
|
||||
class FileSystem
|
||||
|
||||
def initialize(path)
|
||||
@path = path
|
||||
end
|
||||
|
||||
# Called by Liquid to retrieve a template file
|
||||
def read_template_file(template_path, context)
|
||||
File.read(@path + '/' + template_path + '.liquid')
|
||||
end
|
||||
end
|
||||
|
||||
# Load all templates into memory, do this now so that
|
||||
# we don't profile IO.
|
||||
@@ -58,7 +47,7 @@ class ThemeRunner
|
||||
|
||||
# Compute page_tempalte outside of profiler run, uninteresting to profiler
|
||||
page_template = File.basename(template_name, File.extname(template_name))
|
||||
compile_and_render(liquid, layout, assigns, page_template, template_name)
|
||||
compile_and_render(liquid, layout, assigns, page_template)
|
||||
|
||||
end
|
||||
end
|
||||
@@ -85,7 +74,7 @@ class ThemeRunner
|
||||
html = nil
|
||||
|
||||
RubyProf.resume
|
||||
html = compile_and_render(liquid, layout, assigns, page_template, template_name)
|
||||
html = compile_and_render(liquid, layout, assigns, page_template)
|
||||
RubyProf.pause
|
||||
|
||||
|
||||
@@ -99,11 +88,10 @@ class ThemeRunner
|
||||
RubyProf.stop
|
||||
end
|
||||
|
||||
def compile_and_render(template, layout, assigns, page_template, template_file)
|
||||
def compile_and_render(template, layout, assigns, page_template)
|
||||
tmpl = Liquid::Template.new
|
||||
tmpl.assigns['page_title'] = 'Page title'
|
||||
tmpl.assigns['template'] = page_template
|
||||
tmpl.registers[:file_system] = ThemeRunner::FileSystem.new(File.dirname(template_file))
|
||||
|
||||
content_for_layout = tmpl.parse(template).render(assigns)
|
||||
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
require 'test_helper'
|
||||
|
||||
module MoneyFilter
|
||||
def money(input)
|
||||
sprintf(' %d$ ', input)
|
||||
end
|
||||
end
|
||||
|
||||
module CanadianMoneyFilter
|
||||
def money(input)
|
||||
sprintf(' %d$ CAD ', input)
|
||||
end
|
||||
end
|
||||
|
||||
class HashOrderingTest < Test::Unit::TestCase
|
||||
include Liquid
|
||||
|
||||
def test_global_register_order
|
||||
Template.register_filter(MoneyFilter)
|
||||
Template.register_filter(CanadianMoneyFilter)
|
||||
|
||||
assert_equal " 1000$ CAD ", Template.parse("{{1000 | money}}").render(nil, nil)
|
||||
end
|
||||
|
||||
end
|
||||
@@ -1,5 +1,3 @@
|
||||
# encoding: utf-8
|
||||
|
||||
require 'test_helper'
|
||||
|
||||
class Filters
|
||||
@@ -60,7 +58,6 @@ class StandardFiltersTest < Test::Unit::TestCase
|
||||
assert_equal '1234567890', @filters.truncate('1234567890', 20)
|
||||
assert_equal '...', @filters.truncate('1234567890', 0)
|
||||
assert_equal '1234567890', @filters.truncate('1234567890')
|
||||
assert_equal "测试...", @filters.truncate("测试测试测试测试", 5)
|
||||
end
|
||||
|
||||
def test_strip
|
||||
@@ -85,16 +82,12 @@ class StandardFiltersTest < Test::Unit::TestCase
|
||||
assert_equal 'one two...', @filters.truncatewords('one two three', 2)
|
||||
assert_equal 'one two three', @filters.truncatewords('one two three')
|
||||
assert_equal 'Two small (13” x 5.5” x 10” high) baskets fit inside one large basket (13”...', @filters.truncatewords('Two small (13” x 5.5” x 10” high) baskets fit inside one large basket (13” x 16” x 10.5” high) with cover.', 15)
|
||||
assert_equal "测试测试测试测试", @filters.truncatewords('测试测试测试测试', 5)
|
||||
end
|
||||
|
||||
def test_strip_html
|
||||
assert_equal 'test', @filters.strip_html("<div>test</div>")
|
||||
assert_equal 'test', @filters.strip_html("<div id='test'>test</div>")
|
||||
assert_equal '', @filters.strip_html("<script type='text/javascript'>document.write('some stuff');</script>")
|
||||
assert_equal '', @filters.strip_html("<style type='text/css'>foo bar</style>")
|
||||
assert_equal 'test', @filters.strip_html("<div\nclass='multiline'>test</div>")
|
||||
assert_equal 'test', @filters.strip_html("<!-- foo bar \n test -->test")
|
||||
assert_equal '', @filters.strip_html(nil)
|
||||
end
|
||||
|
||||
@@ -108,10 +101,6 @@ class StandardFiltersTest < Test::Unit::TestCase
|
||||
assert_equal [{"a" => 1}, {"a" => 2}, {"a" => 3}, {"a" => 4}], @filters.sort([{"a" => 4}, {"a" => 3}, {"a" => 1}, {"a" => 2}], "a")
|
||||
end
|
||||
|
||||
def test_reverse
|
||||
assert_equal [4,3,2,1], @filters.reverse([1,2,3,4])
|
||||
end
|
||||
|
||||
def test_map
|
||||
assert_equal [1,2,3,4], @filters.map([{"a" => 1}, {"a" => 2}, {"a" => 3}, {"a" => 4}], 'a')
|
||||
assert_template_result 'abc', "{{ ary | map:'foo' | map:'bar' }}",
|
||||
@@ -152,8 +141,6 @@ class StandardFiltersTest < Test::Unit::TestCase
|
||||
assert_equal '07/05/2006', @filters.date("2006-07-05 10:00:00", "%m/%d/%Y")
|
||||
|
||||
assert_equal "07/16/2004", @filters.date("Fri Jul 16 01:00:00 2004", "%m/%d/%Y")
|
||||
assert_equal "#{Date.today.year}", @filters.date('now', '%Y')
|
||||
assert_equal "#{Date.today.year}", @filters.date('today', '%Y')
|
||||
|
||||
assert_equal nil, @filters.date(nil, "%B")
|
||||
|
||||
@@ -187,7 +174,6 @@ class StandardFiltersTest < Test::Unit::TestCase
|
||||
|
||||
def test_strip_newlines
|
||||
assert_template_result 'abc', "{{ source | strip_newlines }}", 'source' => "a\nb\nc"
|
||||
assert_template_result 'abc', "{{ source | strip_newlines }}", 'source' => "a\r\nb\nc"
|
||||
end
|
||||
|
||||
def test_newlines_to_br
|
||||
@@ -212,8 +198,6 @@ class StandardFiltersTest < Test::Unit::TestCase
|
||||
assert_match(/(6\.3)|(6\.(0{13})1)/, Template.parse("{{ '2.1' | times:3 }}").render)
|
||||
|
||||
assert_template_result "6", "{{ '2.1' | times:3 | replace: '.','-' | plus:0}}"
|
||||
|
||||
assert_template_result "7.25", "{{ 0.0725 | times:100 }}"
|
||||
end
|
||||
|
||||
def test_divided_by
|
||||
@@ -225,8 +209,6 @@ class StandardFiltersTest < Test::Unit::TestCase
|
||||
|
||||
assert_template_result "5", "{{ 15 | divided_by:3 }}"
|
||||
assert_template_result "Liquid error: divided by 0", "{{ 5 | divided_by:0 }}"
|
||||
|
||||
assert_template_result "0.5", "{{ 2.0 | divided_by:4 }}"
|
||||
end
|
||||
|
||||
def test_modulo
|
||||
|
||||
@@ -281,17 +281,4 @@ HERE
|
||||
def test_blank_string_not_iterable
|
||||
assert_template_result('', "{% for char in characters %}I WILL NOT BE OUTPUT{% endfor %}", 'characters' => '')
|
||||
end
|
||||
|
||||
def test_bad_variable_naming_in_for_loop
|
||||
assert_raise(Liquid::SyntaxError) do
|
||||
Liquid::Template.parse('{% for a/b in x %}{% endfor %}')
|
||||
end
|
||||
end
|
||||
|
||||
def test_spacing_with_variable_naming_in_for_loop
|
||||
expected = '12345'
|
||||
template = '{% for item in items %}{{item}}{% endfor %}'
|
||||
assigns = {'items' => [1,2,3,4,5]}
|
||||
assert_template_result(expected, template, assigns)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -39,15 +39,6 @@ class OtherFileSystem
|
||||
end
|
||||
end
|
||||
|
||||
class CountingFileSystem
|
||||
attr_reader :count
|
||||
def read_template_file(template_path, context)
|
||||
@count ||= 0
|
||||
@count += 1
|
||||
'from CountingFileSystem'
|
||||
end
|
||||
end
|
||||
|
||||
class IncludeTagTest < Test::Unit::TestCase
|
||||
include Liquid
|
||||
|
||||
@@ -145,22 +136,4 @@ class IncludeTagTest < Test::Unit::TestCase
|
||||
|
||||
assert_equal "Product: Draft 151cm ", Template.parse("{% include template for product %}").render("template" => 'product', 'product' => { 'title' => 'Draft 151cm'})
|
||||
end
|
||||
|
||||
def test_include_tag_caches_second_read_of_same_partial
|
||||
file_system = CountingFileSystem.new
|
||||
assert_equal 'from CountingFileSystemfrom CountingFileSystem',
|
||||
Template.parse("{% include 'pick_a_source' %}{% include 'pick_a_source' %}").render({}, :registers => {:file_system => file_system})
|
||||
assert_equal 1, file_system.count
|
||||
end
|
||||
|
||||
def test_include_tag_doesnt_cache_partials_across_renders
|
||||
file_system = CountingFileSystem.new
|
||||
assert_equal 'from CountingFileSystem',
|
||||
Template.parse("{% include 'pick_a_source' %}").render({}, :registers => {:file_system => file_system})
|
||||
assert_equal 1, file_system.count
|
||||
|
||||
assert_equal 'from CountingFileSystem',
|
||||
Template.parse("{% include 'pick_a_source' %}").render({}, :registers => {:file_system => file_system})
|
||||
assert_equal 2, file_system.count
|
||||
end
|
||||
end # IncludeTagTest
|
||||
@@ -9,16 +9,7 @@ class RawTagTest < Test::Unit::TestCase
|
||||
end
|
||||
|
||||
def test_output_in_raw
|
||||
assert_template_result '{{ test }}', '{% raw %}{{ test }}{% endraw %}'
|
||||
end
|
||||
|
||||
def test_open_tag_in_raw
|
||||
assert_template_result ' Foobar {% invalid ', '{% raw %} Foobar {% invalid {% endraw %}'
|
||||
assert_template_result ' Foobar invalid %} ', '{% raw %} Foobar invalid %} {% endraw %}'
|
||||
assert_template_result ' Foobar {{ invalid ', '{% raw %} Foobar {{ invalid {% endraw %}'
|
||||
assert_template_result ' Foobar invalid }} ', '{% raw %} Foobar invalid }} {% endraw %}'
|
||||
assert_template_result ' Foobar {% invalid {% {% endraw ', '{% raw %} Foobar {% invalid {% {% endraw {% endraw %}'
|
||||
assert_template_result ' Foobar {% {% {% ', '{% raw %} Foobar {% {% {% {% endraw %}'
|
||||
assert_template_result ' test {% raw %} {% endraw %}', '{% raw %} test {% raw %} {% {% endraw %}endraw %}'
|
||||
assert_template_result '{{ test }}',
|
||||
'{% raw %}{{ test }}{% endraw %}'
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,19 +1,5 @@
|
||||
require 'test_helper'
|
||||
|
||||
class TemplateContextDrop < Liquid::Drop
|
||||
def before_method(method)
|
||||
method
|
||||
end
|
||||
|
||||
def foo
|
||||
'fizzbuzz'
|
||||
end
|
||||
|
||||
def baz
|
||||
@context.registers['lulz']
|
||||
end
|
||||
end
|
||||
|
||||
class TemplateTest < Test::Unit::TestCase
|
||||
include Liquid
|
||||
|
||||
@@ -85,62 +71,4 @@ class TemplateTest < Test::Unit::TestCase
|
||||
assert_equal '1', t.render(assigns)
|
||||
@global = nil
|
||||
end
|
||||
|
||||
def test_resource_limits_render_length
|
||||
t = Template.parse("0123456789")
|
||||
t.resource_limits = { :render_length_limit => 5 }
|
||||
assert_equal "Liquid error: Memory limits exceeded", t.render()
|
||||
assert t.resource_limits[:reached]
|
||||
t.resource_limits = { :render_length_limit => 10 }
|
||||
assert_equal "0123456789", t.render()
|
||||
assert_not_nil t.resource_limits[:render_length_current]
|
||||
end
|
||||
|
||||
def test_resource_limits_render_score
|
||||
t = Template.parse("{% for a in (1..10) %} {% for a in (1..10) %} foo {% endfor %} {% endfor %}")
|
||||
t.resource_limits = { :render_score_limit => 50 }
|
||||
assert_equal "Liquid error: Memory limits exceeded", t.render()
|
||||
assert t.resource_limits[:reached]
|
||||
t = Template.parse("{% for a in (1..100) %} foo {% endfor %}")
|
||||
t.resource_limits = { :render_score_limit => 50 }
|
||||
assert_equal "Liquid error: Memory limits exceeded", t.render()
|
||||
assert t.resource_limits[:reached]
|
||||
t.resource_limits = { :render_score_limit => 200 }
|
||||
assert_equal (" foo " * 100), t.render()
|
||||
assert_not_nil t.resource_limits[:render_score_current]
|
||||
end
|
||||
|
||||
def test_resource_limits_assign_score
|
||||
t = Template.parse("{% assign foo = 42 %}{% assign bar = 23 %}")
|
||||
t.resource_limits = { :assign_score_limit => 1 }
|
||||
assert_equal "Liquid error: Memory limits exceeded", t.render()
|
||||
assert t.resource_limits[:reached]
|
||||
t.resource_limits = { :assign_score_limit => 2 }
|
||||
assert_equal "", t.render()
|
||||
assert_not_nil t.resource_limits[:assign_score_current]
|
||||
end
|
||||
|
||||
def test_resource_limits_aborts_rendering_after_first_error
|
||||
t = Template.parse("{% for a in (1..100) %} foo1 {% endfor %} bar {% for a in (1..100) %} foo2 {% endfor %}")
|
||||
t.resource_limits = { :render_score_limit => 50 }
|
||||
assert_equal "Liquid error: Memory limits exceeded", t.render()
|
||||
assert t.resource_limits[:reached]
|
||||
end
|
||||
|
||||
def test_resource_limits_hash_in_template_gets_updated_even_if_no_limits_are_set
|
||||
t = Template.parse("{% for a in (1..100) %} {% assign foo = 1 %} {% endfor %}")
|
||||
t.render()
|
||||
assert t.resource_limits[:assign_score_current] > 0
|
||||
assert t.resource_limits[:render_score_current] > 0
|
||||
assert t.resource_limits[:render_length_current] > 0
|
||||
end
|
||||
|
||||
def test_can_use_drop_as_context
|
||||
t = Template.new
|
||||
t.registers['lulz'] = 'haha'
|
||||
drop = TemplateContextDrop.new
|
||||
assert_equal 'fizzbuzz', t.parse('{{foo}}').render(drop)
|
||||
assert_equal 'bar', t.parse('{{bar}}').render(drop)
|
||||
assert_equal 'haha', t.parse("{{baz}}").render(drop)
|
||||
end
|
||||
end # TemplateTest
|
||||
|
||||
@@ -113,12 +113,6 @@ class VariableTest < Test::Unit::TestCase
|
||||
assert_equal 'hello', var.name
|
||||
assert_equal [['things',["greeting: \"world\"","farewell: 'goodbye'"]]], var.filters
|
||||
end
|
||||
|
||||
def test_lax_filter_argument_parsing
|
||||
var = Variable.new(%! number_of_comments | pluralize: 'comment': 'comments' !)
|
||||
assert_equal 'number_of_comments', var.name
|
||||
assert_equal [['pluralize',["'comment'","'comments'"]]], var.filters
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user