liquid 3.0.6 → 4.0.0

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 (98) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +98 -58
  3. data/README.md +31 -0
  4. data/lib/liquid/block.rb +31 -124
  5. data/lib/liquid/block_body.rb +75 -59
  6. data/lib/liquid/condition.rb +23 -22
  7. data/lib/liquid/context.rb +50 -46
  8. data/lib/liquid/document.rb +19 -9
  9. data/lib/liquid/drop.rb +17 -16
  10. data/lib/liquid/errors.rb +20 -24
  11. data/lib/liquid/expression.rb +15 -3
  12. data/lib/liquid/extensions.rb +13 -7
  13. data/lib/liquid/file_system.rb +11 -11
  14. data/lib/liquid/forloop_drop.rb +42 -0
  15. data/lib/liquid/i18n.rb +5 -5
  16. data/lib/liquid/interrupts.rb +1 -2
  17. data/lib/liquid/lexer.rb +6 -4
  18. data/lib/liquid/locales/en.yml +5 -1
  19. data/lib/liquid/parse_context.rb +37 -0
  20. data/lib/liquid/parser_switching.rb +4 -4
  21. data/lib/liquid/profiler/hooks.rb +7 -7
  22. data/lib/liquid/profiler.rb +18 -19
  23. data/lib/liquid/range_lookup.rb +16 -1
  24. data/lib/liquid/resource_limits.rb +23 -0
  25. data/lib/liquid/standardfilters.rb +121 -61
  26. data/lib/liquid/strainer.rb +14 -7
  27. data/lib/liquid/tablerowloop_drop.rb +62 -0
  28. data/lib/liquid/tag.rb +9 -8
  29. data/lib/liquid/tags/assign.rb +17 -4
  30. data/lib/liquid/tags/break.rb +0 -3
  31. data/lib/liquid/tags/capture.rb +1 -1
  32. data/lib/liquid/tags/case.rb +19 -12
  33. data/lib/liquid/tags/comment.rb +2 -2
  34. data/lib/liquid/tags/cycle.rb +6 -6
  35. data/lib/liquid/tags/decrement.rb +1 -4
  36. data/lib/liquid/tags/for.rb +95 -75
  37. data/lib/liquid/tags/if.rb +49 -44
  38. data/lib/liquid/tags/ifchanged.rb +0 -2
  39. data/lib/liquid/tags/include.rb +61 -52
  40. data/lib/liquid/tags/raw.rb +32 -4
  41. data/lib/liquid/tags/table_row.rb +12 -30
  42. data/lib/liquid/tags/unless.rb +3 -4
  43. data/lib/liquid/template.rb +42 -54
  44. data/lib/liquid/tokenizer.rb +31 -0
  45. data/lib/liquid/utils.rb +52 -8
  46. data/lib/liquid/variable.rb +46 -45
  47. data/lib/liquid/variable_lookup.rb +7 -5
  48. data/lib/liquid/version.rb +1 -1
  49. data/lib/liquid.rb +9 -7
  50. data/test/integration/assign_test.rb +8 -8
  51. data/test/integration/blank_test.rb +14 -14
  52. data/test/integration/context_test.rb +2 -2
  53. data/test/integration/document_test.rb +19 -0
  54. data/test/integration/drop_test.rb +42 -40
  55. data/test/integration/error_handling_test.rb +99 -46
  56. data/test/integration/filter_test.rb +60 -20
  57. data/test/integration/hash_ordering_test.rb +9 -9
  58. data/test/integration/output_test.rb +26 -27
  59. data/test/integration/parsing_quirks_test.rb +15 -13
  60. data/test/integration/render_profiling_test.rb +20 -20
  61. data/test/integration/security_test.rb +9 -7
  62. data/test/integration/standard_filter_test.rb +179 -40
  63. data/test/integration/tags/break_tag_test.rb +1 -2
  64. data/test/integration/tags/continue_tag_test.rb +0 -1
  65. data/test/integration/tags/for_tag_test.rb +133 -98
  66. data/test/integration/tags/if_else_tag_test.rb +75 -77
  67. data/test/integration/tags/include_tag_test.rb +34 -30
  68. data/test/integration/tags/increment_tag_test.rb +10 -11
  69. data/test/integration/tags/raw_tag_test.rb +7 -1
  70. data/test/integration/tags/standard_tag_test.rb +121 -122
  71. data/test/integration/tags/statements_test.rb +3 -5
  72. data/test/integration/tags/table_row_test.rb +20 -19
  73. data/test/integration/tags/unless_else_tag_test.rb +6 -6
  74. data/test/integration/template_test.rb +190 -49
  75. data/test/integration/trim_mode_test.rb +525 -0
  76. data/test/integration/variable_test.rb +23 -13
  77. data/test/test_helper.rb +33 -5
  78. data/test/unit/block_unit_test.rb +8 -5
  79. data/test/unit/condition_unit_test.rb +86 -77
  80. data/test/unit/context_unit_test.rb +48 -57
  81. data/test/unit/file_system_unit_test.rb +3 -3
  82. data/test/unit/i18n_unit_test.rb +2 -2
  83. data/test/unit/lexer_unit_test.rb +11 -8
  84. data/test/unit/parser_unit_test.rb +2 -2
  85. data/test/unit/regexp_unit_test.rb +1 -1
  86. data/test/unit/strainer_unit_test.rb +80 -1
  87. data/test/unit/tag_unit_test.rb +7 -2
  88. data/test/unit/tags/case_tag_unit_test.rb +1 -1
  89. data/test/unit/tags/for_tag_unit_test.rb +2 -2
  90. data/test/unit/tags/if_tag_unit_test.rb +1 -1
  91. data/test/unit/template_unit_test.rb +14 -5
  92. data/test/unit/tokenizer_unit_test.rb +24 -7
  93. data/test/unit/variable_unit_test.rb +60 -43
  94. metadata +19 -14
  95. data/lib/liquid/module_ex.rb +0 -62
  96. data/lib/liquid/token.rb +0 -18
  97. data/test/unit/module_ex_unit_test.rb +0 -87
  98. /data/{MIT-LICENSE → LICENSE} +0 -0
data/lib/liquid/utils.rb CHANGED
@@ -1,27 +1,24 @@
1
1
  module Liquid
2
2
  module Utils
3
-
4
3
  def self.slice_collection(collection, from, to)
5
- if (from != 0 || to != nil) && collection.respond_to?(:load_slice)
4
+ if (from != 0 || !to.nil?) && collection.respond_to?(:load_slice)
6
5
  collection.load_slice(from, to)
7
6
  else
8
7
  slice_collection_using_each(collection, from, to)
9
8
  end
10
9
  end
11
10
 
12
- def self.non_blank_string?(collection)
13
- collection.is_a?(String) && collection != ''.freeze
14
- end
15
-
16
11
  def self.slice_collection_using_each(collection, from, to)
17
12
  segments = []
18
13
  index = 0
19
14
 
20
15
  # Maintains Ruby 1.8.7 String#each behaviour on 1.9
21
- return [collection] if non_blank_string?(collection)
16
+ if collection.is_a?(String)
17
+ return collection.empty? ? [] : [collection]
18
+ end
19
+ return [] unless collection.respond_to?(:each)
22
20
 
23
21
  collection.each do |item|
24
-
25
22
  if to && to <= index
26
23
  break
27
24
  end
@@ -35,5 +32,52 @@ module Liquid
35
32
 
36
33
  segments
37
34
  end
35
+
36
+ def self.to_integer(num)
37
+ return num if num.is_a?(Integer)
38
+ num = num.to_s
39
+ begin
40
+ Integer(num)
41
+ rescue ::ArgumentError
42
+ raise Liquid::ArgumentError, "invalid integer"
43
+ end
44
+ end
45
+
46
+ def self.to_number(obj)
47
+ case obj
48
+ when Float
49
+ BigDecimal.new(obj.to_s)
50
+ when Numeric
51
+ obj
52
+ when String
53
+ (obj.strip =~ /\A-?\d+\.\d+\z/) ? BigDecimal.new(obj) : obj.to_i
54
+ else
55
+ if obj.respond_to?(:to_number)
56
+ obj.to_number
57
+ else
58
+ 0
59
+ end
60
+ end
61
+ end
62
+
63
+ def self.to_date(obj)
64
+ return obj if obj.respond_to?(:strftime)
65
+
66
+ if obj.is_a?(String)
67
+ return nil if obj.empty?
68
+ obj = obj.downcase
69
+ end
70
+
71
+ case obj
72
+ when 'now'.freeze, 'today'.freeze
73
+ Time.now
74
+ when /\A\d+\z/, Integer
75
+ Time.at(obj.to_i)
76
+ when String
77
+ Time.parse(obj)
78
+ end
79
+ rescue ::ArgumentError
80
+ nil
81
+ end
38
82
  end
39
83
  end
@@ -1,5 +1,4 @@
1
1
  module Liquid
2
-
3
2
  # Holds variables. Variables are only loaded "just in time"
4
3
  # and are not evaluated as part of the render stage
5
4
  #
@@ -12,15 +11,16 @@ module Liquid
12
11
  #
13
12
  class Variable
14
13
  FilterParser = /(?:\s+|#{QuotedFragment}|#{ArgumentSeparator})+/o
15
- EasyParse = /\A *(\w+(?:\.\w+)*) *\z/
16
- attr_accessor :filters, :name, :warnings
17
- attr_accessor :line_number
14
+ attr_accessor :filters, :name, :line_number
15
+ attr_reader :parse_context
16
+ alias_method :options, :parse_context
18
17
  include ParserSwitching
19
18
 
20
- def initialize(markup, options = {})
19
+ def initialize(markup, parse_context)
21
20
  @markup = markup
22
21
  @name = nil
23
- @options = options || {}
22
+ @parse_context = parse_context
23
+ @line_number = parse_context.line_number
24
24
 
25
25
  parse_with_selected_parser(markup)
26
26
  end
@@ -35,35 +35,27 @@ module Liquid
35
35
 
36
36
  def lax_parse(markup)
37
37
  @filters = []
38
- if markup =~ /(#{QuotedFragment})(.*)/om
39
- name_markup = $1
40
- filter_markup = $2
41
- @name = Expression.parse(name_markup)
42
- if filter_markup =~ /#{FilterSeparator}\s*(.*)/om
43
- filters = $1.scan(FilterParser)
44
- filters.each do |f|
45
- if f =~ /\w+/
46
- filtername = Regexp.last_match(0)
47
- filterargs = f.scan(/(?:#{FilterArgumentSeparator}|#{ArgumentSeparator})\s*((?:\w+\s*\:\s*)?#{QuotedFragment})/o).flatten
48
- @filters << parse_filter_expressions(filtername, filterargs)
49
- end
50
- end
38
+ return unless markup =~ /(#{QuotedFragment})(.*)/om
39
+
40
+ name_markup = $1
41
+ filter_markup = $2
42
+ @name = Expression.parse(name_markup)
43
+ if filter_markup =~ /#{FilterSeparator}\s*(.*)/om
44
+ filters = $1.scan(FilterParser)
45
+ filters.each do |f|
46
+ next unless f =~ /\w+/
47
+ filtername = Regexp.last_match(0)
48
+ filterargs = f.scan(/(?:#{FilterArgumentSeparator}|#{ArgumentSeparator})\s*((?:\w+\s*\:\s*)?#{QuotedFragment})/o).flatten
49
+ @filters << parse_filter_expressions(filtername, filterargs)
51
50
  end
52
51
  end
53
52
  end
54
53
 
55
54
  def strict_parse(markup)
56
- # Very simple valid cases
57
- if markup =~ EasyParse
58
- @name = Expression.parse($1)
59
- @filters = []
60
- return
61
- end
62
-
63
55
  @filters = []
64
56
  p = Parser.new(markup)
65
- # Could be just filters with no input
66
- @name = p.look(:pipe) ? nil : Expression.parse(p.expression)
57
+
58
+ @name = Expression.parse(p.expression)
67
59
  while p.consume?(:pipe)
68
60
  filtername = p.consume(:id)
69
61
  filterargs = p.consume?(:colon) ? parse_filterargs(p) : []
@@ -76,17 +68,21 @@ module Liquid
76
68
  # first argument
77
69
  filterargs = [p.argument]
78
70
  # followed by comma separated others
79
- while p.consume?(:comma)
80
- filterargs << p.argument
81
- end
71
+ filterargs << p.argument while p.consume?(:comma)
82
72
  filterargs
83
73
  end
84
74
 
85
75
  def render(context)
86
- @filters.inject(context.evaluate(@name)) do |output, (filter_name, filter_args, filter_kwargs)|
76
+ obj = @filters.inject(context.evaluate(@name)) do |output, (filter_name, filter_args, filter_kwargs)|
87
77
  filter_args = evaluate_filter_expressions(context, filter_args, filter_kwargs)
88
- output = context.invoke(filter_name, output, *filter_args)
89
- end.tap{ |obj| taint_check(obj) }
78
+ context.invoke(filter_name, output, *filter_args)
79
+ end
80
+
81
+ obj = context.apply_global_filter(obj)
82
+
83
+ taint_check(context, obj)
84
+
85
+ obj
90
86
  end
91
87
 
92
88
  private
@@ -118,17 +114,22 @@ module Liquid
118
114
  parsed_args
119
115
  end
120
116
 
121
- def taint_check(obj)
122
- if obj.tainted?
123
- @markup =~ QuotedFragment
124
- name = Regexp.last_match(0)
125
- case Template.taint_mode
126
- when :warn
127
- @warnings ||= []
128
- @warnings << "variable '#{name}' is tainted and was not escaped"
129
- when :error
130
- raise TaintedError, "Error - variable '#{name}' is tainted and was not escaped"
131
- end
117
+ def taint_check(context, obj)
118
+ return unless obj.tainted?
119
+ return if Template.taint_mode == :lax
120
+
121
+ @markup =~ QuotedFragment
122
+ name = Regexp.last_match(0)
123
+
124
+ error = TaintedError.new("variable '#{name}' is tainted and was not escaped")
125
+ error.line_number = line_number
126
+ error.template_name = context.template_name
127
+
128
+ case Template.taint_mode
129
+ when :warn
130
+ context.warnings << error
131
+ when :error
132
+ raise error
132
133
  end
133
134
  end
134
135
  end
@@ -41,8 +41,8 @@ module Liquid
41
41
  # If object is a hash- or array-like object we look for the
42
42
  # presence of the key and if its available we return it
43
43
  if object.respond_to?(:[]) &&
44
- ((object.respond_to?(:has_key?) && object.has_key?(key)) ||
45
- (object.respond_to?(:fetch) && key.is_a?(Integer)))
44
+ ((object.respond_to?(:key?) && object.key?(key)) ||
45
+ (object.respond_to?(:fetch) && key.is_a?(Integer)))
46
46
 
47
47
  # if its a proc we will replace the entry with the proc
48
48
  res = context.lookup_and_evaluate(object, key)
@@ -55,9 +55,11 @@ module Liquid
55
55
  object = object.send(key).to_liquid
56
56
 
57
57
  # No key was present with the desired value and it wasn't one of the directly supported
58
- # keywords either. The only thing we got left is to return nil
58
+ # keywords either. The only thing we got left is to return nil or
59
+ # raise an exception if `strict_variables` option is set to true
59
60
  else
60
- return nil
61
+ return nil unless context.strict_variables
62
+ raise Liquid::UndefinedVariable, "undefined variable #{key}"
61
63
  end
62
64
 
63
65
  # If we are dealing with a drop here we have to
@@ -68,7 +70,7 @@ module Liquid
68
70
  end
69
71
 
70
72
  def ==(other)
71
- self.class == other.class && self.state == other.state
73
+ self.class == other.class && state == other.state
72
74
  end
73
75
 
74
76
  protected
@@ -1,4 +1,4 @@
1
1
  # encoding: utf-8
2
2
  module Liquid
3
- VERSION = "3.0.6"
3
+ VERSION = "4.0.0"
4
4
  end
data/lib/liquid.rb CHANGED
@@ -24,6 +24,7 @@ module Liquid
24
24
  ArgumentSeparator = ','.freeze
25
25
  FilterArgumentSeparator = ':'.freeze
26
26
  VariableAttributeSeparator = '.'.freeze
27
+ WhitespaceControl = '-'.freeze
27
28
  TagStart = /\{\%/
28
29
  TagEnd = /\%\}/
29
30
  VariableSignature = /\(?[\w\-\.\[\]]\)?/
@@ -34,7 +35,7 @@ module Liquid
34
35
  QuotedString = /"[^"]*"|'[^']*'/
35
36
  QuotedFragment = /#{QuotedString}|(?:[^\s,\|'"]|#{QuotedString})+/o
36
37
  TagAttributes = /(\w+)\s*\:\s*(#{QuotedFragment})/o
37
- AnyStartingTag = /\{\{|\{\%/
38
+ AnyStartingTag = /#{TagStart}|#{VariableStart}/o
38
39
  PartialTemplateParser = /#{TagStart}.*?#{TagEnd}|#{VariableStart}.*?#{VariableIncompleteEnd}/om
39
40
  TemplateParser = /(#{PartialTemplateParser}|#{AnyStartingTag})/om
40
41
  VariableParser = /\[[^\]]+\]|#{VariableSegment}+\??/o
@@ -48,6 +49,8 @@ require 'liquid/lexer'
48
49
  require 'liquid/parser'
49
50
  require 'liquid/i18n'
50
51
  require 'liquid/drop'
52
+ require 'liquid/tablerowloop_drop'
53
+ require 'liquid/forloop_drop'
51
54
  require 'liquid/extensions'
52
55
  require 'liquid/errors'
53
56
  require 'liquid/interrupts'
@@ -57,21 +60,20 @@ require 'liquid/context'
57
60
  require 'liquid/parser_switching'
58
61
  require 'liquid/tag'
59
62
  require 'liquid/block'
63
+ require 'liquid/block_body'
60
64
  require 'liquid/document'
61
65
  require 'liquid/variable'
62
66
  require 'liquid/variable_lookup'
63
67
  require 'liquid/range_lookup'
64
68
  require 'liquid/file_system'
69
+ require 'liquid/resource_limits'
65
70
  require 'liquid/template'
66
71
  require 'liquid/standardfilters'
67
72
  require 'liquid/condition'
68
- require 'liquid/module_ex'
69
73
  require 'liquid/utils'
70
- require 'liquid/token'
74
+ require 'liquid/tokenizer'
75
+ require 'liquid/parse_context'
71
76
 
72
77
  # Load all the tags of the standard library
73
78
  #
74
- Dir[File.dirname(__FILE__) + '/liquid/tags/*.rb'].each { |f| require f }
75
-
76
- require 'liquid/profiler'
77
- require 'liquid/profiler/hooks'
79
+ Dir["#{__dir__}/liquid/tags/*.rb"].each { |f| require f }
@@ -15,24 +15,24 @@ class AssignTest < Minitest::Test
15
15
 
16
16
  def test_assigned_variable
17
17
  assert_template_result('.foo.',
18
- '{% assign foo = values %}.{{ foo[0] }}.',
19
- 'values' => %w{foo bar baz})
18
+ '{% assign foo = values %}.{{ foo[0] }}.',
19
+ 'values' => %w(foo bar baz))
20
20
 
21
21
  assert_template_result('.bar.',
22
- '{% assign foo = values %}.{{ foo[1] }}.',
23
- 'values' => %w{foo bar baz})
22
+ '{% assign foo = values %}.{{ foo[1] }}.',
23
+ 'values' => %w(foo bar baz))
24
24
  end
25
25
 
26
26
  def test_assign_with_filter
27
27
  assert_template_result('.bar.',
28
- '{% assign foo = values | split: "," %}.{{ foo[1] }}.',
29
- 'values' => "foo,bar,baz")
28
+ '{% assign foo = values | split: "," %}.{{ foo[1] }}.',
29
+ 'values' => "foo,bar,baz")
30
30
  end
31
31
 
32
32
  def test_assign_syntax_error
33
33
  assert_match_syntax_error(/assign/,
34
- '{% assign foo not values %}.',
35
- 'values' => "foo,bar,baz")
34
+ '{% assign foo not values %}.',
35
+ 'values' => "foo,bar,baz")
36
36
  end
37
37
 
38
38
  def test_assign_uses_error_mode
@@ -9,7 +9,7 @@ class FoobarTag < Liquid::Tag
9
9
  end
10
10
 
11
11
  class BlankTestFileSystem
12
- def read_template_file(template_path, context)
12
+ def read_template_file(template_path)
13
13
  template_path
14
14
  end
15
15
  end
@@ -31,7 +31,7 @@ class BlankTest < Minitest::Test
31
31
  end
32
32
 
33
33
  def test_new_tags_are_not_blank_by_default
34
- assert_template_result(" "*N, wrap_in_for("{% foobar %}"))
34
+ assert_template_result(" " * N, wrap_in_for("{% foobar %}"))
35
35
  end
36
36
 
37
37
  def test_loops_are_blank
@@ -47,7 +47,7 @@ class BlankTest < Minitest::Test
47
47
  end
48
48
 
49
49
  def test_mark_as_blank_only_during_parsing
50
- assert_template_result(" "*(N+1), wrap(" {% if false %} this never happens, but still, this block is not blank {% endif %}"))
50
+ assert_template_result(" " * (N + 1), wrap(" {% if false %} this never happens, but still, this block is not blank {% endif %}"))
51
51
  end
52
52
 
53
53
  def test_comments_are_blank
@@ -60,9 +60,9 @@ class BlankTest < Minitest::Test
60
60
 
61
61
  def test_nested_blocks_are_blank_but_only_if_all_children_are
62
62
  assert_template_result("", wrap(wrap(" ")))
63
- assert_template_result("\n but this is not "*(N+1),
64
- wrap(%q{{% if true %} {% comment %} this is blank {% endcomment %} {% endif %}
65
- {% if true %} but this is not {% endif %}}))
63
+ assert_template_result("\n but this is not " * (N + 1),
64
+ wrap('{% if true %} {% comment %} this is blank {% endcomment %} {% endif %}
65
+ {% if true %} but this is not {% endif %}'))
66
66
  end
67
67
 
68
68
  def test_assigns_are_blank
@@ -76,31 +76,31 @@ class BlankTest < Minitest::Test
76
76
 
77
77
  def test_whitespace_is_not_blank_if_other_stuff_is_present
78
78
  body = " x "
79
- assert_template_result(body*(N+1), wrap(body))
79
+ assert_template_result(body * (N + 1), wrap(body))
80
80
  end
81
81
 
82
82
  def test_increment_is_not_blank
83
- assert_template_result(" 0"*2*(N+1), wrap("{% assign foo = 0 %} {% increment foo %} {% decrement foo %}"))
83
+ assert_template_result(" 0" * 2 * (N + 1), wrap("{% assign foo = 0 %} {% increment foo %} {% decrement foo %}"))
84
84
  end
85
85
 
86
86
  def test_cycle_is_not_blank
87
- assert_template_result(" "*((N+1)/2)+" ", wrap("{% cycle ' ', ' ' %}"))
87
+ assert_template_result(" " * ((N + 1) / 2) + " ", wrap("{% cycle ' ', ' ' %}"))
88
88
  end
89
89
 
90
90
  def test_raw_is_not_blank
91
- assert_template_result(" "*(N+1), wrap(" {% raw %} {% endraw %}"))
91
+ assert_template_result(" " * (N + 1), wrap(" {% raw %} {% endraw %}"))
92
92
  end
93
93
 
94
94
  def test_include_is_blank
95
95
  Liquid::Template.file_system = BlankTestFileSystem.new
96
- assert_template_result "foobar"*(N+1), wrap("{% include 'foobar' %}")
97
- assert_template_result " foobar "*(N+1), wrap("{% include ' foobar ' %}")
98
- assert_template_result " "*(N+1), wrap(" {% include ' ' %} ")
96
+ assert_template_result "foobar" * (N + 1), wrap("{% include 'foobar' %}")
97
+ assert_template_result " foobar " * (N + 1), wrap("{% include ' foobar ' %}")
98
+ assert_template_result " " * (N + 1), wrap(" {% include ' ' %} ")
99
99
  end
100
100
 
101
101
  def test_case_is_blank
102
102
  assert_template_result("", wrap(" {% assign foo = 'bar' %} {% case foo %} {% when 'bar' %} {% when 'whatever' %} {% else %} {% endcase %} "))
103
103
  assert_template_result("", wrap(" {% assign foo = 'else' %} {% case foo %} {% when 'bar' %} {% when 'whatever' %} {% else %} {% endcase %} "))
104
- assert_template_result(" x "*(N+1), wrap(" {% assign foo = 'else' %} {% case foo %} {% when 'bar' %} {% when 'whatever' %} {% else %} x {% endcase %} "))
104
+ assert_template_result(" x " * (N + 1), wrap(" {% assign foo = 'else' %} {% case foo %} {% when 'bar' %} {% when 'whatever' %} {% else %} x {% endcase %} "))
105
105
  end
106
106
  end
@@ -18,14 +18,14 @@ class ContextTest < Minitest::Test
18
18
 
19
19
  with_global_filter(global) do
20
20
  assert_equal 'Global test', Template.parse("{{'test' | notice }}").render!
21
- assert_equal 'Local test', Template.parse("{{'test' | notice }}").render!({}, :filters => [local])
21
+ assert_equal 'Local test', Template.parse("{{'test' | notice }}").render!({}, filters: [local])
22
22
  end
23
23
  end
24
24
 
25
25
  def test_has_key_will_not_add_an_error_for_missing_keys
26
26
  with_error_mode :strict do
27
27
  context = Context.new
28
- context.has_key?('unknown')
28
+ context.key?('unknown')
29
29
  assert_empty context.errors
30
30
  end
31
31
  end
@@ -0,0 +1,19 @@
1
+ require 'test_helper'
2
+
3
+ class DocumentTest < Minitest::Test
4
+ include Liquid
5
+
6
+ def test_unexpected_outer_tag
7
+ exc = assert_raises(SyntaxError) do
8
+ Template.parse("{% else %}")
9
+ end
10
+ assert_equal exc.message, "Liquid syntax error: Unexpected outer 'else' tag"
11
+ end
12
+
13
+ def test_unknown_tag
14
+ exc = assert_raises(SyntaxError) do
15
+ Template.parse("{% foo %}")
16
+ end
17
+ assert_equal exc.message, "Liquid syntax error: Unknown tag 'foo'"
18
+ end
19
+ end
@@ -13,13 +13,12 @@ class ContextDrop < Liquid::Drop
13
13
  @context['forloop.index']
14
14
  end
15
15
 
16
- def before_method(method)
17
- return @context[method]
16
+ def liquid_method_missing(method)
17
+ @context[method]
18
18
  end
19
19
  end
20
20
 
21
21
  class ProductDrop < Liquid::Drop
22
-
23
22
  class TextDrop < Liquid::Drop
24
23
  def array
25
24
  ['text1', 'text2']
@@ -31,8 +30,8 @@ class ProductDrop < Liquid::Drop
31
30
  end
32
31
 
33
32
  class CatchallDrop < Liquid::Drop
34
- def before_method(method)
35
- return 'method: ' << method.to_s
33
+ def liquid_method_missing(method)
34
+ 'catchall_method: ' << method.to_s
36
35
  end
37
36
  end
38
37
 
@@ -53,13 +52,14 @@ class ProductDrop < Liquid::Drop
53
52
  end
54
53
 
55
54
  protected
56
- def callmenot
57
- "protected"
58
- end
55
+
56
+ def callmenot
57
+ "protected"
58
+ end
59
59
  end
60
60
 
61
61
  class EnumerableDrop < Liquid::Drop
62
- def before_method(method)
62
+ def liquid_method_missing(method)
63
63
  method
64
64
  end
65
65
 
@@ -93,7 +93,7 @@ end
93
93
  class RealEnumerableDrop < Liquid::Drop
94
94
  include Enumerable
95
95
 
96
- def before_method(method)
96
+ def liquid_method_missing(method)
97
97
  method
98
98
  end
99
99
 
@@ -124,8 +124,10 @@ class DropsTest < Minitest::Test
124
124
  def test_rendering_warns_on_tainted_attr
125
125
  with_taint_mode(:warn) do
126
126
  tpl = Liquid::Template.parse('{{ product.user_input }}')
127
- tpl.render!('product' => ProductDrop.new)
128
- assert_match /tainted/, tpl.warnings.first
127
+ context = Context.new('product' => ProductDrop.new)
128
+ tpl.render!(context)
129
+ assert_equal [Liquid::TaintedError], context.warnings.map(&:class)
130
+ assert_equal "variable 'product.user_input' is tainted and was not escaped", context.warnings.first.to_s(false)
129
131
  end
130
132
  end
131
133
 
@@ -151,37 +153,37 @@ class DropsTest < Minitest::Test
151
153
  end
152
154
 
153
155
  def test_text_drop
154
- output = Liquid::Template.parse( ' {{ product.texts.text }} ' ).render!('product' => ProductDrop.new)
156
+ output = Liquid::Template.parse(' {{ product.texts.text }} ').render!('product' => ProductDrop.new)
155
157
  assert_equal ' text1 ', output
156
158
  end
157
159
 
158
- def test_unknown_method
159
- output = Liquid::Template.parse( ' {{ product.catchall.unknown }} ' ).render!('product' => ProductDrop.new)
160
- assert_equal ' method: unknown ', output
160
+ def test_catchall_unknown_method
161
+ output = Liquid::Template.parse(' {{ product.catchall.unknown }} ').render!('product' => ProductDrop.new)
162
+ assert_equal ' catchall_method: unknown ', output
161
163
  end
162
164
 
163
- def test_integer_argument_drop
164
- output = Liquid::Template.parse( ' {{ product.catchall[8] }} ' ).render!('product' => ProductDrop.new)
165
- assert_equal ' method: 8 ', output
165
+ def test_catchall_integer_argument_drop
166
+ output = Liquid::Template.parse(' {{ product.catchall[8] }} ').render!('product' => ProductDrop.new)
167
+ assert_equal ' catchall_method: 8 ', output
166
168
  end
167
169
 
168
170
  def test_text_array_drop
169
- output = Liquid::Template.parse( '{% for text in product.texts.array %} {{text}} {% endfor %}' ).render!('product' => ProductDrop.new)
171
+ output = Liquid::Template.parse('{% for text in product.texts.array %} {{text}} {% endfor %}').render!('product' => ProductDrop.new)
170
172
  assert_equal ' text1 text2 ', output
171
173
  end
172
174
 
173
175
  def test_context_drop
174
- output = Liquid::Template.parse( ' {{ context.bar }} ' ).render!('context' => ContextDrop.new, 'bar' => "carrot")
176
+ output = Liquid::Template.parse(' {{ context.bar }} ').render!('context' => ContextDrop.new, 'bar' => "carrot")
175
177
  assert_equal ' carrot ', output
176
178
  end
177
179
 
178
180
  def test_nested_context_drop
179
- output = Liquid::Template.parse( ' {{ product.context.foo }} ' ).render!('product' => ProductDrop.new, 'foo' => "monkey")
181
+ output = Liquid::Template.parse(' {{ product.context.foo }} ').render!('product' => ProductDrop.new, 'foo' => "monkey")
180
182
  assert_equal ' monkey ', output
181
183
  end
182
184
 
183
185
  def test_protected
184
- output = Liquid::Template.parse( ' {{ product.callmenot }} ' ).render!('product' => ProductDrop.new)
186
+ output = Liquid::Template.parse(' {{ product.callmenot }} ').render!('product' => ProductDrop.new)
185
187
  assert_equal ' ', output
186
188
  end
187
189
 
@@ -193,43 +195,43 @@ class DropsTest < Minitest::Test
193
195
  end
194
196
 
195
197
  def test_scope
196
- assert_equal '1', Liquid::Template.parse( '{{ context.scopes }}' ).render!('context' => ContextDrop.new)
197
- assert_equal '2', Liquid::Template.parse( '{%for i in dummy%}{{ context.scopes }}{%endfor%}' ).render!('context' => ContextDrop.new, 'dummy' => [1])
198
- assert_equal '3', Liquid::Template.parse( '{%for i in dummy%}{%for i in dummy%}{{ context.scopes }}{%endfor%}{%endfor%}' ).render!('context' => ContextDrop.new, 'dummy' => [1])
198
+ assert_equal '1', Liquid::Template.parse('{{ context.scopes }}').render!('context' => ContextDrop.new)
199
+ assert_equal '2', Liquid::Template.parse('{%for i in dummy%}{{ context.scopes }}{%endfor%}').render!('context' => ContextDrop.new, 'dummy' => [1])
200
+ assert_equal '3', Liquid::Template.parse('{%for i in dummy%}{%for i in dummy%}{{ context.scopes }}{%endfor%}{%endfor%}').render!('context' => ContextDrop.new, 'dummy' => [1])
199
201
  end
200
202
 
201
203
  def test_scope_though_proc
202
- assert_equal '1', Liquid::Template.parse( '{{ s }}' ).render!('context' => ContextDrop.new, 's' => Proc.new{|c| c['context.scopes'] })
203
- assert_equal '2', Liquid::Template.parse( '{%for i in dummy%}{{ s }}{%endfor%}' ).render!('context' => ContextDrop.new, 's' => Proc.new{|c| c['context.scopes'] }, 'dummy' => [1])
204
- assert_equal '3', Liquid::Template.parse( '{%for i in dummy%}{%for i in dummy%}{{ s }}{%endfor%}{%endfor%}' ).render!('context' => ContextDrop.new, 's' => Proc.new{|c| c['context.scopes'] }, 'dummy' => [1])
204
+ assert_equal '1', Liquid::Template.parse('{{ s }}').render!('context' => ContextDrop.new, 's' => proc{ |c| c['context.scopes'] })
205
+ assert_equal '2', Liquid::Template.parse('{%for i in dummy%}{{ s }}{%endfor%}').render!('context' => ContextDrop.new, 's' => proc{ |c| c['context.scopes'] }, 'dummy' => [1])
206
+ assert_equal '3', Liquid::Template.parse('{%for i in dummy%}{%for i in dummy%}{{ s }}{%endfor%}{%endfor%}').render!('context' => ContextDrop.new, 's' => proc{ |c| c['context.scopes'] }, 'dummy' => [1])
205
207
  end
206
208
 
207
209
  def test_scope_with_assigns
208
- assert_equal 'variable', Liquid::Template.parse( '{% assign a = "variable"%}{{a}}' ).render!('context' => ContextDrop.new)
209
- assert_equal 'variable', Liquid::Template.parse( '{% assign a = "variable"%}{%for i in dummy%}{{a}}{%endfor%}' ).render!('context' => ContextDrop.new, 'dummy' => [1])
210
- assert_equal 'test', Liquid::Template.parse( '{% assign header_gif = "test"%}{{header_gif}}' ).render!('context' => ContextDrop.new)
211
- assert_equal 'test', Liquid::Template.parse( "{% assign header_gif = 'test'%}{{header_gif}}" ).render!('context' => ContextDrop.new)
210
+ assert_equal 'variable', Liquid::Template.parse('{% assign a = "variable"%}{{a}}').render!('context' => ContextDrop.new)
211
+ assert_equal 'variable', Liquid::Template.parse('{% assign a = "variable"%}{%for i in dummy%}{{a}}{%endfor%}').render!('context' => ContextDrop.new, 'dummy' => [1])
212
+ assert_equal 'test', Liquid::Template.parse('{% assign header_gif = "test"%}{{header_gif}}').render!('context' => ContextDrop.new)
213
+ assert_equal 'test', Liquid::Template.parse("{% assign header_gif = 'test'%}{{header_gif}}").render!('context' => ContextDrop.new)
212
214
  end
213
215
 
214
216
  def test_scope_from_tags
215
- assert_equal '1', Liquid::Template.parse( '{% for i in context.scopes_as_array %}{{i}}{% endfor %}' ).render!('context' => ContextDrop.new, 'dummy' => [1])
216
- assert_equal '12', Liquid::Template.parse( '{%for a in dummy%}{% for i in context.scopes_as_array %}{{i}}{% endfor %}{% endfor %}' ).render!('context' => ContextDrop.new, 'dummy' => [1])
217
- assert_equal '123', Liquid::Template.parse( '{%for a in dummy%}{%for a in dummy%}{% for i in context.scopes_as_array %}{{i}}{% endfor %}{% endfor %}{% endfor %}' ).render!('context' => ContextDrop.new, 'dummy' => [1])
217
+ assert_equal '1', Liquid::Template.parse('{% for i in context.scopes_as_array %}{{i}}{% endfor %}').render!('context' => ContextDrop.new, 'dummy' => [1])
218
+ assert_equal '12', Liquid::Template.parse('{%for a in dummy%}{% for i in context.scopes_as_array %}{{i}}{% endfor %}{% endfor %}').render!('context' => ContextDrop.new, 'dummy' => [1])
219
+ assert_equal '123', Liquid::Template.parse('{%for a in dummy%}{%for a in dummy%}{% for i in context.scopes_as_array %}{{i}}{% endfor %}{% endfor %}{% endfor %}').render!('context' => ContextDrop.new, 'dummy' => [1])
218
220
  end
219
221
 
220
222
  def test_access_context_from_drop
221
- assert_equal '123', Liquid::Template.parse( '{%for a in dummy%}{{ context.loop_pos }}{% endfor %}' ).render!('context' => ContextDrop.new, 'dummy' => [1,2,3])
223
+ assert_equal '123', Liquid::Template.parse('{%for a in dummy%}{{ context.loop_pos }}{% endfor %}').render!('context' => ContextDrop.new, 'dummy' => [1, 2, 3])
222
224
  end
223
225
 
224
226
  def test_enumerable_drop
225
- assert_equal '123', Liquid::Template.parse( '{% for c in collection %}{{c}}{% endfor %}').render!('collection' => EnumerableDrop.new)
227
+ assert_equal '123', Liquid::Template.parse('{% for c in collection %}{{c}}{% endfor %}').render!('collection' => EnumerableDrop.new)
226
228
  end
227
229
 
228
230
  def test_enumerable_drop_size
229
- assert_equal '3', Liquid::Template.parse( '{{collection.size}}').render!('collection' => EnumerableDrop.new)
231
+ assert_equal '3', Liquid::Template.parse('{{collection.size}}').render!('collection' => EnumerableDrop.new)
230
232
  end
231
233
 
232
- def test_enumerable_drop_will_invoke_before_method_for_clashing_method_names
234
+ def test_enumerable_drop_will_invoke_liquid_method_missing_for_clashing_method_names
233
235
  ["select", "each", "map", "cycle"].each do |method|
234
236
  assert_equal method.to_s, Liquid::Template.parse("{{collection.#{method}}}").render!('collection' => EnumerableDrop.new)
235
237
  assert_equal method.to_s, Liquid::Template.parse("{{collection[\"#{method}\"]}}").render!('collection' => EnumerableDrop.new)