liquid 3.0.6 → 4.0.3

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.
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