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/README.md
CHANGED
@@ -2,38 +2,44 @@
|
|
2
2
|
|
3
3
|
## Introduction
|
4
4
|
|
5
|
-
Liquid is a template engine which
|
5
|
+
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
|
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
|
-
## Why should
|
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
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
{{product.description | prettyprint | paragraph }}
|
28
|
-
</li>
|
29
|
-
{% endfor %}
|
30
|
-
</ul>
|
21
|
+
```html
|
22
|
+
<ul id="products">
|
23
|
+
{% for product in products %}
|
24
|
+
<li>
|
25
|
+
<h2>{{ product.name }}</h2>
|
26
|
+
Only {{ product.price | price }}
|
31
27
|
|
32
|
-
|
28
|
+
{{ product.description | prettyprint | paragraph }}
|
29
|
+
</li>
|
30
|
+
{% endfor %}
|
31
|
+
</ul>
|
32
|
+
```
|
33
|
+
|
34
|
+
## How to use Liquid
|
35
|
+
>>>>>>> upstream/master
|
33
36
|
|
34
37
|
Liquid supports a very simple API based around the Liquid::Template class.
|
35
38
|
For standard use you can just pass it the content of a file and call render with a parameters hash.
|
36
39
|
|
40
|
+
```ruby
|
41
|
+
@template = Liquid::Template.parse("hi {{name}}") # Parses and compiles the template
|
42
|
+
@template.render('name' => 'tobi') # => "hi tobi"
|
43
|
+
```
|
37
44
|
|
38
|
-
|
39
|
-
@template.render( 'name' => 'tobi' ) # => "hi tobi"
|
45
|
+
[![Build Status](https://secure.travis-ci.org/Shopify/liquid.png)](http://travis-ci.org/Shopify/liquid)
|
data/Rakefile
CHANGED
@@ -1,8 +1,10 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
|
+
|
2
3
|
require 'rubygems'
|
3
4
|
require 'bundler/setup'
|
4
5
|
|
5
6
|
require 'rake'
|
7
|
+
require 'rake/testtask'
|
6
8
|
require 'rspec'
|
7
9
|
require 'rspec/core/rake_task'
|
8
10
|
require 'rubygems/package_task'
|
@@ -27,14 +29,21 @@ RSpec::Core::RakeTask.new('spec:progress') do |spec|
|
|
27
29
|
spec.pattern = "spec/**/*_spec.rb"
|
28
30
|
end
|
29
31
|
|
30
|
-
|
32
|
+
Rake::TestTask.new(:test) do |t|
|
33
|
+
t.libs << '.' << 'lib' << 'test'
|
34
|
+
t.test_files = FileList['test/liquid/**/*_test.rb']
|
35
|
+
t.verbose = false
|
36
|
+
end
|
37
|
+
|
38
|
+
task :default => [:spec, :test]
|
31
39
|
|
32
40
|
gemspec = eval(File.read('locomotive_liquid.gemspec'))
|
41
|
+
|
33
42
|
Gem::PackageTask.new(gemspec) do |pkg|
|
34
43
|
pkg.gem_spec = gemspec
|
35
44
|
end
|
36
45
|
|
37
|
-
desc "
|
46
|
+
desc "Build the gem and release it to rubygems.org"
|
38
47
|
task :release => :gem do
|
39
48
|
puts "Tagging #{gemspec.version}..."
|
40
49
|
system "git tag -a #{gemspec.version} -m 'Tagging #{gemspec.version}'"
|
@@ -44,20 +53,30 @@ task :release => :gem do
|
|
44
53
|
system "gem push pkg/#{gemspec.name}-#{gemspec.version}.gem"
|
45
54
|
end
|
46
55
|
|
47
|
-
namespace :
|
48
|
-
|
49
|
-
task :default => [:run]
|
56
|
+
namespace :benchmark do
|
50
57
|
|
51
|
-
desc "Run the liquid
|
58
|
+
desc "Run the liquid benchmark"
|
52
59
|
task :run do
|
60
|
+
ruby "./performance/benchmark.rb"
|
61
|
+
end
|
53
62
|
|
54
|
-
|
63
|
+
end
|
64
|
+
|
65
|
+
namespace :profile do
|
55
66
|
|
67
|
+
desc "Run the liquid profile/performance coverage"
|
68
|
+
task :run do
|
69
|
+
ruby "./performance/profile.rb"
|
56
70
|
end
|
57
71
|
|
58
72
|
desc "Run KCacheGrind"
|
59
73
|
task :grind => :run do
|
60
|
-
system "
|
74
|
+
system "qcachegrind /tmp/liquid.rubyprof_calltreeprinter.txt"
|
61
75
|
end
|
76
|
+
|
62
77
|
end
|
63
78
|
|
79
|
+
desc "Run example"
|
80
|
+
task :example do
|
81
|
+
ruby "-w -d -Ilib example/server/server.rb"
|
82
|
+
end
|
data/lib/liquid.rb
CHANGED
@@ -19,8 +19,6 @@
|
|
19
19
|
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
20
|
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
21
21
|
|
22
|
-
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
23
|
-
|
24
22
|
module Liquid
|
25
23
|
FilterSeparator = /\|/
|
26
24
|
ArgumentSeparator = ','
|
@@ -34,23 +32,23 @@ module Liquid
|
|
34
32
|
VariableEnd = /\}\}/
|
35
33
|
VariableIncompleteEnd = /\}\}?/
|
36
34
|
QuotedString = /"[^"]*"|'[^']*'/
|
37
|
-
QuotedFragment = /#{QuotedString}|(?:[^\s,\|'"]|#{QuotedString})+/
|
38
|
-
StrictQuotedFragment = /"[^"]+"|'[^']+'|[^\s
|
39
|
-
FirstFilterArgument = /#{FilterArgumentSeparator}(?:#{StrictQuotedFragment})/
|
40
|
-
OtherFilterArgument = /#{ArgumentSeparator}(?:#{StrictQuotedFragment})/
|
41
|
-
SpacelessFilter = /^(?:'[^']+'|"[^"]+"|[^'"])*#{FilterSeparator}(?:#{StrictQuotedFragment})(?:#{FirstFilterArgument}(?:#{OtherFilterArgument})*)?/
|
42
|
-
Expression = /(?:#{QuotedFragment}(?:#{SpacelessFilter})*)/
|
43
|
-
TagAttributes = /(\w+)\s*\:\s*(#{QuotedFragment})/
|
35
|
+
QuotedFragment = /#{QuotedString}|(?:[^\s,\|'"]|#{QuotedString})+/o
|
36
|
+
StrictQuotedFragment = /"[^"]+"|'[^']+'|[^\s|:,]+/
|
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
|
44
42
|
AnyStartingTag = /\{\{|\{\%/
|
45
|
-
PartialTemplateParser = /#{TagStart}.*?#{TagEnd}|#{VariableStart}.*?#{VariableIncompleteEnd}/
|
46
|
-
TemplateParser = /(#{PartialTemplateParser}|#{AnyStartingTag})/
|
47
|
-
VariableParser = /\[[^\]]+\]|#{VariableSegment}+\??/
|
48
|
-
LiteralShorthand = /^(?:\{\{\{\s?)(.*?)(?:\s*\}\}\})$/
|
43
|
+
PartialTemplateParser = /#{TagStart}.*?#{TagEnd}|#{VariableStart}.*?#{VariableIncompleteEnd}/o
|
44
|
+
TemplateParser = /(#{PartialTemplateParser}|#{AnyStartingTag})/o
|
45
|
+
VariableParser = /\[[^\]]+\]|#{VariableSegment}+\??/o
|
49
46
|
end
|
50
47
|
|
51
48
|
require 'liquid/drop'
|
52
49
|
require 'liquid/extensions'
|
53
50
|
require 'liquid/errors'
|
51
|
+
require 'liquid/interrupts'
|
54
52
|
require 'liquid/strainer'
|
55
53
|
require 'liquid/context'
|
56
54
|
require 'liquid/tag'
|
@@ -63,6 +61,7 @@ require 'liquid/htmltags'
|
|
63
61
|
require 'liquid/standardfilters'
|
64
62
|
require 'liquid/condition'
|
65
63
|
require 'liquid/module_ex'
|
64
|
+
require 'liquid/utils'
|
66
65
|
|
67
66
|
# Load all the tags of the standard library
|
68
67
|
#
|
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 ||= []
|
@@ -89,13 +89,27 @@ module Liquid
|
|
89
89
|
end
|
90
90
|
|
91
91
|
def render_all(list, context)
|
92
|
-
|
92
|
+
output = []
|
93
|
+
list.each do |token|
|
94
|
+
# Break out if we have any unhanded interrupts.
|
95
|
+
break if context.has_interrupt?
|
96
|
+
|
93
97
|
begin
|
94
|
-
|
95
|
-
|
96
|
-
|
98
|
+
# If we get an Interrupt that means the block must stop processing. An
|
99
|
+
# Interrupt is any command that stops block execution such as {% break %}
|
100
|
+
# or {% continue %}
|
101
|
+
if token.is_a? Continue or token.is_a? Break
|
102
|
+
context.push_interrupt(token.interrupt)
|
103
|
+
break
|
104
|
+
end
|
105
|
+
|
106
|
+
output << (token.respond_to?(:render) ? token.render(context) : token)
|
107
|
+
rescue ::StandardError => e
|
108
|
+
output << (context.handle_error(e))
|
97
109
|
end
|
98
110
|
end
|
111
|
+
|
112
|
+
output.join
|
99
113
|
end
|
100
114
|
end
|
101
115
|
end
|
data/lib/liquid/context.rb
CHANGED
@@ -22,6 +22,8 @@ module Liquid
|
|
22
22
|
@errors = []
|
23
23
|
@rethrow_errors = rethrow_errors
|
24
24
|
squash_instance_assigns_with_environments
|
25
|
+
|
26
|
+
@interrupts = []
|
25
27
|
end
|
26
28
|
|
27
29
|
def strainer
|
@@ -41,6 +43,21 @@ module Liquid
|
|
41
43
|
end
|
42
44
|
end
|
43
45
|
|
46
|
+
# are there any not handled interrupts?
|
47
|
+
def has_interrupt?
|
48
|
+
!@interrupts.empty?
|
49
|
+
end
|
50
|
+
|
51
|
+
# push an interrupt to the stack. this interrupt is considered not handled.
|
52
|
+
def push_interrupt(e)
|
53
|
+
@interrupts.push(e)
|
54
|
+
end
|
55
|
+
|
56
|
+
# pop an interrupt from the stack
|
57
|
+
def pop_interrupt
|
58
|
+
@interrupts.pop
|
59
|
+
end
|
60
|
+
|
44
61
|
def handle_error(e)
|
45
62
|
errors.push(e)
|
46
63
|
raise if @rethrow_errors
|
@@ -63,8 +80,8 @@ module Liquid
|
|
63
80
|
|
64
81
|
# Push new local scope on the stack. use <tt>Context#stack</tt> instead
|
65
82
|
def push(new_scope={})
|
66
|
-
raise StackLevelError, "Nesting too deep" if @scopes.length > 100
|
67
83
|
@scopes.unshift(new_scope)
|
84
|
+
raise StackLevelError, "Nesting too deep" if @scopes.length > 100
|
68
85
|
end
|
69
86
|
|
70
87
|
# Merge a hash of variables in the current local scope
|
@@ -86,17 +103,11 @@ module Liquid
|
|
86
103
|
# end
|
87
104
|
#
|
88
105
|
# context['var] #=> nil
|
89
|
-
def stack(new_scope={}
|
90
|
-
result = nil
|
106
|
+
def stack(new_scope={})
|
91
107
|
push(new_scope)
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
ensure
|
96
|
-
pop
|
97
|
-
end
|
98
|
-
|
99
|
-
result
|
108
|
+
yield
|
109
|
+
ensure
|
110
|
+
pop
|
100
111
|
end
|
101
112
|
|
102
113
|
def clear_instance_assigns
|
@@ -117,6 +128,15 @@ module Liquid
|
|
117
128
|
end
|
118
129
|
|
119
130
|
private
|
131
|
+
|
132
|
+
LITERALS = {
|
133
|
+
nil => nil, 'nil' => nil, 'null' => nil, '' => nil,
|
134
|
+
'true' => true,
|
135
|
+
'false' => false,
|
136
|
+
'blank' => :blank?,
|
137
|
+
'empty' => :empty?
|
138
|
+
}
|
139
|
+
|
120
140
|
# Look up variable, either resolve directly after considering the name. We can directly handle
|
121
141
|
# Strings, digits, floats and booleans (true,false).
|
122
142
|
# If no match is made we lookup the variable in the current scope and
|
@@ -126,29 +146,23 @@ module Liquid
|
|
126
146
|
# Example:
|
127
147
|
# products == empty #=> products.empty?
|
128
148
|
def resolve(key)
|
129
|
-
|
130
|
-
|
131
|
-
nil
|
132
|
-
when 'true'
|
133
|
-
true
|
134
|
-
when 'false'
|
135
|
-
false
|
136
|
-
when 'blank'
|
137
|
-
:blank?
|
138
|
-
when 'empty' # Single quoted strings
|
139
|
-
:empty?
|
140
|
-
when /^'(.*)'$/ # Double quoted strings
|
141
|
-
$1.to_s
|
142
|
-
when /^"(.*)"$/ # Integer and floats
|
143
|
-
$1.to_s
|
144
|
-
when /^(\d+)$/ # Ranges
|
145
|
-
$1.to_i
|
146
|
-
when /^\((\S+)\.\.(\S+)\)$/ # Floats
|
147
|
-
(resolve($1).to_i..resolve($2).to_i)
|
148
|
-
when /^(\d[\d\.]+)$/
|
149
|
-
$1.to_f
|
149
|
+
if LITERALS.key?(key)
|
150
|
+
LITERALS[key]
|
150
151
|
else
|
151
|
-
|
152
|
+
case key
|
153
|
+
when /^'(.*)'$/ # Single quoted strings
|
154
|
+
$1
|
155
|
+
when /^"(.*)"$/ # Double quoted strings
|
156
|
+
$1
|
157
|
+
when /^(-?\d+)$/ # Integer and floats
|
158
|
+
$1.to_i
|
159
|
+
when /^\((\S+)\.\.(\S+)\)$/ # Ranges
|
160
|
+
(resolve($1).to_i..resolve($2).to_i)
|
161
|
+
when /^(-?\d[\d\.]+)$/ # Floats
|
162
|
+
$1.to_f
|
163
|
+
else
|
164
|
+
variable(key)
|
165
|
+
end
|
152
166
|
end
|
153
167
|
end
|
154
168
|
|
data/lib/liquid/drop.rb
CHANGED
@@ -1,40 +1,40 @@
|
|
1
1
|
module Liquid
|
2
2
|
|
3
|
-
# A drop in liquid is a class which allows you to
|
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
|
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
|
#
|
11
|
-
#
|
12
|
-
#
|
13
|
-
#
|
11
|
+
# class ProductDrop < Liquid::Drop
|
12
|
+
# def top_sales
|
13
|
+
# Shop.current.products.find(:all, :order => 'sales', :limit => 10 )
|
14
|
+
# end
|
14
15
|
# end
|
15
|
-
# end
|
16
16
|
#
|
17
|
-
#
|
18
|
-
#
|
17
|
+
# tmpl = Liquid::Template.parse( ' {% for product in product.top_sales %} {{ product.name }} {%endfor%} ' )
|
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
|
|
25
|
+
EMPTY_STRING = ''.freeze
|
26
|
+
|
25
27
|
# Catch all for the method
|
26
28
|
def before_method(method)
|
27
29
|
nil
|
28
30
|
end
|
29
31
|
|
30
32
|
# called by liquid to invoke a drop
|
31
|
-
def invoke_drop(
|
32
|
-
|
33
|
-
|
34
|
-
if methods.include?(method.to_s)
|
35
|
-
send(method.to_sym)
|
33
|
+
def invoke_drop(method_or_key)
|
34
|
+
if method_or_key && method_or_key != EMPTY_STRING && self.class.public_method_defined?(method_or_key.to_s.to_sym)
|
35
|
+
send(method_or_key.to_s.to_sym)
|
36
36
|
else
|
37
|
-
before_method(
|
37
|
+
before_method(method_or_key)
|
38
38
|
end
|
39
39
|
end
|
40
40
|
|
data/lib/liquid/errors.rb
CHANGED
data/lib/liquid/extensions.rb
CHANGED
@@ -43,14 +43,20 @@ class Date # :nodoc:
|
|
43
43
|
end
|
44
44
|
end
|
45
45
|
|
46
|
-
|
47
|
-
|
46
|
+
class TrueClass
|
47
|
+
def to_liquid # :nodoc:
|
48
|
+
self
|
49
|
+
end
|
48
50
|
end
|
49
51
|
|
50
|
-
|
51
|
-
|
52
|
+
class FalseClass
|
53
|
+
def to_liquid # :nodoc:
|
54
|
+
self
|
55
|
+
end
|
52
56
|
end
|
53
57
|
|
54
|
-
|
55
|
-
|
56
|
-
|
58
|
+
class NilClass
|
59
|
+
def to_liquid # :nodoc:
|
60
|
+
self
|
61
|
+
end
|
62
|
+
end
|