locomotive_liquid 2.1.3
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +46 -0
- data/History.txt +44 -0
- data/MIT-LICENSE +20 -0
- data/README.txt +38 -0
- data/Rakefile +50 -0
- data/init.rb +8 -0
- data/lib/extras/liquid_view.rb +51 -0
- data/lib/liquid.rb +68 -0
- data/lib/liquid/block.rb +101 -0
- data/lib/liquid/condition.rb +120 -0
- data/lib/liquid/context.rb +254 -0
- data/lib/liquid/document.rb +18 -0
- data/lib/liquid/drop.rb +51 -0
- data/lib/liquid/errors.rb +11 -0
- data/lib/liquid/extensions.rb +56 -0
- data/lib/liquid/file_system.rb +62 -0
- data/lib/liquid/htmltags.rb +74 -0
- data/lib/liquid/module_ex.rb +62 -0
- data/lib/liquid/standardfilters.rb +222 -0
- data/lib/liquid/strainer.rb +51 -0
- data/lib/liquid/tag.rb +27 -0
- data/lib/liquid/tags/assign.rb +33 -0
- data/lib/liquid/tags/capture.rb +35 -0
- data/lib/liquid/tags/case.rb +83 -0
- data/lib/liquid/tags/comment.rb +9 -0
- data/lib/liquid/tags/cycle.rb +59 -0
- data/lib/liquid/tags/extends.rb +75 -0
- data/lib/liquid/tags/for.rb +136 -0
- data/lib/liquid/tags/if.rb +80 -0
- data/lib/liquid/tags/ifchanged.rb +20 -0
- data/lib/liquid/tags/include.rb +56 -0
- data/lib/liquid/tags/inherited_block.rb +93 -0
- data/lib/liquid/tags/unless.rb +33 -0
- data/lib/liquid/template.rb +149 -0
- data/lib/liquid/variable.rb +50 -0
- data/lib/locomotive_liquid.rb +1 -0
- metadata +111 -0
data/CHANGELOG
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
* Make context and assign work the same
|
2
|
+
|
3
|
+
* Ruby 1.9.1 bugfixes
|
4
|
+
|
5
|
+
* Fix LiquidView for Rails 2.2. Fix local assigns for all versions of Rails
|
6
|
+
|
7
|
+
* Fixed gem install rake task
|
8
|
+
* Improve Error encapsulation in liquid by maintaining a own set of exceptions instead of relying on ruby build ins
|
9
|
+
|
10
|
+
* Added If with or / and expressions
|
11
|
+
|
12
|
+
* Implemented .to_liquid for all objects which can be passed to liquid like Strings Arrays Hashes Numerics and Booleans. To export new objects to liquid just implement .to_liquid on them and return objects which themselves have .to_liquid methods.
|
13
|
+
|
14
|
+
* Added more tags to standard library
|
15
|
+
|
16
|
+
* Added include tag ( like partials in rails )
|
17
|
+
|
18
|
+
* [...] Gazillion of detail improvements
|
19
|
+
|
20
|
+
* Added strainers as filter hosts for better security [Tobias Luetke]
|
21
|
+
|
22
|
+
* Fixed that rails integration would call filter with the wrong "self" [Michael Geary]
|
23
|
+
|
24
|
+
* Fixed bad error reporting when a filter called a method which doesn't exist. Liquid told you that it couldn't find the filter which was obviously misleading [Tobias Luetke]
|
25
|
+
|
26
|
+
* Removed count helper from standard lib. use size [Tobias Luetke]
|
27
|
+
|
28
|
+
* Fixed bug with string filter parameters failing to tolerate commas in strings. [Paul Hammond]
|
29
|
+
|
30
|
+
* Improved filter parameters. Filter parameters are now context sensitive; Types are resolved according to the rules of the context. Multiple parameters are now separated by the Liquid::ArgumentSeparator: , by default [Paul Hammond]
|
31
|
+
|
32
|
+
{{ 'Typo' | link_to: 'http://typo.leetsoft.com', 'Typo - a modern weblog engine' }}
|
33
|
+
|
34
|
+
|
35
|
+
* Added Liquid::Drop. A base class which you can use for exporting proxy objects to liquid which can acquire more data when used in liquid. [Tobias Luetke]
|
36
|
+
|
37
|
+
class ProductDrop < Liquid::Drop
|
38
|
+
def top_sales
|
39
|
+
Shop.current.products.find(:all, :order => 'sales', :limit => 10 )
|
40
|
+
end
|
41
|
+
end
|
42
|
+
t = Liquid::Template.parse( ' {% for product in product.top_sales %} {{ product.name }} {% endfor %} ' )
|
43
|
+
t.render('product' => ProductDrop.new )
|
44
|
+
|
45
|
+
|
46
|
+
* Added filter parameters support. Example: {{ date | format_date: "%Y" }} [Paul Hammond]
|
data/History.txt
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
1.9.0 / 2008-03-04
|
2
|
+
|
3
|
+
* Fixed gem install rake task
|
4
|
+
* Improve Error encapsulation in liquid by maintaining a own set of exceptions instead of relying on ruby build ins
|
5
|
+
|
6
|
+
Before 1.9.0
|
7
|
+
|
8
|
+
* Added If with or / and expressions
|
9
|
+
|
10
|
+
* Implemented .to_liquid for all objects which can be passed to liquid like Strings Arrays Hashes Numerics and Booleans. To export new objects to liquid just implement .to_liquid on them and return objects which themselves have .to_liquid methods.
|
11
|
+
|
12
|
+
* Added more tags to standard library
|
13
|
+
|
14
|
+
* Added include tag ( like partials in rails )
|
15
|
+
|
16
|
+
* [...] Gazillion of detail improvements
|
17
|
+
|
18
|
+
* Added strainers as filter hosts for better security [Tobias Luetke]
|
19
|
+
|
20
|
+
* Fixed that rails integration would call filter with the wrong "self" [Michael Geary]
|
21
|
+
|
22
|
+
* Fixed bad error reporting when a filter called a method which doesn't exist. Liquid told you that it couldn't find the filter which was obviously misleading [Tobias Luetke]
|
23
|
+
|
24
|
+
* Removed count helper from standard lib. use size [Tobias Luetke]
|
25
|
+
|
26
|
+
* Fixed bug with string filter parameters failing to tolerate commas in strings. [Paul Hammond]
|
27
|
+
|
28
|
+
* Improved filter parameters. Filter parameters are now context sensitive; Types are resolved according to the rules of the context. Multiple parameters are now separated by the Liquid::ArgumentSeparator: , by default [Paul Hammond]
|
29
|
+
|
30
|
+
{{ 'Typo' | link_to: 'http://typo.leetsoft.com', 'Typo - a modern weblog engine' }}
|
31
|
+
|
32
|
+
|
33
|
+
* Added Liquid::Drop. A base class which you can use for exporting proxy objects to liquid which can acquire more data when used in liquid. [Tobias Luetke]
|
34
|
+
|
35
|
+
class ProductDrop < Liquid::Drop
|
36
|
+
def top_sales
|
37
|
+
Shop.current.products.find(:all, :order => 'sales', :limit => 10 )
|
38
|
+
end
|
39
|
+
end
|
40
|
+
t = Liquid::Template.parse( ' {% for product in product.top_sales %} {{ product.name }} {% endfor %} ' )
|
41
|
+
t.render('product' => ProductDrop.new )
|
42
|
+
|
43
|
+
|
44
|
+
* Added filter parameters support. Example: {{ date | format_date: "%Y" }} [Paul Hammond]
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2005, 2006 Tobias Luetke
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.txt
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
= Liquid template engine
|
2
|
+
|
3
|
+
Liquid is a template engine which I wrote for very specific requirements
|
4
|
+
|
5
|
+
* It has to have beautiful and simple markup.
|
6
|
+
Template engines which don't produce good looking markup are no fun to use.
|
7
|
+
* 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.
|
8
|
+
* 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
|
9
|
+
just render it passing in a hash with local variables and objects.
|
10
|
+
|
11
|
+
== Why should i use Liquid
|
12
|
+
|
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 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 one
|
18
|
+
|
19
|
+
== What does it look like?
|
20
|
+
|
21
|
+
<ul id="products">
|
22
|
+
{% for product in products %}
|
23
|
+
<li>
|
24
|
+
<h2>{{product.name}}</h2>
|
25
|
+
Only {{product.price | price }}
|
26
|
+
|
27
|
+
{{product.description | prettyprint | paragraph }}
|
28
|
+
</li>
|
29
|
+
{% endfor %}
|
30
|
+
</ul>
|
31
|
+
|
32
|
+
== Howto use Liquid
|
33
|
+
|
34
|
+
Liquid supports a very simple API based around the Liquid::Template class.
|
35
|
+
For standard use you can just pass it the content of a file and call render with a parameters hash.
|
36
|
+
|
37
|
+
@template = Liquid::Template.parse("hi {{name}}") # Parses and compiles the template
|
38
|
+
@template.render( 'name' => 'tobi' ) # => "hi tobi"
|
data/Rakefile
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'rubygems'
|
3
|
+
require 'rake'
|
4
|
+
require 'rake/testtask'
|
5
|
+
require 'rake/gempackagetask'
|
6
|
+
|
7
|
+
task :default => 'test'
|
8
|
+
|
9
|
+
Rake::TestTask.new(:test) do |t|
|
10
|
+
t.libs << "lib"
|
11
|
+
t.libs << "test"
|
12
|
+
t.pattern = 'test/*_test.rb'
|
13
|
+
t.verbose = false
|
14
|
+
end
|
15
|
+
|
16
|
+
Rake::TestTask.new(:ti) do |t|
|
17
|
+
t.libs << "lib"
|
18
|
+
t.libs << "test"
|
19
|
+
t.test_files = ['test/test_helper.rb', 'test/extends_test.rb', 'test/inherited_block_test.rb']
|
20
|
+
# t.test_files = ['test/test_helper.rb', 'test/inherited_block_test.rb', 'test/inherited_block_test.rb']
|
21
|
+
t.verbose = false
|
22
|
+
end
|
23
|
+
|
24
|
+
gemspec = eval(File.read('locomotive_liquid.gemspec'))
|
25
|
+
Rake::GemPackageTask.new(gemspec) do |pkg|
|
26
|
+
pkg.gem_spec = gemspec
|
27
|
+
end
|
28
|
+
|
29
|
+
desc "build the gem and release it to rubygems.org"
|
30
|
+
task :release => :gem do
|
31
|
+
sh "gem push pkg/locomotive_liquid-#{gemspec.version}.gem"
|
32
|
+
end
|
33
|
+
|
34
|
+
namespace :profile do
|
35
|
+
|
36
|
+
task :default => [:run]
|
37
|
+
|
38
|
+
desc "Run the liquid profile/perforamce coverage"
|
39
|
+
task :run do
|
40
|
+
|
41
|
+
ruby "performance/shopify.rb"
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
desc "Run KCacheGrind"
|
46
|
+
task :grind => :run do
|
47
|
+
system "kcachegrind /tmp/liquid.rubyprof_calltreeprinter.txt"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
data/init.rb
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
# LiquidView is a action view extension class. You can register it with rails
|
2
|
+
# and use liquid as an template system for .liquid files
|
3
|
+
#
|
4
|
+
# Example
|
5
|
+
#
|
6
|
+
# ActionView::Base::register_template_handler :liquid, LiquidView
|
7
|
+
class LiquidView
|
8
|
+
PROTECTED_ASSIGNS = %w( template_root response _session template_class action_name request_origin session template
|
9
|
+
_response url _request _cookies variables_added _flash params _headers request cookies
|
10
|
+
ignore_missing_templates flash _params logger before_filter_chain_aborted headers )
|
11
|
+
PROTECTED_INSTANCE_VARIABLES = %w( @_request @controller @_first_render @_memoized__pick_template @view_paths
|
12
|
+
@helpers @assigns_added @template @_render_stack @template_format @assigns )
|
13
|
+
|
14
|
+
def self.call(template)
|
15
|
+
"LiquidView.new(self).render(template, local_assigns)"
|
16
|
+
end
|
17
|
+
|
18
|
+
def initialize(view)
|
19
|
+
@view = view
|
20
|
+
end
|
21
|
+
|
22
|
+
def render(template, local_assigns = nil)
|
23
|
+
@view.controller.headers["Content-Type"] ||= 'text/html; charset=utf-8'
|
24
|
+
|
25
|
+
# Rails 2.2 Template has source, but not locals
|
26
|
+
if template.respond_to?(:source) && !template.respond_to?(:locals)
|
27
|
+
assigns = (@view.instance_variables - PROTECTED_INSTANCE_VARIABLES).inject({}) do |hash, ivar|
|
28
|
+
hash[ivar[1..-1]] = @view.instance_variable_get(ivar)
|
29
|
+
hash
|
30
|
+
end
|
31
|
+
else
|
32
|
+
assigns = @view.assigns.reject{ |k,v| PROTECTED_ASSIGNS.include?(k) }
|
33
|
+
end
|
34
|
+
|
35
|
+
source = template.respond_to?(:source) ? template.source : template
|
36
|
+
local_assigns = (template.respond_to?(:locals) ? template.locals : local_assigns) || {}
|
37
|
+
|
38
|
+
if content_for_layout = @view.instance_variable_get("@content_for_layout")
|
39
|
+
assigns['content_for_layout'] = content_for_layout
|
40
|
+
end
|
41
|
+
assigns.merge!(local_assigns.stringify_keys)
|
42
|
+
|
43
|
+
liquid = Liquid::Template.parse(source)
|
44
|
+
liquid.render(assigns, :filters => [@view.controller.master_helper_module], :registers => {:action_view => @view, :controller => @view.controller})
|
45
|
+
end
|
46
|
+
|
47
|
+
def compilable?
|
48
|
+
false
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
data/lib/liquid.rb
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
# Copyright (c) 2005 Tobias Luetke
|
2
|
+
#
|
3
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
# a copy of this software and associated documentation files (the
|
5
|
+
# "Software"), to deal in the Software without restriction, including
|
6
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
# the following conditions:
|
10
|
+
#
|
11
|
+
# The above copyright notice and this permission notice shall be
|
12
|
+
# included in all copies or substantial portions of the Software.
|
13
|
+
#
|
14
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
21
|
+
|
22
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
23
|
+
|
24
|
+
module Liquid
|
25
|
+
FilterSeparator = /\|/
|
26
|
+
ArgumentSeparator = ','
|
27
|
+
FilterArgumentSeparator = ':'
|
28
|
+
VariableAttributeSeparator = '.'
|
29
|
+
TagStart = /\{\%/
|
30
|
+
TagEnd = /\%\}/
|
31
|
+
VariableSignature = /\(?[\w\-\.\[\]]\)?/
|
32
|
+
VariableSegment = /[\w\-]/
|
33
|
+
VariableStart = /\{\{/
|
34
|
+
VariableEnd = /\}\}/
|
35
|
+
VariableIncompleteEnd = /\}\}?/
|
36
|
+
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})/
|
44
|
+
AnyStartingTag = /\{\{|\{\%/
|
45
|
+
PartialTemplateParser = /#{TagStart}.*?#{TagEnd}|#{VariableStart}.*?#{VariableIncompleteEnd}/
|
46
|
+
TemplateParser = /(#{PartialTemplateParser}|#{AnyStartingTag})/
|
47
|
+
VariableParser = /\[[^\]]+\]|#{VariableSegment}+\??/
|
48
|
+
end
|
49
|
+
|
50
|
+
require 'liquid/drop'
|
51
|
+
require 'liquid/extensions'
|
52
|
+
require 'liquid/errors'
|
53
|
+
require 'liquid/strainer'
|
54
|
+
require 'liquid/context'
|
55
|
+
require 'liquid/tag'
|
56
|
+
require 'liquid/block'
|
57
|
+
require 'liquid/document'
|
58
|
+
require 'liquid/variable'
|
59
|
+
require 'liquid/file_system'
|
60
|
+
require 'liquid/template'
|
61
|
+
require 'liquid/htmltags'
|
62
|
+
require 'liquid/standardfilters'
|
63
|
+
require 'liquid/condition'
|
64
|
+
require 'liquid/module_ex'
|
65
|
+
|
66
|
+
# Load all the tags of the standard library
|
67
|
+
#
|
68
|
+
Dir[File.dirname(__FILE__) + '/liquid/tags/*.rb'].each { |f| require f }
|
data/lib/liquid/block.rb
ADDED
@@ -0,0 +1,101 @@
|
|
1
|
+
module Liquid
|
2
|
+
|
3
|
+
class Block < Tag
|
4
|
+
IsTag = /^#{TagStart}/
|
5
|
+
IsVariable = /^#{VariableStart}/
|
6
|
+
FullToken = /^#{TagStart}\s*(\w+)\s*(.*)?#{TagEnd}$/
|
7
|
+
ContentOfVariable = /^#{VariableStart}(.*)#{VariableEnd}$/
|
8
|
+
|
9
|
+
def parse(tokens)
|
10
|
+
@nodelist ||= []
|
11
|
+
@nodelist.clear
|
12
|
+
|
13
|
+
while token = tokens.shift
|
14
|
+
|
15
|
+
case token
|
16
|
+
when IsTag
|
17
|
+
if token =~ FullToken
|
18
|
+
|
19
|
+
# if we found the proper block delimitor just end parsing here and let the outer block
|
20
|
+
# proceed
|
21
|
+
if block_delimiter == $1
|
22
|
+
end_tag
|
23
|
+
return
|
24
|
+
end
|
25
|
+
|
26
|
+
# fetch the tag from registered blocks
|
27
|
+
if tag = Template.tags[$1]
|
28
|
+
@nodelist << tag.new($1, $2, tokens, @context)
|
29
|
+
else
|
30
|
+
# this tag is not registered with the system
|
31
|
+
# pass it to the current block for special handling or error reporting
|
32
|
+
unknown_tag($1, $2, tokens)
|
33
|
+
end
|
34
|
+
else
|
35
|
+
raise SyntaxError, "Tag '#{token}' was not properly terminated with regexp: #{TagEnd.inspect} "
|
36
|
+
end
|
37
|
+
when IsVariable
|
38
|
+
@nodelist << create_variable(token)
|
39
|
+
when ''
|
40
|
+
# pass
|
41
|
+
else
|
42
|
+
@nodelist << token
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# Make sure that its ok to end parsing in the current block.
|
47
|
+
# Effectively this method will throw and exception unless the current block is
|
48
|
+
# of type Document
|
49
|
+
assert_missing_delimitation!
|
50
|
+
end
|
51
|
+
|
52
|
+
def end_tag
|
53
|
+
end
|
54
|
+
|
55
|
+
def unknown_tag(tag, params, tokens)
|
56
|
+
case tag
|
57
|
+
when 'else'
|
58
|
+
raise SyntaxError, "#{block_name} tag does not expect else tag"
|
59
|
+
when 'end'
|
60
|
+
raise SyntaxError, "'end' is not a valid delimiter for #{block_name} tags. use #{block_delimiter}"
|
61
|
+
else
|
62
|
+
raise SyntaxError, "Unknown tag '#{tag}'"
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def block_delimiter
|
67
|
+
"end#{block_name}"
|
68
|
+
end
|
69
|
+
|
70
|
+
def block_name
|
71
|
+
@tag_name
|
72
|
+
end
|
73
|
+
|
74
|
+
def create_variable(token)
|
75
|
+
token.scan(ContentOfVariable) do |content|
|
76
|
+
return Variable.new(content.first)
|
77
|
+
end
|
78
|
+
raise SyntaxError.new("Variable '#{token}' was not properly terminated with regexp: #{VariableEnd.inspect} ")
|
79
|
+
end
|
80
|
+
|
81
|
+
def render(context)
|
82
|
+
render_all(@nodelist, context)
|
83
|
+
end
|
84
|
+
|
85
|
+
protected
|
86
|
+
|
87
|
+
def assert_missing_delimitation!
|
88
|
+
raise SyntaxError.new("#{block_name} tag was never closed")
|
89
|
+
end
|
90
|
+
|
91
|
+
def render_all(list, context)
|
92
|
+
list.collect do |token|
|
93
|
+
begin
|
94
|
+
token.respond_to?(:render) ? token.render(context) : token
|
95
|
+
rescue Exception => e
|
96
|
+
context.handle_error(e)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,120 @@
|
|
1
|
+
module Liquid
|
2
|
+
# Container for liquid nodes which conveniently wraps decision making logic
|
3
|
+
#
|
4
|
+
# Example:
|
5
|
+
#
|
6
|
+
# c = Condition.new('1', '==', '1')
|
7
|
+
# c.evaluate #=> true
|
8
|
+
#
|
9
|
+
class Condition #:nodoc:
|
10
|
+
@@operators = {
|
11
|
+
'==' => lambda { |cond, left, right| cond.send(:equal_variables, left, right) },
|
12
|
+
'!=' => lambda { |cond, left, right| !cond.send(:equal_variables, left, right) },
|
13
|
+
'<>' => lambda { |cond, left, right| !cond.send(:equal_variables, left, right) },
|
14
|
+
'<' => :<,
|
15
|
+
'>' => :>,
|
16
|
+
'>=' => :>=,
|
17
|
+
'<=' => :<=,
|
18
|
+
'contains' => lambda { |cond, left, right| left && right ? left.include?(right) : false }
|
19
|
+
}
|
20
|
+
|
21
|
+
def self.operators
|
22
|
+
@@operators
|
23
|
+
end
|
24
|
+
|
25
|
+
attr_reader :attachment
|
26
|
+
attr_accessor :left, :operator, :right
|
27
|
+
|
28
|
+
def initialize(left = nil, operator = nil, right = nil)
|
29
|
+
@left, @operator, @right = left, operator, right
|
30
|
+
@child_relation = nil
|
31
|
+
@child_condition = nil
|
32
|
+
end
|
33
|
+
|
34
|
+
def evaluate(context = Context.new)
|
35
|
+
result = interpret_condition(left, right, operator, context)
|
36
|
+
|
37
|
+
case @child_relation
|
38
|
+
when :or
|
39
|
+
result || @child_condition.evaluate(context)
|
40
|
+
when :and
|
41
|
+
result && @child_condition.evaluate(context)
|
42
|
+
else
|
43
|
+
result
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def or(condition)
|
48
|
+
@child_relation, @child_condition = :or, condition
|
49
|
+
end
|
50
|
+
|
51
|
+
def and(condition)
|
52
|
+
@child_relation, @child_condition = :and, condition
|
53
|
+
end
|
54
|
+
|
55
|
+
def attach(attachment)
|
56
|
+
@attachment = attachment
|
57
|
+
end
|
58
|
+
|
59
|
+
def else?
|
60
|
+
false
|
61
|
+
end
|
62
|
+
|
63
|
+
def inspect
|
64
|
+
"#<Condition #{[@left, @operator, @right].compact.join(' ')}>"
|
65
|
+
end
|
66
|
+
|
67
|
+
private
|
68
|
+
|
69
|
+
def equal_variables(left, right)
|
70
|
+
if left.is_a?(Symbol)
|
71
|
+
if right.respond_to?(left)
|
72
|
+
return right.send(left.to_s)
|
73
|
+
else
|
74
|
+
return nil
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
if right.is_a?(Symbol)
|
79
|
+
if left.respond_to?(right)
|
80
|
+
return left.send(right.to_s)
|
81
|
+
else
|
82
|
+
return nil
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
left == right
|
87
|
+
end
|
88
|
+
|
89
|
+
def interpret_condition(left, right, op, context)
|
90
|
+
# If the operator is empty this means that the decision statement is just
|
91
|
+
# a single variable. We can just poll this variable from the context and
|
92
|
+
# return this as the result.
|
93
|
+
return context[left] if op == nil
|
94
|
+
|
95
|
+
left, right = context[left], context[right]
|
96
|
+
|
97
|
+
operation = self.class.operators[op] || raise(ArgumentError.new("Unknown operator #{op}"))
|
98
|
+
|
99
|
+
if operation.respond_to?(:call)
|
100
|
+
operation.call(self, left, right)
|
101
|
+
elsif left.respond_to?(operation) and right.respond_to?(operation)
|
102
|
+
left.send(operation, right)
|
103
|
+
else
|
104
|
+
nil
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
|
110
|
+
class ElseCondition < Condition
|
111
|
+
def else?
|
112
|
+
true
|
113
|
+
end
|
114
|
+
|
115
|
+
def evaluate(context)
|
116
|
+
true
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
end
|