liquid 3.0.6 → 4.0.3

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 +5 -5
  2. data/History.md +154 -58
  3. data/{MIT-LICENSE → LICENSE} +0 -0
  4. data/README.md +33 -0
  5. data/lib/liquid/block.rb +42 -125
  6. data/lib/liquid/block_body.rb +99 -79
  7. data/lib/liquid/condition.rb +52 -32
  8. data/lib/liquid/context.rb +57 -51
  9. data/lib/liquid/document.rb +19 -9
  10. data/lib/liquid/drop.rb +17 -16
  11. data/lib/liquid/errors.rb +20 -24
  12. data/lib/liquid/expression.rb +26 -10
  13. data/lib/liquid/extensions.rb +19 -7
  14. data/lib/liquid/file_system.rb +11 -11
  15. data/lib/liquid/forloop_drop.rb +42 -0
  16. data/lib/liquid/i18n.rb +6 -6
  17. data/lib/liquid/interrupts.rb +1 -2
  18. data/lib/liquid/lexer.rb +12 -8
  19. data/lib/liquid/locales/en.yml +6 -2
  20. data/lib/liquid/parse_context.rb +38 -0
  21. data/lib/liquid/parse_tree_visitor.rb +42 -0
  22. data/lib/liquid/parser_switching.rb +4 -4
  23. data/lib/liquid/profiler/hooks.rb +7 -7
  24. data/lib/liquid/profiler.rb +18 -19
  25. data/lib/liquid/range_lookup.rb +16 -1
  26. data/lib/liquid/resource_limits.rb +23 -0
  27. data/lib/liquid/standardfilters.rb +207 -61
  28. data/lib/liquid/strainer.rb +15 -8
  29. data/lib/liquid/tablerowloop_drop.rb +62 -0
  30. data/lib/liquid/tag.rb +9 -8
  31. data/lib/liquid/tags/assign.rb +25 -4
  32. data/lib/liquid/tags/break.rb +0 -3
  33. data/lib/liquid/tags/capture.rb +1 -1
  34. data/lib/liquid/tags/case.rb +27 -12
  35. data/lib/liquid/tags/comment.rb +2 -2
  36. data/lib/liquid/tags/cycle.rb +16 -8
  37. data/lib/liquid/tags/decrement.rb +1 -4
  38. data/lib/liquid/tags/for.rb +103 -75
  39. data/lib/liquid/tags/if.rb +60 -44
  40. data/lib/liquid/tags/ifchanged.rb +0 -2
  41. data/lib/liquid/tags/include.rb +71 -51
  42. data/lib/liquid/tags/raw.rb +32 -4
  43. data/lib/liquid/tags/table_row.rb +21 -31
  44. data/lib/liquid/tags/unless.rb +3 -4
  45. data/lib/liquid/template.rb +42 -54
  46. data/lib/liquid/tokenizer.rb +31 -0
  47. data/lib/liquid/truffle.rb +5 -0
  48. data/lib/liquid/utils.rb +52 -8
  49. data/lib/liquid/variable.rb +59 -46
  50. data/lib/liquid/variable_lookup.rb +14 -6
  51. data/lib/liquid/version.rb +2 -1
  52. data/lib/liquid.rb +10 -7
  53. data/test/integration/assign_test.rb +8 -8
  54. data/test/integration/blank_test.rb +14 -14
  55. data/test/integration/block_test.rb +12 -0
  56. data/test/integration/context_test.rb +2 -2
  57. data/test/integration/document_test.rb +19 -0
  58. data/test/integration/drop_test.rb +42 -40
  59. data/test/integration/error_handling_test.rb +96 -43
  60. data/test/integration/filter_test.rb +60 -20
  61. data/test/integration/hash_ordering_test.rb +9 -9
  62. data/test/integration/output_test.rb +26 -27
  63. data/test/integration/parse_tree_visitor_test.rb +247 -0
  64. data/test/integration/parsing_quirks_test.rb +19 -13
  65. data/test/integration/render_profiling_test.rb +20 -20
  66. data/test/integration/security_test.rb +23 -7
  67. data/test/integration/standard_filter_test.rb +426 -46
  68. data/test/integration/tags/break_tag_test.rb +1 -2
  69. data/test/integration/tags/continue_tag_test.rb +0 -1
  70. data/test/integration/tags/for_tag_test.rb +135 -100
  71. data/test/integration/tags/if_else_tag_test.rb +75 -77
  72. data/test/integration/tags/include_tag_test.rb +50 -31
  73. data/test/integration/tags/increment_tag_test.rb +10 -11
  74. data/test/integration/tags/raw_tag_test.rb +7 -1
  75. data/test/integration/tags/standard_tag_test.rb +121 -122
  76. data/test/integration/tags/statements_test.rb +3 -5
  77. data/test/integration/tags/table_row_test.rb +20 -19
  78. data/test/integration/tags/unless_else_tag_test.rb +6 -6
  79. data/test/integration/template_test.rb +199 -49
  80. data/test/integration/trim_mode_test.rb +529 -0
  81. data/test/integration/variable_test.rb +27 -13
  82. data/test/test_helper.rb +33 -6
  83. data/test/truffle/truffle_test.rb +9 -0
  84. data/test/unit/block_unit_test.rb +8 -5
  85. data/test/unit/condition_unit_test.rb +94 -77
  86. data/test/unit/context_unit_test.rb +69 -72
  87. data/test/unit/file_system_unit_test.rb +3 -3
  88. data/test/unit/i18n_unit_test.rb +2 -2
  89. data/test/unit/lexer_unit_test.rb +12 -9
  90. data/test/unit/parser_unit_test.rb +2 -2
  91. data/test/unit/regexp_unit_test.rb +1 -1
  92. data/test/unit/strainer_unit_test.rb +96 -1
  93. data/test/unit/tag_unit_test.rb +7 -2
  94. data/test/unit/tags/case_tag_unit_test.rb +1 -1
  95. data/test/unit/tags/for_tag_unit_test.rb +2 -2
  96. data/test/unit/tags/if_tag_unit_test.rb +1 -1
  97. data/test/unit/template_unit_test.rb +14 -5
  98. data/test/unit/tokenizer_unit_test.rb +24 -7
  99. data/test/unit/variable_unit_test.rb +60 -43
  100. metadata +62 -50
  101. data/lib/liquid/module_ex.rb +0 -62
  102. data/lib/liquid/token.rb +0 -18
  103. data/test/unit/module_ex_unit_test.rb +0 -87
data/lib/liquid/drop.rb CHANGED
@@ -1,7 +1,6 @@
1
1
  require 'set'
2
2
 
3
3
  module Liquid
4
-
5
4
  # A drop in liquid is a class which allows you to export DOM like things to liquid.
6
5
  # Methods of drops are callable.
7
6
  # The main use for liquid drops is to implement lazy loaded objects.
@@ -19,28 +18,27 @@ module Liquid
19
18
  # tmpl = Liquid::Template.parse( ' {% for product in product.top_sales %} {{ product.name }} {%endfor%} ' )
20
19
  # tmpl.render('product' => ProductDrop.new ) # will invoke top_sales query.
21
20
  #
22
- # Your drop can either implement the methods sans any parameters or implement the before_method(name) method which is a
23
- # catch all.
21
+ # Your drop can either implement the methods sans any parameters
22
+ # or implement the liquid_method_missing(name) method which is a catch all.
24
23
  class Drop
25
24
  attr_writer :context
26
25
 
27
- EMPTY_STRING = ''.freeze
28
-
29
26
  # Catch all for the method
30
- def before_method(method)
31
- nil
27
+ def liquid_method_missing(method)
28
+ return nil unless @context && @context.strict_variables
29
+ raise Liquid::UndefinedDropMethod, "undefined method #{method}"
32
30
  end
33
31
 
34
32
  # called by liquid to invoke a drop
35
33
  def invoke_drop(method_or_key)
36
- if method_or_key && method_or_key != EMPTY_STRING && self.class.invokable?(method_or_key)
34
+ if self.class.invokable?(method_or_key)
37
35
  send(method_or_key)
38
36
  else
39
- before_method(method_or_key)
37
+ liquid_method_missing(method_or_key)
40
38
  end
41
39
  end
42
40
 
43
- def has_key?(name)
41
+ def key?(_name)
44
42
  true
45
43
  end
46
44
 
@@ -56,22 +54,25 @@ module Liquid
56
54
  self.class.name
57
55
  end
58
56
 
59
- alias :[] :invoke_drop
60
-
61
- private
57
+ alias_method :[], :invoke_drop
62
58
 
63
59
  # Check for method existence without invoking respond_to?, which creates symbols
64
60
  def self.invokable?(method_name)
65
- unless @invokable_methods
61
+ invokable_methods.include?(method_name.to_s)
62
+ end
63
+
64
+ def self.invokable_methods
65
+ @invokable_methods ||= begin
66
66
  blacklist = Liquid::Drop.public_instance_methods + [:each]
67
+
67
68
  if include?(Enumerable)
68
69
  blacklist += Enumerable.public_instance_methods
69
70
  blacklist -= [:sort, :count, :first, :min, :max, :include?]
70
71
  end
72
+
71
73
  whitelist = [:to_liquid] + (public_instance_methods - blacklist)
72
- @invokable_methods = Set.new(whitelist.map(&:to_s))
74
+ Set.new(whitelist.map(&:to_s))
73
75
  end
74
- @invokable_methods.include?(method_name.to_s)
75
76
  end
76
77
  end
77
78
  end
data/lib/liquid/errors.rb CHANGED
@@ -1,9 +1,10 @@
1
1
  module Liquid
2
2
  class Error < ::StandardError
3
3
  attr_accessor :line_number
4
+ attr_accessor :template_name
4
5
  attr_accessor :markup_context
5
6
 
6
- def to_s(with_prefix=true)
7
+ def to_s(with_prefix = true)
7
8
  str = ""
8
9
  str << message_prefix if with_prefix
9
10
  str << super()
@@ -16,20 +17,6 @@ module Liquid
16
17
  str
17
18
  end
18
19
 
19
- def set_line_number_from_token(token)
20
- return unless token.respond_to?(:line_number)
21
- return if self.line_number
22
- self.line_number = token.line_number
23
- end
24
-
25
- def self.render(e)
26
- if e.is_a?(Liquid::Error)
27
- e.to_s
28
- else
29
- "Liquid error: #{e.to_s}"
30
- end
31
- end
32
-
33
20
  private
34
21
 
35
22
  def message_prefix
@@ -41,7 +28,9 @@ module Liquid
41
28
  end
42
29
 
43
30
  if line_number
44
- str << " (line #{line_number})"
31
+ str << " ("
32
+ str << template_name << " " if template_name
33
+ str << "line " << line_number.to_s << ")"
45
34
  end
46
35
 
47
36
  str << ": "
@@ -49,12 +38,19 @@ module Liquid
49
38
  end
50
39
  end
51
40
 
52
- class ArgumentError < Error; end
53
- class ContextError < Error; end
54
- class FileSystemError < Error; end
55
- class StandardError < Error; end
56
- class SyntaxError < Error; end
57
- class StackLevelError < Error; end
58
- class TaintedError < Error; end
59
- class MemoryError < Error; end
41
+ ArgumentError = Class.new(Error)
42
+ ContextError = Class.new(Error)
43
+ FileSystemError = Class.new(Error)
44
+ StandardError = Class.new(Error)
45
+ SyntaxError = Class.new(Error)
46
+ StackLevelError = Class.new(Error)
47
+ TaintedError = Class.new(Error)
48
+ MemoryError = Class.new(Error)
49
+ ZeroDivisionError = Class.new(Error)
50
+ FloatDomainError = Class.new(Error)
51
+ UndefinedVariable = Class.new(Error)
52
+ UndefinedDropMethod = Class.new(Error)
53
+ UndefinedFilter = Class.new(Error)
54
+ MethodOverrideError = Class.new(Error)
55
+ InternalError = Class.new(Error)
60
56
  end
@@ -1,33 +1,49 @@
1
1
  module Liquid
2
2
  class Expression
3
+ class MethodLiteral
4
+ attr_reader :method_name, :to_s
5
+
6
+ def initialize(method_name, to_s)
7
+ @method_name = method_name
8
+ @to_s = to_s
9
+ end
10
+
11
+ def to_liquid
12
+ to_s
13
+ end
14
+ end
15
+
3
16
  LITERALS = {
4
17
  nil => nil, 'nil'.freeze => nil, 'null'.freeze => nil, ''.freeze => nil,
5
18
  'true'.freeze => true,
6
19
  'false'.freeze => false,
7
- 'blank'.freeze => :blank?,
8
- 'empty'.freeze => :empty?
9
- }
20
+ 'blank'.freeze => MethodLiteral.new(:blank?, '').freeze,
21
+ 'empty'.freeze => MethodLiteral.new(:empty?, '').freeze
22
+ }.freeze
23
+
24
+ SINGLE_QUOTED_STRING = /\A'(.*)'\z/m
25
+ DOUBLE_QUOTED_STRING = /\A"(.*)"\z/m
26
+ INTEGERS_REGEX = /\A(-?\d+)\z/
27
+ FLOATS_REGEX = /\A(-?\d[\d\.]+)\z/
28
+ RANGES_REGEX = /\A\((\S+)\.\.(\S+)\)\z/
10
29
 
11
30
  def self.parse(markup)
12
31
  if LITERALS.key?(markup)
13
32
  LITERALS[markup]
14
33
  else
15
34
  case markup
16
- when /\A'(.*)'\z/m # Single quoted strings
35
+ when SINGLE_QUOTED_STRING, DOUBLE_QUOTED_STRING
17
36
  $1
18
- when /\A"(.*)"\z/m # Double quoted strings
19
- $1
20
- when /\A(-?\d+)\z/ # Integer and floats
37
+ when INTEGERS_REGEX
21
38
  $1.to_i
22
- when /\A\((\S+)\.\.(\S+)\)\z/ # Ranges
39
+ when RANGES_REGEX
23
40
  RangeLookup.parse($1, $2)
24
- when /\A(-?\d[\d\.]+)\z/ # Floats
41
+ when FLOATS_REGEX
25
42
  $1.to_f
26
43
  else
27
44
  VariableLookup.parse(markup)
28
45
  end
29
46
  end
30
47
  end
31
-
32
48
  end
33
49
  end
@@ -7,44 +7,56 @@ class String # :nodoc:
7
7
  end
8
8
  end
9
9
 
10
- class Array # :nodoc:
10
+ class Symbol # :nodoc:
11
+ def to_liquid
12
+ to_s
13
+ end
14
+ end
15
+
16
+ class Array # :nodoc:
17
+ def to_liquid
18
+ self
19
+ end
20
+ end
21
+
22
+ class Hash # :nodoc:
11
23
  def to_liquid
12
24
  self
13
25
  end
14
26
  end
15
27
 
16
- class Hash # :nodoc:
28
+ class Numeric # :nodoc:
17
29
  def to_liquid
18
30
  self
19
31
  end
20
32
  end
21
33
 
22
- class Numeric # :nodoc:
34
+ class Range # :nodoc:
23
35
  def to_liquid
24
36
  self
25
37
  end
26
38
  end
27
39
 
28
- class Time # :nodoc:
40
+ class Time # :nodoc:
29
41
  def to_liquid
30
42
  self
31
43
  end
32
44
  end
33
45
 
34
- class DateTime < Date # :nodoc:
46
+ class DateTime < Date # :nodoc:
35
47
  def to_liquid
36
48
  self
37
49
  end
38
50
  end
39
51
 
40
- class Date # :nodoc:
52
+ class Date # :nodoc:
41
53
  def to_liquid
42
54
  self
43
55
  end
44
56
  end
45
57
 
46
58
  class TrueClass
47
- def to_liquid # :nodoc:
59
+ def to_liquid # :nodoc:
48
60
  self
49
61
  end
50
62
  end
@@ -8,13 +8,13 @@ module Liquid
8
8
  #
9
9
  # Example:
10
10
  #
11
- # Liquid::Template.file_system = Liquid::LocalFileSystem.new(template_path)
12
- # liquid = Liquid::Template.parse(template)
11
+ # Liquid::Template.file_system = Liquid::LocalFileSystem.new(template_path)
12
+ # liquid = Liquid::Template.parse(template)
13
13
  #
14
14
  # This will parse the template with a LocalFileSystem implementation rooted at 'template_path'.
15
15
  class BlankFileSystem
16
16
  # Called by Liquid to retrieve a template file
17
- def read_template_file(template_path, context)
17
+ def read_template_file(_template_path)
18
18
  raise FileSystemError, "This liquid context does not allow includes."
19
19
  end
20
20
  end
@@ -26,10 +26,10 @@ module Liquid
26
26
  #
27
27
  # Example:
28
28
  #
29
- # file_system = Liquid::LocalFileSystem.new("/some/path")
29
+ # file_system = Liquid::LocalFileSystem.new("/some/path")
30
30
  #
31
- # file_system.full_path("mypartial") # => "/some/path/_mypartial.liquid"
32
- # file_system.full_path("dir/mypartial") # => "/some/path/dir/_mypartial.liquid"
31
+ # file_system.full_path("mypartial") # => "/some/path/_mypartial.liquid"
32
+ # file_system.full_path("dir/mypartial") # => "/some/path/dir/_mypartial.liquid"
33
33
  #
34
34
  # Optionally in the second argument you can specify a custom pattern for template filenames.
35
35
  # The Kernel::sprintf format specification is used.
@@ -37,9 +37,9 @@ module Liquid
37
37
  #
38
38
  # Example:
39
39
  #
40
- # file_system = Liquid::LocalFileSystem.new("/some/path", "%s.html")
40
+ # file_system = Liquid::LocalFileSystem.new("/some/path", "%s.html")
41
41
  #
42
- # file_system.full_path("index") # => "/some/path/index.html"
42
+ # file_system.full_path("index") # => "/some/path/index.html"
43
43
  #
44
44
  class LocalFileSystem
45
45
  attr_accessor :root
@@ -49,9 +49,9 @@ module Liquid
49
49
  @pattern = pattern
50
50
  end
51
51
 
52
- def read_template_file(template_path, context)
52
+ def read_template_file(template_path)
53
53
  full_path = full_path(template_path)
54
- raise FileSystemError, "No such template '#{template_path}'" unless File.exists?(full_path)
54
+ raise FileSystemError, "No such template '#{template_path}'" unless File.exist?(full_path)
55
55
 
56
56
  File.read(full_path)
57
57
  end
@@ -65,7 +65,7 @@ module Liquid
65
65
  File.join(root, @pattern % template_path)
66
66
  end
67
67
 
68
- raise FileSystemError, "Illegal template path '#{File.expand_path(full_path)}'" unless File.expand_path(full_path) =~ /\A#{File.expand_path(root)}/
68
+ raise FileSystemError, "Illegal template path '#{File.expand_path(full_path)}'" unless File.expand_path(full_path).start_with?(File.expand_path(root))
69
69
 
70
70
  full_path
71
71
  end
@@ -0,0 +1,42 @@
1
+ module Liquid
2
+ class ForloopDrop < Drop
3
+ def initialize(name, length, parentloop)
4
+ @name = name
5
+ @length = length
6
+ @parentloop = parentloop
7
+ @index = 0
8
+ end
9
+
10
+ attr_reader :name, :length, :parentloop
11
+
12
+ def index
13
+ @index + 1
14
+ end
15
+
16
+ def index0
17
+ @index
18
+ end
19
+
20
+ def rindex
21
+ @length - @index
22
+ end
23
+
24
+ def rindex0
25
+ @length - @index - 1
26
+ end
27
+
28
+ def first
29
+ @index == 0
30
+ end
31
+
32
+ def last
33
+ @index == @length - 1
34
+ end
35
+
36
+ protected
37
+
38
+ def increment!
39
+ @index += 1
40
+ end
41
+ end
42
+ end
data/lib/liquid/i18n.rb CHANGED
@@ -2,10 +2,9 @@ require 'yaml'
2
2
 
3
3
  module Liquid
4
4
  class I18n
5
- DEFAULT_LOCALE = File.join(File.expand_path(File.dirname(__FILE__)), "locales", "en.yml")
5
+ DEFAULT_LOCALE = File.join(File.expand_path(__dir__), "locales", "en.yml")
6
6
 
7
- class TranslationError < StandardError
8
- end
7
+ TranslationError = Class.new(StandardError)
9
8
 
10
9
  attr_reader :path
11
10
 
@@ -23,11 +22,12 @@ module Liquid
23
22
  end
24
23
 
25
24
  private
25
+
26
26
  def interpolate(name, vars)
27
- name.gsub(/%\{(\w+)\}/) {
27
+ name.gsub(/%\{(\w+)\}/) do
28
28
  # raise TranslationError, "Undefined key #{$1} for interpolation in translation #{name}" unless vars[$1.to_sym]
29
- "#{vars[$1.to_sym]}"
30
- }
29
+ (vars[$1.to_sym]).to_s
30
+ end
31
31
  end
32
32
 
33
33
  def deep_fetch_translation(name)
@@ -1,10 +1,9 @@
1
1
  module Liquid
2
-
3
2
  # An interrupt is any command that breaks processing of a block (ex: a for loop).
4
3
  class Interrupt
5
4
  attr_reader :message
6
5
 
7
- def initialize(message=nil)
6
+ def initialize(message = nil)
8
7
  @message = message || "interrupt".freeze
9
8
  end
10
9
  end
data/lib/liquid/lexer.rb CHANGED
@@ -9,24 +9,28 @@ module Liquid
9
9
  '['.freeze => :open_square,
10
10
  ']'.freeze => :close_square,
11
11
  '('.freeze => :open_round,
12
- ')'.freeze => :close_round
13
- }
14
- IDENTIFIER = /[\w\-?!]+/
12
+ ')'.freeze => :close_round,
13
+ '?'.freeze => :question,
14
+ '-'.freeze => :dash
15
+ }.freeze
16
+ IDENTIFIER = /[a-zA-Z_][\w-]*\??/
15
17
  SINGLE_STRING_LITERAL = /'[^\']*'/
16
18
  DOUBLE_STRING_LITERAL = /"[^\"]*"/
17
19
  NUMBER_LITERAL = /-?\d+(\.\d+)?/
18
20
  DOTDOT = /\.\./
19
- COMPARISON_OPERATOR = /==|!=|<>|<=?|>=?|contains/
21
+ COMPARISON_OPERATOR = /==|!=|<>|<=?|>=?|contains(?=\s)/
22
+ WHITESPACE_OR_NOTHING = /\s*/
20
23
 
21
24
  def initialize(input)
22
- @ss = StringScanner.new(input.rstrip)
25
+ @ss = StringScanner.new(input)
23
26
  end
24
27
 
25
28
  def tokenize
26
29
  @output = []
27
30
 
28
- while !@ss.eos?
29
- @ss.skip(/\s*/)
31
+ until @ss.eos?
32
+ @ss.skip(WHITESPACE_OR_NOTHING)
33
+ break if @ss.eos?
30
34
  tok = case
31
35
  when t = @ss.scan(COMPARISON_OPERATOR) then [:comparison, t]
32
36
  when t = @ss.scan(SINGLE_STRING_LITERAL) then [:string, t]
@@ -37,7 +41,7 @@ module Liquid
37
41
  else
38
42
  c = @ss.getch
39
43
  if s = SPECIALS[c]
40
- [s,c]
44
+ [s, c]
41
45
  else
42
46
  raise SyntaxError, "Unexpected character #{c}"
43
47
  end
@@ -1,6 +1,7 @@
1
1
  ---
2
2
  errors:
3
3
  syntax:
4
+ tag_unexpected_args: "Syntax Error in '%{tag}' - Valid syntax: %{tag}"
4
5
  assign: "Syntax Error in 'assign' - Valid syntax: assign [var] = [source]"
5
6
  capture: "Syntax Error in 'capture' - Valid syntax: capture [var]"
6
7
  case: "Syntax Error in 'case' - Valid syntax: case [condition]"
@@ -13,10 +14,13 @@
13
14
  if: "Syntax Error in tag 'if' - Valid syntax: if [expression]"
14
15
  include: "Error in tag 'include' - Valid syntax: include '[template]' (with|for) [object|collection]"
15
16
  unknown_tag: "Unknown tag '%{tag}'"
16
- invalid_delimiter: "'end' is not a valid delimiter for %{block_name} tags. use %{block_delimiter}"
17
- unexpected_else: "%{block_name} tag does not expect else tag"
17
+ invalid_delimiter: "'%{tag}' is not a valid delimiter for %{block_name} tags. use %{block_delimiter}"
18
+ unexpected_else: "%{block_name} tag does not expect 'else' tag"
19
+ unexpected_outer_tag: "Unexpected outer '%{tag}' tag"
18
20
  tag_termination: "Tag '%{token}' was not properly terminated with regexp: %{tag_end}"
19
21
  variable_termination: "Variable '%{token}' was not properly terminated with regexp: %{tag_end}"
20
22
  tag_never_closed: "'%{block_name}' tag was never closed"
21
23
  meta_syntax_error: "Liquid syntax error: #{e.message}"
22
24
  table_row: "Syntax Error in 'table_row loop' - Valid syntax: table_row [item] in [collection] cols=3"
25
+ argument:
26
+ include: "Argument error in tag 'include' - Illegal template name"
@@ -0,0 +1,38 @@
1
+ module Liquid
2
+ class ParseContext
3
+ attr_accessor :locale, :line_number, :trim_whitespace, :depth
4
+ attr_reader :partial, :warnings, :error_mode
5
+
6
+ def initialize(options = {})
7
+ @template_options = options ? options.dup : {}
8
+ @locale = @template_options[:locale] ||= I18n.new
9
+ @warnings = []
10
+ self.depth = 0
11
+ self.partial = false
12
+ end
13
+
14
+ def [](option_key)
15
+ @options[option_key]
16
+ end
17
+
18
+ def partial=(value)
19
+ @partial = value
20
+ @options = value ? partial_options : @template_options
21
+ @error_mode = @options[:error_mode] || Template.error_mode
22
+ value
23
+ end
24
+
25
+ def partial_options
26
+ @partial_options ||= begin
27
+ dont_pass = @template_options[:include_options_blacklist]
28
+ if dont_pass == true
29
+ { locale: locale }
30
+ elsif dont_pass.is_a?(Array)
31
+ @template_options.reject { |k, v| dont_pass.include?(k) }
32
+ else
33
+ @template_options
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Liquid
4
+ class ParseTreeVisitor
5
+ def self.for(node, callbacks = Hash.new(proc {}))
6
+ if defined?(node.class::ParseTreeVisitor)
7
+ node.class::ParseTreeVisitor
8
+ else
9
+ self
10
+ end.new(node, callbacks)
11
+ end
12
+
13
+ def initialize(node, callbacks)
14
+ @node = node
15
+ @callbacks = callbacks
16
+ end
17
+
18
+ def add_callback_for(*classes, &block)
19
+ callback = block
20
+ callback = ->(node, _) { yield node } if block.arity.abs == 1
21
+ callback = ->(_, _) { yield } if block.arity.zero?
22
+ classes.each { |klass| @callbacks[klass] = callback }
23
+ self
24
+ end
25
+
26
+ def visit(context = nil)
27
+ children.map do |node|
28
+ item, new_context = @callbacks[node.class].call(node, context)
29
+ [
30
+ item,
31
+ ParseTreeVisitor.for(node, @callbacks).visit(new_context || context)
32
+ ]
33
+ end
34
+ end
35
+
36
+ protected
37
+
38
+ def children
39
+ @node.respond_to?(:nodelist) ? Array(@node.nodelist) : []
40
+ end
41
+ end
42
+ end
@@ -1,25 +1,25 @@
1
1
  module Liquid
2
2
  module ParserSwitching
3
3
  def parse_with_selected_parser(markup)
4
- case @options[:error_mode] || Template.error_mode
4
+ case parse_context.error_mode
5
5
  when :strict then strict_parse_with_error_context(markup)
6
6
  when :lax then lax_parse(markup)
7
7
  when :warn
8
8
  begin
9
9
  return strict_parse_with_error_context(markup)
10
10
  rescue SyntaxError => e
11
- e.set_line_number_from_token(markup)
12
- @warnings ||= []
13
- @warnings << e
11
+ parse_context.warnings << e
14
12
  return lax_parse(markup)
15
13
  end
16
14
  end
17
15
  end
18
16
 
19
17
  private
18
+
20
19
  def strict_parse_with_error_context(markup)
21
20
  strict_parse(markup)
22
21
  rescue SyntaxError => e
22
+ e.line_number = line_number
23
23
  e.markup_context = markup_context(markup)
24
24
  raise e
25
25
  end
@@ -1,18 +1,18 @@
1
1
  module Liquid
2
- class Block < Tag
3
- def render_token_with_profiling(token, context)
4
- Profiler.profile_token_render(token) do
5
- render_token_without_profiling(token, context)
2
+ class BlockBody
3
+ def render_node_with_profiling(node, output, context, skip_output = false)
4
+ Profiler.profile_node_render(node) do
5
+ render_node_without_profiling(node, output, context, skip_output)
6
6
  end
7
7
  end
8
8
 
9
- alias_method :render_token_without_profiling, :render_token
10
- alias_method :render_token, :render_token_with_profiling
9
+ alias_method :render_node_without_profiling, :render_node_to_output
10
+ alias_method :render_node_to_output, :render_node_with_profiling
11
11
  end
12
12
 
13
13
  class Include < Tag
14
14
  def render_with_profiling(context)
15
- Profiler.profile_children(@template_name) do
15
+ Profiler.profile_children(context.evaluate(@template_name_expr).to_s) do
16
16
  render_without_profiling(context)
17
17
  end
18
18
  end