locomotivecms-liquid 2.6.0 → 4.0.0.alpha

Sign up to get free protection for your applications and to get access to all the features.
Files changed (103) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +62 -5
  3. data/README.md +4 -4
  4. data/lib/liquid.rb +16 -12
  5. data/lib/liquid/block.rb +37 -118
  6. data/lib/liquid/block_body.rb +131 -0
  7. data/lib/liquid/condition.rb +28 -17
  8. data/lib/liquid/context.rb +94 -146
  9. data/lib/liquid/document.rb +16 -10
  10. data/lib/liquid/drop.rb +8 -5
  11. data/lib/liquid/drops/inherited_block_drop.rb +24 -0
  12. data/lib/liquid/errors.rb +44 -5
  13. data/lib/liquid/expression.rb +33 -0
  14. data/lib/liquid/file_system.rb +17 -6
  15. data/lib/liquid/i18n.rb +2 -2
  16. data/lib/liquid/interrupts.rb +1 -1
  17. data/lib/liquid/lexer.rb +11 -9
  18. data/lib/liquid/locales/en.yml +2 -4
  19. data/lib/liquid/parser.rb +2 -1
  20. data/lib/liquid/parser_switching.rb +31 -0
  21. data/lib/liquid/profiler.rb +162 -0
  22. data/lib/liquid/profiler/hooks.rb +23 -0
  23. data/lib/liquid/range_lookup.rb +22 -0
  24. data/lib/liquid/resource_limits.rb +23 -0
  25. data/lib/liquid/standardfilters.rb +142 -67
  26. data/lib/liquid/strainer.rb +14 -4
  27. data/lib/liquid/tag.rb +22 -41
  28. data/lib/liquid/tags/assign.rb +15 -10
  29. data/lib/liquid/tags/break.rb +1 -1
  30. data/lib/liquid/tags/capture.rb +7 -9
  31. data/lib/liquid/tags/case.rb +28 -19
  32. data/lib/liquid/tags/comment.rb +2 -2
  33. data/lib/liquid/tags/continue.rb +1 -4
  34. data/lib/liquid/tags/cycle.rb +10 -14
  35. data/lib/liquid/tags/decrement.rb +3 -4
  36. data/lib/liquid/tags/extends.rb +28 -44
  37. data/lib/liquid/tags/for.rb +64 -42
  38. data/lib/liquid/tags/if.rb +30 -19
  39. data/lib/liquid/tags/ifchanged.rb +4 -4
  40. data/lib/liquid/tags/include.rb +30 -20
  41. data/lib/liquid/tags/increment.rb +3 -8
  42. data/lib/liquid/tags/inherited_block.rb +54 -56
  43. data/lib/liquid/tags/raw.rb +18 -10
  44. data/lib/liquid/tags/table_row.rb +72 -0
  45. data/lib/liquid/tags/unless.rb +5 -7
  46. data/lib/liquid/template.rb +113 -53
  47. data/lib/liquid/token.rb +18 -0
  48. data/lib/liquid/utils.rb +13 -4
  49. data/lib/liquid/variable.rb +68 -50
  50. data/lib/liquid/variable_lookup.rb +78 -0
  51. data/lib/liquid/version.rb +1 -1
  52. data/test/fixtures/en_locale.yml +9 -0
  53. data/test/integration/assign_test.rb +48 -0
  54. data/test/integration/blank_test.rb +106 -0
  55. data/test/integration/capture_test.rb +50 -0
  56. data/test/integration/context_test.rb +32 -0
  57. data/test/integration/document_test.rb +19 -0
  58. data/test/integration/drop_test.rb +271 -0
  59. data/test/integration/error_handling_test.rb +207 -0
  60. data/test/integration/filter_test.rb +125 -0
  61. data/test/integration/hash_ordering_test.rb +23 -0
  62. data/test/integration/output_test.rb +116 -0
  63. data/test/integration/parsing_quirks_test.rb +119 -0
  64. data/test/integration/render_profiling_test.rb +154 -0
  65. data/test/integration/security_test.rb +64 -0
  66. data/test/integration/standard_filter_test.rb +379 -0
  67. data/test/integration/tags/break_tag_test.rb +16 -0
  68. data/test/integration/tags/continue_tag_test.rb +16 -0
  69. data/test/integration/tags/extends_tag_test.rb +104 -0
  70. data/test/integration/tags/for_tag_test.rb +375 -0
  71. data/test/integration/tags/if_else_tag_test.rb +169 -0
  72. data/test/integration/tags/include_tag_test.rb +234 -0
  73. data/test/integration/tags/increment_tag_test.rb +24 -0
  74. data/test/integration/tags/raw_tag_test.rb +25 -0
  75. data/test/integration/tags/standard_tag_test.rb +297 -0
  76. data/test/integration/tags/statements_test.rb +113 -0
  77. data/test/integration/tags/table_row_test.rb +63 -0
  78. data/test/integration/tags/unless_else_tag_test.rb +26 -0
  79. data/test/integration/template_test.rb +216 -0
  80. data/test/integration/variable_test.rb +82 -0
  81. data/test/test_helper.rb +83 -0
  82. data/test/unit/block_unit_test.rb +55 -0
  83. data/test/unit/condition_unit_test.rb +149 -0
  84. data/test/unit/context_unit_test.rb +482 -0
  85. data/test/unit/file_system_unit_test.rb +35 -0
  86. data/test/unit/i18n_unit_test.rb +37 -0
  87. data/test/unit/lexer_unit_test.rb +51 -0
  88. data/test/unit/module_ex_unit_test.rb +87 -0
  89. data/test/unit/parser_unit_test.rb +82 -0
  90. data/test/unit/regexp_unit_test.rb +44 -0
  91. data/test/unit/strainer_unit_test.rb +71 -0
  92. data/test/unit/tag_unit_test.rb +16 -0
  93. data/test/unit/tags/case_tag_unit_test.rb +10 -0
  94. data/test/unit/tags/for_tag_unit_test.rb +13 -0
  95. data/test/unit/tags/if_tag_unit_test.rb +8 -0
  96. data/test/unit/template_unit_test.rb +70 -0
  97. data/test/unit/tokenizer_unit_test.rb +38 -0
  98. data/test/unit/variable_unit_test.rb +150 -0
  99. metadata +144 -15
  100. data/lib/extras/liquid_view.rb +0 -51
  101. data/lib/liquid/htmltags.rb +0 -74
  102. data/lib/liquid/tags/default_content.rb +0 -21
  103. data/lib/locomotivecms-liquid.rb +0 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 0dd3865307a040e127b4a088bb3ccda9af424c66
4
- data.tar.gz: bf77f86b27a172af5476e7d00b329a647a9bc96a
3
+ metadata.gz: 390ba2f8603e9d8dd6a70219c727e7912f5a0af6
4
+ data.tar.gz: 3dab20ea5da6160a906c8946b6b8d6e3f96554de
5
5
  SHA512:
6
- metadata.gz: a6de109bf59a18bd39b7f357636a22aa62be7286a23b87e1aa1bc7e8b5f8981d52ca397656147c16dd62c87bb872c448f2a8bc9c5d5b8158d703e7cb2426628f
7
- data.tar.gz: 406c6e6858b0623afe3a5b4eda92fc0b552c4516aa920ec5b16bc3261913223d87a51216fa88cd94c76dfab1573758fe27857ce6ebabbefc6ab5157fbb116c3f
6
+ metadata.gz: d64a2bcf781c84c400d6b17d56e047bfc3c4347d67a86bcc5f15ffdbea84b6f0665acaf18b1009ea5aaad6bad71c724f397a329ee8f04bc7d3d48c3dcce2cfad
7
+ data.tar.gz: 3851c097575f9c31845cb8b396fd795a3a10b8037377b19463516225415a259b7c16d4bc970bbc28e4bf2b6c3e7f47d16b2b8f1bd42a360ec0a73d4781f19d88
data/History.md CHANGED
@@ -1,18 +1,53 @@
1
1
  # Liquid Version History
2
2
 
3
- IMPORTANT: Liquid 2.6 is going to be the last version of Liquid which maintains explicit Ruby 1.8 compatability.
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.0 / Master branch (not yet released)
3
+ ## 3.0.0 / not yet released / branch "master"
7
4
 
8
5
  * ...
6
+ * Block parsing moved to BlockBody class, see #458 [Dylan Thacker-Smith, dylanahsmith]
7
+ * Removed Block#end_tag. Instead, override parse with `super` followed by your code. See #446 [Dylan Thacker-Smith, dylanahsmith]
8
+ * Fixed condition with wrong data types, see #423 [Bogdan Gusiev]
9
+ * Add url_encode to standard filters, see #421 [Derrick Reimer, djreimer]
10
+ * Add uniq to standard filters [Florian Weingarten, fw42]
11
+ * Add exception_handler feature, see #397 and #254 [Bogdan Gusiev, bogdan and Florian Weingarten, fw42]
12
+ * Optimize variable parsing to avoid repeated regex evaluation during template rendering #383 [Jason Hiltz-Laforge, jasonhl]
13
+ * Optimize checking for block interrupts to reduce object allocation #380 [Jason Hiltz-Laforge, jasonhl]
14
+ * Properly set context rethrow_errors on render! #349 [Thierry Joyal, tjoyal]
15
+ * Fix broken rendering of variables which are equal to false, see #345 [Florian Weingarten, fw42]
16
+ * Remove ActionView template handler [Dylan Thacker-Smith, dylanahsmith]
17
+ * Freeze lots of string literals for new Ruby 2.1 optimization, see #297 [Florian Weingarten, fw42]
18
+ * Allow newlines in tags and variables, see #324 [Dylan Thacker-Smith, dylanahsmith]
19
+ * Tag#parse is called after initialize, which now takes options instead of tokens as the 3rd argument. See #321 [Dylan Thacker-Smith, dylanahsmith]
20
+ * Raise `Liquid::ArgumentError` instead of `::ArgumentError` when filter has wrong number of arguments #309 [Bogdan Gusiev, bogdan]
21
+ * Add a to_s default for liquid drops, see #306 [Adam Doeler, releod]
22
+ * Add strip, lstrip, and rstrip to standard filters [Florian Weingarten, fw42]
23
+ * Make if, for & case tags return complete and consistent nodelists, see #250 [Nick Jones, dntj]
24
+ * Prevent arbitrary method invocation on condition objects, see #274 [Dylan Thacker-Smith, dylanahsmith]
25
+ * Don't call to_sym when creating conditions for security reasons, see #273 [Bouke van der Bijl, bouk]
26
+ * Fix resource counting bug with respond_to?(:length), see #263 [Florian Weingarten, fw42]
27
+ * Allow specifying custom patterns for template filenames, see #284 [Andrei Gladkyi, agladkyi]
28
+ * Allow drops to optimize loading a slice of elements, see #282 [Tom Burns, boourns]
29
+ * Support for passing variables to snippets in subdirs, see #271 [Joost Hietbrink, joost]
30
+ * Add a class cache to avoid runtime extend calls, see #249 [James Tucker, raggi]
31
+ * Remove some legacy Ruby 1.8 compatibility code, see #276 [Florian Weingarten, fw42]
32
+ * Add default filter to standard filters, see #267 [Derrick Reimer, djreimer]
9
33
  * Add optional strict parsing and warn parsing, see #235 [Tristan Hume, trishume]
10
34
  * Add I18n syntax error translation, see #241 [Simon Hørup Eskildsen, Sirupsen]
11
35
  * Make sort filter work on enumerable drops, see #239 [Florian Weingarten, fw42]
12
36
  * Fix clashing method names in enumerable drops, see #238 [Florian Weingarten, fw42]
13
37
  * Make map filter work on enumerable drops, see #233 [Florian Weingarten, fw42]
14
- * Fix security issue with map filter, see #230, #232, #234, #237 [Florian Weingarten, fw42]
15
38
  * Improved whitespace stripping for blank blocks, related to #216 [Florian Weingarten, fw42]
39
+
40
+ ## 2.6.1 / 2014-01-10 / branch "2-6-stable"
41
+
42
+ Security fix, cherry-picked from master (4e14a65):
43
+ * Don't call to_sym when creating conditions for security reasons, see #273 [Bouke van der Bijl, bouk]
44
+ * Prevent arbitrary method invocation on condition objects, see #274 [Dylan Thacker-Smith, dylanahsmith]
45
+
46
+ ## 2.6.0 / 2013-11-25
47
+
48
+ IMPORTANT: Liquid 2.6 is going to be the last version of Liquid which maintains explicit Ruby 1.8 compatability.
49
+ The following releases will only be tested against Ruby 1.9 and Ruby 2.0 and are likely to break on Ruby 1.8.
50
+
16
51
  * Bugfix for #106: fix example servlet [gnowoel]
17
52
  * Bugfix for #97: strip_html filter supports multi-line tags [Jo Liss, joliss]
18
53
  * Bugfix for #114: strip_html filter supports style tags [James Allardice, jamesallardice]
@@ -21,6 +56,7 @@ The following releases will only be tested against Ruby 1.9 and Ruby 2.0 and are
21
56
  * Bugfix for #204: 'raw' parsing bug [Florian Weingarten, fw42]
22
57
  * Bugfix for #150: 'for' parsing bug [Peter Schröder, phoet]
23
58
  * Bugfix for #126: Strip CRLF in strip_newline [Peter Schröder, phoet]
59
+ * Bugfix for #174, "can't convert Fixnum into String" for "replace" [wǒ_is神仙, jsw0528]
24
60
  * Allow a Liquid::Drop to be passed into Template#render [Daniel Huckstep, darkhelmet]
25
61
  * Resource limits [Florian Weingarten, fw42]
26
62
  * Add reverse filter [Jay Strybis, unreal]
@@ -31,6 +67,27 @@ The following releases will only be tested against Ruby 1.9 and Ruby 2.0 and are
31
67
  * Better documentation for 'include' tag (closes #163) [Peter Schröder, phoet]
32
68
  * Use of BigDecimal on filters to have better precision (closes #155) [Arthur Nogueira Neves, arthurnn]
33
69
 
70
+ ## 2.5.5 / 2014-01-10 / branch "2-5-stable"
71
+
72
+ Security fix, cherry-picked from master (4e14a65):
73
+ * Don't call to_sym when creating conditions for security reasons, see #273 [Bouke van der Bijl, bouk]
74
+ * Prevent arbitrary method invocation on condition objects, see #274 [Dylan Thacker-Smith, dylanahsmith]
75
+
76
+ ## 2.5.4 / 2013-11-11
77
+
78
+ * Fix "can't convert Fixnum into String" for "replace", see #173, [wǒ_is神仙, jsw0528]
79
+
80
+ ## 2.5.3 / 2013-10-09
81
+
82
+ * #232, #234, #237: Fix map filter bugs [Florian Weingarten, fw42]
83
+
84
+ ## 2.5.2 / 2013-09-03 / deleted
85
+
86
+ Yanked from rubygems, as it contained too many changes that broke compatibility. Those changes will be on following major releases.
87
+
88
+ ## 2.5.1 / 2013-07-24
89
+
90
+ * #230: Fix security issue with map filter, Use invoke_drop in map filter [Florian Weingarten, fw42]
34
91
 
35
92
  ## 2.5.0 / 2013-03-06
36
93
 
data/README.md CHANGED
@@ -1,9 +1,11 @@
1
+ [![Build Status](https://api.travis-ci.org/Shopify/liquid.svg?branch=master)](http://travis-ci.org/Shopify/liquid)
2
+ [![Inline docs](http://inch-ci.org/github/Shopify/liquid.svg?branch=master)](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 from Shopify](http://wiki.shopify.com/Liquid)
7
9
  * [Liquid Wiki at GitHub](https://github.com/Shopify/liquid/wiki)
8
10
  * [Website](http://liquidmarkup.org/)
9
11
 
@@ -52,7 +54,7 @@ For standard use you can just pass it the content of a file and call render with
52
54
 
53
55
  Setting the error mode of Liquid lets you specify how strictly you want your templates to be interpreted.
54
56
  Normally the parser is very lax and will accept almost anything without error. Unfortunately this can make
55
- it very hard to debug and can lead to unexpected behaviour.
57
+ it very hard to debug and can lead to unexpected behaviour.
56
58
 
57
59
  Liquid also comes with a stricter parser that can be used when editing templates to give better error messages
58
60
  when templates are invalid. You can enable this new parser like this:
@@ -71,5 +73,3 @@ This is useful for doing things like enabling strict mode only in the theme edit
71
73
 
72
74
  It is recommended that you enable `:strict` or `:warn` mode on new apps to stop invalid templates from being created.
73
75
  It is also recommended that you use it in the template editors of existing apps to give editors better error messages.
74
-
75
- [![Build Status](https://secure.travis-ci.org/Shopify/liquid.png)](http://travis-ci.org/Shopify/liquid)
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,16 +33,14 @@ 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}/o
44
- TemplateParser = /(#{PartialTemplateParser}|#{AnyStartingTag})/o
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"
@@ -54,19 +52,25 @@ require 'liquid/extensions'
54
52
  require 'liquid/errors'
55
53
  require 'liquid/interrupts'
56
54
  require 'liquid/strainer'
55
+ require 'liquid/expression'
57
56
  require 'liquid/context'
57
+ require 'liquid/parser_switching'
58
58
  require 'liquid/tag'
59
59
  require 'liquid/block'
60
+ require 'liquid/block_body'
60
61
  require 'liquid/document'
61
62
  require 'liquid/variable'
63
+ require 'liquid/variable_lookup'
64
+ require 'liquid/range_lookup'
62
65
  require 'liquid/file_system'
66
+ require 'liquid/resource_limits'
63
67
  require 'liquid/template'
64
- require 'liquid/htmltags'
65
68
  require 'liquid/standardfilters'
66
69
  require 'liquid/condition'
67
70
  require 'liquid/module_ex'
68
71
  require 'liquid/utils'
72
+ require 'liquid/token'
69
73
 
70
74
  # Load all the tags of the standard library
71
75
  #
72
- Dir[File.dirname(__FILE__) + '/liquid/tags/*.rb'].each { |f| require f }
76
+ Dir[File.dirname(__FILE__) + '/liquid/{tags,drops}/*.rb'].each { |f| require f }
data/lib/liquid/block.rb CHANGED
@@ -1,69 +1,26 @@
1
1
  module Liquid
2
2
  class Block < Tag
3
- IsTag = /^#{TagStart}/o
4
- IsVariable = /^#{VariableStart}/o
5
- FullToken = /^#{TagStart}\s*(\w+)\s*(.*)?#{TagEnd}$/o
6
- ContentOfVariable = /^#{VariableStart}(.*)#{VariableEnd}$/o
7
-
8
- def blank?
9
- @blank || false
3
+ def initialize(tag_name, markup, options)
4
+ super
5
+ @blank = true
10
6
  end
11
7
 
12
8
  def parse(tokens)
13
- self.line ||= 1
14
- @blank = true
15
- @nodelist ||= []
16
- @nodelist.clear
17
-
18
- # All child tags of the current block.
19
- @children = []
20
-
21
- while token = tokens.shift
22
- case token
23
- when IsTag
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.new_with_options($1, $2, tokens, @options.merge(line: line))
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
44
- else
45
- raise SyntaxError.new(options[:locale].t("errors.syntax.tag_termination", :token => token, :tag_end => TagEnd.inspect), line)
46
- end
47
- when IsVariable
48
- new_var = create_variable(token)
49
- @nodelist << new_var
50
- @children << new_var
51
- @blank = false
52
- when ''
53
- # pass
54
- else
55
- self.line += token.count("\n") if @options[:count_lines] || Template.count_lines
56
- @nodelist << token
57
- @blank &&= (token =~ /\A\s*\z/)
58
- end
9
+ @body = BlockBody.new
10
+ while more = parse_body(@body, tokens)
59
11
  end
12
+ end
60
13
 
61
- # restore the line
14
+ def render(context)
15
+ @body.render(context)
16
+ end
17
+
18
+ def blank?
19
+ @blank
20
+ end
62
21
 
63
- # Make sure that it's ok to end parsing in the current block.
64
- # Effectively this method will throw an exception unless the current block is
65
- # of type Document
66
- assert_missing_delimitation!
22
+ def nodelist
23
+ @body.nodelist
67
24
  end
68
25
 
69
26
  # warnings of this block and all sub-tags
@@ -71,90 +28,52 @@ module Liquid
71
28
  all_warnings = []
72
29
  all_warnings.concat(@warnings) if @warnings
73
30
 
74
- (@children || []).each do |node|
75
- all_warnings.concat(node.warnings || [])
31
+ (nodelist || []).each do |node|
32
+ all_warnings.concat(node.warnings || []) if node.respond_to?(:warnings)
76
33
  end
77
34
 
78
35
  all_warnings
79
36
  end
80
37
 
81
- def end_tag
82
- end
83
-
84
38
  def unknown_tag(tag, params, tokens)
85
39
  case tag
86
- when 'else'
87
- raise SyntaxError.new(options[:locale].t("errors.syntax.unexpected_else",
88
- :block_name => block_name), line)
89
- when 'end'
90
- raise SyntaxError.new(options[:locale].t("errors.syntax.invalid_delimiter",
40
+ when 'else'.freeze
41
+ raise SyntaxError.new(options[:locale].t("errors.syntax.unexpected_else".freeze,
42
+ :block_name => block_name))
43
+ when 'end'.freeze
44
+ raise SyntaxError.new(options[:locale].t("errors.syntax.invalid_delimiter".freeze,
91
45
  :block_name => block_name,
92
- :block_delimiter => block_delimiter), line)
46
+ :block_delimiter => block_delimiter))
93
47
  else
94
- raise SyntaxError.new(options[:locale].t("errors.syntax.unknown_tag", :tag => tag), line)
48
+ raise SyntaxError.new(options[:locale].t("errors.syntax.unknown_tag".freeze, :tag => tag))
95
49
  end
96
50
  end
97
51
 
98
- def block_delimiter
99
- "end#{block_name}"
100
- end
101
-
102
52
  def block_name
103
53
  @tag_name
104
54
  end
105
55
 
106
- def create_variable(token)
107
- token.scan(ContentOfVariable) do |content|
108
- return Variable.new(content.first, @options)
109
- end
110
- raise SyntaxError.new(options[:locale].t("errors.syntax.variable_termination", :token => token, :tag_end => VariableEnd.inspect), line)
111
- end
112
-
113
- def render(context)
114
- render_all(@nodelist, context)
56
+ def block_delimiter
57
+ @block_delimiter ||= "end#{block_name}"
115
58
  end
116
59
 
117
60
  protected
118
61
 
119
- def assert_missing_delimitation!
120
- raise SyntaxError.new(options[:locale].t("errors.syntax.tag_never_closed", :block_name => block_name), line)
121
- end
122
-
123
- def render_all(list, context)
124
- output = []
125
- context.resource_limits[:render_length_current] = 0
126
- context.resource_limits[:render_score_current] += list.length
62
+ def parse_body(body, tokens)
63
+ body.parse(tokens, options) do |end_tag_name, end_tag_params|
64
+ @blank &&= body.blank?
127
65
 
128
- list.each do |token|
129
- # Break out if we have any unhanded interrupts.
130
- break if context.has_interrupt?
131
-
132
- begin
133
- # If we get an Interrupt that means the block must stop processing. An
134
- # Interrupt is any command that stops block execution such as {% break %}
135
- # or {% continue %}
136
- if token.is_a? Continue or token.is_a? Break
137
- context.push_interrupt(token.interrupt)
138
- break
139
- end
140
-
141
- token_output = (token.respond_to?(:render) ? token.render(context) : token)
142
- context.resource_limits[:render_length_current] += (token_output.respond_to?(:length) ? token_output.length : 1)
143
- if context.resource_limits_reached?
144
- context.resource_limits[:reached] = true
145
- raise MemoryError.new("Memory limits exceeded", token.respond_to?(:line) ? token.line : nil)
146
- end
147
- unless token.is_a?(Block) && token.blank?
148
- output << token_output
149
- end
150
- rescue MemoryError => e
151
- raise e
152
- rescue ::StandardError => e
153
- output << (context.handle_error(e))
66
+ return false if end_tag_name == block_delimiter
67
+ unless end_tag_name
68
+ raise SyntaxError.new(@options[:locale].t("errors.syntax.tag_never_closed".freeze, :block_name => block_name))
154
69
  end
70
+
71
+ # this tag is not registered with the system
72
+ # pass it to the current block for special handling or error reporting
73
+ unknown_tag(end_tag_name, end_tag_params, tokens)
155
74
  end
156
75
 
157
- output.join
76
+ true
158
77
  end
159
78
  end
160
79
  end
@@ -0,0 +1,131 @@
1
+ module Liquid
2
+ class BlockBody
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
+ attr_reader :nodelist
9
+
10
+ def initialize
11
+ @nodelist = []
12
+ @blank = true
13
+ end
14
+
15
+ def parse(tokens, options)
16
+ while token = tokens.shift
17
+ begin
18
+ unless token.empty?
19
+ case
20
+ when token.start_with?(TAGSTART)
21
+ if token =~ FullToken
22
+ tag_name = $1
23
+ markup = $2
24
+ # fetch the tag from registered blocks
25
+ if tag = Template.tags[tag_name]
26
+ markup = token.child(markup) if token.is_a?(Token)
27
+ new_tag = tag.parse(tag_name, markup, tokens, options)
28
+ new_tag.line_number = token.line_number if token.is_a?(Token)
29
+ @blank &&= new_tag.blank?
30
+ @nodelist << new_tag
31
+ else
32
+ # end parsing if we reach an unknown tag and let the caller decide
33
+ # determine how to proceed
34
+ return yield tag_name, markup
35
+ end
36
+ else
37
+ raise_missing_tag_terminator(token, options)
38
+ end
39
+ when token.start_with?(VARSTART)
40
+ new_var = create_variable(token, options)
41
+ new_var.line_number = token.line_number if token.is_a?(Token)
42
+ @nodelist << new_var
43
+ @blank = false
44
+ else
45
+ @nodelist << token
46
+ @blank &&= !!(token =~ /\A\s*\z/)
47
+ end
48
+ end
49
+ rescue SyntaxError => e
50
+ e.set_line_number_from_token(token)
51
+ raise
52
+ end
53
+ end
54
+
55
+ yield nil, nil
56
+ end
57
+
58
+ def blank?
59
+ @blank
60
+ end
61
+
62
+ def warnings
63
+ all_warnings = []
64
+ nodelist.each do |node|
65
+ all_warnings.concat(node.warnings || []) if node.respond_to?(:warnings)
66
+ end
67
+ all_warnings
68
+ end
69
+
70
+ def render(context)
71
+ output = []
72
+ context.resource_limits.render_score += @nodelist.length
73
+
74
+ @nodelist.each do |token|
75
+ # Break out if we have any unhanded interrupts.
76
+ break if context.has_interrupt?
77
+
78
+ begin
79
+ # If we get an Interrupt that means the block must stop processing. An
80
+ # Interrupt is any command that stops block execution such as {% break %}
81
+ # or {% continue %}
82
+ if token.is_a?(Continue) or token.is_a?(Break)
83
+ context.push_interrupt(token.interrupt)
84
+ break
85
+ end
86
+
87
+ token_output = render_token(token, context)
88
+
89
+ unless token.is_a?(Block) && token.blank?
90
+ output << token_output
91
+ end
92
+ rescue MemoryError => e
93
+ raise e
94
+ rescue ::StandardError => e
95
+ output << context.handle_error(e, token)
96
+ end
97
+ end
98
+
99
+ output.join
100
+ end
101
+
102
+ private
103
+
104
+ def render_token(token, context)
105
+ token_output = (token.respond_to?(:render) ? token.render(context) : token)
106
+ token_str = token_output.is_a?(Array) ? token_output.join : token_output.to_s
107
+
108
+ context.resource_limits.render_length += token_str.length
109
+ if context.resource_limits.reached?
110
+ raise MemoryError.new("Memory limits exceeded".freeze)
111
+ end
112
+ token_str
113
+ end
114
+
115
+ def create_variable(token, options)
116
+ token.scan(ContentOfVariable) do |content|
117
+ markup = token.is_a?(Token) ? token.child(content.first) : content.first
118
+ return Variable.new(markup, options)
119
+ end
120
+ raise_missing_variable_terminator(token, options)
121
+ end
122
+
123
+ def raise_missing_tag_terminator(token, options)
124
+ raise SyntaxError.new(options[:locale].t("errors.syntax.tag_termination".freeze, :token => token, :tag_end => TagEnd.inspect))
125
+ end
126
+
127
+ def raise_missing_variable_terminator(token, options)
128
+ raise SyntaxError.new(options[:locale].t("errors.syntax.variable_termination".freeze, :token => token, :tag_end => VariableEnd.inspect))
129
+ end
130
+ end
131
+ end