mirror of
https://github.com/kemko/liquid.git
synced 2026-01-02 16:25:42 +03:00
Compare commits
263 Commits
string-sli
...
3-0-stable
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8d19fdde7f | ||
|
|
e934826820 | ||
|
|
529cc60ef1 | ||
|
|
5068fcae03 | ||
|
|
4095bfa9f8 | ||
|
|
0148289b88 | ||
|
|
d891c2b7ab | ||
|
|
ebbfb54de4 | ||
|
|
8f84ddb5ce | ||
|
|
09de50dcb1 | ||
|
|
49f2af4209 | ||
|
|
5d7c00a202 | ||
|
|
9bd05110dc | ||
|
|
9dd24824f9 | ||
|
|
291b58bc91 | ||
|
|
8c193e203f | ||
|
|
47dbcd93a5 | ||
|
|
000d0c911b | ||
|
|
95b340a7cf | ||
|
|
36a8696c07 | ||
|
|
cbc163ba1c | ||
|
|
9faf8f9a56 | ||
|
|
d6db28c854 | ||
|
|
475ea51f1f | ||
|
|
9c33e9601b | ||
|
|
b242a7273a | ||
|
|
4b1835e3c0 | ||
|
|
2fe3a21a5d | ||
|
|
76ef675eb2 | ||
|
|
e5fd4d929f | ||
|
|
2e42c7be1f | ||
|
|
95b031ee04 | ||
|
|
4d97a714a9 | ||
|
|
aa182f64b4 | ||
|
|
4e870302b1 | ||
|
|
098c89b5f5 | ||
|
|
70c45f8cd8 | ||
|
|
12d526a05c | ||
|
|
2fd8ad08c0 | ||
|
|
15e1d46125 | ||
|
|
73fcd42403 | ||
|
|
263e90e772 | ||
|
|
81770f094d | ||
|
|
dd5ee81089 | ||
|
|
a07e382617 | ||
|
|
4dc682313f | ||
|
|
5616ddf00e | ||
|
|
fcb23a4cd2 | ||
|
|
a8f60ff6b1 | ||
|
|
a206c8301d | ||
|
|
ee0de01480 | ||
|
|
887b05e6ed | ||
|
|
5d68e8803f | ||
|
|
dedd1d3dc0 | ||
|
|
d9ae36ec40 | ||
|
|
b9ac3fef8f | ||
|
|
f5faa4858c | ||
|
|
bc5e444d04 | ||
|
|
3a4b63f37e | ||
|
|
a1a128db19 | ||
|
|
d502b9282a | ||
|
|
fee8e41466 | ||
|
|
37260f17ff | ||
|
|
2da9d49478 | ||
|
|
7196a2d58e | ||
|
|
a056f6521c | ||
|
|
de16db9b72 | ||
|
|
b4ea483c4e | ||
|
|
7843bcca8d | ||
|
|
76ea5596ff | ||
|
|
f9318e8c93 | ||
|
|
71253ec6f9 | ||
|
|
0fa075b879 | ||
|
|
6d080afd22 | ||
|
|
a67e2a0a00 | ||
|
|
f387508666 | ||
|
|
632b1fb702 | ||
|
|
d84870d7a5 | ||
|
|
584b492e70 | ||
|
|
b79c9cb9bf | ||
|
|
cf5ccede50 | ||
|
|
23622a9739 | ||
|
|
7ba5a6ab75 | ||
|
|
be3d261e11 | ||
|
|
eeb061ef44 | ||
|
|
67b2c320a1 | ||
|
|
1d151885be | ||
|
|
e836024dd9 | ||
|
|
638455ed92 | ||
|
|
b2a74883e9 | ||
|
|
6875e5e16f | ||
|
|
a5717a3f8d | ||
|
|
804fcfebd1 | ||
|
|
b37ee5684a | ||
|
|
0573b63b4c | ||
|
|
29c21d7867 | ||
|
|
478eb893a9 | ||
|
|
eae29f8c48 | ||
|
|
4004cb63a5 | ||
|
|
aafdf4adb0 | ||
|
|
debac5dd0b | ||
|
|
ce06ed4bb1 | ||
|
|
939365c234 | ||
|
|
c60fd0715d | ||
|
|
c83e1c7b6d | ||
|
|
aabbd8f1a1 | ||
|
|
60d8a213a5 | ||
|
|
17cc8fdbb3 | ||
|
|
27c1019385 | ||
|
|
3a0ee6ae91 | ||
|
|
5eff375094 | ||
|
|
2df643ba18 | ||
|
|
68af2d6e2a | ||
|
|
dfb6c20493 | ||
|
|
4e9d414fde | ||
|
|
c0ec0652ae | ||
|
|
f8c3cea09b | ||
|
|
0b847e553c | ||
|
|
c2663258be | ||
|
|
d4654d0062 | ||
|
|
ffd4f9d959 | ||
|
|
292161865d | ||
|
|
35808390ee | ||
|
|
1678c07548 | ||
|
|
173a58d36a | ||
|
|
f31e309770 | ||
|
|
ffe1036e15 | ||
|
|
d3b113d2e1 | ||
|
|
2aa9bbbac2 | ||
|
|
d5e57a8ea4 | ||
|
|
5c0e0be639 | ||
|
|
a74d40f1e5 | ||
|
|
79d4ec1a48 | ||
|
|
4db22be8ba | ||
|
|
dc58a4d648 | ||
|
|
2809ec780a | ||
|
|
2d98392bf5 | ||
|
|
df6b442816 | ||
|
|
4b22fc8d1b | ||
|
|
fb6f9c1c13 | ||
|
|
66ae7f3ec0 | ||
|
|
0bea31d2ef | ||
|
|
e5b0487fef | ||
|
|
9117722740 | ||
|
|
baea0a6bf7 | ||
|
|
17347d43de | ||
|
|
794ca9f604 | ||
|
|
15f6cabf83 | ||
|
|
e53d102a2c | ||
|
|
33e7b8e373 | ||
|
|
9b8e3d437e | ||
|
|
a2f0f2547d | ||
|
|
57d5426eed | ||
|
|
3e3a415457 | ||
|
|
deba039d6d | ||
|
|
ee4295c889 | ||
|
|
f5e67a12f9 | ||
|
|
6b56bdd74f | ||
|
|
ba6e3e3da6 | ||
|
|
a8e63ff03d | ||
|
|
052ef9fcb8 | ||
|
|
d07b12dc7d | ||
|
|
32e4f2d3b1 | ||
|
|
2cb1483d54 | ||
|
|
6c6350f18b | ||
|
|
eae24373e6 | ||
|
|
034a47a6cf | ||
|
|
51c1165f26 | ||
|
|
0b45ffeada | ||
|
|
b7b243a13d | ||
|
|
18e8ce1eb0 | ||
|
|
994f309465 | ||
|
|
02d42a1475 | ||
|
|
d099878385 | ||
|
|
6a061cbe81 | ||
|
|
c864a75903 | ||
|
|
d6fdf86acd | ||
|
|
55597b8398 | ||
|
|
c75522026b | ||
|
|
1e0e9f1f31 | ||
|
|
5fc1929b73 | ||
|
|
746a800475 | ||
|
|
85dc7ef610 | ||
|
|
bc3b066ba8 | ||
|
|
3c2de7737d | ||
|
|
adb7d2bbb8 | ||
|
|
0e56cf99ab | ||
|
|
0df3f1c372 | ||
|
|
44b9ad604f | ||
|
|
535d549978 | ||
|
|
32349033a9 | ||
|
|
fd8c30070a | ||
|
|
4cfc05e32a | ||
|
|
c4bc6cf3db | ||
|
|
0ac3ec7834 | ||
|
|
8909c9f27a | ||
|
|
51c708c8f8 | ||
|
|
f57383af37 | ||
|
|
d007c50856 | ||
|
|
101f125a69 | ||
|
|
5110ca906c | ||
|
|
ac0f63eda9 | ||
|
|
1372274fca | ||
|
|
69951be173 | ||
|
|
c9863836cd | ||
|
|
14b8d824d7 | ||
|
|
114a37d9ba | ||
|
|
30bd9ad957 | ||
|
|
2239921804 | ||
|
|
1ea178e7a8 | ||
|
|
5650c7eea1 | ||
|
|
553b0926ae | ||
|
|
2bac6267f9 | ||
|
|
628ab3dc6a | ||
|
|
2eb552c65d | ||
|
|
6e40746ce4 | ||
|
|
75068e8fa4 | ||
|
|
ad1152853a | ||
|
|
73098ac5bc | ||
|
|
8bc3792c0e | ||
|
|
3ef29c624c | ||
|
|
a85fb38769 | ||
|
|
6a1c3cff1a | ||
|
|
bde32018dd | ||
|
|
2a12f253bf | ||
|
|
fa14fd02e7 | ||
|
|
f15d24509d | ||
|
|
09e4378cfb | ||
|
|
af0e26fb16 | ||
|
|
f5502e8152 | ||
|
|
c098235baa | ||
|
|
0e2bf768ba | ||
|
|
4c477c2087 | ||
|
|
cd7fc050b1 | ||
|
|
8291c5e72c | ||
|
|
7e45155aa9 | ||
|
|
3dbb35d823 | ||
|
|
44f29a87a8 | ||
|
|
f0afbc27e0 | ||
|
|
fdf03076e0 | ||
|
|
d1bfda15e3 | ||
|
|
d8d9984a7b | ||
|
|
fbaabf3b59 | ||
|
|
7e0ef867d2 | ||
|
|
3682414cc4 | ||
|
|
af24d2c2ab | ||
|
|
4ee43bc5d2 | ||
|
|
1320a69fca | ||
|
|
3b14e27f55 | ||
|
|
face33a137 | ||
|
|
d4ecaff8b8 | ||
|
|
a5990042ff | ||
|
|
e190bbba9e | ||
|
|
4b5e41d04e | ||
|
|
4b69f6ae83 | ||
|
|
b9feb415f6 | ||
|
|
92781ec43b | ||
|
|
ff5c1f83f7 | ||
|
|
e2b337af2f | ||
|
|
f373b1003d | ||
|
|
503d924274 | ||
|
|
0a7de51e2b | ||
|
|
43ac8d560b |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -5,3 +5,4 @@ pkg
|
|||||||
*.rbc
|
*.rbc
|
||||||
.rvmrc
|
.rvmrc
|
||||||
.ruby-version
|
.ruby-version
|
||||||
|
Gemfile.lock
|
||||||
|
|||||||
13
.travis.yml
13
.travis.yml
@@ -1,14 +1,17 @@
|
|||||||
rvm:
|
rvm:
|
||||||
- 1.9.3
|
- 1.9
|
||||||
- 2.0.0
|
- 2.0
|
||||||
- 2.1.0
|
- 2.1
|
||||||
|
- 2.2
|
||||||
|
- ruby-head
|
||||||
- jruby-19mode
|
- jruby-19mode
|
||||||
- jruby-head
|
- jruby-head
|
||||||
- rbx-19mode
|
- rbx-2
|
||||||
matrix:
|
matrix:
|
||||||
allow_failures:
|
allow_failures:
|
||||||
- rvm: rbx-19mode
|
- rvm: rbx-2
|
||||||
- rvm: jruby-head
|
- rvm: jruby-head
|
||||||
|
- rvm: ruby-head
|
||||||
|
|
||||||
script: "rake test"
|
script: "rake test"
|
||||||
|
|
||||||
|
|||||||
9
Gemfile
Normal file
9
Gemfile
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
source 'https://rubygems.org'
|
||||||
|
|
||||||
|
gemspec
|
||||||
|
gem 'stackprof', platforms: :mri_21
|
||||||
|
|
||||||
|
group :test do
|
||||||
|
gem 'spy', '0.4.1'
|
||||||
|
gem 'benchmark-ips'
|
||||||
|
end
|
||||||
63
History.md
63
History.md
@@ -1,8 +1,42 @@
|
|||||||
# Liquid Version History
|
# Liquid Version History
|
||||||
|
|
||||||
## 3.0.0 / not yet released / branch "master"
|
## 3.0.5 / 2015-07-23 / branch "3-0-stable"
|
||||||
|
|
||||||
* ...
|
* Fix test failure under certain timezones [Dylan Thacker-Smith]
|
||||||
|
|
||||||
|
## 3.0.4 / 2015-07-17
|
||||||
|
|
||||||
|
* Fix chained access to multi-dimensional hashes [Florian Weingarten]
|
||||||
|
|
||||||
|
## 3.0.3 / 2015-05-28
|
||||||
|
|
||||||
|
* Fix condition parse order in strict mode (#569) [Justin Li, pushrax]
|
||||||
|
|
||||||
|
## 3.0.2 / 2015-04-24
|
||||||
|
|
||||||
|
* 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
|
||||||
|
|
||||||
|
* 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, see #423 [Bogdan Gusiev]
|
||||||
|
* Add url_encode to standard filters, see #421 [Derrick Reimer, djreimer]
|
||||||
|
* Add uniq to standard filters [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 checking for block interrupts to reduce object allocation #380 [Jason Hiltz-Laforge, jasonhl]
|
||||||
|
* Properly set context rethrow_errors on render! #349 [Thierry Joyal, tjoyal]
|
||||||
|
* Fix broken rendering of variables which are equal to false, see #345 [Florian Weingarten, fw42]
|
||||||
|
* Remove ActionView template handler [Dylan Thacker-Smith, dylanahsmith]
|
||||||
|
* Freeze lots of string literals for new Ruby 2.1 optimization, see #297 [Florian Weingarten, fw42]
|
||||||
|
* 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]
|
||||||
|
* 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, see #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, see #250 [Nick Jones, dntj]
|
* Make if, for & case tags return complete and consistent nodelists, see #250 [Nick Jones, dntj]
|
||||||
@@ -21,9 +55,22 @@
|
|||||||
* Fix clashing method names in enumerable drops, see #238 [Florian Weingarten, fw42]
|
* Fix clashing method names in enumerable drops, see #238 [Florian Weingarten, fw42]
|
||||||
* Make map filter work on enumerable drops, see #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]
|
||||||
* Raise `Liquid::ArgumentError` instead of `::ArgumentError` when filter has wrong number of arguments #309 [Bogdan Gusiev, bogdan]
|
|
||||||
|
|
||||||
## 2.6.0 / 2013-11-25 / branch "2.6-stable"
|
## 2.6.3 / 2015-07-23 / branch "2-6-stable"
|
||||||
|
|
||||||
|
* Fix test failure under certain timezones [Dylan Thacker-Smith]
|
||||||
|
|
||||||
|
## 2.6.2 / 2015-01-23
|
||||||
|
|
||||||
|
* Remove duplicate hash key [Parker Moore]
|
||||||
|
|
||||||
|
## 2.6.1 / 2014-01-10
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
IMPORTANT: Liquid 2.6 is going to be the last version of Liquid which maintains explicit Ruby 1.8 compatability.
|
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.
|
The following releases will only be tested against Ruby 1.9 and Ruby 2.0 and are likely to break on Ruby 1.8.
|
||||||
@@ -47,7 +94,13 @@ The following releases will only be tested against Ruby 1.9 and Ruby 2.0 and are
|
|||||||
* Better documentation for 'include' tag (closes #163) [Peter Schröder, phoet]
|
* 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]
|
* 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.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.5.4 / 2013-11-11
|
||||||
|
|
||||||
* Fix "can't convert Fixnum into String" for "replace", see #173, [wǒ_is神仙, jsw0528]
|
* Fix "can't convert Fixnum into String" for "replace", see #173, [wǒ_is神仙, jsw0528]
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
[](http://travis-ci.org/Shopify/liquid)
|
[](http://travis-ci.org/Shopify/liquid)
|
||||||
|
[](http://inch-ci.org/github/Shopify/liquid)
|
||||||
|
|
||||||
# Liquid template engine
|
# Liquid template engine
|
||||||
|
|
||||||
* [Contributing guidelines](CONTRIBUTING.md)
|
* [Contributing guidelines](CONTRIBUTING.md)
|
||||||
|
|||||||
8
Rakefile
8
Rakefile
@@ -8,7 +8,7 @@ task :default => 'test'
|
|||||||
desc 'run test suite with default parser'
|
desc 'run test suite with default parser'
|
||||||
Rake::TestTask.new(:base_test) do |t|
|
Rake::TestTask.new(:base_test) do |t|
|
||||||
t.libs << '.' << 'lib' << 'test'
|
t.libs << '.' << 'lib' << 'test'
|
||||||
t.test_files = FileList['test/liquid/**/*_test.rb']
|
t.test_files = FileList['test/{integration,unit}/**/*_test.rb']
|
||||||
t.verbose = false
|
t.verbose = false
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -64,9 +64,9 @@ namespace :profile do
|
|||||||
ruby "./performance/profile.rb"
|
ruby "./performance/profile.rb"
|
||||||
end
|
end
|
||||||
|
|
||||||
desc "Run KCacheGrind"
|
desc "Run the liquid profile/performance coverage with strict parsing"
|
||||||
task :grind => :run do
|
task :strict do
|
||||||
system "qcachegrind /tmp/liquid.rubyprof_calltreeprinter.txt"
|
ruby "./performance/profile.rb strict"
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -11,9 +11,10 @@ class LiquidServlet < WEBrick::HTTPServlet::AbstractServlet
|
|||||||
private
|
private
|
||||||
|
|
||||||
def handle(type, req, res)
|
def handle(type, req, res)
|
||||||
@request, @response = req, res
|
@request = req
|
||||||
|
@response = res
|
||||||
|
|
||||||
@request.path_info =~ /(\w+)$/
|
@request.path_info =~ /(\w+)\z/
|
||||||
@action = $1 || 'index'
|
@action = $1 || 'index'
|
||||||
@assigns = send(@action) if respond_to?(@action)
|
@assigns = send(@action) if respond_to?(@action)
|
||||||
|
|
||||||
|
|||||||
@@ -3,4 +3,4 @@
|
|||||||
<p>It is {{date}}</p>
|
<p>It is {{date}}</p>
|
||||||
|
|
||||||
|
|
||||||
<p>Check out the <a href="http://localhost:3000/products">Products</a> screen </p>
|
<p>Check out the <a href="/products">Products</a> screen </p>
|
||||||
|
|||||||
8
init.rb
8
init.rb
@@ -1,8 +0,0 @@
|
|||||||
require 'liquid'
|
|
||||||
require 'extras/liquid_view'
|
|
||||||
|
|
||||||
if defined? ActionView::Template and ActionView::Template.respond_to? :register_template_handler
|
|
||||||
ActionView::Template
|
|
||||||
else
|
|
||||||
ActionView::Base
|
|
||||||
end.register_template_handler(:liquid, LiquidView)
|
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
# LiquidView is a action view extension class. You can register it with rails
|
|
||||||
# and use liquid as an template system for .liquid files
|
|
||||||
#
|
|
||||||
# Example
|
|
||||||
#
|
|
||||||
# ActionView::Base::register_template_handler :liquid, LiquidView
|
|
||||||
class LiquidView
|
|
||||||
PROTECTED_ASSIGNS = %w( template_root response _session template_class action_name request_origin session template
|
|
||||||
_response url _request _cookies variables_added _flash params _headers request cookies
|
|
||||||
ignore_missing_templates flash _params logger before_filter_chain_aborted headers )
|
|
||||||
PROTECTED_INSTANCE_VARIABLES = %w( @_request @controller @_first_render @_memoized__pick_template @view_paths
|
|
||||||
@helpers @assigns_added @template @_render_stack @template_format @assigns )
|
|
||||||
|
|
||||||
def self.call(template)
|
|
||||||
"LiquidView.new(self).render(template, local_assigns)"
|
|
||||||
end
|
|
||||||
|
|
||||||
def initialize(view)
|
|
||||||
@view = view
|
|
||||||
end
|
|
||||||
|
|
||||||
def render(template, local_assigns = nil)
|
|
||||||
@view.controller.headers["Content-Type"] ||= 'text/html; charset=utf-8'
|
|
||||||
|
|
||||||
# Rails 2.2 Template has source, but not locals
|
|
||||||
if template.respond_to?(:source) && !template.respond_to?(:locals)
|
|
||||||
assigns = (@view.instance_variables - PROTECTED_INSTANCE_VARIABLES).inject({}) do |hash, ivar|
|
|
||||||
hash[ivar[1..-1]] = @view.instance_variable_get(ivar)
|
|
||||||
hash
|
|
||||||
end
|
|
||||||
else
|
|
||||||
assigns = @view.assigns.reject{ |k,v| PROTECTED_ASSIGNS.include?(k) }
|
|
||||||
end
|
|
||||||
|
|
||||||
source = template.respond_to?(:source) ? template.source : template
|
|
||||||
local_assigns = (template.respond_to?(:locals) ? template.locals : local_assigns) || {}
|
|
||||||
|
|
||||||
if content_for_layout = @view.instance_variable_get("@content_for_layout")
|
|
||||||
assigns['content_for_layout'] = content_for_layout
|
|
||||||
end
|
|
||||||
assigns.merge!(local_assigns.stringify_keys)
|
|
||||||
|
|
||||||
liquid = Liquid::Template.parse(source)
|
|
||||||
liquid.render(assigns, :filters => [@view.controller.master_helper_module], :registers => {:action_view => @view, :controller => @view.controller})
|
|
||||||
end
|
|
||||||
|
|
||||||
def compilable?
|
|
||||||
false
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
@@ -21,9 +21,9 @@
|
|||||||
|
|
||||||
module Liquid
|
module Liquid
|
||||||
FilterSeparator = /\|/
|
FilterSeparator = /\|/
|
||||||
ArgumentSeparator = ','
|
ArgumentSeparator = ','.freeze
|
||||||
FilterArgumentSeparator = ':'
|
FilterArgumentSeparator = ':'.freeze
|
||||||
VariableAttributeSeparator = '.'
|
VariableAttributeSeparator = '.'.freeze
|
||||||
TagStart = /\{\%/
|
TagStart = /\{\%/
|
||||||
TagEnd = /\%\}/
|
TagEnd = /\%\}/
|
||||||
VariableSignature = /\(?[\w\-\.\[\]]\)?/
|
VariableSignature = /\(?[\w\-\.\[\]]\)?/
|
||||||
@@ -33,16 +33,14 @@ module Liquid
|
|||||||
VariableIncompleteEnd = /\}\}?/
|
VariableIncompleteEnd = /\}\}?/
|
||||||
QuotedString = /"[^"]*"|'[^']*'/
|
QuotedString = /"[^"]*"|'[^']*'/
|
||||||
QuotedFragment = /#{QuotedString}|(?:[^\s,\|'"]|#{QuotedString})+/o
|
QuotedFragment = /#{QuotedString}|(?:[^\s,\|'"]|#{QuotedString})+/o
|
||||||
StrictQuotedFragment = /"[^"]+"|'[^']+'|[^\s|:,]+/
|
|
||||||
FirstFilterArgument = /#{FilterArgumentSeparator}(?:#{StrictQuotedFragment})/o
|
|
||||||
OtherFilterArgument = /#{ArgumentSeparator}(?:#{StrictQuotedFragment})/o
|
|
||||||
SpacelessFilter = /^(?:'[^']+'|"[^"]+"|[^'"])*#{FilterSeparator}(?:#{StrictQuotedFragment})(?:#{FirstFilterArgument}(?:#{OtherFilterArgument})*)?/o
|
|
||||||
Expression = /(?:#{QuotedFragment}(?:#{SpacelessFilter})*)/o
|
|
||||||
TagAttributes = /(\w+)\s*\:\s*(#{QuotedFragment})/o
|
TagAttributes = /(\w+)\s*\:\s*(#{QuotedFragment})/o
|
||||||
AnyStartingTag = /\{\{|\{\%/
|
AnyStartingTag = /\{\{|\{\%/
|
||||||
PartialTemplateParser = /#{TagStart}.*?#{TagEnd}|#{VariableStart}.*?#{VariableIncompleteEnd}/o
|
PartialTemplateParser = /#{TagStart}.*?#{TagEnd}|#{VariableStart}.*?#{VariableIncompleteEnd}/om
|
||||||
TemplateParser = /(#{PartialTemplateParser}|#{AnyStartingTag})/o
|
TemplateParser = /(#{PartialTemplateParser}|#{AnyStartingTag})/om
|
||||||
VariableParser = /\[[^\]]+\]|#{VariableSegment}+\??/o
|
VariableParser = /\[[^\]]+\]|#{VariableSegment}+\??/o
|
||||||
|
|
||||||
|
singleton_class.send(:attr_accessor, :cache_classes)
|
||||||
|
self.cache_classes = true
|
||||||
end
|
end
|
||||||
|
|
||||||
require "liquid/version"
|
require "liquid/version"
|
||||||
@@ -54,19 +52,26 @@ require 'liquid/extensions'
|
|||||||
require 'liquid/errors'
|
require 'liquid/errors'
|
||||||
require 'liquid/interrupts'
|
require 'liquid/interrupts'
|
||||||
require 'liquid/strainer'
|
require 'liquid/strainer'
|
||||||
|
require 'liquid/expression'
|
||||||
require 'liquid/context'
|
require 'liquid/context'
|
||||||
|
require 'liquid/parser_switching'
|
||||||
require 'liquid/tag'
|
require 'liquid/tag'
|
||||||
require 'liquid/block'
|
require 'liquid/block'
|
||||||
require 'liquid/document'
|
require 'liquid/document'
|
||||||
require 'liquid/variable'
|
require 'liquid/variable'
|
||||||
|
require 'liquid/variable_lookup'
|
||||||
|
require 'liquid/range_lookup'
|
||||||
require 'liquid/file_system'
|
require 'liquid/file_system'
|
||||||
require 'liquid/template'
|
require 'liquid/template'
|
||||||
require 'liquid/htmltags'
|
|
||||||
require 'liquid/standardfilters'
|
require 'liquid/standardfilters'
|
||||||
require 'liquid/condition'
|
require 'liquid/condition'
|
||||||
require 'liquid/module_ex'
|
require 'liquid/module_ex'
|
||||||
require 'liquid/utils'
|
require 'liquid/utils'
|
||||||
|
require 'liquid/token'
|
||||||
|
|
||||||
# Load all the tags of the standard library
|
# Load all the tags of the standard library
|
||||||
#
|
#
|
||||||
Dir[File.dirname(__FILE__) + '/liquid/tags/*.rb'].each { |f| require f }
|
Dir[File.dirname(__FILE__) + '/liquid/tags/*.rb'].each { |f| require f }
|
||||||
|
|
||||||
|
require 'liquid/profiler'
|
||||||
|
require 'liquid/profiler/hooks'
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
module Liquid
|
module Liquid
|
||||||
class Block < Tag
|
class Block < Tag
|
||||||
IsTag = /^#{TagStart}/o
|
FullToken = /\A#{TagStart}\s*(\w+)\s*(.*)?#{TagEnd}\z/om
|
||||||
IsVariable = /^#{VariableStart}/o
|
ContentOfVariable = /\A#{VariableStart}(.*)#{VariableEnd}\z/om
|
||||||
FullToken = /^#{TagStart}\s*(\w+)\s*(.*)?#{TagEnd}$/o
|
TAGSTART = "{%".freeze
|
||||||
ContentOfVariable = /^#{VariableStart}(.*)#{VariableEnd}$/o
|
VARSTART = "{{".freeze
|
||||||
|
|
||||||
def blank?
|
def blank?
|
||||||
@blank || false
|
@blank
|
||||||
end
|
end
|
||||||
|
|
||||||
def parse(tokens)
|
def parse(tokens)
|
||||||
@@ -14,45 +14,45 @@ module Liquid
|
|||||||
@nodelist ||= []
|
@nodelist ||= []
|
||||||
@nodelist.clear
|
@nodelist.clear
|
||||||
|
|
||||||
# All child tags of the current block.
|
|
||||||
@children = []
|
|
||||||
|
|
||||||
while token = tokens.shift
|
while token = tokens.shift
|
||||||
case token
|
begin
|
||||||
when IsTag
|
unless token.empty?
|
||||||
if token =~ FullToken
|
case
|
||||||
|
when token.start_with?(TAGSTART)
|
||||||
|
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 delimiter just end parsing here and let the outer block
|
||||||
# proceed
|
# proceed
|
||||||
if block_delimiter == $1
|
return if block_delimiter == $1
|
||||||
end_tag
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
# fetch the tag from registered blocks
|
# fetch the tag from registered blocks
|
||||||
if tag = Template.tags[$1]
|
if tag = Template.tags[$1]
|
||||||
new_tag = tag.new_with_options($1, $2, tokens, @options || {})
|
markup = token.is_a?(Token) ? token.child($2) : $2
|
||||||
@blank &&= new_tag.blank?
|
new_tag = tag.parse($1, markup, tokens, @options)
|
||||||
@nodelist << new_tag
|
new_tag.line_number = token.line_number if token.is_a?(Token)
|
||||||
@children << new_tag
|
@blank &&= new_tag.blank?
|
||||||
|
@nodelist << new_tag
|
||||||
|
else
|
||||||
|
# this tag is not registered with the system
|
||||||
|
# pass it to the current block for special handling or error reporting
|
||||||
|
unknown_tag($1, $2, tokens)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
raise SyntaxError.new(options[:locale].t("errors.syntax.tag_termination".freeze, :token => token, :tag_end => TagEnd.inspect))
|
||||||
|
end
|
||||||
|
when token.start_with?(VARSTART)
|
||||||
|
new_var = create_variable(token)
|
||||||
|
new_var.line_number = token.line_number if token.is_a?(Token)
|
||||||
|
@nodelist << new_var
|
||||||
|
@blank = false
|
||||||
else
|
else
|
||||||
# this tag is not registered with the system
|
@nodelist << token
|
||||||
# pass it to the current block for special handling or error reporting
|
@blank &&= (token =~ /\A\s*\z/)
|
||||||
unknown_tag($1, $2, tokens)
|
|
||||||
end
|
end
|
||||||
else
|
|
||||||
raise SyntaxError.new(options[:locale].t("errors.syntax.tag_termination", :token => token, :tag_end => TagEnd.inspect))
|
|
||||||
end
|
end
|
||||||
when IsVariable
|
rescue SyntaxError => e
|
||||||
new_var = create_variable(token)
|
e.set_line_number_from_token(token)
|
||||||
@nodelist << new_var
|
raise
|
||||||
@children << new_var
|
|
||||||
@blank = false
|
|
||||||
when ''
|
|
||||||
# pass
|
|
||||||
else
|
|
||||||
@nodelist << token
|
|
||||||
@blank &&= (token =~ /\A\s*\z/)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -67,43 +67,41 @@ module Liquid
|
|||||||
all_warnings = []
|
all_warnings = []
|
||||||
all_warnings.concat(@warnings) if @warnings
|
all_warnings.concat(@warnings) if @warnings
|
||||||
|
|
||||||
(@children || []).each do |node|
|
(nodelist || []).each do |node|
|
||||||
all_warnings.concat(node.warnings || [])
|
all_warnings.concat(node.warnings || []) if node.respond_to?(:warnings)
|
||||||
end
|
end
|
||||||
|
|
||||||
all_warnings
|
all_warnings
|
||||||
end
|
end
|
||||||
|
|
||||||
def end_tag
|
|
||||||
end
|
|
||||||
|
|
||||||
def unknown_tag(tag, params, tokens)
|
def unknown_tag(tag, params, tokens)
|
||||||
case tag
|
case tag
|
||||||
when 'else'
|
when 'else'.freeze
|
||||||
raise SyntaxError.new(options[:locale].t("errors.syntax.unexpected_else",
|
raise SyntaxError.new(options[:locale].t("errors.syntax.unexpected_else".freeze,
|
||||||
:block_name => block_name))
|
:block_name => block_name))
|
||||||
when 'end'
|
when 'end'.freeze
|
||||||
raise SyntaxError.new(options[:locale].t("errors.syntax.invalid_delimiter",
|
raise SyntaxError.new(options[:locale].t("errors.syntax.invalid_delimiter".freeze,
|
||||||
:block_name => block_name,
|
:block_name => block_name,
|
||||||
:block_delimiter => block_delimiter))
|
:block_delimiter => block_delimiter))
|
||||||
else
|
else
|
||||||
raise SyntaxError.new(options[:locale].t("errors.syntax.unknown_tag", :tag => tag))
|
raise SyntaxError.new(options[:locale].t("errors.syntax.unknown_tag".freeze, :tag => tag))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def block_delimiter
|
|
||||||
"end#{block_name}"
|
|
||||||
end
|
|
||||||
|
|
||||||
def block_name
|
def block_name
|
||||||
@tag_name
|
@tag_name
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def block_delimiter
|
||||||
|
@block_delimiter ||= "end#{block_name}"
|
||||||
|
end
|
||||||
|
|
||||||
def create_variable(token)
|
def create_variable(token)
|
||||||
token.scan(ContentOfVariable) do |content|
|
token.scan(ContentOfVariable) do |content|
|
||||||
return Variable.new(content.first, @options)
|
markup = token.is_a?(Token) ? token.child(content.first) : content.first
|
||||||
|
return Variable.new(markup, @options)
|
||||||
end
|
end
|
||||||
raise SyntaxError.new(options[:locale].t("errors.syntax.variable_termination", :token => token, :tag_end => VariableEnd.inspect))
|
raise SyntaxError.new(options[:locale].t("errors.syntax.variable_termination".freeze, :token => token, :tag_end => VariableEnd.inspect))
|
||||||
end
|
end
|
||||||
|
|
||||||
def render(context)
|
def render(context)
|
||||||
@@ -113,7 +111,7 @@ module Liquid
|
|||||||
protected
|
protected
|
||||||
|
|
||||||
def assert_missing_delimitation!
|
def assert_missing_delimitation!
|
||||||
raise SyntaxError.new(options[:locale].t("errors.syntax.tag_never_closed", :block_name => block_name))
|
raise SyntaxError.new(options[:locale].t("errors.syntax.tag_never_closed".freeze, :block_name => block_name))
|
||||||
end
|
end
|
||||||
|
|
||||||
def render_all(list, context)
|
def render_all(list, context)
|
||||||
@@ -134,23 +132,29 @@ module Liquid
|
|||||||
break
|
break
|
||||||
end
|
end
|
||||||
|
|
||||||
token_output = (token.respond_to?(:render) ? token.render(context) : token)
|
token_output = render_token(token, context)
|
||||||
context.increment_used_resources(:render_length_current, token_output)
|
|
||||||
if context.resource_limits_reached?
|
|
||||||
context.resource_limits[:reached] = true
|
|
||||||
raise MemoryError.new("Memory limits exceeded")
|
|
||||||
end
|
|
||||||
unless token.is_a?(Block) && token.blank?
|
unless token.is_a?(Block) && token.blank?
|
||||||
output << token_output
|
output << token_output
|
||||||
end
|
end
|
||||||
rescue MemoryError => e
|
rescue MemoryError => e
|
||||||
raise e
|
raise e
|
||||||
rescue ::StandardError => e
|
rescue ::StandardError => e
|
||||||
output << (context.handle_error(e))
|
output << (context.handle_error(e, token))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
output.join
|
output.join
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def render_token(token, context)
|
||||||
|
token_output = (token.respond_to?(:render) ? token.render(context) : token)
|
||||||
|
context.increment_used_resources(:render_length_current, token_output)
|
||||||
|
if context.resource_limits_reached?
|
||||||
|
context.resource_limits[:reached] = true
|
||||||
|
raise MemoryError.new("Memory limits exceeded".freeze)
|
||||||
|
end
|
||||||
|
token_output
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
123
lib/liquid/block_body.rb
Normal file
123
lib/liquid/block_body.rb
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
module Liquid
|
||||||
|
class BlockBody
|
||||||
|
FullToken = /\A#{TagStart}\s*(\w+)\s*(.*)?#{TagEnd}\z/om
|
||||||
|
ContentOfVariable = /\A#{VariableStart}(.*)#{VariableEnd}\z/om
|
||||||
|
TAGSTART = "{%".freeze
|
||||||
|
VARSTART = "{{".freeze
|
||||||
|
|
||||||
|
attr_reader :nodelist
|
||||||
|
|
||||||
|
def initialize
|
||||||
|
@nodelist = []
|
||||||
|
@blank = true
|
||||||
|
end
|
||||||
|
|
||||||
|
def parse(tokens, options)
|
||||||
|
while token = tokens.shift
|
||||||
|
begin
|
||||||
|
unless token.empty?
|
||||||
|
case
|
||||||
|
when token.start_with?(TAGSTART)
|
||||||
|
if token =~ FullToken
|
||||||
|
tag_name = $1
|
||||||
|
markup = $2
|
||||||
|
# fetch the tag from registered blocks
|
||||||
|
if tag = Template.tags[tag_name]
|
||||||
|
markup = token.child(markup) if token.is_a?(Token)
|
||||||
|
new_tag = tag.parse(tag_name, markup, tokens, options)
|
||||||
|
new_tag.line_number = token.line_number if token.is_a?(Token)
|
||||||
|
@blank &&= new_tag.blank?
|
||||||
|
@nodelist << new_tag
|
||||||
|
else
|
||||||
|
# end parsing if we reach an unknown tag and let the caller decide
|
||||||
|
# determine how to proceed
|
||||||
|
return yield tag_name, markup
|
||||||
|
end
|
||||||
|
else
|
||||||
|
raise SyntaxError.new(options[:locale].t("errors.syntax.tag_termination".freeze, :token => token, :tag_end => TagEnd.inspect))
|
||||||
|
end
|
||||||
|
when token.start_with?(VARSTART)
|
||||||
|
new_var = create_variable(token, options)
|
||||||
|
new_var.line_number = token.line_number if token.is_a?(Token)
|
||||||
|
@nodelist << new_var
|
||||||
|
@blank = false
|
||||||
|
else
|
||||||
|
@nodelist << token
|
||||||
|
@blank &&= !!(token =~ /\A\s*\z/)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
rescue SyntaxError => e
|
||||||
|
e.set_line_number_from_token(token)
|
||||||
|
raise
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
yield nil, nil
|
||||||
|
end
|
||||||
|
|
||||||
|
def blank?
|
||||||
|
@blank
|
||||||
|
end
|
||||||
|
|
||||||
|
def warnings
|
||||||
|
all_warnings = []
|
||||||
|
nodelist.each do |node|
|
||||||
|
all_warnings.concat(node.warnings) if node.respond_to?(:warnings) && node.warnings
|
||||||
|
end
|
||||||
|
all_warnings
|
||||||
|
end
|
||||||
|
|
||||||
|
def render(context)
|
||||||
|
output = []
|
||||||
|
context.resource_limits[:render_length_current] = 0
|
||||||
|
context.resource_limits[:render_score_current] += @nodelist.length
|
||||||
|
|
||||||
|
@nodelist.each do |token|
|
||||||
|
# Break out if we have any unhanded interrupts.
|
||||||
|
break if context.has_interrupt?
|
||||||
|
|
||||||
|
begin
|
||||||
|
# If we get an Interrupt that means the block must stop processing. An
|
||||||
|
# Interrupt is any command that stops block execution such as {% break %}
|
||||||
|
# or {% continue %}
|
||||||
|
if token.is_a?(Continue) or token.is_a?(Break)
|
||||||
|
context.push_interrupt(token.interrupt)
|
||||||
|
break
|
||||||
|
end
|
||||||
|
|
||||||
|
token_output = render_token(token, context)
|
||||||
|
|
||||||
|
unless token.is_a?(Block) && token.blank?
|
||||||
|
output << token_output
|
||||||
|
end
|
||||||
|
rescue MemoryError => e
|
||||||
|
raise e
|
||||||
|
rescue ::StandardError => e
|
||||||
|
output << context.handle_error(e, token)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
output.join
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def render_token(token, context)
|
||||||
|
token_output = (token.respond_to?(:render) ? token.render(context) : token)
|
||||||
|
context.increment_used_resources(:render_length_current, token_output)
|
||||||
|
if context.resource_limits_reached?
|
||||||
|
context.resource_limits[:reached] = true
|
||||||
|
raise MemoryError.new("Memory limits exceeded".freeze)
|
||||||
|
end
|
||||||
|
token_output
|
||||||
|
end
|
||||||
|
|
||||||
|
def create_variable(token, options)
|
||||||
|
token.scan(ContentOfVariable) do |content|
|
||||||
|
markup = token.is_a?(Token) ? token.child(content.first) : content.first
|
||||||
|
return Variable.new(markup, options)
|
||||||
|
end
|
||||||
|
raise SyntaxError.new(options[:locale].t("errors.syntax.variable_termination".freeze, :token => token, :tag_end => VariableEnd.inspect))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -8,14 +8,16 @@ module Liquid
|
|||||||
#
|
#
|
||||||
class Condition #:nodoc:
|
class Condition #:nodoc:
|
||||||
@@operators = {
|
@@operators = {
|
||||||
'==' => lambda { |cond, left, right| cond.send(:equal_variables, left, right) },
|
'=='.freeze => lambda { |cond, left, right| cond.send(:equal_variables, left, right) },
|
||||||
'!=' => lambda { |cond, left, right| !cond.send(:equal_variables, left, right) },
|
'!='.freeze => lambda { |cond, left, right| !cond.send(:equal_variables, left, right) },
|
||||||
'<>' => lambda { |cond, left, right| !cond.send(:equal_variables, left, right) },
|
'<>'.freeze => lambda { |cond, left, right| !cond.send(:equal_variables, left, right) },
|
||||||
'<' => :<,
|
'<'.freeze => :<,
|
||||||
'>' => :>,
|
'>'.freeze => :>,
|
||||||
'>=' => :>=,
|
'>='.freeze => :>=,
|
||||||
'<=' => :<=,
|
'<='.freeze => :<=,
|
||||||
'contains' => lambda { |cond, left, right| left && right ? left.include?(right) : false }
|
'contains'.freeze => lambda { |cond, left, right|
|
||||||
|
left && right && left.respond_to?(:include?) ? left.include?(right) : false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def self.operators
|
def self.operators
|
||||||
@@ -26,7 +28,9 @@ module Liquid
|
|||||||
attr_accessor :left, :operator, :right
|
attr_accessor :left, :operator, :right
|
||||||
|
|
||||||
def initialize(left = nil, operator = nil, right = nil)
|
def initialize(left = nil, operator = nil, right = nil)
|
||||||
@left, @operator, @right = left, operator, right
|
@left = left
|
||||||
|
@operator = operator
|
||||||
|
@right = right
|
||||||
@child_relation = nil
|
@child_relation = nil
|
||||||
@child_condition = nil
|
@child_condition = nil
|
||||||
end
|
end
|
||||||
@@ -45,11 +49,13 @@ module Liquid
|
|||||||
end
|
end
|
||||||
|
|
||||||
def or(condition)
|
def or(condition)
|
||||||
@child_relation, @child_condition = :or, condition
|
@child_relation = :or
|
||||||
|
@child_condition = condition
|
||||||
end
|
end
|
||||||
|
|
||||||
def and(condition)
|
def and(condition)
|
||||||
@child_relation, @child_condition = :and, condition
|
@child_relation = :and
|
||||||
|
@child_condition = condition
|
||||||
end
|
end
|
||||||
|
|
||||||
def attach(attachment)
|
def attach(attachment)
|
||||||
@@ -61,7 +67,7 @@ module Liquid
|
|||||||
end
|
end
|
||||||
|
|
||||||
def inspect
|
def inspect
|
||||||
"#<Condition #{[@left, @operator, @right].compact.join(' ')}>"
|
"#<Condition #{[@left, @operator, @right].compact.join(' '.freeze)}>"
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
@@ -92,14 +98,19 @@ module Liquid
|
|||||||
# return this as the result.
|
# return this as the result.
|
||||||
return context[left] if op == nil
|
return context[left] if op == nil
|
||||||
|
|
||||||
left, right = context[left], context[right]
|
left = context[left]
|
||||||
|
right = context[right]
|
||||||
|
|
||||||
operation = self.class.operators[op] || raise(ArgumentError.new("Unknown operator #{op}"))
|
operation = self.class.operators[op] || raise(Liquid::ArgumentError.new("Unknown operator #{op}"))
|
||||||
|
|
||||||
if operation.respond_to?(:call)
|
if operation.respond_to?(:call)
|
||||||
operation.call(self, left, right)
|
operation.call(self, left, right)
|
||||||
elsif left.respond_to?(operation) and right.respond_to?(operation)
|
elsif left.respond_to?(operation) and right.respond_to?(operation)
|
||||||
left.send(operation, right)
|
begin
|
||||||
|
left.send(operation, right)
|
||||||
|
rescue ::ArgumentError => e
|
||||||
|
raise Liquid::ArgumentError.new(e.message)
|
||||||
|
end
|
||||||
else
|
else
|
||||||
nil
|
nil
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -14,16 +14,25 @@ module Liquid
|
|||||||
# context['bob'] #=> nil class Context
|
# context['bob'] #=> nil class Context
|
||||||
class Context
|
class Context
|
||||||
attr_reader :scopes, :errors, :registers, :environments, :resource_limits
|
attr_reader :scopes, :errors, :registers, :environments, :resource_limits
|
||||||
|
attr_accessor :exception_handler
|
||||||
|
|
||||||
def initialize(environments = {}, outer_scope = {}, registers = {}, rethrow_errors = false, resource_limits = {})
|
def initialize(environments = {}, outer_scope = {}, registers = {}, rethrow_errors = false, resource_limits = nil)
|
||||||
@environments = [environments].flatten
|
@environments = [environments].flatten
|
||||||
@scopes = [(outer_scope || {})]
|
@scopes = [(outer_scope || {})]
|
||||||
@registers = registers
|
@registers = registers
|
||||||
@errors = []
|
@errors = []
|
||||||
@rethrow_errors = rethrow_errors
|
@resource_limits = resource_limits || Template.default_resource_limits.dup
|
||||||
@resource_limits = (resource_limits || {}).merge!({ :render_score_current => 0, :assign_score_current => 0 })
|
@resource_limits[:render_score_current] = 0
|
||||||
|
@resource_limits[:assign_score_current] = 0
|
||||||
|
@parsed_expression = Hash.new{ |cache, markup| cache[markup] = Expression.parse(markup) }
|
||||||
squash_instance_assigns_with_environments
|
squash_instance_assigns_with_environments
|
||||||
|
|
||||||
|
@this_stack_used = false
|
||||||
|
|
||||||
|
if rethrow_errors
|
||||||
|
self.exception_handler = ->(e) { true }
|
||||||
|
end
|
||||||
|
|
||||||
@interrupts = []
|
@interrupts = []
|
||||||
@filters = []
|
@filters = []
|
||||||
end
|
end
|
||||||
@@ -52,26 +61,13 @@ module Liquid
|
|||||||
# for that
|
# for that
|
||||||
def add_filters(filters)
|
def add_filters(filters)
|
||||||
filters = [filters].flatten.compact
|
filters = [filters].flatten.compact
|
||||||
filters.each do |f|
|
@filters += filters
|
||||||
raise ArgumentError, "Expected module but got: #{f.class}" unless f.is_a?(Module)
|
@strainer = nil
|
||||||
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?
|
||||||
def has_interrupt?
|
def has_interrupt?
|
||||||
@interrupts.any?
|
!@interrupts.empty?
|
||||||
end
|
end
|
||||||
|
|
||||||
# push an interrupt to the stack. this interrupt is considered not handled.
|
# push an interrupt to the stack. this interrupt is considered not handled.
|
||||||
@@ -84,26 +80,25 @@ module Liquid
|
|||||||
@interrupts.pop
|
@interrupts.pop
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_error(e)
|
|
||||||
errors.push(e)
|
|
||||||
raise if @rethrow_errors
|
|
||||||
|
|
||||||
case e
|
def handle_error(e, token=nil)
|
||||||
when SyntaxError
|
if e.is_a?(Liquid::Error)
|
||||||
"Liquid syntax error: #{e.message}"
|
e.set_line_number_from_token(token)
|
||||||
else
|
|
||||||
"Liquid error: #{e.message}"
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
errors.push(e)
|
||||||
|
raise if exception_handler && exception_handler.call(e)
|
||||||
|
Liquid::Error.render(e)
|
||||||
end
|
end
|
||||||
|
|
||||||
def invoke(method, *args)
|
def invoke(method, *args)
|
||||||
strainer.invoke(method, *args)
|
strainer.invoke(method, *args).to_liquid
|
||||||
end
|
end
|
||||||
|
|
||||||
# Push new local scope on the stack. use <tt>Context#stack</tt> instead
|
# Push new local scope on the stack. use <tt>Context#stack</tt> instead
|
||||||
def push(new_scope={})
|
def push(new_scope={})
|
||||||
@scopes.unshift(new_scope)
|
@scopes.unshift(new_scope)
|
||||||
raise StackLevelError, "Nesting too deep" if @scopes.length > 100
|
raise StackLevelError, "Nesting too deep".freeze if @scopes.length > 100
|
||||||
end
|
end
|
||||||
|
|
||||||
# Merge a hash of variables in the current local scope
|
# Merge a hash of variables in the current local scope
|
||||||
@@ -125,11 +120,19 @@ module Liquid
|
|||||||
# end
|
# end
|
||||||
#
|
#
|
||||||
# context['var] #=> nil
|
# context['var] #=> nil
|
||||||
def stack(new_scope={})
|
def stack(new_scope=nil)
|
||||||
push(new_scope)
|
old_stack_used = @this_stack_used
|
||||||
|
if new_scope
|
||||||
|
push(new_scope)
|
||||||
|
@this_stack_used = true
|
||||||
|
else
|
||||||
|
@this_stack_used = false
|
||||||
|
end
|
||||||
|
|
||||||
yield
|
yield
|
||||||
ensure
|
ensure
|
||||||
pop
|
pop if @this_stack_used
|
||||||
|
@this_stack_used = old_stack_used
|
||||||
end
|
end
|
||||||
|
|
||||||
def clear_instance_assigns
|
def clear_instance_assigns
|
||||||
@@ -138,138 +141,71 @@ module Liquid
|
|||||||
|
|
||||||
# Only allow String, Numeric, Hash, Array, Proc, Boolean or <tt>Liquid::Drop</tt>
|
# Only allow String, Numeric, Hash, Array, Proc, Boolean or <tt>Liquid::Drop</tt>
|
||||||
def []=(key, value)
|
def []=(key, value)
|
||||||
|
unless @this_stack_used
|
||||||
|
@this_stack_used = true
|
||||||
|
push({})
|
||||||
|
end
|
||||||
@scopes[0][key] = value
|
@scopes[0][key] = value
|
||||||
end
|
end
|
||||||
|
|
||||||
def [](key)
|
# Look up variable, either resolve directly after considering the name. We can directly handle
|
||||||
resolve(key)
|
# Strings, digits, floats and booleans (true,false).
|
||||||
|
# If no match is made we lookup the variable in the current scope and
|
||||||
|
# later move up to the parent blocks to see if we can resolve the variable somewhere up the tree.
|
||||||
|
# Some special keywords return symbols. Those symbols are to be called on the rhs object in expressions
|
||||||
|
#
|
||||||
|
# Example:
|
||||||
|
# products == empty #=> products.empty?
|
||||||
|
def [](expression)
|
||||||
|
evaluate(@parsed_expression[expression])
|
||||||
end
|
end
|
||||||
|
|
||||||
def has_key?(key)
|
def has_key?(key)
|
||||||
resolve(key) != nil
|
self[key] != nil
|
||||||
|
end
|
||||||
|
|
||||||
|
def evaluate(object)
|
||||||
|
object.respond_to?(:evaluate) ? object.evaluate(self) : object
|
||||||
|
end
|
||||||
|
|
||||||
|
# Fetches an object starting at the local scope and then moving up the hierachy
|
||||||
|
def find_variable(key)
|
||||||
|
|
||||||
|
# This was changed from find() to find_index() because this is a very hot
|
||||||
|
# path and find_index() is optimized in MRI to reduce object allocation
|
||||||
|
index = @scopes.find_index { |s| s.has_key?(key) }
|
||||||
|
scope = @scopes[index] if index
|
||||||
|
|
||||||
|
variable = nil
|
||||||
|
|
||||||
|
if scope.nil?
|
||||||
|
@environments.each do |e|
|
||||||
|
variable = lookup_and_evaluate(e, key)
|
||||||
|
unless variable.nil?
|
||||||
|
scope = e
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
scope ||= @environments.last || @scopes.last
|
||||||
|
variable ||= lookup_and_evaluate(scope, key)
|
||||||
|
|
||||||
|
variable = variable.to_liquid
|
||||||
|
variable.context = self if variable.respond_to?(:context=)
|
||||||
|
|
||||||
|
return variable
|
||||||
|
end
|
||||||
|
|
||||||
|
def lookup_and_evaluate(obj, key)
|
||||||
|
if (value = obj[key]).is_a?(Proc) && obj.respond_to?(:[]=)
|
||||||
|
obj[key] = (value.arity == 0) ? value.call : value.call(self)
|
||||||
|
else
|
||||||
|
value
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
LITERALS = {
|
|
||||||
nil => nil, 'nil' => nil, 'null' => nil, '' => nil,
|
|
||||||
'true' => true,
|
|
||||||
'false' => false,
|
|
||||||
'blank' => :blank?,
|
|
||||||
'empty' => :empty?
|
|
||||||
}
|
|
||||||
|
|
||||||
# Look up variable, either resolve directly after considering the name. We can directly handle
|
|
||||||
# Strings, digits, floats and booleans (true,false).
|
|
||||||
# If no match is made we lookup the variable in the current scope and
|
|
||||||
# later move up to the parent blocks to see if we can resolve the variable somewhere up the tree.
|
|
||||||
# Some special keywords return symbols. Those symbols are to be called on the rhs object in expressions
|
|
||||||
#
|
|
||||||
# Example:
|
|
||||||
# products == empty #=> products.empty?
|
|
||||||
def resolve(key)
|
|
||||||
if LITERALS.key?(key)
|
|
||||||
LITERALS[key]
|
|
||||||
else
|
|
||||||
case key
|
|
||||||
when /^'(.*)'$/ # Single quoted strings
|
|
||||||
$1
|
|
||||||
when /^"(.*)"$/ # Double quoted strings
|
|
||||||
$1
|
|
||||||
when /^(-?\d+)$/ # Integer and floats
|
|
||||||
$1.to_i
|
|
||||||
when /^\((\S+)\.\.(\S+)\)$/ # Ranges
|
|
||||||
(resolve($1).to_i..resolve($2).to_i)
|
|
||||||
when /^(-?\d[\d\.]+)$/ # Floats
|
|
||||||
$1.to_f
|
|
||||||
else
|
|
||||||
variable(key)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# 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|
|
|
||||||
if variable = lookup_and_evaluate(e, key)
|
|
||||||
scope = e
|
|
||||||
break
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
scope ||= @environments.last || @scopes.last
|
|
||||||
variable ||= lookup_and_evaluate(scope, key)
|
|
||||||
|
|
||||||
variable = variable.to_liquid
|
|
||||||
variable.context = self if variable.respond_to?(:context=)
|
|
||||||
|
|
||||||
return variable
|
|
||||||
end
|
|
||||||
|
|
||||||
# Resolves namespaced queries gracefully.
|
|
||||||
#
|
|
||||||
# Example
|
|
||||||
# @context['hash'] = {"name" => 'tobi'}
|
|
||||||
# assert_equal 'tobi', @context['hash.name']
|
|
||||||
# assert_equal 'tobi', @context['hash["name"]']
|
|
||||||
def variable(markup)
|
|
||||||
parts = markup.scan(VariableParser)
|
|
||||||
square_bracketed = /^\[(.*)\]$/
|
|
||||||
|
|
||||||
first_part = parts.shift
|
|
||||||
|
|
||||||
if first_part =~ square_bracketed
|
|
||||||
first_part = resolve($1)
|
|
||||||
end
|
|
||||||
|
|
||||||
if object = find_variable(first_part)
|
|
||||||
|
|
||||||
parts.each do |part|
|
|
||||||
part = resolve($1) if part_resolved = (part =~ square_bracketed)
|
|
||||||
|
|
||||||
# If object is a hash- or array-like object we look for the
|
|
||||||
# presence of the key and if its available we return it
|
|
||||||
if object.respond_to?(:[]) and
|
|
||||||
((object.respond_to?(:has_key?) and object.has_key?(part)) or
|
|
||||||
(object.respond_to?(:fetch) and part.is_a?(Integer)))
|
|
||||||
|
|
||||||
# if its a proc we will replace the entry with the proc
|
|
||||||
res = lookup_and_evaluate(object, part)
|
|
||||||
object = res.to_liquid
|
|
||||||
|
|
||||||
# Some special cases. If the part wasn't in square brackets and
|
|
||||||
# no key with the same name was found we interpret following calls
|
|
||||||
# as commands and call them on the current object
|
|
||||||
elsif !part_resolved and object.respond_to?(part) and ['size', 'first', 'last'].include?(part)
|
|
||||||
|
|
||||||
object = object.send(part.intern).to_liquid
|
|
||||||
|
|
||||||
# No key was present with the desired value and it wasn't one of the directly supported
|
|
||||||
# keywords either. The only thing we got left is to return nil
|
|
||||||
else
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
|
|
||||||
# If we are dealing with a drop here we have to
|
|
||||||
object.context = self if object.respond_to?(:context=)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
object
|
|
||||||
end # variable
|
|
||||||
|
|
||||||
def lookup_and_evaluate(obj, key)
|
|
||||||
if (value = obj[key]).is_a?(Proc) && obj.respond_to?(:[]=)
|
|
||||||
obj[key] = (value.arity == 0) ? value.call : value.call(self)
|
|
||||||
else
|
|
||||||
value
|
|
||||||
end
|
|
||||||
end # lookup_and_evaluate
|
|
||||||
|
|
||||||
def squash_instance_assigns_with_environments
|
def squash_instance_assigns_with_environments
|
||||||
@scopes.last.each_key do |k|
|
@scopes.last.each_key do |k|
|
||||||
@environments.each do |env|
|
@environments.each do |env|
|
||||||
@@ -281,5 +217,4 @@ module Liquid
|
|||||||
end
|
end
|
||||||
end # squash_instance_assigns_with_environments
|
end # squash_instance_assigns_with_environments
|
||||||
end # Context
|
end # Context
|
||||||
|
|
||||||
end # Liquid
|
end # Liquid
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
module Liquid
|
module Liquid
|
||||||
class Document < Block
|
class Document < Block
|
||||||
# we don't need markup to open this block
|
def self.parse(tokens, options={})
|
||||||
def initialize(tokens, options = {})
|
# we don't need markup to open this block
|
||||||
@options = options
|
super(nil, nil, tokens, options)
|
||||||
parse(tokens)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# There isn't a real delimiter
|
# There isn't a real delimiter
|
||||||
|
|||||||
@@ -1,12 +1,60 @@
|
|||||||
module Liquid
|
module Liquid
|
||||||
class Error < ::StandardError; end
|
class Error < ::StandardError
|
||||||
|
attr_accessor :line_number
|
||||||
|
attr_accessor :markup_context
|
||||||
|
|
||||||
|
def to_s(with_prefix=true)
|
||||||
|
str = ""
|
||||||
|
str << message_prefix if with_prefix
|
||||||
|
str << super()
|
||||||
|
|
||||||
|
if markup_context
|
||||||
|
str << " "
|
||||||
|
str << markup_context
|
||||||
|
end
|
||||||
|
|
||||||
|
str
|
||||||
|
end
|
||||||
|
|
||||||
|
def set_line_number_from_token(token)
|
||||||
|
return unless token.respond_to?(:line_number)
|
||||||
|
return if self.line_number
|
||||||
|
self.line_number = token.line_number
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.render(e)
|
||||||
|
if e.is_a?(Liquid::Error)
|
||||||
|
e.to_s
|
||||||
|
else
|
||||||
|
"Liquid error: #{e.to_s}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def message_prefix
|
||||||
|
str = ""
|
||||||
|
if is_a?(SyntaxError)
|
||||||
|
str << "Liquid syntax error"
|
||||||
|
else
|
||||||
|
str << "Liquid error"
|
||||||
|
end
|
||||||
|
|
||||||
|
if line_number
|
||||||
|
str << " (line #{line_number})"
|
||||||
|
end
|
||||||
|
|
||||||
|
str << ": "
|
||||||
|
str
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
class ArgumentError < Error; end
|
class ArgumentError < Error; end
|
||||||
class ContextError < Error; end
|
class ContextError < Error; end
|
||||||
class FilterNotFound < Error; end
|
|
||||||
class FileSystemError < Error; end
|
class FileSystemError < Error; end
|
||||||
class StandardError < Error; end
|
class StandardError < Error; end
|
||||||
class SyntaxError < Error; end
|
class SyntaxError < Error; end
|
||||||
class StackLevelError < Error; end
|
class StackLevelError < Error; end
|
||||||
|
class TaintedError < Error; end
|
||||||
class MemoryError < Error; end
|
class MemoryError < Error; end
|
||||||
end
|
end
|
||||||
|
|||||||
33
lib/liquid/expression.rb
Normal file
33
lib/liquid/expression.rb
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
module Liquid
|
||||||
|
class Expression
|
||||||
|
LITERALS = {
|
||||||
|
nil => nil, 'nil'.freeze => nil, 'null'.freeze => nil, ''.freeze => nil,
|
||||||
|
'true'.freeze => true,
|
||||||
|
'false'.freeze => false,
|
||||||
|
'blank'.freeze => :blank?,
|
||||||
|
'empty'.freeze => :empty?
|
||||||
|
}
|
||||||
|
|
||||||
|
def self.parse(markup)
|
||||||
|
if LITERALS.key?(markup)
|
||||||
|
LITERALS[markup]
|
||||||
|
else
|
||||||
|
case markup
|
||||||
|
when /\A'(.*)'\z/m # Single quoted strings
|
||||||
|
$1
|
||||||
|
when /\A"(.*)"\z/m # Double quoted strings
|
||||||
|
$1
|
||||||
|
when /\A(-?\d+)\z/ # Integer and floats
|
||||||
|
$1.to_i
|
||||||
|
when /\A\((\S+)\.\.(\S+)\)\z/ # Ranges
|
||||||
|
RangeLookup.parse($1, $2)
|
||||||
|
when /\A(-?\d[\d\.]+)\z/ # Floats
|
||||||
|
$1.to_f
|
||||||
|
else
|
||||||
|
VariableLookup.parse(markup)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -44,7 +44,7 @@ module Liquid
|
|||||||
class LocalFileSystem
|
class LocalFileSystem
|
||||||
attr_accessor :root
|
attr_accessor :root
|
||||||
|
|
||||||
def initialize(root, pattern = "_%s.liquid")
|
def initialize(root, pattern = "_%s.liquid".freeze)
|
||||||
@root = root
|
@root = root
|
||||||
@pattern = pattern
|
@pattern = pattern
|
||||||
end
|
end
|
||||||
@@ -57,15 +57,15 @@ module Liquid
|
|||||||
end
|
end
|
||||||
|
|
||||||
def full_path(template_path)
|
def full_path(template_path)
|
||||||
raise FileSystemError, "Illegal template name '#{template_path}'" unless template_path =~ /^[^.\/][a-zA-Z0-9_\/]+$/
|
raise FileSystemError, "Illegal template name '#{template_path}'" unless template_path =~ /\A[^.\/][a-zA-Z0-9_\/]+\z/
|
||||||
|
|
||||||
full_path = if template_path.include?('/')
|
full_path = if template_path.include?('/'.freeze)
|
||||||
File.join(root, File.dirname(template_path), @pattern % File.basename(template_path))
|
File.join(root, File.dirname(template_path), @pattern % File.basename(template_path))
|
||||||
else
|
else
|
||||||
File.join(root, @pattern % template_path)
|
File.join(root, @pattern % template_path)
|
||||||
end
|
end
|
||||||
|
|
||||||
raise FileSystemError, "Illegal template path '#{File.expand_path(full_path)}'" unless File.expand_path(full_path) =~ /^#{File.expand_path(root)}/
|
raise FileSystemError, "Illegal template path '#{File.expand_path(full_path)}'" unless File.expand_path(full_path) =~ /\A#{File.expand_path(root)}/
|
||||||
|
|
||||||
full_path
|
full_path
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,74 +0,0 @@
|
|||||||
module Liquid
|
|
||||||
class TableRow < Block
|
|
||||||
Syntax = /(\w+)\s+in\s+(#{QuotedFragment}+)/o
|
|
||||||
|
|
||||||
def initialize(tag_name, markup, tokens)
|
|
||||||
if markup =~ Syntax
|
|
||||||
@variable_name = $1
|
|
||||||
@collection_name = $2
|
|
||||||
@attributes = {}
|
|
||||||
markup.scan(TagAttributes) do |key, value|
|
|
||||||
@attributes[key] = value
|
|
||||||
end
|
|
||||||
else
|
|
||||||
raise SyntaxError.new(options[:locale].t("errors.syntax.table_row"))
|
|
||||||
end
|
|
||||||
|
|
||||||
super
|
|
||||||
end
|
|
||||||
|
|
||||||
def render(context)
|
|
||||||
collection = context[@collection_name] or return ''
|
|
||||||
|
|
||||||
from = @attributes['offset'] ? context[@attributes['offset']].to_i : 0
|
|
||||||
to = @attributes['limit'] ? from + context[@attributes['limit']].to_i : nil
|
|
||||||
|
|
||||||
collection = Utils.slice_collection(collection, from, to)
|
|
||||||
|
|
||||||
length = collection.length
|
|
||||||
|
|
||||||
cols = context[@attributes['cols']].to_i
|
|
||||||
|
|
||||||
row = 1
|
|
||||||
col = 0
|
|
||||||
|
|
||||||
result = "<tr class=\"row1\">\n"
|
|
||||||
context.stack do
|
|
||||||
|
|
||||||
collection.each_with_index do |item, index|
|
|
||||||
context[@variable_name] = item
|
|
||||||
context['tablerowloop'] = {
|
|
||||||
'length' => length,
|
|
||||||
'index' => index + 1,
|
|
||||||
'index0' => index,
|
|
||||||
'col' => col + 1,
|
|
||||||
'col0' => col,
|
|
||||||
'index0' => index,
|
|
||||||
'rindex' => length - index,
|
|
||||||
'rindex0' => length - index - 1,
|
|
||||||
'first' => (index == 0),
|
|
||||||
'last' => (index == length - 1),
|
|
||||||
'col_first' => (col == 0),
|
|
||||||
'col_last' => (col == cols - 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
col += 1
|
|
||||||
|
|
||||||
result << "<td class=\"col#{col}\">" << render_all(@nodelist, context) << '</td>'
|
|
||||||
|
|
||||||
if col == cols and (index != length - 1)
|
|
||||||
col = 0
|
|
||||||
row += 1
|
|
||||||
result << "</tr>\n<tr class=\"row#{row}\">"
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
end
|
|
||||||
result << "</tr>\n"
|
|
||||||
result
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
Template.register_tag('tablerow', TableRow)
|
|
||||||
end
|
|
||||||
@@ -6,7 +6,7 @@ module Liquid
|
|||||||
|
|
||||||
class TranslationError < StandardError
|
class TranslationError < StandardError
|
||||||
end
|
end
|
||||||
|
|
||||||
attr_reader :path
|
attr_reader :path
|
||||||
|
|
||||||
def initialize(path = DEFAULT_LOCALE)
|
def initialize(path = DEFAULT_LOCALE)
|
||||||
@@ -31,7 +31,7 @@ module Liquid
|
|||||||
end
|
end
|
||||||
|
|
||||||
def deep_fetch_translation(name)
|
def deep_fetch_translation(name)
|
||||||
name.split('.').reduce(locale) do |level, cur|
|
name.split('.'.freeze).reduce(locale) do |level, cur|
|
||||||
level[cur] or raise TranslationError, "Translation for #{name} does not exist in locale #{path}"
|
level[cur] or raise TranslationError, "Translation for #{name} does not exist in locale #{path}"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ module Liquid
|
|||||||
attr_reader :message
|
attr_reader :message
|
||||||
|
|
||||||
def initialize(message=nil)
|
def initialize(message=nil)
|
||||||
@message = message || "interrupt"
|
@message = message || "interrupt".freeze
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -2,14 +2,14 @@ require "strscan"
|
|||||||
module Liquid
|
module Liquid
|
||||||
class Lexer
|
class Lexer
|
||||||
SPECIALS = {
|
SPECIALS = {
|
||||||
'|' => :pipe,
|
'|'.freeze => :pipe,
|
||||||
'.' => :dot,
|
'.'.freeze => :dot,
|
||||||
':' => :colon,
|
':'.freeze => :colon,
|
||||||
',' => :comma,
|
','.freeze => :comma,
|
||||||
'[' => :open_square,
|
'['.freeze => :open_square,
|
||||||
']' => :close_square,
|
']'.freeze => :close_square,
|
||||||
'(' => :open_round,
|
'('.freeze => :open_round,
|
||||||
')' => :close_round
|
')'.freeze => :close_round
|
||||||
}
|
}
|
||||||
IDENTIFIER = /[\w\-?!]+/
|
IDENTIFIER = /[\w\-?!]+/
|
||||||
SINGLE_STRING_LITERAL = /'[^\']*'/
|
SINGLE_STRING_LITERAL = /'[^\']*'/
|
||||||
|
|||||||
@@ -66,15 +66,16 @@ module Liquid
|
|||||||
str = ""
|
str = ""
|
||||||
# might be a keyword argument (identifier: expression)
|
# might be a keyword argument (identifier: expression)
|
||||||
if look(:id) && look(:colon, 1)
|
if look(:id) && look(:colon, 1)
|
||||||
str << consume << consume << ' '
|
str << consume << consume << ' '.freeze
|
||||||
end
|
end
|
||||||
|
|
||||||
str << expression
|
str << expression
|
||||||
|
str
|
||||||
end
|
end
|
||||||
|
|
||||||
def variable_signature
|
def variable_signature
|
||||||
str = consume(:id)
|
str = consume(:id)
|
||||||
if look(:open_square)
|
while look(:open_square)
|
||||||
str << consume
|
str << consume
|
||||||
str << expression
|
str << expression
|
||||||
str << consume(:close_square)
|
str << consume(:close_square)
|
||||||
|
|||||||
31
lib/liquid/parser_switching.rb
Normal file
31
lib/liquid/parser_switching.rb
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
module Liquid
|
||||||
|
module ParserSwitching
|
||||||
|
def parse_with_selected_parser(markup)
|
||||||
|
case @options[:error_mode] || Template.error_mode
|
||||||
|
when :strict then strict_parse_with_error_context(markup)
|
||||||
|
when :lax then lax_parse(markup)
|
||||||
|
when :warn
|
||||||
|
begin
|
||||||
|
return strict_parse_with_error_context(markup)
|
||||||
|
rescue SyntaxError => e
|
||||||
|
e.set_line_number_from_token(markup)
|
||||||
|
@warnings ||= []
|
||||||
|
@warnings << e
|
||||||
|
return lax_parse(markup)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
def strict_parse_with_error_context(markup)
|
||||||
|
strict_parse(markup)
|
||||||
|
rescue SyntaxError => e
|
||||||
|
e.markup_context = markup_context(markup)
|
||||||
|
raise e
|
||||||
|
end
|
||||||
|
|
||||||
|
def markup_context(markup)
|
||||||
|
"in \"#{markup.strip}\""
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
159
lib/liquid/profiler.rb
Normal file
159
lib/liquid/profiler.rb
Normal file
@@ -0,0 +1,159 @@
|
|||||||
|
module Liquid
|
||||||
|
|
||||||
|
# Profiler enables support for profiling template rendering to help track down performance issues.
|
||||||
|
#
|
||||||
|
# To enable profiling, pass the <tt>profile: true</tt> option to <tt>Liquid::Template.parse</tt>. Then, after
|
||||||
|
# <tt>Liquid::Template#render</tt> is called, the template object makes available an instance of this
|
||||||
|
# class via the <tt>Liquid::Template#profiler</tt> method.
|
||||||
|
#
|
||||||
|
# template = Liquid::Template.parse(template_content, profile: true)
|
||||||
|
# output = template.render
|
||||||
|
# profile = template.profiler
|
||||||
|
#
|
||||||
|
# This object contains all profiling information, containing information on what tags were rendered,
|
||||||
|
# where in the templates these tags live, and how long each tag took to render.
|
||||||
|
#
|
||||||
|
# This is a tree structure that is Enumerable all the way down, and keeps track of tags and rendering times
|
||||||
|
# inside of <tt>{% include %}</tt> tags.
|
||||||
|
#
|
||||||
|
# profile.each do |node|
|
||||||
|
# # Access to the token itself
|
||||||
|
# node.code
|
||||||
|
#
|
||||||
|
# # Which template and line number of this node.
|
||||||
|
# # If top level, this will be "<root>".
|
||||||
|
# node.partial
|
||||||
|
# node.line_number
|
||||||
|
#
|
||||||
|
# # Render time in seconds of this node
|
||||||
|
# node.render_time
|
||||||
|
#
|
||||||
|
# # If the template used {% include %}, this node will also have children.
|
||||||
|
# node.children.each do |child2|
|
||||||
|
# # ...
|
||||||
|
# end
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# Profiler also exposes the total time of the template's render in <tt>Liquid::Profiler#total_render_time</tt>.
|
||||||
|
#
|
||||||
|
# All render times are in seconds. There is a small performance hit when profiling is enabled.
|
||||||
|
#
|
||||||
|
class Profiler
|
||||||
|
include Enumerable
|
||||||
|
|
||||||
|
class Timing
|
||||||
|
attr_reader :code, :partial, :line_number, :children
|
||||||
|
|
||||||
|
def initialize(token, partial)
|
||||||
|
@code = token.respond_to?(:raw) ? token.raw : token
|
||||||
|
@partial = partial
|
||||||
|
@line_number = token.respond_to?(:line_number) ? token.line_number : nil
|
||||||
|
@children = []
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.start(token, partial)
|
||||||
|
new(token, partial).tap do |t|
|
||||||
|
t.start
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def start
|
||||||
|
@start_time = Time.now
|
||||||
|
end
|
||||||
|
|
||||||
|
def finish
|
||||||
|
@end_time = Time.now
|
||||||
|
end
|
||||||
|
|
||||||
|
def render_time
|
||||||
|
@end_time - @start_time
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.profile_token_render(token)
|
||||||
|
if Profiler.current_profile && token.respond_to?(:render)
|
||||||
|
Profiler.current_profile.start_token(token)
|
||||||
|
output = yield
|
||||||
|
Profiler.current_profile.end_token(token)
|
||||||
|
output
|
||||||
|
else
|
||||||
|
yield
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.profile_children(template_name)
|
||||||
|
if Profiler.current_profile
|
||||||
|
Profiler.current_profile.push_partial(template_name)
|
||||||
|
output = yield
|
||||||
|
Profiler.current_profile.pop_partial
|
||||||
|
output
|
||||||
|
else
|
||||||
|
yield
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.current_profile
|
||||||
|
Thread.current[:liquid_profiler]
|
||||||
|
end
|
||||||
|
|
||||||
|
def initialize
|
||||||
|
@partial_stack = ["<root>"]
|
||||||
|
|
||||||
|
@root_timing = Timing.new("", current_partial)
|
||||||
|
@timing_stack = [@root_timing]
|
||||||
|
|
||||||
|
@render_start_at = Time.now
|
||||||
|
@render_end_at = @render_start_at
|
||||||
|
end
|
||||||
|
|
||||||
|
def start
|
||||||
|
Thread.current[:liquid_profiler] = self
|
||||||
|
@render_start_at = Time.now
|
||||||
|
end
|
||||||
|
|
||||||
|
def stop
|
||||||
|
Thread.current[:liquid_profiler] = nil
|
||||||
|
@render_end_at = Time.now
|
||||||
|
end
|
||||||
|
|
||||||
|
def total_render_time
|
||||||
|
@render_end_at - @render_start_at
|
||||||
|
end
|
||||||
|
|
||||||
|
def each(&block)
|
||||||
|
@root_timing.children.each(&block)
|
||||||
|
end
|
||||||
|
|
||||||
|
def [](idx)
|
||||||
|
@root_timing.children[idx]
|
||||||
|
end
|
||||||
|
|
||||||
|
def length
|
||||||
|
@root_timing.children.length
|
||||||
|
end
|
||||||
|
|
||||||
|
def start_token(token)
|
||||||
|
@timing_stack.push(Timing.start(token, current_partial))
|
||||||
|
end
|
||||||
|
|
||||||
|
def end_token(token)
|
||||||
|
timing = @timing_stack.pop
|
||||||
|
timing.finish
|
||||||
|
|
||||||
|
@timing_stack.last.children << timing
|
||||||
|
end
|
||||||
|
|
||||||
|
def current_partial
|
||||||
|
@partial_stack.last
|
||||||
|
end
|
||||||
|
|
||||||
|
def push_partial(partial_name)
|
||||||
|
@partial_stack.push(partial_name)
|
||||||
|
end
|
||||||
|
|
||||||
|
def pop_partial
|
||||||
|
@partial_stack.pop
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
23
lib/liquid/profiler/hooks.rb
Normal file
23
lib/liquid/profiler/hooks.rb
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
module Liquid
|
||||||
|
class Block < Tag
|
||||||
|
def render_token_with_profiling(token, context)
|
||||||
|
Profiler.profile_token_render(token) do
|
||||||
|
render_token_without_profiling(token, context)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
alias_method :render_token_without_profiling, :render_token
|
||||||
|
alias_method :render_token, :render_token_with_profiling
|
||||||
|
end
|
||||||
|
|
||||||
|
class Include < Tag
|
||||||
|
def render_with_profiling(context)
|
||||||
|
Profiler.profile_children(@template_name) do
|
||||||
|
render_without_profiling(context)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
alias_method :render_without_profiling, :render
|
||||||
|
alias_method :render, :render_with_profiling
|
||||||
|
end
|
||||||
|
end
|
||||||
22
lib/liquid/range_lookup.rb
Normal file
22
lib/liquid/range_lookup.rb
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
module Liquid
|
||||||
|
class RangeLookup
|
||||||
|
def self.parse(start_markup, end_markup)
|
||||||
|
start_obj = Expression.parse(start_markup)
|
||||||
|
end_obj = Expression.parse(end_markup)
|
||||||
|
if start_obj.respond_to?(:evaluate) || end_obj.respond_to?(:evaluate)
|
||||||
|
new(start_obj, end_obj)
|
||||||
|
else
|
||||||
|
start_obj.to_i..end_obj.to_i
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def initialize(start_obj, end_obj)
|
||||||
|
@start_obj = start_obj
|
||||||
|
@end_obj = end_obj
|
||||||
|
end
|
||||||
|
|
||||||
|
def evaluate(context)
|
||||||
|
context.evaluate(@start_obj).to_i..context.evaluate(@end_obj).to_i
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -4,12 +4,17 @@ require 'bigdecimal'
|
|||||||
module Liquid
|
module Liquid
|
||||||
|
|
||||||
module StandardFilters
|
module StandardFilters
|
||||||
HTML_ESCAPE = { '&' => '&', '>' => '>', '<' => '<', '"' => '"', "'" => ''' }
|
HTML_ESCAPE = {
|
||||||
|
'&'.freeze => '&'.freeze,
|
||||||
|
'>'.freeze => '>'.freeze,
|
||||||
|
'<'.freeze => '<'.freeze,
|
||||||
|
'"'.freeze => '"'.freeze,
|
||||||
|
"'".freeze => '''.freeze
|
||||||
|
}
|
||||||
HTML_ESCAPE_ONCE_REGEXP = /["><']|&(?!([a-zA-Z]+|(#\d+));)/
|
HTML_ESCAPE_ONCE_REGEXP = /["><']|&(?!([a-zA-Z]+|(#\d+));)/
|
||||||
|
|
||||||
# Return the size of an array or of an string
|
# Return the size of an array or of an string
|
||||||
def size(input)
|
def size(input)
|
||||||
|
|
||||||
input.respond_to?(:size) ? input.size : 0
|
input.respond_to?(:size) ? input.size : 0
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -29,29 +34,43 @@ module Liquid
|
|||||||
end
|
end
|
||||||
|
|
||||||
def escape(input)
|
def escape(input)
|
||||||
CGI.escapeHTML(input) rescue input
|
CGI.escapeHTML(input).untaint rescue input
|
||||||
end
|
end
|
||||||
|
alias_method :h, :escape
|
||||||
|
|
||||||
def escape_once(input)
|
def escape_once(input)
|
||||||
input.to_s.gsub(HTML_ESCAPE_ONCE_REGEXP, HTML_ESCAPE)
|
input.to_s.gsub(HTML_ESCAPE_ONCE_REGEXP, HTML_ESCAPE)
|
||||||
end
|
end
|
||||||
|
|
||||||
alias_method :h, :escape
|
def url_encode(input)
|
||||||
|
CGI.escape(input) rescue input
|
||||||
|
end
|
||||||
|
|
||||||
|
def slice(input, offset, length=nil)
|
||||||
|
offset = Integer(offset)
|
||||||
|
length = length ? Integer(length) : 1
|
||||||
|
|
||||||
|
if input.is_a?(Array)
|
||||||
|
input.slice(offset, length) || []
|
||||||
|
else
|
||||||
|
input.to_s.slice(offset, length) || ''
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
# Truncate a string down to x characters
|
# Truncate a string down to x characters
|
||||||
def truncate(input, length = 50, truncate_string = "...")
|
def truncate(input, length = 50, truncate_string = "...".freeze)
|
||||||
if input.nil? then return end
|
if input.nil? then return end
|
||||||
l = length.to_i - truncate_string.length
|
l = length.to_i - truncate_string.length
|
||||||
l = 0 if l < 0
|
l = 0 if l < 0
|
||||||
input.length > length.to_i ? input[0...l] + truncate_string : input
|
input.length > length.to_i ? input[0...l] + truncate_string : input
|
||||||
end
|
end
|
||||||
|
|
||||||
def truncatewords(input, words = 15, truncate_string = "...")
|
def truncatewords(input, words = 15, truncate_string = "...".freeze)
|
||||||
if input.nil? then return end
|
if input.nil? then return end
|
||||||
wordlist = input.to_s.split
|
wordlist = input.to_s.split
|
||||||
l = words.to_i - 1
|
l = words.to_i - 1
|
||||||
l = 0 if l < 0
|
l = 0 if l < 0
|
||||||
wordlist.length > l ? wordlist[0..l].join(" ") + truncate_string : input
|
wordlist.length > l ? wordlist[0..l].join(" ".freeze) + truncate_string : input
|
||||||
end
|
end
|
||||||
|
|
||||||
# Split input string into an array of substrings separated by given pattern.
|
# Split input string into an array of substrings separated by given pattern.
|
||||||
@@ -60,7 +79,7 @@ module Liquid
|
|||||||
# <div class="summary">{{ post | split '//' | first }}</div>
|
# <div class="summary">{{ post | split '//' | first }}</div>
|
||||||
#
|
#
|
||||||
def split(input, pattern)
|
def split(input, pattern)
|
||||||
input.split(pattern)
|
input.to_s.split(pattern)
|
||||||
end
|
end
|
||||||
|
|
||||||
def strip(input)
|
def strip(input)
|
||||||
@@ -76,44 +95,56 @@ module Liquid
|
|||||||
end
|
end
|
||||||
|
|
||||||
def strip_html(input)
|
def strip_html(input)
|
||||||
input.to_s.gsub(/<script.*?<\/script>/m, '').gsub(/<!--.*?-->/m, '').gsub(/<style.*?<\/style>/m, '').gsub(/<.*?>/m, '')
|
empty = ''.freeze
|
||||||
|
input.to_s.gsub(/<script.*?<\/script>/m, empty).gsub(/<!--.*?-->/m, empty).gsub(/<style.*?<\/style>/m, empty).gsub(/<.*?>/m, empty)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Remove all newlines from the string
|
# Remove all newlines from the string
|
||||||
def strip_newlines(input)
|
def strip_newlines(input)
|
||||||
input.to_s.gsub(/\r?\n/, '')
|
input.to_s.gsub(/\r?\n/, ''.freeze)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Join elements of the array with certain character between them
|
# Join elements of the array with certain character between them
|
||||||
def join(input, glue = ' ')
|
def join(input, glue = ' '.freeze)
|
||||||
[input].flatten.join(glue)
|
InputIterator.new(input).join(glue)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Sort elements of the array
|
# Sort elements of the array
|
||||||
# provide optional property with which to sort an array of hashes or drops
|
# provide optional property with which to sort an array of hashes or drops
|
||||||
def sort(input, property = nil)
|
def sort(input, property = nil)
|
||||||
ary = flatten_if_necessary(input)
|
ary = InputIterator.new(input)
|
||||||
if property.nil?
|
if property.nil?
|
||||||
ary.sort
|
ary.sort
|
||||||
elsif ary.first.respond_to?('[]') and !ary.first[property].nil?
|
elsif ary.first.respond_to?(:[]) && !ary.first[property].nil?
|
||||||
ary.sort {|a,b| a[property] <=> b[property] }
|
ary.sort {|a,b| a[property] <=> b[property] }
|
||||||
elsif ary.first.respond_to?(property)
|
elsif ary.first.respond_to?(property)
|
||||||
ary.sort {|a,b| a.send(property) <=> b.send(property) }
|
ary.sort {|a,b| a.send(property) <=> b.send(property) }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Remove duplicate elements from an array
|
||||||
|
# provide optional property with which to determine uniqueness
|
||||||
|
def uniq(input, property = nil)
|
||||||
|
ary = InputIterator.new(input)
|
||||||
|
if property.nil?
|
||||||
|
input.uniq
|
||||||
|
elsif input.first.respond_to?(:[])
|
||||||
|
input.uniq{ |a| a[property] }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
# Reverse the elements of an array
|
# Reverse the elements of an array
|
||||||
def reverse(input)
|
def reverse(input)
|
||||||
ary = [input].flatten
|
ary = InputIterator.new(input)
|
||||||
ary.reverse
|
ary.reverse
|
||||||
end
|
end
|
||||||
|
|
||||||
# map/collect on a given property
|
# map/collect on a given property
|
||||||
def map(input, property)
|
def map(input, property)
|
||||||
flatten_if_necessary(input).map do |e|
|
InputIterator.new(input).map do |e|
|
||||||
e = e.call if e.is_a?(Proc)
|
e = e.call if e.is_a?(Proc)
|
||||||
|
|
||||||
if property == "to_liquid"
|
if property == "to_liquid".freeze
|
||||||
e
|
e
|
||||||
elsif e.respond_to?(:[])
|
elsif e.respond_to?(:[])
|
||||||
e[property]
|
e[property]
|
||||||
@@ -122,23 +153,23 @@ module Liquid
|
|||||||
end
|
end
|
||||||
|
|
||||||
# Replace occurrences of a string with another
|
# Replace occurrences of a string with another
|
||||||
def replace(input, string, replacement = '')
|
def replace(input, string, replacement = ''.freeze)
|
||||||
input.to_s.gsub(string, replacement.to_s)
|
input.to_s.gsub(string, replacement.to_s)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Replace the first occurrences of a string with another
|
# Replace the first occurrences of a string with another
|
||||||
def replace_first(input, string, replacement = '')
|
def replace_first(input, string, replacement = ''.freeze)
|
||||||
input.to_s.sub(string, replacement.to_s)
|
input.to_s.sub(string, replacement.to_s)
|
||||||
end
|
end
|
||||||
|
|
||||||
# remove a substring
|
# remove a substring
|
||||||
def remove(input, string)
|
def remove(input, string)
|
||||||
input.to_s.gsub(string, '')
|
input.to_s.gsub(string, ''.freeze)
|
||||||
end
|
end
|
||||||
|
|
||||||
# remove the first occurrences of a substring
|
# remove the first occurrences of a substring
|
||||||
def remove_first(input, string)
|
def remove_first(input, string)
|
||||||
input.to_s.sub(string, '')
|
input.to_s.sub(string, ''.freeze)
|
||||||
end
|
end
|
||||||
|
|
||||||
# add one string to another
|
# add one string to another
|
||||||
@@ -153,10 +184,10 @@ module Liquid
|
|||||||
|
|
||||||
# Add <br /> tags in front of all newlines in input string
|
# Add <br /> tags in front of all newlines in input string
|
||||||
def newline_to_br(input)
|
def newline_to_br(input)
|
||||||
input.to_s.gsub(/\n/, "<br />\n")
|
input.to_s.gsub(/\n/, "<br />\n".freeze)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Reformat a date
|
# Reformat a date using Ruby's core Time#strftime( string ) -> string
|
||||||
#
|
#
|
||||||
# %a - The abbreviated weekday name (``Sun'')
|
# %a - The abbreviated weekday name (``Sun'')
|
||||||
# %A - The full weekday name (``Sunday'')
|
# %A - The full weekday name (``Sunday'')
|
||||||
@@ -170,6 +201,7 @@ module Liquid
|
|||||||
# %m - Month of the year (01..12)
|
# %m - Month of the year (01..12)
|
||||||
# %M - Minute of the hour (00..59)
|
# %M - Minute of the hour (00..59)
|
||||||
# %p - Meridian indicator (``AM'' or ``PM'')
|
# %p - Meridian indicator (``AM'' or ``PM'')
|
||||||
|
# %s - Number of seconds since 1970-01-01 00:00:00 UTC.
|
||||||
# %S - Second of the minute (00..60)
|
# %S - Second of the minute (00..60)
|
||||||
# %U - Week number of the current year,
|
# %U - Week number of the current year,
|
||||||
# starting with the first Sunday as the first
|
# starting with the first Sunday as the first
|
||||||
@@ -184,34 +216,14 @@ module Liquid
|
|||||||
# %Y - Year with century
|
# %Y - Year with century
|
||||||
# %Z - Time zone name
|
# %Z - Time zone name
|
||||||
# %% - Literal ``%'' character
|
# %% - Literal ``%'' character
|
||||||
|
#
|
||||||
|
# See also: http://www.ruby-doc.org/core/Time.html#method-i-strftime
|
||||||
def date(input, format)
|
def date(input, format)
|
||||||
|
return input if format.to_s.empty?
|
||||||
|
|
||||||
if format.to_s.empty?
|
return input unless date = to_date(input)
|
||||||
return input.to_s
|
|
||||||
end
|
|
||||||
|
|
||||||
if ((input.is_a?(String) && !/^\d+$/.match(input.to_s).nil?) || input.is_a?(Integer)) && input.to_i > 0
|
date.strftime(format.to_s)
|
||||||
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
|
|
||||||
|
|
||||||
if date.respond_to?(:strftime)
|
|
||||||
date.strftime(format.to_s)
|
|
||||||
else
|
|
||||||
input
|
|
||||||
end
|
|
||||||
rescue
|
|
||||||
input
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# Get the first element of the passed in array
|
# Get the first element of the passed in array
|
||||||
@@ -256,24 +268,28 @@ module Liquid
|
|||||||
apply_operation(input, operand, :%)
|
apply_operation(input, operand, :%)
|
||||||
end
|
end
|
||||||
|
|
||||||
def default(input, default_value = "")
|
def round(input, n = 0)
|
||||||
|
result = to_number(input).round(to_number(n))
|
||||||
|
result = result.to_f if result.is_a?(BigDecimal)
|
||||||
|
result = result.to_i if n == 0
|
||||||
|
result
|
||||||
|
end
|
||||||
|
|
||||||
|
def ceil(input)
|
||||||
|
to_number(input).ceil.to_i
|
||||||
|
end
|
||||||
|
|
||||||
|
def floor(input)
|
||||||
|
to_number(input).floor.to_i
|
||||||
|
end
|
||||||
|
|
||||||
|
def default(input, default_value = "".freeze)
|
||||||
is_blank = input.respond_to?(:empty?) ? input.empty? : !input
|
is_blank = input.respond_to?(:empty?) ? input.empty? : !input
|
||||||
is_blank ? default_value : input
|
is_blank ? default_value : input
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def flatten_if_necessary(input)
|
|
||||||
ary = if input.is_a?(Array)
|
|
||||||
input.flatten
|
|
||||||
elsif input.is_a?(Enumerable) && !input.is_a?(Hash)
|
|
||||||
input
|
|
||||||
else
|
|
||||||
[input].flatten
|
|
||||||
end
|
|
||||||
ary.map{ |e| e.respond_to?(:to_liquid) ? e.to_liquid : e }
|
|
||||||
end
|
|
||||||
|
|
||||||
def to_number(obj)
|
def to_number(obj)
|
||||||
case obj
|
case obj
|
||||||
when Float
|
when Float
|
||||||
@@ -281,16 +297,63 @@ module Liquid
|
|||||||
when Numeric
|
when Numeric
|
||||||
obj
|
obj
|
||||||
when String
|
when String
|
||||||
(obj.strip =~ /^\d+\.\d+$/) ? BigDecimal.new(obj) : obj.to_i
|
(obj.strip =~ /\A\d+\.\d+\z/) ? BigDecimal.new(obj) : obj.to_i
|
||||||
else
|
else
|
||||||
0
|
0
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def to_date(obj)
|
||||||
|
return obj if obj.respond_to?(:strftime)
|
||||||
|
|
||||||
|
case obj
|
||||||
|
when 'now'.freeze, 'today'.freeze
|
||||||
|
Time.now
|
||||||
|
when /\A\d+\z/, Integer
|
||||||
|
Time.at(obj.to_i)
|
||||||
|
when String
|
||||||
|
Time.parse(obj)
|
||||||
|
else
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
rescue ::ArgumentError
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
|
||||||
def apply_operation(input, operand, operation)
|
def apply_operation(input, operand, operation)
|
||||||
result = to_number(input).send(operation, to_number(operand))
|
result = to_number(input).send(operation, to_number(operand))
|
||||||
result.is_a?(BigDecimal) ? result.to_f : result
|
result.is_a?(BigDecimal) ? result.to_f : result
|
||||||
end
|
end
|
||||||
|
|
||||||
|
class InputIterator
|
||||||
|
include Enumerable
|
||||||
|
|
||||||
|
def initialize(input)
|
||||||
|
@input = if input.is_a?(Array)
|
||||||
|
input.flatten
|
||||||
|
elsif input.is_a?(Hash)
|
||||||
|
[input]
|
||||||
|
elsif input.is_a?(Enumerable)
|
||||||
|
input
|
||||||
|
else
|
||||||
|
Array(input)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def join(glue)
|
||||||
|
to_a.join(glue)
|
||||||
|
end
|
||||||
|
|
||||||
|
def reverse
|
||||||
|
reverse_each.to_a
|
||||||
|
end
|
||||||
|
|
||||||
|
def each
|
||||||
|
@input.each do |e|
|
||||||
|
yield(e.respond_to?(:to_liquid) ? e.to_liquid : e)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
Template.register_filter(StandardFilters)
|
Template.register_filter(StandardFilters)
|
||||||
|
|||||||
@@ -8,12 +8,13 @@ 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:
|
||||||
@@filters = []
|
@@global_strainer = Class.new(Strainer) do
|
||||||
@@known_filters = Set.new
|
@filter_methods = Set.new
|
||||||
@@known_methods = Set.new
|
end
|
||||||
@@strainer_class_cache = Hash.new do |hash, filters|
|
@@strainer_class_cache = Hash.new do |hash, filters|
|
||||||
hash[filters] = Class.new(Strainer) do
|
hash[filters] = Class.new(@@global_strainer) do
|
||||||
filters.each { |f| include f }
|
@filter_methods = @@global_strainer.filter_methods.dup
|
||||||
|
filters.each { |f| add_filter(f) }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -21,33 +22,32 @@ module Liquid
|
|||||||
@context = context
|
@context = context
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.global_filter(filter)
|
def self.filter_methods
|
||||||
raise ArgumentError, "Passed filter is not a module" unless filter.is_a?(Module)
|
@filter_methods
|
||||||
add_known_filter(filter)
|
|
||||||
@@filters << filter unless @@filters.include?(filter)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.add_known_filter(filter)
|
def self.add_filter(filter)
|
||||||
unless @@known_filters.include?(filter)
|
raise ArgumentError, "Expected module but got: #{f.class}" unless filter.is_a?(Module)
|
||||||
@@method_blacklist ||= Set.new(Strainer.instance_methods.map(&:to_s))
|
unless self.class.include?(filter)
|
||||||
new_methods = filter.instance_methods.map(&:to_s)
|
self.send(:include, filter)
|
||||||
new_methods.reject!{ |m| @@method_blacklist.include?(m) }
|
@filter_methods.merge(filter.public_instance_methods.map(&:to_s))
|
||||||
@@known_methods.merge(new_methods)
|
|
||||||
@@known_filters.add(filter)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.strainer_class_cache
|
def self.global_filter(filter)
|
||||||
@@strainer_class_cache
|
@@global_strainer.add_filter(filter)
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.invokable?(method)
|
||||||
|
@filter_methods.include?(method.to_s)
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.create(context, filters = [])
|
def self.create(context, filters = [])
|
||||||
filters = @@filters + filters
|
@@strainer_class_cache[filters].new(context)
|
||||||
strainer_class_cache[filters].new(context)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def invoke(method, *args)
|
def invoke(method, *args)
|
||||||
if invokable?(method)
|
if self.class.invokable?(method)
|
||||||
send(method, *args)
|
send(method, *args)
|
||||||
else
|
else
|
||||||
args.first
|
args.first
|
||||||
@@ -55,9 +55,5 @@ 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
|
||||||
|
|||||||
@@ -1,60 +1,42 @@
|
|||||||
module Liquid
|
module Liquid
|
||||||
class Tag
|
class Tag
|
||||||
attr_accessor :options
|
attr_accessor :options, :line_number
|
||||||
attr_reader :nodelist, :warnings
|
attr_reader :nodelist, :warnings
|
||||||
|
include ParserSwitching
|
||||||
|
|
||||||
def self.new_with_options(tag_name, markup, tokens, options)
|
class << self
|
||||||
# Forgive me Matz for I have sinned. I know this code is weird
|
def parse(tag_name, markup, tokens, options)
|
||||||
# but it was necessary to maintain API compatibility.
|
tag = new(tag_name, markup, options)
|
||||||
new_tag = self.allocate
|
tag.parse(tokens)
|
||||||
new_tag.options = options
|
tag
|
||||||
new_tag.send(:initialize, tag_name, markup, tokens)
|
end
|
||||||
new_tag
|
|
||||||
|
private :new
|
||||||
end
|
end
|
||||||
|
|
||||||
def initialize(tag_name, markup, tokens)
|
def initialize(tag_name, markup, options)
|
||||||
@tag_name = tag_name
|
@tag_name = tag_name
|
||||||
@markup = markup
|
@markup = markup
|
||||||
@options ||= {} # needs || because might be set before initialize
|
@options = options
|
||||||
parse(tokens)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def parse(tokens)
|
def parse(tokens)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def raw
|
||||||
|
"#{@tag_name} #{@markup}"
|
||||||
|
end
|
||||||
|
|
||||||
def name
|
def name
|
||||||
self.class.name.downcase
|
self.class.name.downcase
|
||||||
end
|
end
|
||||||
|
|
||||||
def render(context)
|
def render(context)
|
||||||
''
|
''.freeze
|
||||||
end
|
end
|
||||||
|
|
||||||
def blank?
|
def blank?
|
||||||
@blank || false
|
false
|
||||||
end
|
end
|
||||||
|
end
|
||||||
def parse_with_selected_parser(markup)
|
end
|
||||||
case @options[:error_mode] || Template.error_mode
|
|
||||||
when :strict then strict_parse_with_error_context(markup)
|
|
||||||
when :lax then lax_parse(markup)
|
|
||||||
when :warn
|
|
||||||
begin
|
|
||||||
return strict_parse_with_error_context(markup)
|
|
||||||
rescue SyntaxError => e
|
|
||||||
@warnings ||= []
|
|
||||||
@warnings << e
|
|
||||||
return lax_parse(markup)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
def strict_parse_with_error_context(markup)
|
|
||||||
strict_parse(markup)
|
|
||||||
rescue SyntaxError => e
|
|
||||||
e.message << " in \"#{markup.strip}\""
|
|
||||||
raise e
|
|
||||||
end
|
|
||||||
end # Tag
|
|
||||||
end # Liquid
|
|
||||||
|
|||||||
@@ -9,24 +9,24 @@ module Liquid
|
|||||||
# {{ foo }}
|
# {{ foo }}
|
||||||
#
|
#
|
||||||
class Assign < Tag
|
class Assign < Tag
|
||||||
Syntax = /(#{VariableSignature}+)\s*=\s*(.*)\s*/o
|
Syntax = /(#{VariableSignature}+)\s*=\s*(.*)\s*/om
|
||||||
|
|
||||||
def initialize(tag_name, markup, tokens)
|
def initialize(tag_name, markup, options)
|
||||||
|
super
|
||||||
if markup =~ Syntax
|
if markup =~ Syntax
|
||||||
@to = $1
|
@to = $1
|
||||||
@from = Variable.new($2)
|
@from = Variable.new($2,options)
|
||||||
|
@from.line_number = line_number
|
||||||
else
|
else
|
||||||
raise SyntaxError.new options[:locale].t("errors.syntax.assign")
|
raise SyntaxError.new options[:locale].t("errors.syntax.assign".freeze)
|
||||||
end
|
end
|
||||||
|
|
||||||
super
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def render(context)
|
def render(context)
|
||||||
val = @from.render(context)
|
val = @from.render(context)
|
||||||
context.scopes.last[@to] = val
|
context.scopes.last[@to] = val
|
||||||
context.increment_used_resources(:assign_score_current, val)
|
context.increment_used_resources(:assign_score_current, val)
|
||||||
''
|
''.freeze
|
||||||
end
|
end
|
||||||
|
|
||||||
def blank?
|
def blank?
|
||||||
@@ -34,5 +34,5 @@ module Liquid
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
Template.register_tag('assign', Assign)
|
Template.register_tag('assign'.freeze, Assign)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -17,5 +17,5 @@ module Liquid
|
|||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
Template.register_tag('break', Break)
|
Template.register_tag('break'.freeze, Break)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
module Liquid
|
module Liquid
|
||||||
|
|
||||||
# Capture stores the result of a block into a variable without rendering it inplace.
|
# Capture stores the result of a block into a variable without rendering it inplace.
|
||||||
#
|
#
|
||||||
# {% capture heading %}
|
# {% capture heading %}
|
||||||
@@ -12,23 +11,22 @@ module Liquid
|
|||||||
# in a sidebar or footer.
|
# in a sidebar or footer.
|
||||||
#
|
#
|
||||||
class Capture < Block
|
class Capture < Block
|
||||||
Syntax = /(\w+)/
|
Syntax = /(#{VariableSignature}+)/o
|
||||||
|
|
||||||
def initialize(tag_name, markup, tokens)
|
def initialize(tag_name, markup, options)
|
||||||
|
super
|
||||||
if markup =~ Syntax
|
if markup =~ Syntax
|
||||||
@to = $1
|
@to = $1
|
||||||
else
|
else
|
||||||
raise SyntaxError.new(options[:locale].t("errors.syntax.capture"))
|
raise SyntaxError.new(options[:locale].t("errors.syntax.capture"))
|
||||||
end
|
end
|
||||||
|
|
||||||
super
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def render(context)
|
def render(context)
|
||||||
output = super
|
output = super
|
||||||
context.scopes.last[@to] = output
|
context.scopes.last[@to] = output
|
||||||
context.increment_used_resources(:assign_score_current, output)
|
context.increment_used_resources(:assign_score_current, output)
|
||||||
''
|
''.freeze
|
||||||
end
|
end
|
||||||
|
|
||||||
def blank?
|
def blank?
|
||||||
@@ -36,5 +34,5 @@ module Liquid
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
Template.register_tag('capture', Capture)
|
Template.register_tag('capture'.freeze, Capture)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,30 +1,29 @@
|
|||||||
module Liquid
|
module Liquid
|
||||||
class Case < Block
|
class Case < Block
|
||||||
Syntax = /(#{QuotedFragment})/o
|
Syntax = /(#{QuotedFragment})/o
|
||||||
WhenSyntax = /(#{QuotedFragment})(?:(?:\s+or\s+|\s*\,\s*)(#{QuotedFragment}.*))?/o
|
WhenSyntax = /(#{QuotedFragment})(?:(?:\s+or\s+|\s*\,\s*)(#{QuotedFragment}.*))?/om
|
||||||
|
|
||||||
def initialize(tag_name, markup, tokens)
|
def initialize(tag_name, markup, options)
|
||||||
|
super
|
||||||
@blocks = []
|
@blocks = []
|
||||||
|
|
||||||
if markup =~ Syntax
|
if markup =~ Syntax
|
||||||
@left = $1
|
@left = $1
|
||||||
else
|
else
|
||||||
raise SyntaxError.new(options[:locale].t("errors.syntax.case"))
|
raise SyntaxError.new(options[:locale].t("errors.syntax.case".freeze))
|
||||||
end
|
end
|
||||||
|
|
||||||
super
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def nodelist
|
def nodelist
|
||||||
@blocks.map(&:attachment).flatten
|
@blocks.flat_map(&:attachment)
|
||||||
end
|
end
|
||||||
|
|
||||||
def unknown_tag(tag, markup, tokens)
|
def unknown_tag(tag, markup, tokens)
|
||||||
@nodelist = []
|
@nodelist = []
|
||||||
case tag
|
case tag
|
||||||
when 'when'
|
when 'when'.freeze
|
||||||
record_when_condition(markup)
|
record_when_condition(markup)
|
||||||
when 'else'
|
when 'else'.freeze
|
||||||
record_else_condition(markup)
|
record_else_condition(markup)
|
||||||
else
|
else
|
||||||
super
|
super
|
||||||
@@ -54,12 +53,12 @@ module Liquid
|
|||||||
while markup
|
while markup
|
||||||
# Create a new nodelist and assign it to the new block
|
# Create a new nodelist and assign it to the new block
|
||||||
if not markup =~ WhenSyntax
|
if not markup =~ WhenSyntax
|
||||||
raise SyntaxError.new(options[:locale].t("errors.syntax.case_invalid_when"))
|
raise SyntaxError.new(options[:locale].t("errors.syntax.case_invalid_when".freeze))
|
||||||
end
|
end
|
||||||
|
|
||||||
markup = $2
|
markup = $2
|
||||||
|
|
||||||
block = Condition.new(@left, '==', $1)
|
block = Condition.new(@left, '=='.freeze, $1)
|
||||||
block.attach(@nodelist)
|
block.attach(@nodelist)
|
||||||
@blocks.push(block)
|
@blocks.push(block)
|
||||||
end
|
end
|
||||||
@@ -67,7 +66,7 @@ module Liquid
|
|||||||
|
|
||||||
def record_else_condition(markup)
|
def record_else_condition(markup)
|
||||||
if not markup.strip.empty?
|
if not markup.strip.empty?
|
||||||
raise SyntaxError.new(options[:locale].t("errors.syntax.case_invalid_else"))
|
raise SyntaxError.new(options[:locale].t("errors.syntax.case_invalid_else".freeze))
|
||||||
end
|
end
|
||||||
|
|
||||||
block = ElseCondition.new
|
block = ElseCondition.new
|
||||||
@@ -76,5 +75,5 @@ module Liquid
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
Template.register_tag('case', Case)
|
Template.register_tag('case'.freeze, Case)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
module Liquid
|
module Liquid
|
||||||
class Comment < Block
|
class Comment < Block
|
||||||
def render(context)
|
def render(context)
|
||||||
''
|
''.freeze
|
||||||
end
|
end
|
||||||
|
|
||||||
def unknown_tag(tag, markup, tokens)
|
def unknown_tag(tag, markup, tokens)
|
||||||
@@ -12,5 +12,5 @@ module Liquid
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
Template.register_tag('comment', Comment)
|
Template.register_tag('comment'.freeze, Comment)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
module Liquid
|
module Liquid
|
||||||
|
|
||||||
# Continue tag to be used to break out of a for loop.
|
# Continue tag to be used to break out of a for loop.
|
||||||
#
|
#
|
||||||
# == Basic Usage:
|
# == Basic Usage:
|
||||||
@@ -10,12 +9,10 @@ module Liquid
|
|||||||
# {% endfor %}
|
# {% endfor %}
|
||||||
#
|
#
|
||||||
class Continue < Tag
|
class Continue < Tag
|
||||||
|
|
||||||
def interrupt
|
def interrupt
|
||||||
ContinueInterrupt.new
|
ContinueInterrupt.new
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
Template.register_tag('continue', Continue)
|
Template.register_tag('continue'.freeze, Continue)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -12,10 +12,11 @@ module Liquid
|
|||||||
# <div class="green"> Item five</div>
|
# <div class="green"> Item five</div>
|
||||||
#
|
#
|
||||||
class Cycle < Tag
|
class Cycle < Tag
|
||||||
SimpleSyntax = /^#{QuotedFragment}+/o
|
SimpleSyntax = /\A#{QuotedFragment}+/o
|
||||||
NamedSyntax = /^(#{QuotedFragment})\s*\:\s*(.*)/o
|
NamedSyntax = /\A(#{QuotedFragment})\s*\:\s*(.*)/om
|
||||||
|
|
||||||
def initialize(tag_name, markup, tokens)
|
def initialize(tag_name, markup, options)
|
||||||
|
super
|
||||||
case markup
|
case markup
|
||||||
when NamedSyntax
|
when NamedSyntax
|
||||||
@variables = variables_from_string($2)
|
@variables = variables_from_string($2)
|
||||||
@@ -24,9 +25,8 @@ module Liquid
|
|||||||
@variables = variables_from_string(markup)
|
@variables = variables_from_string(markup)
|
||||||
@name = "'#{@variables.to_s}'"
|
@name = "'#{@variables.to_s}'"
|
||||||
else
|
else
|
||||||
raise SyntaxError.new(options[:locale].t("errors.syntax.cycle"))
|
raise SyntaxError.new(options[:locale].t("errors.syntax.cycle".freeze))
|
||||||
end
|
end
|
||||||
super
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def render(context)
|
def render(context)
|
||||||
@@ -43,11 +43,8 @@ module Liquid
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def blank?
|
|
||||||
false
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def variables_from_string(markup)
|
def variables_from_string(markup)
|
||||||
markup.split(',').collect do |var|
|
markup.split(',').collect do |var|
|
||||||
var =~ /\s*(#{QuotedFragment})\s*/o
|
var =~ /\s*(#{QuotedFragment})\s*/o
|
||||||
|
|||||||
@@ -19,10 +19,9 @@ module Liquid
|
|||||||
# Hello: -3
|
# Hello: -3
|
||||||
#
|
#
|
||||||
class Decrement < Tag
|
class Decrement < Tag
|
||||||
def initialize(tag_name, markup, tokens)
|
def initialize(tag_name, markup, options)
|
||||||
@variable = markup.strip
|
|
||||||
|
|
||||||
super
|
super
|
||||||
|
@variable = markup.strip
|
||||||
end
|
end
|
||||||
|
|
||||||
def render(context)
|
def render(context)
|
||||||
@@ -35,5 +34,5 @@ module Liquid
|
|||||||
private
|
private
|
||||||
end
|
end
|
||||||
|
|
||||||
Template.register_tag('decrement', Decrement)
|
Template.register_tag('decrement'.freeze, Decrement)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -46,10 +46,10 @@ module Liquid
|
|||||||
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
|
||||||
|
|
||||||
def initialize(tag_name, markup, tokens)
|
def initialize(tag_name, markup, options)
|
||||||
|
super
|
||||||
parse_with_selected_parser(markup)
|
parse_with_selected_parser(markup)
|
||||||
@nodelist = @for_block = []
|
@nodelist = @for_block = []
|
||||||
super
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def nodelist
|
def nodelist
|
||||||
@@ -61,7 +61,7 @@ module Liquid
|
|||||||
end
|
end
|
||||||
|
|
||||||
def unknown_tag(tag, markup, tokens)
|
def unknown_tag(tag, markup, tokens)
|
||||||
return super unless tag == 'else'
|
return super unless tag == 'else'.freeze
|
||||||
@nodelist = @else_block = []
|
@nodelist = @else_block = []
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -74,13 +74,13 @@ module Liquid
|
|||||||
# Maintains Ruby 1.8.7 String#each behaviour on 1.9
|
# Maintains Ruby 1.8.7 String#each behaviour on 1.9
|
||||||
return render_else(context) unless iterable?(collection)
|
return render_else(context) unless iterable?(collection)
|
||||||
|
|
||||||
from = if @attributes['offset'] == 'continue'
|
from = if @attributes['offset'.freeze] == 'continue'.freeze
|
||||||
context.registers[:for][@name].to_i
|
context.registers[:for][@name].to_i
|
||||||
else
|
else
|
||||||
context[@attributes['offset']].to_i
|
context[@attributes['offset'.freeze]].to_i
|
||||||
end
|
end
|
||||||
|
|
||||||
limit = context[@attributes['limit']]
|
limit = context[@attributes['limit'.freeze]]
|
||||||
to = limit ? limit.to_i + from : nil
|
to = limit ? limit.to_i + from : nil
|
||||||
|
|
||||||
segment = Utils.slice_collection(collection, from, to)
|
segment = Utils.slice_collection(collection, from, to)
|
||||||
@@ -99,15 +99,16 @@ module Liquid
|
|||||||
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'] = {
|
context['forloop'.freeze] = {
|
||||||
'name' => @name,
|
'name'.freeze => @name,
|
||||||
'length' => length,
|
'length'.freeze => length,
|
||||||
'index' => index + 1,
|
'index'.freeze => index + 1,
|
||||||
'index0' => index,
|
'index0'.freeze => index,
|
||||||
'rindex' => length - index,
|
'rindex'.freeze => length - index,
|
||||||
'rindex0' => length - index - 1,
|
'rindex0'.freeze => length - index - 1,
|
||||||
'first' => (index == 0),
|
'first'.freeze => (index == 0),
|
||||||
'last' => (index == length - 1) }
|
'last'.freeze => (index == length - 1)
|
||||||
|
}
|
||||||
|
|
||||||
result << render_all(@for_block, context)
|
result << render_all(@for_block, context)
|
||||||
|
|
||||||
@@ -135,22 +136,22 @@ module Liquid
|
|||||||
@attributes[key] = value
|
@attributes[key] = value
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
raise SyntaxError.new(options[:locale].t("errors.syntax.for"))
|
raise SyntaxError.new(options[:locale].t("errors.syntax.for".freeze))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def strict_parse(markup)
|
def strict_parse(markup)
|
||||||
p = Parser.new(markup)
|
p = Parser.new(markup)
|
||||||
@variable_name = p.consume(:id)
|
@variable_name = p.consume(:id)
|
||||||
raise SyntaxError.new(options[:locale].t("errors.syntax.for_invalid_in")) unless p.id?('in')
|
raise SyntaxError.new(options[:locale].t("errors.syntax.for_invalid_in".freeze)) unless p.id?('in'.freeze)
|
||||||
@collection_name = p.expression
|
@collection_name = p.expression
|
||||||
@name = "#{@variable_name}-#{@collection_name}"
|
@name = "#{@variable_name}-#{@collection_name}"
|
||||||
@reversed = p.id?('reversed')
|
@reversed = p.id?('reversed'.freeze)
|
||||||
|
|
||||||
@attributes = {}
|
@attributes = {}
|
||||||
while p.look(:id) && p.look(:colon, 1)
|
while p.look(:id) && p.look(:colon, 1)
|
||||||
unless attribute = p.id?('limit') || p.id?('offset')
|
unless attribute = p.id?('limit'.freeze) || p.id?('offset'.freeze)
|
||||||
raise SyntaxError.new(options[:locale].t("errors.syntax.for_invalid_attribute"))
|
raise SyntaxError.new(options[:locale].t("errors.syntax.for_invalid_attribute".freeze))
|
||||||
end
|
end
|
||||||
p.consume
|
p.consume
|
||||||
val = p.expression
|
val = p.expression
|
||||||
@@ -161,14 +162,14 @@ module Liquid
|
|||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def render_else(context)
|
def render_else(context)
|
||||||
return @else_block ? [render_all(@else_block, context)] : ''
|
return @else_block ? [render_all(@else_block, context)] : ''.freeze
|
||||||
end
|
end
|
||||||
|
|
||||||
def iterable?(collection)
|
def iterable?(collection)
|
||||||
collection.respond_to?(:each) || Utils.non_blank_string?(collection)
|
collection.respond_to?(:each) || Utils.non_blank_string?(collection)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
Template.register_tag('for', For)
|
Template.register_tag('for'.freeze, For)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -14,18 +14,18 @@ module Liquid
|
|||||||
ExpressionsAndOperators = /(?:\b(?:\s?and\s?|\s?or\s?)\b|(?:\s*(?!\b(?:\s?and\s?|\s?or\s?)\b)(?:#{QuotedFragment}|\S+)\s*)+)/o
|
ExpressionsAndOperators = /(?:\b(?:\s?and\s?|\s?or\s?)\b|(?:\s*(?!\b(?:\s?and\s?|\s?or\s?)\b)(?:#{QuotedFragment}|\S+)\s*)+)/o
|
||||||
BOOLEAN_OPERATORS = %w(and or)
|
BOOLEAN_OPERATORS = %w(and or)
|
||||||
|
|
||||||
def initialize(tag_name, markup, tokens)
|
def initialize(tag_name, markup, options)
|
||||||
@blocks = []
|
|
||||||
push_block('if', markup)
|
|
||||||
super
|
super
|
||||||
|
@blocks = []
|
||||||
|
push_block('if'.freeze, markup)
|
||||||
end
|
end
|
||||||
|
|
||||||
def nodelist
|
def nodelist
|
||||||
@blocks.map(&:attachment).flatten
|
@blocks.flat_map(&:attachment)
|
||||||
end
|
end
|
||||||
|
|
||||||
def unknown_tag(tag, markup, tokens)
|
def unknown_tag(tag, markup, tokens)
|
||||||
if ['elsif', 'else'].include?(tag)
|
if ['elsif'.freeze, 'else'.freeze].include?(tag)
|
||||||
push_block(tag, markup)
|
push_block(tag, markup)
|
||||||
else
|
else
|
||||||
super
|
super
|
||||||
@@ -39,14 +39,14 @@ module Liquid
|
|||||||
return render_all(block.attachment, context)
|
return render_all(block.attachment, context)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
''
|
''.freeze
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def push_block(tag, markup)
|
def push_block(tag, markup)
|
||||||
block = if tag == 'else'
|
block = if tag == 'else'.freeze
|
||||||
ElseCondition.new
|
ElseCondition.new
|
||||||
else
|
else
|
||||||
parse_with_selected_parser(markup)
|
parse_with_selected_parser(markup)
|
||||||
@@ -57,18 +57,18 @@ module Liquid
|
|||||||
end
|
end
|
||||||
|
|
||||||
def lax_parse(markup)
|
def lax_parse(markup)
|
||||||
expressions = markup.scan(ExpressionsAndOperators).reverse
|
expressions = markup.scan(ExpressionsAndOperators)
|
||||||
raise(SyntaxError.new(options[:locale].t("errors.syntax.if"))) unless expressions.shift =~ Syntax
|
raise(SyntaxError.new(options[:locale].t("errors.syntax.if".freeze))) unless expressions.pop =~ Syntax
|
||||||
|
|
||||||
condition = Condition.new($1, $2, $3)
|
condition = Condition.new($1, $2, $3)
|
||||||
|
|
||||||
while not expressions.empty?
|
while not expressions.empty?
|
||||||
operator = (expressions.shift).to_s.strip
|
operator = expressions.pop.to_s.strip
|
||||||
|
|
||||||
raise(SyntaxError.new(options[:locale].t("errors.syntax.if"))) unless expressions.shift.to_s =~ Syntax
|
raise(SyntaxError.new(options[:locale].t("errors.syntax.if".freeze))) unless expressions.pop.to_s =~ Syntax
|
||||||
|
|
||||||
new_condition = Condition.new($1, $2, $3)
|
new_condition = Condition.new($1, $2, $3)
|
||||||
raise(SyntaxError.new(options[:locale].t("errors.syntax.if"))) unless BOOLEAN_OPERATORS.include?(operator)
|
raise(SyntaxError.new(options[:locale].t("errors.syntax.if".freeze))) unless BOOLEAN_OPERATORS.include?(operator)
|
||||||
new_condition.send(operator, condition)
|
new_condition.send(operator, condition)
|
||||||
condition = new_condition
|
condition = new_condition
|
||||||
end
|
end
|
||||||
@@ -78,16 +78,16 @@ module Liquid
|
|||||||
|
|
||||||
def strict_parse(markup)
|
def strict_parse(markup)
|
||||||
p = Parser.new(markup)
|
p = Parser.new(markup)
|
||||||
|
condition = parse_binary_comparison(p)
|
||||||
condition = parse_comparison(p)
|
|
||||||
|
|
||||||
while op = (p.id?('and') || p.id?('or'))
|
|
||||||
new_cond = parse_comparison(p)
|
|
||||||
new_cond.send(op, condition)
|
|
||||||
condition = new_cond
|
|
||||||
end
|
|
||||||
p.consume(:end_of_string)
|
p.consume(:end_of_string)
|
||||||
|
condition
|
||||||
|
end
|
||||||
|
|
||||||
|
def parse_binary_comparison(p)
|
||||||
|
condition = parse_comparison(p)
|
||||||
|
if op = (p.id?('and'.freeze) || p.id?('or'.freeze))
|
||||||
|
condition.send(op, parse_binary_comparison(p))
|
||||||
|
end
|
||||||
condition
|
condition
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -102,5 +102,5 @@ module Liquid
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
Template.register_tag('if', If)
|
Template.register_tag('if'.freeze, If)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -4,17 +4,17 @@ module Liquid
|
|||||||
def render(context)
|
def render(context)
|
||||||
context.stack do
|
context.stack do
|
||||||
|
|
||||||
output = render_all(@nodelist, context)
|
output = super
|
||||||
|
|
||||||
if output != context.registers[:ifchanged]
|
if output != context.registers[:ifchanged]
|
||||||
context.registers[:ifchanged] = output
|
context.registers[:ifchanged] = output
|
||||||
output
|
output
|
||||||
else
|
else
|
||||||
''
|
''.freeze
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
Template.register_tag('ifchanged', Ifchanged)
|
Template.register_tag('ifchanged'.freeze, Ifchanged)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -17,7 +17,9 @@ module Liquid
|
|||||||
class Include < Tag
|
class Include < Tag
|
||||||
Syntax = /(#{QuotedFragment}+)(\s+(?:with|for)\s+(#{QuotedFragment}+))?/o
|
Syntax = /(#{QuotedFragment}+)(\s+(?:with|for)\s+(#{QuotedFragment}+))?/o
|
||||||
|
|
||||||
def initialize(tag_name, markup, tokens)
|
def initialize(tag_name, markup, options)
|
||||||
|
super
|
||||||
|
|
||||||
if markup =~ Syntax
|
if markup =~ Syntax
|
||||||
|
|
||||||
@template_name = $1
|
@template_name = $1
|
||||||
@@ -29,19 +31,13 @@ module Liquid
|
|||||||
end
|
end
|
||||||
|
|
||||||
else
|
else
|
||||||
raise SyntaxError.new(options[:locale].t("errors.syntax.include"))
|
raise SyntaxError.new(options[:locale].t("errors.syntax.include".freeze))
|
||||||
end
|
end
|
||||||
|
|
||||||
super
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def parse(tokens)
|
def parse(tokens)
|
||||||
end
|
end
|
||||||
|
|
||||||
def blank?
|
|
||||||
false
|
|
||||||
end
|
|
||||||
|
|
||||||
def render(context)
|
def render(context)
|
||||||
partial = load_cached_partial(context)
|
partial = load_cached_partial(context)
|
||||||
variable = context[@variable_name || @template_name[1..-2]]
|
variable = context[@variable_name || @template_name[1..-2]]
|
||||||
@@ -51,7 +47,7 @@ module Liquid
|
|||||||
context[key] = context[value]
|
context[key] = context[value]
|
||||||
end
|
end
|
||||||
|
|
||||||
context_variable_name = @template_name[1..-2].split('/').last
|
context_variable_name = @template_name[1..-2].split('/'.freeze).last
|
||||||
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
|
||||||
@@ -73,7 +69,7 @@ module Liquid
|
|||||||
return cached
|
return cached
|
||||||
end
|
end
|
||||||
source = read_template_from_file_system(context)
|
source = read_template_from_file_system(context)
|
||||||
partial = Liquid::Template.parse(source)
|
partial = Liquid::Template.parse(source, pass_options)
|
||||||
cached_partials[template_name] = partial
|
cached_partials[template_name] = partial
|
||||||
context.registers[:cached_partials] = cached_partials
|
context.registers[:cached_partials] = cached_partials
|
||||||
partial
|
partial
|
||||||
@@ -92,7 +88,17 @@ module Liquid
|
|||||||
raise ArgumentError, "file_system.read_template_file expects two parameters: (template_name, context)"
|
raise ArgumentError, "file_system.read_template_file expects two parameters: (template_name, context)"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def pass_options
|
||||||
|
dont_pass = @options[:include_options_blacklist]
|
||||||
|
return {locale: @options[:locale]} if dont_pass == true
|
||||||
|
opts = @options.merge(included: true, include_options_blacklist: false)
|
||||||
|
if dont_pass.is_a?(Array)
|
||||||
|
dont_pass.each {|o| opts.delete(o)}
|
||||||
|
end
|
||||||
|
opts
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
Template.register_tag('include', Include)
|
Template.register_tag('include'.freeze, Include)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -15,9 +15,9 @@ module Liquid
|
|||||||
# Hello: 2
|
# Hello: 2
|
||||||
#
|
#
|
||||||
class Increment < Tag
|
class Increment < Tag
|
||||||
def initialize(tag_name, markup, tokens)
|
def initialize(tag_name, markup, options)
|
||||||
@variable = markup.strip
|
|
||||||
super
|
super
|
||||||
|
@variable = markup.strip
|
||||||
end
|
end
|
||||||
|
|
||||||
def render(context)
|
def render(context)
|
||||||
@@ -25,11 +25,7 @@ module Liquid
|
|||||||
context.environments.first[@variable] = value + 1
|
context.environments.first[@variable] = value + 1
|
||||||
value.to_s
|
value.to_s
|
||||||
end
|
end
|
||||||
|
|
||||||
def blank?
|
|
||||||
false
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
Template.register_tag('increment', Increment)
|
Template.register_tag('increment'.freeze, Increment)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,22 +1,19 @@
|
|||||||
module Liquid
|
module Liquid
|
||||||
class Raw < Block
|
class Raw < Block
|
||||||
FullTokenPossiblyInvalid = /^(.*)#{TagStart}\s*(\w+)\s*(.*)?#{TagEnd}$/o
|
FullTokenPossiblyInvalid = /\A(.*)#{TagStart}\s*(\w+)\s*(.*)?#{TagEnd}\z/om
|
||||||
|
|
||||||
def parse(tokens)
|
def parse(tokens)
|
||||||
@nodelist ||= []
|
@nodelist ||= []
|
||||||
@nodelist.clear
|
@nodelist.clear
|
||||||
while token = tokens.shift
|
while token = tokens.shift
|
||||||
if token =~ FullTokenPossiblyInvalid
|
if token =~ FullTokenPossiblyInvalid
|
||||||
@nodelist << $1 if $1 != ""
|
@nodelist << $1 if $1 != "".freeze
|
||||||
if block_delimiter == $2
|
return if block_delimiter == $2
|
||||||
end_tag
|
|
||||||
return
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
@nodelist << token if not token.empty?
|
@nodelist << token if not token.empty?
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
Template.register_tag('raw', Raw)
|
Template.register_tag('raw'.freeze, Raw)
|
||||||
end
|
end
|
||||||
|
|||||||
72
lib/liquid/tags/table_row.rb
Normal file
72
lib/liquid/tags/table_row.rb
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
module Liquid
|
||||||
|
class TableRow < Block
|
||||||
|
Syntax = /(\w+)\s+in\s+(#{QuotedFragment}+)/o
|
||||||
|
|
||||||
|
def initialize(tag_name, markup, options)
|
||||||
|
super
|
||||||
|
if markup =~ Syntax
|
||||||
|
@variable_name = $1
|
||||||
|
@collection_name = $2
|
||||||
|
@attributes = {}
|
||||||
|
markup.scan(TagAttributes) do |key, value|
|
||||||
|
@attributes[key] = value
|
||||||
|
end
|
||||||
|
else
|
||||||
|
raise SyntaxError.new(options[:locale].t("errors.syntax.table_row".freeze))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def render(context)
|
||||||
|
collection = context[@collection_name] or return ''.freeze
|
||||||
|
|
||||||
|
from = @attributes['offset'.freeze] ? context[@attributes['offset'.freeze]].to_i : 0
|
||||||
|
to = @attributes['limit'.freeze] ? from + context[@attributes['limit'.freeze]].to_i : nil
|
||||||
|
|
||||||
|
collection = Utils.slice_collection(collection, from, to)
|
||||||
|
|
||||||
|
length = collection.length
|
||||||
|
|
||||||
|
cols = context[@attributes['cols'.freeze]].to_i
|
||||||
|
|
||||||
|
row = 1
|
||||||
|
col = 0
|
||||||
|
|
||||||
|
result = "<tr class=\"row1\">\n"
|
||||||
|
context.stack do
|
||||||
|
|
||||||
|
collection.each_with_index do |item, index|
|
||||||
|
context[@variable_name] = item
|
||||||
|
context['tablerowloop'.freeze] = {
|
||||||
|
'length'.freeze => length,
|
||||||
|
'index'.freeze => index + 1,
|
||||||
|
'index0'.freeze => index,
|
||||||
|
'col'.freeze => col + 1,
|
||||||
|
'col0'.freeze => col,
|
||||||
|
'rindex'.freeze => length - index,
|
||||||
|
'rindex0'.freeze => length - index - 1,
|
||||||
|
'first'.freeze => (index == 0),
|
||||||
|
'last'.freeze => (index == length - 1),
|
||||||
|
'col_first'.freeze => (col == 0),
|
||||||
|
'col_last'.freeze => (col == cols - 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
col += 1
|
||||||
|
|
||||||
|
result << "<td class=\"col#{col}\">" << super << '</td>'
|
||||||
|
|
||||||
|
if col == cols and (index != length - 1)
|
||||||
|
col = 0
|
||||||
|
row += 1
|
||||||
|
result << "</tr>\n<tr class=\"row#{row}\">"
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
|
result << "</tr>\n"
|
||||||
|
result
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
Template.register_tag('tablerow'.freeze, TableRow)
|
||||||
|
end
|
||||||
@@ -1,10 +1,9 @@
|
|||||||
require File.dirname(__FILE__) + '/if'
|
require File.dirname(__FILE__) + '/if'
|
||||||
|
|
||||||
module Liquid
|
module Liquid
|
||||||
|
|
||||||
# Unless is a conditional just like 'if' but works on the inverse logic.
|
# Unless is a conditional just like 'if' but works on the inverse logic.
|
||||||
#
|
#
|
||||||
# {% unless x < 0 %} x is greater than zero {% end %}
|
# {% unless x < 0 %} x is greater than zero {% endunless %}
|
||||||
#
|
#
|
||||||
class Unless < If
|
class Unless < If
|
||||||
def render(context)
|
def render(context)
|
||||||
@@ -23,11 +22,10 @@ module Liquid
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
''
|
''.freeze
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
Template.register_tag('unless'.freeze, Unless)
|
||||||
Template.register_tag('unless', Unless)
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -21,7 +21,51 @@ module Liquid
|
|||||||
attr_accessor :root, :resource_limits
|
attr_accessor :root, :resource_limits
|
||||||
@@file_system = BlankFileSystem.new
|
@@file_system = BlankFileSystem.new
|
||||||
|
|
||||||
|
class TagRegistry
|
||||||
|
def initialize
|
||||||
|
@tags = {}
|
||||||
|
@cache = {}
|
||||||
|
end
|
||||||
|
|
||||||
|
def [](tag_name)
|
||||||
|
return nil unless @tags.has_key?(tag_name)
|
||||||
|
return @cache[tag_name] if Liquid.cache_classes
|
||||||
|
|
||||||
|
lookup_class(@tags[tag_name]).tap { |o| @cache[tag_name] = o }
|
||||||
|
end
|
||||||
|
|
||||||
|
def []=(tag_name, klass)
|
||||||
|
@tags[tag_name] = klass.name
|
||||||
|
@cache[tag_name] = klass
|
||||||
|
end
|
||||||
|
|
||||||
|
def delete(tag_name)
|
||||||
|
@tags.delete(tag_name)
|
||||||
|
@cache.delete(tag_name)
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def lookup_class(name)
|
||||||
|
name.split("::").reject(&:empty?).reduce(Object) { |scope, const| scope.const_get(const) }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
attr_reader :profiler
|
||||||
|
|
||||||
class << self
|
class << self
|
||||||
|
# Sets how strict the parser should be.
|
||||||
|
# :lax acts like liquid 2.5 and silently ignores malformed tags in most cases.
|
||||||
|
# :warn is the default and will give deprecation warnings when invalid syntax is used.
|
||||||
|
# :strict will enforce correct syntax.
|
||||||
|
attr_writer :error_mode
|
||||||
|
|
||||||
|
# Sets how strict the taint checker should be.
|
||||||
|
# :lax is the default, and ignores the taint flag completely
|
||||||
|
# :warn adds a warning, but does not interrupt the rendering
|
||||||
|
# :error raises an error when tainted output is used
|
||||||
|
attr_writer :taint_mode
|
||||||
|
|
||||||
def file_system
|
def file_system
|
||||||
@@file_system
|
@@file_system
|
||||||
end
|
end
|
||||||
@@ -35,44 +79,47 @@ module Liquid
|
|||||||
end
|
end
|
||||||
|
|
||||||
def tags
|
def tags
|
||||||
@tags ||= {}
|
@tags ||= TagRegistry.new
|
||||||
end
|
|
||||||
|
|
||||||
# Sets how strict the parser should be.
|
|
||||||
# :lax acts like liquid 2.5 and silently ignores malformed tags in most cases.
|
|
||||||
# :warn is the default and will give deprecation warnings when invalid syntax is used.
|
|
||||||
# :strict will enforce correct syntax.
|
|
||||||
def error_mode=(mode)
|
|
||||||
@error_mode = mode
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def error_mode
|
def error_mode
|
||||||
@error_mode || :lax
|
@error_mode || :lax
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def taint_mode
|
||||||
|
@taint_mode || :lax
|
||||||
|
end
|
||||||
|
|
||||||
# Pass a module with filter methods which should be available
|
# Pass a module with filter methods which should be available
|
||||||
# to all liquid views. Good for registering the standard library
|
# to all liquid views. Good for registering the standard library
|
||||||
def register_filter(mod)
|
def register_filter(mod)
|
||||||
Strainer.global_filter(mod)
|
Strainer.global_filter(mod)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def default_resource_limits
|
||||||
|
@default_resource_limits ||= {}
|
||||||
|
end
|
||||||
|
|
||||||
# creates a new <tt>Template</tt> object from liquid source code
|
# creates a new <tt>Template</tt> object from liquid source code
|
||||||
|
# To enable profiling, pass in <tt>profile: true</tt> as an option.
|
||||||
|
# See Liquid::Profiler for more information
|
||||||
def parse(source, options = {})
|
def parse(source, options = {})
|
||||||
template = Template.new
|
template = Template.new
|
||||||
template.parse(source, options)
|
template.parse(source, options)
|
||||||
template
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# creates a new <tt>Template</tt> from an array of tokens. Use <tt>Template.parse</tt> instead
|
|
||||||
def initialize
|
def initialize
|
||||||
@resource_limits = {}
|
@resource_limits = self.class.default_resource_limits.dup
|
||||||
end
|
end
|
||||||
|
|
||||||
# Parse source code.
|
# Parse source code.
|
||||||
# Returns self for easy chaining
|
# Returns self for easy chaining
|
||||||
def parse(source, options = {})
|
def parse(source, options = {})
|
||||||
@root = Document.new(tokenize(source), DEFAULT_OPTIONS.merge(options))
|
@options = options
|
||||||
|
@profiling = options[:profile]
|
||||||
|
@line_numbers = options[:line_numbers] || @profiling
|
||||||
|
@root = Document.parse(tokenize(source), DEFAULT_OPTIONS.merge(options))
|
||||||
@warnings = nil
|
@warnings = nil
|
||||||
self
|
self
|
||||||
end
|
end
|
||||||
@@ -103,6 +150,9 @@ module Liquid
|
|||||||
# if you use the same filters over and over again consider registering them globally
|
# if you use the same filters over and over again consider registering them globally
|
||||||
# with <tt>Template.register_filter</tt>
|
# with <tt>Template.register_filter</tt>
|
||||||
#
|
#
|
||||||
|
# if profiling was enabled in <tt>Template#parse</tt> then the resulting profiling information
|
||||||
|
# will be available via <tt>Template#profiler</tt>
|
||||||
|
#
|
||||||
# Following options can be passed:
|
# Following options can be passed:
|
||||||
#
|
#
|
||||||
# * <tt>filters</tt> : array with local filters
|
# * <tt>filters</tt> : array with local filters
|
||||||
@@ -110,11 +160,17 @@ module Liquid
|
|||||||
# filters and tags and might be useful to integrate liquid more with its host application
|
# filters and tags and might be useful to integrate liquid more with its host application
|
||||||
#
|
#
|
||||||
def render(*args)
|
def render(*args)
|
||||||
return '' if @root.nil?
|
return ''.freeze if @root.nil?
|
||||||
|
|
||||||
context = case args.first
|
context = case args.first
|
||||||
when Liquid::Context
|
when Liquid::Context
|
||||||
args.shift
|
c = args.shift
|
||||||
|
|
||||||
|
if @rethrow_errors
|
||||||
|
c.exception_handler = ->(e) { true }
|
||||||
|
end
|
||||||
|
|
||||||
|
c
|
||||||
when Liquid::Drop
|
when Liquid::Drop
|
||||||
drop = args.shift
|
drop = args.shift
|
||||||
drop.context = Context.new([drop, assigns], instance_assigns, registers, @rethrow_errors, @resource_limits)
|
drop.context = Context.new([drop, assigns], instance_assigns, registers, @rethrow_errors, @resource_limits)
|
||||||
@@ -138,6 +194,9 @@ module Liquid
|
|||||||
context.add_filters(options[:filters])
|
context.add_filters(options[:filters])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if options[:exception_handler]
|
||||||
|
context.exception_handler = options[:exception_handler]
|
||||||
|
end
|
||||||
when Module
|
when Module
|
||||||
context.add_filters(args.pop)
|
context.add_filters(args.pop)
|
||||||
when Array
|
when Array
|
||||||
@@ -147,7 +206,9 @@ module Liquid
|
|||||||
begin
|
begin
|
||||||
# render the nodelist.
|
# render the nodelist.
|
||||||
# for performance reasons we get an array back here. join will make a string out of it.
|
# for performance reasons we get an array back here. join will make a string out of it.
|
||||||
result = @root.render(context)
|
result = with_profiling do
|
||||||
|
@root.render(context)
|
||||||
|
end
|
||||||
result.respond_to?(:join) ? result.join : result
|
result.respond_to?(:join) ? result.join : result
|
||||||
rescue Liquid::MemoryError => e
|
rescue Liquid::MemoryError => e
|
||||||
context.handle_error(e)
|
context.handle_error(e)
|
||||||
@@ -157,7 +218,8 @@ module Liquid
|
|||||||
end
|
end
|
||||||
|
|
||||||
def render!(*args)
|
def render!(*args)
|
||||||
@rethrow_errors = true; render(*args)
|
@rethrow_errors = true
|
||||||
|
render(*args)
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
@@ -166,7 +228,8 @@ module Liquid
|
|||||||
def tokenize(source)
|
def tokenize(source)
|
||||||
source = source.source if source.respond_to?(:source)
|
source = source.source if source.respond_to?(:source)
|
||||||
return [] if source.to_s.empty?
|
return [] if source.to_s.empty?
|
||||||
tokens = source.split(TemplateParser)
|
|
||||||
|
tokens = calculate_line_numbers(source.split(TemplateParser))
|
||||||
|
|
||||||
# removes the rogue empty element at the beginning of the array
|
# removes the rogue empty element at the beginning of the array
|
||||||
tokens.shift if tokens[0] and tokens[0].empty?
|
tokens.shift if tokens[0] and tokens[0].empty?
|
||||||
@@ -174,5 +237,30 @@ module Liquid
|
|||||||
tokens
|
tokens
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def calculate_line_numbers(raw_tokens)
|
||||||
|
return raw_tokens unless @line_numbers
|
||||||
|
|
||||||
|
current_line = 1
|
||||||
|
raw_tokens.map do |token|
|
||||||
|
Token.new(token, current_line).tap do
|
||||||
|
current_line += token.count("\n")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def with_profiling
|
||||||
|
if @profiling && !@options[:included]
|
||||||
|
@profiler = Profiler.new
|
||||||
|
@profiler.start
|
||||||
|
|
||||||
|
begin
|
||||||
|
yield
|
||||||
|
ensure
|
||||||
|
@profiler.stop
|
||||||
|
end
|
||||||
|
else
|
||||||
|
yield
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
18
lib/liquid/token.rb
Normal file
18
lib/liquid/token.rb
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
module Liquid
|
||||||
|
class Token < String
|
||||||
|
attr_reader :line_number
|
||||||
|
|
||||||
|
def initialize(content, line_number)
|
||||||
|
super(content)
|
||||||
|
@line_number = line_number
|
||||||
|
end
|
||||||
|
|
||||||
|
def raw
|
||||||
|
"<raw>"
|
||||||
|
end
|
||||||
|
|
||||||
|
def child(string)
|
||||||
|
Token.new(string, @line_number)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -10,7 +10,7 @@ module Liquid
|
|||||||
end
|
end
|
||||||
|
|
||||||
def self.non_blank_string?(collection)
|
def self.non_blank_string?(collection)
|
||||||
collection.is_a?(String) && collection != ''
|
collection.is_a?(String) && collection != ''.freeze
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.slice_collection_using_each(collection, from, to)
|
def self.slice_collection_using_each(collection, from, to)
|
||||||
|
|||||||
@@ -11,41 +11,41 @@ module Liquid
|
|||||||
# {{ user | link }}
|
# {{ user | link }}
|
||||||
#
|
#
|
||||||
class Variable
|
class Variable
|
||||||
FilterParser = /(?:#{FilterSeparator}|(?:\s*(?:#{QuotedFragment}|#{ArgumentSeparator})\s*)+)/o
|
FilterParser = /(?:\s+|#{QuotedFragment}|#{ArgumentSeparator})+/o
|
||||||
EasyParse = /^ *(\w+(?:\.\w+)*) *$/
|
EasyParse = /\A *(\w+(?:\.\w+)*) *\z/
|
||||||
attr_accessor :filters, :name, :warnings
|
attr_accessor :filters, :name, :warnings
|
||||||
|
attr_accessor :line_number
|
||||||
|
include ParserSwitching
|
||||||
|
|
||||||
def initialize(markup, options = {})
|
def initialize(markup, options = {})
|
||||||
@markup = markup
|
@markup = markup
|
||||||
@name = nil
|
@name = nil
|
||||||
@options = options || {}
|
@options = options || {}
|
||||||
|
|
||||||
|
|
||||||
case @options[:error_mode] || Template.error_mode
|
parse_with_selected_parser(markup)
|
||||||
when :strict then strict_parse(markup)
|
end
|
||||||
when :lax then lax_parse(markup)
|
|
||||||
when :warn
|
def raw
|
||||||
begin
|
@markup
|
||||||
strict_parse(markup)
|
end
|
||||||
rescue SyntaxError => e
|
|
||||||
@warnings ||= []
|
def markup_context(markup)
|
||||||
@warnings << e
|
"in \"{{#{markup}}}\""
|
||||||
lax_parse(markup)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def lax_parse(markup)
|
def lax_parse(markup)
|
||||||
@filters = []
|
@filters = []
|
||||||
if match = markup.match(/\s*(#{QuotedFragment})(.*)/o)
|
if markup =~ /(#{QuotedFragment})(.*)/om
|
||||||
@name = match[1]
|
name_markup = $1
|
||||||
if match[2].match(/#{FilterSeparator}\s*(.*)/o)
|
filter_markup = $2
|
||||||
filters = Regexp.last_match(1).scan(FilterParser)
|
@name = Expression.parse(name_markup)
|
||||||
|
if filter_markup =~ /#{FilterSeparator}\s*(.*)/om
|
||||||
|
filters = $1.scan(FilterParser)
|
||||||
filters.each do |f|
|
filters.each do |f|
|
||||||
if matches = f.match(/\s*(\w+)/)
|
if f =~ /\w+/
|
||||||
filtername = matches[1]
|
filtername = Regexp.last_match(0)
|
||||||
filterargs = f.scan(/(?:#{FilterArgumentSeparator}|#{ArgumentSeparator})\s*((?:\w+\s*\:\s*)?#{QuotedFragment})/o).flatten
|
filterargs = f.scan(/(?:#{FilterArgumentSeparator}|#{ArgumentSeparator})\s*((?:\w+\s*\:\s*)?#{QuotedFragment})/o).flatten
|
||||||
@filters << [filtername, filterargs]
|
@filters << parse_filter_expressions(filtername, filterargs)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -55,7 +55,7 @@ module Liquid
|
|||||||
def strict_parse(markup)
|
def strict_parse(markup)
|
||||||
# Very simple valid cases
|
# Very simple valid cases
|
||||||
if markup =~ EasyParse
|
if markup =~ EasyParse
|
||||||
@name = $1
|
@name = Expression.parse($1)
|
||||||
@filters = []
|
@filters = []
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
@@ -63,16 +63,13 @@ module Liquid
|
|||||||
@filters = []
|
@filters = []
|
||||||
p = Parser.new(markup)
|
p = Parser.new(markup)
|
||||||
# Could be just filters with no input
|
# Could be just filters with no input
|
||||||
@name = p.look(:pipe) ? '' : p.expression
|
@name = p.look(:pipe) ? nil : Expression.parse(p.expression)
|
||||||
while p.consume?(:pipe)
|
while p.consume?(:pipe)
|
||||||
filtername = p.consume(:id)
|
filtername = p.consume(:id)
|
||||||
filterargs = p.consume?(:colon) ? parse_filterargs(p) : []
|
filterargs = p.consume?(:colon) ? parse_filterargs(p) : []
|
||||||
@filters << [filtername, filterargs]
|
@filters << parse_filter_expressions(filtername, filterargs)
|
||||||
end
|
end
|
||||||
p.consume(:end_of_string)
|
p.consume(:end_of_string)
|
||||||
rescue SyntaxError => e
|
|
||||||
e.message << " in \"{{#{markup}}}\""
|
|
||||||
raise e
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def parse_filterargs(p)
|
def parse_filterargs(p)
|
||||||
@@ -86,22 +83,51 @@ module Liquid
|
|||||||
end
|
end
|
||||||
|
|
||||||
def render(context)
|
def render(context)
|
||||||
return '' if @name.nil?
|
@filters.inject(context.evaluate(@name)) do |output, (filter_name, filter_args, filter_kwargs)|
|
||||||
@filters.inject(context[@name]) do |output, filter|
|
filter_args = evaluate_filter_expressions(context, filter_args, filter_kwargs)
|
||||||
filterargs = []
|
output = context.invoke(filter_name, output, *filter_args)
|
||||||
keyword_args = {}
|
end.tap{ |obj| taint_check(obj) }
|
||||||
filter[1].to_a.each do |a|
|
end
|
||||||
if matches = a.match(/\A#{TagAttributes}\z/o)
|
|
||||||
keyword_args[matches[1]] = context[matches[2]]
|
private
|
||||||
else
|
|
||||||
filterargs << context[a]
|
def parse_filter_expressions(filter_name, unparsed_args)
|
||||||
end
|
filter_args = []
|
||||||
|
keyword_args = {}
|
||||||
|
unparsed_args.each do |a|
|
||||||
|
if matches = a.match(/\A#{TagAttributes}\z/o)
|
||||||
|
keyword_args[matches[1]] = Expression.parse(matches[2])
|
||||||
|
else
|
||||||
|
filter_args << Expression.parse(a)
|
||||||
end
|
end
|
||||||
filterargs << keyword_args unless keyword_args.empty?
|
end
|
||||||
begin
|
result = [filter_name, filter_args]
|
||||||
output = context.invoke(filter[0], output, *filterargs)
|
result << keyword_args unless keyword_args.empty?
|
||||||
rescue FilterNotFound
|
result
|
||||||
raise FilterNotFound, "Error - filter '#{filter[0]}' in '#{@markup.strip}' could not be found."
|
end
|
||||||
|
|
||||||
|
def evaluate_filter_expressions(context, filter_args, filter_kwargs)
|
||||||
|
parsed_args = filter_args.map{ |expr| context.evaluate(expr) }
|
||||||
|
if filter_kwargs
|
||||||
|
parsed_kwargs = {}
|
||||||
|
filter_kwargs.each do |key, expr|
|
||||||
|
parsed_kwargs[key] = context.evaluate(expr)
|
||||||
|
end
|
||||||
|
parsed_args << parsed_kwargs
|
||||||
|
end
|
||||||
|
parsed_args
|
||||||
|
end
|
||||||
|
|
||||||
|
def taint_check(obj)
|
||||||
|
if obj.tainted?
|
||||||
|
@markup =~ QuotedFragment
|
||||||
|
name = Regexp.last_match(0)
|
||||||
|
case Template.taint_mode
|
||||||
|
when :warn
|
||||||
|
@warnings ||= []
|
||||||
|
@warnings << "variable '#{name}' is tainted and was not escaped"
|
||||||
|
when :error
|
||||||
|
raise TaintedError, "Error - variable '#{name}' is tainted and was not escaped"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
80
lib/liquid/variable_lookup.rb
Normal file
80
lib/liquid/variable_lookup.rb
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
module Liquid
|
||||||
|
class VariableLookup
|
||||||
|
SQUARE_BRACKETED = /\A\[(.*)\]\z/m
|
||||||
|
COMMAND_METHODS = ['size'.freeze, 'first'.freeze, 'last'.freeze]
|
||||||
|
|
||||||
|
attr_reader :name, :lookups
|
||||||
|
|
||||||
|
def self.parse(markup)
|
||||||
|
new(markup)
|
||||||
|
end
|
||||||
|
|
||||||
|
def initialize(markup)
|
||||||
|
lookups = markup.scan(VariableParser)
|
||||||
|
|
||||||
|
name = lookups.shift
|
||||||
|
if name =~ SQUARE_BRACKETED
|
||||||
|
name = Expression.parse($1)
|
||||||
|
end
|
||||||
|
@name = name
|
||||||
|
|
||||||
|
@lookups = lookups
|
||||||
|
@command_flags = 0
|
||||||
|
|
||||||
|
@lookups.each_index do |i|
|
||||||
|
lookup = lookups[i]
|
||||||
|
if lookup =~ SQUARE_BRACKETED
|
||||||
|
lookups[i] = Expression.parse($1)
|
||||||
|
elsif COMMAND_METHODS.include?(lookup)
|
||||||
|
@command_flags |= 1 << i
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def evaluate(context)
|
||||||
|
name = context.evaluate(@name)
|
||||||
|
object = context.find_variable(name)
|
||||||
|
|
||||||
|
@lookups.each_index do |i|
|
||||||
|
key = context.evaluate(@lookups[i])
|
||||||
|
|
||||||
|
# If object is a hash- or array-like object we look for the
|
||||||
|
# presence of the key and if its available we return it
|
||||||
|
if object.respond_to?(:[]) &&
|
||||||
|
((object.respond_to?(:has_key?) && object.has_key?(key)) ||
|
||||||
|
(object.respond_to?(:fetch) && key.is_a?(Integer)))
|
||||||
|
|
||||||
|
# if its a proc we will replace the entry with the proc
|
||||||
|
res = context.lookup_and_evaluate(object, key)
|
||||||
|
object = res.to_liquid
|
||||||
|
|
||||||
|
# Some special cases. If the part wasn't in square brackets and
|
||||||
|
# no key with the same name was found we interpret following calls
|
||||||
|
# as commands and call them on the current object
|
||||||
|
elsif @command_flags & (1 << i) != 0 && object.respond_to?(key)
|
||||||
|
object = object.send(key).to_liquid
|
||||||
|
|
||||||
|
# No key was present with the desired value and it wasn't one of the directly supported
|
||||||
|
# keywords either. The only thing we got left is to return nil
|
||||||
|
else
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
# If we are dealing with a drop here we have to
|
||||||
|
object.context = context if object.respond_to?(:context=)
|
||||||
|
end
|
||||||
|
|
||||||
|
object
|
||||||
|
end
|
||||||
|
|
||||||
|
def ==(other)
|
||||||
|
self.class == other.class && self.state == other.state
|
||||||
|
end
|
||||||
|
|
||||||
|
protected
|
||||||
|
|
||||||
|
def state
|
||||||
|
[@name, @lookups, @command_flags]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
# encoding: utf-8
|
# encoding: utf-8
|
||||||
module Liquid
|
module Liquid
|
||||||
VERSION = "3.0.0"
|
VERSION = "3.0.6"
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -23,4 +23,7 @@ Gem::Specification.new do |s|
|
|||||||
s.extra_rdoc_files = ["History.md", "README.md"]
|
s.extra_rdoc_files = ["History.md", "README.md"]
|
||||||
|
|
||||||
s.require_path = "lib"
|
s.require_path = "lib"
|
||||||
|
|
||||||
|
s.add_development_dependency 'rake'
|
||||||
|
s.add_development_dependency 'minitest'
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,12 +1,17 @@
|
|||||||
require 'rubygems'
|
require 'benchmark/ips'
|
||||||
require 'benchmark'
|
|
||||||
require File.dirname(__FILE__) + '/theme_runner'
|
require File.dirname(__FILE__) + '/theme_runner'
|
||||||
|
|
||||||
Liquid::Template.error_mode = ARGV.first.to_sym if ARGV.first
|
Liquid::Template.error_mode = ARGV.first.to_sym if ARGV.first
|
||||||
profiler = ThemeRunner.new
|
profiler = ThemeRunner.new
|
||||||
|
|
||||||
Benchmark.bmbm do |x|
|
Benchmark.ips do |x|
|
||||||
x.report("parse:") { 100.times { profiler.compile } }
|
x.time = 60
|
||||||
x.report("parse & run:") { 100.times { profiler.run } }
|
x.warmup = 5
|
||||||
end
|
|
||||||
|
|
||||||
|
puts
|
||||||
|
puts "Running benchmark for #{x.time} seconds (with #{x.warmup} seconds warmup)."
|
||||||
|
puts
|
||||||
|
|
||||||
|
x.report("parse:") { profiler.compile }
|
||||||
|
x.report("parse & run:") { profiler.run }
|
||||||
|
end
|
||||||
|
|||||||
@@ -1,19 +1,17 @@
|
|||||||
require 'rubygems'
|
require 'stackprof' rescue fail("install stackprof extension/gem")
|
||||||
require 'ruby-prof' rescue fail("install ruby-prof extension/gem")
|
|
||||||
require File.dirname(__FILE__) + '/theme_runner'
|
require File.dirname(__FILE__) + '/theme_runner'
|
||||||
|
|
||||||
|
Liquid::Template.error_mode = ARGV.first.to_sym if ARGV.first
|
||||||
profiler = ThemeRunner.new
|
profiler = ThemeRunner.new
|
||||||
|
profiler.run
|
||||||
|
|
||||||
puts 'Running profiler...'
|
[:cpu, :object].each do |profile_type|
|
||||||
|
puts "Profiling in #{profile_type.to_s} mode..."
|
||||||
results = profiler.run_profile
|
results = StackProf.run(mode: profile_type) do
|
||||||
|
100.times do
|
||||||
puts 'Success'
|
profiler.run
|
||||||
puts
|
end
|
||||||
|
end
|
||||||
[RubyProf::FlatPrinter, RubyProf::GraphHtmlPrinter, RubyProf::CallTreePrinter, RubyProf::DotPrinter].each do |klass|
|
StackProf::Report.new(results).print_text(false, 20)
|
||||||
filename = (ENV['TMP'] || '/tmp') + (klass.name.include?('Html') ? "/liquid.#{klass.name.downcase}.html" : "/callgrind.liquid.#{klass.name.downcase}.txt")
|
File.write(ENV['FILENAME'] + "." + profile_type.to_s, Marshal.dump(results)) if ENV['FILENAME']
|
||||||
filename.gsub!(/:+/, '_')
|
|
||||||
File.open(filename, "w+") { |fp| klass.new(results).print(fp, :print_file => true, :min_percent => 3) }
|
|
||||||
$stderr.puts "wrote #{klass.name} output to #{filename}"
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
class CommentForm < Liquid::Block
|
class CommentForm < Liquid::Block
|
||||||
Syntax = /(#{Liquid::VariableSignature}+)/
|
Syntax = /(#{Liquid::VariableSignature}+)/
|
||||||
|
|
||||||
def initialize(tag_name, markup, tokens)
|
def initialize(tag_name, markup, options)
|
||||||
|
super
|
||||||
|
|
||||||
if markup =~ Syntax
|
if markup =~ Syntax
|
||||||
@variable_name = $1
|
@variable_name = $1
|
||||||
@attributes = {}
|
@attributes = {}
|
||||||
else
|
else
|
||||||
raise SyntaxError.new("Syntax Error in 'comment_form' - Valid syntax: comment_form [article]")
|
raise SyntaxError.new("Syntax Error in 'comment_form' - Valid syntax: comment_form [article]")
|
||||||
end
|
end
|
||||||
|
|
||||||
super
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def render(context)
|
def render(context)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
require 'yaml'
|
require 'yaml'
|
||||||
module Database
|
|
||||||
|
|
||||||
|
module Database
|
||||||
# Load the standard vision toolkit database and re-arrage it to be simply exportable
|
# Load the standard vision toolkit database and re-arrage it to be simply exportable
|
||||||
# to liquid as assigns. All this is based on Shopify
|
# to liquid as assigns. All this is based on Shopify
|
||||||
def self.tables
|
def self.tables
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
|
require 'json'
|
||||||
|
|
||||||
module JsonFilter
|
module JsonFilter
|
||||||
|
|
||||||
def json(object)
|
def json(object)
|
||||||
object.reject {|k,v| k == "collections" }.to_json
|
JSON.dump(object.reject {|k,v| k == "collections" })
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
class Paginate < Liquid::Block
|
class Paginate < Liquid::Block
|
||||||
Syntax = /(#{Liquid::QuotedFragment})\s*(by\s*(\d+))?/
|
Syntax = /(#{Liquid::QuotedFragment})\s*(by\s*(\d+))?/
|
||||||
|
|
||||||
def initialize(tag_name, markup, tokens)
|
def initialize(tag_name, markup, options)
|
||||||
@nodelist = []
|
super
|
||||||
|
|
||||||
if markup =~ Syntax
|
if markup =~ Syntax
|
||||||
@collection_name = $1
|
@collection_name = $1
|
||||||
@@ -19,8 +19,6 @@ class Paginate < Liquid::Block
|
|||||||
else
|
else
|
||||||
raise SyntaxError.new("Syntax Error in tag 'paginate' - Valid syntax: paginate [collection] by number")
|
raise SyntaxError.new("Syntax Error in tag 'paginate' - Valid syntax: paginate [collection] by number")
|
||||||
end
|
end
|
||||||
|
|
||||||
super
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def render(context)
|
def render(context)
|
||||||
@@ -73,7 +71,7 @@ class Paginate < Liquid::Block
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
render_all(@nodelist, context)
|
super
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -45,16 +45,16 @@ module ShopFilter
|
|||||||
end
|
end
|
||||||
|
|
||||||
def url_for_vendor(vendor_title)
|
def url_for_vendor(vendor_title)
|
||||||
"/collections/#{vendor_title.to_handle}"
|
"/collections/#{to_handle(vendor_title)}"
|
||||||
end
|
end
|
||||||
|
|
||||||
def url_for_type(type_title)
|
def url_for_type(type_title)
|
||||||
"/collections/#{type_title.to_handle}"
|
"/collections/#{to_handle(type_title)}"
|
||||||
end
|
end
|
||||||
|
|
||||||
def product_img_url(url, style = 'small')
|
def product_img_url(url, style = 'small')
|
||||||
|
|
||||||
unless url =~ /^products\/([\w\-\_]+)\.(\w{2,4})/
|
unless url =~ /\Aproducts\/([\w\-\_]+)\.(\w{2,4})/
|
||||||
raise ArgumentError, 'filter "size" can only be called on product images'
|
raise ArgumentError, 'filter "size" can only be called on product images'
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -95,4 +95,16 @@ module ShopFilter
|
|||||||
input == 1 ? singular : plural
|
input == 1 ? singular : plural
|
||||||
end
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def to_handle(str)
|
||||||
|
result = str.dup
|
||||||
|
result.downcase!
|
||||||
|
result.delete!("'\"()[]")
|
||||||
|
result.gsub!(/\W+/, '-')
|
||||||
|
result.gsub!(/-+\z/, '') if result[-1] == '-'
|
||||||
|
result.gsub!(/\A-+/, '') if result[0] == '-'
|
||||||
|
result
|
||||||
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -6,10 +6,6 @@
|
|||||||
# Shopify which is likely the biggest user of liquid in the world which something to the tune of several
|
# Shopify which is likely the biggest user of liquid in the world which something to the tune of several
|
||||||
# million Template#render calls a day.
|
# million Template#render calls a day.
|
||||||
|
|
||||||
require 'rubygems'
|
|
||||||
require 'active_support'
|
|
||||||
require 'yaml'
|
|
||||||
require 'digest/md5'
|
|
||||||
require File.dirname(__FILE__) + '/shopify/liquid'
|
require File.dirname(__FILE__) + '/shopify/liquid'
|
||||||
require File.dirname(__FILE__) + '/shopify/database.rb'
|
require File.dirname(__FILE__) + '/shopify/database.rb'
|
||||||
|
|
||||||
@@ -64,57 +60,19 @@ class ThemeRunner
|
|||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
def run_profile
|
|
||||||
RubyProf.measure_mode = RubyProf::WALL_TIME
|
|
||||||
|
|
||||||
# Dup assigns because will make some changes to them
|
|
||||||
assigns = Database.tables.dup
|
|
||||||
|
|
||||||
@tests.each do |liquid, layout, template_name|
|
|
||||||
|
|
||||||
# Compute page_tempalte outside of profiler run, uninteresting to profiler
|
|
||||||
html = nil
|
|
||||||
page_template = File.basename(template_name, File.extname(template_name))
|
|
||||||
|
|
||||||
unless @started
|
|
||||||
RubyProf.start
|
|
||||||
RubyProf.pause
|
|
||||||
@started = true
|
|
||||||
end
|
|
||||||
|
|
||||||
html = nil
|
|
||||||
|
|
||||||
RubyProf.resume
|
|
||||||
html = compile_and_render(liquid, layout, assigns, page_template, template_name)
|
|
||||||
RubyProf.pause
|
|
||||||
|
|
||||||
|
|
||||||
# return the result and the MD5 of the content, this can be used to detect regressions between liquid version
|
|
||||||
$stdout.puts "* rendered template %s, content: %s" % [template_name, Digest::MD5.hexdigest(html)]
|
|
||||||
|
|
||||||
# Uncomment to dump html files to /tmp so that you can inspect for errors
|
|
||||||
# File.open("/tmp/#{File.basename(template_name)}.html", "w+") { |fp| fp <<html}
|
|
||||||
end
|
|
||||||
|
|
||||||
RubyProf.stop
|
|
||||||
end
|
|
||||||
|
|
||||||
def compile_and_render(template, layout, assigns, page_template, template_file)
|
def compile_and_render(template, layout, assigns, page_template, template_file)
|
||||||
tmpl = Liquid::Template.new
|
tmpl = Liquid::Template.new
|
||||||
tmpl.assigns['page_title'] = 'Page title'
|
tmpl.assigns['page_title'] = 'Page title'
|
||||||
tmpl.assigns['template'] = page_template
|
tmpl.assigns['template'] = page_template
|
||||||
tmpl.registers[:file_system] = ThemeRunner::FileSystem.new(File.dirname(template_file))
|
tmpl.registers[:file_system] = ThemeRunner::FileSystem.new(File.dirname(template_file))
|
||||||
|
|
||||||
content_for_layout = tmpl.parse(template).render(assigns)
|
content_for_layout = tmpl.parse(template).render!(assigns)
|
||||||
|
|
||||||
if layout
|
if layout
|
||||||
assigns['content_for_layout'] = content_for_layout
|
assigns['content_for_layout'] = content_for_layout
|
||||||
tmpl.parse(layout).render(assigns)
|
tmpl.parse(layout).render!(assigns)
|
||||||
else
|
else
|
||||||
content_for_layout
|
content_for_layout
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,18 @@
|
|||||||
require 'test_helper'
|
require 'test_helper'
|
||||||
|
|
||||||
class AssignTest < Test::Unit::TestCase
|
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] }}.',
|
||||||
@@ -24,4 +34,15 @@ class AssignTest < Test::Unit::TestCase
|
|||||||
'{% assign foo not values %}.',
|
'{% assign foo not values %}.',
|
||||||
'values' => "foo,bar,baz")
|
'values' => "foo,bar,baz")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_assign_uses_error_mode
|
||||||
|
with_error_mode(:strict) do
|
||||||
|
assert_raises(SyntaxError) do
|
||||||
|
Template.parse("{% assign foo = ('X' | downcase) %}")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
with_error_mode(:lax) do
|
||||||
|
assert Template.parse("{% assign foo = ('X' | downcase) %}")
|
||||||
|
end
|
||||||
|
end
|
||||||
end # AssignTest
|
end # AssignTest
|
||||||
@@ -14,7 +14,7 @@ class BlankTestFileSystem
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
class BlankTest < Test::Unit::TestCase
|
class BlankTest < Minitest::Test
|
||||||
include Liquid
|
include Liquid
|
||||||
N = 10
|
N = 10
|
||||||
|
|
||||||
@@ -93,9 +93,9 @@ class BlankTest < Test::Unit::TestCase
|
|||||||
|
|
||||||
def test_include_is_blank
|
def test_include_is_blank
|
||||||
Liquid::Template.file_system = BlankTestFileSystem.new
|
Liquid::Template.file_system = BlankTestFileSystem.new
|
||||||
assert_equal "foobar"*(N+1), Template.parse(wrap("{% include 'foobar' %}")).render()
|
assert_template_result "foobar"*(N+1), wrap("{% include 'foobar' %}")
|
||||||
assert_equal " foobar "*(N+1), Template.parse(wrap("{% include ' foobar ' %}")).render()
|
assert_template_result " foobar "*(N+1), wrap("{% include ' foobar ' %}")
|
||||||
assert_equal " ", Template.parse(" {% include ' ' %} ").render()
|
assert_template_result " "*(N+1), wrap(" {% include ' ' %} ")
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_case_is_blank
|
def test_case_is_blank
|
||||||
@@ -1,12 +1,22 @@
|
|||||||
require 'test_helper'
|
require 'test_helper'
|
||||||
|
|
||||||
class CaptureTest < Test::Unit::TestCase
|
class CaptureTest < Minitest::Test
|
||||||
include Liquid
|
include Liquid
|
||||||
|
|
||||||
def test_captures_block_content_in_variable
|
def test_captures_block_content_in_variable
|
||||||
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 = '' %}
|
||||||
@@ -19,7 +29,7 @@ class CaptureTest < Test::Unit::TestCase
|
|||||||
{{var}}
|
{{var}}
|
||||||
END_TEMPLATE
|
END_TEMPLATE
|
||||||
template = Template.parse(template_source)
|
template = Template.parse(template_source)
|
||||||
rendered = template.render
|
rendered = template.render!
|
||||||
assert_equal "test-string", rendered.gsub(/\s/, '')
|
assert_equal "test-string", rendered.gsub(/\s/, '')
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -34,7 +44,7 @@ class CaptureTest < Test::Unit::TestCase
|
|||||||
{{ first }}-{{ second }}
|
{{ first }}-{{ second }}
|
||||||
END_TEMPLATE
|
END_TEMPLATE
|
||||||
template = Template.parse(template_source)
|
template = Template.parse(template_source)
|
||||||
rendered = template.render
|
rendered = template.render!
|
||||||
assert_equal "3-3", rendered.gsub(/\s/, '')
|
assert_equal "3-3", rendered.gsub(/\s/, '')
|
||||||
end
|
end
|
||||||
end # CaptureTest
|
end # CaptureTest
|
||||||
32
test/integration/context_test.rb
Normal file
32
test/integration/context_test.rb
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
require 'test_helper'
|
||||||
|
|
||||||
|
class ContextTest < Minitest::Test
|
||||||
|
include Liquid
|
||||||
|
|
||||||
|
def test_override_global_filter
|
||||||
|
global = Module.new do
|
||||||
|
def notice(output)
|
||||||
|
"Global #{output}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local = Module.new do
|
||||||
|
def notice(output)
|
||||||
|
"Local #{output}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
with_global_filter(global) do
|
||||||
|
assert_equal 'Global test', Template.parse("{{'test' | notice }}").render!
|
||||||
|
assert_equal 'Local test', Template.parse("{{'test' | notice }}").render!({}, :filters => [local])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_has_key_will_not_add_an_error_for_missing_keys
|
||||||
|
with_error_mode :strict do
|
||||||
|
context = Context.new
|
||||||
|
context.has_key?('unknown')
|
||||||
|
assert_empty context.errors
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -48,6 +48,10 @@ class ProductDrop < Liquid::Drop
|
|||||||
ContextDrop.new
|
ContextDrop.new
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def user_input
|
||||||
|
"foo".taint
|
||||||
|
end
|
||||||
|
|
||||||
protected
|
protected
|
||||||
def callmenot
|
def callmenot
|
||||||
"protected"
|
"protected"
|
||||||
@@ -100,146 +104,168 @@ class RealEnumerableDrop < Liquid::Drop
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
class DropsTest < Test::Unit::TestCase
|
class DropsTest < Minitest::Test
|
||||||
include Liquid
|
include Liquid
|
||||||
|
|
||||||
def test_product_drop
|
def test_product_drop
|
||||||
assert_nothing_raised do
|
tpl = Liquid::Template.parse(' ')
|
||||||
tpl = Liquid::Template.parse( ' ' )
|
assert_equal ' ', tpl.render!('product' => ProductDrop.new)
|
||||||
tpl.render('product' => ProductDrop.new)
|
end
|
||||||
|
|
||||||
|
def test_rendering_raises_on_tainted_attr
|
||||||
|
with_taint_mode(:error) do
|
||||||
|
tpl = Liquid::Template.parse('{{ product.user_input }}')
|
||||||
|
assert_raises TaintedError do
|
||||||
|
tpl.render!('product' => ProductDrop.new)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_rendering_warns_on_tainted_attr
|
||||||
|
with_taint_mode(:warn) do
|
||||||
|
tpl = Liquid::Template.parse('{{ product.user_input }}')
|
||||||
|
tpl.render!('product' => ProductDrop.new)
|
||||||
|
assert_match /tainted/, tpl.warnings.first
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_rendering_doesnt_raise_on_escaped_tainted_attr
|
||||||
|
with_taint_mode(:error) do
|
||||||
|
tpl = Liquid::Template.parse('{{ product.user_input | escape }}')
|
||||||
|
tpl.render!('product' => ProductDrop.new)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_drop_does_only_respond_to_whitelisted_methods
|
def test_drop_does_only_respond_to_whitelisted_methods
|
||||||
assert_equal "", Liquid::Template.parse("{{ product.inspect }}").render('product' => ProductDrop.new)
|
assert_equal "", Liquid::Template.parse("{{ product.inspect }}").render!('product' => ProductDrop.new)
|
||||||
assert_equal "", Liquid::Template.parse("{{ product.pretty_inspect }}").render('product' => ProductDrop.new)
|
assert_equal "", Liquid::Template.parse("{{ product.pretty_inspect }}").render!('product' => ProductDrop.new)
|
||||||
assert_equal "", Liquid::Template.parse("{{ product.whatever }}").render('product' => ProductDrop.new)
|
assert_equal "", Liquid::Template.parse("{{ product.whatever }}").render!('product' => ProductDrop.new)
|
||||||
assert_equal "", Liquid::Template.parse('{{ product | map: "inspect" }}').render('product' => ProductDrop.new)
|
assert_equal "", Liquid::Template.parse('{{ product | map: "inspect" }}').render!('product' => ProductDrop.new)
|
||||||
assert_equal "", Liquid::Template.parse('{{ product | map: "pretty_inspect" }}').render('product' => ProductDrop.new)
|
assert_equal "", Liquid::Template.parse('{{ product | map: "pretty_inspect" }}').render!('product' => ProductDrop.new)
|
||||||
assert_equal "", Liquid::Template.parse('{{ product | map: "whatever" }}').render('product' => ProductDrop.new)
|
assert_equal "", Liquid::Template.parse('{{ product | map: "whatever" }}').render!('product' => ProductDrop.new)
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_drops_respond_to_to_liquid
|
def test_drops_respond_to_to_liquid
|
||||||
assert_equal "text1", Liquid::Template.parse("{{ product.to_liquid.texts.text }}").render('product' => ProductDrop.new)
|
assert_equal "text1", Liquid::Template.parse("{{ product.to_liquid.texts.text }}").render!('product' => ProductDrop.new)
|
||||||
assert_equal "text1", Liquid::Template.parse('{{ product | map: "to_liquid" | map: "texts" | map: "text" }}').render('product' => ProductDrop.new)
|
assert_equal "text1", Liquid::Template.parse('{{ product | map: "to_liquid" | map: "texts" | map: "text" }}').render!('product' => ProductDrop.new)
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_text_drop
|
def test_text_drop
|
||||||
output = Liquid::Template.parse( ' {{ product.texts.text }} ' ).render('product' => ProductDrop.new)
|
output = Liquid::Template.parse( ' {{ product.texts.text }} ' ).render!('product' => ProductDrop.new)
|
||||||
assert_equal ' text1 ', output
|
assert_equal ' text1 ', output
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_unknown_method
|
def test_unknown_method
|
||||||
output = Liquid::Template.parse( ' {{ product.catchall.unknown }} ' ).render('product' => ProductDrop.new)
|
output = Liquid::Template.parse( ' {{ product.catchall.unknown }} ' ).render!('product' => ProductDrop.new)
|
||||||
assert_equal ' method: unknown ', output
|
assert_equal ' method: unknown ', output
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_integer_argument_drop
|
def test_integer_argument_drop
|
||||||
output = Liquid::Template.parse( ' {{ product.catchall[8] }} ' ).render('product' => ProductDrop.new)
|
output = Liquid::Template.parse( ' {{ product.catchall[8] }} ' ).render!('product' => ProductDrop.new)
|
||||||
assert_equal ' method: 8 ', output
|
assert_equal ' method: 8 ', output
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_text_array_drop
|
def test_text_array_drop
|
||||||
output = Liquid::Template.parse( '{% for text in product.texts.array %} {{text}} {% endfor %}' ).render('product' => ProductDrop.new)
|
output = Liquid::Template.parse( '{% for text in product.texts.array %} {{text}} {% endfor %}' ).render!('product' => ProductDrop.new)
|
||||||
assert_equal ' text1 text2 ', output
|
assert_equal ' text1 text2 ', output
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_context_drop
|
def test_context_drop
|
||||||
output = Liquid::Template.parse( ' {{ context.bar }} ' ).render('context' => ContextDrop.new, 'bar' => "carrot")
|
output = Liquid::Template.parse( ' {{ context.bar }} ' ).render!('context' => ContextDrop.new, 'bar' => "carrot")
|
||||||
assert_equal ' carrot ', output
|
assert_equal ' carrot ', output
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_nested_context_drop
|
def test_nested_context_drop
|
||||||
output = Liquid::Template.parse( ' {{ product.context.foo }} ' ).render('product' => ProductDrop.new, 'foo' => "monkey")
|
output = Liquid::Template.parse( ' {{ product.context.foo }} ' ).render!('product' => ProductDrop.new, 'foo' => "monkey")
|
||||||
assert_equal ' monkey ', output
|
assert_equal ' monkey ', output
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_protected
|
def test_protected
|
||||||
output = Liquid::Template.parse( ' {{ product.callmenot }} ' ).render('product' => ProductDrop.new)
|
output = Liquid::Template.parse( ' {{ product.callmenot }} ' ).render!('product' => ProductDrop.new)
|
||||||
assert_equal ' ', output
|
assert_equal ' ', output
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_object_methods_not_allowed
|
def test_object_methods_not_allowed
|
||||||
[:dup, :clone, :singleton_class, :eval, :class_eval, :inspect].each do |method|
|
[:dup, :clone, :singleton_class, :eval, :class_eval, :inspect].each do |method|
|
||||||
output = Liquid::Template.parse(" {{ product.#{method} }} ").render('product' => ProductDrop.new)
|
output = Liquid::Template.parse(" {{ product.#{method} }} ").render!('product' => ProductDrop.new)
|
||||||
assert_equal ' ', output
|
assert_equal ' ', output
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_scope
|
def test_scope
|
||||||
assert_equal '1', Liquid::Template.parse( '{{ context.scopes }}' ).render('context' => ContextDrop.new)
|
assert_equal '1', Liquid::Template.parse( '{{ context.scopes }}' ).render!('context' => ContextDrop.new)
|
||||||
assert_equal '2', Liquid::Template.parse( '{%for i in dummy%}{{ context.scopes }}{%endfor%}' ).render('context' => ContextDrop.new, 'dummy' => [1])
|
assert_equal '2', Liquid::Template.parse( '{%for i in dummy%}{{ context.scopes }}{%endfor%}' ).render!('context' => ContextDrop.new, 'dummy' => [1])
|
||||||
assert_equal '3', Liquid::Template.parse( '{%for i in dummy%}{%for i in dummy%}{{ context.scopes }}{%endfor%}{%endfor%}' ).render('context' => ContextDrop.new, 'dummy' => [1])
|
assert_equal '3', Liquid::Template.parse( '{%for i in dummy%}{%for i in dummy%}{{ context.scopes }}{%endfor%}{%endfor%}' ).render!('context' => ContextDrop.new, 'dummy' => [1])
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_scope_though_proc
|
def test_scope_though_proc
|
||||||
assert_equal '1', Liquid::Template.parse( '{{ s }}' ).render('context' => ContextDrop.new, 's' => Proc.new{|c| c['context.scopes'] })
|
assert_equal '1', Liquid::Template.parse( '{{ s }}' ).render!('context' => ContextDrop.new, 's' => Proc.new{|c| c['context.scopes'] })
|
||||||
assert_equal '2', Liquid::Template.parse( '{%for i in dummy%}{{ s }}{%endfor%}' ).render('context' => ContextDrop.new, 's' => Proc.new{|c| c['context.scopes'] }, 'dummy' => [1])
|
assert_equal '2', Liquid::Template.parse( '{%for i in dummy%}{{ s }}{%endfor%}' ).render!('context' => ContextDrop.new, 's' => Proc.new{|c| c['context.scopes'] }, 'dummy' => [1])
|
||||||
assert_equal '3', Liquid::Template.parse( '{%for i in dummy%}{%for i in dummy%}{{ s }}{%endfor%}{%endfor%}' ).render('context' => ContextDrop.new, 's' => Proc.new{|c| c['context.scopes'] }, 'dummy' => [1])
|
assert_equal '3', Liquid::Template.parse( '{%for i in dummy%}{%for i in dummy%}{{ s }}{%endfor%}{%endfor%}' ).render!('context' => ContextDrop.new, 's' => Proc.new{|c| c['context.scopes'] }, 'dummy' => [1])
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_scope_with_assigns
|
def test_scope_with_assigns
|
||||||
assert_equal 'variable', Liquid::Template.parse( '{% assign a = "variable"%}{{a}}' ).render('context' => ContextDrop.new)
|
assert_equal 'variable', Liquid::Template.parse( '{% assign a = "variable"%}{{a}}' ).render!('context' => ContextDrop.new)
|
||||||
assert_equal 'variable', Liquid::Template.parse( '{% assign a = "variable"%}{%for i in dummy%}{{a}}{%endfor%}' ).render('context' => ContextDrop.new, 'dummy' => [1])
|
assert_equal 'variable', Liquid::Template.parse( '{% assign a = "variable"%}{%for i in dummy%}{{a}}{%endfor%}' ).render!('context' => ContextDrop.new, 'dummy' => [1])
|
||||||
assert_equal 'test', Liquid::Template.parse( '{% assign header_gif = "test"%}{{header_gif}}' ).render('context' => ContextDrop.new)
|
assert_equal 'test', Liquid::Template.parse( '{% assign header_gif = "test"%}{{header_gif}}' ).render!('context' => ContextDrop.new)
|
||||||
assert_equal 'test', Liquid::Template.parse( "{% assign header_gif = 'test'%}{{header_gif}}" ).render('context' => ContextDrop.new)
|
assert_equal 'test', Liquid::Template.parse( "{% assign header_gif = 'test'%}{{header_gif}}" ).render!('context' => ContextDrop.new)
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_scope_from_tags
|
def test_scope_from_tags
|
||||||
assert_equal '1', Liquid::Template.parse( '{% for i in context.scopes_as_array %}{{i}}{% endfor %}' ).render('context' => ContextDrop.new, 'dummy' => [1])
|
assert_equal '1', Liquid::Template.parse( '{% for i in context.scopes_as_array %}{{i}}{% endfor %}' ).render!('context' => ContextDrop.new, 'dummy' => [1])
|
||||||
assert_equal '12', Liquid::Template.parse( '{%for a in dummy%}{% for i in context.scopes_as_array %}{{i}}{% endfor %}{% endfor %}' ).render('context' => ContextDrop.new, 'dummy' => [1])
|
assert_equal '12', Liquid::Template.parse( '{%for a in dummy%}{% for i in context.scopes_as_array %}{{i}}{% endfor %}{% endfor %}' ).render!('context' => ContextDrop.new, 'dummy' => [1])
|
||||||
assert_equal '123', Liquid::Template.parse( '{%for a in dummy%}{%for a in dummy%}{% for i in context.scopes_as_array %}{{i}}{% endfor %}{% endfor %}{% endfor %}' ).render('context' => ContextDrop.new, 'dummy' => [1])
|
assert_equal '123', Liquid::Template.parse( '{%for a in dummy%}{%for a in dummy%}{% for i in context.scopes_as_array %}{{i}}{% endfor %}{% endfor %}{% endfor %}' ).render!('context' => ContextDrop.new, 'dummy' => [1])
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_access_context_from_drop
|
def test_access_context_from_drop
|
||||||
assert_equal '123', Liquid::Template.parse( '{%for a in dummy%}{{ context.loop_pos }}{% endfor %}' ).render('context' => ContextDrop.new, 'dummy' => [1,2,3])
|
assert_equal '123', Liquid::Template.parse( '{%for a in dummy%}{{ context.loop_pos }}{% endfor %}' ).render!('context' => ContextDrop.new, 'dummy' => [1,2,3])
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_enumerable_drop
|
def test_enumerable_drop
|
||||||
assert_equal '123', Liquid::Template.parse( '{% for c in collection %}{{c}}{% endfor %}').render('collection' => EnumerableDrop.new)
|
assert_equal '123', Liquid::Template.parse( '{% for c in collection %}{{c}}{% endfor %}').render!('collection' => EnumerableDrop.new)
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_enumerable_drop_size
|
def test_enumerable_drop_size
|
||||||
assert_equal '3', Liquid::Template.parse( '{{collection.size}}').render('collection' => EnumerableDrop.new)
|
assert_equal '3', Liquid::Template.parse( '{{collection.size}}').render!('collection' => EnumerableDrop.new)
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_enumerable_drop_will_invoke_before_method_for_clashing_method_names
|
def test_enumerable_drop_will_invoke_before_method_for_clashing_method_names
|
||||||
["select", "each", "map", "cycle"].each do |method|
|
["select", "each", "map", "cycle"].each do |method|
|
||||||
assert_equal method.to_s, Liquid::Template.parse("{{collection.#{method}}}").render('collection' => EnumerableDrop.new)
|
assert_equal method.to_s, Liquid::Template.parse("{{collection.#{method}}}").render!('collection' => EnumerableDrop.new)
|
||||||
assert_equal method.to_s, Liquid::Template.parse("{{collection[\"#{method}\"]}}").render('collection' => EnumerableDrop.new)
|
assert_equal method.to_s, Liquid::Template.parse("{{collection[\"#{method}\"]}}").render!('collection' => EnumerableDrop.new)
|
||||||
assert_equal method.to_s, Liquid::Template.parse("{{collection.#{method}}}").render('collection' => RealEnumerableDrop.new)
|
assert_equal method.to_s, Liquid::Template.parse("{{collection.#{method}}}").render!('collection' => RealEnumerableDrop.new)
|
||||||
assert_equal method.to_s, Liquid::Template.parse("{{collection[\"#{method}\"]}}").render('collection' => RealEnumerableDrop.new)
|
assert_equal method.to_s, Liquid::Template.parse("{{collection[\"#{method}\"]}}").render!('collection' => RealEnumerableDrop.new)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_some_enumerable_methods_still_get_invoked
|
def test_some_enumerable_methods_still_get_invoked
|
||||||
[ :count, :max ].each do |method|
|
[ :count, :max ].each do |method|
|
||||||
assert_equal "3", Liquid::Template.parse("{{collection.#{method}}}").render('collection' => RealEnumerableDrop.new)
|
assert_equal "3", Liquid::Template.parse("{{collection.#{method}}}").render!('collection' => RealEnumerableDrop.new)
|
||||||
assert_equal "3", Liquid::Template.parse("{{collection[\"#{method}\"]}}").render('collection' => RealEnumerableDrop.new)
|
assert_equal "3", Liquid::Template.parse("{{collection[\"#{method}\"]}}").render!('collection' => RealEnumerableDrop.new)
|
||||||
assert_equal "3", Liquid::Template.parse("{{collection.#{method}}}").render('collection' => EnumerableDrop.new)
|
assert_equal "3", Liquid::Template.parse("{{collection.#{method}}}").render!('collection' => EnumerableDrop.new)
|
||||||
assert_equal "3", Liquid::Template.parse("{{collection[\"#{method}\"]}}").render('collection' => EnumerableDrop.new)
|
assert_equal "3", Liquid::Template.parse("{{collection[\"#{method}\"]}}").render!('collection' => EnumerableDrop.new)
|
||||||
end
|
end
|
||||||
|
|
||||||
assert_equal "yes", Liquid::Template.parse("{% if collection contains 3 %}yes{% endif %}").render('collection' => RealEnumerableDrop.new)
|
assert_equal "yes", Liquid::Template.parse("{% if collection contains 3 %}yes{% endif %}").render!('collection' => RealEnumerableDrop.new)
|
||||||
|
|
||||||
[ :min, :first ].each do |method|
|
[ :min, :first ].each do |method|
|
||||||
assert_equal "1", Liquid::Template.parse("{{collection.#{method}}}").render('collection' => RealEnumerableDrop.new)
|
assert_equal "1", Liquid::Template.parse("{{collection.#{method}}}").render!('collection' => RealEnumerableDrop.new)
|
||||||
assert_equal "1", Liquid::Template.parse("{{collection[\"#{method}\"]}}").render('collection' => RealEnumerableDrop.new)
|
assert_equal "1", Liquid::Template.parse("{{collection[\"#{method}\"]}}").render!('collection' => RealEnumerableDrop.new)
|
||||||
assert_equal "1", Liquid::Template.parse("{{collection.#{method}}}").render('collection' => EnumerableDrop.new)
|
assert_equal "1", Liquid::Template.parse("{{collection.#{method}}}").render!('collection' => EnumerableDrop.new)
|
||||||
assert_equal "1", Liquid::Template.parse("{{collection[\"#{method}\"]}}").render('collection' => EnumerableDrop.new)
|
assert_equal "1", Liquid::Template.parse("{{collection[\"#{method}\"]}}").render!('collection' => EnumerableDrop.new)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_empty_string_value_access
|
def test_empty_string_value_access
|
||||||
assert_equal '', Liquid::Template.parse('{{ product[value] }}').render('product' => ProductDrop.new, 'value' => '')
|
assert_equal '', Liquid::Template.parse('{{ product[value] }}').render!('product' => ProductDrop.new, 'value' => '')
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_nil_value_access
|
def test_nil_value_access
|
||||||
assert_equal '', Liquid::Template.parse('{{ product[value] }}').render('product' => ProductDrop.new, 'value' => nil)
|
assert_equal '', Liquid::Template.parse('{{ product[value] }}').render!('product' => ProductDrop.new, 'value' => nil)
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_default_to_s_on_drops
|
def test_default_to_s_on_drops
|
||||||
assert_equal 'ProductDrop', Liquid::Template.parse("{{ product }}").render('product' => ProductDrop.new)
|
assert_equal 'ProductDrop', Liquid::Template.parse("{{ product }}").render!('product' => ProductDrop.new)
|
||||||
assert_equal 'EnumerableDrop', Liquid::Template.parse('{{ collection }}').render('collection' => EnumerableDrop.new)
|
assert_equal 'EnumerableDrop', Liquid::Template.parse('{{ collection }}').render!('collection' => EnumerableDrop.new)
|
||||||
end
|
end
|
||||||
end # DropsTest
|
end # DropsTest
|
||||||
207
test/integration/error_handling_test.rb
Normal file
207
test/integration/error_handling_test.rb
Normal file
@@ -0,0 +1,207 @@
|
|||||||
|
require 'test_helper'
|
||||||
|
|
||||||
|
class ErrorDrop < Liquid::Drop
|
||||||
|
def standard_error
|
||||||
|
raise Liquid::StandardError, 'standard error'
|
||||||
|
end
|
||||||
|
|
||||||
|
def argument_error
|
||||||
|
raise Liquid::ArgumentError, 'argument error'
|
||||||
|
end
|
||||||
|
|
||||||
|
def syntax_error
|
||||||
|
raise Liquid::SyntaxError, 'syntax error'
|
||||||
|
end
|
||||||
|
|
||||||
|
def exception
|
||||||
|
raise Exception, 'exception'
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
class ErrorHandlingTest < Minitest::Test
|
||||||
|
include Liquid
|
||||||
|
|
||||||
|
def test_templates_parsed_with_line_numbers_renders_them_in_errors
|
||||||
|
template = <<-LIQUID
|
||||||
|
Hello,
|
||||||
|
|
||||||
|
{{ errors.standard_error }} will raise a standard error.
|
||||||
|
|
||||||
|
Bla bla test.
|
||||||
|
|
||||||
|
{{ errors.syntax_error }} will raise a syntax error.
|
||||||
|
|
||||||
|
This is an argument error: {{ errors.argument_error }}
|
||||||
|
|
||||||
|
Bla.
|
||||||
|
LIQUID
|
||||||
|
|
||||||
|
expected = <<-TEXT
|
||||||
|
Hello,
|
||||||
|
|
||||||
|
Liquid error (line 3): standard error will raise a standard error.
|
||||||
|
|
||||||
|
Bla bla test.
|
||||||
|
|
||||||
|
Liquid syntax error (line 7): syntax error will raise a syntax error.
|
||||||
|
|
||||||
|
This is an argument error: Liquid error (line 9): argument error
|
||||||
|
|
||||||
|
Bla.
|
||||||
|
TEXT
|
||||||
|
|
||||||
|
output = Liquid::Template.parse(template, line_numbers: true).render('errors' => ErrorDrop.new)
|
||||||
|
assert_equal expected, output
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_standard_error
|
||||||
|
template = Liquid::Template.parse( ' {{ errors.standard_error }} ' )
|
||||||
|
assert_equal ' Liquid error: standard error ', template.render('errors' => ErrorDrop.new)
|
||||||
|
|
||||||
|
assert_equal 1, template.errors.size
|
||||||
|
assert_equal StandardError, template.errors.first.class
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_syntax
|
||||||
|
template = Liquid::Template.parse( ' {{ errors.syntax_error }} ' )
|
||||||
|
assert_equal ' Liquid syntax error: syntax error ', template.render('errors' => ErrorDrop.new)
|
||||||
|
|
||||||
|
assert_equal 1, template.errors.size
|
||||||
|
assert_equal SyntaxError, template.errors.first.class
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_argument
|
||||||
|
template = Liquid::Template.parse( ' {{ errors.argument_error }} ' )
|
||||||
|
assert_equal ' Liquid error: argument error ', template.render('errors' => ErrorDrop.new)
|
||||||
|
|
||||||
|
assert_equal 1, template.errors.size
|
||||||
|
assert_equal ArgumentError, template.errors.first.class
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_missing_endtag_parse_time_error
|
||||||
|
assert_raises(Liquid::SyntaxError) do
|
||||||
|
Liquid::Template.parse(' {% for a in b %} ... ')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_unrecognized_operator
|
||||||
|
with_error_mode(:strict) do
|
||||||
|
assert_raises(SyntaxError) do
|
||||||
|
Liquid::Template.parse(' {% if 1 =! 2 %}ok{% endif %} ')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_lax_unrecognized_operator
|
||||||
|
template = Liquid::Template.parse(' {% if 1 =! 2 %}ok{% endif %} ', :error_mode => :lax)
|
||||||
|
assert_equal ' Liquid error: Unknown operator =! ', template.render
|
||||||
|
assert_equal 1, template.errors.size
|
||||||
|
assert_equal Liquid::ArgumentError, template.errors.first.class
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_with_line_numbers_adds_numbers_to_parser_errors
|
||||||
|
err = assert_raises(SyntaxError) do
|
||||||
|
template = Liquid::Template.parse(%q{
|
||||||
|
foobar
|
||||||
|
|
||||||
|
{% "cat" | foobar %}
|
||||||
|
|
||||||
|
bla
|
||||||
|
},
|
||||||
|
:line_numbers => true
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
assert_match /Liquid syntax error \(line 4\)/, err.message
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_parsing_warn_with_line_numbers_adds_numbers_to_lexer_errors
|
||||||
|
template = Liquid::Template.parse(%q{
|
||||||
|
foobar
|
||||||
|
|
||||||
|
{% if 1 =! 2 %}ok{% endif %}
|
||||||
|
|
||||||
|
bla
|
||||||
|
},
|
||||||
|
:error_mode => :warn,
|
||||||
|
:line_numbers => true
|
||||||
|
)
|
||||||
|
|
||||||
|
assert_equal ['Liquid syntax error (line 4): Unexpected character = in "1 =! 2"'],
|
||||||
|
template.warnings.map(&:message)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_parsing_strict_with_line_numbers_adds_numbers_to_lexer_errors
|
||||||
|
err = assert_raises(SyntaxError) do
|
||||||
|
Liquid::Template.parse(%q{
|
||||||
|
foobar
|
||||||
|
|
||||||
|
{% if 1 =! 2 %}ok{% endif %}
|
||||||
|
|
||||||
|
bla
|
||||||
|
},
|
||||||
|
:error_mode => :strict,
|
||||||
|
:line_numbers => true
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
assert_equal 'Liquid syntax error (line 4): Unexpected character = in "1 =! 2"', err.message
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_syntax_errors_in_nested_blocks_have_correct_line_number
|
||||||
|
err = assert_raises(SyntaxError) do
|
||||||
|
Liquid::Template.parse(%q{
|
||||||
|
foobar
|
||||||
|
|
||||||
|
{% if 1 != 2 %}
|
||||||
|
{% foo %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
bla
|
||||||
|
},
|
||||||
|
:line_numbers => true
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
assert_equal "Liquid syntax error (line 5): Unknown tag 'foo'", err.message
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_strict_error_messages
|
||||||
|
err = assert_raises(SyntaxError) do
|
||||||
|
Liquid::Template.parse(' {% if 1 =! 2 %}ok{% endif %} ', :error_mode => :strict)
|
||||||
|
end
|
||||||
|
assert_equal 'Liquid syntax error: Unexpected character = in "1 =! 2"', err.message
|
||||||
|
|
||||||
|
err = assert_raises(SyntaxError) do
|
||||||
|
Liquid::Template.parse('{{%%%}}', :error_mode => :strict)
|
||||||
|
end
|
||||||
|
assert_equal 'Liquid syntax error: Unexpected character % in "{{%%%}}"', err.message
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_warnings
|
||||||
|
template = Liquid::Template.parse('{% if ~~~ %}{{%%%}}{% else %}{{ hello. }}{% endif %}', :error_mode => :warn)
|
||||||
|
assert_equal 3, template.warnings.size
|
||||||
|
assert_equal 'Unexpected character ~ in "~~~"', template.warnings[0].to_s(false)
|
||||||
|
assert_equal 'Unexpected character % in "{{%%%}}"', template.warnings[1].to_s(false)
|
||||||
|
assert_equal 'Expected id but found end_of_string in "{{ hello. }}"', template.warnings[2].to_s(false)
|
||||||
|
assert_equal '', template.render
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_warning_line_numbers
|
||||||
|
template = Liquid::Template.parse("{% if ~~~ %}\n{{%%%}}{% else %}\n{{ hello. }}{% endif %}", :error_mode => :warn, :line_numbers => true)
|
||||||
|
assert_equal 'Liquid syntax error (line 1): Unexpected character ~ in "~~~"', template.warnings[0].message
|
||||||
|
assert_equal 'Liquid syntax error (line 2): Unexpected character % in "{{%%%}}"', template.warnings[1].message
|
||||||
|
assert_equal 'Liquid syntax error (line 3): Expected id but found end_of_string in "{{ hello. }}"', template.warnings[2].message
|
||||||
|
assert_equal 3, template.warnings.size
|
||||||
|
assert_equal [1,2,3], template.warnings.map(&:line_number)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Liquid should not catch Exceptions that are not subclasses of StandardError, like Interrupt and NoMemoryError
|
||||||
|
def test_exceptions_propagate
|
||||||
|
assert_raises Exception do
|
||||||
|
template = Liquid::Template.parse('{{ errors.exception }}')
|
||||||
|
template.render('errors' => ErrorDrop.new)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -22,9 +22,15 @@ module SubstituteFilter
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
class FiltersTest < Test::Unit::TestCase
|
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
|
||||||
@@ -67,12 +73,12 @@ class FiltersTest < Test::Unit::TestCase
|
|||||||
@context['value'] = 3
|
@context['value'] = 3
|
||||||
@context['numbers'] = [2,1,4,3]
|
@context['numbers'] = [2,1,4,3]
|
||||||
@context['words'] = ['expected', 'as', 'alphabetic']
|
@context['words'] = ['expected', 'as', 'alphabetic']
|
||||||
@context['arrays'] = [['flattened'], ['are']]
|
@context['arrays'] = ['flower', 'are']
|
||||||
|
|
||||||
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', 'flattened'], Variable.new("arrays | sort").render(@context)
|
assert_equal ['are', 'flower'], Variable.new("arrays | sort").render(@context)
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_strip_html
|
def test_strip_html
|
||||||
@@ -105,21 +111,28 @@ class FiltersTest < Test::Unit::TestCase
|
|||||||
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 < Test::Unit::TestCase
|
class FiltersInTemplate < Minitest::Test
|
||||||
include Liquid
|
include Liquid
|
||||||
|
|
||||||
def test_local_global
|
def test_local_global
|
||||||
Template.register_filter(MoneyFilter)
|
with_global_filter(MoneyFilter) do
|
||||||
|
assert_equal " 1000$ ", Template.parse("{{1000 | money}}").render!(nil, nil)
|
||||||
assert_equal " 1000$ ", Template.parse("{{1000 | money}}").render(nil, nil)
|
assert_equal " 1000$ CAD ", Template.parse("{{1000 | money}}").render!(nil, :filters => CanadianMoneyFilter)
|
||||||
assert_equal " 1000$ CAD ", Template.parse("{{1000 | money}}").render(nil, :filters => CanadianMoneyFilter)
|
assert_equal " 1000$ CAD ", Template.parse("{{1000 | money}}").render!(nil, :filters => [CanadianMoneyFilter])
|
||||||
assert_equal " 1000$ CAD ", Template.parse("{{1000 | money}}").render(nil, :filters => [CanadianMoneyFilter])
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_local_filter_with_deprecated_syntax
|
def test_local_filter_with_deprecated_syntax
|
||||||
assert_equal " 1000$ CAD ", Template.parse("{{1000 | money}}").render(nil, CanadianMoneyFilter)
|
assert_equal " 1000$ CAD ", Template.parse("{{1000 | money}}").render!(nil, CanadianMoneyFilter)
|
||||||
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
|
||||||
23
test/integration/hash_ordering_test.rb
Normal file
23
test/integration/hash_ordering_test.rb
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
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 < Minitest::Test
|
||||||
|
include Liquid
|
||||||
|
|
||||||
|
def test_global_register_order
|
||||||
|
with_global_filter(MoneyFilter, CanadianMoneyFilter) do
|
||||||
|
assert_equal " 1000$ CAD ", Template.parse("{{1000 | money}}").render(nil, nil)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -27,7 +27,7 @@ module FunnyFilter
|
|||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
class OutputTest < Test::Unit::TestCase
|
class OutputTest < Minitest::Test
|
||||||
include Liquid
|
include Liquid
|
||||||
|
|
||||||
def setup
|
def setup
|
||||||
@@ -41,76 +41,84 @@ class OutputTest < Test::Unit::TestCase
|
|||||||
text = %| {{best_cars}} |
|
text = %| {{best_cars}} |
|
||||||
|
|
||||||
expected = %| bmw |
|
expected = %| bmw |
|
||||||
assert_equal expected, Template.parse(text).render(@assigns)
|
assert_equal expected, Template.parse(text).render!(@assigns)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_variable_traversing_with_two_brackets
|
||||||
|
text = %({{ site.data.menu[include.menu][include.locale] }})
|
||||||
|
assert_equal "it works!", Template.parse(text).render!(
|
||||||
|
"site" => { "data" => { "menu" => { "foo" => { "bar" => "it works!" } } } },
|
||||||
|
"include" => { "menu" => "foo", "locale" => "bar" }
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_variable_traversing
|
def test_variable_traversing
|
||||||
text = %| {{car.bmw}} {{car.gm}} {{car.bmw}} |
|
text = %| {{car.bmw}} {{car.gm}} {{car.bmw}} |
|
||||||
|
|
||||||
expected = %| good bad good |
|
expected = %| good bad good |
|
||||||
assert_equal expected, Template.parse(text).render(@assigns)
|
assert_equal expected, Template.parse(text).render!(@assigns)
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_variable_piping
|
def test_variable_piping
|
||||||
text = %( {{ car.gm | make_funny }} )
|
text = %( {{ car.gm | make_funny }} )
|
||||||
expected = %| LOL |
|
expected = %| LOL |
|
||||||
|
|
||||||
assert_equal expected, Template.parse(text).render(@assigns, :filters => [FunnyFilter])
|
assert_equal expected, Template.parse(text).render!(@assigns, :filters => [FunnyFilter])
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_variable_piping_with_input
|
def test_variable_piping_with_input
|
||||||
text = %( {{ car.gm | cite_funny }} )
|
text = %( {{ car.gm | cite_funny }} )
|
||||||
expected = %| LOL: bad |
|
expected = %| LOL: bad |
|
||||||
|
|
||||||
assert_equal expected, Template.parse(text).render(@assigns, :filters => [FunnyFilter])
|
assert_equal expected, Template.parse(text).render!(@assigns, :filters => [FunnyFilter])
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_variable_piping_with_args
|
def test_variable_piping_with_args
|
||||||
text = %! {{ car.gm | add_smiley : ':-(' }} !
|
text = %! {{ car.gm | add_smiley : ':-(' }} !
|
||||||
expected = %| bad :-( |
|
expected = %| bad :-( |
|
||||||
|
|
||||||
assert_equal expected, Template.parse(text).render(@assigns, :filters => [FunnyFilter])
|
assert_equal expected, Template.parse(text).render!(@assigns, :filters => [FunnyFilter])
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_variable_piping_with_no_args
|
def test_variable_piping_with_no_args
|
||||||
text = %! {{ car.gm | add_smiley }} !
|
text = %! {{ car.gm | add_smiley }} !
|
||||||
expected = %| bad :-) |
|
expected = %| bad :-) |
|
||||||
|
|
||||||
assert_equal expected, Template.parse(text).render(@assigns, :filters => [FunnyFilter])
|
assert_equal expected, Template.parse(text).render!(@assigns, :filters => [FunnyFilter])
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_multiple_variable_piping_with_args
|
def test_multiple_variable_piping_with_args
|
||||||
text = %! {{ car.gm | add_smiley : ':-(' | add_smiley : ':-('}} !
|
text = %! {{ car.gm | add_smiley : ':-(' | add_smiley : ':-('}} !
|
||||||
expected = %| bad :-( :-( |
|
expected = %| bad :-( :-( |
|
||||||
|
|
||||||
assert_equal expected, Template.parse(text).render(@assigns, :filters => [FunnyFilter])
|
assert_equal expected, Template.parse(text).render!(@assigns, :filters => [FunnyFilter])
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_variable_piping_with_multiple_args
|
def test_variable_piping_with_multiple_args
|
||||||
text = %! {{ car.gm | add_tag : 'span', 'bar'}} !
|
text = %! {{ car.gm | add_tag : 'span', 'bar'}} !
|
||||||
expected = %| <span id="bar">bad</span> |
|
expected = %| <span id="bar">bad</span> |
|
||||||
|
|
||||||
assert_equal expected, Template.parse(text).render(@assigns, :filters => [FunnyFilter])
|
assert_equal expected, Template.parse(text).render!(@assigns, :filters => [FunnyFilter])
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_variable_piping_with_variable_args
|
def test_variable_piping_with_variable_args
|
||||||
text = %! {{ car.gm | add_tag : 'span', car.bmw}} !
|
text = %! {{ car.gm | add_tag : 'span', car.bmw}} !
|
||||||
expected = %| <span id="good">bad</span> |
|
expected = %| <span id="good">bad</span> |
|
||||||
|
|
||||||
assert_equal expected, Template.parse(text).render(@assigns, :filters => [FunnyFilter])
|
assert_equal expected, Template.parse(text).render!(@assigns, :filters => [FunnyFilter])
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_multiple_pipings
|
def test_multiple_pipings
|
||||||
text = %( {{ best_cars | cite_funny | paragraph }} )
|
text = %( {{ best_cars | cite_funny | paragraph }} )
|
||||||
expected = %| <p>LOL: bmw</p> |
|
expected = %| <p>LOL: bmw</p> |
|
||||||
|
|
||||||
assert_equal expected, Template.parse(text).render(@assigns, :filters => [FunnyFilter])
|
assert_equal expected, Template.parse(text).render!(@assigns, :filters => [FunnyFilter])
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_link_to
|
def test_link_to
|
||||||
text = %( {{ 'Typo' | link_to: 'http://typo.leetsoft.com' }} )
|
text = %( {{ 'Typo' | link_to: 'http://typo.leetsoft.com' }} )
|
||||||
expected = %| <a href="http://typo.leetsoft.com">Typo</a> |
|
expected = %| <a href="http://typo.leetsoft.com">Typo</a> |
|
||||||
|
|
||||||
assert_equal expected, Template.parse(text).render(@assigns, :filters => [FunnyFilter])
|
assert_equal expected, Template.parse(text).render!(@assigns, :filters => [FunnyFilter])
|
||||||
end
|
end
|
||||||
end # OutputTest
|
end # OutputTest
|
||||||
116
test/integration/parsing_quirks_test.rb
Normal file
116
test/integration/parsing_quirks_test.rb
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
require 'test_helper'
|
||||||
|
|
||||||
|
class ParsingQuirksTest < Minitest::Test
|
||||||
|
include Liquid
|
||||||
|
|
||||||
|
def test_parsing_css
|
||||||
|
text = " div { font-weight: bold; } "
|
||||||
|
assert_equal text, Template.parse(text).render!
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_raise_on_single_close_bracet
|
||||||
|
assert_raises(SyntaxError) do
|
||||||
|
Template.parse("text {{method} oh nos!")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_raise_on_label_and_no_close_bracets
|
||||||
|
assert_raises(SyntaxError) do
|
||||||
|
Template.parse("TEST {{ ")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_raise_on_label_and_no_close_bracets_percent
|
||||||
|
assert_raises(SyntaxError) do
|
||||||
|
Template.parse("TEST {% ")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_error_on_empty_filter
|
||||||
|
assert Template.parse("{{test}}")
|
||||||
|
assert Template.parse("{{|test}}")
|
||||||
|
with_error_mode(:strict) do
|
||||||
|
assert_raises(SyntaxError) do
|
||||||
|
Template.parse("{{test |a|b|}}")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_meaningless_parens_error
|
||||||
|
with_error_mode(:strict) do
|
||||||
|
assert_raises(SyntaxError) do
|
||||||
|
markup = "a == 'foo' or (b == 'bar' and c == 'baz') or false"
|
||||||
|
Template.parse("{% if #{markup} %} YES {% endif %}")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_unexpected_characters_syntax_error
|
||||||
|
with_error_mode(:strict) do
|
||||||
|
assert_raises(SyntaxError) do
|
||||||
|
markup = "true && false"
|
||||||
|
Template.parse("{% if #{markup} %} YES {% endif %}")
|
||||||
|
end
|
||||||
|
assert_raises(SyntaxError) do
|
||||||
|
markup = "false || true"
|
||||||
|
Template.parse("{% if #{markup} %} YES {% endif %}")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_no_error_on_lax_empty_filter
|
||||||
|
assert Template.parse("{{test |a|b|}}", :error_mode => :lax)
|
||||||
|
assert Template.parse("{{test}}", :error_mode => :lax)
|
||||||
|
assert Template.parse("{{|test|}}", :error_mode => :lax)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_meaningless_parens_lax
|
||||||
|
with_error_mode(:lax) do
|
||||||
|
assigns = {'b' => 'bar', 'c' => 'baz'}
|
||||||
|
markup = "a == 'foo' or (b == 'bar' and c == 'baz') or false"
|
||||||
|
assert_template_result(' YES ',"{% if #{markup} %} YES {% endif %}", assigns)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_unexpected_characters_silently_eat_logic_lax
|
||||||
|
with_error_mode(:lax) do
|
||||||
|
markup = "true && false"
|
||||||
|
assert_template_result(' YES ',"{% if #{markup} %} YES {% endif %}")
|
||||||
|
markup = "false || true"
|
||||||
|
assert_template_result('',"{% if #{markup} %} YES {% endif %}")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_raise_on_invalid_tag_delimiter
|
||||||
|
assert_raises(Liquid::SyntaxError) do
|
||||||
|
Template.new.parse('{% end %}')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_unanchored_filter_arguments
|
||||||
|
with_error_mode(:lax) do
|
||||||
|
assert_template_result('hi',"{{ 'hi there' | split$$$:' ' | first }}")
|
||||||
|
|
||||||
|
assert_template_result('x', "{{ 'X' | downcase) }}")
|
||||||
|
|
||||||
|
# After the messed up quotes a filter without parameters (reverse) should work
|
||||||
|
# but one with parameters (remove) shouldn't be detected.
|
||||||
|
assert_template_result('here', "{{ 'hi there' | split:\"t\"\" | reverse | first}}")
|
||||||
|
assert_template_result('hi ', "{{ 'hi there' | split:\"t\"\" | remove:\"i\" | first}}")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_invalid_variables_work
|
||||||
|
with_error_mode(:lax) do
|
||||||
|
assert_template_result('bar', "{% assign 123foo = 'bar' %}{{ 123foo }}")
|
||||||
|
assert_template_result('123', "{% assign 123 = 'bar' %}{{ 123 }}")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_extra_dots_in_ranges
|
||||||
|
with_error_mode(:lax) do
|
||||||
|
assert_template_result('12345', "{% for i in (1...5) %}{{ i }}{% endfor %}")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end # ParsingQuirksTest
|
||||||
154
test/integration/render_profiling_test.rb
Normal file
154
test/integration/render_profiling_test.rb
Normal file
@@ -0,0 +1,154 @@
|
|||||||
|
require 'test_helper'
|
||||||
|
|
||||||
|
class RenderProfilingTest < Minitest::Test
|
||||||
|
include Liquid
|
||||||
|
|
||||||
|
class ProfilingFileSystem
|
||||||
|
def read_template_file(template_path, context)
|
||||||
|
"Rendering template {% assign template_name = '#{template_path}'%}\n{{ template_name }}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def setup
|
||||||
|
Liquid::Template.file_system = ProfilingFileSystem.new
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_template_allows_flagging_profiling
|
||||||
|
t = Template.parse("{{ 'a string' | upcase }}")
|
||||||
|
t.render!
|
||||||
|
|
||||||
|
assert_nil t.profiler
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_parse_makes_available_simple_profiling
|
||||||
|
t = Template.parse("{{ 'a string' | upcase }}", :profile => true)
|
||||||
|
t.render!
|
||||||
|
|
||||||
|
assert_equal 1, t.profiler.length
|
||||||
|
|
||||||
|
node = t.profiler[0]
|
||||||
|
assert_equal " 'a string' | upcase ", node.code
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_render_ignores_raw_strings_when_profiling
|
||||||
|
t = Template.parse("This is raw string\nstuff\nNewline", :profile => true)
|
||||||
|
t.render!
|
||||||
|
|
||||||
|
assert_equal 0, t.profiler.length
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_profiling_includes_line_numbers_of_liquid_nodes
|
||||||
|
t = Template.parse("{{ 'a string' | upcase }}\n{% increment test %}", :profile => true)
|
||||||
|
t.render!
|
||||||
|
assert_equal 2, t.profiler.length
|
||||||
|
|
||||||
|
# {{ 'a string' | upcase }}
|
||||||
|
assert_equal 1, t.profiler[0].line_number
|
||||||
|
# {{ increment test }}
|
||||||
|
assert_equal 2, t.profiler[1].line_number
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_profiling_includes_line_numbers_of_included_partials
|
||||||
|
t = Template.parse("{% include 'a_template' %}", :profile => true)
|
||||||
|
t.render!
|
||||||
|
|
||||||
|
included_children = t.profiler[0].children
|
||||||
|
|
||||||
|
# {% assign template_name = 'a_template' %}
|
||||||
|
assert_equal 1, included_children[0].line_number
|
||||||
|
# {{ template_name }}
|
||||||
|
assert_equal 2, included_children[1].line_number
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_profiling_times_the_rendering_of_tokens
|
||||||
|
t = Template.parse("{% include 'a_template' %}", :profile => true)
|
||||||
|
t.render!
|
||||||
|
|
||||||
|
node = t.profiler[0]
|
||||||
|
refute_nil node.render_time
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_profiling_times_the_entire_render
|
||||||
|
t = Template.parse("{% include 'a_template' %}", :profile => true)
|
||||||
|
t.render!
|
||||||
|
|
||||||
|
assert t.profiler.total_render_time >= 0, "Total render time was not calculated"
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_profiling_uses_include_to_mark_children
|
||||||
|
t = Template.parse("{{ 'a string' | upcase }}\n{% include 'a_template' %}", :profile => true)
|
||||||
|
t.render!
|
||||||
|
|
||||||
|
include_node = t.profiler[1]
|
||||||
|
assert_equal 2, include_node.children.length
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_profiling_marks_children_with_the_name_of_included_partial
|
||||||
|
t = Template.parse("{{ 'a string' | upcase }}\n{% include 'a_template' %}", :profile => true)
|
||||||
|
t.render!
|
||||||
|
|
||||||
|
include_node = t.profiler[1]
|
||||||
|
include_node.children.each do |child|
|
||||||
|
assert_equal "'a_template'", child.partial
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_profiling_supports_multiple_templates
|
||||||
|
t = Template.parse("{{ 'a string' | upcase }}\n{% include 'a_template' %}\n{% include 'b_template' %}", :profile => true)
|
||||||
|
t.render!
|
||||||
|
|
||||||
|
a_template = t.profiler[1]
|
||||||
|
a_template.children.each do |child|
|
||||||
|
assert_equal "'a_template'", child.partial
|
||||||
|
end
|
||||||
|
|
||||||
|
b_template = t.profiler[2]
|
||||||
|
b_template.children.each do |child|
|
||||||
|
assert_equal "'b_template'", child.partial
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_profiling_supports_rendering_the_same_partial_multiple_times
|
||||||
|
t = Template.parse("{{ 'a string' | upcase }}\n{% include 'a_template' %}\n{% include 'a_template' %}", :profile => true)
|
||||||
|
t.render!
|
||||||
|
|
||||||
|
a_template1 = t.profiler[1]
|
||||||
|
a_template1.children.each do |child|
|
||||||
|
assert_equal "'a_template'", child.partial
|
||||||
|
end
|
||||||
|
|
||||||
|
a_template2 = t.profiler[2]
|
||||||
|
a_template2.children.each do |child|
|
||||||
|
assert_equal "'a_template'", child.partial
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_can_iterate_over_each_profiling_entry
|
||||||
|
t = Template.parse("{{ 'a string' | upcase }}\n{% increment test %}", :profile => true)
|
||||||
|
t.render!
|
||||||
|
|
||||||
|
timing_count = 0
|
||||||
|
t.profiler.each do |timing|
|
||||||
|
timing_count += 1
|
||||||
|
end
|
||||||
|
|
||||||
|
assert_equal 2, timing_count
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_profiling_marks_children_of_if_blocks
|
||||||
|
t = Template.parse("{% if true %} {% increment test %} {{ test }} {% endif %}", :profile => true)
|
||||||
|
t.render!
|
||||||
|
|
||||||
|
assert_equal 1, t.profiler.length
|
||||||
|
assert_equal 2, t.profiler[0].children.length
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_profiling_marks_children_of_for_blocks
|
||||||
|
t = Template.parse("{% for item in collection %} {{ item }} {% endfor %}", :profile => true)
|
||||||
|
t.render!({"collection" => ["one", "two"]})
|
||||||
|
|
||||||
|
assert_equal 1, t.profiler.length
|
||||||
|
# Will profile each invocation of the for block
|
||||||
|
assert_equal 2, t.profiler[0].children.length
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -6,21 +6,21 @@ module SecurityFilter
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
class SecurityTest < Test::Unit::TestCase
|
class SecurityTest < Minitest::Test
|
||||||
include Liquid
|
include Liquid
|
||||||
|
|
||||||
def test_no_instance_eval
|
def test_no_instance_eval
|
||||||
text = %( {{ '1+1' | instance_eval }} )
|
text = %( {{ '1+1' | instance_eval }} )
|
||||||
expected = %| 1+1 |
|
expected = %| 1+1 |
|
||||||
|
|
||||||
assert_equal expected, Template.parse(text).render(@assigns)
|
assert_equal expected, Template.parse(text).render!(@assigns)
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_no_existing_instance_eval
|
def test_no_existing_instance_eval
|
||||||
text = %( {{ '1+1' | __instance_eval__ }} )
|
text = %( {{ '1+1' | __instance_eval__ }} )
|
||||||
expected = %| 1+1 |
|
expected = %| 1+1 |
|
||||||
|
|
||||||
assert_equal expected, Template.parse(text).render(@assigns)
|
assert_equal expected, Template.parse(text).render!(@assigns)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
@@ -28,7 +28,7 @@ class SecurityTest < Test::Unit::TestCase
|
|||||||
text = %( {{ '1+1' | instance_eval }} )
|
text = %( {{ '1+1' | instance_eval }} )
|
||||||
expected = %| 1+1 |
|
expected = %| 1+1 |
|
||||||
|
|
||||||
assert_equal expected, Template.parse(text).render(@assigns)
|
assert_equal expected, Template.parse(text).render!(@assigns)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
@@ -36,7 +36,7 @@ class SecurityTest < Test::Unit::TestCase
|
|||||||
text = %( {{ '1+1' | add_one | instance_eval }} )
|
text = %( {{ '1+1' | add_one | instance_eval }} )
|
||||||
expected = %| 1+1 + 1 |
|
expected = %| 1+1 + 1 |
|
||||||
|
|
||||||
assert_equal expected, Template.parse(text).render(@assigns, :filters => SecurityFilter)
|
assert_equal expected, Template.parse(text).render!(@assigns, :filters => SecurityFilter)
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_does_not_add_filters_to_symbol_table
|
def test_does_not_add_filters_to_symbol_table
|
||||||
@@ -47,17 +47,17 @@ class SecurityTest < Test::Unit::TestCase
|
|||||||
template = Template.parse(test)
|
template = Template.parse(test)
|
||||||
assert_equal [], (Symbol.all_symbols - current_symbols)
|
assert_equal [], (Symbol.all_symbols - current_symbols)
|
||||||
|
|
||||||
template.render
|
template.render!
|
||||||
assert_equal [], (Symbol.all_symbols - current_symbols)
|
assert_equal [], (Symbol.all_symbols - current_symbols)
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_does_not_add_drop_methods_to_symbol_table
|
def test_does_not_add_drop_methods_to_symbol_table
|
||||||
current_symbols = Symbol.all_symbols
|
current_symbols = Symbol.all_symbols
|
||||||
|
|
||||||
drop = Drop.new
|
assigns = { 'drop' => Drop.new }
|
||||||
drop.invoke_drop("custom_method_1")
|
assert_equal "", Template.parse("{{ drop.custom_method_1 }}", assigns).render!
|
||||||
drop.invoke_drop("custom_method_2")
|
assert_equal "", Template.parse("{{ drop.custom_method_2 }}", assigns).render!
|
||||||
drop.invoke_drop("custom_method_3")
|
assert_equal "", Template.parse("{{ drop.custom_method_3 }}", assigns).render!
|
||||||
|
|
||||||
assert_equal [], (Symbol.all_symbols - current_symbols)
|
assert_equal [], (Symbol.all_symbols - current_symbols)
|
||||||
end
|
end
|
||||||
@@ -7,6 +7,8 @@ class Filters
|
|||||||
end
|
end
|
||||||
|
|
||||||
class TestThing
|
class TestThing
|
||||||
|
attr_reader :foo
|
||||||
|
|
||||||
def initialize
|
def initialize
|
||||||
@foo = 0
|
@foo = 0
|
||||||
end
|
end
|
||||||
@@ -39,7 +41,7 @@ class TestEnumerable < Liquid::Drop
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
class StandardFiltersTest < Test::Unit::TestCase
|
class StandardFiltersTest < Minitest::Test
|
||||||
include Liquid
|
include Liquid
|
||||||
|
|
||||||
def setup
|
def setup
|
||||||
@@ -62,6 +64,34 @@ class StandardFiltersTest < Test::Unit::TestCase
|
|||||||
assert_equal '', @filters.upcase(nil)
|
assert_equal '', @filters.upcase(nil)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_slice
|
||||||
|
assert_equal 'oob', @filters.slice('foobar', 1, 3)
|
||||||
|
assert_equal 'oobar', @filters.slice('foobar', 1, 1000)
|
||||||
|
assert_equal '', @filters.slice('foobar', 1, 0)
|
||||||
|
assert_equal 'o', @filters.slice('foobar', 1, 1)
|
||||||
|
assert_equal 'bar', @filters.slice('foobar', 3, 3)
|
||||||
|
assert_equal 'ar', @filters.slice('foobar', -2, 2)
|
||||||
|
assert_equal 'ar', @filters.slice('foobar', -2, 1000)
|
||||||
|
assert_equal 'r', @filters.slice('foobar', -1)
|
||||||
|
assert_equal '', @filters.slice(nil, 0)
|
||||||
|
assert_equal '', @filters.slice('foobar', 100, 10)
|
||||||
|
assert_equal '', @filters.slice('foobar', -100, 10)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_slice_on_arrays
|
||||||
|
input = 'foobar'.split(//)
|
||||||
|
assert_equal %w{o o b}, @filters.slice(input, 1, 3)
|
||||||
|
assert_equal %w{o o b a r}, @filters.slice(input, 1, 1000)
|
||||||
|
assert_equal %w{}, @filters.slice(input, 1, 0)
|
||||||
|
assert_equal %w{o}, @filters.slice(input, 1, 1)
|
||||||
|
assert_equal %w{b a r}, @filters.slice(input, 3, 3)
|
||||||
|
assert_equal %w{a r}, @filters.slice(input, -2, 2)
|
||||||
|
assert_equal %w{a r}, @filters.slice(input, -2, 1000)
|
||||||
|
assert_equal %w{r}, @filters.slice(input, -1)
|
||||||
|
assert_equal %w{}, @filters.slice(input, 100, 10)
|
||||||
|
assert_equal %w{}, @filters.slice(input, -100, 10)
|
||||||
|
end
|
||||||
|
|
||||||
def test_truncate
|
def test_truncate
|
||||||
assert_equal '1234...', @filters.truncate('1234567890', 7)
|
assert_equal '1234...', @filters.truncate('1234567890', 7)
|
||||||
assert_equal '1234567890', @filters.truncate('1234567890', 20)
|
assert_equal '1234567890', @filters.truncate('1234567890', 20)
|
||||||
@@ -76,6 +106,7 @@ class StandardFiltersTest < Test::Unit::TestCase
|
|||||||
assert_equal ['A?Z'], @filters.split('A?Z', '~')
|
assert_equal ['A?Z'], @filters.split('A?Z', '~')
|
||||||
# Regexp works although Liquid does not support.
|
# Regexp works although Liquid does not support.
|
||||||
assert_equal ['A','Z'], @filters.split('AxZ', /x/)
|
assert_equal ['A','Z'], @filters.split('AxZ', /x/)
|
||||||
|
assert_equal [], @filters.split(nil, ' ')
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_escape
|
def test_escape
|
||||||
@@ -87,6 +118,11 @@ class StandardFiltersTest < Test::Unit::TestCase
|
|||||||
assert_equal '<strong>Hulk</strong>', @filters.escape_once('<strong>Hulk</strong>')
|
assert_equal '<strong>Hulk</strong>', @filters.escape_once('<strong>Hulk</strong>')
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_url_encode
|
||||||
|
assert_equal 'foo%2B1%40example.com', @filters.url_encode('foo+1@example.com')
|
||||||
|
assert_equal nil, @filters.url_encode(nil)
|
||||||
|
end
|
||||||
|
|
||||||
def test_truncatewords
|
def test_truncatewords
|
||||||
assert_equal 'one two three', @filters.truncatewords('one two three', 4)
|
assert_equal 'one two three', @filters.truncatewords('one two three', 4)
|
||||||
assert_equal 'one two...', @filters.truncatewords('one two three', 2)
|
assert_equal 'one two...', @filters.truncatewords('one two three', 2)
|
||||||
@@ -115,10 +151,32 @@ 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")
|
assert_equal [{"a" => 1}, {"a" => 2}, {"a" => 3}, {"a" => 4}], @filters.sort([{"a" => 4}, {"a" => 3}, {"a" => 1}, {"a" => 2}], "a")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_legacy_sort_hash
|
||||||
|
assert_equal [{a:1, b:2}], @filters.sort({a:1, b:2})
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_numerical_vs_lexicographical_sort
|
||||||
|
assert_equal [2, 10], @filters.sort([10, 2])
|
||||||
|
assert_equal [{"a" => 2}, {"a" => 10}], @filters.sort([{"a" => 10}, {"a" => 2}], "a")
|
||||||
|
assert_equal ["10", "2"], @filters.sort(["10", "2"])
|
||||||
|
assert_equal [{"a" => "10"}, {"a" => "2"}], @filters.sort([{"a" => "10"}, {"a" => "2"}], "a")
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_uniq
|
||||||
|
assert_equal [1,3,2,4], @filters.uniq([1,1,3,2,3,1,4,3,2,1])
|
||||||
|
assert_equal [{"a" => 1}, {"a" => 3}, {"a" => 2}], @filters.uniq([{"a" => 1}, {"a" => 3}, {"a" => 1}, {"a" => 2}], "a")
|
||||||
|
testdrop = TestDrop.new
|
||||||
|
assert_equal [testdrop], @filters.uniq([testdrop, TestDrop.new], 'test')
|
||||||
|
end
|
||||||
|
|
||||||
def test_reverse
|
def test_reverse
|
||||||
assert_equal [4,3,2,1], @filters.reverse([1,2,3,4])
|
assert_equal [4,3,2,1], @filters.reverse([1,2,3,4])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_legacy_reverse_hash
|
||||||
|
assert_equal [{a:1, b:2}], @filters.reverse(a:1, b:2)
|
||||||
|
end
|
||||||
|
|
||||||
def test_map
|
def test_map
|
||||||
assert_equal [1,2,3,4], @filters.map([{"a" => 1}, {"a" => 2}, {"a" => 3}, {"a" => 4}], 'a')
|
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' }}",
|
assert_template_result 'abc', "{{ ary | map:'foo' | map:'bar' }}",
|
||||||
@@ -126,37 +184,50 @@ class StandardFiltersTest < Test::Unit::TestCase
|
|||||||
end
|
end
|
||||||
|
|
||||||
def test_map_doesnt_call_arbitrary_stuff
|
def test_map_doesnt_call_arbitrary_stuff
|
||||||
assert_equal "", Liquid::Template.parse('{{ "foo" | map: "__id__" }}').render
|
assert_template_result "", '{{ "foo" | map: "__id__" }}'
|
||||||
assert_equal "", Liquid::Template.parse('{{ "foo" | map: "inspect" }}').render
|
assert_template_result "", '{{ "foo" | map: "inspect" }}'
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_map_calls_to_liquid
|
def test_map_calls_to_liquid
|
||||||
t = TestThing.new
|
t = TestThing.new
|
||||||
assert_equal "woot: 1", Liquid::Template.parse('{{ foo | map: "whatever" }}').render("foo" => [t])
|
assert_template_result "woot: 1", '{{ foo | map: "whatever" }}', "foo" => [t]
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_map_on_hashes
|
def test_map_on_hashes
|
||||||
assert_equal "4217", Liquid::Template.parse('{{ thing | map: "foo" | map: "bar" }}').render("thing" => { "foo" => [ { "bar" => 42 }, { "bar" => 17 } ] })
|
assert_template_result "4217", '{{ thing | map: "foo" | map: "bar" }}',
|
||||||
|
"thing" => { "foo" => [ { "bar" => 42 }, { "bar" => 17 } ] }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_legacy_map_on_hashes_with_dynamic_key
|
||||||
|
template = "{% assign key = 'foo' %}{{ thing | map: key | map: 'bar' }}"
|
||||||
|
hash = { "foo" => { "bar" => 42 } }
|
||||||
|
assert_template_result "42", template, "thing" => hash
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_sort_calls_to_liquid
|
def test_sort_calls_to_liquid
|
||||||
t = TestThing.new
|
t = TestThing.new
|
||||||
assert_equal "woot: 1", Liquid::Template.parse('{{ foo | sort: "whatever" }}').render("foo" => [t])
|
Liquid::Template.parse('{{ foo | sort: "whatever" }}').render("foo" => [t])
|
||||||
|
assert t.foo > 0
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_map_over_proc
|
def test_map_over_proc
|
||||||
drop = TestDrop.new
|
drop = TestDrop.new
|
||||||
p = Proc.new{ drop }
|
p = Proc.new{ drop }
|
||||||
templ = '{{ procs | map: "test" }}'
|
templ = '{{ procs | map: "test" }}'
|
||||||
assert_equal "testfoo", Liquid::Template.parse(templ).render("procs" => [p])
|
assert_template_result "testfoo", templ, "procs" => [p]
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_map_works_on_enumerables
|
def test_map_works_on_enumerables
|
||||||
assert_equal "123", Liquid::Template.parse('{{ foo | map: "foo" }}').render!("foo" => TestEnumerable.new)
|
assert_template_result "123", '{{ foo | map: "foo" }}', "foo" => TestEnumerable.new
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_sort_works_on_enumerables
|
def test_sort_works_on_enumerables
|
||||||
assert_equal "213", Liquid::Template.parse('{{ foo | sort: "bar" | map: "foo" }}').render!("foo" => TestEnumerable.new)
|
assert_template_result "213", '{{ foo | sort: "bar" | map: "foo" }}', "foo" => TestEnumerable.new
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_first_and_last_call_to_liquid
|
||||||
|
assert_template_result 'foobar', '{{ foo | first }}', 'foo' => [ThingWithToLiquid.new]
|
||||||
|
assert_template_result 'foobar', '{{ foo | last }}', 'foo' => [ThingWithToLiquid.new]
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_date
|
def test_date
|
||||||
@@ -181,11 +252,12 @@ class StandardFiltersTest < Test::Unit::TestCase
|
|||||||
|
|
||||||
assert_equal nil, @filters.date(nil, "%B")
|
assert_equal nil, @filters.date(nil, "%B")
|
||||||
|
|
||||||
assert_equal "07/05/2006", @filters.date(1152098955, "%m/%d/%Y")
|
with_timezone("UTC") do
|
||||||
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
|
end
|
||||||
|
|
||||||
|
|
||||||
def test_first_last
|
def test_first_last
|
||||||
assert_equal 1, @filters.first([1,2,3])
|
assert_equal 1, @filters.first([1,2,3])
|
||||||
assert_equal 3, @filters.last([1,2,3])
|
assert_equal 3, @filters.last([1,2,3])
|
||||||
@@ -257,7 +329,7 @@ class StandardFiltersTest < Test::Unit::TestCase
|
|||||||
assert_template_result "4", "{{ 14 | divided_by:3 }}"
|
assert_template_result "4", "{{ 14 | divided_by:3 }}"
|
||||||
|
|
||||||
assert_template_result "5", "{{ 15 | divided_by:3 }}"
|
assert_template_result "5", "{{ 15 | divided_by:3 }}"
|
||||||
assert_template_result "Liquid error: divided by 0", "{{ 5 | divided_by:0 }}"
|
assert_equal "Liquid error: divided by 0", Template.parse("{{ 5 | divided_by:0 }}").render
|
||||||
|
|
||||||
assert_template_result "0.5", "{{ 2.0 | divided_by:4 }}"
|
assert_template_result "0.5", "{{ 2.0 | divided_by:4 }}"
|
||||||
end
|
end
|
||||||
@@ -266,6 +338,22 @@ class StandardFiltersTest < Test::Unit::TestCase
|
|||||||
assert_template_result "1", "{{ 3 | modulo:2 }}"
|
assert_template_result "1", "{{ 3 | modulo:2 }}"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_round
|
||||||
|
assert_template_result "5", "{{ input | round }}", 'input' => 4.6
|
||||||
|
assert_template_result "4", "{{ '4.3' | round }}"
|
||||||
|
assert_template_result "4.56", "{{ input | round: 2 }}", 'input' => 4.5612
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_ceil
|
||||||
|
assert_template_result "5", "{{ input | ceil }}", 'input' => 4.6
|
||||||
|
assert_template_result "5", "{{ '4.3' | ceil }}"
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_floor
|
||||||
|
assert_template_result "4", "{{ input | floor }}", 'input' => 4.6
|
||||||
|
assert_template_result "4", "{{ '4.3' | floor }}"
|
||||||
|
end
|
||||||
|
|
||||||
def test_append
|
def test_append
|
||||||
assigns = {'a' => 'bc', 'b' => 'd' }
|
assigns = {'a' => 'bc', 'b' => 'd' }
|
||||||
assert_template_result('bcd',"{{ a | append: 'd'}}",assigns)
|
assert_template_result('bcd',"{{ a | append: 'd'}}",assigns)
|
||||||
@@ -290,4 +378,19 @@ class StandardFiltersTest < Test::Unit::TestCase
|
|||||||
def test_cannot_access_private_methods
|
def test_cannot_access_private_methods
|
||||||
assert_template_result('a',"{{ 'a' | to_number }}")
|
assert_template_result('a',"{{ 'a' | to_number }}")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_date_raises_nothing
|
||||||
|
assert_template_result('', "{{ '' | date: '%D' }}")
|
||||||
|
assert_template_result('abc', "{{ 'abc' | date: '%D' }}")
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def with_timezone(tz)
|
||||||
|
old_tz = ENV['TZ']
|
||||||
|
ENV['TZ'] = tz
|
||||||
|
yield
|
||||||
|
ensure
|
||||||
|
ENV['TZ'] = old_tz
|
||||||
|
end
|
||||||
end # StandardFiltersTest
|
end # StandardFiltersTest
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
require 'test_helper'
|
require 'test_helper'
|
||||||
|
|
||||||
class BreakTagTest < Test::Unit::TestCase
|
class BreakTagTest < Minitest::Test
|
||||||
include Liquid
|
include Liquid
|
||||||
|
|
||||||
# tests that no weird errors are raised if break is called outside of a
|
# tests that no weird errors are raised if break is called outside of a
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
require 'test_helper'
|
require 'test_helper'
|
||||||
|
|
||||||
class ContinueTagTest < Test::Unit::TestCase
|
class ContinueTagTest < Minitest::Test
|
||||||
include Liquid
|
include Liquid
|
||||||
|
|
||||||
# tests that no weird errors are raised if continue is called outside of a
|
# tests that no weird errors are raised if continue is called outside of a
|
||||||
@@ -1,6 +1,12 @@
|
|||||||
require 'test_helper'
|
require 'test_helper'
|
||||||
|
|
||||||
class ForTagTest < Test::Unit::TestCase
|
class ThingWithValue < Liquid::Drop
|
||||||
|
def value
|
||||||
|
3
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class ForTagTest < Minitest::Test
|
||||||
include Liquid
|
include Liquid
|
||||||
|
|
||||||
def test_for
|
def test_for
|
||||||
@@ -34,6 +40,20 @@ HERE
|
|||||||
assert_template_result(' 1 2 3 ','{%for item in (1..3) %} {{item}} {%endfor%}')
|
assert_template_result(' 1 2 3 ','{%for item in (1..3) %} {{item}} {%endfor%}')
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_for_with_variable_range
|
||||||
|
assert_template_result(' 1 2 3 ','{%for item in (1..foobar) %} {{item}} {%endfor%}', "foobar" => 3)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_for_with_hash_value_range
|
||||||
|
foobar = { "value" => 3 }
|
||||||
|
assert_template_result(' 1 2 3 ','{%for item in (1..foobar.value) %} {{item}} {%endfor%}', "foobar" => foobar)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_for_with_drop_value_range
|
||||||
|
foobar = ThingWithValue.new
|
||||||
|
assert_template_result(' 1 2 3 ','{%for item in (1..foobar.value) %} {{item}} {%endfor%}', "foobar" => foobar)
|
||||||
|
end
|
||||||
|
|
||||||
def test_for_with_variable
|
def test_for_with_variable
|
||||||
assert_template_result(' 1 2 3 ','{%for item in array%} {{item}} {%endfor%}','array' => [1,2,3])
|
assert_template_result(' 1 2 3 ','{%for item in array%} {{item}} {%endfor%}','array' => [1,2,3])
|
||||||
assert_template_result('123','{%for item in array%}{{item}}{%endfor%}','array' => [1,2,3])
|
assert_template_result('123','{%for item in array%}{{item}}{%endfor%}','array' => [1,2,3])
|
||||||
@@ -283,7 +303,7 @@ HERE
|
|||||||
end
|
end
|
||||||
|
|
||||||
def test_bad_variable_naming_in_for_loop
|
def test_bad_variable_naming_in_for_loop
|
||||||
assert_raise(Liquid::SyntaxError) do
|
assert_raises(Liquid::SyntaxError) do
|
||||||
Liquid::Template.parse('{% for a/b in x %}{% endfor %}')
|
Liquid::Template.parse('{% for a/b in x %}{% endfor %}')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -295,16 +315,6 @@ HERE
|
|||||||
assert_template_result(expected, template, assigns)
|
assert_template_result(expected, template, assigns)
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_for_nodelist
|
|
||||||
template = Liquid::Template.parse('{% for item in items %}FOR{% endfor %}')
|
|
||||||
assert_equal ['FOR'], template.root.nodelist[0].nodelist
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_for_else_nodelist
|
|
||||||
template = Liquid::Template.parse('{% for item in items %}FOR{% else %}ELSE{% endfor %}')
|
|
||||||
assert_equal ['FOR', 'ELSE'], template.root.nodelist[0].nodelist
|
|
||||||
end
|
|
||||||
|
|
||||||
class LoaderDrop < Liquid::Drop
|
class LoaderDrop < Liquid::Drop
|
||||||
attr_accessor :each_called, :load_slice_called
|
attr_accessor :each_called, :load_slice_called
|
||||||
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
require 'test_helper'
|
require 'test_helper'
|
||||||
|
|
||||||
class IfElseTagTest < Test::Unit::TestCase
|
class IfElseTagTest < Minitest::Test
|
||||||
include Liquid
|
include Liquid
|
||||||
|
|
||||||
def test_if
|
def test_if
|
||||||
@@ -10,6 +10,11 @@ class IfElseTagTest < Test::Unit::TestCase
|
|||||||
assert_template_result(' you rock ?','{% if false %} you suck {% endif %} {% if true %} you rock {% endif %}?')
|
assert_template_result(' you rock ?','{% if false %} you suck {% endif %} {% if true %} you rock {% endif %}?')
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_literal_comparisons
|
||||||
|
assert_template_result(' NO ','{% assign v = false %}{% if v %} YES {% else %} NO {% endif %}')
|
||||||
|
assert_template_result(' YES ','{% assign v = nil %}{% if v == nil %} YES {% else %} NO {% endif %}')
|
||||||
|
end
|
||||||
|
|
||||||
def test_if_else
|
def test_if_else
|
||||||
assert_template_result(' YES ','{% if false %} NO {% else %} YES {% endif %}')
|
assert_template_result(' YES ','{% if false %} NO {% else %} YES {% endif %}')
|
||||||
assert_template_result(' YES ','{% if true %} YES {% else %} NO {% endif %}')
|
assert_template_result(' YES ','{% if true %} YES {% else %} NO {% endif %}')
|
||||||
@@ -37,25 +42,19 @@ class IfElseTagTest < Test::Unit::TestCase
|
|||||||
end
|
end
|
||||||
|
|
||||||
def test_comparison_of_strings_containing_and_or_or
|
def test_comparison_of_strings_containing_and_or_or
|
||||||
assert_nothing_raised do
|
awful_markup = "a == 'and' and b == 'or' and c == 'foo and bar' and d == 'bar or baz' and e == 'foo' and foo and bar"
|
||||||
awful_markup = "a == 'and' and b == 'or' and c == 'foo and bar' and d == 'bar or baz' and e == 'foo' and foo and bar"
|
assigns = {'a' => 'and', 'b' => 'or', 'c' => 'foo and bar', 'd' => 'bar or baz', 'e' => 'foo', 'foo' => true, 'bar' => true}
|
||||||
assigns = {'a' => 'and', 'b' => 'or', 'c' => 'foo and bar', 'd' => 'bar or baz', 'e' => 'foo', 'foo' => true, 'bar' => true}
|
assert_template_result(' YES ',"{% if #{awful_markup} %} YES {% endif %}", assigns)
|
||||||
assert_template_result(' YES ',"{% if #{awful_markup} %} YES {% endif %}", assigns)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_comparison_of_expressions_starting_with_and_or_or
|
def test_comparison_of_expressions_starting_with_and_or_or
|
||||||
assigns = {'order' => {'items_count' => 0}, 'android' => {'name' => 'Roy'}}
|
assigns = {'order' => {'items_count' => 0}, 'android' => {'name' => 'Roy'}}
|
||||||
assert_nothing_raised do
|
assert_template_result( "YES",
|
||||||
assert_template_result( "YES",
|
"{% if android.name == 'Roy' %}YES{% endif %}",
|
||||||
"{% if android.name == 'Roy' %}YES{% endif %}",
|
assigns)
|
||||||
assigns)
|
assert_template_result( "YES",
|
||||||
end
|
"{% if order.items_count == 0 %}YES{% endif %}",
|
||||||
assert_nothing_raised do
|
assigns)
|
||||||
assert_template_result( "YES",
|
|
||||||
"{% if order.items_count == 0 %}YES{% endif %}",
|
|
||||||
assigns)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_if_and
|
def test_if_and
|
||||||
@@ -135,37 +134,57 @@ class IfElseTagTest < Test::Unit::TestCase
|
|||||||
end
|
end
|
||||||
|
|
||||||
def test_syntax_error_no_variable
|
def test_syntax_error_no_variable
|
||||||
assert_raise(SyntaxError){ assert_template_result('', '{% if jerry == 1 %}')}
|
assert_raises(SyntaxError){ assert_template_result('', '{% if jerry == 1 %}')}
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_syntax_error_no_expression
|
def test_syntax_error_no_expression
|
||||||
assert_raise(SyntaxError) { assert_template_result('', '{% if %}') }
|
assert_raises(SyntaxError) { assert_template_result('', '{% if %}') }
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_if_with_custom_condition
|
def test_if_with_custom_condition
|
||||||
|
original_op = Condition.operators['contains']
|
||||||
Condition.operators['contains'] = :[]
|
Condition.operators['contains'] = :[]
|
||||||
|
|
||||||
assert_template_result('yes', %({% if 'bob' contains 'o' %}yes{% endif %}))
|
assert_template_result('yes', %({% if 'bob' contains 'o' %}yes{% endif %}))
|
||||||
assert_template_result('no', %({% if 'bob' contains 'f' %}yes{% else %}no{% endif %}))
|
assert_template_result('no', %({% if 'bob' contains 'f' %}yes{% else %}no{% endif %}))
|
||||||
ensure
|
ensure
|
||||||
Condition.operators.delete 'contains'
|
Condition.operators['contains'] = original_op
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_operators_are_ignored_unless_isolated
|
def test_operators_are_ignored_unless_isolated
|
||||||
|
original_op = Condition.operators['contains']
|
||||||
Condition.operators['contains'] = :[]
|
Condition.operators['contains'] = :[]
|
||||||
|
|
||||||
assert_template_result('yes',
|
assert_template_result('yes',
|
||||||
%({% if 'gnomeslab-and-or-liquid' contains 'gnomeslab-and-or-liquid' %}yes{% endif %}))
|
%({% if 'gnomeslab-and-or-liquid' contains 'gnomeslab-and-or-liquid' %}yes{% endif %}))
|
||||||
end
|
ensure
|
||||||
|
Condition.operators['contains'] = original_op
|
||||||
def test_if_nodelist
|
|
||||||
template = Liquid::Template.parse('{% if true %}IF{% else %}ELSE{% endif %}')
|
|
||||||
assert_equal ['IF', 'ELSE'], template.root.nodelist[0].nodelist
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_operators_are_whitelisted
|
def test_operators_are_whitelisted
|
||||||
assert_raise(SyntaxError) do
|
assert_raises(SyntaxError) do
|
||||||
assert_template_result('', %({% if 1 or throw or or 1 %}yes{% endif %}))
|
assert_template_result('', %({% if 1 or throw or or 1 %}yes{% endif %}))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end # IfElseTest
|
|
||||||
|
def test_multiple_conditions
|
||||||
|
tpl = "{% if a or b and c %}true{% else %}false{% endif %}"
|
||||||
|
|
||||||
|
tests = {
|
||||||
|
[true, true, true] => true,
|
||||||
|
[true, true, false] => true,
|
||||||
|
[true, false, true] => true,
|
||||||
|
[true, false, false] => true,
|
||||||
|
[false, true, true] => true,
|
||||||
|
[false, true, false] => false,
|
||||||
|
[false, false, true] => false,
|
||||||
|
[false, false, false] => false,
|
||||||
|
}
|
||||||
|
|
||||||
|
tests.each do |vals, expected|
|
||||||
|
a, b, c = vals
|
||||||
|
assigns = { 'a' => a, 'b' => b, 'c' => c }
|
||||||
|
assert_template_result expected.to_s, tpl, assigns, assigns.to_s
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -27,6 +27,9 @@ class TestFileSystem
|
|||||||
when "pick_a_source"
|
when "pick_a_source"
|
||||||
"from TestFileSystem"
|
"from TestFileSystem"
|
||||||
|
|
||||||
|
when 'assignments'
|
||||||
|
"{% assign foo = 'bar' %}"
|
||||||
|
|
||||||
else
|
else
|
||||||
template_path
|
template_path
|
||||||
end
|
end
|
||||||
@@ -60,16 +63,12 @@ class CustomInclude < Liquid::Tag
|
|||||||
def parse(tokens)
|
def parse(tokens)
|
||||||
end
|
end
|
||||||
|
|
||||||
def blank?
|
|
||||||
false
|
|
||||||
end
|
|
||||||
|
|
||||||
def render(context)
|
def render(context)
|
||||||
@template_name[1..-2]
|
@template_name[1..-2]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
class IncludeTagTest < Test::Unit::TestCase
|
class IncludeTagTest < Minitest::Test
|
||||||
include Liquid
|
include Liquid
|
||||||
|
|
||||||
def setup
|
def setup
|
||||||
@@ -78,57 +77,56 @@ class IncludeTagTest < Test::Unit::TestCase
|
|||||||
|
|
||||||
def test_include_tag_looks_for_file_system_in_registers_first
|
def test_include_tag_looks_for_file_system_in_registers_first
|
||||||
assert_equal 'from OtherFileSystem',
|
assert_equal 'from OtherFileSystem',
|
||||||
Template.parse("{% include 'pick_a_source' %}").render({}, :registers => {:file_system => OtherFileSystem.new})
|
Template.parse("{% include 'pick_a_source' %}").render!({}, :registers => {:file_system => OtherFileSystem.new})
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
def test_include_tag_with
|
def test_include_tag_with
|
||||||
assert_equal "Product: Draft 151cm ",
|
assert_template_result "Product: Draft 151cm ",
|
||||||
Template.parse("{% include 'product' with products[0] %}").render( "products" => [ {'title' => 'Draft 151cm'}, {'title' => 'Element 155cm'} ] )
|
"{% include 'product' with products[0] %}", "products" => [ {'title' => 'Draft 151cm'}, {'title' => 'Element 155cm'} ]
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_include_tag_with_default_name
|
def test_include_tag_with_default_name
|
||||||
assert_equal "Product: Draft 151cm ",
|
assert_template_result "Product: Draft 151cm ",
|
||||||
Template.parse("{% include 'product' %}").render( "product" => {'title' => 'Draft 151cm'} )
|
"{% include 'product' %}", "product" => {'title' => 'Draft 151cm'}
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_include_tag_for
|
def test_include_tag_for
|
||||||
|
assert_template_result "Product: Draft 151cm Product: Element 155cm ",
|
||||||
assert_equal "Product: Draft 151cm Product: Element 155cm ",
|
"{% include 'product' for products %}", "products" => [ {'title' => 'Draft 151cm'}, {'title' => 'Element 155cm'} ]
|
||||||
Template.parse("{% include 'product' for products %}").render( "products" => [ {'title' => 'Draft 151cm'}, {'title' => 'Element 155cm'} ] )
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_include_tag_with_local_variables
|
def test_include_tag_with_local_variables
|
||||||
assert_equal "Locale: test123 ",
|
assert_template_result "Locale: test123 ", "{% include 'locale_variables' echo1: 'test123' %}"
|
||||||
Template.parse("{% include 'locale_variables' echo1: 'test123' %}").render
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_include_tag_with_multiple_local_variables
|
def test_include_tag_with_multiple_local_variables
|
||||||
assert_equal "Locale: test123 test321",
|
assert_template_result "Locale: test123 test321",
|
||||||
Template.parse("{% include 'locale_variables' echo1: 'test123', echo2: 'test321' %}").render
|
"{% include 'locale_variables' echo1: 'test123', echo2: 'test321' %}"
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_include_tag_with_multiple_local_variables_from_context
|
def test_include_tag_with_multiple_local_variables_from_context
|
||||||
assert_equal "Locale: test123 test321",
|
assert_template_result "Locale: test123 test321",
|
||||||
Template.parse("{% include 'locale_variables' echo1: echo1, echo2: more_echos.echo2 %}").render('echo1' => 'test123', 'more_echos' => { "echo2" => 'test321'})
|
"{% include 'locale_variables' echo1: echo1, echo2: more_echos.echo2 %}",
|
||||||
|
'echo1' => 'test123', 'more_echos' => { "echo2" => 'test321'}
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_included_templates_assigns_variables
|
||||||
|
assert_template_result "bar", "{% include 'assignments' %}{{ foo }}"
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_nested_include_tag
|
def test_nested_include_tag
|
||||||
assert_equal "body body_detail",
|
assert_template_result "body body_detail", "{% include 'body' %}"
|
||||||
Template.parse("{% include 'body' %}").render
|
|
||||||
|
|
||||||
assert_equal "header body body_detail footer",
|
assert_template_result "header body body_detail footer", "{% include 'nested_template' %}"
|
||||||
Template.parse("{% include 'nested_template' %}").render
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_nested_include_with_variable
|
def test_nested_include_with_variable
|
||||||
|
assert_template_result "Product: Draft 151cm details ",
|
||||||
|
"{% include 'nested_product_template' with product %}", "product" => {"title" => 'Draft 151cm'}
|
||||||
|
|
||||||
assert_equal "Product: Draft 151cm details ",
|
assert_template_result "Product: Draft 151cm details Product: Element 155cm details ",
|
||||||
Template.parse("{% include 'nested_product_template' with product %}").render("product" => {"title" => 'Draft 151cm'})
|
"{% include 'nested_product_template' for products %}", "products" => [{"title" => 'Draft 151cm'}, {"title" => 'Element 155cm'}]
|
||||||
|
|
||||||
assert_equal "Product: Draft 151cm details Product: Element 155cm details ",
|
|
||||||
Template.parse("{% include 'nested_product_template' for products %}").render("products" => [{"title" => 'Draft 151cm'}, {"title" => 'Element 155cm'}])
|
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_recursively_included_template_does_not_produce_endless_loop
|
def test_recursively_included_template_does_not_produce_endless_loop
|
||||||
@@ -141,7 +139,7 @@ class IncludeTagTest < Test::Unit::TestCase
|
|||||||
|
|
||||||
Liquid::Template.file_system = infinite_file_system.new
|
Liquid::Template.file_system = infinite_file_system.new
|
||||||
|
|
||||||
assert_raise(Liquid::StackLevelError) do
|
assert_raises(Liquid::StackLevelError, SystemStackError) do
|
||||||
Template.parse("{% include 'loop' %}").render!
|
Template.parse("{% include 'loop' %}").render!
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -160,34 +158,33 @@ class IncludeTagTest < Test::Unit::TestCase
|
|||||||
end
|
end
|
||||||
|
|
||||||
def test_dynamically_choosen_template
|
def test_dynamically_choosen_template
|
||||||
|
assert_template_result "Test123", "{% include template %}", "template" => 'Test123'
|
||||||
|
assert_template_result "Test321", "{% include template %}", "template" => 'Test321'
|
||||||
|
|
||||||
assert_equal "Test123", Template.parse("{% include template %}").render("template" => 'Test123')
|
assert_template_result "Product: Draft 151cm ", "{% include template for product %}",
|
||||||
assert_equal "Test321", Template.parse("{% include template %}").render("template" => 'Test321')
|
"template" => 'product', 'product' => { 'title' => 'Draft 151cm'}
|
||||||
|
|
||||||
assert_equal "Product: Draft 151cm ", Template.parse("{% include template for product %}").render("template" => 'product', 'product' => { 'title' => 'Draft 151cm'})
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_include_tag_caches_second_read_of_same_partial
|
def test_include_tag_caches_second_read_of_same_partial
|
||||||
file_system = CountingFileSystem.new
|
file_system = CountingFileSystem.new
|
||||||
assert_equal 'from CountingFileSystemfrom CountingFileSystem',
|
assert_equal 'from CountingFileSystemfrom CountingFileSystem',
|
||||||
Template.parse("{% include 'pick_a_source' %}{% include 'pick_a_source' %}").render({}, :registers => {:file_system => file_system})
|
Template.parse("{% include 'pick_a_source' %}{% include 'pick_a_source' %}").render!({}, :registers => {:file_system => file_system})
|
||||||
assert_equal 1, file_system.count
|
assert_equal 1, file_system.count
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_include_tag_doesnt_cache_partials_across_renders
|
def test_include_tag_doesnt_cache_partials_across_renders
|
||||||
file_system = CountingFileSystem.new
|
file_system = CountingFileSystem.new
|
||||||
assert_equal 'from CountingFileSystem',
|
assert_equal 'from CountingFileSystem',
|
||||||
Template.parse("{% include 'pick_a_source' %}").render({}, :registers => {:file_system => file_system})
|
Template.parse("{% include 'pick_a_source' %}").render!({}, :registers => {:file_system => file_system})
|
||||||
assert_equal 1, file_system.count
|
assert_equal 1, file_system.count
|
||||||
|
|
||||||
assert_equal 'from CountingFileSystem',
|
assert_equal 'from CountingFileSystem',
|
||||||
Template.parse("{% include 'pick_a_source' %}").render({}, :registers => {:file_system => file_system})
|
Template.parse("{% include 'pick_a_source' %}").render!({}, :registers => {:file_system => file_system})
|
||||||
assert_equal 2, file_system.count
|
assert_equal 2, file_system.count
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_include_tag_within_if_statement
|
def test_include_tag_within_if_statement
|
||||||
assert_equal "foo_if_true",
|
assert_template_result "foo_if_true", "{% if true %}{% include 'foo_if_true' %}{% endif %}"
|
||||||
Template.parse("{% if true %}{% include 'foo_if_true' %}{% endif %}").render
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_custom_include_tag
|
def test_custom_include_tag
|
||||||
@@ -195,7 +192,7 @@ class IncludeTagTest < Test::Unit::TestCase
|
|||||||
Liquid::Template.tags['include'] = CustomInclude
|
Liquid::Template.tags['include'] = CustomInclude
|
||||||
begin
|
begin
|
||||||
assert_equal "custom_foo",
|
assert_equal "custom_foo",
|
||||||
Template.parse("{% include 'custom_foo' %}").render
|
Template.parse("{% include 'custom_foo' %}").render!
|
||||||
ensure
|
ensure
|
||||||
Liquid::Template.tags['include'] = original_tag
|
Liquid::Template.tags['include'] = original_tag
|
||||||
end
|
end
|
||||||
@@ -206,9 +203,32 @@ class IncludeTagTest < Test::Unit::TestCase
|
|||||||
Liquid::Template.tags['include'] = CustomInclude
|
Liquid::Template.tags['include'] = CustomInclude
|
||||||
begin
|
begin
|
||||||
assert_equal "custom_foo_if_true",
|
assert_equal "custom_foo_if_true",
|
||||||
Template.parse("{% if true %}{% include 'custom_foo_if_true' %}{% endif %}").render
|
Template.parse("{% if true %}{% include 'custom_foo_if_true' %}{% endif %}").render!
|
||||||
ensure
|
ensure
|
||||||
Liquid::Template.tags['include'] = original_tag
|
Liquid::Template.tags['include'] = original_tag
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_does_not_add_error_in_strict_mode_for_missing_variable
|
||||||
|
Liquid::Template.file_system = TestFileSystem.new
|
||||||
|
|
||||||
|
a = Liquid::Template.parse(' {% include "nested_template" %}')
|
||||||
|
a.render!
|
||||||
|
assert_empty a.errors
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_passing_options_to_included_templates
|
||||||
|
assert_raises(Liquid::SyntaxError) do
|
||||||
|
Template.parse("{% include template %}", error_mode: :strict).render!("template" => '{{ "X" || downcase }}')
|
||||||
|
end
|
||||||
|
with_error_mode(:lax) do
|
||||||
|
assert_equal 'x', Template.parse("{% include template %}", error_mode: :strict, include_options_blacklist: true).render!("template" => '{{ "X" || downcase }}')
|
||||||
|
end
|
||||||
|
assert_raises(Liquid::SyntaxError) do
|
||||||
|
Template.parse("{% include template %}", error_mode: :strict, include_options_blacklist: [:locale]).render!("template" => '{{ "X" || downcase }}')
|
||||||
|
end
|
||||||
|
with_error_mode(:lax) do
|
||||||
|
assert_equal 'x', Template.parse("{% include template %}", error_mode: :strict, include_options_blacklist: [:error_mode]).render!("template" => '{{ "X" || downcase }}')
|
||||||
|
end
|
||||||
|
end
|
||||||
end # IncludeTagTest
|
end # IncludeTagTest
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
require 'test_helper'
|
require 'test_helper'
|
||||||
|
|
||||||
class IncrementTagTest < Test::Unit::TestCase
|
class IncrementTagTest < Minitest::Test
|
||||||
include Liquid
|
include Liquid
|
||||||
|
|
||||||
def test_inc
|
def test_inc
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
require 'test_helper'
|
require 'test_helper'
|
||||||
|
|
||||||
class RawTagTest < Test::Unit::TestCase
|
class RawTagTest < Minitest::Test
|
||||||
include Liquid
|
include Liquid
|
||||||
|
|
||||||
def test_tag_in_raw
|
def test_tag_in_raw
|
||||||
@@ -20,5 +20,6 @@ class RawTagTest < Test::Unit::TestCase
|
|||||||
assert_template_result ' Foobar {% invalid {% {% endraw ', '{% raw %} Foobar {% invalid {% {% endraw {% endraw %}'
|
assert_template_result ' Foobar {% invalid {% {% endraw ', '{% raw %} Foobar {% invalid {% {% endraw {% endraw %}'
|
||||||
assert_template_result ' Foobar {% {% {% ', '{% raw %} Foobar {% {% {% {% endraw %}'
|
assert_template_result ' Foobar {% {% {% ', '{% raw %} Foobar {% {% {% {% endraw %}'
|
||||||
assert_template_result ' test {% raw %} {% endraw %}', '{% raw %} test {% raw %} {% {% endraw %}endraw %}'
|
assert_template_result ' test {% raw %} {% endraw %}', '{% raw %} test {% raw %} {% {% endraw %}endraw %}'
|
||||||
|
assert_template_result ' Foobar {{ invalid 1', '{% raw %} Foobar {{ invalid {% endraw %}{{ 1 }}'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -1,14 +1,8 @@
|
|||||||
require 'test_helper'
|
require 'test_helper'
|
||||||
|
|
||||||
class StandardTagTest < Test::Unit::TestCase
|
class StandardTagTest < Minitest::Test
|
||||||
include Liquid
|
include Liquid
|
||||||
|
|
||||||
def test_tag
|
|
||||||
tag = Tag.new('tag', [], [])
|
|
||||||
assert_equal 'liquid::tag', tag.name
|
|
||||||
assert_equal '', tag.render(Context.new)
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_no_transform
|
def test_no_transform
|
||||||
assert_template_result('this text should come out of the template without change...',
|
assert_template_result('this text should come out of the template without change...',
|
||||||
'this text should come out of the template without change...')
|
'this text should come out of the template without change...')
|
||||||
@@ -72,7 +66,7 @@ class StandardTagTest < Test::Unit::TestCase
|
|||||||
end
|
end
|
||||||
|
|
||||||
def test_capture_detects_bad_syntax
|
def test_capture_detects_bad_syntax
|
||||||
assert_raise(SyntaxError) do
|
assert_raises(SyntaxError) do
|
||||||
assert_template_result('content foo content foo ',
|
assert_template_result('content foo content foo ',
|
||||||
'{{ var2 }}{% capture %}{{ var }} foo {% endcapture %}{{ var2 }}{{ var2 }}',
|
'{{ var2 }}{% capture %}{{ var }} foo {% endcapture %}{{ var2 }}{{ var2 }}',
|
||||||
{'var' => 'content' })
|
{'var' => 'content' })
|
||||||
@@ -180,11 +174,11 @@ class StandardTagTest < Test::Unit::TestCase
|
|||||||
# Example from the shopify forums
|
# Example from the shopify forums
|
||||||
code = %q({% case collection.handle %}{% when 'menswear-jackets' %}{% assign ptitle = 'menswear' %}{% when 'menswear-t-shirts' %}{% assign ptitle = 'menswear' %}{% else %}{% assign ptitle = 'womenswear' %}{% endcase %}{{ ptitle }})
|
code = %q({% case collection.handle %}{% when 'menswear-jackets' %}{% assign ptitle = 'menswear' %}{% when 'menswear-t-shirts' %}{% assign ptitle = 'menswear' %}{% else %}{% assign ptitle = 'womenswear' %}{% endcase %}{{ ptitle }})
|
||||||
template = Liquid::Template.parse(code)
|
template = Liquid::Template.parse(code)
|
||||||
assert_equal "menswear", template.render("collection" => {'handle' => 'menswear-jackets'})
|
assert_equal "menswear", template.render!("collection" => {'handle' => 'menswear-jackets'})
|
||||||
assert_equal "menswear", template.render("collection" => {'handle' => 'menswear-t-shirts'})
|
assert_equal "menswear", template.render!("collection" => {'handle' => 'menswear-t-shirts'})
|
||||||
assert_equal "womenswear", template.render("collection" => {'handle' => 'x'})
|
assert_equal "womenswear", template.render!("collection" => {'handle' => 'x'})
|
||||||
assert_equal "womenswear", template.render("collection" => {'handle' => 'y'})
|
assert_equal "womenswear", template.render!("collection" => {'handle' => 'y'})
|
||||||
assert_equal "womenswear", template.render("collection" => {'handle' => 'z'})
|
assert_equal "womenswear", template.render!("collection" => {'handle' => 'z'})
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_case_when_or
|
def test_case_when_or
|
||||||
@@ -218,7 +212,7 @@ class StandardTagTest < Test::Unit::TestCase
|
|||||||
end
|
end
|
||||||
|
|
||||||
def test_assign
|
def test_assign
|
||||||
assert_equal 'variable', Liquid::Template.parse( '{% assign a = "variable"%}{{a}}').render
|
assert_template_result 'variable', '{% assign a = "variable"%}{{a}}'
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_assign_unassigned
|
def test_assign_unassigned
|
||||||
@@ -227,20 +221,19 @@ class StandardTagTest < Test::Unit::TestCase
|
|||||||
end
|
end
|
||||||
|
|
||||||
def test_assign_an_empty_string
|
def test_assign_an_empty_string
|
||||||
assert_equal '', Liquid::Template.parse( '{% assign a = ""%}{{a}}' ).render
|
assert_template_result '', '{% assign a = ""%}{{a}}'
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_assign_is_global
|
def test_assign_is_global
|
||||||
assert_equal 'variable',
|
assert_template_result 'variable', '{%for i in (1..2) %}{% assign a = "variable"%}{% endfor %}{{a}}'
|
||||||
Liquid::Template.parse( '{%for i in (1..2) %}{% assign a = "variable"%}{% endfor %}{{a}}' ).render
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_case_detects_bad_syntax
|
def test_case_detects_bad_syntax
|
||||||
assert_raise(SyntaxError) do
|
assert_raises(SyntaxError) do
|
||||||
assert_template_result('', '{% case false %}{% when %}true{% endcase %}', {})
|
assert_template_result('', '{% case false %}{% when %}true{% endcase %}', {})
|
||||||
end
|
end
|
||||||
|
|
||||||
assert_raise(SyntaxError) do
|
assert_raises(SyntaxError) do
|
||||||
assert_template_result('', '{% case false %}{% huh %}true{% endcase %}', {})
|
assert_template_result('', '{% case false %}{% huh %}true{% endcase %}', {})
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -297,4 +290,8 @@ class StandardTagTest < Test::Unit::TestCase
|
|||||||
assigns = {'array' => [ 1, 1, 1, 1] }
|
assigns = {'array' => [ 1, 1, 1, 1] }
|
||||||
assert_template_result('1','{%for item in array%}{%ifchanged%}{{item}}{% endifchanged %}{%endfor%}',assigns)
|
assert_template_result('1','{%for item in array%}{%ifchanged%}{{item}}{% endifchanged %}{%endfor%}',assigns)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_multiline_tag
|
||||||
|
assert_template_result '0 1 2 3', "0{%\nfor i in (1..3)\n%} {{\ni\n}}{%\nendfor\n%}"
|
||||||
|
end
|
||||||
end # StandardTagTest
|
end # StandardTagTest
|
||||||
113
test/integration/tags/statements_test.rb
Normal file
113
test/integration/tags/statements_test.rb
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
require 'test_helper'
|
||||||
|
|
||||||
|
class StatementsTest < Minitest::Test
|
||||||
|
include Liquid
|
||||||
|
|
||||||
|
def test_true_eql_true
|
||||||
|
text = ' {% if true == true %} true {% else %} false {% endif %} '
|
||||||
|
assert_template_result ' true ', text
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_true_not_eql_true
|
||||||
|
text = ' {% if true != true %} true {% else %} false {% endif %} '
|
||||||
|
assert_template_result ' false ', text
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_true_lq_true
|
||||||
|
text = ' {% if 0 > 0 %} true {% else %} false {% endif %} '
|
||||||
|
assert_template_result ' false ', text
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_one_lq_zero
|
||||||
|
text = ' {% if 1 > 0 %} true {% else %} false {% endif %} '
|
||||||
|
assert_template_result ' true ', text
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_zero_lq_one
|
||||||
|
text = ' {% if 0 < 1 %} true {% else %} false {% endif %} '
|
||||||
|
assert_template_result ' true ', text
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_zero_lq_or_equal_one
|
||||||
|
text = ' {% if 0 <= 0 %} true {% else %} false {% endif %} '
|
||||||
|
assert_template_result ' true ', text
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_zero_lq_or_equal_one_involving_nil
|
||||||
|
text = ' {% if null <= 0 %} true {% else %} false {% endif %} '
|
||||||
|
assert_template_result ' false ', text
|
||||||
|
|
||||||
|
|
||||||
|
text = ' {% if 0 <= null %} true {% else %} false {% endif %} '
|
||||||
|
assert_template_result ' false ', text
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_zero_lqq_or_equal_one
|
||||||
|
text = ' {% if 0 >= 0 %} true {% else %} false {% endif %} '
|
||||||
|
assert_template_result ' true ', text
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_strings
|
||||||
|
text = " {% if 'test' == 'test' %} true {% else %} false {% endif %} "
|
||||||
|
assert_template_result ' true ', text
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_strings_not_equal
|
||||||
|
text = " {% if 'test' != 'test' %} true {% else %} false {% endif %} "
|
||||||
|
assert_template_result ' false ', text
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_var_strings_equal
|
||||||
|
text = ' {% if var == "hello there!" %} true {% else %} false {% endif %} '
|
||||||
|
assert_template_result ' true ', text, 'var' => 'hello there!'
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_var_strings_are_not_equal
|
||||||
|
text = ' {% if "hello there!" == var %} true {% else %} false {% endif %} '
|
||||||
|
assert_template_result ' true ', text, 'var' => 'hello there!'
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_var_and_long_string_are_equal
|
||||||
|
text = " {% if var == 'hello there!' %} true {% else %} false {% endif %} "
|
||||||
|
assert_template_result ' true ', text, 'var' => 'hello there!'
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
def test_var_and_long_string_are_equal_backwards
|
||||||
|
text = " {% if 'hello there!' == var %} true {% else %} false {% endif %} "
|
||||||
|
assert_template_result ' true ', text, 'var' => 'hello there!'
|
||||||
|
end
|
||||||
|
|
||||||
|
#def test_is_nil
|
||||||
|
# text = %| {% if var != nil %} true {% else %} false {% end %} |
|
||||||
|
# @template.assigns = { 'var' => 'hello there!'}
|
||||||
|
# expected = %| true |
|
||||||
|
# assert_equal expected, @template.parse(text)
|
||||||
|
#end
|
||||||
|
|
||||||
|
def test_is_collection_empty
|
||||||
|
text = ' {% if array == empty %} true {% else %} false {% endif %} '
|
||||||
|
assert_template_result ' true ', text, 'array' => []
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_is_not_collection_empty
|
||||||
|
text = ' {% if array == empty %} true {% else %} false {% endif %} '
|
||||||
|
assert_template_result ' false ', text, 'array' => [1,2,3]
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_nil
|
||||||
|
text = ' {% if var == nil %} true {% else %} false {% endif %} '
|
||||||
|
assert_template_result ' true ', text, 'var' => nil
|
||||||
|
|
||||||
|
text = ' {% if var == null %} true {% else %} false {% endif %} '
|
||||||
|
assert_template_result ' true ', text, 'var' => nil
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_not_nil
|
||||||
|
text = ' {% if var != nil %} true {% else %} false {% endif %} '
|
||||||
|
assert_template_result ' true ', text, 'var' => 1
|
||||||
|
|
||||||
|
text = ' {% if var != null %} true {% else %} false {% endif %} '
|
||||||
|
assert_template_result ' true ', text, 'var' => 1
|
||||||
|
end
|
||||||
|
end # StatementsTest
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
require 'test_helper'
|
require 'test_helper'
|
||||||
|
|
||||||
class HtmlTagTest < Test::Unit::TestCase
|
class TableRowTest < Minitest::Test
|
||||||
include Liquid
|
include Liquid
|
||||||
|
|
||||||
class ArrayDrop < Liquid::Drop
|
class ArrayDrop < Liquid::Drop
|
||||||
@@ -15,7 +15,7 @@ class HtmlTagTest < Test::Unit::TestCase
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_html_table
|
def test_table_row
|
||||||
|
|
||||||
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%} {{n}} {% endtablerow %}',
|
'{% tablerow n in numbers cols:3%} {{n}} {% endtablerow %}',
|
||||||
@@ -26,14 +26,14 @@ class HtmlTagTest < Test::Unit::TestCase
|
|||||||
'numbers' => [])
|
'numbers' => [])
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_html_table_with_different_cols
|
def test_table_row_with_different_cols
|
||||||
assert_template_result("<tr class=\"row1\">\n<td class=\"col1\"> 1 </td><td class=\"col2\"> 2 </td><td class=\"col3\"> 3 </td><td class=\"col4\"> 4 </td><td class=\"col5\"> 5 </td></tr>\n<tr class=\"row2\"><td class=\"col1\"> 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><td class=\"col4\"> 4 </td><td class=\"col5\"> 5 </td></tr>\n<tr class=\"row2\"><td class=\"col1\"> 6 </td></tr>\n",
|
||||||
'{% tablerow n in numbers cols:5%} {{n}} {% endtablerow %}',
|
'{% tablerow n in numbers cols:5%} {{n}} {% endtablerow %}',
|
||||||
'numbers' => [1,2,3,4,5,6])
|
'numbers' => [1,2,3,4,5,6])
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_html_col_counter
|
def test_table_col_counter
|
||||||
assert_template_result("<tr class=\"row1\">\n<td class=\"col1\">1</td><td class=\"col2\">2</td></tr>\n<tr class=\"row2\"><td class=\"col1\">1</td><td class=\"col2\">2</td></tr>\n<tr class=\"row3\"><td class=\"col1\">1</td><td class=\"col2\">2</td></tr>\n",
|
assert_template_result("<tr class=\"row1\">\n<td class=\"col1\">1</td><td class=\"col2\">2</td></tr>\n<tr class=\"row2\"><td class=\"col1\">1</td><td class=\"col2\">2</td></tr>\n<tr class=\"row3\"><td class=\"col1\">1</td><td class=\"col2\">2</td></tr>\n",
|
||||||
'{% tablerow n in numbers cols:2%}{{tablerowloop.col}}{% endtablerow %}',
|
'{% tablerow n in numbers cols:2%}{{tablerowloop.col}}{% endtablerow %}',
|
||||||
'numbers' => [1,2,3,4,5,6])
|
'numbers' => [1,2,3,4,5,6])
|
||||||
@@ -60,4 +60,4 @@ class HtmlTagTest < Test::Unit::TestCase
|
|||||||
'{% 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 # HtmlTagTest
|
end
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
require 'test_helper'
|
require 'test_helper'
|
||||||
|
|
||||||
class UnlessElseTagTest < Test::Unit::TestCase
|
class UnlessElseTagTest < Minitest::Test
|
||||||
include Liquid
|
include Liquid
|
||||||
|
|
||||||
def test_unless
|
def test_unless
|
||||||
@@ -22,82 +22,68 @@ class SomethingWithLength
|
|||||||
liquid_methods :length
|
liquid_methods :length
|
||||||
end
|
end
|
||||||
|
|
||||||
class TemplateTest < Test::Unit::TestCase
|
class ErroneousDrop < Liquid::Drop
|
||||||
|
def bad_method
|
||||||
|
raise 'ruby error in drop'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class TemplateTest < Minitest::Test
|
||||||
include Liquid
|
include Liquid
|
||||||
|
|
||||||
def test_tokenize_strings
|
|
||||||
assert_equal [' '], Template.new.send(:tokenize, ' ')
|
|
||||||
assert_equal ['hello world'], Template.new.send(:tokenize, 'hello world')
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_tokenize_variables
|
|
||||||
assert_equal ['{{funk}}'], Template.new.send(:tokenize, '{{funk}}')
|
|
||||||
assert_equal [' ', '{{funk}}', ' '], Template.new.send(:tokenize, ' {{funk}} ')
|
|
||||||
assert_equal [' ', '{{funk}}', ' ', '{{so}}', ' ', '{{brother}}', ' '], Template.new.send(:tokenize, ' {{funk}} {{so}} {{brother}} ')
|
|
||||||
assert_equal [' ', '{{ funk }}', ' '], Template.new.send(:tokenize, ' {{ funk }} ')
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_tokenize_blocks
|
|
||||||
assert_equal ['{%comment%}'], Template.new.send(:tokenize, '{%comment%}')
|
|
||||||
assert_equal [' ', '{%comment%}', ' '], Template.new.send(:tokenize, ' {%comment%} ')
|
|
||||||
|
|
||||||
assert_equal [' ', '{%comment%}', ' ', '{%endcomment%}', ' '], Template.new.send(:tokenize, ' {%comment%} {%endcomment%} ')
|
|
||||||
assert_equal [' ', '{% comment %}', ' ', '{% endcomment %}', ' '], Template.new.send(:tokenize, " {% comment %} {% endcomment %} ")
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_instance_assigns_persist_on_same_template_object_between_parses
|
def test_instance_assigns_persist_on_same_template_object_between_parses
|
||||||
t = Template.new
|
t = Template.new
|
||||||
assert_equal 'from instance assigns', t.parse("{% assign foo = 'from instance assigns' %}{{ foo }}").render
|
assert_equal 'from instance assigns', t.parse("{% assign foo = 'from instance assigns' %}{{ foo }}").render!
|
||||||
assert_equal 'from instance assigns', t.parse("{{ foo }}").render
|
assert_equal 'from instance assigns', t.parse("{{ foo }}").render!
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_instance_assigns_persist_on_same_template_parsing_between_renders
|
def test_instance_assigns_persist_on_same_template_parsing_between_renders
|
||||||
t = Template.new.parse("{{ foo }}{% assign foo = 'foo' %}{{ foo }}")
|
t = Template.new.parse("{{ foo }}{% assign foo = 'foo' %}{{ foo }}")
|
||||||
assert_equal 'foo', t.render
|
assert_equal 'foo', t.render!
|
||||||
assert_equal 'foofoo', t.render
|
assert_equal 'foofoo', t.render!
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_custom_assigns_do_not_persist_on_same_template
|
def test_custom_assigns_do_not_persist_on_same_template
|
||||||
t = Template.new
|
t = Template.new
|
||||||
assert_equal 'from custom assigns', t.parse("{{ foo }}").render('foo' => 'from custom assigns')
|
assert_equal 'from custom assigns', t.parse("{{ foo }}").render!('foo' => 'from custom assigns')
|
||||||
assert_equal '', t.parse("{{ foo }}").render
|
assert_equal '', t.parse("{{ foo }}").render!
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_custom_assigns_squash_instance_assigns
|
def test_custom_assigns_squash_instance_assigns
|
||||||
t = Template.new
|
t = Template.new
|
||||||
assert_equal 'from instance assigns', t.parse("{% assign foo = 'from instance assigns' %}{{ foo }}").render
|
assert_equal 'from instance assigns', t.parse("{% assign foo = 'from instance assigns' %}{{ foo }}").render!
|
||||||
assert_equal 'from custom assigns', t.parse("{{ foo }}").render('foo' => 'from custom assigns')
|
assert_equal 'from custom assigns', t.parse("{{ foo }}").render!('foo' => 'from custom assigns')
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_persistent_assigns_squash_instance_assigns
|
def test_persistent_assigns_squash_instance_assigns
|
||||||
t = Template.new
|
t = Template.new
|
||||||
assert_equal 'from instance assigns', t.parse("{% assign foo = 'from instance assigns' %}{{ foo }}").render
|
assert_equal 'from instance assigns', t.parse("{% assign foo = 'from instance assigns' %}{{ foo }}").render!
|
||||||
t.assigns['foo'] = 'from persistent assigns'
|
t.assigns['foo'] = 'from persistent assigns'
|
||||||
assert_equal 'from persistent assigns', t.parse("{{ foo }}").render
|
assert_equal 'from persistent assigns', t.parse("{{ foo }}").render!
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_lambda_is_called_once_from_persistent_assigns_over_multiple_parses_and_renders
|
def test_lambda_is_called_once_from_persistent_assigns_over_multiple_parses_and_renders
|
||||||
t = Template.new
|
t = Template.new
|
||||||
t.assigns['number'] = lambda { @global ||= 0; @global += 1 }
|
t.assigns['number'] = lambda { @global ||= 0; @global += 1 }
|
||||||
assert_equal '1', t.parse("{{number}}").render
|
assert_equal '1', t.parse("{{number}}").render!
|
||||||
assert_equal '1', t.parse("{{number}}").render
|
assert_equal '1', t.parse("{{number}}").render!
|
||||||
assert_equal '1', t.render
|
assert_equal '1', t.render!
|
||||||
@global = nil
|
@global = nil
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_lambda_is_called_once_from_custom_assigns_over_multiple_parses_and_renders
|
def test_lambda_is_called_once_from_custom_assigns_over_multiple_parses_and_renders
|
||||||
t = Template.new
|
t = Template.new
|
||||||
assigns = {'number' => lambda { @global ||= 0; @global += 1 }}
|
assigns = {'number' => lambda { @global ||= 0; @global += 1 }}
|
||||||
assert_equal '1', t.parse("{{number}}").render(assigns)
|
assert_equal '1', t.parse("{{number}}").render!(assigns)
|
||||||
assert_equal '1', t.parse("{{number}}").render(assigns)
|
assert_equal '1', t.parse("{{number}}").render!(assigns)
|
||||||
assert_equal '1', t.render(assigns)
|
assert_equal '1', t.render!(assigns)
|
||||||
@global = nil
|
@global = nil
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_resource_limits_works_with_custom_length_method
|
def test_resource_limits_works_with_custom_length_method
|
||||||
t = Template.parse("{% assign foo = bar %}")
|
t = Template.parse("{% assign foo = bar %}")
|
||||||
t.resource_limits = { :render_length_limit => 42 }
|
t.resource_limits = { :render_length_limit => 42 }
|
||||||
assert_equal "", t.render("bar" => SomethingWithLength.new)
|
assert_equal "", t.render!("bar" => SomethingWithLength.new)
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_resource_limits_render_length
|
def test_resource_limits_render_length
|
||||||
@@ -106,8 +92,8 @@ class TemplateTest < Test::Unit::TestCase
|
|||||||
assert_equal "Liquid error: Memory limits exceeded", t.render()
|
assert_equal "Liquid error: Memory limits exceeded", t.render()
|
||||||
assert t.resource_limits[:reached]
|
assert t.resource_limits[:reached]
|
||||||
t.resource_limits = { :render_length_limit => 10 }
|
t.resource_limits = { :render_length_limit => 10 }
|
||||||
assert_equal "0123456789", t.render()
|
assert_equal "0123456789", t.render!()
|
||||||
assert_not_nil t.resource_limits[:render_length_current]
|
refute_nil t.resource_limits[:render_length_current]
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_resource_limits_render_score
|
def test_resource_limits_render_score
|
||||||
@@ -120,8 +106,8 @@ class TemplateTest < Test::Unit::TestCase
|
|||||||
assert_equal "Liquid error: Memory limits exceeded", t.render()
|
assert_equal "Liquid error: Memory limits exceeded", t.render()
|
||||||
assert t.resource_limits[:reached]
|
assert t.resource_limits[:reached]
|
||||||
t.resource_limits = { :render_score_limit => 200 }
|
t.resource_limits = { :render_score_limit => 200 }
|
||||||
assert_equal (" foo " * 100), t.render()
|
assert_equal (" foo " * 100), t.render!()
|
||||||
assert_not_nil t.resource_limits[:render_score_current]
|
refute_nil t.resource_limits[:render_score_current]
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_resource_limits_assign_score
|
def test_resource_limits_assign_score
|
||||||
@@ -130,8 +116,8 @@ class TemplateTest < Test::Unit::TestCase
|
|||||||
assert_equal "Liquid error: Memory limits exceeded", t.render()
|
assert_equal "Liquid error: Memory limits exceeded", t.render()
|
||||||
assert t.resource_limits[:reached]
|
assert t.resource_limits[:reached]
|
||||||
t.resource_limits = { :assign_score_limit => 2 }
|
t.resource_limits = { :assign_score_limit => 2 }
|
||||||
assert_equal "", t.render()
|
assert_equal "", t.render!()
|
||||||
assert_not_nil t.resource_limits[:assign_score_current]
|
refute_nil t.resource_limits[:assign_score_current]
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_resource_limits_aborts_rendering_after_first_error
|
def test_resource_limits_aborts_rendering_after_first_error
|
||||||
@@ -143,32 +129,54 @@ class TemplateTest < Test::Unit::TestCase
|
|||||||
|
|
||||||
def test_resource_limits_hash_in_template_gets_updated_even_if_no_limits_are_set
|
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 = Template.parse("{% for a in (1..100) %} {% assign foo = 1 %} {% endfor %}")
|
||||||
t.render()
|
t.render!()
|
||||||
assert t.resource_limits[:assign_score_current] > 0
|
assert t.resource_limits[:assign_score_current] > 0
|
||||||
assert t.resource_limits[:render_score_current] > 0
|
assert t.resource_limits[:render_score_current] > 0
|
||||||
assert t.resource_limits[:render_length_current] > 0
|
assert t.resource_limits[:render_length_current] > 0
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_default_resource_limits_unaffected_by_render_with_context
|
||||||
|
context = Context.new
|
||||||
|
t = Template.parse("{% for a in (1..100) %} {% assign foo = 1 %} {% endfor %}")
|
||||||
|
t.render!(context)
|
||||||
|
assert context.resource_limits[:assign_score_current] > 0
|
||||||
|
assert context.resource_limits[:render_score_current] > 0
|
||||||
|
assert context.resource_limits[:render_length_current] > 0
|
||||||
|
refute Template.default_resource_limits.key?(:assign_score_current)
|
||||||
|
refute Template.default_resource_limits.key?(:render_score_current)
|
||||||
|
refute Template.default_resource_limits.key?(:render_length_current)
|
||||||
|
end
|
||||||
|
|
||||||
def test_can_use_drop_as_context
|
def test_can_use_drop_as_context
|
||||||
t = Template.new
|
t = Template.new
|
||||||
t.registers['lulz'] = 'haha'
|
t.registers['lulz'] = 'haha'
|
||||||
drop = TemplateContextDrop.new
|
drop = TemplateContextDrop.new
|
||||||
assert_equal 'fizzbuzz', t.parse('{{foo}}').render(drop)
|
assert_equal 'fizzbuzz', t.parse('{{foo}}').render!(drop)
|
||||||
assert_equal 'bar', t.parse('{{bar}}').render(drop)
|
assert_equal 'bar', t.parse('{{bar}}').render!(drop)
|
||||||
assert_equal 'haha', t.parse("{{baz}}").render(drop)
|
assert_equal 'haha', t.parse("{{baz}}").render!(drop)
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_sets_default_localization_in_document
|
def test_render_bang_force_rethrow_errors_on_passed_context
|
||||||
t = Template.new
|
context = Context.new({'drop' => ErroneousDrop.new})
|
||||||
t.parse('')
|
t = Template.new.parse('{{ drop.bad_method }}')
|
||||||
assert_instance_of I18n, t.root.options[:locale]
|
|
||||||
|
e = assert_raises RuntimeError do
|
||||||
|
t.render!(context)
|
||||||
|
end
|
||||||
|
assert_equal 'ruby error in drop', e.message
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_sets_default_localization_in_context_with_quick_initialization
|
def test_exception_handler_doesnt_reraise_if_it_returns_false
|
||||||
t = Template.new
|
exception = nil
|
||||||
t.parse('{{foo}}', :locale => I18n.new(fixture("en_locale.yml")))
|
Template.parse("{{ 1 | divided_by: 0 }}").render({}, exception_handler: ->(e) { exception = e; false })
|
||||||
|
assert exception.is_a?(ZeroDivisionError)
|
||||||
|
end
|
||||||
|
|
||||||
assert_instance_of I18n, t.root.options[:locale]
|
def test_exception_handler_does_reraise_if_it_returns_true
|
||||||
assert_equal fixture("en_locale.yml"), t.root.options[:locale].path
|
exception = nil
|
||||||
|
assert_raises(ZeroDivisionError) do
|
||||||
|
Template.parse("{{ 1 | divided_by: 0 }}").render({}, exception_handler: ->(e) { exception = e; true })
|
||||||
|
end
|
||||||
|
assert exception.is_a?(ZeroDivisionError)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
82
test/integration/variable_test.rb
Normal file
82
test/integration/variable_test.rb
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
require 'test_helper'
|
||||||
|
|
||||||
|
class VariableTest < Minitest::Test
|
||||||
|
include Liquid
|
||||||
|
|
||||||
|
def test_simple_variable
|
||||||
|
template = Template.parse(%|{{test}}|)
|
||||||
|
assert_equal 'worked', template.render!('test' => 'worked')
|
||||||
|
assert_equal 'worked wonderfully', template.render!('test' => 'worked wonderfully')
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_variable_render_calls_to_liquid
|
||||||
|
assert_template_result 'foobar', '{{ foo }}', 'foo' => ThingWithToLiquid.new
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_simple_with_whitespaces
|
||||||
|
template = Template.parse(%| {{ test }} |)
|
||||||
|
assert_equal ' worked ', template.render!('test' => 'worked')
|
||||||
|
assert_equal ' worked wonderfully ', template.render!('test' => 'worked wonderfully')
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_ignore_unknown
|
||||||
|
template = Template.parse(%|{{ test }}|)
|
||||||
|
assert_equal '', template.render!
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_hash_scoping
|
||||||
|
template = Template.parse(%|{{ test.test }}|)
|
||||||
|
assert_equal 'worked', template.render!('test' => {'test' => 'worked'})
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_false_renders_as_false
|
||||||
|
assert_equal 'false', Template.parse("{{ foo }}").render!('foo' => false)
|
||||||
|
assert_equal 'false', Template.parse("{{ false }}").render!
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_nil_renders_as_empty_string
|
||||||
|
assert_equal '', Template.parse("{{ nil }}").render!
|
||||||
|
assert_equal 'cat', Template.parse("{{ nil | append: 'cat' }}").render!
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_preset_assigns
|
||||||
|
template = Template.parse(%|{{ test }}|)
|
||||||
|
template.assigns['test'] = 'worked'
|
||||||
|
assert_equal 'worked', template.render!
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_reuse_parsed_template
|
||||||
|
template = Template.parse(%|{{ greeting }} {{ name }}|)
|
||||||
|
template.assigns['greeting'] = 'Goodbye'
|
||||||
|
assert_equal 'Hello Tobi', template.render!('greeting' => 'Hello', 'name' => 'Tobi')
|
||||||
|
assert_equal 'Hello ', template.render!('greeting' => 'Hello', 'unknown' => 'Tobi')
|
||||||
|
assert_equal 'Hello Brian', template.render!('greeting' => 'Hello', 'name' => 'Brian')
|
||||||
|
assert_equal 'Goodbye Brian', template.render!('name' => 'Brian')
|
||||||
|
assert_equal({'greeting'=>'Goodbye'}, template.assigns)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_assigns_not_polluted_from_template
|
||||||
|
template = Template.parse(%|{{ test }}{% assign test = 'bar' %}{{ test }}|)
|
||||||
|
template.assigns['test'] = 'baz'
|
||||||
|
assert_equal 'bazbar', template.render!
|
||||||
|
assert_equal 'bazbar', template.render!
|
||||||
|
assert_equal 'foobar', template.render!('test' => 'foo')
|
||||||
|
assert_equal 'bazbar', template.render!
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_hash_with_default_proc
|
||||||
|
template = Template.parse(%|Hello {{ test }}|)
|
||||||
|
assigns = Hash.new { |h,k| raise "Unknown variable '#{k}'" }
|
||||||
|
assigns['test'] = 'Tobi'
|
||||||
|
assert_equal 'Hello Tobi', template.render!(assigns)
|
||||||
|
assigns.delete('test')
|
||||||
|
e = assert_raises(RuntimeError) {
|
||||||
|
template.render!(assigns)
|
||||||
|
}
|
||||||
|
assert_equal "Unknown variable 'test'", e.message
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_multiline_variable
|
||||||
|
assert_equal 'worked', Template.parse("{{\ntest\n}}").render!('test' => 'worked')
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -1,110 +0,0 @@
|
|||||||
require 'test_helper'
|
|
||||||
|
|
||||||
class ErrorDrop < Liquid::Drop
|
|
||||||
def standard_error
|
|
||||||
raise Liquid::StandardError, 'standard error'
|
|
||||||
end
|
|
||||||
|
|
||||||
def argument_error
|
|
||||||
raise Liquid::ArgumentError, 'argument error'
|
|
||||||
end
|
|
||||||
|
|
||||||
def syntax_error
|
|
||||||
raise Liquid::SyntaxError, 'syntax error'
|
|
||||||
end
|
|
||||||
|
|
||||||
def exception
|
|
||||||
raise Exception, 'exception'
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
class ErrorHandlingTest < Test::Unit::TestCase
|
|
||||||
include Liquid
|
|
||||||
|
|
||||||
def test_standard_error
|
|
||||||
assert_nothing_raised do
|
|
||||||
template = Liquid::Template.parse( ' {{ errors.standard_error }} ' )
|
|
||||||
assert_equal ' Liquid error: standard error ', template.render('errors' => ErrorDrop.new)
|
|
||||||
|
|
||||||
assert_equal 1, template.errors.size
|
|
||||||
assert_equal StandardError, template.errors.first.class
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_syntax
|
|
||||||
|
|
||||||
assert_nothing_raised do
|
|
||||||
|
|
||||||
template = Liquid::Template.parse( ' {{ errors.syntax_error }} ' )
|
|
||||||
assert_equal ' Liquid syntax error: syntax error ', template.render('errors' => ErrorDrop.new)
|
|
||||||
|
|
||||||
assert_equal 1, template.errors.size
|
|
||||||
assert_equal SyntaxError, template.errors.first.class
|
|
||||||
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_argument
|
|
||||||
assert_nothing_raised do
|
|
||||||
|
|
||||||
template = Liquid::Template.parse( ' {{ errors.argument_error }} ' )
|
|
||||||
assert_equal ' Liquid error: argument error ', template.render('errors' => ErrorDrop.new)
|
|
||||||
|
|
||||||
assert_equal 1, template.errors.size
|
|
||||||
assert_equal ArgumentError, template.errors.first.class
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_missing_endtag_parse_time_error
|
|
||||||
assert_raise(Liquid::SyntaxError) do
|
|
||||||
Liquid::Template.parse(' {% for a in b %} ... ')
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_unrecognized_operator
|
|
||||||
with_error_mode(:strict) do
|
|
||||||
assert_raise(SyntaxError) do
|
|
||||||
Liquid::Template.parse(' {% if 1 =! 2 %}ok{% endif %} ')
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_lax_unrecognized_operator
|
|
||||||
assert_nothing_raised do
|
|
||||||
template = Liquid::Template.parse(' {% if 1 =! 2 %}ok{% endif %} ', :error_mode => :lax)
|
|
||||||
assert_equal ' Liquid error: Unknown operator =! ', template.render
|
|
||||||
assert_equal 1, template.errors.size
|
|
||||||
assert_equal Liquid::ArgumentError, template.errors.first.class
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_strict_error_messages
|
|
||||||
err = assert_raise(SyntaxError) do
|
|
||||||
Liquid::Template.parse(' {% if 1 =! 2 %}ok{% endif %} ', :error_mode => :strict)
|
|
||||||
end
|
|
||||||
assert_equal 'Unexpected character = in "1 =! 2"', err.message
|
|
||||||
|
|
||||||
err = assert_raise(SyntaxError) do
|
|
||||||
Liquid::Template.parse('{{%%%}}', :error_mode => :strict)
|
|
||||||
end
|
|
||||||
assert_equal 'Unexpected character % in "{{%%%}}"', err.message
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_warnings
|
|
||||||
template = Liquid::Template.parse('{% if ~~~ %}{{%%%}}{% else %}{{ hello. }}{% endif %}', :error_mode => :warn)
|
|
||||||
assert_equal 3, template.warnings.size
|
|
||||||
assert_equal 'Unexpected character ~ in "~~~"', template.warnings[0].message
|
|
||||||
assert_equal 'Unexpected character % in "{{%%%}}"', template.warnings[1].message
|
|
||||||
assert_equal 'Expected id but found end_of_string in "{{ hello. }}"', template.warnings[2].message
|
|
||||||
assert_equal '', template.render
|
|
||||||
end
|
|
||||||
|
|
||||||
# Liquid should not catch Exceptions that are not subclasses of StandardError, like Interrupt and NoMemoryError
|
|
||||||
def test_exceptions_propagate
|
|
||||||
assert_raise Exception do
|
|
||||||
template = Liquid::Template.parse( ' {{ errors.exception }} ' )
|
|
||||||
template.render('errors' => ErrorDrop.new)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end # ErrorHandlingTest
|
|
||||||
@@ -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,90 +0,0 @@
|
|||||||
require 'test_helper'
|
|
||||||
|
|
||||||
class ParsingQuirksTest < Test::Unit::TestCase
|
|
||||||
include Liquid
|
|
||||||
|
|
||||||
def test_error_with_css
|
|
||||||
text = %| div { font-weight: bold; } |
|
|
||||||
template = Template.parse(text)
|
|
||||||
|
|
||||||
assert_equal text, template.render
|
|
||||||
assert_equal [String], template.root.nodelist.collect {|i| i.class}
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_raise_on_single_close_bracet
|
|
||||||
assert_raise(SyntaxError) do
|
|
||||||
Template.parse("text {{method} oh nos!")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_raise_on_label_and_no_close_bracets
|
|
||||||
assert_raise(SyntaxError) do
|
|
||||||
Template.parse("TEST {{ ")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_raise_on_label_and_no_close_bracets_percent
|
|
||||||
assert_raise(SyntaxError) do
|
|
||||||
Template.parse("TEST {% ")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_error_on_empty_filter
|
|
||||||
assert_nothing_raised do
|
|
||||||
Template.parse("{{test}}")
|
|
||||||
Template.parse("{{|test}}")
|
|
||||||
end
|
|
||||||
with_error_mode(:strict) do
|
|
||||||
assert_raise(SyntaxError) do
|
|
||||||
Template.parse("{{test |a|b|}}")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_meaningless_parens_error
|
|
||||||
with_error_mode(:strict) do
|
|
||||||
assert_raise(SyntaxError) do
|
|
||||||
markup = "a == 'foo' or (b == 'bar' and c == 'baz') or false"
|
|
||||||
Template.parse("{% if #{markup} %} YES {% endif %}")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_unexpected_characters_syntax_error
|
|
||||||
with_error_mode(:strict) do
|
|
||||||
assert_raise(SyntaxError) do
|
|
||||||
markup = "true && false"
|
|
||||||
Template.parse("{% if #{markup} %} YES {% endif %}")
|
|
||||||
end
|
|
||||||
assert_raise(SyntaxError) do
|
|
||||||
markup = "false || true"
|
|
||||||
Template.parse("{% if #{markup} %} YES {% endif %}")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_no_error_on_lax_empty_filter
|
|
||||||
assert_nothing_raised do
|
|
||||||
Template.parse("{{test |a|b|}}", :error_mode => :lax)
|
|
||||||
Template.parse("{{test}}", :error_mode => :lax)
|
|
||||||
Template.parse("{{|test|}}", :error_mode => :lax)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_meaningless_parens_lax
|
|
||||||
with_error_mode(:lax) do
|
|
||||||
assigns = {'b' => 'bar', 'c' => 'baz'}
|
|
||||||
markup = "a == 'foo' or (b == 'bar' and c == 'baz') or false"
|
|
||||||
assert_template_result(' YES ',"{% if #{markup} %} YES {% endif %}", assigns)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_unexpected_characters_silently_eat_logic_lax
|
|
||||||
with_error_mode(:lax) do
|
|
||||||
markup = "true && false"
|
|
||||||
assert_template_result(' YES ',"{% if #{markup} %} YES {% endif %}")
|
|
||||||
markup = "false || true"
|
|
||||||
assert_template_result('',"{% if #{markup} %} YES {% endif %}")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end # ParsingQuirksTest
|
|
||||||
@@ -1,134 +0,0 @@
|
|||||||
require 'test_helper'
|
|
||||||
|
|
||||||
class StatementsTest < Test::Unit::TestCase
|
|
||||||
include Liquid
|
|
||||||
|
|
||||||
def test_true_eql_true
|
|
||||||
text = %| {% if true == true %} true {% else %} false {% endif %} |
|
|
||||||
expected = %| true |
|
|
||||||
assert_equal expected, Template.parse(text).render
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_true_not_eql_true
|
|
||||||
text = %| {% if true != true %} true {% else %} false {% endif %} |
|
|
||||||
expected = %| false |
|
|
||||||
assert_equal expected, Template.parse(text).render
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_true_lq_true
|
|
||||||
text = %| {% if 0 > 0 %} true {% else %} false {% endif %} |
|
|
||||||
expected = %| false |
|
|
||||||
assert_equal expected, Template.parse(text).render
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_one_lq_zero
|
|
||||||
text = %| {% if 1 > 0 %} true {% else %} false {% endif %} |
|
|
||||||
expected = %| true |
|
|
||||||
assert_equal expected, Template.parse(text).render
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_zero_lq_one
|
|
||||||
text = %| {% if 0 < 1 %} true {% else %} false {% endif %} |
|
|
||||||
expected = %| true |
|
|
||||||
assert_equal expected, Template.parse(text).render
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_zero_lq_or_equal_one
|
|
||||||
text = %| {% if 0 <= 0 %} true {% else %} false {% endif %} |
|
|
||||||
expected = %| true |
|
|
||||||
assert_equal expected, Template.parse(text).render
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_zero_lq_or_equal_one_involving_nil
|
|
||||||
text = %| {% if null <= 0 %} true {% else %} false {% endif %} |
|
|
||||||
expected = %| false |
|
|
||||||
assert_equal expected, Template.parse(text).render
|
|
||||||
|
|
||||||
|
|
||||||
text = %| {% if 0 <= null %} true {% else %} false {% endif %} |
|
|
||||||
expected = %| false |
|
|
||||||
assert_equal expected, Template.parse(text).render
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_zero_lqq_or_equal_one
|
|
||||||
text = %| {% if 0 >= 0 %} true {% else %} false {% endif %} |
|
|
||||||
expected = %| true |
|
|
||||||
assert_equal expected, Template.parse(text).render
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_strings
|
|
||||||
text = %| {% if 'test' == 'test' %} true {% else %} false {% endif %} |
|
|
||||||
expected = %| true |
|
|
||||||
assert_equal expected, Template.parse(text).render
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_strings_not_equal
|
|
||||||
text = %| {% if 'test' != 'test' %} true {% else %} false {% endif %} |
|
|
||||||
expected = %| false |
|
|
||||||
assert_equal expected, Template.parse(text).render
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_var_strings_equal
|
|
||||||
text = %| {% if var == "hello there!" %} true {% else %} false {% endif %} |
|
|
||||||
expected = %| true |
|
|
||||||
assert_equal expected, Template.parse(text).render('var' => 'hello there!')
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_var_strings_are_not_equal
|
|
||||||
text = %| {% if "hello there!" == var %} true {% else %} false {% endif %} |
|
|
||||||
expected = %| true |
|
|
||||||
assert_equal expected, Template.parse(text).render('var' => 'hello there!')
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_var_and_long_string_are_equal
|
|
||||||
text = %| {% if var == 'hello there!' %} true {% else %} false {% endif %} |
|
|
||||||
expected = %| true |
|
|
||||||
assert_equal expected, Template.parse(text).render('var' => 'hello there!')
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
def test_var_and_long_string_are_equal_backwards
|
|
||||||
text = %| {% if 'hello there!' == var %} true {% else %} false {% endif %} |
|
|
||||||
expected = %| true |
|
|
||||||
assert_equal expected, Template.parse(text).render('var' => 'hello there!')
|
|
||||||
end
|
|
||||||
|
|
||||||
#def test_is_nil
|
|
||||||
# text = %| {% if var != nil %} true {% else %} false {% end %} |
|
|
||||||
# @template.assigns = { 'var' => 'hello there!'}
|
|
||||||
# expected = %| true |
|
|
||||||
# assert_equal expected, @template.parse(text)
|
|
||||||
#end
|
|
||||||
|
|
||||||
def test_is_collection_empty
|
|
||||||
text = %| {% if array == empty %} true {% else %} false {% endif %} |
|
|
||||||
expected = %| true |
|
|
||||||
assert_equal expected, Template.parse(text).render('array' => [])
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_is_not_collection_empty
|
|
||||||
text = %| {% if array == empty %} true {% else %} false {% endif %} |
|
|
||||||
expected = %| false |
|
|
||||||
assert_equal expected, Template.parse(text).render('array' => [1,2,3])
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_nil
|
|
||||||
text = %| {% if var == nil %} true {% else %} false {% endif %} |
|
|
||||||
expected = %| true |
|
|
||||||
assert_equal expected, Template.parse(text).render('var' => nil)
|
|
||||||
|
|
||||||
text = %| {% if var == null %} true {% else %} false {% endif %} |
|
|
||||||
expected = %| true |
|
|
||||||
assert_equal expected, Template.parse(text).render('var' => nil)
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_not_nil
|
|
||||||
text = %| {% if var != nil %} true {% else %} false {% endif %} |
|
|
||||||
expected = %| true |
|
|
||||||
assert_equal expected, Template.parse(text).render('var' => 1 )
|
|
||||||
|
|
||||||
text = %| {% if var != null %} true {% else %} false {% endif %} |
|
|
||||||
expected = %| true |
|
|
||||||
assert_equal expected, Template.parse(text).render('var' => 1 )
|
|
||||||
end
|
|
||||||
end # StatementsTest
|
|
||||||
@@ -1,200 +0,0 @@
|
|||||||
require 'test_helper'
|
|
||||||
|
|
||||||
class VariableTest < Test::Unit::TestCase
|
|
||||||
include Liquid
|
|
||||||
|
|
||||||
def test_variable
|
|
||||||
var = Variable.new('hello')
|
|
||||||
assert_equal 'hello', var.name
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_filters
|
|
||||||
var = Variable.new('hello | textileze')
|
|
||||||
assert_equal 'hello', var.name
|
|
||||||
assert_equal [["textileze",[]]], var.filters
|
|
||||||
|
|
||||||
var = Variable.new('hello | textileze | paragraph')
|
|
||||||
assert_equal 'hello', var.name
|
|
||||||
assert_equal [["textileze",[]], ["paragraph",[]]], var.filters
|
|
||||||
|
|
||||||
var = Variable.new(%! hello | strftime: '%Y'!)
|
|
||||||
assert_equal 'hello', var.name
|
|
||||||
assert_equal [["strftime",["'%Y'"]]], var.filters
|
|
||||||
|
|
||||||
var = Variable.new(%! 'typo' | link_to: 'Typo', true !)
|
|
||||||
assert_equal %!'typo'!, var.name
|
|
||||||
assert_equal [["link_to",["'Typo'", "true"]]], var.filters
|
|
||||||
|
|
||||||
var = Variable.new(%! 'typo' | link_to: 'Typo', false !)
|
|
||||||
assert_equal %!'typo'!, var.name
|
|
||||||
assert_equal [["link_to",["'Typo'", "false"]]], var.filters
|
|
||||||
|
|
||||||
var = Variable.new(%! 'foo' | repeat: 3 !)
|
|
||||||
assert_equal %!'foo'!, var.name
|
|
||||||
assert_equal [["repeat",["3"]]], var.filters
|
|
||||||
|
|
||||||
var = Variable.new(%! 'foo' | repeat: 3, 3 !)
|
|
||||||
assert_equal %!'foo'!, var.name
|
|
||||||
assert_equal [["repeat",["3","3"]]], var.filters
|
|
||||||
|
|
||||||
var = Variable.new(%! 'foo' | repeat: 3, 3, 3 !)
|
|
||||||
assert_equal %!'foo'!, var.name
|
|
||||||
assert_equal [["repeat",["3","3","3"]]], var.filters
|
|
||||||
|
|
||||||
var = Variable.new(%! hello | strftime: '%Y, okay?'!)
|
|
||||||
assert_equal 'hello', var.name
|
|
||||||
assert_equal [["strftime",["'%Y, okay?'"]]], var.filters
|
|
||||||
|
|
||||||
var = Variable.new(%! hello | things: "%Y, okay?", 'the other one'!)
|
|
||||||
assert_equal 'hello', var.name
|
|
||||||
assert_equal [["things",["\"%Y, okay?\"","'the other one'"]]], var.filters
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_filter_with_date_parameter
|
|
||||||
|
|
||||||
var = Variable.new(%! '2006-06-06' | date: "%m/%d/%Y"!)
|
|
||||||
assert_equal "'2006-06-06'", var.name
|
|
||||||
assert_equal [["date",["\"%m/%d/%Y\""]]], var.filters
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_filters_without_whitespace
|
|
||||||
var = Variable.new('hello | textileze | paragraph')
|
|
||||||
assert_equal 'hello', var.name
|
|
||||||
assert_equal [["textileze",[]], ["paragraph",[]]], var.filters
|
|
||||||
|
|
||||||
var = Variable.new('hello|textileze|paragraph')
|
|
||||||
assert_equal 'hello', var.name
|
|
||||||
assert_equal [["textileze",[]], ["paragraph",[]]], var.filters
|
|
||||||
|
|
||||||
var = Variable.new("hello|replace:'foo','bar'|textileze")
|
|
||||||
assert_equal 'hello', var.name
|
|
||||||
assert_equal [["replace", ["'foo'", "'bar'"]], ["textileze", []]], var.filters
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_symbol
|
|
||||||
var = Variable.new("http://disney.com/logo.gif | image: 'med' ", :error_mode => :lax)
|
|
||||||
assert_equal "http://disney.com/logo.gif", var.name
|
|
||||||
assert_equal [["image",["'med'"]]], var.filters
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_string_to_filter
|
|
||||||
var = Variable.new("'http://disney.com/logo.gif' | image: 'med' ")
|
|
||||||
assert_equal "'http://disney.com/logo.gif'", var.name
|
|
||||||
assert_equal [["image",["'med'"]]], var.filters
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_string_single_quoted
|
|
||||||
var = Variable.new(%| "hello" |)
|
|
||||||
assert_equal '"hello"', var.name
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_string_double_quoted
|
|
||||||
var = Variable.new(%| 'hello' |)
|
|
||||||
assert_equal "'hello'", var.name
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_integer
|
|
||||||
var = Variable.new(%| 1000 |)
|
|
||||||
assert_equal "1000", var.name
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_float
|
|
||||||
var = Variable.new(%| 1000.01 |)
|
|
||||||
assert_equal "1000.01", var.name
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_string_with_special_chars
|
|
||||||
var = Variable.new(%| 'hello! $!@.;"ddasd" ' |)
|
|
||||||
assert_equal %|'hello! $!@.;"ddasd" '|, var.name
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_string_dot
|
|
||||||
var = Variable.new(%| test.test |)
|
|
||||||
assert_equal 'test.test', var.name
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_filter_with_keyword_arguments
|
|
||||||
var = Variable.new(%! hello | things: greeting: "world", farewell: 'goodbye'!)
|
|
||||||
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' !, :error_mode => :lax)
|
|
||||||
assert_equal 'number_of_comments', var.name
|
|
||||||
assert_equal [['pluralize',["'comment'","'comments'"]]], var.filters
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_strict_filter_argument_parsing
|
|
||||||
with_error_mode(:strict) do
|
|
||||||
assert_raises(SyntaxError) do
|
|
||||||
Variable.new(%! number_of_comments | pluralize: 'comment': 'comments' !)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
class VariableResolutionTest < Test::Unit::TestCase
|
|
||||||
include Liquid
|
|
||||||
|
|
||||||
def test_simple_variable
|
|
||||||
template = Template.parse(%|{{test}}|)
|
|
||||||
assert_equal 'worked', template.render('test' => 'worked')
|
|
||||||
assert_equal 'worked wonderfully', template.render('test' => 'worked wonderfully')
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_simple_with_whitespaces
|
|
||||||
template = Template.parse(%| {{ test }} |)
|
|
||||||
assert_equal ' worked ', template.render('test' => 'worked')
|
|
||||||
assert_equal ' worked wonderfully ', template.render('test' => 'worked wonderfully')
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_ignore_unknown
|
|
||||||
template = Template.parse(%|{{ test }}|)
|
|
||||||
assert_equal '', template.render
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_hash_scoping
|
|
||||||
template = Template.parse(%|{{ test.test }}|)
|
|
||||||
assert_equal 'worked', template.render('test' => {'test' => 'worked'})
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_preset_assigns
|
|
||||||
template = Template.parse(%|{{ test }}|)
|
|
||||||
template.assigns['test'] = 'worked'
|
|
||||||
assert_equal 'worked', template.render
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_reuse_parsed_template
|
|
||||||
template = Template.parse(%|{{ greeting }} {{ name }}|)
|
|
||||||
template.assigns['greeting'] = 'Goodbye'
|
|
||||||
assert_equal 'Hello Tobi', template.render('greeting' => 'Hello', 'name' => 'Tobi')
|
|
||||||
assert_equal 'Hello ', template.render('greeting' => 'Hello', 'unknown' => 'Tobi')
|
|
||||||
assert_equal 'Hello Brian', template.render('greeting' => 'Hello', 'name' => 'Brian')
|
|
||||||
assert_equal 'Goodbye Brian', template.render('name' => 'Brian')
|
|
||||||
assert_equal({'greeting'=>'Goodbye'}, template.assigns)
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_assigns_not_polluted_from_template
|
|
||||||
template = Template.parse(%|{{ test }}{% assign test = 'bar' %}{{ test }}|)
|
|
||||||
template.assigns['test'] = 'baz'
|
|
||||||
assert_equal 'bazbar', template.render
|
|
||||||
assert_equal 'bazbar', template.render
|
|
||||||
assert_equal 'foobar', template.render('test' => 'foo')
|
|
||||||
assert_equal 'bazbar', template.render
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_hash_with_default_proc
|
|
||||||
template = Template.parse(%|Hello {{ test }}|)
|
|
||||||
assigns = Hash.new { |h,k| raise "Unknown variable '#{k}'" }
|
|
||||||
assigns['test'] = 'Tobi'
|
|
||||||
assert_equal 'Hello Tobi', template.render!(assigns)
|
|
||||||
assigns.delete('test')
|
|
||||||
e = assert_raises(RuntimeError) {
|
|
||||||
template.render!(assigns)
|
|
||||||
}
|
|
||||||
assert_equal "Unknown variable 'test'", e.message
|
|
||||||
end
|
|
||||||
end # VariableTest
|
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
#!/usr/bin/env ruby
|
#!/usr/bin/env ruby
|
||||||
|
|
||||||
require 'test/unit'
|
ENV["MT_NO_EXPECTATIONS"] = "1"
|
||||||
require 'test/unit/assertions'
|
require 'minitest/autorun'
|
||||||
|
require 'spy/integration'
|
||||||
|
|
||||||
$:.unshift(File.join(File.expand_path(File.dirname(__FILE__)), '..', 'lib'))
|
$:.unshift(File.join(File.expand_path(File.dirname(__FILE__)), '..', 'lib'))
|
||||||
require 'liquid.rb'
|
require 'liquid.rb'
|
||||||
@@ -13,41 +14,76 @@ if env_mode = ENV['LIQUID_PARSER_MODE']
|
|||||||
end
|
end
|
||||||
Liquid::Template.error_mode = mode
|
Liquid::Template.error_mode = mode
|
||||||
|
|
||||||
|
if Minitest.const_defined?('Test')
|
||||||
|
# We're on Minitest 5+. Nothing to do here.
|
||||||
|
else
|
||||||
|
# Minitest 4 doesn't have Minitest::Test yet.
|
||||||
|
Minitest::Test = MiniTest::Unit::TestCase
|
||||||
|
end
|
||||||
|
|
||||||
module Test
|
module Minitest
|
||||||
module Unit
|
class Test
|
||||||
class TestCase
|
def fixture(name)
|
||||||
def fixture(name)
|
File.join(File.expand_path(File.dirname(__FILE__)), "fixtures", name)
|
||||||
File.join(File.expand_path(File.dirname(__FILE__)), "fixtures", name)
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
module Assertions
|
||||||
|
include Liquid
|
||||||
|
|
||||||
|
def assert_template_result(expected, template, assigns = {}, message = nil)
|
||||||
|
assert_equal expected, Template.parse(template).render!(assigns), message
|
||||||
end
|
end
|
||||||
|
|
||||||
module Assertions
|
def assert_template_result_matches(expected, template, assigns = {}, message = nil)
|
||||||
include Liquid
|
return assert_template_result(expected, template, assigns, message) unless expected.is_a? Regexp
|
||||||
|
|
||||||
def assert_template_result(expected, template, assigns = {}, message = nil)
|
assert_match expected, Template.parse(template).render!(assigns), message
|
||||||
assert_equal expected, Template.parse(template).render(assigns)
|
end
|
||||||
|
|
||||||
|
def assert_match_syntax_error(match, template, registers = {})
|
||||||
|
exception = assert_raises(Liquid::SyntaxError) {
|
||||||
|
Template.parse(template).render(assigns)
|
||||||
|
}
|
||||||
|
assert_match match, exception.message
|
||||||
|
end
|
||||||
|
|
||||||
|
def with_global_filter(*globals)
|
||||||
|
original_global_strainer = Liquid::Strainer.class_variable_get(:@@global_strainer)
|
||||||
|
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|
|
||||||
|
Liquid::Template.register_filter(global)
|
||||||
end
|
end
|
||||||
|
yield
|
||||||
|
ensure
|
||||||
|
Liquid::Strainer.class_variable_get(:@@strainer_class_cache).clear
|
||||||
|
Liquid::Strainer.class_variable_set(:@@global_strainer, original_global_strainer)
|
||||||
|
end
|
||||||
|
|
||||||
def assert_template_result_matches(expected, template, assigns = {}, message = nil)
|
def with_taint_mode(mode)
|
||||||
return assert_template_result(expected, template, assigns, message) unless expected.is_a? Regexp
|
old_mode = Liquid::Template.taint_mode
|
||||||
|
Liquid::Template.taint_mode = mode
|
||||||
|
yield
|
||||||
|
ensure
|
||||||
|
Liquid::Template.taint_mode = old_mode
|
||||||
|
end
|
||||||
|
|
||||||
assert_match expected, Template.parse(template).render(assigns)
|
def with_error_mode(mode)
|
||||||
end
|
old_mode = Liquid::Template.error_mode
|
||||||
|
Liquid::Template.error_mode = mode
|
||||||
|
yield
|
||||||
|
ensure
|
||||||
|
Liquid::Template.error_mode = old_mode
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def assert_match_syntax_error(match, template, registers = {})
|
class ThingWithToLiquid
|
||||||
exception = assert_raise(Liquid::SyntaxError) {
|
def to_liquid
|
||||||
Template.parse(template).render(assigns)
|
'foobar'
|
||||||
}
|
end
|
||||||
assert_match match, exception.message
|
end
|
||||||
end
|
|
||||||
|
|
||||||
def with_error_mode(mode)
|
|
||||||
old_mode = Liquid::Template.error_mode
|
|
||||||
Liquid::Template.error_mode = mode
|
|
||||||
yield
|
|
||||||
Liquid::Template.error_mode = old_mode
|
|
||||||
end
|
|
||||||
end # Assertions
|
|
||||||
end # Unit
|
|
||||||
end # Test
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
require 'test_helper'
|
require 'test_helper'
|
||||||
|
|
||||||
class BlockTest < Test::Unit::TestCase
|
class BlockUnitTest < Minitest::Test
|
||||||
include Liquid
|
include Liquid
|
||||||
|
|
||||||
def test_blankspace
|
def test_blankspace
|
||||||
@@ -45,10 +45,7 @@ class BlockTest < Test::Unit::TestCase
|
|||||||
|
|
||||||
def test_with_custom_tag
|
def test_with_custom_tag
|
||||||
Liquid::Template.register_tag("testtag", Block)
|
Liquid::Template.register_tag("testtag", Block)
|
||||||
|
assert Liquid::Template.parse( "{% testtag %} {% endtesttag %}")
|
||||||
assert_nothing_thrown do
|
|
||||||
template = Liquid::Template.parse( "{% testtag %} {% endtesttag %}")
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
require 'test_helper'
|
require 'test_helper'
|
||||||
|
|
||||||
class ConditionTest < Test::Unit::TestCase
|
class ConditionUnitTest < Minitest::Test
|
||||||
include Liquid
|
include Liquid
|
||||||
|
|
||||||
def test_basic_condition
|
def test_basic_condition
|
||||||
@@ -49,6 +49,17 @@ class ConditionTest < Test::Unit::TestCase
|
|||||||
assert_evalutes_false "'bob'", 'contains', "'---'"
|
assert_evalutes_false "'bob'", 'contains', "'---'"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_invalid_comparation_operator
|
||||||
|
assert_evaluates_argument_error "1", '~~', '0'
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_comparation_of_int_and_str
|
||||||
|
assert_evaluates_argument_error "'1'", '>', '0'
|
||||||
|
assert_evaluates_argument_error "'1'", '<', '0'
|
||||||
|
assert_evaluates_argument_error "'1'", '>=', '0'
|
||||||
|
assert_evaluates_argument_error "'1'", '<=', '0'
|
||||||
|
end
|
||||||
|
|
||||||
def test_contains_works_on_arrays
|
def test_contains_works_on_arrays
|
||||||
@context = Liquid::Context.new
|
@context = Liquid::Context.new
|
||||||
@context['array'] = [1,2,3,4,5]
|
@context['array'] = [1,2,3,4,5]
|
||||||
@@ -69,6 +80,10 @@ class ConditionTest < Test::Unit::TestCase
|
|||||||
assert_evalutes_false "0", 'contains', 'not_assigned'
|
assert_evalutes_false "0", 'contains', 'not_assigned'
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_contains_return_false_on_wrong_data_type
|
||||||
|
assert_evalutes_false "1", 'contains', '0'
|
||||||
|
end
|
||||||
|
|
||||||
def test_or_condition
|
def test_or_condition
|
||||||
condition = Condition.new('1', '==', '2')
|
condition = Condition.new('1', '==', '2')
|
||||||
|
|
||||||
@@ -124,4 +139,11 @@ class ConditionTest < Test::Unit::TestCase
|
|||||||
assert !Condition.new(left, op, right).evaluate(@context || Liquid::Context.new),
|
assert !Condition.new(left, op, right).evaluate(@context || Liquid::Context.new),
|
||||||
"Evaluated true: #{left} #{op} #{right}"
|
"Evaluated true: #{left} #{op} #{right}"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def assert_evaluates_argument_error(left, op, right)
|
||||||
|
assert_raises(Liquid::ArgumentError) do
|
||||||
|
Condition.new(left, op, right).evaluate(@context || Liquid::Context.new)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
end # ConditionTest
|
end # ConditionTest
|
||||||
@@ -63,13 +63,17 @@ class ArrayLike
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
class ContextTest < Test::Unit::TestCase
|
class ContextUnitTest < Minitest::Test
|
||||||
include Liquid
|
include Liquid
|
||||||
|
|
||||||
def setup
|
def setup
|
||||||
@context = Liquid::Context.new
|
@context = Liquid::Context.new
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def teardown
|
||||||
|
Spy.teardown
|
||||||
|
end
|
||||||
|
|
||||||
def test_variables
|
def test_variables
|
||||||
@context['string'] = 'string'
|
@context['string'] = 'string'
|
||||||
assert_equal 'string', @context['string']
|
assert_equal 'string', @context['string']
|
||||||
@@ -103,16 +107,14 @@ class ContextTest < Test::Unit::TestCase
|
|||||||
end
|
end
|
||||||
|
|
||||||
def test_scoping
|
def test_scoping
|
||||||
assert_nothing_raised do
|
@context.push
|
||||||
@context.push
|
@context.pop
|
||||||
|
|
||||||
|
assert_raises(Liquid::ContextError) do
|
||||||
@context.pop
|
@context.pop
|
||||||
end
|
end
|
||||||
|
|
||||||
assert_raise(Liquid::ContextError) do
|
assert_raises(Liquid::ContextError) do
|
||||||
@context.pop
|
|
||||||
end
|
|
||||||
|
|
||||||
assert_raise(Liquid::ContextError) do
|
|
||||||
@context.push
|
@context.push
|
||||||
@context.pop
|
@context.pop
|
||||||
@context.pop
|
@context.pop
|
||||||
@@ -162,24 +164,6 @@ class ContextTest < Test::Unit::TestCase
|
|||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_override_global_filter
|
|
||||||
global = Module.new do
|
|
||||||
def notice(output)
|
|
||||||
"Global #{output}"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local = Module.new do
|
|
||||||
def notice(output)
|
|
||||||
"Local #{output}"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
Template.register_filter(global)
|
|
||||||
assert_equal 'Global test', Template.parse("{{'test' | notice }}").render
|
|
||||||
assert_equal 'Local test', Template.parse("{{'test' | notice }}").render({}, :filters => [local])
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_only_intended_filters_make_it_there
|
def test_only_intended_filters_make_it_there
|
||||||
|
|
||||||
filter = Module.new do
|
filter = Module.new do
|
||||||
@@ -475,4 +459,34 @@ class ContextTest < Test::Unit::TestCase
|
|||||||
assert_kind_of CategoryDrop, @context['category']
|
assert_kind_of CategoryDrop, @context['category']
|
||||||
assert_equal @context, @context['category'].context
|
assert_equal @context, @context['category'].context
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_use_empty_instead_of_any_in_interrupt_handling_to_avoid_lots_of_unnecessary_object_allocations
|
||||||
|
mock_any = Spy.on_instance_method(Array, :any?)
|
||||||
|
mock_empty = Spy.on_instance_method(Array, :empty?)
|
||||||
|
mock_has_interrupt = Spy.on(@context, :has_interrupt?).and_call_through
|
||||||
|
|
||||||
|
@context.has_interrupt?
|
||||||
|
|
||||||
|
refute mock_any.has_been_called?
|
||||||
|
assert mock_empty.has_been_called?
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_variable_lookup_caches_markup
|
||||||
|
mock_scan = Spy.on_instance_method(String, :scan).and_return(["string"])
|
||||||
|
|
||||||
|
@context['string'] = 'string'
|
||||||
|
@context['string']
|
||||||
|
@context['string']
|
||||||
|
|
||||||
|
assert_equal 1, mock_scan.calls.size
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_context_initialization_with_a_proc_in_environment
|
||||||
|
contx = Context.new([:test => lambda { |c| c['poutine']}], {:test => :foo})
|
||||||
|
|
||||||
|
assert contx
|
||||||
|
assert_nil contx['poutine']
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
end # ContextTest
|
end # ContextTest
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
require 'test_helper'
|
require 'test_helper'
|
||||||
|
|
||||||
class FileSystemTest < Test::Unit::TestCase
|
class FileSystemUnitTest < Minitest::Test
|
||||||
include Liquid
|
include Liquid
|
||||||
|
|
||||||
def test_default
|
def test_default
|
||||||
assert_raise(FileSystemError) do
|
assert_raises(FileSystemError) do
|
||||||
BlankFileSystem.new.read_template_file("dummy", {'dummy'=>'smarty'})
|
BlankFileSystem.new.read_template_file("dummy", {'dummy'=>'smarty'})
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -14,15 +14,15 @@ class FileSystemTest < Test::Unit::TestCase
|
|||||||
assert_equal "/some/path/_mypartial.liquid" , file_system.full_path("mypartial")
|
assert_equal "/some/path/_mypartial.liquid" , file_system.full_path("mypartial")
|
||||||
assert_equal "/some/path/dir/_mypartial.liquid", file_system.full_path("dir/mypartial")
|
assert_equal "/some/path/dir/_mypartial.liquid", file_system.full_path("dir/mypartial")
|
||||||
|
|
||||||
assert_raise(FileSystemError) do
|
assert_raises(FileSystemError) do
|
||||||
file_system.full_path("../dir/mypartial")
|
file_system.full_path("../dir/mypartial")
|
||||||
end
|
end
|
||||||
|
|
||||||
assert_raise(FileSystemError) do
|
assert_raises(FileSystemError) do
|
||||||
file_system.full_path("/dir/../../dir/mypartial")
|
file_system.full_path("/dir/../../dir/mypartial")
|
||||||
end
|
end
|
||||||
|
|
||||||
assert_raise(FileSystemError) do
|
assert_raises(FileSystemError) do
|
||||||
file_system.full_path("/etc/passwd")
|
file_system.full_path("/etc/passwd")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
require 'test_helper'
|
require 'test_helper'
|
||||||
|
|
||||||
class I18nTest < Test::Unit::TestCase
|
class I18nUnitTest < Minitest::Test
|
||||||
include Liquid
|
include Liquid
|
||||||
|
|
||||||
def setup
|
def setup
|
||||||
@@ -20,13 +20,13 @@ class I18nTest < Test::Unit::TestCase
|
|||||||
end
|
end
|
||||||
|
|
||||||
# def test_raises_translation_error_on_undefined_interpolation_key
|
# def test_raises_translation_error_on_undefined_interpolation_key
|
||||||
# assert_raise I18n::TranslationError do
|
# assert_raises I18n::TranslationError do
|
||||||
# @i18n.translate("whatever", :oopstypos => "yes")
|
# @i18n.translate("whatever", :oopstypos => "yes")
|
||||||
# end
|
# end
|
||||||
# end
|
# end
|
||||||
|
|
||||||
def test_raises_unknown_translation
|
def test_raises_unknown_translation
|
||||||
assert_raise I18n::TranslationError do
|
assert_raises I18n::TranslationError do
|
||||||
@i18n.translate("doesnt_exist")
|
@i18n.translate("doesnt_exist")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
require 'test_helper'
|
require 'test_helper'
|
||||||
|
|
||||||
class LexerTest < Test::Unit::TestCase
|
class LexerUnitTest < Minitest::Test
|
||||||
include Liquid
|
include Liquid
|
||||||
|
|
||||||
def test_strings
|
def test_strings
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user