locomotive_liquid 2.2.3 → 2.4.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -14,7 +14,7 @@ module Liquid
14
14
  # This will parse the template with a LocalFileSystem implementation rooted at 'template_path'.
15
15
  class BlankFileSystem
16
16
  # Called by Liquid to retrieve a template file
17
- def read_template_file(template_path)
17
+ def read_template_file(template_path, context)
18
18
  raise FileSystemError, "This liquid context does not allow includes."
19
19
  end
20
20
  end
@@ -38,7 +38,7 @@ module Liquid
38
38
  @root = root
39
39
  end
40
40
 
41
- def read_template_file(template_path)
41
+ def read_template_file(template_path, context)
42
42
  full_path = full_path(template_path)
43
43
  raise FileSystemError, "No such template '#{template_path}'" unless File.exists?(full_path)
44
44
 
@@ -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, context)
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
 
@@ -33,7 +32,7 @@ module Liquid
33
32
  row = 1
34
33
  col = 0
35
34
 
36
- result = ["<tr class=\"row1\">\n"]
35
+ result = "<tr class=\"row1\">\n"
37
36
  context.stack do
38
37
 
39
38
  collection.each_with_index do |item, index|
@@ -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),
@@ -56,17 +55,18 @@ module Liquid
56
55
 
57
56
  col += 1
58
57
 
59
- result << ["<td class=\"col#{col}\">"] + render_all(@nodelist, context) + ['</td>']
58
+ result << "<td class=\"col#{col}\">" << render_all(@nodelist, context) << '</td>'
60
59
 
61
60
  if col == cols and not (index == length - 1)
62
61
  col = 0
63
62
  row += 1
64
- result << ["</tr>\n<tr class=\"row#{row}\">"]
63
+ result << "</tr>\n<tr class=\"row#{row}\">"
65
64
  end
66
65
 
67
66
  end
68
67
  end
69
- result + ["</tr>\n"]
68
+ result << "</tr>\n"
69
+ result
70
70
  end
71
71
  end
72
72
 
@@ -0,0 +1,17 @@
1
+ module Liquid
2
+
3
+ # An interrupt is any command that breaks processing of a block (ex: a for loop).
4
+ class Interrupt
5
+ attr_reader :message
6
+
7
+ def initialize(message=nil)
8
+ @message = message || "interrupt"
9
+ end
10
+ end
11
+
12
+ # Interrupt that is thrown whenever a {% break %} is called.
13
+ class BreakInterrupt < Interrupt; end
14
+
15
+ # Interrupt that is thrown whenever a {% continue %} is called.
16
+ class ContinueInterrupt < Interrupt; end
17
+ end
@@ -29,6 +29,12 @@ module Liquid
29
29
  CGI.escapeHTML(input) rescue input
30
30
  end
31
31
 
32
+ def escape_once(input)
33
+ ActionView::Helpers::TagHelper.escape_once(input)
34
+ rescue NameError
35
+ input
36
+ end
37
+
32
38
  alias_method :h, :escape
33
39
 
34
40
  # Truncate a string down to x characters
@@ -47,8 +53,17 @@ module Liquid
47
53
  wordlist.length > l ? wordlist[0..l].join(" ") + truncate_string : input
48
54
  end
49
55
 
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
+ #
61
+ def split(input, pattern)
62
+ input.split(pattern)
63
+ end
64
+
50
65
  def strip_html(input)
51
- input.to_s.gsub(/<script.*?<\/script>/, '').gsub(/<.*?>/, '')
66
+ input.to_s.gsub(/<script.*?<\/script>/, '').gsub(/<!--.*?-->/, '').gsub(/<.*?>/, '')
52
67
  end
53
68
 
54
69
  # Remove all newlines from the string
@@ -158,6 +173,10 @@ module Liquid
158
173
  return input.to_s
159
174
  end
160
175
 
176
+ if ((input.is_a?(String) && !/^\d+$/.match(input.to_s).nil?) || input.is_a?(Integer)) && input.to_i > 0
177
+ input = Time.at(input.to_i)
178
+ end
179
+
161
180
  date = input.is_a?(String) ? Time.parse(input) : input
162
181
 
163
182
  if date.respond_to?(:strftime)
@@ -207,18 +226,22 @@ module Liquid
207
226
  to_number(input) / to_number(operand)
208
227
  end
209
228
 
229
+ def modulo(input, operand)
230
+ to_number(input) % to_number(operand)
231
+ end
232
+
210
233
  private
211
234
 
212
- def to_number(obj)
213
- case obj
214
- when Numeric
215
- obj
216
- when String
217
- (obj.strip =~ /^\d+\.\d+$/) ? obj.to_f : obj.to_i
218
- else
219
- 0
235
+ def to_number(obj)
236
+ case obj
237
+ when Numeric
238
+ obj
239
+ when String
240
+ (obj.strip =~ /^\d+\.\d+$/) ? obj.to_f : obj.to_i
241
+ else
242
+ 0
243
+ end
220
244
  end
221
- end
222
245
 
223
246
  end
224
247
 
@@ -14,7 +14,10 @@ module Liquid
14
14
  # One of the strainer's responsibilities is to keep malicious method calls out
15
15
  class Strainer < parent_object #:nodoc:
16
16
  INTERNAL_METHOD = /^__/
17
- @@required_methods = Set.new([:__id__, :__send__, :respond_to?, :extend, :methods, :class, :object_id])
17
+ @@required_methods = Set.new([:__id__, :__send__, :respond_to?, :kind_of?, :extend, :methods, :singleton_methods, :class, :object_id])
18
+
19
+ # Ruby 1.9.2 introduces Object#respond_to_missing?, which is invoked by Object#respond_to?
20
+ @@required_methods << :respond_to_missing? if Object.respond_to? :respond_to_missing?
18
21
 
19
22
  # Ruby 1.9.2 introduces Object#respond_to_missing?, which is invoked by Object#respond_to?
20
23
  @@required_methods << :respond_to_missing? if Object.respond_to? :respond_to_missing?
@@ -1,6 +1,7 @@
1
1
  module Liquid
2
2
 
3
3
  class Tag
4
+
4
5
  attr_accessor :nodelist, :context
5
6
 
6
7
  def initialize(tag_name, markup, tokens, context)
@@ -20,8 +21,7 @@ module Liquid
20
21
  def render(context)
21
22
  ''
22
23
  end
23
- end
24
-
25
24
 
26
- end
25
+ end # Tag
27
26
 
27
+ end # Tag
@@ -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, context)
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
 
@@ -0,0 +1,21 @@
1
+ module Liquid
2
+
3
+ # Break tag to be used to break out of a for loop.
4
+ #
5
+ # == Basic Usage:
6
+ # {% for item in collection %}
7
+ # {% if item.condition %}
8
+ # {% break %}
9
+ # {% endif %}
10
+ # {% endfor %}
11
+ #
12
+ class Break < Tag
13
+
14
+ def interrupt
15
+ BreakInterrupt.new
16
+ end
17
+
18
+ end
19
+
20
+ Template.register_tag('break', Break)
21
+ end
@@ -26,7 +26,7 @@ module Liquid
26
26
 
27
27
  def render(context)
28
28
  output = super
29
- context.scopes.last[@to] = output.join
29
+ context.scopes.last[@to] = output
30
30
  ''
31
31
  end
32
32
  end
@@ -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, context)
7
7
  @blocks = []
@@ -31,20 +31,16 @@ module Liquid
31
31
  context.stack do
32
32
  execute_else_block = true
33
33
 
34
- @blocks.inject([]) do |output, block|
35
-
34
+ output = ''
35
+ @blocks.each do |block|
36
36
  if block.else?
37
-
38
37
  return render_all(block.attachment, context) if execute_else_block
39
-
40
38
  elsif block.evaluate(context)
41
-
42
39
  execute_else_block = false
43
- output += render_all(block.attachment, context)
40
+ output << render_all(block.attachment, context)
44
41
  end
45
-
46
- output
47
42
  end
43
+ output
48
44
  end
49
45
  end
50
46
 
@@ -6,4 +6,4 @@ module Liquid
6
6
  end
7
7
 
8
8
  Template.register_tag('comment', Comment)
9
- end
9
+ end
@@ -0,0 +1,21 @@
1
+ module Liquid
2
+
3
+ # Continue tag to be used to break out of a for loop.
4
+ #
5
+ # == Basic Usage:
6
+ # {% for item in collection %}
7
+ # {% if item.condition %}
8
+ # {% continue %}
9
+ # {% endif %}
10
+ # {% endfor %}
11
+ #
12
+ class Continue < Tag
13
+
14
+ def interrupt
15
+ ContinueInterrupt.new
16
+ end
17
+
18
+ end
19
+
20
+ Template.register_tag('continue', Continue)
21
+ end
@@ -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, context)
20
20
  case markup
@@ -48,12 +48,12 @@ module Liquid
48
48
 
49
49
  def variables_from_string(markup)
50
50
  markup.split(',').collect do |var|
51
- var =~ /\s*(#{QuotedFragment})\s*/
52
- $1 ? $1 : nil
53
- end.compact
51
+ var =~ /\s*(#{QuotedFragment})\s*/o
52
+ $1 ? $1 : nil
53
+ end.compact
54
54
  end
55
55
 
56
56
  end
57
57
 
58
58
  Template.register_tag('cycle', Cycle)
59
- end
59
+ end
@@ -0,0 +1,39 @@
1
+ module Liquid
2
+
3
+ # decrement is used in a place where one needs to insert a counter
4
+ # into a template, and needs the counter to survive across
5
+ # multiple instantiations of the template.
6
+ # NOTE: decrement is a pre-decrement, --i,
7
+ # while increment is post: i++.
8
+ #
9
+ # (To achieve the survival, the application must keep the context)
10
+ #
11
+ # if the variable does not exist, it is created with value 0.
12
+
13
+ # Hello: {% decrement variable %}
14
+ #
15
+ # gives you:
16
+ #
17
+ # Hello: -1
18
+ # Hello: -2
19
+ # Hello: -3
20
+ #
21
+ class Decrement < Tag
22
+ def initialize(tag_name, markup, tokens, context)
23
+ @variable = markup.strip
24
+
25
+ super
26
+ end
27
+
28
+ def render(context)
29
+ value = context.environments.first[@variable] ||= 0
30
+ value = value - 1
31
+ context.environments.first[@variable] = value
32
+ value.to_s
33
+ end
34
+
35
+ private
36
+ end
37
+
38
+ Template.register_tag('decrement', Decrement)
39
+ end
@@ -13,6 +13,8 @@ module Liquid
13
13
  # <div {% if forloop.first %}class="first"{% endif %}>
14
14
  # Item {{ forloop.index }}: {{ item.name }}
15
15
  # </div>
16
+ # {% else %}
17
+ # There is nothing in the collection.
16
18
  # {% endfor %}
17
19
  #
18
20
  # You can also define a limit and offset much like SQL. Remember
@@ -42,7 +44,7 @@ module Liquid
42
44
  # forloop.last:: Returns true if the item is the last item.
43
45
  #
44
46
  class For < Block
45
- Syntax = /(\w+)\s+in\s+(#{QuotedFragment}+)\s*(reversed)?/
47
+ Syntax = /(\w+)\s+in\s+(#{QuotedFragment}+)\s*(reversed)?/o
46
48
 
47
49
  def initialize(tag_name, markup, tokens, context)
48
50
  if markup =~ Syntax
@@ -58,16 +60,23 @@ module Liquid
58
60
  raise SyntaxError.new("Syntax Error in 'for loop' - Valid syntax: for [item] in [collection]")
59
61
  end
60
62
 
63
+ @nodelist = @for_block = []
61
64
  super
62
65
  end
63
66
 
67
+ def unknown_tag(tag, markup, tokens)
68
+ return super unless tag == 'else'
69
+ @nodelist = @else_block = []
70
+ end
71
+
64
72
  def render(context)
65
73
  context.registers[:for] ||= Hash.new(0)
66
74
 
67
75
  collection = context[@collection_name]
68
76
  collection = collection.to_a if collection.is_a?(Range)
69
77
 
70
- return '' 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)
71
80
 
72
81
  from = if @attributes['offset'] == 'continue'
73
82
  context.registers[:for][@name].to_i
@@ -79,13 +88,13 @@ module Liquid
79
88
  to = limit ? limit.to_i + from : nil
80
89
 
81
90
 
82
- segment = slice_collection_using_each(collection, from, to)
91
+ segment = Utils.slice_collection_using_each(collection, from, to)
83
92
 
84
- return '' if segment.empty?
93
+ return render_else(context) if segment.empty?
85
94
 
86
95
  segment.reverse! if @reversed
87
96
 
88
- result = []
97
+ result = ''
89
98
 
90
99
  length = segment.length
91
100
 
@@ -101,36 +110,33 @@ module Liquid
101
110
  'index' => index + 1,
102
111
  'index0' => index,
103
112
  'rindex' => length - index,
104
- 'rindex0' => length - index -1,
113
+ 'rindex0' => length - index - 1,
105
114
  'first' => (index == 0),
106
115
  'last' => (index == length - 1) }
107
116
 
108
- result << render_all(@nodelist, context)
117
+ result << render_all(@for_block, context)
118
+
119
+ # Handle any interrupts if they exist.
120
+ if context.has_interrupt?
121
+ interrupt = context.pop_interrupt
122
+ break if interrupt.is_a? BreakInterrupt
123
+ next if interrupt.is_a? ContinueInterrupt
124
+ end
109
125
  end
110
126
  end
111
127
  result
112
128
  end
113
129
 
114
- def slice_collection_using_each(collection, from, to)
115
- segments = []
116
- index = 0
117
- yielded = 0
118
- collection.each do |item|
119
-
120
- if to && to <= index
121
- break
122
- end
130
+ private
123
131
 
124
- if from <= index
125
- segments << item
126
- end
127
-
128
- index += 1
132
+ def render_else(context)
133
+ return @else_block ? [render_all(@else_block, context)] : ''
129
134
  end
130
135
 
131
- segments
132
- end
136
+ def iterable?(collection)
137
+ collection.respond_to?(:each) || Utils.non_blank_string?(collection)
138
+ end
133
139
  end
134
140
 
135
141
  Template.register_tag('for', For)
136
- end
142
+ end