liquid 2.6.3 → 3.0.0.rc1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/History.md +42 -13
- data/README.md +27 -2
- data/lib/liquid.rb +11 -11
- data/lib/liquid/block.rb +75 -45
- data/lib/liquid/condition.rb +15 -11
- data/lib/liquid/context.rb +68 -29
- data/lib/liquid/document.rb +3 -3
- data/lib/liquid/drop.rb +17 -1
- data/lib/liquid/file_system.rb +17 -6
- data/lib/liquid/i18n.rb +39 -0
- data/lib/liquid/interrupts.rb +1 -1
- data/lib/liquid/lexer.rb +51 -0
- data/lib/liquid/locales/en.yml +22 -0
- data/lib/liquid/parser.rb +90 -0
- data/lib/liquid/standardfilters.rb +115 -52
- data/lib/liquid/strainer.rb +14 -4
- data/lib/liquid/tag.rb +42 -7
- data/lib/liquid/tags/assign.rb +10 -8
- data/lib/liquid/tags/break.rb +1 -1
- data/lib/liquid/tags/capture.rb +10 -8
- data/lib/liquid/tags/case.rb +13 -13
- data/lib/liquid/tags/comment.rb +9 -2
- data/lib/liquid/tags/continue.rb +1 -4
- data/lib/liquid/tags/cycle.rb +5 -7
- data/lib/liquid/tags/decrement.rb +3 -4
- data/lib/liquid/tags/for.rb +69 -36
- data/lib/liquid/tags/if.rb +52 -25
- data/lib/liquid/tags/ifchanged.rb +2 -2
- data/lib/liquid/tags/include.rb +8 -7
- data/lib/liquid/tags/increment.rb +4 -8
- data/lib/liquid/tags/raw.rb +3 -3
- data/lib/liquid/tags/table_row.rb +73 -0
- data/lib/liquid/tags/unless.rb +2 -4
- data/lib/liquid/template.rb +69 -10
- data/lib/liquid/utils.rb +13 -4
- data/lib/liquid/variable.rb +59 -8
- data/lib/liquid/version.rb +1 -1
- data/test/fixtures/en_locale.yml +9 -0
- data/test/{liquid → integration}/assign_test.rb +6 -0
- data/test/integration/blank_test.rb +106 -0
- data/test/{liquid → integration}/capture_test.rb +2 -2
- data/test/integration/context_test.rb +33 -0
- data/test/integration/drop_test.rb +245 -0
- data/test/{liquid → integration}/error_handling_test.rb +31 -2
- data/test/{liquid → integration}/filter_test.rb +7 -7
- data/test/{liquid → integration}/hash_ordering_test.rb +0 -0
- data/test/{liquid → integration}/output_test.rb +12 -12
- data/test/integration/parsing_quirks_test.rb +94 -0
- data/test/{liquid → integration}/security_test.rb +9 -9
- data/test/{liquid → integration}/standard_filter_test.rb +103 -33
- data/test/{liquid → integration}/tags/break_tag_test.rb +0 -0
- data/test/{liquid → integration}/tags/continue_tag_test.rb +0 -0
- data/test/{liquid → integration}/tags/for_tag_test.rb +78 -0
- data/test/{liquid → integration}/tags/if_else_tag_test.rb +1 -1
- data/test/integration/tags/include_tag_test.rb +212 -0
- data/test/{liquid → integration}/tags/increment_tag_test.rb +0 -0
- data/test/{liquid → integration}/tags/raw_tag_test.rb +1 -0
- data/test/{liquid → integration}/tags/standard_tag_test.rb +24 -22
- data/test/integration/tags/statements_test.rb +113 -0
- data/test/{liquid/tags/html_tag_test.rb → integration/tags/table_row_test.rb} +5 -5
- data/test/{liquid → integration}/tags/unless_else_tag_test.rb +0 -0
- data/test/{liquid → integration}/template_test.rb +66 -42
- data/test/integration/variable_test.rb +72 -0
- data/test/test_helper.rb +32 -7
- data/test/{liquid/block_test.rb → unit/block_unit_test.rb} +1 -1
- data/test/{liquid/condition_test.rb → unit/condition_unit_test.rb} +19 -1
- data/test/{liquid/context_test.rb → unit/context_unit_test.rb} +27 -19
- data/test/{liquid/file_system_test.rb → unit/file_system_unit_test.rb} +7 -1
- data/test/unit/i18n_unit_test.rb +37 -0
- data/test/unit/lexer_unit_test.rb +48 -0
- data/test/{liquid/module_ex_test.rb → unit/module_ex_unit_test.rb} +7 -7
- data/test/unit/parser_unit_test.rb +82 -0
- data/test/{liquid/regexp_test.rb → unit/regexp_unit_test.rb} +3 -3
- data/test/{liquid/strainer_test.rb → unit/strainer_unit_test.rb} +19 -1
- data/test/unit/tag_unit_test.rb +11 -0
- data/test/unit/tags/case_tag_unit_test.rb +10 -0
- data/test/unit/tags/for_tag_unit_test.rb +13 -0
- data/test/unit/tags/if_tag_unit_test.rb +8 -0
- data/test/unit/template_unit_test.rb +69 -0
- data/test/unit/tokenizer_unit_test.rb +29 -0
- data/test/{liquid/variable_test.rb → unit/variable_unit_test.rb} +17 -67
- metadata +117 -73
- data/lib/extras/liquid_view.rb +0 -51
- data/lib/liquid/htmltags.rb +0 -73
- data/test/liquid/drop_test.rb +0 -180
- data/test/liquid/parsing_quirks_test.rb +0 -52
- data/test/liquid/tags/include_tag_test.rb +0 -166
- data/test/liquid/tags/statements_test.rb +0 -134
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f98aa57f8713db177b94a4cd217b6c5f40234bc5
|
4
|
+
data.tar.gz: 50a9905b550fcedf21315f4764360f104d0964bb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7c9c6d8d006ef94568651f21dba6d4619cef50c575510e74159602140263a10f20148b4c278c17aa77221690e7dedb547aedf4af5fc866b1c3ab4644243b8d68
|
7
|
+
data.tar.gz: ff19880456ff443065ed50371ad4223fedcf350494564ba69e51c9c2c4e06281ffc54773e9f0cbae60fbee95a60be4514f13924b0d831db43a41fcabadab1a8c
|
data/History.md
CHANGED
@@ -1,17 +1,38 @@
|
|
1
1
|
# Liquid Version History
|
2
2
|
|
3
|
-
|
4
|
-
The following releases will only be tested against Ruby 1.9 and Ruby 2.0 and are likely to break on Ruby 1.8.
|
5
|
-
|
6
|
-
## 2.6.3 / 2015-07-23 / branch "2-6-stable"
|
7
|
-
|
8
|
-
* Fix test failure under certain timezones [Dylan Thacker-Smith]
|
9
|
-
|
10
|
-
## 2.6.2 / 2015-01-23
|
3
|
+
## 3.0.0 / not yet released / branch "master"
|
11
4
|
|
12
|
-
*
|
13
|
-
|
14
|
-
|
5
|
+
* ...
|
6
|
+
* Add exception_handler feature, see #397 and #254 [Bogdan Gusiev, bogdan and Florian Weingarten, fw42]
|
7
|
+
* Optimize variable parsing to avoid repeated regex evaluation during template rendering #383 [Jason Hiltz-Laforge, jasonhl]
|
8
|
+
* Optimize checking for block interrupts to reduce object allocation #380 [Jason Hiltz-Laforge, jasonhl]
|
9
|
+
* Properly set context rethrow_errors on render! #349 [Thierry Joyal, tjoyal]
|
10
|
+
* Fix broken rendering of variables which are equal to false, see #345 [Florian Weingarten, fw42]
|
11
|
+
* Remove ActionView template handler [Dylan Thacker-Smith, dylanahsmith]
|
12
|
+
* Freeze lots of string literals for new Ruby 2.1 optimization, see #297 [Florian Weingarten, fw42]
|
13
|
+
* Allow newlines in tags and variables, see #324 [Dylan Thacker-Smith, dylanahsmith]
|
14
|
+
* Tag#parse is called after initialize, which now takes options instead of tokens as the 3rd argument. See #321 [Dylan Thacker-Smith, dylanahsmith]
|
15
|
+
* Raise `Liquid::ArgumentError` instead of `::ArgumentError` when filter has wrong number of arguments #309 [Bogdan Gusiev, bogdan]
|
16
|
+
* Add a to_s default for liquid drops, see #306 [Adam Doeler, releod]
|
17
|
+
* Add strip, lstrip, and rstrip to standard filters [Florian Weingarten, fw42]
|
18
|
+
* Make if, for & case tags return complete and consistent nodelists, see #250 [Nick Jones, dntj]
|
19
|
+
* Prevent arbitrary method invocation on condition objects, see #274 [Dylan Thacker-Smith, dylanahsmith]
|
20
|
+
* Don't call to_sym when creating conditions for security reasons, see #273 [Bouke van der Bijl, bouk]
|
21
|
+
* Fix resource counting bug with respond_to?(:length), see #263 [Florian Weingarten, fw42]
|
22
|
+
* Allow specifying custom patterns for template filenames, see #284 [Andrei Gladkyi, agladkyi]
|
23
|
+
* Allow drops to optimize loading a slice of elements, see #282 [Tom Burns, boourns]
|
24
|
+
* Support for passing variables to snippets in subdirs, see #271 [Joost Hietbrink, joost]
|
25
|
+
* Add a class cache to avoid runtime extend calls, see #249 [James Tucker, raggi]
|
26
|
+
* Remove some legacy Ruby 1.8 compatibility code, see #276 [Florian Weingarten, fw42]
|
27
|
+
* Add default filter to standard filters, see #267 [Derrick Reimer, djreimer]
|
28
|
+
* Add optional strict parsing and warn parsing, see #235 [Tristan Hume, trishume]
|
29
|
+
* Add I18n syntax error translation, see #241 [Simon Hørup Eskildsen, Sirupsen]
|
30
|
+
* Make sort filter work on enumerable drops, see #239 [Florian Weingarten, fw42]
|
31
|
+
* Fix clashing method names in enumerable drops, see #238 [Florian Weingarten, fw42]
|
32
|
+
* Make map filter work on enumerable drops, see #233 [Florian Weingarten, fw42]
|
33
|
+
* Improved whitespace stripping for blank blocks, related to #216 [Florian Weingarten, fw42]
|
34
|
+
|
35
|
+
## 2.6.1 / 2014-01-10 / branch "2-6-stable"
|
15
36
|
|
16
37
|
Security fix, cherry-picked from master (4e14a65):
|
17
38
|
* Don't call to_sym when creating conditions for security reasons, see #273 [Bouke van der Bijl, bouk]
|
@@ -19,7 +40,9 @@ Security fix, cherry-picked from master (4e14a65):
|
|
19
40
|
|
20
41
|
## 2.6.0 / 2013-11-25
|
21
42
|
|
22
|
-
|
43
|
+
IMPORTANT: Liquid 2.6 is going to be the last version of Liquid which maintains explicit Ruby 1.8 compatability.
|
44
|
+
The following releases will only be tested against Ruby 1.9 and Ruby 2.0 and are likely to break on Ruby 1.8.
|
45
|
+
|
23
46
|
* Bugfix for #106: fix example servlet [gnowoel]
|
24
47
|
* Bugfix for #97: strip_html filter supports multi-line tags [Jo Liss, joliss]
|
25
48
|
* Bugfix for #114: strip_html filter supports style tags [James Allardice, jamesallardice]
|
@@ -39,7 +62,13 @@ Security fix, cherry-picked from master (4e14a65):
|
|
39
62
|
* Better documentation for 'include' tag (closes #163) [Peter Schröder, phoet]
|
40
63
|
* Use of BigDecimal on filters to have better precision (closes #155) [Arthur Nogueira Neves, arthurnn]
|
41
64
|
|
42
|
-
## 2.5.
|
65
|
+
## 2.5.5 / 2014-01-10 / branch "2-5-stable"
|
66
|
+
|
67
|
+
Security fix, cherry-picked from master (4e14a65):
|
68
|
+
* Don't call to_sym when creating conditions for security reasons, see #273 [Bouke van der Bijl, bouk]
|
69
|
+
* Prevent arbitrary method invocation on condition objects, see #274 [Dylan Thacker-Smith, dylanahsmith]
|
70
|
+
|
71
|
+
## 2.5.4 / 2013-11-11
|
43
72
|
|
44
73
|
* Fix "can't convert Fixnum into String" for "replace", see #173, [wǒ_is神仙, jsw0528]
|
45
74
|
|
data/README.md
CHANGED
@@ -1,9 +1,12 @@
|
|
1
|
+
[![Build Status](https://secure.travis-ci.org/Shopify/liquid.png?branch=master)](http://travis-ci.org/Shopify/liquid)
|
2
|
+
[![Inline docs](http://inch-ci.org/github/Shopify/liquid.png)](http://inch-ci.org/github/Shopify/liquid)
|
3
|
+
|
1
4
|
# Liquid template engine
|
2
5
|
|
3
6
|
* [Contributing guidelines](CONTRIBUTING.md)
|
4
7
|
* [Version history](History.md)
|
5
8
|
* [Liquid documentation from Shopify](http://docs.shopify.com/themes/liquid-basics)
|
6
|
-
* [Liquid Wiki
|
9
|
+
* [Liquid Wiki at GitHub](https://github.com/Shopify/liquid/wiki)
|
7
10
|
* [Website](http://liquidmarkup.org/)
|
8
11
|
|
9
12
|
## Introduction
|
@@ -47,4 +50,26 @@ For standard use you can just pass it the content of a file and call render with
|
|
47
50
|
@template.render('name' => 'tobi') # => "hi tobi"
|
48
51
|
```
|
49
52
|
|
50
|
-
|
53
|
+
### Error Modes
|
54
|
+
|
55
|
+
Setting the error mode of Liquid lets you specify how strictly you want your templates to be interpreted.
|
56
|
+
Normally the parser is very lax and will accept almost anything without error. Unfortunately this can make
|
57
|
+
it very hard to debug and can lead to unexpected behaviour.
|
58
|
+
|
59
|
+
Liquid also comes with a stricter parser that can be used when editing templates to give better error messages
|
60
|
+
when templates are invalid. You can enable this new parser like this:
|
61
|
+
|
62
|
+
```ruby
|
63
|
+
Liquid::Template.error_mode = :strict # Raises a SyntaxError when invalid syntax is used
|
64
|
+
Liquid::Template.error_mode = :warn # Adds errors to template.errors but continues as normal
|
65
|
+
Liquid::Template.error_mode = :lax # The default mode, accepts almost anything.
|
66
|
+
```
|
67
|
+
|
68
|
+
If you want to set the error mode only on specific templates you can pass `:error_mode` as an option to `parse`:
|
69
|
+
```ruby
|
70
|
+
Liquid::Template.parse(source, :error_mode => :strict)
|
71
|
+
```
|
72
|
+
This is useful for doing things like enabling strict mode only in the theme editor.
|
73
|
+
|
74
|
+
It is recommended that you enable `:strict` or `:warn` mode on new apps to stop invalid templates from being created.
|
75
|
+
It is also recommended that you use it in the template editors of existing apps to give editors better error messages.
|
data/lib/liquid.rb
CHANGED
@@ -21,9 +21,9 @@
|
|
21
21
|
|
22
22
|
module Liquid
|
23
23
|
FilterSeparator = /\|/
|
24
|
-
ArgumentSeparator = ','
|
25
|
-
FilterArgumentSeparator = ':'
|
26
|
-
VariableAttributeSeparator = '.'
|
24
|
+
ArgumentSeparator = ','.freeze
|
25
|
+
FilterArgumentSeparator = ':'.freeze
|
26
|
+
VariableAttributeSeparator = '.'.freeze
|
27
27
|
TagStart = /\{\%/
|
28
28
|
TagEnd = /\%\}/
|
29
29
|
VariableSignature = /\(?[\w\-\.\[\]]\)?/
|
@@ -33,19 +33,20 @@ module Liquid
|
|
33
33
|
VariableIncompleteEnd = /\}\}?/
|
34
34
|
QuotedString = /"[^"]*"|'[^']*'/
|
35
35
|
QuotedFragment = /#{QuotedString}|(?:[^\s,\|'"]|#{QuotedString})+/o
|
36
|
-
StrictQuotedFragment = /"[^"]+"|'[^']+'|[^\s|:,]+/
|
37
|
-
FirstFilterArgument = /#{FilterArgumentSeparator}(?:#{StrictQuotedFragment})/o
|
38
|
-
OtherFilterArgument = /#{ArgumentSeparator}(?:#{StrictQuotedFragment})/o
|
39
|
-
SpacelessFilter = /^(?:'[^']+'|"[^"]+"|[^'"])*#{FilterSeparator}(?:#{StrictQuotedFragment})(?:#{FirstFilterArgument}(?:#{OtherFilterArgument})*)?/o
|
40
|
-
Expression = /(?:#{QuotedFragment}(?:#{SpacelessFilter})*)/o
|
41
36
|
TagAttributes = /(\w+)\s*\:\s*(#{QuotedFragment})/o
|
42
37
|
AnyStartingTag = /\{\{|\{\%/
|
43
|
-
PartialTemplateParser = /#{TagStart}.*?#{TagEnd}|#{VariableStart}.*?#{VariableIncompleteEnd}/
|
44
|
-
TemplateParser = /(#{PartialTemplateParser}|#{AnyStartingTag})/
|
38
|
+
PartialTemplateParser = /#{TagStart}.*?#{TagEnd}|#{VariableStart}.*?#{VariableIncompleteEnd}/om
|
39
|
+
TemplateParser = /(#{PartialTemplateParser}|#{AnyStartingTag})/om
|
45
40
|
VariableParser = /\[[^\]]+\]|#{VariableSegment}+\??/o
|
41
|
+
|
42
|
+
singleton_class.send(:attr_accessor, :cache_classes)
|
43
|
+
self.cache_classes = true
|
46
44
|
end
|
47
45
|
|
48
46
|
require "liquid/version"
|
47
|
+
require 'liquid/lexer'
|
48
|
+
require 'liquid/parser'
|
49
|
+
require 'liquid/i18n'
|
49
50
|
require 'liquid/drop'
|
50
51
|
require 'liquid/extensions'
|
51
52
|
require 'liquid/errors'
|
@@ -58,7 +59,6 @@ require 'liquid/document'
|
|
58
59
|
require 'liquid/variable'
|
59
60
|
require 'liquid/file_system'
|
60
61
|
require 'liquid/template'
|
61
|
-
require 'liquid/htmltags'
|
62
62
|
require 'liquid/standardfilters'
|
63
63
|
require 'liquid/condition'
|
64
64
|
require 'liquid/module_ex'
|
data/lib/liquid/block.rb
CHANGED
@@ -1,45 +1,58 @@
|
|
1
1
|
module Liquid
|
2
|
-
|
3
2
|
class Block < Tag
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
3
|
+
FullToken = /\A#{TagStart}\s*(\w+)\s*(.*)?#{TagEnd}\z/om
|
4
|
+
ContentOfVariable = /\A#{VariableStart}(.*)#{VariableEnd}\z/om
|
5
|
+
TAGSTART = "{%".freeze
|
6
|
+
VARSTART = "{{".freeze
|
7
|
+
|
8
|
+
def blank?
|
9
|
+
@blank
|
10
|
+
end
|
8
11
|
|
9
12
|
def parse(tokens)
|
13
|
+
@blank = true
|
10
14
|
@nodelist ||= []
|
11
15
|
@nodelist.clear
|
12
16
|
|
13
|
-
|
14
|
-
|
15
|
-
case token
|
16
|
-
when IsTag
|
17
|
-
if token =~ FullToken
|
17
|
+
# All child tags of the current block.
|
18
|
+
@children = []
|
18
19
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
20
|
+
while token = tokens.shift
|
21
|
+
unless token.empty?
|
22
|
+
case
|
23
|
+
when token.start_with?(TAGSTART)
|
24
|
+
if token =~ FullToken
|
25
|
+
|
26
|
+
# if we found the proper block delimiter just end parsing here and let the outer block
|
27
|
+
# proceed
|
28
|
+
if block_delimiter == $1
|
29
|
+
end_tag
|
30
|
+
return
|
31
|
+
end
|
32
|
+
|
33
|
+
# fetch the tag from registered blocks
|
34
|
+
if tag = Template.tags[$1]
|
35
|
+
new_tag = tag.parse($1, $2, tokens, @options)
|
36
|
+
@blank &&= new_tag.blank?
|
37
|
+
@nodelist << new_tag
|
38
|
+
@children << new_tag
|
39
|
+
else
|
40
|
+
# this tag is not registered with the system
|
41
|
+
# pass it to the current block for special handling or error reporting
|
42
|
+
unknown_tag($1, $2, tokens)
|
43
|
+
end
|
29
44
|
else
|
30
|
-
|
31
|
-
# pass it to the current block for special handling or error reporting
|
32
|
-
unknown_tag($1, $2, tokens)
|
45
|
+
raise SyntaxError.new(options[:locale].t("errors.syntax.tag_termination".freeze, :token => token, :tag_end => TagEnd.inspect))
|
33
46
|
end
|
47
|
+
when token.start_with?(VARSTART)
|
48
|
+
new_var = create_variable(token)
|
49
|
+
@nodelist << new_var
|
50
|
+
@children << new_var
|
51
|
+
@blank = false
|
34
52
|
else
|
35
|
-
|
53
|
+
@nodelist << token
|
54
|
+
@blank &&= (token =~ /\A\s*\z/)
|
36
55
|
end
|
37
|
-
when IsVariable
|
38
|
-
@nodelist << create_variable(token)
|
39
|
-
when ''
|
40
|
-
# pass
|
41
|
-
else
|
42
|
-
@nodelist << token
|
43
56
|
end
|
44
57
|
end
|
45
58
|
|
@@ -49,33 +62,48 @@ module Liquid
|
|
49
62
|
assert_missing_delimitation!
|
50
63
|
end
|
51
64
|
|
65
|
+
# warnings of this block and all sub-tags
|
66
|
+
def warnings
|
67
|
+
all_warnings = []
|
68
|
+
all_warnings.concat(@warnings) if @warnings
|
69
|
+
|
70
|
+
(@children || []).each do |node|
|
71
|
+
all_warnings.concat(node.warnings || [])
|
72
|
+
end
|
73
|
+
|
74
|
+
all_warnings
|
75
|
+
end
|
76
|
+
|
52
77
|
def end_tag
|
53
78
|
end
|
54
79
|
|
55
80
|
def unknown_tag(tag, params, tokens)
|
56
81
|
case tag
|
57
|
-
when 'else'
|
58
|
-
raise SyntaxError
|
59
|
-
|
60
|
-
|
82
|
+
when 'else'.freeze
|
83
|
+
raise SyntaxError.new(options[:locale].t("errors.syntax.unexpected_else".freeze,
|
84
|
+
:block_name => block_name))
|
85
|
+
when 'end'.freeze
|
86
|
+
raise SyntaxError.new(options[:locale].t("errors.syntax.invalid_delimiter".freeze,
|
87
|
+
:block_name => block_name,
|
88
|
+
:block_delimiter => block_delimiter))
|
61
89
|
else
|
62
|
-
raise SyntaxError,
|
90
|
+
raise SyntaxError.new(options[:locale].t("errors.syntax.unknown_tag".freeze, :tag => tag))
|
63
91
|
end
|
64
92
|
end
|
65
93
|
|
66
|
-
def block_delimiter
|
67
|
-
"end#{block_name}"
|
68
|
-
end
|
69
|
-
|
70
94
|
def block_name
|
71
95
|
@tag_name
|
72
96
|
end
|
73
97
|
|
98
|
+
def block_delimiter
|
99
|
+
@block_delimiter ||= "end#{block_name}"
|
100
|
+
end
|
101
|
+
|
74
102
|
def create_variable(token)
|
75
103
|
token.scan(ContentOfVariable) do |content|
|
76
|
-
return Variable.new(content.first)
|
104
|
+
return Variable.new(content.first, @options)
|
77
105
|
end
|
78
|
-
raise SyntaxError.new("
|
106
|
+
raise SyntaxError.new(options[:locale].t("errors.syntax.variable_termination".freeze, :token => token, :tag_end => VariableEnd.inspect))
|
79
107
|
end
|
80
108
|
|
81
109
|
def render(context)
|
@@ -85,7 +113,7 @@ module Liquid
|
|
85
113
|
protected
|
86
114
|
|
87
115
|
def assert_missing_delimitation!
|
88
|
-
raise SyntaxError.new("
|
116
|
+
raise SyntaxError.new(options[:locale].t("errors.syntax.tag_never_closed".freeze, :block_name => block_name))
|
89
117
|
end
|
90
118
|
|
91
119
|
def render_all(list, context)
|
@@ -107,12 +135,14 @@ module Liquid
|
|
107
135
|
end
|
108
136
|
|
109
137
|
token_output = (token.respond_to?(:render) ? token.render(context) : token)
|
110
|
-
context.
|
138
|
+
context.increment_used_resources(:render_length_current, token_output)
|
111
139
|
if context.resource_limits_reached?
|
112
140
|
context.resource_limits[:reached] = true
|
113
|
-
raise MemoryError.new("Memory limits exceeded")
|
141
|
+
raise MemoryError.new("Memory limits exceeded".freeze)
|
142
|
+
end
|
143
|
+
unless token.is_a?(Block) && token.blank?
|
144
|
+
output << token_output
|
114
145
|
end
|
115
|
-
output << token_output
|
116
146
|
rescue MemoryError => e
|
117
147
|
raise e
|
118
148
|
rescue ::StandardError => e
|
data/lib/liquid/condition.rb
CHANGED
@@ -8,14 +8,14 @@ module Liquid
|
|
8
8
|
#
|
9
9
|
class Condition #:nodoc:
|
10
10
|
@@operators = {
|
11
|
-
'==' => lambda { |cond, left, right| cond.send(:equal_variables, left, right) },
|
12
|
-
'!=' => lambda { |cond, left, right| !cond.send(:equal_variables, left, right) },
|
13
|
-
'<>' => lambda { |cond, left, right| !cond.send(:equal_variables, left, right) },
|
14
|
-
'<' => :<,
|
15
|
-
'>' => :>,
|
16
|
-
'>=' => :>=,
|
17
|
-
'<=' => :<=,
|
18
|
-
'contains' => lambda { |cond, left, right| left && right ? left.include?(right) : false }
|
11
|
+
'=='.freeze => lambda { |cond, left, right| cond.send(:equal_variables, left, right) },
|
12
|
+
'!='.freeze => lambda { |cond, left, right| !cond.send(:equal_variables, left, right) },
|
13
|
+
'<>'.freeze => lambda { |cond, left, right| !cond.send(:equal_variables, left, right) },
|
14
|
+
'<'.freeze => :<,
|
15
|
+
'>'.freeze => :>,
|
16
|
+
'>='.freeze => :>=,
|
17
|
+
'<='.freeze => :<=,
|
18
|
+
'contains'.freeze => lambda { |cond, left, right| left && right ? left.include?(right) : false }
|
19
19
|
}
|
20
20
|
|
21
21
|
def self.operators
|
@@ -61,7 +61,7 @@ module Liquid
|
|
61
61
|
end
|
62
62
|
|
63
63
|
def inspect
|
64
|
-
"#<Condition #{[@left, @operator, @right].compact.join(' ')}>"
|
64
|
+
"#<Condition #{[@left, @operator, @right].compact.join(' '.freeze)}>"
|
65
65
|
end
|
66
66
|
|
67
67
|
private
|
@@ -94,12 +94,16 @@ module Liquid
|
|
94
94
|
|
95
95
|
left, right = context[left], context[right]
|
96
96
|
|
97
|
-
operation = self.class.operators[op] || raise(ArgumentError.new("Unknown operator #{op}"))
|
97
|
+
operation = self.class.operators[op] || raise(Liquid::ArgumentError.new("Unknown operator #{op}"))
|
98
98
|
|
99
99
|
if operation.respond_to?(:call)
|
100
100
|
operation.call(self, left, right)
|
101
101
|
elsif left.respond_to?(operation) and right.respond_to?(operation)
|
102
|
-
|
102
|
+
begin
|
103
|
+
left.send(operation, right)
|
104
|
+
rescue ::ArgumentError => e
|
105
|
+
raise Liquid::ArgumentError.new(e.message)
|
106
|
+
end
|
103
107
|
else
|
104
108
|
nil
|
105
109
|
end
|
data/lib/liquid/context.rb
CHANGED
@@ -14,17 +14,33 @@ module Liquid
|
|
14
14
|
# context['bob'] #=> nil class Context
|
15
15
|
class Context
|
16
16
|
attr_reader :scopes, :errors, :registers, :environments, :resource_limits
|
17
|
+
attr_accessor :exception_handler
|
18
|
+
|
19
|
+
SQUARE_BRACKETED = /\A\[(.*)\]\z/m
|
17
20
|
|
18
21
|
def initialize(environments = {}, outer_scope = {}, registers = {}, rethrow_errors = false, resource_limits = {})
|
19
22
|
@environments = [environments].flatten
|
20
23
|
@scopes = [(outer_scope || {})]
|
21
24
|
@registers = registers
|
22
25
|
@errors = []
|
23
|
-
@rethrow_errors = rethrow_errors
|
24
26
|
@resource_limits = (resource_limits || {}).merge!({ :render_score_current => 0, :assign_score_current => 0 })
|
25
27
|
squash_instance_assigns_with_environments
|
26
28
|
|
29
|
+
if rethrow_errors
|
30
|
+
self.exception_handler = ->(e) { true }
|
31
|
+
end
|
32
|
+
|
27
33
|
@interrupts = []
|
34
|
+
@filters = []
|
35
|
+
@parsed_variables = Hash.new{ |cache, markup| cache[markup] = variable_parse(markup) }
|
36
|
+
end
|
37
|
+
|
38
|
+
def increment_used_resources(key, obj)
|
39
|
+
@resource_limits[key] += if obj.kind_of?(String) || obj.kind_of?(Array) || obj.kind_of?(Hash)
|
40
|
+
obj.length
|
41
|
+
else
|
42
|
+
1
|
43
|
+
end
|
28
44
|
end
|
29
45
|
|
30
46
|
def resource_limits_reached?
|
@@ -34,7 +50,7 @@ module Liquid
|
|
34
50
|
end
|
35
51
|
|
36
52
|
def strainer
|
37
|
-
@strainer ||= Strainer.create(self)
|
53
|
+
@strainer ||= Strainer.create(self, @filters)
|
38
54
|
end
|
39
55
|
|
40
56
|
# Adds filters to this context.
|
@@ -43,17 +59,26 @@ module Liquid
|
|
43
59
|
# for that
|
44
60
|
def add_filters(filters)
|
45
61
|
filters = [filters].flatten.compact
|
46
|
-
|
47
62
|
filters.each do |f|
|
48
63
|
raise ArgumentError, "Expected module but got: #{f.class}" unless f.is_a?(Module)
|
49
64
|
Strainer.add_known_filter(f)
|
50
|
-
|
65
|
+
end
|
66
|
+
|
67
|
+
# If strainer is already setup then there's no choice but to use a runtime
|
68
|
+
# extend call. If strainer is not yet created, we can utilize strainers
|
69
|
+
# cached class based API, which avoids busting the method cache.
|
70
|
+
if @strainer
|
71
|
+
filters.each do |f|
|
72
|
+
strainer.extend(f)
|
73
|
+
end
|
74
|
+
else
|
75
|
+
@filters.concat filters
|
51
76
|
end
|
52
77
|
end
|
53
78
|
|
54
79
|
# are there any not handled interrupts?
|
55
80
|
def has_interrupt?
|
56
|
-
|
81
|
+
!@interrupts.empty?
|
57
82
|
end
|
58
83
|
|
59
84
|
# push an interrupt to the stack. this interrupt is considered not handled.
|
@@ -68,7 +93,8 @@ module Liquid
|
|
68
93
|
|
69
94
|
def handle_error(e)
|
70
95
|
errors.push(e)
|
71
|
-
|
96
|
+
|
97
|
+
raise if exception_handler && exception_handler.call(e)
|
72
98
|
|
73
99
|
case e
|
74
100
|
when SyntaxError
|
@@ -85,7 +111,7 @@ module Liquid
|
|
85
111
|
# Push new local scope on the stack. use <tt>Context#stack</tt> instead
|
86
112
|
def push(new_scope={})
|
87
113
|
@scopes.unshift(new_scope)
|
88
|
-
raise StackLevelError, "Nesting too deep" if @scopes.length > 100
|
114
|
+
raise StackLevelError, "Nesting too deep".freeze if @scopes.length > 100
|
89
115
|
end
|
90
116
|
|
91
117
|
# Merge a hash of variables in the current local scope
|
@@ -133,11 +159,11 @@ module Liquid
|
|
133
159
|
|
134
160
|
private
|
135
161
|
LITERALS = {
|
136
|
-
nil => nil, 'nil' => nil, 'null' => nil, '' => nil,
|
137
|
-
'true' => true,
|
138
|
-
'false' => false,
|
139
|
-
'blank' => :blank?,
|
140
|
-
'empty' => :empty?
|
162
|
+
nil => nil, 'nil'.freeze => nil, 'null'.freeze => nil, ''.freeze => nil,
|
163
|
+
'true'.freeze => true,
|
164
|
+
'false'.freeze => false,
|
165
|
+
'blank'.freeze => :blank?,
|
166
|
+
'empty'.freeze => :empty?
|
141
167
|
}
|
142
168
|
|
143
169
|
# Look up variable, either resolve directly after considering the name. We can directly handle
|
@@ -153,15 +179,15 @@ module Liquid
|
|
153
179
|
LITERALS[key]
|
154
180
|
else
|
155
181
|
case key
|
156
|
-
when
|
182
|
+
when /\A'(.*)'\z/m # Single quoted strings
|
157
183
|
$1
|
158
|
-
when
|
184
|
+
when /\A"(.*)"\z/m # Double quoted strings
|
159
185
|
$1
|
160
|
-
when
|
186
|
+
when /\A(-?\d+)\z/ # Integer and floats
|
161
187
|
$1.to_i
|
162
|
-
when
|
188
|
+
when /\A\((\S+)\.\.(\S+)\)\z/ # Ranges
|
163
189
|
(resolve($1).to_i..resolve($2).to_i)
|
164
|
-
when
|
190
|
+
when /\A(-?\d[\d\.]+)\z/ # Floats
|
165
191
|
$1.to_f
|
166
192
|
else
|
167
193
|
variable(key)
|
@@ -171,12 +197,18 @@ module Liquid
|
|
171
197
|
|
172
198
|
# Fetches an object starting at the local scope and then moving up the hierachy
|
173
199
|
def find_variable(key)
|
174
|
-
|
200
|
+
|
201
|
+
# This was changed from find() to find_index() because this is a very hot
|
202
|
+
# path and find_index() is optimized in MRI to reduce object allocation
|
203
|
+
index = @scopes.find_index { |s| s.has_key?(key) }
|
204
|
+
scope = @scopes[index] if index
|
205
|
+
|
175
206
|
variable = nil
|
176
207
|
|
177
208
|
if scope.nil?
|
178
209
|
@environments.each do |e|
|
179
|
-
|
210
|
+
variable = lookup_and_evaluate(e, key)
|
211
|
+
unless variable.nil?
|
180
212
|
scope = e
|
181
213
|
break
|
182
214
|
end
|
@@ -192,6 +224,16 @@ module Liquid
|
|
192
224
|
return variable
|
193
225
|
end
|
194
226
|
|
227
|
+
def variable_parse(markup)
|
228
|
+
parts = markup.scan(VariableParser)
|
229
|
+
needs_resolution = false
|
230
|
+
if parts.first =~ SQUARE_BRACKETED
|
231
|
+
needs_resolution = true
|
232
|
+
parts[0] = $1
|
233
|
+
end
|
234
|
+
{:first => parts.shift, :needs_resolution => needs_resolution, :rest => parts}
|
235
|
+
end
|
236
|
+
|
195
237
|
# Resolves namespaced queries gracefully.
|
196
238
|
#
|
197
239
|
# Example
|
@@ -199,19 +241,17 @@ module Liquid
|
|
199
241
|
# assert_equal 'tobi', @context['hash.name']
|
200
242
|
# assert_equal 'tobi', @context['hash["name"]']
|
201
243
|
def variable(markup)
|
202
|
-
parts = markup
|
203
|
-
square_bracketed = /^\[(.*)\]$/
|
244
|
+
parts = @parsed_variables[markup]
|
204
245
|
|
205
|
-
first_part = parts
|
206
|
-
|
207
|
-
|
208
|
-
first_part = resolve($1)
|
246
|
+
first_part = parts[:first]
|
247
|
+
if parts[:needs_resolution]
|
248
|
+
first_part = resolve(parts[:first])
|
209
249
|
end
|
210
250
|
|
211
251
|
if object = find_variable(first_part)
|
212
252
|
|
213
|
-
parts.each do |part|
|
214
|
-
part = resolve($1) if part_resolved = (part =~
|
253
|
+
parts[:rest].each do |part|
|
254
|
+
part = resolve($1) if part_resolved = (part =~ SQUARE_BRACKETED)
|
215
255
|
|
216
256
|
# If object is a hash- or array-like object we look for the
|
217
257
|
# presence of the key and if its available we return it
|
@@ -226,7 +266,7 @@ module Liquid
|
|
226
266
|
# Some special cases. If the part wasn't in square brackets and
|
227
267
|
# no key with the same name was found we interpret following calls
|
228
268
|
# as commands and call them on the current object
|
229
|
-
elsif !part_resolved and object.respond_to?(part) and ['size', 'first', 'last'].include?(part)
|
269
|
+
elsif !part_resolved and object.respond_to?(part) and ['size'.freeze, 'first'.freeze, 'last'.freeze].include?(part)
|
230
270
|
|
231
271
|
object = object.send(part.intern).to_liquid
|
232
272
|
|
@@ -263,5 +303,4 @@ module Liquid
|
|
263
303
|
end
|
264
304
|
end # squash_instance_assigns_with_environments
|
265
305
|
end # Context
|
266
|
-
|
267
306
|
end # Liquid
|