liquid 2.6.3 → 3.0.0.rc1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|
+
[](http://travis-ci.org/Shopify/liquid)
|
2
|
+
[](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
|