liquid 2.3.0 → 2.4.0

Sign up to get free protection for your applications and to get access to all the features.
data/History.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Liquid Version History
2
2
 
3
- ## 2.3.0
3
+ ## 2.3.0 / 2011-10-16
4
4
 
5
5
  * Several speed/memory improvements
6
6
  * Numerous bug fixes
data/README.md CHANGED
@@ -6,15 +6,15 @@ Liquid is a template engine which was written with very specific requirements:
6
6
 
7
7
  * It has to have beautiful and simple markup. Template engines which don't produce good looking markup are no fun to use.
8
8
  * It needs to be non evaling and secure. Liquid templates are made so that users can edit them. You don't want to run code on your server which your users wrote.
9
- * It has to be stateless. Compile and render steps have to be seperate so that the expensive parsing and compiling can be done once and later on you can just render it passing in a hash with local variables and objects.
9
+ * It has to be stateless. Compile and render steps have to be separate so that the expensive parsing and compiling can be done once and later on you can just render it passing in a hash with local variables and objects.
10
10
 
11
11
  ## Why you should use Liquid
12
12
 
13
13
  * You want to allow your users to edit the appearance of your application but don't want them to run **insecure code on your server**.
14
- * You want to render templates directly from the database
15
- * You like smarty (PHP) style template engines
16
- * You need a template engine which does HTML just as well as emails
17
- * You don't like the markup of your current templating engine
14
+ * You want to render templates directly from the database.
15
+ * You like smarty (PHP) style template engines.
16
+ * You need a template engine which does HTML just as well as emails.
17
+ * You don't like the markup of your current templating engine.
18
18
 
19
19
  ## What does it look like?
20
20
 
@@ -31,7 +31,7 @@ Liquid is a template engine which was written with very specific requirements:
31
31
  </ul>
32
32
  ```
33
33
 
34
- ## Howto use Liquid
34
+ ## How to use Liquid
35
35
 
36
36
  Liquid supports a very simple API based around the Liquid::Template class.
37
37
  For standard use you can just pass it the content of a file and call render with a parameters hash.
data/lib/liquid.rb CHANGED
@@ -32,17 +32,17 @@ module Liquid
32
32
  VariableEnd = /\}\}/
33
33
  VariableIncompleteEnd = /\}\}?/
34
34
  QuotedString = /"[^"]*"|'[^']*'/
35
- QuotedFragment = /#{QuotedString}|(?:[^\s,\|'"]|#{QuotedString})+/
35
+ QuotedFragment = /#{QuotedString}|(?:[^\s,\|'"]|#{QuotedString})+/o
36
36
  StrictQuotedFragment = /"[^"]+"|'[^']+'|[^\s|:,]+/
37
- FirstFilterArgument = /#{FilterArgumentSeparator}(?:#{StrictQuotedFragment})/
38
- OtherFilterArgument = /#{ArgumentSeparator}(?:#{StrictQuotedFragment})/
39
- SpacelessFilter = /^(?:'[^']+'|"[^"]+"|[^'"])*#{FilterSeparator}(?:#{StrictQuotedFragment})(?:#{FirstFilterArgument}(?:#{OtherFilterArgument})*)?/
40
- Expression = /(?:#{QuotedFragment}(?:#{SpacelessFilter})*)/
41
- TagAttributes = /(\w+)\s*\:\s*(#{QuotedFragment})/
37
+ FirstFilterArgument = /#{FilterArgumentSeparator}(?:#{StrictQuotedFragment})/o
38
+ OtherFilterArgument = /#{ArgumentSeparator}(?:#{StrictQuotedFragment})/o
39
+ SpacelessFilter = /^(?:'[^']+'|"[^"]+"|[^'"])*#{FilterSeparator}(?:#{StrictQuotedFragment})(?:#{FirstFilterArgument}(?:#{OtherFilterArgument})*)?/o
40
+ Expression = /(?:#{QuotedFragment}(?:#{SpacelessFilter})*)/o
41
+ TagAttributes = /(\w+)\s*\:\s*(#{QuotedFragment})/o
42
42
  AnyStartingTag = /\{\{|\{\%/
43
- PartialTemplateParser = /#{TagStart}.*?#{TagEnd}|#{VariableStart}.*?#{VariableIncompleteEnd}/
44
- TemplateParser = /(#{PartialTemplateParser}|#{AnyStartingTag})/
45
- VariableParser = /\[[^\]]+\]|#{VariableSegment}+\??/
43
+ PartialTemplateParser = /#{TagStart}.*?#{TagEnd}|#{VariableStart}.*?#{VariableIncompleteEnd}/o
44
+ TemplateParser = /(#{PartialTemplateParser}|#{AnyStartingTag})/o
45
+ VariableParser = /\[[^\]]+\]|#{VariableSegment}+\??/o
46
46
  end
47
47
 
48
48
  require 'liquid/drop'
@@ -60,6 +60,7 @@ require 'liquid/htmltags'
60
60
  require 'liquid/standardfilters'
61
61
  require 'liquid/condition'
62
62
  require 'liquid/module_ex'
63
+ require 'liquid/utils'
63
64
 
64
65
  # Load all the tags of the standard library
65
66
  #
data/lib/liquid/block.rb CHANGED
@@ -1,10 +1,10 @@
1
1
  module Liquid
2
2
 
3
3
  class Block < Tag
4
- IsTag = /^#{TagStart}/
5
- IsVariable = /^#{VariableStart}/
6
- FullToken = /^#{TagStart}\s*(\w+)\s*(.*)?#{TagEnd}$/
7
- ContentOfVariable = /^#{VariableStart}(.*)#{VariableEnd}$/
4
+ IsTag = /^#{TagStart}/o
5
+ IsVariable = /^#{VariableStart}/o
6
+ FullToken = /^#{TagStart}\s*(\w+)\s*(.*)?#{TagEnd}$/o
7
+ ContentOfVariable = /^#{VariableStart}(.*)#{VariableEnd}$/o
8
8
 
9
9
  def parse(tokens)
10
10
  @nodelist ||= []
@@ -136,11 +136,11 @@ module Liquid
136
136
  $1
137
137
  when /^"(.*)"$/ # Double quoted strings
138
138
  $1
139
- when /^(\d+)$/ # Integer and floats
139
+ when /^(-?\d+)$/ # Integer and floats
140
140
  $1.to_i
141
141
  when /^\((\S+)\.\.(\S+)\)$/ # Ranges
142
142
  (resolve($1).to_i..resolve($2).to_i)
143
- when /^(\d[\d\.]+)$/ # Floats
143
+ when /^(-?\d[\d\.]+)$/ # Floats
144
144
  $1.to_f
145
145
  else
146
146
  variable(key)
data/lib/liquid/drop.rb CHANGED
@@ -1,10 +1,10 @@
1
1
  module Liquid
2
2
 
3
- # A drop in liquid is a class which allows you to to export DOM like things to liquid.
3
+ # A drop in liquid is a class which allows you to export DOM like things to liquid.
4
4
  # Methods of drops are callable.
5
- # The main use for liquid drops is the implement lazy loaded objects.
5
+ # The main use for liquid drops is to implement lazy loaded objects.
6
6
  # If you would like to make data available to the web designers which you don't want loaded unless needed then
7
- # a drop is a great way to do that
7
+ # a drop is a great way to do that.
8
8
  #
9
9
  # Example:
10
10
  #
@@ -18,7 +18,7 @@ module Liquid
18
18
  # tmpl.render('product' => ProductDrop.new ) # will invoke top_sales query.
19
19
  #
20
20
  # Your drop can either implement the methods sans any parameters or implement the before_method(name) method which is a
21
- # catch all
21
+ # catch all.
22
22
  class Drop
23
23
  attr_writer :context
24
24
 
@@ -1,6 +1,6 @@
1
1
  module Liquid
2
2
  class TableRow < Block
3
- Syntax = /(\w+)\s+in\s+(#{VariableSignature}+)/
3
+ Syntax = /(\w+)\s+in\s+(#{QuotedFragment}+)/o
4
4
 
5
5
  def initialize(tag_name, markup, tokens)
6
6
  if markup =~ Syntax
@@ -20,11 +20,10 @@ module Liquid
20
20
  def render(context)
21
21
  collection = context[@collection_name] or return ''
22
22
 
23
- if @attributes['limit'] or @attributes['offset']
24
- limit = context[@attributes['limit']] || -1
25
- offset = context[@attributes['offset']] || 0
26
- collection = collection[offset.to_i..(limit.to_i + offset.to_i - 1)]
27
- end
23
+ from = @attributes['offset'] ? context[@attributes['offset']].to_i : 0
24
+ to = @attributes['limit'] ? from + context[@attributes['limit']].to_i : nil
25
+
26
+ collection = Utils.slice_collection_using_each(collection, from, to)
28
27
 
29
28
  length = collection.length
30
29
 
@@ -46,7 +45,7 @@ module Liquid
46
45
  'col0' => col,
47
46
  'index0' => index,
48
47
  'rindex' => length - index,
49
- 'rindex0' => length - index -1,
48
+ 'rindex0' => length - index - 1,
50
49
  'first' => (index == 0),
51
50
  'last' => (index == length - 1),
52
51
  'col_first' => (col == 0),
@@ -54,12 +54,16 @@ module Liquid
54
54
  end
55
55
 
56
56
  # Split input string into an array of substrings separated by given pattern.
57
+ #
58
+ # Example:
59
+ # <div class="summary">{{ post | split '//' | first }}</div>
60
+ #
57
61
  def split(input, pattern)
58
62
  input.split(pattern)
59
63
  end
60
64
 
61
65
  def strip_html(input)
62
- input.to_s.gsub(/<script.*?<\/script>/, '').gsub(/<.*?>/, '')
66
+ input.to_s.gsub(/<script.*?<\/script>/, '').gsub(/<!--.*?-->/, '').gsub(/<.*?>/, '')
63
67
  end
64
68
 
65
69
  # Remove all newlines from the string
@@ -218,6 +222,10 @@ module Liquid
218
222
  to_number(input) / to_number(operand)
219
223
  end
220
224
 
225
+ def modulo(input, operand)
226
+ to_number(input) % to_number(operand)
227
+ end
228
+
221
229
  private
222
230
 
223
231
  def to_number(obj)
@@ -9,12 +9,12 @@ module Liquid
9
9
  # {{ foo }}
10
10
  #
11
11
  class Assign < Tag
12
- Syntax = /(#{VariableSignature}+)\s*=\s*(#{QuotedFragment}+)/
12
+ Syntax = /(#{VariableSignature}+)\s*=\s*(.*)\s*/o
13
13
 
14
14
  def initialize(tag_name, markup, tokens)
15
15
  if markup =~ Syntax
16
16
  @to = $1
17
- @from = $2
17
+ @from = Variable.new($2)
18
18
  else
19
19
  raise SyntaxError.new("Syntax Error in 'assign' - Valid syntax: assign [var] = [source]")
20
20
  end
@@ -23,7 +23,7 @@ module Liquid
23
23
  end
24
24
 
25
25
  def render(context)
26
- context.scopes.last[@to] = context[@from]
26
+ context.scopes.last[@to] = @from.render(context)
27
27
  ''
28
28
  end
29
29
 
@@ -1,7 +1,7 @@
1
1
  module Liquid
2
2
  class Case < Block
3
- Syntax = /(#{QuotedFragment})/
4
- WhenSyntax = /(#{QuotedFragment})(?:(?:\s+or\s+|\s*\,\s*)(#{QuotedFragment}.*))?/
3
+ Syntax = /(#{QuotedFragment})/o
4
+ WhenSyntax = /(#{QuotedFragment})(?:(?:\s+or\s+|\s*\,\s*)(#{QuotedFragment}.*))?/o
5
5
 
6
6
  def initialize(tag_name, markup, tokens)
7
7
  @blocks = []
@@ -13,8 +13,8 @@ module Liquid
13
13
  # <div class="green"> Item five</div>
14
14
  #
15
15
  class Cycle < Tag
16
- SimpleSyntax = /^#{QuotedFragment}+/
17
- NamedSyntax = /^(#{QuotedFragment})\s*\:\s*(.*)/
16
+ SimpleSyntax = /^#{QuotedFragment}+/o
17
+ NamedSyntax = /^(#{QuotedFragment})\s*\:\s*(.*)/o
18
18
 
19
19
  def initialize(tag_name, markup, tokens)
20
20
  case markup
@@ -48,7 +48,7 @@ module Liquid
48
48
 
49
49
  def variables_from_string(markup)
50
50
  markup.split(',').collect do |var|
51
- var =~ /\s*(#{QuotedFragment})\s*/
51
+ var =~ /\s*(#{QuotedFragment})\s*/o
52
52
  $1 ? $1 : nil
53
53
  end.compact
54
54
  end
@@ -56,4 +56,4 @@ module Liquid
56
56
  end
57
57
 
58
58
  Template.register_tag('cycle', Cycle)
59
- end
59
+ end
@@ -44,7 +44,7 @@ module Liquid
44
44
  # forloop.last:: Returns true if the item is the last item.
45
45
  #
46
46
  class For < Block
47
- Syntax = /(\w+)\s+in\s+(#{QuotedFragment}+)\s*(reversed)?/
47
+ Syntax = /(\w+)\s+in\s+(#{QuotedFragment}+)\s*(reversed)?/o
48
48
 
49
49
  def initialize(tag_name, markup, tokens)
50
50
  if markup =~ Syntax
@@ -75,7 +75,8 @@ module Liquid
75
75
  collection = context[@collection_name]
76
76
  collection = collection.to_a if collection.is_a?(Range)
77
77
 
78
- return render_else(context) unless collection.respond_to?(:each)
78
+ # Maintains Ruby 1.8.7 String#each behaviour on 1.9
79
+ return render_else(context) unless iterable?(collection)
79
80
 
80
81
  from = if @attributes['offset'] == 'continue'
81
82
  context.registers[:for][@name].to_i
@@ -85,10 +86,10 @@ module Liquid
85
86
 
86
87
  limit = context[@attributes['limit']]
87
88
  to = limit ? limit.to_i + from : nil
88
-
89
-
90
- segment = slice_collection_using_each(collection, from, to)
91
-
89
+
90
+
91
+ segment = Utils.slice_collection_using_each(collection, from, to)
92
+
92
93
  return render_else(context) if segment.empty?
93
94
 
94
95
  segment.reverse! if @reversed
@@ -109,7 +110,7 @@ module Liquid
109
110
  'index' => index + 1,
110
111
  'index0' => index,
111
112
  'rindex' => length - index,
112
- 'rindex0' => length - index -1,
113
+ 'rindex0' => length - index - 1,
113
114
  'first' => (index == 0),
114
115
  'last' => (index == length - 1) }
115
116
 
@@ -118,26 +119,6 @@ module Liquid
118
119
  end
119
120
  result
120
121
  end
121
-
122
- def slice_collection_using_each(collection, from, to)
123
- segments = []
124
- index = 0
125
- yielded = 0
126
- collection.each do |item|
127
-
128
- if to && to <= index
129
- break
130
- end
131
-
132
- if from <= index
133
- segments << item
134
- end
135
-
136
- index += 1
137
- end
138
-
139
- segments
140
- end
141
122
 
142
123
  private
143
124
 
@@ -145,6 +126,9 @@ module Liquid
145
126
  return @else_block ? [render_all(@else_block, context)] : ''
146
127
  end
147
128
 
129
+ def iterable?(collection)
130
+ collection.respond_to?(:each) || Utils.non_blank_string?(collection)
131
+ end
148
132
  end
149
133
 
150
134
  Template.register_tag('for', For)
@@ -13,8 +13,8 @@ module Liquid
13
13
  #
14
14
  class If < Block
15
15
  SyntaxHelp = "Syntax Error in tag 'if' - Valid syntax: if [expression]"
16
- Syntax = /(#{QuotedFragment})\s*([=!<>a-z_]+)?\s*(#{QuotedFragment})?/
17
- ExpressionsAndOperators = /(?:\b(?:\s?and\s?|\s?or\s?)\b|(?:\s*(?!\b(?:\s?and\s?|\s?or\s?)\b)(?:#{QuotedFragment}|\S+)\s*)+)/
16
+ Syntax = /(#{QuotedFragment})\s*([=!<>a-z_]+)?\s*(#{QuotedFragment})?/o
17
+ ExpressionsAndOperators = /(?:\b(?:\s?and\s?|\s?or\s?)\b|(?:\s*(?!\b(?:\s?and\s?|\s?or\s?)\b)(?:#{QuotedFragment}|\S+)\s*)+)/o
18
18
 
19
19
  def initialize(tag_name, markup, tokens)
20
20
  @blocks = []
@@ -1,6 +1,6 @@
1
1
  module Liquid
2
2
  class Include < Tag
3
- Syntax = /(#{QuotedFragment}+)(\s+(?:with|for)\s+(#{QuotedFragment}+))?/
3
+ Syntax = /(#{QuotedFragment}+)(\s+(?:with|for)\s+(#{QuotedFragment}+))?/o
4
4
 
5
5
  def initialize(tag_name, markup, tokens)
6
6
  if markup =~ Syntax
@@ -62,4 +62,4 @@ module Liquid
62
62
  end
63
63
 
64
64
  Template.register_tag('include', Include)
65
- end
65
+ end
@@ -0,0 +1,31 @@
1
+ module Liquid
2
+ module Utils
3
+ def self.slice_collection_using_each(collection, from, to)
4
+ segments = []
5
+ index = 0
6
+ yielded = 0
7
+
8
+ # Maintains Ruby 1.8.7 String#each behaviour on 1.9
9
+ return [collection] if non_blank_string?(collection)
10
+
11
+ collection.each do |item|
12
+
13
+ if to && to <= index
14
+ break
15
+ end
16
+
17
+ if from <= index
18
+ segments << item
19
+ end
20
+
21
+ index += 1
22
+ end
23
+
24
+ segments
25
+ end
26
+
27
+ def self.non_blank_string?(collection)
28
+ collection.is_a?(String) && collection != ''
29
+ end
30
+ end
31
+ end
@@ -11,21 +11,21 @@ module Liquid
11
11
  # {{ user | link }}
12
12
  #
13
13
  class Variable
14
- FilterParser = /(?:#{FilterSeparator}|(?:\s*(?!(?:#{FilterSeparator}))(?:#{QuotedFragment}|\S+)\s*)+)/
14
+ FilterParser = /(?:#{FilterSeparator}|(?:\s*(?!(?:#{FilterSeparator}))(?:#{QuotedFragment}|\S+)\s*)+)/o
15
15
  attr_accessor :filters, :name
16
16
 
17
17
  def initialize(markup)
18
18
  @markup = markup
19
19
  @name = nil
20
20
  @filters = []
21
- if match = markup.match(/\s*(#{QuotedFragment})(.*)/)
21
+ if match = markup.match(/\s*(#{QuotedFragment})(.*)/o)
22
22
  @name = match[1]
23
- if match[2].match(/#{FilterSeparator}\s*(.*)/)
23
+ if match[2].match(/#{FilterSeparator}\s*(.*)/o)
24
24
  filters = Regexp.last_match(1).scan(FilterParser)
25
25
  filters.each do |f|
26
26
  if matches = f.match(/\s*(\w+)/)
27
27
  filtername = matches[1]
28
- filterargs = f.scan(/(?:#{FilterArgumentSeparator}|#{ArgumentSeparator})\s*(#{QuotedFragment})/).flatten
28
+ filterargs = f.scan(/(?:#{FilterArgumentSeparator}|#{ArgumentSeparator})\s*(#{QuotedFragment})/o).flatten
29
29
  @filters << [filtername.to_sym, filterargs]
30
30
  end
31
31
  end
@@ -12,4 +12,10 @@ class AssignTest < Test::Unit::TestCase
12
12
  '{% assign foo = values %}.{{ foo[1] }}.',
13
13
  'values' => %w{foo bar baz})
14
14
  end
15
+
16
+ def test_assign_with_filter
17
+ assert_template_result('.bar.',
18
+ '{% assign foo = values | split: "," %}.{{ foo[1] }}.',
19
+ 'values' => "foo,bar,baz")
20
+ end
15
21
  end # AssignTest
@@ -18,6 +18,11 @@ class ConditionTest < Test::Unit::TestCase
18
18
  assert_evalutes_true '2', '>=', '1'
19
19
  assert_evalutes_true '1', '<=', '2'
20
20
  assert_evalutes_true '1', '<=', '1'
21
+ # negative numbers
22
+ assert_evalutes_true '1', '>', '-1'
23
+ assert_evalutes_true '-1', '<', '1'
24
+ assert_evalutes_true '1.0', '>', '-1.0'
25
+ assert_evalutes_true '-1.0', '<', '1.0'
21
26
  end
22
27
 
23
28
  def test_default_operators_evalute_false
@@ -75,6 +75,12 @@ class FiltersTest < Test::Unit::TestCase
75
75
  assert_equal "bla blub", Variable.new("var | strip_html").render(@context)
76
76
  end
77
77
 
78
+ def test_strip_html_ignore_comments_with_html
79
+ @context['var'] = "<!-- split and some <ul> tag --><b>bla blub</a>"
80
+
81
+ assert_equal "bla blub", Variable.new("var | strip_html").render(@context)
82
+ end
83
+
78
84
  def test_capitalize
79
85
  @context['var'] = "blub"
80
86
 
@@ -173,6 +173,10 @@ class StandardFiltersTest < Test::Unit::TestCase
173
173
  assert_template_result "Liquid error: divided by 0", "{{ 5 | divided_by:0 }}"
174
174
  end
175
175
 
176
+ def test_modulo
177
+ assert_template_result "1", "{{ 3 | modulo:2 }}"
178
+ end
179
+
176
180
  def test_append
177
181
  assigns = {'a' => 'bc', 'b' => 'd' }
178
182
  assert_template_result('bcd',"{{ a | append: 'd'}}",assigns)
@@ -0,0 +1,202 @@
1
+ require 'test_helper'
2
+
3
+ class ForTagTest < Test::Unit::TestCase
4
+ include Liquid
5
+
6
+ def test_for
7
+ assert_template_result(' yo yo yo yo ','{%for item in array%} yo {%endfor%}','array' => [1,2,3,4])
8
+ assert_template_result('yoyo','{%for item in array%}yo{%endfor%}','array' => [1,2])
9
+ assert_template_result(' yo ','{%for item in array%} yo {%endfor%}','array' => [1])
10
+ assert_template_result('','{%for item in array%}{%endfor%}','array' => [1,2])
11
+ expected = <<HERE
12
+
13
+ yo
14
+
15
+ yo
16
+
17
+ yo
18
+
19
+ HERE
20
+ template = <<HERE
21
+ {%for item in array%}
22
+ yo
23
+ {%endfor%}
24
+ HERE
25
+ assert_template_result(expected,template,'array' => [1,2,3])
26
+ end
27
+
28
+ def test_for_reversed
29
+ assigns = {'array' => [ 1, 2, 3] }
30
+ assert_template_result('321','{%for item in array reversed %}{{item}}{%endfor%}',assigns)
31
+ end
32
+
33
+ def test_for_with_range
34
+ assert_template_result(' 1 2 3 ','{%for item in (1..3) %} {{item}} {%endfor%}')
35
+ end
36
+
37
+ def test_for_with_variable
38
+ assert_template_result(' 1 2 3 ','{%for item in array%} {{item}} {%endfor%}','array' => [1,2,3])
39
+ assert_template_result('123','{%for item in array%}{{item}}{%endfor%}','array' => [1,2,3])
40
+ assert_template_result('123','{% for item in array %}{{item}}{% endfor %}','array' => [1,2,3])
41
+ assert_template_result('abcd','{%for item in array%}{{item}}{%endfor%}','array' => ['a','b','c','d'])
42
+ assert_template_result('a b c','{%for item in array%}{{item}}{%endfor%}','array' => ['a',' ','b',' ','c'])
43
+ assert_template_result('abc','{%for item in array%}{{item}}{%endfor%}','array' => ['a','','b','','c'])
44
+ end
45
+
46
+ def test_for_helpers
47
+ assigns = {'array' => [1,2,3] }
48
+ assert_template_result(' 1/3 2/3 3/3 ',
49
+ '{%for item in array%} {{forloop.index}}/{{forloop.length}} {%endfor%}',
50
+ assigns)
51
+ assert_template_result(' 1 2 3 ', '{%for item in array%} {{forloop.index}} {%endfor%}', assigns)
52
+ assert_template_result(' 0 1 2 ', '{%for item in array%} {{forloop.index0}} {%endfor%}', assigns)
53
+ assert_template_result(' 2 1 0 ', '{%for item in array%} {{forloop.rindex0}} {%endfor%}', assigns)
54
+ assert_template_result(' 3 2 1 ', '{%for item in array%} {{forloop.rindex}} {%endfor%}', assigns)
55
+ assert_template_result(' true false false ', '{%for item in array%} {{forloop.first}} {%endfor%}', assigns)
56
+ assert_template_result(' false false true ', '{%for item in array%} {{forloop.last}} {%endfor%}', assigns)
57
+ end
58
+
59
+ def test_for_and_if
60
+ assigns = {'array' => [1,2,3] }
61
+ assert_template_result('+--',
62
+ '{%for item in array%}{% if forloop.first %}+{% else %}-{% endif %}{%endfor%}',
63
+ assigns)
64
+ end
65
+
66
+ def test_for_else
67
+ assert_template_result('+++', '{%for item in array%}+{%else%}-{%endfor%}', 'array'=>[1,2,3])
68
+ assert_template_result('-', '{%for item in array%}+{%else%}-{%endfor%}', 'array'=>[])
69
+ assert_template_result('-', '{%for item in array%}+{%else%}-{%endfor%}', 'array'=>nil)
70
+ end
71
+
72
+ def test_limiting
73
+ assigns = {'array' => [1,2,3,4,5,6,7,8,9,0]}
74
+ assert_template_result('12', '{%for i in array limit:2 %}{{ i }}{%endfor%}', assigns)
75
+ assert_template_result('1234', '{%for i in array limit:4 %}{{ i }}{%endfor%}', assigns)
76
+ assert_template_result('3456', '{%for i in array limit:4 offset:2 %}{{ i }}{%endfor%}', assigns)
77
+ assert_template_result('3456', '{%for i in array limit: 4 offset: 2 %}{{ i }}{%endfor%}', assigns)
78
+ end
79
+
80
+ def test_dynamic_variable_limiting
81
+ assigns = {'array' => [1,2,3,4,5,6,7,8,9,0]}
82
+ assigns['limit'] = 2
83
+ assigns['offset'] = 2
84
+
85
+ assert_template_result('34', '{%for i in array limit: limit offset: offset %}{{ i }}{%endfor%}', assigns)
86
+ end
87
+
88
+ def test_nested_for
89
+ assigns = {'array' => [[1,2],[3,4],[5,6]] }
90
+ assert_template_result('123456', '{%for item in array%}{%for i in item%}{{ i }}{%endfor%}{%endfor%}', assigns)
91
+ end
92
+
93
+ def test_offset_only
94
+ assigns = {'array' => [1,2,3,4,5,6,7,8,9,0]}
95
+ assert_template_result('890', '{%for i in array offset:7 %}{{ i }}{%endfor%}', assigns)
96
+ end
97
+
98
+ def test_pause_resume
99
+ assigns = {'array' => {'items' => [1,2,3,4,5,6,7,8,9,0]}}
100
+ markup = <<-MKUP
101
+ {%for i in array.items limit: 3 %}{{i}}{%endfor%}
102
+ next
103
+ {%for i in array.items offset:continue limit: 3 %}{{i}}{%endfor%}
104
+ next
105
+ {%for i in array.items offset:continue limit: 3 %}{{i}}{%endfor%}
106
+ MKUP
107
+ expected = <<-XPCTD
108
+ 123
109
+ next
110
+ 456
111
+ next
112
+ 789
113
+ XPCTD
114
+ assert_template_result(expected,markup,assigns)
115
+ end
116
+
117
+ def test_pause_resume_limit
118
+ assigns = {'array' => {'items' => [1,2,3,4,5,6,7,8,9,0]}}
119
+ markup = <<-MKUP
120
+ {%for i in array.items limit:3 %}{{i}}{%endfor%}
121
+ next
122
+ {%for i in array.items offset:continue limit:3 %}{{i}}{%endfor%}
123
+ next
124
+ {%for i in array.items offset:continue limit:1 %}{{i}}{%endfor%}
125
+ MKUP
126
+ expected = <<-XPCTD
127
+ 123
128
+ next
129
+ 456
130
+ next
131
+ 7
132
+ XPCTD
133
+ assert_template_result(expected,markup,assigns)
134
+ end
135
+
136
+ def test_pause_resume_BIG_limit
137
+ assigns = {'array' => {'items' => [1,2,3,4,5,6,7,8,9,0]}}
138
+ markup = <<-MKUP
139
+ {%for i in array.items limit:3 %}{{i}}{%endfor%}
140
+ next
141
+ {%for i in array.items offset:continue limit:3 %}{{i}}{%endfor%}
142
+ next
143
+ {%for i in array.items offset:continue limit:1000 %}{{i}}{%endfor%}
144
+ MKUP
145
+ expected = <<-XPCTD
146
+ 123
147
+ next
148
+ 456
149
+ next
150
+ 7890
151
+ XPCTD
152
+ assert_template_result(expected,markup,assigns)
153
+ end
154
+
155
+
156
+ def test_pause_resume_BIG_offset
157
+ assigns = {'array' => {'items' => [1,2,3,4,5,6,7,8,9,0]}}
158
+ markup = %q({%for i in array.items limit:3 %}{{i}}{%endfor%}
159
+ next
160
+ {%for i in array.items offset:continue limit:3 %}{{i}}{%endfor%}
161
+ next
162
+ {%for i in array.items offset:continue limit:3 offset:1000 %}{{i}}{%endfor%})
163
+ expected = %q(123
164
+ next
165
+ 456
166
+ next
167
+ )
168
+ assert_template_result(expected,markup,assigns)
169
+ end
170
+
171
+
172
+ def test_for_tag_string
173
+ # ruby 1.8.7 "String".each => Enumerator with single "String" element.
174
+ # ruby 1.9.3 no longer supports .each on String though we mimic
175
+ # the functionality for backwards compatibility
176
+
177
+ assert_template_result('test string',
178
+ '{%for val in string%}{{val}}{%endfor%}',
179
+ 'string' => "test string")
180
+
181
+ assert_template_result('test string',
182
+ '{%for val in string limit:1%}{{val}}{%endfor%}',
183
+ 'string' => "test string")
184
+
185
+ assert_template_result('val-string-1-1-0-1-0-true-true-test string',
186
+ '{%for val in string%}' +
187
+ '{{forloop.name}}-' +
188
+ '{{forloop.index}}-' +
189
+ '{{forloop.length}}-' +
190
+ '{{forloop.index0}}-' +
191
+ '{{forloop.rindex}}-' +
192
+ '{{forloop.rindex0}}-' +
193
+ '{{forloop.first}}-' +
194
+ '{{forloop.last}}-' +
195
+ '{{val}}{%endfor%}',
196
+ 'string' => "test string")
197
+ end
198
+
199
+ def test_blank_string_not_iterable
200
+ assert_template_result('', "{% for char in characters %}I WILL NOT BE OUTPUT{% endfor %}", 'characters' => '')
201
+ end
202
+ end
@@ -3,6 +3,18 @@ require 'test_helper'
3
3
  class HtmlTagTest < Test::Unit::TestCase
4
4
  include Liquid
5
5
 
6
+ class ArrayDrop < Liquid::Drop
7
+ include Enumerable
8
+
9
+ def initialize(array)
10
+ @array = array
11
+ end
12
+
13
+ def each(&block)
14
+ @array.each(&block)
15
+ end
16
+ end
17
+
6
18
  def test_html_table
7
19
 
8
20
  assert_template_result("<tr class=\"row1\">\n<td class=\"col1\"> 1 </td><td class=\"col2\"> 2 </td><td class=\"col3\"> 3 </td></tr>\n<tr class=\"row2\"><td class=\"col1\"> 4 </td><td class=\"col2\"> 5 </td><td class=\"col3\"> 6 </td></tr>\n",
@@ -26,4 +38,26 @@ class HtmlTagTest < Test::Unit::TestCase
26
38
  '{% tablerow n in numbers cols:2%}{{tablerowloop.col}}{% endtablerow %}',
27
39
  'numbers' => [1,2,3,4,5,6])
28
40
  end
41
+
42
+ def test_quoted_fragment
43
+ assert_template_result("<tr class=\"row1\">\n<td class=\"col1\"> 1 </td><td class=\"col2\"> 2 </td><td class=\"col3\"> 3 </td></tr>\n<tr class=\"row2\"><td class=\"col1\"> 4 </td><td class=\"col2\"> 5 </td><td class=\"col3\"> 6 </td></tr>\n",
44
+ "{% tablerow n in collections.frontpage cols:3%} {{n}} {% endtablerow %}",
45
+ 'collections' => {'frontpage' => [1,2,3,4,5,6]})
46
+ assert_template_result("<tr class=\"row1\">\n<td class=\"col1\"> 1 </td><td class=\"col2\"> 2 </td><td class=\"col3\"> 3 </td></tr>\n<tr class=\"row2\"><td class=\"col1\"> 4 </td><td class=\"col2\"> 5 </td><td class=\"col3\"> 6 </td></tr>\n",
47
+ "{% tablerow n in collections['frontpage'] cols:3%} {{n}} {% endtablerow %}",
48
+ 'collections' => {'frontpage' => [1,2,3,4,5,6]})
49
+
50
+ end
51
+
52
+ def test_enumerable_drop
53
+ assert_template_result("<tr class=\"row1\">\n<td class=\"col1\"> 1 </td><td class=\"col2\"> 2 </td><td class=\"col3\"> 3 </td></tr>\n<tr class=\"row2\"><td class=\"col1\"> 4 </td><td class=\"col2\"> 5 </td><td class=\"col3\"> 6 </td></tr>\n",
54
+ '{% tablerow n in numbers cols:3%} {{n}} {% endtablerow %}',
55
+ 'numbers' => ArrayDrop.new([1,2,3,4,5,6]))
56
+ end
57
+
58
+ def test_offset_and_limit
59
+ assert_template_result("<tr class=\"row1\">\n<td class=\"col1\"> 1 </td><td class=\"col2\"> 2 </td><td class=\"col3\"> 3 </td></tr>\n<tr class=\"row2\"><td class=\"col1\"> 4 </td><td class=\"col2\"> 5 </td><td class=\"col3\"> 6 </td></tr>\n",
60
+ '{% tablerow n in numbers cols:3 offset:1 limit:6%} {{n}} {% endtablerow %}',
61
+ 'numbers' => [0,1,2,3,4,5,6,7])
62
+ end
29
63
  end # HtmlTagTest
@@ -47,166 +47,6 @@ class StandardTagTest < Test::Unit::TestCase
47
47
  {%endcomment%}bar')
48
48
  end
49
49
 
50
- def test_for
51
- assert_template_result(' yo yo yo yo ','{%for item in array%} yo {%endfor%}','array' => [1,2,3,4])
52
- assert_template_result('yoyo','{%for item in array%}yo{%endfor%}','array' => [1,2])
53
- assert_template_result(' yo ','{%for item in array%} yo {%endfor%}','array' => [1])
54
- assert_template_result('','{%for item in array%}{%endfor%}','array' => [1,2])
55
- expected = <<HERE
56
-
57
- yo
58
-
59
- yo
60
-
61
- yo
62
-
63
- HERE
64
- template = <<HERE
65
- {%for item in array%}
66
- yo
67
- {%endfor%}
68
- HERE
69
- assert_template_result(expected,template,'array' => [1,2,3])
70
- end
71
-
72
- def test_for_with_range
73
- assert_template_result(' 1 2 3 ','{%for item in (1..3) %} {{item}} {%endfor%}')
74
- end
75
-
76
- def test_for_with_variable
77
- assert_template_result(' 1 2 3 ','{%for item in array%} {{item}} {%endfor%}','array' => [1,2,3])
78
- assert_template_result('123','{%for item in array%}{{item}}{%endfor%}','array' => [1,2,3])
79
- assert_template_result('123','{% for item in array %}{{item}}{% endfor %}','array' => [1,2,3])
80
- assert_template_result('abcd','{%for item in array%}{{item}}{%endfor%}','array' => ['a','b','c','d'])
81
- assert_template_result('a b c','{%for item in array%}{{item}}{%endfor%}','array' => ['a',' ','b',' ','c'])
82
- assert_template_result('abc','{%for item in array%}{{item}}{%endfor%}','array' => ['a','','b','','c'])
83
- end
84
-
85
- def test_for_helpers
86
- assigns = {'array' => [1,2,3] }
87
- assert_template_result(' 1/3 2/3 3/3 ',
88
- '{%for item in array%} {{forloop.index}}/{{forloop.length}} {%endfor%}',
89
- assigns)
90
- assert_template_result(' 1 2 3 ', '{%for item in array%} {{forloop.index}} {%endfor%}', assigns)
91
- assert_template_result(' 0 1 2 ', '{%for item in array%} {{forloop.index0}} {%endfor%}', assigns)
92
- assert_template_result(' 2 1 0 ', '{%for item in array%} {{forloop.rindex0}} {%endfor%}', assigns)
93
- assert_template_result(' 3 2 1 ', '{%for item in array%} {{forloop.rindex}} {%endfor%}', assigns)
94
- assert_template_result(' true false false ', '{%for item in array%} {{forloop.first}} {%endfor%}', assigns)
95
- assert_template_result(' false false true ', '{%for item in array%} {{forloop.last}} {%endfor%}', assigns)
96
- end
97
-
98
- def test_for_and_if
99
- assigns = {'array' => [1,2,3] }
100
- assert_template_result('+--',
101
- '{%for item in array%}{% if forloop.first %}+{% else %}-{% endif %}{%endfor%}',
102
- assigns)
103
- end
104
-
105
- def test_for_else
106
- assert_template_result('+++', '{%for item in array%}+{%else%}-{%endfor%}', 'array'=>[1,2,3])
107
- assert_template_result('-', '{%for item in array%}+{%else%}-{%endfor%}', 'array'=>[])
108
- assert_template_result('-', '{%for item in array%}+{%else%}-{%endfor%}', 'array'=>nil)
109
- end
110
-
111
- def test_limiting
112
- assigns = {'array' => [1,2,3,4,5,6,7,8,9,0]}
113
- assert_template_result('12', '{%for i in array limit:2 %}{{ i }}{%endfor%}', assigns)
114
- assert_template_result('1234', '{%for i in array limit:4 %}{{ i }}{%endfor%}', assigns)
115
- assert_template_result('3456', '{%for i in array limit:4 offset:2 %}{{ i }}{%endfor%}', assigns)
116
- assert_template_result('3456', '{%for i in array limit: 4 offset: 2 %}{{ i }}{%endfor%}', assigns)
117
- end
118
-
119
- def test_dynamic_variable_limiting
120
- assigns = {'array' => [1,2,3,4,5,6,7,8,9,0]}
121
- assigns['limit'] = 2
122
- assigns['offset'] = 2
123
-
124
- assert_template_result('34', '{%for i in array limit: limit offset: offset %}{{ i }}{%endfor%}', assigns)
125
- end
126
-
127
- def test_nested_for
128
- assigns = {'array' => [[1,2],[3,4],[5,6]] }
129
- assert_template_result('123456', '{%for item in array%}{%for i in item%}{{ i }}{%endfor%}{%endfor%}', assigns)
130
- end
131
-
132
- def test_offset_only
133
- assigns = {'array' => [1,2,3,4,5,6,7,8,9,0]}
134
- assert_template_result('890', '{%for i in array offset:7 %}{{ i }}{%endfor%}', assigns)
135
- end
136
-
137
- def test_pause_resume
138
- assigns = {'array' => {'items' => [1,2,3,4,5,6,7,8,9,0]}}
139
- markup = <<-MKUP
140
- {%for i in array.items limit: 3 %}{{i}}{%endfor%}
141
- next
142
- {%for i in array.items offset:continue limit: 3 %}{{i}}{%endfor%}
143
- next
144
- {%for i in array.items offset:continue limit: 3 %}{{i}}{%endfor%}
145
- MKUP
146
- expected = <<-XPCTD
147
- 123
148
- next
149
- 456
150
- next
151
- 789
152
- XPCTD
153
- assert_template_result(expected,markup,assigns)
154
- end
155
-
156
- def test_pause_resume_limit
157
- assigns = {'array' => {'items' => [1,2,3,4,5,6,7,8,9,0]}}
158
- markup = <<-MKUP
159
- {%for i in array.items limit:3 %}{{i}}{%endfor%}
160
- next
161
- {%for i in array.items offset:continue limit:3 %}{{i}}{%endfor%}
162
- next
163
- {%for i in array.items offset:continue limit:1 %}{{i}}{%endfor%}
164
- MKUP
165
- expected = <<-XPCTD
166
- 123
167
- next
168
- 456
169
- next
170
- 7
171
- XPCTD
172
- assert_template_result(expected,markup,assigns)
173
- end
174
-
175
- def test_pause_resume_BIG_limit
176
- assigns = {'array' => {'items' => [1,2,3,4,5,6,7,8,9,0]}}
177
- markup = <<-MKUP
178
- {%for i in array.items limit:3 %}{{i}}{%endfor%}
179
- next
180
- {%for i in array.items offset:continue limit:3 %}{{i}}{%endfor%}
181
- next
182
- {%for i in array.items offset:continue limit:1000 %}{{i}}{%endfor%}
183
- MKUP
184
- expected = <<-XPCTD
185
- 123
186
- next
187
- 456
188
- next
189
- 7890
190
- XPCTD
191
- assert_template_result(expected,markup,assigns)
192
- end
193
-
194
-
195
- def test_pause_resume_BIG_offset
196
- assigns = {'array' => {'items' => [1,2,3,4,5,6,7,8,9,0]}}
197
- markup = %q({%for i in array.items limit:3 %}{{i}}{%endfor%}
198
- next
199
- {%for i in array.items offset:continue limit:3 %}{{i}}{%endfor%}
200
- next
201
- {%for i in array.items offset:continue limit:3 offset:1000 %}{{i}}{%endfor%})
202
- expected = %q(123
203
- next
204
- 456
205
- next
206
- )
207
- assert_template_result(expected,markup,assigns)
208
- end
209
-
210
50
  def test_assign
211
51
  assigns = {'var' => 'content' }
212
52
  assert_template_result('var2: var2:content', 'var2:{{var2}} {%assign var2 = var%} var2:{{var2}}', assigns)
@@ -445,12 +285,6 @@ HERE
445
285
  assert_template_result('', '{% if null == true %}?{% endif %}', {})
446
286
  end
447
287
 
448
- def test_for_reversed
449
- assigns = {'array' => [ 1, 2, 3] }
450
- assert_template_result('321','{%for item in array reversed %}{{item}}{%endfor%}',assigns)
451
- end
452
-
453
-
454
288
  def test_ifchanged
455
289
  assigns = {'array' => [ 1, 1, 2, 2, 3, 3] }
456
290
  assert_template_result('123','{%for item in array%}{%ifchanged%}{{item}}{% endifchanged %}{%endfor%}',assigns)
metadata CHANGED
@@ -1,34 +1,25 @@
1
- --- !ruby/object:Gem::Specification
1
+ --- !ruby/object:Gem::Specification
2
2
  name: liquid
3
- version: !ruby/object:Gem::Version
4
- hash: 3
3
+ version: !ruby/object:Gem::Version
4
+ version: 2.4.0
5
5
  prerelease:
6
- segments:
7
- - 2
8
- - 3
9
- - 0
10
- version: 2.3.0
11
6
  platform: ruby
12
- authors:
7
+ authors:
13
8
  - Tobias Luetke
14
9
  autorequire:
15
10
  bindir: bin
16
11
  cert_chain: []
17
-
18
- date: 2011-10-16 00:00:00 Z
12
+ date: 2012-08-03 00:00:00.000000000 Z
19
13
  dependencies: []
20
-
21
14
  description:
22
- email:
15
+ email:
23
16
  - tobi@leetsoft.com
24
17
  executables: []
25
-
26
18
  extensions: []
27
-
28
- extra_rdoc_files:
19
+ extra_rdoc_files:
29
20
  - History.md
30
21
  - README.md
31
- files:
22
+ files:
32
23
  - lib/extras/liquid_view.rb
33
24
  - lib/liquid/block.rb
34
25
  - lib/liquid/condition.rb
@@ -57,6 +48,7 @@ files:
57
48
  - lib/liquid/tags/raw.rb
58
49
  - lib/liquid/tags/unless.rb
59
50
  - lib/liquid/template.rb
51
+ - lib/liquid/utils.rb
60
52
  - lib/liquid/variable.rb
61
53
  - lib/liquid.rb
62
54
  - MIT-LICENSE
@@ -77,6 +69,7 @@ files:
77
69
  - test/liquid/security_test.rb
78
70
  - test/liquid/standard_filter_test.rb
79
71
  - test/liquid/strainer_test.rb
72
+ - test/liquid/tags/for_tag_test.rb
80
73
  - test/liquid/tags/html_tag_test.rb
81
74
  - test/liquid/tags/if_else_tag_test.rb
82
75
  - test/liquid/tags/include_tag_test.rb
@@ -91,40 +84,29 @@ files:
91
84
  - History.md
92
85
  homepage: http://www.liquidmarkup.org
93
86
  licenses: []
94
-
95
87
  post_install_message:
96
88
  rdoc_options: []
97
-
98
- require_paths:
89
+ require_paths:
99
90
  - lib
100
- required_ruby_version: !ruby/object:Gem::Requirement
91
+ required_ruby_version: !ruby/object:Gem::Requirement
101
92
  none: false
102
- requirements:
103
- - - ">="
104
- - !ruby/object:Gem::Version
105
- hash: 3
106
- segments:
107
- - 0
108
- version: "0"
109
- required_rubygems_version: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ! '>='
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ required_rubygems_version: !ruby/object:Gem::Requirement
110
98
  none: false
111
- requirements:
112
- - - ">="
113
- - !ruby/object:Gem::Version
114
- hash: 21
115
- segments:
116
- - 1
117
- - 3
118
- - 7
99
+ requirements:
100
+ - - ! '>='
101
+ - !ruby/object:Gem::Version
119
102
  version: 1.3.7
120
103
  requirements: []
121
-
122
104
  rubyforge_project:
123
105
  rubygems_version: 1.8.11
124
106
  signing_key:
125
107
  specification_version: 3
126
108
  summary: A secure, non-evaling end user template engine with aesthetic markup.
127
- test_files:
109
+ test_files:
128
110
  - test/liquid/assign_test.rb
129
111
  - test/liquid/block_test.rb
130
112
  - test/liquid/capture_test.rb
@@ -141,6 +123,7 @@ test_files:
141
123
  - test/liquid/security_test.rb
142
124
  - test/liquid/standard_filter_test.rb
143
125
  - test/liquid/strainer_test.rb
126
+ - test/liquid/tags/for_tag_test.rb
144
127
  - test/liquid/tags/html_tag_test.rb
145
128
  - test/liquid/tags/if_else_tag_test.rb
146
129
  - test/liquid/tags/include_tag_test.rb