liquid 2.6.3 → 3.0.0.rc1

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