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.
- data/README.md +26 -20
- data/Rakefile +27 -8
- data/lib/liquid.rb +12 -13
- data/lib/liquid/block.rb +22 -8
- data/lib/liquid/context.rb +47 -33
- data/lib/liquid/drop.rb +16 -16
- data/lib/liquid/errors.rb +1 -1
- data/lib/liquid/extensions.rb +13 -7
- data/lib/liquid/file_system.rb +2 -2
- data/lib/liquid/htmltags.rb +11 -11
- data/lib/liquid/interrupts.rb +17 -0
- data/lib/liquid/standardfilters.rb +33 -10
- data/lib/liquid/strainer.rb +4 -1
- data/lib/liquid/tag.rb +3 -3
- data/lib/liquid/tags/assign.rb +3 -3
- data/lib/liquid/tags/break.rb +21 -0
- data/lib/liquid/tags/capture.rb +1 -1
- data/lib/liquid/tags/case.rb +6 -10
- data/lib/liquid/tags/comment.rb +1 -1
- data/lib/liquid/tags/continue.rb +21 -0
- data/lib/liquid/tags/cycle.rb +6 -6
- data/lib/liquid/tags/decrement.rb +39 -0
- data/lib/liquid/tags/for.rb +30 -24
- data/lib/liquid/tags/if.rb +21 -23
- data/lib/liquid/tags/include.rb +18 -9
- data/lib/liquid/tags/increment.rb +35 -0
- data/lib/liquid/tags/raw.rb +1 -1
- data/lib/liquid/tags/unless.rb +0 -1
- data/lib/liquid/template.rb +3 -2
- data/lib/liquid/utils.rb +31 -0
- data/lib/liquid/variable.rb +5 -5
- metadata +11 -7
- data/CHANGELOG +0 -46
- data/lib/liquid/tags/literal.rb +0 -42
data/lib/liquid/file_system.rb
CHANGED
@@ -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
|
|
data/lib/liquid/htmltags.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
module Liquid
|
2
2
|
class TableRow < Block
|
3
|
-
Syntax = /(\w+)\s+in\s+(#{
|
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
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
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 =
|
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 <<
|
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 <<
|
63
|
+
result << "</tr>\n<tr class=\"row#{row}\">"
|
65
64
|
end
|
66
65
|
|
67
66
|
end
|
68
67
|
end
|
69
|
-
result
|
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
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
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
|
|
data/lib/liquid/strainer.rb
CHANGED
@@ -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?
|
data/lib/liquid/tag.rb
CHANGED
data/lib/liquid/tags/assign.rb
CHANGED
@@ -9,12 +9,12 @@ module Liquid
|
|
9
9
|
# {{ foo }}
|
10
10
|
#
|
11
11
|
class Assign < Tag
|
12
|
-
Syntax = /(#{VariableSignature}+)\s*=\s*(
|
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] =
|
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
|
data/lib/liquid/tags/capture.rb
CHANGED
data/lib/liquid/tags/case.rb
CHANGED
@@ -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
|
-
|
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
|
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
|
|
data/lib/liquid/tags/comment.rb
CHANGED
@@ -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
|
data/lib/liquid/tags/cycle.rb
CHANGED
@@ -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
|
-
|
52
|
-
|
53
|
-
|
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
|
data/lib/liquid/tags/for.rb
CHANGED
@@ -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
|
-
|
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
|
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(@
|
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
|
-
|
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
|
-
|
125
|
-
|
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
|
-
|
132
|
-
|
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
|