mirror of
https://github.com/kemko/liquid.git
synced 2026-01-05 09:45:40 +03:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f68fa84a2f |
@@ -3,7 +3,6 @@ language: ruby
|
|||||||
rvm:
|
rvm:
|
||||||
- 2.0
|
- 2.0
|
||||||
- 2.1
|
- 2.1
|
||||||
- 2.2
|
|
||||||
- ruby-head
|
- ruby-head
|
||||||
- jruby-head
|
- jruby-head
|
||||||
- rbx-2
|
- rbx-2
|
||||||
|
|||||||
87
History.md
87
History.md
@@ -1,72 +1,47 @@
|
|||||||
# Liquid Change Log
|
# Liquid Version History
|
||||||
|
|
||||||
## 4.0.0 / not yet released / branch "master"
|
## 3.0.0 / not yet released / branch "master"
|
||||||
### Changed
|
|
||||||
* Add sort_natural filter (#554) [Martin Hanzel, arthanzel]
|
|
||||||
* 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]
|
|
||||||
* Add concat filter to concatenate arrays (#429) [Diogo Beato, dvbeato]
|
|
||||||
* Ruby 1.9 support dropped (#491) [Justin Li, pushrax]
|
|
||||||
* Liquid::Template.file_system's read_template_file method is no longer passed the context. (#441) [James Reid-Smith, sunblaze]
|
|
||||||
|
|
||||||
### 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 case sensitivity regression in date standard filter (#499) [Kelley Reynolds, kreynolds]
|
|
||||||
* Disallow filters with no variable in strict mode (#475) [Justin Li, pushrax]
|
|
||||||
* Disallow variable names in the strict parser that are not valid in the lax parser (#463) [Justin Li, pushrax]
|
|
||||||
* Fix BlockBody#warnings taking exponential time to compute (#486) [Justin Li, pushrax]
|
|
||||||
|
|
||||||
## 3.0.2 / 2015-04-24 / branch "3-0-stable"
|
|
||||||
|
|
||||||
* Expose VariableLookup private members (#551) [Justin Li, pushrax]
|
|
||||||
* Documentation fixes
|
|
||||||
|
|
||||||
## 3.0.1 / 2015-01-23
|
|
||||||
|
|
||||||
* Remove duplicate `index0` key in TableRow tag (#502) [Alfred Xing]
|
|
||||||
|
|
||||||
## 3.0.0 / 2014-11-12
|
|
||||||
|
|
||||||
|
* ...
|
||||||
|
* Block parsing moved to BlockBody class, see #458 [Dylan Thacker-Smith, dylanahsmith]
|
||||||
* Removed Block#end_tag. Instead, override parse with `super` followed by your code. See #446 [Dylan Thacker-Smith, dylanahsmith]
|
* Removed Block#end_tag. Instead, override parse with `super` followed by your code. See #446 [Dylan Thacker-Smith, dylanahsmith]
|
||||||
* Fixed condition with wrong data types (#423) [Bogdan Gusiev]
|
* Fixed condition with wrong data types, see #423 [Bogdan Gusiev]
|
||||||
* Add url_encode to standard filters (#421) [Derrick Reimer, djreimer]
|
* Add url_encode to standard filters, see #421 [Derrick Reimer, djreimer]
|
||||||
* Add uniq to standard filters [Florian Weingarten, fw42]
|
* Add uniq to standard filters [Florian Weingarten, fw42]
|
||||||
* Add exception_handler feature (#397) and #254 [Bogdan Gusiev, bogdan and Florian Weingarten, fw42]
|
* Add exception_handler feature, see #397 and #254 [Bogdan Gusiev, bogdan and Florian Weingarten, fw42]
|
||||||
* Optimize variable parsing to avoid repeated regex evaluation during template rendering #383 [Jason Hiltz-Laforge, jasonhl]
|
* Optimize variable parsing to avoid repeated regex evaluation during template rendering #383 [Jason Hiltz-Laforge, jasonhl]
|
||||||
* Optimize checking for block interrupts to reduce object allocation #380 [Jason Hiltz-Laforge, jasonhl]
|
* Optimize checking for block interrupts to reduce object allocation #380 [Jason Hiltz-Laforge, jasonhl]
|
||||||
* Properly set context rethrow_errors on render! #349 [Thierry Joyal, tjoyal]
|
* Properly set context rethrow_errors on render! #349 [Thierry Joyal, tjoyal]
|
||||||
* Fix broken rendering of variables which are equal to false (#345) [Florian Weingarten, fw42]
|
* Fix broken rendering of variables which are equal to false, see #345 [Florian Weingarten, fw42]
|
||||||
* Remove ActionView template handler [Dylan Thacker-Smith, dylanahsmith]
|
* Remove ActionView template handler [Dylan Thacker-Smith, dylanahsmith]
|
||||||
* Freeze lots of string literals for new Ruby 2.1 optimization (#297) [Florian Weingarten, fw42]
|
* Freeze lots of string literals for new Ruby 2.1 optimization, see #297 [Florian Weingarten, fw42]
|
||||||
* Allow newlines in tags and variables (#324) [Dylan Thacker-Smith, dylanahsmith]
|
* Allow newlines in tags and variables, see #324 [Dylan Thacker-Smith, dylanahsmith]
|
||||||
* Tag#parse is called after initialize, which now takes options instead of tokens as the 3rd argument. See #321 [Dylan Thacker-Smith, dylanahsmith]
|
* Tag#parse is called after initialize, which now takes options instead of tokens as the 3rd argument. See #321 [Dylan Thacker-Smith, dylanahsmith]
|
||||||
* Raise `Liquid::ArgumentError` instead of `::ArgumentError` when filter has wrong number of arguments #309 [Bogdan Gusiev, bogdan]
|
* Raise `Liquid::ArgumentError` instead of `::ArgumentError` when filter has wrong number of arguments #309 [Bogdan Gusiev, bogdan]
|
||||||
* Add a to_s default for liquid drops (#306) [Adam Doeler, releod]
|
* Add a to_s default for liquid drops, see #306 [Adam Doeler, releod]
|
||||||
* Add strip, lstrip, and rstrip to standard filters [Florian Weingarten, fw42]
|
* Add strip, lstrip, and rstrip to standard filters [Florian Weingarten, fw42]
|
||||||
* Make if, for & case tags return complete and consistent nodelists (#250) [Nick Jones, dntj]
|
* Make if, for & case tags return complete and consistent nodelists, see #250 [Nick Jones, dntj]
|
||||||
* Prevent arbitrary method invocation on condition objects (#274) [Dylan Thacker-Smith, dylanahsmith]
|
* Prevent arbitrary method invocation on condition objects, see #274 [Dylan Thacker-Smith, dylanahsmith]
|
||||||
* Don't call to_sym when creating conditions for security reasons (#273) [Bouke van der Bijl, bouk]
|
* Don't call to_sym when creating conditions for security reasons, see #273 [Bouke van der Bijl, bouk]
|
||||||
* Fix resource counting bug with respond_to?(:length) (#263) [Florian Weingarten, fw42]
|
* Fix resource counting bug with respond_to?(:length), see #263 [Florian Weingarten, fw42]
|
||||||
* Allow specifying custom patterns for template filenames (#284) [Andrei Gladkyi, agladkyi]
|
* Allow specifying custom patterns for template filenames, see #284 [Andrei Gladkyi, agladkyi]
|
||||||
* Allow drops to optimize loading a slice of elements (#282) [Tom Burns, boourns]
|
* Allow drops to optimize loading a slice of elements, see #282 [Tom Burns, boourns]
|
||||||
* Support for passing variables to snippets in subdirs (#271) [Joost Hietbrink, joost]
|
* Support for passing variables to snippets in subdirs, see #271 [Joost Hietbrink, joost]
|
||||||
* Add a class cache to avoid runtime extend calls (#249) [James Tucker, raggi]
|
* Add a class cache to avoid runtime extend calls, see #249 [James Tucker, raggi]
|
||||||
* Remove some legacy Ruby 1.8 compatibility code (#276) [Florian Weingarten, fw42]
|
* Remove some legacy Ruby 1.8 compatibility code, see #276 [Florian Weingarten, fw42]
|
||||||
* Add default filter to standard filters (#267) [Derrick Reimer, djreimer]
|
* Add default filter to standard filters, see #267 [Derrick Reimer, djreimer]
|
||||||
* Add optional strict parsing and warn parsing (#235) [Tristan Hume, trishume]
|
* Add optional strict parsing and warn parsing, see #235 [Tristan Hume, trishume]
|
||||||
* Add I18n syntax error translation (#241) [Simon Hørup Eskildsen, Sirupsen]
|
* Add I18n syntax error translation, see #241 [Simon Hørup Eskildsen, Sirupsen]
|
||||||
* Make sort filter work on enumerable drops (#239) [Florian Weingarten, fw42]
|
* Make sort filter work on enumerable drops, see #239 [Florian Weingarten, fw42]
|
||||||
* Fix clashing method names in enumerable drops (#238) [Florian Weingarten, fw42]
|
* Fix clashing method names in enumerable drops, see #238 [Florian Weingarten, fw42]
|
||||||
* Make map filter work on enumerable drops (#233) [Florian Weingarten, fw42]
|
* Make map filter work on enumerable drops, see #233 [Florian Weingarten, fw42]
|
||||||
* Improved whitespace stripping for blank blocks, related to #216 [Florian Weingarten, fw42]
|
* Improved whitespace stripping for blank blocks, related to #216 [Florian Weingarten, fw42]
|
||||||
|
|
||||||
## 2.6.1 / 2014-01-10 / branch "2-6-stable"
|
## 2.6.1 / 2014-01-10 / branch "2-6-stable"
|
||||||
|
|
||||||
Security fix, cherry-picked from master (4e14a65):
|
Security fix, cherry-picked from master (4e14a65):
|
||||||
* Don't call to_sym when creating conditions for security reasons (#273) [Bouke van der Bijl, bouk]
|
* 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 (#274) [Dylan Thacker-Smith, dylanahsmith]
|
* Prevent arbitrary method invocation on condition objects, see #274 [Dylan Thacker-Smith, dylanahsmith]
|
||||||
|
|
||||||
## 2.6.0 / 2013-11-25
|
## 2.6.0 / 2013-11-25
|
||||||
|
|
||||||
@@ -95,12 +70,12 @@ The following releases will only be tested against Ruby 1.9 and Ruby 2.0 and are
|
|||||||
## 2.5.5 / 2014-01-10 / branch "2-5-stable"
|
## 2.5.5 / 2014-01-10 / branch "2-5-stable"
|
||||||
|
|
||||||
Security fix, cherry-picked from master (4e14a65):
|
Security fix, cherry-picked from master (4e14a65):
|
||||||
* Don't call to_sym when creating conditions for security reasons (#273) [Bouke van der Bijl, bouk]
|
* 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 (#274) [Dylan Thacker-Smith, dylanahsmith]
|
* Prevent arbitrary method invocation on condition objects, see #274 [Dylan Thacker-Smith, dylanahsmith]
|
||||||
|
|
||||||
## 2.5.4 / 2013-11-11
|
## 2.5.4 / 2013-11-11
|
||||||
|
|
||||||
* Fix "can't convert Fixnum into String" for "replace" (#173), [wǒ_is神仙, jsw0528]
|
* Fix "can't convert Fixnum into String" for "replace", see #173, [wǒ_is神仙, jsw0528]
|
||||||
|
|
||||||
## 2.5.3 / 2013-10-09
|
## 2.5.3 / 2013-10-09
|
||||||
|
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ class Servlet < LiquidServlet
|
|||||||
end
|
end
|
||||||
|
|
||||||
def products
|
def products
|
||||||
{ 'products' => products_list, 'more_products' => more_products_list, 'description' => description, 'section' => 'Snowboards', 'cool_products' => true}
|
{ 'products' => products_list, 'description' => description, 'section' => 'Snowboards', 'cool_products' => true}
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
@@ -34,11 +34,6 @@ 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'}]
|
{'name' => 'Arbor Diamond', 'price' => 59900, 'description' => 'the *arbor diamond* is a made up product because im obsessed with arbor and have no creativity'}]
|
||||||
end
|
end
|
||||||
|
|
||||||
def more_products_list
|
|
||||||
[{'name' => 'Arbor Catalyst', 'price' => 39900, 'description' => 'the *arbor catalyst* is an advanced drop-through for freestyle and flatground performance and versatility' },
|
|
||||||
{'name' => 'Arbor Fish', 'price' => 40000, 'description' => 'the *arbor fish* is a compact pin that features an extended wheelbase and time-honored teardrop shape'}]
|
|
||||||
end
|
|
||||||
|
|
||||||
def description
|
def description
|
||||||
"List of Products ~ This is a list of products with price and description."
|
"List of Products ~ This is a list of products with price and description."
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -16,12 +16,12 @@
|
|||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
{% assign all_products = products | concat: more_products %}
|
|
||||||
<h1>{{ description | split: '~' | first }}</h1>
|
<h1>{{ description | split: '~' | first }}</h1>
|
||||||
|
|
||||||
<h2>{{ description | split: '~' | last }}</h2>
|
<h2>{{ description | split: '~' | last }}</h2>
|
||||||
|
|
||||||
<h2>There are currently {{all_products | count}} products in the {{section}} catalog</h2>
|
<h2>There are currently {{products | count}} products in the {{section}} catalog</h2>
|
||||||
|
|
||||||
{% if cool_products %}
|
{% if cool_products %}
|
||||||
Cool products :)
|
Cool products :)
|
||||||
@@ -31,7 +31,7 @@
|
|||||||
|
|
||||||
<ul id="products">
|
<ul id="products">
|
||||||
|
|
||||||
{% for product in all_products %}
|
{% for product in products %}
|
||||||
<li>
|
<li>
|
||||||
<h2>{{product.name}}</h2>
|
<h2>{{product.name}}</h2>
|
||||||
Only {{product.price | price }}
|
Only {{product.price | price }}
|
||||||
|
|||||||
@@ -57,10 +57,6 @@ 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)
|
||||||
|
|||||||
@@ -99,18 +99,6 @@ module Liquid
|
|||||||
output.join
|
output.join
|
||||||
end
|
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
|
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def render_token(token, context)
|
def render_token(token, context)
|
||||||
|
|||||||
@@ -33,12 +33,6 @@ 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)
|
||||||
@@ -76,20 +70,6 @@ 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)
|
||||||
|
|||||||
@@ -44,8 +44,21 @@ module Liquid
|
|||||||
# for that
|
# for that
|
||||||
def add_filters(filters)
|
def add_filters(filters)
|
||||||
filters = [filters].flatten.compact
|
filters = [filters].flatten.compact
|
||||||
@filters += filters
|
filters.each do |f|
|
||||||
@strainer = nil
|
raise ArgumentError, "Expected module but got: #{f.class}" unless f.is_a?(Module)
|
||||||
|
Strainer.add_known_filter(f)
|
||||||
|
end
|
||||||
|
|
||||||
|
# If strainer is already setup then there's no choice but to use a runtime
|
||||||
|
# extend call. If strainer is not yet created, we can utilize strainers
|
||||||
|
# cached class based API, which avoids busting the method cache.
|
||||||
|
if @strainer
|
||||||
|
filters.each do |f|
|
||||||
|
strainer.extend(f)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
@filters.concat filters
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# are there any not handled interrupts?
|
# are there any not handled interrupts?
|
||||||
|
|||||||
@@ -62,10 +62,6 @@ module Liquid
|
|||||||
|
|
||||||
# Check for method existence without invoking respond_to?, which creates symbols
|
# Check for method existence without invoking respond_to?, which creates symbols
|
||||||
def self.invokable?(method_name)
|
def self.invokable?(method_name)
|
||||||
self.invokable_methods.include?(method_name.to_s)
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.invokable_methods
|
|
||||||
unless @invokable_methods
|
unless @invokable_methods
|
||||||
blacklist = Liquid::Drop.public_instance_methods + [:each]
|
blacklist = Liquid::Drop.public_instance_methods + [:each]
|
||||||
if include?(Enumerable)
|
if include?(Enumerable)
|
||||||
@@ -75,7 +71,7 @@ module Liquid
|
|||||||
whitelist = [:to_liquid] + (public_instance_methods - blacklist)
|
whitelist = [:to_liquid] + (public_instance_methods - blacklist)
|
||||||
@invokable_methods = Set.new(whitelist.map(&:to_s))
|
@invokable_methods = Set.new(whitelist.map(&:to_s))
|
||||||
end
|
end
|
||||||
@invokable_methods
|
@invokable_methods.include?(method_name.to_s)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -8,8 +8,6 @@ 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]
|
||||||
@@ -31,18 +29,5 @@ 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
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ module Liquid
|
|||||||
# This will parse the template with a LocalFileSystem implementation rooted at 'template_path'.
|
# This will parse the template with a LocalFileSystem implementation rooted at 'template_path'.
|
||||||
class BlankFileSystem
|
class BlankFileSystem
|
||||||
# Called by Liquid to retrieve a template file
|
# Called by Liquid to retrieve a template file
|
||||||
def read_template_file(template_path)
|
def read_template_file(template_path, context)
|
||||||
raise FileSystemError, "This liquid context does not allow includes."
|
raise FileSystemError, "This liquid context does not allow includes."
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -49,7 +49,7 @@ module Liquid
|
|||||||
@pattern = pattern
|
@pattern = pattern
|
||||||
end
|
end
|
||||||
|
|
||||||
def read_template_file(template_path)
|
def read_template_file(template_path, context)
|
||||||
full_path = full_path(template_path)
|
full_path = full_path(template_path)
|
||||||
raise FileSystemError, "No such template '#{template_path}'" unless File.exists?(full_path)
|
raise FileSystemError, "No such template '#{template_path}'" unless File.exists?(full_path)
|
||||||
|
|
||||||
|
|||||||
@@ -7,17 +7,16 @@
|
|||||||
# to the allowed method passed with the liquid_methods call
|
# to the allowed method passed with the liquid_methods call
|
||||||
# Example:
|
# Example:
|
||||||
#
|
#
|
||||||
# class SomeClass
|
# class SomeClass
|
||||||
# liquid_methods :an_allowed_method
|
# liquid_methods :an_allowed_method
|
||||||
#
|
#
|
||||||
# def an_allowed_method
|
# def an_allowed_method
|
||||||
# 'this comes from an allowed method'
|
# 'this comes from an allowed method'
|
||||||
# end
|
|
||||||
#
|
|
||||||
# def unallowed_method
|
|
||||||
# 'this will never be an output'
|
|
||||||
# end
|
|
||||||
# end
|
# end
|
||||||
|
# def unallowed_method
|
||||||
|
# 'this will never be an output'
|
||||||
|
# end
|
||||||
|
# end
|
||||||
#
|
#
|
||||||
# if you want to extend the drop to other methods you can defines more methods
|
# if you want to extend the drop to other methods you can defines more methods
|
||||||
# in the class <YourClass>::LiquidDropClass
|
# in the class <YourClass>::LiquidDropClass
|
||||||
@@ -27,33 +26,31 @@
|
|||||||
# 'and this from another allowed method'
|
# 'and this from another allowed method'
|
||||||
# end
|
# end
|
||||||
# end
|
# end
|
||||||
#
|
# end
|
||||||
#
|
#
|
||||||
# usage:
|
# usage:
|
||||||
# @something = SomeClass.new
|
# @something = SomeClass.new
|
||||||
#
|
#
|
||||||
# template:
|
# template:
|
||||||
# {{something.an_allowed_method}}{{something.unallowed_method}} {{something.another_allowed_method}}
|
# {{something.an_allowed_method}}{{something.unallowed_method}} {{something.another_allowed_method}}
|
||||||
#
|
#
|
||||||
# output:
|
# output:
|
||||||
# 'this comes from an allowed method and this from another allowed method'
|
# 'this comes from an allowed method and this from another allowed method'
|
||||||
#
|
#
|
||||||
# You can also chain associations, by adding the liquid_method call in the
|
# You can also chain associations, by adding the liquid_method call in the
|
||||||
# association models.
|
# association models.
|
||||||
#
|
#
|
||||||
class Module
|
class Module
|
||||||
|
|
||||||
def liquid_methods(*allowed_methods)
|
def liquid_methods(*allowed_methods)
|
||||||
drop_class = eval "class #{self.to_s}::LiquidDropClass < Liquid::Drop; self; end"
|
drop_class = eval "class #{self.to_s}::LiquidDropClass < Liquid::Drop; self; end"
|
||||||
|
|
||||||
define_method :to_liquid do
|
define_method :to_liquid do
|
||||||
drop_class.new(self)
|
drop_class.new(self)
|
||||||
end
|
end
|
||||||
|
|
||||||
drop_class.class_eval do
|
drop_class.class_eval do
|
||||||
def initialize(object)
|
def initialize(object)
|
||||||
@object = object
|
@object = object
|
||||||
end
|
end
|
||||||
|
|
||||||
allowed_methods.each do |sym|
|
allowed_methods.each do |sym|
|
||||||
define_method sym do
|
define_method sym do
|
||||||
@object.send sym
|
@object.send sym
|
||||||
@@ -61,4 +58,5 @@ class Module
|
|||||||
end
|
end
|
||||||
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_expr).to_s) do
|
Profiler.profile_children(context.evaluate(@template_name).to_s) do
|
||||||
render_without_profiling(context)
|
render_without_profiling(context)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -18,9 +18,5 @@ 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,20 +122,6 @@ 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)
|
||||||
@@ -191,10 +177,6 @@ module Liquid
|
|||||||
input.to_s + string.to_s
|
input.to_s + string.to_s
|
||||||
end
|
end
|
||||||
|
|
||||||
def concat(input, array)
|
|
||||||
InputIterator.new(input).concat(array)
|
|
||||||
end
|
|
||||||
|
|
||||||
# prepend a string to another
|
# prepend a string to another
|
||||||
def prepend(input, string)
|
def prepend(input, string)
|
||||||
string.to_s + input.to_s
|
string.to_s + input.to_s
|
||||||
@@ -324,11 +306,6 @@ module Liquid
|
|||||||
def to_date(obj)
|
def to_date(obj)
|
||||||
return obj if obj.respond_to?(:strftime)
|
return obj if obj.respond_to?(:strftime)
|
||||||
|
|
||||||
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
|
||||||
Time.now
|
Time.now
|
||||||
@@ -367,10 +344,6 @@ module Liquid
|
|||||||
to_a.join(glue)
|
to_a.join(glue)
|
||||||
end
|
end
|
||||||
|
|
||||||
def concat(args)
|
|
||||||
to_a.concat args
|
|
||||||
end
|
|
||||||
|
|
||||||
def reverse
|
def reverse
|
||||||
reverse_each.to_a
|
reverse_each.to_a
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -8,13 +8,12 @@ module Liquid
|
|||||||
# The Strainer only allows method calls defined in filters given to it via Strainer.global_filter,
|
# The Strainer only allows method calls defined in filters given to it via Strainer.global_filter,
|
||||||
# Context#add_filters or Template.register_filter
|
# Context#add_filters or Template.register_filter
|
||||||
class Strainer #:nodoc:
|
class Strainer #:nodoc:
|
||||||
@@global_strainer = Class.new(Strainer) do
|
@@filters = []
|
||||||
@filter_methods = Set.new
|
@@known_filters = Set.new
|
||||||
end
|
@@known_methods = Set.new
|
||||||
@@strainer_class_cache = Hash.new do |hash, filters|
|
@@strainer_class_cache = Hash.new do |hash, filters|
|
||||||
hash[filters] = Class.new(@@global_strainer) do
|
hash[filters] = Class.new(Strainer) do
|
||||||
@filter_methods = @@global_strainer.filter_methods.dup
|
filters.each { |f| include f }
|
||||||
filters.each { |f| add_filter(f) }
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -22,32 +21,33 @@ module Liquid
|
|||||||
@context = context
|
@context = context
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.filter_methods
|
def self.global_filter(filter)
|
||||||
@filter_methods
|
raise ArgumentError, "Passed filter is not a module" unless filter.is_a?(Module)
|
||||||
|
add_known_filter(filter)
|
||||||
|
@@filters << filter unless @@filters.include?(filter)
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.add_filter(filter)
|
def self.add_known_filter(filter)
|
||||||
raise ArgumentError, "Expected module but got: #{f.class}" unless filter.is_a?(Module)
|
unless @@known_filters.include?(filter)
|
||||||
unless self.class.include?(filter)
|
@@method_blacklist ||= Set.new(Strainer.instance_methods.map(&:to_s))
|
||||||
self.send(:include, filter)
|
new_methods = filter.instance_methods.map(&:to_s)
|
||||||
@filter_methods.merge(filter.public_instance_methods.map(&:to_s))
|
new_methods.reject!{ |m| @@method_blacklist.include?(m) }
|
||||||
|
@@known_methods.merge(new_methods)
|
||||||
|
@@known_filters.add(filter)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.global_filter(filter)
|
def self.strainer_class_cache
|
||||||
@@global_strainer.add_filter(filter)
|
@@strainer_class_cache
|
||||||
end
|
|
||||||
|
|
||||||
def self.invokable?(method)
|
|
||||||
@filter_methods.include?(method.to_s)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.create(context, filters = [])
|
def self.create(context, filters = [])
|
||||||
@@strainer_class_cache[filters].new(context)
|
filters = @@filters + filters
|
||||||
|
strainer_class_cache[filters].new(context)
|
||||||
end
|
end
|
||||||
|
|
||||||
def invoke(method, *args)
|
def invoke(method, *args)
|
||||||
if self.class.invokable?(method)
|
if invokable?(method)
|
||||||
send(method, *args)
|
send(method, *args)
|
||||||
else
|
else
|
||||||
args.first
|
args.first
|
||||||
@@ -55,5 +55,9 @@ module Liquid
|
|||||||
rescue ::ArgumentError => e
|
rescue ::ArgumentError => e
|
||||||
raise Liquid::ArgumentError.new(e.message)
|
raise Liquid::ArgumentError.new(e.message)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def invokable?(method)
|
||||||
|
@@known_methods.include?(method.to_s) && respond_to?(method)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -24,10 +24,7 @@ module Liquid
|
|||||||
end
|
end
|
||||||
|
|
||||||
def raw
|
def raw
|
||||||
tag = @tag_name.strip
|
"#{@tag_name} #{@markup}"
|
||||||
markup = @markup.strip
|
|
||||||
tag << " #{markup}" unless markup.empty?
|
|
||||||
tag
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def name
|
def name
|
||||||
@@ -41,9 +38,5 @@ module Liquid
|
|||||||
def blank?
|
def blank?
|
||||||
false
|
false
|
||||||
end
|
end
|
||||||
|
|
||||||
def format
|
|
||||||
"{% #{raw} %}"
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -35,10 +35,6 @@ 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)
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ module Liquid
|
|||||||
# in a sidebar or footer.
|
# in a sidebar or footer.
|
||||||
#
|
#
|
||||||
class Capture < Block
|
class Capture < Block
|
||||||
Syntax = /(#{VariableSignature}+)/o
|
Syntax = /(\w+)/
|
||||||
|
|
||||||
def initialize(tag_name, markup, options)
|
def initialize(tag_name, markup, options)
|
||||||
super
|
super
|
||||||
@@ -32,10 +32,6 @@ 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)
|
||||||
@initial_body = body = BlockBody.new
|
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,21 +53,6 @@ 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,7 +21,6 @@ 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
|
||||||
@@ -44,15 +43,6 @@ 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)
|
||||||
|
|||||||
@@ -42,7 +42,6 @@ module Liquid
|
|||||||
# where 0 is the last item.
|
# where 0 is the last item.
|
||||||
# forloop.first:: Returns true if the item is the first item.
|
# forloop.first:: Returns true if the item is the first item.
|
||||||
# forloop.last:: Returns true if the item is the last item.
|
# forloop.last:: Returns true if the item is the last item.
|
||||||
# forloop.parentloop:: Provides access to the parent loop, if present.
|
|
||||||
#
|
#
|
||||||
class For < Block
|
class For < Block
|
||||||
Syntax = /\A(#{VariableSegment}+)\s+in\s+(#{QuotedFragment}+)\s*(reversed)?/o
|
Syntax = /\A(#{VariableSegment}+)\s+in\s+(#{QuotedFragment}+)\s*(reversed)?/o
|
||||||
@@ -99,21 +98,18 @@ module Liquid
|
|||||||
# Store our progress through the collection for the continue flag
|
# Store our progress through the collection for the continue flag
|
||||||
context.registers[:for][@name] = from + segment.length
|
context.registers[:for][@name] = from + segment.length
|
||||||
|
|
||||||
parent_loop = context['forloop'.freeze]
|
|
||||||
|
|
||||||
context.stack do
|
context.stack do
|
||||||
segment.each_with_index do |item, index|
|
segment.each_with_index do |item, index|
|
||||||
context[@variable_name] = item
|
context[@variable_name] = item
|
||||||
context['forloop'.freeze] = {
|
context['forloop'.freeze] = {
|
||||||
'name'.freeze => @name,
|
'name'.freeze => @name,
|
||||||
'length'.freeze => length,
|
'length'.freeze => length,
|
||||||
'index'.freeze => index + 1,
|
'index'.freeze => index + 1,
|
||||||
'index0'.freeze => index,
|
'index0'.freeze => index,
|
||||||
'rindex'.freeze => length - index,
|
'rindex'.freeze => length - index,
|
||||||
'rindex0'.freeze => length - index - 1,
|
'rindex0'.freeze => length - index - 1,
|
||||||
'first'.freeze => (index == 0),
|
'first'.freeze => (index == 0),
|
||||||
'last'.freeze => (index == length - 1),
|
'last'.freeze => (index == length - 1)
|
||||||
'parentloop'.freeze => parent_loop
|
|
||||||
}
|
}
|
||||||
|
|
||||||
result << @for_block.render(context)
|
result << @for_block.render(context)
|
||||||
@@ -129,21 +125,6 @@ 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,25 +48,6 @@ 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,10 @@ module Liquid
|
|||||||
template_name = $1
|
template_name = $1
|
||||||
variable_name = $3
|
variable_name = $3
|
||||||
|
|
||||||
@variable_name_expr = variable_name ? Expression.parse(variable_name) : nil
|
@variable_name = Expression.parse(variable_name || template_name[1..-2])
|
||||||
@template_name_expr = Expression.parse(template_name)
|
@context_variable_name = template_name[1..-2].split('/'.freeze).last
|
||||||
@attributes = {}
|
@template_name = Expression.parse(template_name)
|
||||||
|
@attributes = {}
|
||||||
|
|
||||||
markup.scan(TagAttributes) do |key, value|
|
markup.scan(TagAttributes) do |key, value|
|
||||||
@attributes[key] = Expression.parse(value)
|
@attributes[key] = Expression.parse(value)
|
||||||
@@ -43,15 +44,7 @@ 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|
|
||||||
@@ -60,35 +53,20 @@ 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_expr)
|
template_name = context.evaluate(@template_name)
|
||||||
|
|
||||||
if cached = cached_partials[template_name]
|
if cached = cached_partials[template_name]
|
||||||
return cached
|
return cached
|
||||||
@@ -103,7 +81,15 @@ 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_expr))
|
# make read_template_file call backwards-compatible.
|
||||||
|
case file_system.method(:read_template_file).arity
|
||||||
|
when 1
|
||||||
|
file_system.read_template_file(context.evaluate(@template_name))
|
||||||
|
when 2
|
||||||
|
file_system.read_template_file(context.evaluate(@template_name), context)
|
||||||
|
else
|
||||||
|
raise ArgumentError, "file_system.read_template_file expects two parameters: (template_name, context)"
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def pass_options
|
def pass_options
|
||||||
|
|||||||
@@ -24,10 +24,6 @@ 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)
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ module Liquid
|
|||||||
'index0'.freeze => index,
|
'index0'.freeze => index,
|
||||||
'col'.freeze => col + 1,
|
'col'.freeze => col + 1,
|
||||||
'col0'.freeze => col,
|
'col0'.freeze => col,
|
||||||
|
'index0'.freeze => index,
|
||||||
'rindex'.freeze => length - index,
|
'rindex'.freeze => length - index,
|
||||||
'rindex0'.freeze => length - index - 1,
|
'rindex0'.freeze => length - index - 1,
|
||||||
'first'.freeze => (index == 0),
|
'first'.freeze => (index == 0),
|
||||||
@@ -66,12 +67,6 @@ 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)
|
||||||
|
|||||||
@@ -246,9 +246,16 @@ module Liquid
|
|||||||
return raw_tokens unless @line_numbers
|
return raw_tokens unless @line_numbers
|
||||||
|
|
||||||
current_line = 1
|
current_line = 1
|
||||||
|
current_column = 1
|
||||||
raw_tokens.map do |token|
|
raw_tokens.map do |token|
|
||||||
Token.new(token, current_line).tap do
|
Token.new(token, current_line, current_column).tap do
|
||||||
current_line += token.count("\n")
|
new_line_count = token.count("\n")
|
||||||
|
if new_line_count > 0
|
||||||
|
current_line += new_line_count
|
||||||
|
current_column = token.size - token.rindex("\n") + 1
|
||||||
|
else
|
||||||
|
current_column += token.size
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
module Liquid
|
module Liquid
|
||||||
class Token < String
|
class Token < String
|
||||||
attr_reader :line_number
|
attr_reader :line_number, :column_number
|
||||||
|
|
||||||
def initialize(content, line_number)
|
def initialize(content, line_number, column_number=nil)
|
||||||
super(content)
|
super(content)
|
||||||
@line_number = line_number
|
@line_number = line_number
|
||||||
|
@column_number = column_number
|
||||||
end
|
end
|
||||||
|
|
||||||
def raw
|
def raw
|
||||||
|
|||||||
@@ -28,28 +28,6 @@ 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
|
||||||
|
|||||||
@@ -3,8 +3,6 @@ module Liquid
|
|||||||
SQUARE_BRACKETED = /\A\[(.*)\]\z/m
|
SQUARE_BRACKETED = /\A\[(.*)\]\z/m
|
||||||
COMMAND_METHODS = ['size'.freeze, 'first'.freeze, 'last'.freeze]
|
COMMAND_METHODS = ['size'.freeze, 'first'.freeze, 'last'.freeze]
|
||||||
|
|
||||||
attr_reader :name, :lookups
|
|
||||||
|
|
||||||
def self.parse(markup)
|
def self.parse(markup)
|
||||||
new(markup)
|
new(markup)
|
||||||
end
|
end
|
||||||
@@ -14,7 +12,6 @@ module Liquid
|
|||||||
|
|
||||||
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
|
||||||
@@ -72,20 +69,6 @@ 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,4 +1,4 @@
|
|||||||
# encoding: utf-8
|
# encoding: utf-8
|
||||||
module Liquid
|
module Liquid
|
||||||
VERSION = "4.0.0.alpha"
|
VERSION = "3.0.0"
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ Gem::Specification.new do |s|
|
|||||||
s.version = Liquid::VERSION
|
s.version = Liquid::VERSION
|
||||||
s.platform = Gem::Platform::RUBY
|
s.platform = Gem::Platform::RUBY
|
||||||
s.summary = "A secure, non-evaling end user template engine with aesthetic markup."
|
s.summary = "A secure, non-evaling end user template engine with aesthetic markup."
|
||||||
s.authors = ["Tobias Lütke"]
|
s.authors = ["Tobias Luetke"]
|
||||||
s.email = ["tobi@leetsoft.com"]
|
s.email = ["tobi@leetsoft.com"]
|
||||||
s.homepage = "http://www.liquidmarkup.org"
|
s.homepage = "http://www.liquidmarkup.org"
|
||||||
s.license = "MIT"
|
s.license = "MIT"
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ class ThemeRunner
|
|||||||
end
|
end
|
||||||
|
|
||||||
# Called by Liquid to retrieve a template file
|
# Called by Liquid to retrieve a template file
|
||||||
def read_template_file(template_path)
|
def read_template_file(template_path, context)
|
||||||
File.read(@path + '/' + template_path + '.liquid')
|
File.read(@path + '/' + template_path + '.liquid')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -3,16 +3,6 @@ require 'test_helper'
|
|||||||
class AssignTest < Minitest::Test
|
class AssignTest < Minitest::Test
|
||||||
include Liquid
|
include Liquid
|
||||||
|
|
||||||
def test_assign_with_hyphen_in_variable_name
|
|
||||||
template_source = <<-END_TEMPLATE
|
|
||||||
{% assign this-thing = 'Print this-thing' %}
|
|
||||||
{{ this-thing }}
|
|
||||||
END_TEMPLATE
|
|
||||||
template = Template.parse(template_source)
|
|
||||||
rendered = template.render!
|
|
||||||
assert_equal "Print this-thing", rendered.strip
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_assigned_variable
|
def test_assigned_variable
|
||||||
assert_template_result('.foo.',
|
assert_template_result('.foo.',
|
||||||
'{% assign foo = values %}.{{ foo[0] }}.',
|
'{% assign foo = values %}.{{ foo[0] }}.',
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ class FoobarTag < Liquid::Tag
|
|||||||
end
|
end
|
||||||
|
|
||||||
class BlankTestFileSystem
|
class BlankTestFileSystem
|
||||||
def read_template_file(template_path)
|
def read_template_file(template_path, context)
|
||||||
template_path
|
template_path
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -7,16 +7,6 @@ class CaptureTest < Minitest::Test
|
|||||||
assert_template_result("test string", "{% capture 'var' %}test string{% endcapture %}{{var}}", {})
|
assert_template_result("test string", "{% capture 'var' %}test string{% endcapture %}{{var}}", {})
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_capture_with_hyphen_in_variable_name
|
|
||||||
template_source = <<-END_TEMPLATE
|
|
||||||
{% capture this-thing %}Print this-thing{% endcapture %}
|
|
||||||
{{ this-thing }}
|
|
||||||
END_TEMPLATE
|
|
||||||
template = Template.parse(template_source)
|
|
||||||
rendered = template.render!
|
|
||||||
assert_equal "Print this-thing", rendered.strip
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_capture_to_variable_from_outer_scope_if_existing
|
def test_capture_to_variable_from_outer_scope_if_existing
|
||||||
template_source = <<-END_TEMPLATE
|
template_source = <<-END_TEMPLATE
|
||||||
{% assign var = '' %}
|
{% assign var = '' %}
|
||||||
|
|||||||
@@ -25,12 +25,6 @@ end
|
|||||||
class FiltersTest < Minitest::Test
|
class FiltersTest < Minitest::Test
|
||||||
include Liquid
|
include Liquid
|
||||||
|
|
||||||
module OverrideObjectMethodFilter
|
|
||||||
def tap(input)
|
|
||||||
"tap overridden"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def setup
|
def setup
|
||||||
@context = Context.new
|
@context = Context.new
|
||||||
end
|
end
|
||||||
@@ -74,34 +68,11 @@ 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
|
||||||
@@ -134,13 +105,6 @@ class FiltersTest < Minitest::Test
|
|||||||
output = Variable.new(%! 'hello %{first_name}, %{last_name}' | substitute: first_name: surname, last_name: 'doe' !).render(@context)
|
output = Variable.new(%! 'hello %{first_name}, %{last_name}' | substitute: first_name: surname, last_name: 'doe' !).render(@context)
|
||||||
assert_equal 'hello john, doe', output
|
assert_equal 'hello john, doe', output
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_override_object_method_in_filter
|
|
||||||
assert_equal "tap overridden", Template.parse("{{var | tap}}").render!({ 'var' => 1000 }, :filters => [OverrideObjectMethodFilter])
|
|
||||||
|
|
||||||
# tap still treated as a non-existent filter
|
|
||||||
assert_equal "1000", Template.parse("{{var | tap}}").render!({ 'var' => 1000 })
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
class FiltersInTemplate < Minitest::Test
|
class FiltersInTemplate < Minitest::Test
|
||||||
@@ -159,10 +123,3 @@ 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
|
|
||||||
|
|||||||
@@ -1,172 +0,0 @@
|
|||||||
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
|
|
||||||
@@ -4,7 +4,7 @@ class RenderProfilingTest < Minitest::Test
|
|||||||
include Liquid
|
include Liquid
|
||||||
|
|
||||||
class ProfilingFileSystem
|
class ProfilingFileSystem
|
||||||
def read_template_file(template_path)
|
def read_template_file(template_path, context)
|
||||||
"Rendering template {% assign template_name = '#{template_path}'%}\n{{ template_name }}"
|
"Rendering template {% assign template_name = '#{template_path}'%}\n{{ template_name }}"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -249,12 +249,9 @@ class StandardFiltersTest < Minitest::Test
|
|||||||
assert_equal "07/16/2004", @filters.date("Fri Jul 16 01:00:00 2004", "%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('now', '%Y')
|
||||||
assert_equal "#{Date.today.year}", @filters.date('today', '%Y')
|
assert_equal "#{Date.today.year}", @filters.date('today', '%Y')
|
||||||
assert_equal "#{Date.today.year}", @filters.date('Today', '%Y')
|
|
||||||
|
|
||||||
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
|
||||||
@@ -361,17 +358,6 @@ class StandardFiltersTest < Minitest::Test
|
|||||||
assert_template_result('bcd',"{{ a | append: b}}",assigns)
|
assert_template_result('bcd',"{{ a | append: b}}",assigns)
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_concat
|
|
||||||
assert_equal [1, 2, 3, 4], @filters.concat([1, 2], [3, 4])
|
|
||||||
assert_equal [1, 2, 'a'], @filters.concat([1, 2], ['a'])
|
|
||||||
assert_equal [1, 2, 10], @filters.concat([1, 2], [10])
|
|
||||||
|
|
||||||
assert_raises(TypeError) do
|
|
||||||
# no implicit conversion of Fixnum into Array
|
|
||||||
@filters.concat([1, 2], 10)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_prepend
|
def test_prepend
|
||||||
assigns = {'a' => 'bc', 'b' => 'a' }
|
assigns = {'a' => 'bc', 'b' => 'a' }
|
||||||
assert_template_result('abc',"{{ a | prepend: 'a'}}",assigns)
|
assert_template_result('abc',"{{ a | prepend: 'a'}}",assigns)
|
||||||
|
|||||||
@@ -298,22 +298,6 @@ HERE
|
|||||||
'string' => "test string")
|
'string' => "test string")
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_for_parentloop_references_parent_loop
|
|
||||||
assert_template_result('1.1 1.2 1.3 2.1 2.2 2.3 ',
|
|
||||||
'{% for inner in outer %}{% for k in inner %}' +
|
|
||||||
'{{ forloop.parentloop.index }}.{{ forloop.index }} ' +
|
|
||||||
'{% endfor %}{% endfor %}',
|
|
||||||
'outer' => [[1, 1, 1], [1, 1, 1]])
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_for_parentloop_nil_when_not_present
|
|
||||||
assert_template_result('.1 .2 ',
|
|
||||||
'{% for inner in outer %}' +
|
|
||||||
'{{ forloop.parentloop.index }}.{{ forloop.index }} ' +
|
|
||||||
'{% endfor %}',
|
|
||||||
'outer' => [[1, 1, 1], [1, 1, 1]])
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_blank_string_not_iterable
|
def test_blank_string_not_iterable
|
||||||
assert_template_result('', "{% for char in characters %}I WILL NOT BE OUTPUT{% endfor %}", 'characters' => '')
|
assert_template_result('', "{% for char in characters %}I WILL NOT BE OUTPUT{% endfor %}", 'characters' => '')
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
require 'test_helper'
|
require 'test_helper'
|
||||||
|
|
||||||
class TestFileSystem
|
class TestFileSystem
|
||||||
def read_template_file(template_path)
|
def read_template_file(template_path, context)
|
||||||
case template_path
|
case template_path
|
||||||
when "product"
|
when "product"
|
||||||
"Product: {{ product.title }} "
|
"Product: {{ product.title }} "
|
||||||
@@ -37,14 +37,14 @@ class TestFileSystem
|
|||||||
end
|
end
|
||||||
|
|
||||||
class OtherFileSystem
|
class OtherFileSystem
|
||||||
def read_template_file(template_path)
|
def read_template_file(template_path, context)
|
||||||
'from OtherFileSystem'
|
'from OtherFileSystem'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
class CountingFileSystem
|
class CountingFileSystem
|
||||||
attr_reader :count
|
attr_reader :count
|
||||||
def read_template_file(template_path)
|
def read_template_file(template_path, context)
|
||||||
@count ||= 0
|
@count ||= 0
|
||||||
@count += 1
|
@count += 1
|
||||||
'from CountingFileSystem'
|
'from CountingFileSystem'
|
||||||
@@ -132,7 +132,7 @@ class IncludeTagTest < Minitest::Test
|
|||||||
def test_recursively_included_template_does_not_produce_endless_loop
|
def test_recursively_included_template_does_not_produce_endless_loop
|
||||||
|
|
||||||
infinite_file_system = Class.new do
|
infinite_file_system = Class.new do
|
||||||
def read_template_file(template_path)
|
def read_template_file(template_path, context)
|
||||||
"-{% include 'loop' %}"
|
"-{% include 'loop' %}"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -145,6 +145,18 @@ class IncludeTagTest < Minitest::Test
|
|||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_backwards_compatability_support_for_overridden_read_template_file
|
||||||
|
infinite_file_system = Class.new do
|
||||||
|
def read_template_file(template_path) # testing only one argument here.
|
||||||
|
"- hi mom"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
Liquid::Template.file_system = infinite_file_system.new
|
||||||
|
|
||||||
|
Template.parse("{% include 'hi_mom' %}").render!
|
||||||
|
end
|
||||||
|
|
||||||
def test_dynamically_choosen_template
|
def test_dynamically_choosen_template
|
||||||
assert_template_result "Test123", "{% include template %}", "template" => 'Test123'
|
assert_template_result "Test123", "{% include template %}", "template" => 'Test123'
|
||||||
assert_template_result "Test321", "{% include template %}", "template" => 'Test321'
|
assert_template_result "Test321", "{% include template %}", "template" => 'Test321'
|
||||||
@@ -219,12 +231,4 @@ 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
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
#!/usr/bin/env ruby
|
#!/usr/bin/env ruby
|
||||||
|
|
||||||
ENV["MT_NO_EXPECTATIONS"] = "1"
|
|
||||||
require 'minitest/autorun'
|
require 'minitest/autorun'
|
||||||
require 'spy/integration'
|
require 'spy/integration'
|
||||||
|
|
||||||
@@ -50,19 +49,13 @@ module Minitest
|
|||||||
end
|
end
|
||||||
|
|
||||||
def with_global_filter(*globals)
|
def with_global_filter(*globals)
|
||||||
original_global_strainer = Liquid::Strainer.class_variable_get(:@@global_strainer)
|
original_filters = Array.new(Liquid::Strainer.class_variable_get(:@@filters))
|
||||||
Liquid::Strainer.class_variable_set(:@@global_strainer, Class.new(Liquid::Strainer) do
|
|
||||||
@filter_methods = Set.new
|
|
||||||
end)
|
|
||||||
Liquid::Strainer.class_variable_get(:@@strainer_class_cache).clear
|
|
||||||
|
|
||||||
globals.each do |global|
|
globals.each do |global|
|
||||||
Liquid::Template.register_filter(global)
|
Liquid::Template.register_filter(global)
|
||||||
end
|
end
|
||||||
yield
|
yield
|
||||||
ensure
|
ensure
|
||||||
Liquid::Strainer.class_variable_get(:@@strainer_class_cache).clear
|
Liquid::Strainer.class_variable_set(:@@filters, original_filters)
|
||||||
Liquid::Strainer.class_variable_set(:@@global_strainer, original_global_strainer)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def with_taint_mode(mode)
|
def with_taint_mode(mode)
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ class FileSystemUnitTest < Minitest::Test
|
|||||||
|
|
||||||
def test_default
|
def test_default
|
||||||
assert_raises(FileSystemError) do
|
assert_raises(FileSystemError) do
|
||||||
BlankFileSystem.new.read_template_file("dummy")
|
BlankFileSystem.new.read_template_file("dummy", {'dummy'=>'smarty'})
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -31,11 +31,11 @@ class StrainerUnitTest < Minitest::Test
|
|||||||
|
|
||||||
def test_strainer_only_invokes_public_filter_methods
|
def test_strainer_only_invokes_public_filter_methods
|
||||||
strainer = Strainer.create(nil)
|
strainer = Strainer.create(nil)
|
||||||
assert_equal false, strainer.class.invokable?('__test__')
|
assert_equal false, strainer.invokable?('__test__')
|
||||||
assert_equal false, strainer.class.invokable?('test')
|
assert_equal false, strainer.invokable?('test')
|
||||||
assert_equal false, strainer.class.invokable?('instance_eval')
|
assert_equal false, strainer.invokable?('instance_eval')
|
||||||
assert_equal false, strainer.class.invokable?('__send__')
|
assert_equal false, strainer.invokable?('__send__')
|
||||||
assert_equal true, strainer.class.invokable?('size') # from the standard lib
|
assert_equal true, strainer.invokable?('size') # from the standard lib
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_strainer_returns_nil_if_no_filter_method_found
|
def test_strainer_returns_nil_if_no_filter_method_found
|
||||||
@@ -63,7 +63,9 @@ class StrainerUnitTest < Minitest::Test
|
|||||||
assert_kind_of Strainer, strainer
|
assert_kind_of Strainer, strainer
|
||||||
assert_kind_of a, strainer
|
assert_kind_of a, strainer
|
||||||
assert_kind_of b, strainer
|
assert_kind_of b, strainer
|
||||||
assert_kind_of Liquid::StandardFilters, strainer
|
Strainer.class_variable_get(:@@filters).each do |m|
|
||||||
|
assert_kind_of m, strainer
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
end # StrainerTest
|
end # StrainerTest
|
||||||
|
|||||||
@@ -147,10 +147,4 @@ class VariableUnitTest < Minitest::Test
|
|||||||
var = Variable.new(%! name_of_variable | upcase !)
|
var = Variable.new(%! name_of_variable | upcase !)
|
||||||
assert_equal " name_of_variable | upcase ", var.raw
|
assert_equal " name_of_variable | upcase ", var.raw
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_variable_lookup_interface
|
|
||||||
lookup = VariableLookup.new('a.b.c')
|
|
||||||
assert_equal 'a', lookup.name
|
|
||||||
assert_equal ['b', 'c'], lookup.lookups
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|||||||
Reference in New Issue
Block a user