liquid 1.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (61) hide show
  1. data/CHANGELOG +38 -0
  2. data/MIT-LICENSE +20 -0
  3. data/Manifest.txt +60 -0
  4. data/README +38 -0
  5. data/Rakefile +24 -0
  6. data/example/server/example_servlet.rb +37 -0
  7. data/example/server/liquid_servlet.rb +28 -0
  8. data/example/server/server.rb +12 -0
  9. data/example/server/templates/index.liquid +6 -0
  10. data/example/server/templates/products.liquid +45 -0
  11. data/init.rb +6 -0
  12. data/lib/extras/liquid_view.rb +27 -0
  13. data/lib/liquid.rb +66 -0
  14. data/lib/liquid/block.rb +101 -0
  15. data/lib/liquid/condition.rb +91 -0
  16. data/lib/liquid/context.rb +216 -0
  17. data/lib/liquid/document.rb +17 -0
  18. data/lib/liquid/drop.rb +48 -0
  19. data/lib/liquid/errors.rb +7 -0
  20. data/lib/liquid/extensions.rb +56 -0
  21. data/lib/liquid/file_system.rb +62 -0
  22. data/lib/liquid/htmltags.rb +64 -0
  23. data/lib/liquid/standardfilters.rb +125 -0
  24. data/lib/liquid/strainer.rb +43 -0
  25. data/lib/liquid/tag.rb +25 -0
  26. data/lib/liquid/tags/assign.rb +22 -0
  27. data/lib/liquid/tags/capture.rb +22 -0
  28. data/lib/liquid/tags/case.rb +68 -0
  29. data/lib/liquid/tags/comment.rb +9 -0
  30. data/lib/liquid/tags/cycle.rb +46 -0
  31. data/lib/liquid/tags/for.rb +81 -0
  32. data/lib/liquid/tags/if.rb +51 -0
  33. data/lib/liquid/tags/ifchanged.rb +20 -0
  34. data/lib/liquid/tags/include.rb +56 -0
  35. data/lib/liquid/tags/unless.rb +29 -0
  36. data/lib/liquid/template.rb +150 -0
  37. data/lib/liquid/variable.rb +39 -0
  38. data/test/block_test.rb +50 -0
  39. data/test/context_test.rb +340 -0
  40. data/test/drop_test.rb +139 -0
  41. data/test/error_handling_test.rb +65 -0
  42. data/test/extra/breakpoint.rb +547 -0
  43. data/test/extra/caller.rb +80 -0
  44. data/test/file_system_test.rb +30 -0
  45. data/test/filter_test.rb +98 -0
  46. data/test/helper.rb +20 -0
  47. data/test/html_tag_test.rb +24 -0
  48. data/test/if_else_test.rb +95 -0
  49. data/test/include_tag_test.rb +91 -0
  50. data/test/output_test.rb +121 -0
  51. data/test/parsing_quirks_test.rb +14 -0
  52. data/test/regexp_test.rb +39 -0
  53. data/test/security_test.rb +41 -0
  54. data/test/standard_filter_test.rb +101 -0
  55. data/test/standard_tag_test.rb +336 -0
  56. data/test/statements_test.rb +137 -0
  57. data/test/strainer_test.rb +16 -0
  58. data/test/template_test.rb +26 -0
  59. data/test/unless_else_test.rb +19 -0
  60. data/test/variable_test.rb +135 -0
  61. metadata +114 -0
data/CHANGELOG ADDED
@@ -0,0 +1,38 @@
1
+ Changelog
2
+
3
+ Implemented .to_liquid for all objects which can be passed to liquid like Strings Arrays Hashes Numerics and Booleans.
4
+ To export new objects to liquid just implement .to_liquid on them and return objects which themselves have .to_liquid methods.
5
+
6
+ Added more tags to standard library
7
+
8
+ Added include tag ( like partials in rails )
9
+
10
+ [...] Gazillion of detail improvements
11
+
12
+ Added strainers as filter hosts for better security [Tobias Luetke]
13
+
14
+ Fixed that rails integration would call filter with the wrong "self" [Michael Geary]
15
+
16
+ Fixed bad error reporting when a filter called a method which doesn't exist. Liquid told you that it couldn't find the
17
+ filter which was obviously misleading [Tobias Luetke]
18
+
19
+ Removed count helper from standard lib. use size [Tobias Luetke]
20
+
21
+ Fixed bug with string filter parameters failing to tolerate commas in strings. [Paul Hammond]
22
+
23
+ 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]
24
+ {{ 'Typo' | link_to: 'http://typo.leetsoft.com', 'Typo - a modern weblog engine' }}
25
+
26
+
27
+ 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]
28
+
29
+ class ProductDrop < Liquid::Drop
30
+ def top_sales
31
+ Shop.current.products.find(:all, :order => 'sales', :limit => 10 )
32
+ end
33
+ end
34
+ t = Liquid::Template.parse( ' {% for product in product.top_sales %} {{ product.name }} {% endfor %} ' )
35
+ t.render('product' => ProductDrop.new )
36
+
37
+
38
+ 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 PURPOa AND
17
+ NONINFRINGEMENT. IN NO EVENT SaALL 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/Manifest.txt ADDED
@@ -0,0 +1,60 @@
1
+ CHANGELOG
2
+ MIT-LICENSE
3
+ Manifest.txt
4
+ README
5
+ Rakefile
6
+ example/server/example_servlet.rb
7
+ example/server/liquid_servlet.rb
8
+ example/server/server.rb
9
+ example/server/templates/index.liquid
10
+ example/server/templates/products.liquid
11
+ init.rb
12
+ lib/extras/liquid_view.rb
13
+ lib/liquid.rb
14
+ lib/liquid/block.rb
15
+ lib/liquid/condition.rb
16
+ lib/liquid/context.rb
17
+ lib/liquid/document.rb
18
+ lib/liquid/drop.rb
19
+ lib/liquid/errors.rb
20
+ lib/liquid/extensions.rb
21
+ lib/liquid/file_system.rb
22
+ lib/liquid/htmltags.rb
23
+ lib/liquid/standardfilters.rb
24
+ lib/liquid/strainer.rb
25
+ lib/liquid/tag.rb
26
+ lib/liquid/tags/assign.rb
27
+ lib/liquid/tags/capture.rb
28
+ lib/liquid/tags/case.rb
29
+ lib/liquid/tags/comment.rb
30
+ lib/liquid/tags/cycle.rb
31
+ lib/liquid/tags/for.rb
32
+ lib/liquid/tags/if.rb
33
+ lib/liquid/tags/ifchanged.rb
34
+ lib/liquid/tags/include.rb
35
+ lib/liquid/tags/unless.rb
36
+ lib/liquid/template.rb
37
+ lib/liquid/variable.rb
38
+ test/block_test.rb
39
+ test/context_test.rb
40
+ test/drop_test.rb
41
+ test/error_handling_test.rb
42
+ test/extra/breakpoint.rb
43
+ test/extra/caller.rb
44
+ test/file_system_test.rb
45
+ test/filter_test.rb
46
+ test/helper.rb
47
+ test/html_tag_test.rb
48
+ test/if_else_test.rb
49
+ test/include_tag_test.rb
50
+ test/output_test.rb
51
+ test/parsing_quirks_test.rb
52
+ test/regexp_test.rb
53
+ test/security_test.rb
54
+ test/standard_filter_test.rb
55
+ test/standard_tag_test.rb
56
+ test/statements_test.rb
57
+ test/strainer_test.rb
58
+ test/template_test.rb
59
+ test/unless_else_test.rb
60
+ test/variable_test.rb
data/README 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,24 @@
1
+ #!/usr/bin/env ruby
2
+ require 'rubygems'
3
+ require 'rake'
4
+ require 'hoe'
5
+
6
+ PKG_VERSION = "1.7.0"
7
+ PKG_NAME = "liquid"
8
+ PKG_DESC = "A secure non evaling end user template engine with aesthetic markup."
9
+
10
+ Rake::TestTask.new(:test) do |t|
11
+ t.libs << "lib"
12
+ t.libs << "test"
13
+ t.pattern = 'test/*_test.rb'
14
+ t.verbose = false
15
+ end
16
+
17
+ Hoe.new(PKG_NAME, PKG_VERSION) do |p|
18
+ p.rubyforge_name = PKG_NAME
19
+ p.summary = PKG_DESC
20
+ p.description = nil
21
+ p.author = "Tobias Luetke"
22
+ p.email = "tobi@leetsoft.com"
23
+ p.url = "http://home.leetsoft.com/liquid"
24
+ end
@@ -0,0 +1,37 @@
1
+ module ProductsFilter
2
+ def price(integer)
3
+ sprintf("$%.2d USD", integer / 100.0)
4
+ end
5
+
6
+ def prettyprint(text)
7
+ text.gsub( /\*(.*)\*/, '<b>\1</b>' )
8
+ end
9
+
10
+ def count(array)
11
+ array.size
12
+ end
13
+
14
+ def paragraph(p)
15
+ "<p>#{p}</p>"
16
+ end
17
+ end
18
+
19
+ class Servlet < LiquidServlet
20
+
21
+ def index
22
+ { 'date' => Time.now }
23
+ end
24
+
25
+ def products
26
+ { 'products' => products_list, 'section' => 'Snowboards', 'cool_products' => true}
27
+ end
28
+
29
+ private
30
+
31
+ def products_list
32
+ [{'name' => 'Arbor Draft', 'price' => 39900, 'description' => 'the *arbor draft* is a excellent product' },
33
+ {'name' => 'Arbor Element', 'price' => 40000, 'description' => 'the *arbor element* rocks for freestyling'},
34
+ {'name' => 'Arbor Diamond', 'price' => 59900, 'description' => 'the *arbor diamond* is a made up product because im obsessed with arbor and have no creativity'}]
35
+ end
36
+
37
+ end
@@ -0,0 +1,28 @@
1
+ class LiquidServlet < WEBrick::HTTPServlet::AbstractServlet
2
+
3
+ def do_GET(req, res)
4
+ handle(:get, req, res)
5
+ end
6
+
7
+ def do_POST(req, res)
8
+ handle(:post, req, res)
9
+ end
10
+
11
+ private
12
+
13
+ def handle(type, req, res)
14
+ @request, @response = req, res
15
+
16
+ @request.path_info =~ /(\w+)$/
17
+ @action = $1 || 'index'
18
+ @assigns = send(@action) if respond_to?(@action)
19
+
20
+ @response['Content-Type'] = "text/html"
21
+ @response.status = 200
22
+ @response.body = Liquid::Template.parse(read_template).render(@assigns, :filters => [ProductsFilter])
23
+ end
24
+
25
+ def read_template(filename = @action)
26
+ File.read( File.dirname(__FILE__) + "/templates/#{filename}.liquid" )
27
+ end
28
+ end
@@ -0,0 +1,12 @@
1
+ require 'webrick'
2
+ require 'rexml/document'
3
+
4
+ require File.dirname(__FILE__) + '/../../lib/liquid'
5
+ require File.dirname(__FILE__) + '/liquid_servlet'
6
+ require File.dirname(__FILE__) + '/example_servlet'
7
+
8
+ # Setup webrick
9
+ server = WEBrick::HTTPServer.new( :Port => ARGV[1] || 3000 )
10
+ server.mount('/', Servlet)
11
+ trap("INT"){ server.shutdown }
12
+ server.start
@@ -0,0 +1,6 @@
1
+ <p>Hello world!</p>
2
+
3
+ <p>It is {{date}}</p>
4
+
5
+
6
+ <p>Check out the <a href="http://localhost:3000/products">Products</a> screen </p>
@@ -0,0 +1,45 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
3
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
4
+ <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
5
+ <head>
6
+ <meta http-equiv="Content-type" content="text/html; charset=utf-8" />
7
+ <meta http-equiv="Content-Language" content="en-us" />
8
+
9
+ <title>products</title>
10
+
11
+ <meta name="ROBOTS" content="ALL" />
12
+ <meta http-equiv="imagetoolbar" content="no" />
13
+ <meta name="MSSmartTagsPreventParsing" content="true" />
14
+ <meta name="Copyright" content="(c) 2005 Copyright content: Copyright design: Tobias Luetke" />
15
+ <!-- (c) Copyright 2005 by Tobias Luetke All Rights Reserved. -->
16
+ </head>
17
+
18
+ <body>
19
+
20
+ <h1>There are currently {{products | count}} products in the {{section}} catalog</h1>
21
+
22
+ {% if cool_products %}
23
+ Cool products :)
24
+ {% else %}
25
+ Uncool products :(
26
+ {% endif %}
27
+
28
+ <ul id="products">
29
+
30
+ {% for product in products %}
31
+ <li>
32
+ <h2>{{product.name}}</h2>
33
+ Only {{product.price | price }}
34
+
35
+ {{product.description | prettyprint | paragraph }}
36
+
37
+ {{ 'it rocks!' | paragraph }}
38
+
39
+ </li>
40
+ {% endfor %}
41
+
42
+ </ul>
43
+
44
+ </body>
45
+ </html>
data/init.rb ADDED
@@ -0,0 +1,6 @@
1
+ require 'liquid'
2
+ require 'extras/liquid_view'
3
+
4
+ ActionView::Base::register_template_handler :liquid, LiquidView
5
+
6
+
@@ -0,0 +1,27 @@
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
+
9
+ def initialize(action_view)
10
+ @action_view = action_view
11
+ end
12
+
13
+
14
+ def render(template, local_assigns)
15
+ @action_view.controller.headers["Content-Type"] ||= 'text/html; charset=utf-8'
16
+ assigns = @action_view.assigns.dup
17
+
18
+ if content_for_layout = @action_view.instance_variable_get("@content_for_layout")
19
+ assigns['content_for_layout'] = content_for_layout
20
+ end
21
+ assigns.merge!(local_assigns)
22
+
23
+ liquid = Liquid::Template.parse(template)
24
+ liquid.render(assigns, :filters => [@action_view.controller.master_helper_module], :registers => {:action_view => @action_view, :controller => @action_view.controller})
25
+ end
26
+
27
+ end
data/lib/liquid.rb ADDED
@@ -0,0 +1,66 @@
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 PURPOa AND
17
+ # NONINFRINGEMENT. IN NO EVENT SaALL 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
+ FilterSperator = /\|/
26
+ ArgumentSeparator = ','
27
+ FilterArgumentSeparator = ':'
28
+ VariableAttributeSeparator = '.'
29
+ TagStart = /\{\%/
30
+ TagEnd = /\%\}/
31
+ VariableSignature = /[\w\-\.\[\]]/
32
+ VariableSegment = /[\w\-]/
33
+ VariableStart = /\{\{/
34
+ VariableEnd = /\}\}/
35
+ QuotedFragment = /"[^"]+"|'[^']+'|[^\s,|]+/
36
+ TagAttributes = /(\w+)\s*\:\s*(#{QuotedFragment})/
37
+ TemplateParser = /(#{TagStart}.*?#{TagEnd}|#{VariableStart}.*?#{VariableEnd})/
38
+ VariableParser = /(?=\[)#{VariableSegment}+(?=\])|#{VariableSegment}+/
39
+ end
40
+
41
+ require 'liquid/drop'
42
+ require 'liquid/extensions'
43
+ require 'liquid/errors'
44
+ require 'liquid/strainer'
45
+ require 'liquid/context'
46
+ require 'liquid/tag'
47
+ require 'liquid/block'
48
+ require 'liquid/document'
49
+ require 'liquid/variable'
50
+ require 'liquid/file_system'
51
+ require 'liquid/template'
52
+ require 'liquid/htmltags'
53
+ require 'liquid/standardfilters'
54
+ require 'liquid/condition'
55
+
56
+ # Load all the tags of the standard library
57
+ #
58
+ Dir[File.dirname(__FILE__) + '/liquid/tags/*.rb'].each { |f| require f }
59
+
60
+
61
+
62
+
63
+
64
+
65
+
66
+
@@ -0,0 +1,101 @@
1
+ module Liquid
2
+
3
+ class Block < Tag
4
+ def parse(tokens)
5
+ @nodelist ||= []
6
+ @nodelist.clear
7
+
8
+ while token = tokens.shift
9
+
10
+ case token
11
+ when /^#{TagStart}/
12
+ if token =~ /^#{TagStart}\s*(\w+)\s*(.*)?#{TagEnd}$/
13
+
14
+ # if we found the proper block delimitor just end parsing here and let the outer block
15
+ # proceed
16
+ if block_delimiter == $1
17
+ end_tag
18
+ return
19
+ end
20
+
21
+ # fetch the tag from registered blocks
22
+ if tag = Template.tags[$1]
23
+ @nodelist << tag.new($2, tokens)
24
+ else
25
+ # this tag is not registered with the system
26
+ # pass it to the current block for special handling or error reporting
27
+ unknown_tag($1, $2, tokens)
28
+ end
29
+ else
30
+ raise SyntaxError, "Tag '#{token}' was not properly terminated with regexp: #{TagEnd.inspect} "
31
+ end
32
+ when /^#{VariableStart}/
33
+ @nodelist << create_variable(token)
34
+ when ''
35
+ # pass
36
+ else
37
+ @nodelist << token
38
+ end
39
+ end
40
+
41
+ # Make sure that its ok to end parsing in the current block.
42
+ # Effectively this method will throw and exception unless the current block is
43
+ # of type Document
44
+ assert_missing_delimitation!
45
+ end
46
+
47
+ def end_tag
48
+ end
49
+
50
+ def unknown_tag(tag, params, tokens)
51
+ case tag
52
+ when 'else'
53
+ raise SyntaxError, "#{block_name} tag does not expect else tag"
54
+ when 'end'
55
+ raise SyntaxError, "'end' is not a valid delimiter for #{block_name} tags. use #{block_delimiter}"
56
+ else
57
+ raise SyntaxError, "Unknown tag '#{tag}'"
58
+ end
59
+ end
60
+
61
+ def block_delimiter
62
+ "end#{block_name}"
63
+ end
64
+
65
+ def block_name
66
+ self.class.name.scan(/\w+$/).first.downcase
67
+ end
68
+
69
+ def create_variable(token)
70
+ token.scan(/^#{VariableStart}(.*)#{VariableEnd}$/) do |content|
71
+ return Variable.new(content.first)
72
+ end
73
+ raise SyntaxError.new("Variable '#{token}' was not properly terminated with regexp: #{VariableEnd.inspect} ")
74
+ end
75
+
76
+ def render(context)
77
+ render_all(@nodelist, context)
78
+ end
79
+
80
+ protected
81
+
82
+ def assert_missing_delimitation!
83
+ raise SyntaxError.new("#{block_name} tag was never closed")
84
+ end
85
+
86
+ def render_all(list, context)
87
+ list.collect do |token|
88
+ begin
89
+ if token.respond_to?(:render)
90
+ token.render(context)
91
+ else
92
+ token.to_s
93
+ end
94
+ rescue Exception => e
95
+ context.template.handle_error(e)
96
+ end
97
+
98
+ end
99
+ end
100
+ end
101
+ end