liquid 2.6.3 → 3.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (89) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +42 -13
  3. data/README.md +27 -2
  4. data/lib/liquid.rb +11 -11
  5. data/lib/liquid/block.rb +75 -45
  6. data/lib/liquid/condition.rb +15 -11
  7. data/lib/liquid/context.rb +68 -29
  8. data/lib/liquid/document.rb +3 -3
  9. data/lib/liquid/drop.rb +17 -1
  10. data/lib/liquid/file_system.rb +17 -6
  11. data/lib/liquid/i18n.rb +39 -0
  12. data/lib/liquid/interrupts.rb +1 -1
  13. data/lib/liquid/lexer.rb +51 -0
  14. data/lib/liquid/locales/en.yml +22 -0
  15. data/lib/liquid/parser.rb +90 -0
  16. data/lib/liquid/standardfilters.rb +115 -52
  17. data/lib/liquid/strainer.rb +14 -4
  18. data/lib/liquid/tag.rb +42 -7
  19. data/lib/liquid/tags/assign.rb +10 -8
  20. data/lib/liquid/tags/break.rb +1 -1
  21. data/lib/liquid/tags/capture.rb +10 -8
  22. data/lib/liquid/tags/case.rb +13 -13
  23. data/lib/liquid/tags/comment.rb +9 -2
  24. data/lib/liquid/tags/continue.rb +1 -4
  25. data/lib/liquid/tags/cycle.rb +5 -7
  26. data/lib/liquid/tags/decrement.rb +3 -4
  27. data/lib/liquid/tags/for.rb +69 -36
  28. data/lib/liquid/tags/if.rb +52 -25
  29. data/lib/liquid/tags/ifchanged.rb +2 -2
  30. data/lib/liquid/tags/include.rb +8 -7
  31. data/lib/liquid/tags/increment.rb +4 -8
  32. data/lib/liquid/tags/raw.rb +3 -3
  33. data/lib/liquid/tags/table_row.rb +73 -0
  34. data/lib/liquid/tags/unless.rb +2 -4
  35. data/lib/liquid/template.rb +69 -10
  36. data/lib/liquid/utils.rb +13 -4
  37. data/lib/liquid/variable.rb +59 -8
  38. data/lib/liquid/version.rb +1 -1
  39. data/test/fixtures/en_locale.yml +9 -0
  40. data/test/{liquid → integration}/assign_test.rb +6 -0
  41. data/test/integration/blank_test.rb +106 -0
  42. data/test/{liquid → integration}/capture_test.rb +2 -2
  43. data/test/integration/context_test.rb +33 -0
  44. data/test/integration/drop_test.rb +245 -0
  45. data/test/{liquid → integration}/error_handling_test.rb +31 -2
  46. data/test/{liquid → integration}/filter_test.rb +7 -7
  47. data/test/{liquid → integration}/hash_ordering_test.rb +0 -0
  48. data/test/{liquid → integration}/output_test.rb +12 -12
  49. data/test/integration/parsing_quirks_test.rb +94 -0
  50. data/test/{liquid → integration}/security_test.rb +9 -9
  51. data/test/{liquid → integration}/standard_filter_test.rb +103 -33
  52. data/test/{liquid → integration}/tags/break_tag_test.rb +0 -0
  53. data/test/{liquid → integration}/tags/continue_tag_test.rb +0 -0
  54. data/test/{liquid → integration}/tags/for_tag_test.rb +78 -0
  55. data/test/{liquid → integration}/tags/if_else_tag_test.rb +1 -1
  56. data/test/integration/tags/include_tag_test.rb +212 -0
  57. data/test/{liquid → integration}/tags/increment_tag_test.rb +0 -0
  58. data/test/{liquid → integration}/tags/raw_tag_test.rb +1 -0
  59. data/test/{liquid → integration}/tags/standard_tag_test.rb +24 -22
  60. data/test/integration/tags/statements_test.rb +113 -0
  61. data/test/{liquid/tags/html_tag_test.rb → integration/tags/table_row_test.rb} +5 -5
  62. data/test/{liquid → integration}/tags/unless_else_tag_test.rb +0 -0
  63. data/test/{liquid → integration}/template_test.rb +66 -42
  64. data/test/integration/variable_test.rb +72 -0
  65. data/test/test_helper.rb +32 -7
  66. data/test/{liquid/block_test.rb → unit/block_unit_test.rb} +1 -1
  67. data/test/{liquid/condition_test.rb → unit/condition_unit_test.rb} +19 -1
  68. data/test/{liquid/context_test.rb → unit/context_unit_test.rb} +27 -19
  69. data/test/{liquid/file_system_test.rb → unit/file_system_unit_test.rb} +7 -1
  70. data/test/unit/i18n_unit_test.rb +37 -0
  71. data/test/unit/lexer_unit_test.rb +48 -0
  72. data/test/{liquid/module_ex_test.rb → unit/module_ex_unit_test.rb} +7 -7
  73. data/test/unit/parser_unit_test.rb +82 -0
  74. data/test/{liquid/regexp_test.rb → unit/regexp_unit_test.rb} +3 -3
  75. data/test/{liquid/strainer_test.rb → unit/strainer_unit_test.rb} +19 -1
  76. data/test/unit/tag_unit_test.rb +11 -0
  77. data/test/unit/tags/case_tag_unit_test.rb +10 -0
  78. data/test/unit/tags/for_tag_unit_test.rb +13 -0
  79. data/test/unit/tags/if_tag_unit_test.rb +8 -0
  80. data/test/unit/template_unit_test.rb +69 -0
  81. data/test/unit/tokenizer_unit_test.rb +29 -0
  82. data/test/{liquid/variable_test.rb → unit/variable_unit_test.rb} +17 -67
  83. metadata +117 -73
  84. data/lib/extras/liquid_view.rb +0 -51
  85. data/lib/liquid/htmltags.rb +0 -73
  86. data/test/liquid/drop_test.rb +0 -180
  87. data/test/liquid/parsing_quirks_test.rb +0 -52
  88. data/test/liquid/tags/include_tag_test.rb +0 -166
  89. data/test/liquid/tags/statements_test.rb +0 -134
@@ -11,6 +11,11 @@ module Liquid
11
11
  @@filters = []
12
12
  @@known_filters = Set.new
13
13
  @@known_methods = Set.new
14
+ @@strainer_class_cache = Hash.new do |hash, filters|
15
+ hash[filters] = Class.new(Strainer) do
16
+ filters.each { |f| include f }
17
+ end
18
+ end
14
19
 
15
20
  def initialize(context)
16
21
  @context = context
@@ -32,10 +37,13 @@ module Liquid
32
37
  end
33
38
  end
34
39
 
35
- def self.create(context)
36
- strainer = Strainer.new(context)
37
- @@filters.each { |m| strainer.extend(m) }
38
- strainer
40
+ def self.strainer_class_cache
41
+ @@strainer_class_cache
42
+ end
43
+
44
+ def self.create(context, filters = [])
45
+ filters = @@filters + filters
46
+ strainer_class_cache[filters].new(context)
39
47
  end
40
48
 
41
49
  def invoke(method, *args)
@@ -44,6 +52,8 @@ module Liquid
44
52
  else
45
53
  args.first
46
54
  end
55
+ rescue ::ArgumentError => e
56
+ raise Liquid::ArgumentError.new(e.message)
47
57
  end
48
58
 
49
59
  def invokable?(method)
data/lib/liquid/tag.rb CHANGED
@@ -1,13 +1,22 @@
1
1
  module Liquid
2
-
3
2
  class Tag
3
+ attr_accessor :options
4
+ attr_reader :nodelist, :warnings
5
+
6
+ class << self
7
+ def parse(tag_name, markup, tokens, options)
8
+ tag = new(tag_name, markup, options)
9
+ tag.parse(tokens)
10
+ tag
11
+ end
4
12
 
5
- attr_accessor :nodelist
13
+ private :new
14
+ end
6
15
 
7
- def initialize(tag_name, markup, tokens)
16
+ def initialize(tag_name, markup, options)
8
17
  @tag_name = tag_name
9
18
  @markup = markup
10
- parse(tokens)
19
+ @options = options
11
20
  end
12
21
 
13
22
  def parse(tokens)
@@ -18,9 +27,35 @@ module Liquid
18
27
  end
19
28
 
20
29
  def render(context)
21
- ''
30
+ ''.freeze
31
+ end
32
+
33
+ def blank?
34
+ false
35
+ end
36
+
37
+ def parse_with_selected_parser(markup)
38
+ case @options[:error_mode] || Template.error_mode
39
+ when :strict then strict_parse_with_error_context(markup)
40
+ when :lax then lax_parse(markup)
41
+ when :warn
42
+ begin
43
+ return strict_parse_with_error_context(markup)
44
+ rescue SyntaxError => e
45
+ @warnings ||= []
46
+ @warnings << e
47
+ return lax_parse(markup)
48
+ end
49
+ end
22
50
  end
23
51
 
24
- end # Tag
52
+ private
25
53
 
26
- end # Liquid
54
+ def strict_parse_with_error_context(markup)
55
+ strict_parse(markup)
56
+ rescue SyntaxError => e
57
+ e.message << " in \"#{markup.strip}\""
58
+ raise e
59
+ end
60
+ end
61
+ end
@@ -9,27 +9,29 @@ module Liquid
9
9
  # {{ foo }}
10
10
  #
11
11
  class Assign < Tag
12
- Syntax = /(#{VariableSignature}+)\s*=\s*(.*)\s*/o
12
+ Syntax = /(#{VariableSignature}+)\s*=\s*(.*)\s*/om
13
13
 
14
- def initialize(tag_name, markup, tokens)
14
+ def initialize(tag_name, markup, options)
15
+ super
15
16
  if markup =~ Syntax
16
17
  @to = $1
17
18
  @from = Variable.new($2)
18
19
  else
19
- raise SyntaxError.new("Syntax Error in 'assign' - Valid syntax: assign [var] = [source]")
20
+ raise SyntaxError.new options[:locale].t("errors.syntax.assign".freeze)
20
21
  end
21
-
22
- super
23
22
  end
24
23
 
25
24
  def render(context)
26
25
  val = @from.render(context)
27
26
  context.scopes.last[@to] = val
28
- context.resource_limits[:assign_score_current] += (val.respond_to?(:length) ? val.length : 1)
29
- ''
27
+ context.increment_used_resources(:assign_score_current, val)
28
+ ''.freeze
30
29
  end
31
30
 
31
+ def blank?
32
+ true
33
+ end
32
34
  end
33
35
 
34
- Template.register_tag('assign', Assign)
36
+ Template.register_tag('assign'.freeze, Assign)
35
37
  end
@@ -17,5 +17,5 @@ module Liquid
17
17
 
18
18
  end
19
19
 
20
- Template.register_tag('break', Break)
20
+ Template.register_tag('break'.freeze, Break)
21
21
  end
@@ -1,5 +1,4 @@
1
1
  module Liquid
2
-
3
2
  # Capture stores the result of a block into a variable without rendering it inplace.
4
3
  #
5
4
  # {% capture heading %}
@@ -14,23 +13,26 @@ module Liquid
14
13
  class Capture < Block
15
14
  Syntax = /(\w+)/
16
15
 
17
- def initialize(tag_name, markup, tokens)
16
+ def initialize(tag_name, markup, options)
17
+ super
18
18
  if markup =~ Syntax
19
19
  @to = $1
20
20
  else
21
- raise SyntaxError.new("Syntax Error in 'capture' - Valid syntax: capture [var]")
21
+ raise SyntaxError.new(options[:locale].t("errors.syntax.capture"))
22
22
  end
23
-
24
- super
25
23
  end
26
24
 
27
25
  def render(context)
28
26
  output = super
29
27
  context.scopes.last[@to] = output
30
- context.resource_limits[:assign_score_current] += (output.respond_to?(:length) ? output.length : 1)
31
- ''
28
+ context.increment_used_resources(:assign_score_current, output)
29
+ ''.freeze
30
+ end
31
+
32
+ def blank?
33
+ true
32
34
  end
33
35
  end
34
36
 
35
- Template.register_tag('capture', Capture)
37
+ Template.register_tag('capture'.freeze, Capture)
36
38
  end
@@ -1,26 +1,29 @@
1
1
  module Liquid
2
2
  class Case < Block
3
3
  Syntax = /(#{QuotedFragment})/o
4
- WhenSyntax = /(#{QuotedFragment})(?:(?:\s+or\s+|\s*\,\s*)(#{QuotedFragment}.*))?/o
4
+ WhenSyntax = /(#{QuotedFragment})(?:(?:\s+or\s+|\s*\,\s*)(#{QuotedFragment}.*))?/om
5
5
 
6
- def initialize(tag_name, markup, tokens)
6
+ def initialize(tag_name, markup, options)
7
+ super
7
8
  @blocks = []
8
9
 
9
10
  if markup =~ Syntax
10
11
  @left = $1
11
12
  else
12
- raise SyntaxError.new("Syntax Error in tag 'case' - Valid syntax: case [condition]")
13
+ raise SyntaxError.new(options[:locale].t("errors.syntax.case".freeze))
13
14
  end
15
+ end
14
16
 
15
- super
17
+ def nodelist
18
+ @blocks.map(&:attachment).flatten
16
19
  end
17
20
 
18
21
  def unknown_tag(tag, markup, tokens)
19
22
  @nodelist = []
20
23
  case tag
21
- when 'when'
24
+ when 'when'.freeze
22
25
  record_when_condition(markup)
23
- when 'else'
26
+ when 'else'.freeze
24
27
  record_else_condition(markup)
25
28
  else
26
29
  super
@@ -50,30 +53,27 @@ module Liquid
50
53
  while markup
51
54
  # Create a new nodelist and assign it to the new block
52
55
  if not markup =~ WhenSyntax
53
- raise SyntaxError.new("Syntax Error in tag 'case' - Valid when condition: {% when [condition] [or condition2...] %} ")
56
+ raise SyntaxError.new(options[:locale].t("errors.syntax.case_invalid_when".freeze))
54
57
  end
55
58
 
56
59
  markup = $2
57
60
 
58
- block = Condition.new(@left, '==', $1)
61
+ block = Condition.new(@left, '=='.freeze, $1)
59
62
  block.attach(@nodelist)
60
63
  @blocks.push(block)
61
64
  end
62
65
  end
63
66
 
64
67
  def record_else_condition(markup)
65
-
66
68
  if not markup.strip.empty?
67
- raise SyntaxError.new("Syntax Error in tag 'case' - Valid else condition: {% else %} (no parameters) ")
69
+ raise SyntaxError.new(options[:locale].t("errors.syntax.case_invalid_else".freeze))
68
70
  end
69
71
 
70
72
  block = ElseCondition.new
71
73
  block.attach(@nodelist)
72
74
  @blocks << block
73
75
  end
74
-
75
-
76
76
  end
77
77
 
78
- Template.register_tag('case', Case)
78
+ Template.register_tag('case'.freeze, Case)
79
79
  end
@@ -1,9 +1,16 @@
1
1
  module Liquid
2
2
  class Comment < Block
3
3
  def render(context)
4
- ''
4
+ ''.freeze
5
+ end
6
+
7
+ def unknown_tag(tag, markup, tokens)
8
+ end
9
+
10
+ def blank?
11
+ true
5
12
  end
6
13
  end
7
14
 
8
- Template.register_tag('comment', Comment)
15
+ Template.register_tag('comment'.freeze, Comment)
9
16
  end
@@ -1,5 +1,4 @@
1
1
  module Liquid
2
-
3
2
  # Continue tag to be used to break out of a for loop.
4
3
  #
5
4
  # == Basic Usage:
@@ -10,12 +9,10 @@ module Liquid
10
9
  # {% endfor %}
11
10
  #
12
11
  class Continue < Tag
13
-
14
12
  def interrupt
15
13
  ContinueInterrupt.new
16
14
  end
17
-
18
15
  end
19
16
 
20
- Template.register_tag('continue', Continue)
17
+ Template.register_tag('continue'.freeze, Continue)
21
18
  end
@@ -1,5 +1,4 @@
1
1
  module Liquid
2
-
3
2
  # Cycle is usually used within a loop to alternate between values, like colors or DOM classes.
4
3
  #
5
4
  # {% for item in items %}
@@ -13,10 +12,11 @@ module Liquid
13
12
  # <div class="green"> Item five</div>
14
13
  #
15
14
  class Cycle < Tag
16
- SimpleSyntax = /^#{QuotedFragment}+/o
17
- NamedSyntax = /^(#{QuotedFragment})\s*\:\s*(.*)/o
15
+ SimpleSyntax = /\A#{QuotedFragment}+/o
16
+ NamedSyntax = /\A(#{QuotedFragment})\s*\:\s*(.*)/om
18
17
 
19
- def initialize(tag_name, markup, tokens)
18
+ def initialize(tag_name, markup, options)
19
+ super
20
20
  case markup
21
21
  when NamedSyntax
22
22
  @variables = variables_from_string($2)
@@ -25,9 +25,8 @@ module Liquid
25
25
  @variables = variables_from_string(markup)
26
26
  @name = "'#{@variables.to_s}'"
27
27
  else
28
- raise SyntaxError.new("Syntax Error in 'cycle' - Valid syntax: cycle [name :] var [, var2, var3 ...]")
28
+ raise SyntaxError.new(options[:locale].t("errors.syntax.cycle".freeze))
29
29
  end
30
- super
31
30
  end
32
31
 
33
32
  def render(context)
@@ -52,7 +51,6 @@ module Liquid
52
51
  $1 ? $1 : nil
53
52
  end.compact
54
53
  end
55
-
56
54
  end
57
55
 
58
56
  Template.register_tag('cycle', Cycle)
@@ -19,10 +19,9 @@ module Liquid
19
19
  # Hello: -3
20
20
  #
21
21
  class Decrement < Tag
22
- def initialize(tag_name, markup, tokens)
23
- @variable = markup.strip
24
-
22
+ def initialize(tag_name, markup, options)
25
23
  super
24
+ @variable = markup.strip
26
25
  end
27
26
 
28
27
  def render(context)
@@ -35,5 +34,5 @@ module Liquid
35
34
  private
36
35
  end
37
36
 
38
- Template.register_tag('decrement', Decrement)
37
+ Template.register_tag('decrement'.freeze, Decrement)
39
38
  end
@@ -44,28 +44,24 @@ module Liquid
44
44
  # forloop.last:: Returns true if the item is the last item.
45
45
  #
46
46
  class For < Block
47
- Syntax = /\A(\w+)\s+in\s+(#{QuotedFragment}+)\s*(reversed)?/o
47
+ Syntax = /\A(#{VariableSegment}+)\s+in\s+(#{QuotedFragment}+)\s*(reversed)?/o
48
48
 
49
- def initialize(tag_name, markup, tokens)
50
- if markup =~ Syntax
51
- @variable_name = $1
52
- @collection_name = $2
53
- @name = "#{$1}-#{$2}"
54
- @reversed = $3
55
- @attributes = {}
56
- markup.scan(TagAttributes) do |key, value|
57
- @attributes[key] = value
58
- end
49
+ def initialize(tag_name, markup, options)
50
+ super
51
+ parse_with_selected_parser(markup)
52
+ @nodelist = @for_block = []
53
+ end
54
+
55
+ def nodelist
56
+ if @else_block
57
+ @for_block + @else_block
59
58
  else
60
- raise SyntaxError.new("Syntax Error in 'for loop' - Valid syntax: for [item] in [collection]")
59
+ @for_block
61
60
  end
62
-
63
- @nodelist = @for_block = []
64
- super
65
61
  end
66
62
 
67
63
  def unknown_tag(tag, markup, tokens)
68
- return super unless tag == 'else'
64
+ return super unless tag == 'else'.freeze
69
65
  @nodelist = @else_block = []
70
66
  end
71
67
 
@@ -78,17 +74,16 @@ module Liquid
78
74
  # Maintains Ruby 1.8.7 String#each behaviour on 1.9
79
75
  return render_else(context) unless iterable?(collection)
80
76
 
81
- from = if @attributes['offset'] == 'continue'
77
+ from = if @attributes['offset'.freeze] == 'continue'.freeze
82
78
  context.registers[:for][@name].to_i
83
79
  else
84
- context[@attributes['offset']].to_i
80
+ context[@attributes['offset'.freeze]].to_i
85
81
  end
86
82
 
87
- limit = context[@attributes['limit']]
83
+ limit = context[@attributes['limit'.freeze]]
88
84
  to = limit ? limit.to_i + from : nil
89
85
 
90
-
91
- segment = Utils.slice_collection_using_each(collection, from, to)
86
+ segment = Utils.slice_collection(collection, from, to)
92
87
 
93
88
  return render_else(context) if segment.empty?
94
89
 
@@ -104,15 +99,16 @@ module Liquid
104
99
  context.stack do
105
100
  segment.each_with_index do |item, index|
106
101
  context[@variable_name] = item
107
- context['forloop'] = {
108
- 'name' => @name,
109
- 'length' => length,
110
- 'index' => index + 1,
111
- 'index0' => index,
112
- 'rindex' => length - index,
113
- 'rindex0' => length - index - 1,
114
- 'first' => (index == 0),
115
- 'last' => (index == length - 1) }
102
+ context['forloop'.freeze] = {
103
+ 'name'.freeze => @name,
104
+ 'length'.freeze => length,
105
+ 'index'.freeze => index + 1,
106
+ 'index0'.freeze => index,
107
+ 'rindex'.freeze => length - index,
108
+ 'rindex0'.freeze => length - index - 1,
109
+ 'first'.freeze => (index == 0),
110
+ 'last'.freeze => (index == length - 1)
111
+ }
116
112
 
117
113
  result << render_all(@for_block, context)
118
114
 
@@ -127,16 +123,53 @@ module Liquid
127
123
  result
128
124
  end
129
125
 
130
- private
126
+ protected
131
127
 
132
- def render_else(context)
133
- return @else_block ? [render_all(@else_block, context)] : ''
128
+ def lax_parse(markup)
129
+ if markup =~ Syntax
130
+ @variable_name = $1
131
+ @collection_name = $2
132
+ @name = "#{$1}-#{$2}"
133
+ @reversed = $3
134
+ @attributes = {}
135
+ markup.scan(TagAttributes) do |key, value|
136
+ @attributes[key] = value
137
+ end
138
+ else
139
+ raise SyntaxError.new(options[:locale].t("errors.syntax.for".freeze))
134
140
  end
141
+ end
135
142
 
136
- def iterable?(collection)
137
- collection.respond_to?(:each) || Utils.non_blank_string?(collection)
143
+ def strict_parse(markup)
144
+ p = Parser.new(markup)
145
+ @variable_name = p.consume(:id)
146
+ raise SyntaxError.new(options[:locale].t("errors.syntax.for_invalid_in".freeze)) unless p.id?('in'.freeze)
147
+ @collection_name = p.expression
148
+ @name = "#{@variable_name}-#{@collection_name}"
149
+ @reversed = p.id?('reversed'.freeze)
150
+
151
+ @attributes = {}
152
+ while p.look(:id) && p.look(:colon, 1)
153
+ unless attribute = p.id?('limit'.freeze) || p.id?('offset'.freeze)
154
+ raise SyntaxError.new(options[:locale].t("errors.syntax.for_invalid_attribute".freeze))
155
+ end
156
+ p.consume
157
+ val = p.expression
158
+ @attributes[attribute] = val
138
159
  end
160
+ p.consume(:end_of_string)
161
+ end
162
+
163
+ private
164
+
165
+ def render_else(context)
166
+ return @else_block ? [render_all(@else_block, context)] : ''.freeze
167
+ end
168
+
169
+ def iterable?(collection)
170
+ collection.respond_to?(:each) || Utils.non_blank_string?(collection)
171
+ end
139
172
  end
140
173
 
141
- Template.register_tag('for', For)
174
+ Template.register_tag('for'.freeze, For)
142
175
  end