locomotive_liquid 2.1.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -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]
@@ -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]
@@ -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.
@@ -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"
@@ -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,8 @@
1
+ require 'liquid'
2
+ require 'extras/liquid_view'
3
+
4
+ if defined? ActionView::Template and ActionView::Template.respond_to? :register_template_handler
5
+ ActionView::Template
6
+ else
7
+ ActionView::Base
8
+ end.register_template_handler(:liquid, LiquidView)
@@ -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
@@ -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 }
@@ -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