liquid 2.2.2 → 2.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. data/{History.txt → History.md} +26 -23
  2. data/README.md +18 -16
  3. data/lib/liquid.rb +1 -4
  4. data/lib/liquid/block.rb +2 -2
  5. data/lib/liquid/context.rb +29 -33
  6. data/lib/liquid/drop.rb +11 -13
  7. data/lib/liquid/extensions.rb +13 -7
  8. data/lib/liquid/file_system.rb +2 -2
  9. data/lib/liquid/htmltags.rb +5 -4
  10. data/lib/liquid/standardfilters.rb +12 -1
  11. data/lib/liquid/strainer.rb +1 -1
  12. data/lib/liquid/tags/capture.rb +1 -1
  13. data/lib/liquid/tags/case.rb +4 -8
  14. data/lib/liquid/tags/decrement.rb +39 -0
  15. data/lib/liquid/tags/for.rb +20 -5
  16. data/lib/liquid/tags/include.rb +21 -12
  17. data/lib/liquid/tags/increment.rb +35 -0
  18. data/lib/liquid/tags/raw.rb +21 -0
  19. data/lib/liquid/tags/unless.rb +5 -5
  20. data/lib/liquid/template.rb +3 -2
  21. data/lib/liquid/variable.rb +1 -1
  22. data/test/liquid/assign_test.rb +15 -0
  23. data/test/liquid/block_test.rb +58 -0
  24. data/test/liquid/capture_test.rb +40 -0
  25. data/test/liquid/condition_test.rb +122 -0
  26. data/test/liquid/context_test.rb +478 -0
  27. data/test/liquid/drop_test.rb +162 -0
  28. data/test/liquid/error_handling_test.rb +81 -0
  29. data/test/liquid/file_system_test.rb +29 -0
  30. data/test/liquid/filter_test.rb +106 -0
  31. data/test/liquid/module_ex_test.rb +87 -0
  32. data/test/liquid/output_test.rb +116 -0
  33. data/test/liquid/parsing_quirks_test.rb +52 -0
  34. data/test/liquid/regexp_test.rb +44 -0
  35. data/test/liquid/security_test.rb +41 -0
  36. data/test/liquid/standard_filter_test.rb +191 -0
  37. data/test/liquid/strainer_test.rb +25 -0
  38. data/test/liquid/tags/html_tag_test.rb +29 -0
  39. data/test/liquid/tags/if_else_tag_test.rb +160 -0
  40. data/test/liquid/tags/include_tag_test.rb +139 -0
  41. data/test/liquid/tags/increment_tag_test.rb +24 -0
  42. data/test/liquid/tags/raw_tag_test.rb +15 -0
  43. data/test/liquid/tags/standard_tag_test.rb +461 -0
  44. data/test/liquid/tags/statements_test.rb +134 -0
  45. data/test/liquid/tags/unless_else_tag_test.rb +26 -0
  46. data/test/liquid/template_test.rb +74 -0
  47. data/test/liquid/variable_test.rb +170 -0
  48. data/test/test_helper.rb +29 -0
  49. metadata +67 -16
  50. data/Manifest.txt +0 -34
  51. data/lib/liquid/tags/literal.rb +0 -42
@@ -1,53 +1,56 @@
1
- 2.2.1 / 2010-08-23
1
+ # Liquid Version History
2
+
3
+ ## 2.3.0
4
+
5
+ * Several speed/memory improvements
6
+ * Numerous bug fixes
7
+ * Added support for MRI 1.9, Rubinius, and JRuby
8
+ * Added support for integer drop parameters
9
+ * Added epoch support to `date` filter
10
+ * New `raw` tag that suppresses parsing
11
+ * Added `else` option to `for` tag
12
+ * New `increment` tag
13
+ * New `split` filter
14
+
15
+
16
+ ## 2.2.1 / 2010-08-23
2
17
 
3
18
  * Added support for literal tags
4
19
 
5
- 2.2.0 / 2010-08-22
20
+
21
+ ## 2.2.0 / 2010-08-22
6
22
 
7
23
  * Compatible with Ruby 1.8.7, 1.9.1 and 1.9.2-p0
8
24
  * Merged some changed made by the community
9
25
 
10
- 1.9.0 / 2008-03-04
26
+
27
+ ## 1.9.0 / 2008-03-04
11
28
 
12
29
  * Fixed gem install rake task
13
30
  * Improve Error encapsulation in liquid by maintaining a own set of exceptions instead of relying on ruby build ins
14
31
 
15
- Before 1.9.0
16
-
17
- * Added If with or / and expressions
18
32
 
19
- * 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.
33
+ ## Before 1.9.0
20
34
 
35
+ * Added If with or / and expressions
36
+ * 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.
21
37
  * Added more tags to standard library
22
-
23
38
  * Added include tag ( like partials in rails )
24
-
25
39
  * [...] Gazillion of detail improvements
26
-
27
40
  * Added strainers as filter hosts for better security [Tobias Luetke]
28
-
29
41
  * Fixed that rails integration would call filter with the wrong "self" [Michael Geary]
30
-
31
42
  * 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]
32
-
33
43
  * Removed count helper from standard lib. use size [Tobias Luetke]
34
-
35
44
  * Fixed bug with string filter parameters failing to tolerate commas in strings. [Paul Hammond]
36
-
37
45
  * 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]
38
-
39
- {{ 'Typo' | link_to: 'http://typo.leetsoft.com', 'Typo - a modern weblog engine' }}
40
-
41
-
42
- * 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]
46
+ {{ 'Typo' | link_to: 'http://typo.leetsoft.com', 'Typo - a modern weblog engine' }}
47
+ * 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]
43
48
 
44
49
  class ProductDrop < Liquid::Drop
45
50
  def top_sales
46
51
  Shop.current.products.find(:all, :order => 'sales', :limit => 10 )
47
52
  end
48
- end
53
+ end
49
54
  t = Liquid::Template.parse( ' {% for product in product.top_sales %} {{ product.name }} {% endfor %} ' )
50
55
  t.render('product' => ProductDrop.new )
51
-
52
-
53
56
  * Added filter parameters support. Example: {{ date | format_date: "%Y" }} [Paul Hammond]
data/README.md CHANGED
@@ -2,13 +2,13 @@
2
2
 
3
3
  ## Introduction
4
4
 
5
- Liquid is a template engine which I wrote for very specific requirements
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
9
  * 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 just render it passing in a hash with local variables and objects.
10
10
 
11
- ## Why should I use Liquid
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
14
  * You want to render templates directly from the database
@@ -18,25 +18,27 @@ Liquid is a template engine which I wrote for very specific requirements
18
18
 
19
19
  ## What does it look like?
20
20
 
21
- <code>
22
- <ul id="products">
23
- {% for product in products %}
24
- <li>
25
- <h2>{{product.name}}</h2>
26
- Only {{product.price | price }}
21
+ ```html
22
+ <ul id="products">
23
+ {% for product in products %}
24
+ <li>
25
+ <h2>{{ product.name }}</h2>
26
+ Only {{ product.price | price }}
27
27
 
28
- {{product.description | prettyprint | paragraph }}
29
- </li>
30
- {% endfor %}
31
- </ul>
32
- </code>
28
+ {{ product.description | prettyprint | paragraph }}
29
+ </li>
30
+ {% endfor %}
31
+ </ul>
32
+ ```
33
33
 
34
34
  ## Howto use Liquid
35
35
 
36
36
  Liquid supports a very simple API based around the Liquid::Template class.
37
37
  For standard use you can just pass it the content of a file and call render with a parameters hash.
38
38
 
39
- <pre>
39
+ ```ruby
40
40
  @template = Liquid::Template.parse("hi {{name}}") # Parses and compiles the template
41
- @template.render( 'name' => 'tobi' ) # => "hi tobi"
42
- </pre>
41
+ @template.render('name' => 'tobi') # => "hi tobi"
42
+ ```
43
+
44
+ [![Build Status](https://secure.travis-ci.org/Shopify/liquid.png)](http://travis-ci.org/Shopify/liquid)
@@ -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 = ','
@@ -35,7 +33,7 @@ module Liquid
35
33
  VariableIncompleteEnd = /\}\}?/
36
34
  QuotedString = /"[^"]*"|'[^']*'/
37
35
  QuotedFragment = /#{QuotedString}|(?:[^\s,\|'"]|#{QuotedString})+/
38
- StrictQuotedFragment = /"[^"]+"|'[^']+'|[^\s,\|,\:,\,]+/
36
+ StrictQuotedFragment = /"[^"]+"|'[^']+'|[^\s|:,]+/
39
37
  FirstFilterArgument = /#{FilterArgumentSeparator}(?:#{StrictQuotedFragment})/
40
38
  OtherFilterArgument = /#{ArgumentSeparator}(?:#{StrictQuotedFragment})/
41
39
  SpacelessFilter = /^(?:'[^']+'|"[^"]+"|[^'"])*#{FilterSeparator}(?:#{StrictQuotedFragment})(?:#{FirstFilterArgument}(?:#{OtherFilterArgument})*)?/
@@ -45,7 +43,6 @@ module Liquid
45
43
  PartialTemplateParser = /#{TagStart}.*?#{TagEnd}|#{VariableStart}.*?#{VariableIncompleteEnd}/
46
44
  TemplateParser = /(#{PartialTemplateParser}|#{AnyStartingTag})/
47
45
  VariableParser = /\[[^\]]+\]|#{VariableSegment}+\??/
48
- LiteralShorthand = /^(?:\{\{\{\s?)(.*?)(?:\s*\}\}\})$/
49
46
  end
50
47
 
51
48
  require 'liquid/drop'
@@ -92,10 +92,10 @@ module Liquid
92
92
  list.collect do |token|
93
93
  begin
94
94
  token.respond_to?(:render) ? token.render(context) : token
95
- rescue Exception => e
95
+ rescue ::StandardError => e
96
96
  context.handle_error(e)
97
97
  end
98
- end
98
+ end.join
99
99
  end
100
100
  end
101
101
  end
@@ -63,8 +63,8 @@ module Liquid
63
63
 
64
64
  # Push new local scope on the stack. use <tt>Context#stack</tt> instead
65
65
  def push(new_scope={})
66
- raise StackLevelError, "Nesting too deep" if @scopes.length > 100
67
66
  @scopes.unshift(new_scope)
67
+ raise StackLevelError, "Nesting too deep" if @scopes.length > 100
68
68
  end
69
69
 
70
70
  # Merge a hash of variables in the current local scope
@@ -86,17 +86,11 @@ module Liquid
86
86
  # end
87
87
  #
88
88
  # context['var] #=> nil
89
- def stack(new_scope={},&block)
90
- result = nil
89
+ def stack(new_scope={})
91
90
  push(new_scope)
92
-
93
- begin
94
- result = yield
95
- ensure
96
- pop
97
- end
98
-
99
- result
91
+ yield
92
+ ensure
93
+ pop
100
94
  end
101
95
 
102
96
  def clear_instance_assigns
@@ -117,6 +111,14 @@ module Liquid
117
111
  end
118
112
 
119
113
  private
114
+ LITERALS = {
115
+ nil => nil, 'nil' => nil, 'null' => nil, '' => nil,
116
+ 'true' => true,
117
+ 'false' => false,
118
+ 'blank' => :blank?,
119
+ 'empty' => :empty?
120
+ }
121
+
120
122
  # Look up variable, either resolve directly after considering the name. We can directly handle
121
123
  # Strings, digits, floats and booleans (true,false).
122
124
  # If no match is made we lookup the variable in the current scope and
@@ -126,29 +128,23 @@ module Liquid
126
128
  # Example:
127
129
  # products == empty #=> products.empty?
128
130
  def resolve(key)
129
- case key
130
- when nil, 'nil', 'null', ''
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
131
+ if LITERALS.key?(key)
132
+ LITERALS[key]
150
133
  else
151
- variable(key)
134
+ case key
135
+ when /^'(.*)'$/ # Single quoted strings
136
+ $1
137
+ when /^"(.*)"$/ # Double quoted strings
138
+ $1
139
+ when /^(\d+)$/ # Integer and floats
140
+ $1.to_i
141
+ when /^\((\S+)\.\.(\S+)\)$/ # Ranges
142
+ (resolve($1).to_i..resolve($2).to_i)
143
+ when /^(\d[\d\.]+)$/ # Floats
144
+ $1.to_f
145
+ else
146
+ variable(key)
147
+ end
152
148
  end
153
149
  end
154
150
 
@@ -1,6 +1,6 @@
1
1
  module Liquid
2
2
 
3
- # A drop in liquid is a class which allows you to to export DOM like things to liquid
3
+ # A drop in liquid is a class which allows you to to export DOM like things to liquid.
4
4
  # Methods of drops are callable.
5
5
  # The main use for liquid drops is the 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
@@ -8,14 +8,14 @@ module Liquid
8
8
  #
9
9
  # Example:
10
10
  #
11
- # class ProductDrop < Liquid::Drop
12
- # def top_sales
13
- # Shop.current.products.find(:all, :order => 'sales', :limit => 10 )
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
- # tmpl = Liquid::Template.parse( ' {% for product in product.top_sales %} {{ product.name }} {%endfor%} ' )
18
- # tmpl.render('product' => ProductDrop.new ) # will invoke top_sales query.
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
21
  # catch all
@@ -28,13 +28,11 @@ module Liquid
28
28
  end
29
29
 
30
30
  # called by liquid to invoke a drop
31
- def invoke_drop(method)
32
- # for backward compatibility with Ruby 1.8
33
- methods = self.class.public_instance_methods.map { |m| m.to_s }
34
- if methods.include?(method.to_s)
35
- send(method.to_sym)
31
+ def invoke_drop(method_or_key)
32
+ if method_or_key && method_or_key != '' && self.class.public_method_defined?(method_or_key.to_s.to_sym)
33
+ send(method_or_key.to_s.to_sym)
36
34
  else
37
- before_method(method)
35
+ before_method(method_or_key)
38
36
  end
39
37
  end
40
38
 
@@ -43,14 +43,20 @@ class Date # :nodoc:
43
43
  end
44
44
  end
45
45
 
46
- def true.to_liquid # :nodoc:
47
- self
46
+ class TrueClass
47
+ def to_liquid # :nodoc:
48
+ self
49
+ end
48
50
  end
49
51
 
50
- def false.to_liquid # :nodoc:
51
- self
52
+ class FalseClass
53
+ def to_liquid # :nodoc:
54
+ self
55
+ end
52
56
  end
53
57
 
54
- def nil.to_liquid # :nodoc:
55
- self
56
- end
58
+ class NilClass
59
+ def to_liquid # :nodoc:
60
+ self
61
+ end
62
+ end
@@ -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
 
@@ -33,7 +33,7 @@ module Liquid
33
33
  row = 1
34
34
  col = 0
35
35
 
36
- result = ["<tr class=\"row1\">\n"]
36
+ result = "<tr class=\"row1\">\n"
37
37
  context.stack do
38
38
 
39
39
  collection.each_with_index do |item, index|
@@ -56,17 +56,18 @@ module Liquid
56
56
 
57
57
  col += 1
58
58
 
59
- result << ["<td class=\"col#{col}\">"] + render_all(@nodelist, context) + ['</td>']
59
+ result << "<td class=\"col#{col}\">" << render_all(@nodelist, context) << '</td>'
60
60
 
61
61
  if col == cols and not (index == length - 1)
62
62
  col = 0
63
63
  row += 1
64
- result << ["</tr>\n<tr class=\"row#{row}\">"]
64
+ result << "</tr>\n<tr class=\"row#{row}\">"
65
65
  end
66
66
 
67
67
  end
68
68
  end
69
- result + ["</tr>\n"]
69
+ result << "</tr>\n"
70
+ result
70
71
  end
71
72
  end
72
73
 
@@ -30,7 +30,9 @@ module Liquid
30
30
  end
31
31
 
32
32
  def escape_once(input)
33
- ActionView::Helpers::TagHelper.escape_once(input) rescue input
33
+ ActionView::Helpers::TagHelper.escape_once(input)
34
+ rescue NameError
35
+ input
34
36
  end
35
37
 
36
38
  alias_method :h, :escape
@@ -51,6 +53,11 @@ module Liquid
51
53
  wordlist.length > l ? wordlist[0..l].join(" ") + truncate_string : input
52
54
  end
53
55
 
56
+ # Split input string into an array of substrings separated by given pattern.
57
+ def split(input, pattern)
58
+ input.split(pattern)
59
+ end
60
+
54
61
  def strip_html(input)
55
62
  input.to_s.gsub(/<script.*?<\/script>/, '').gsub(/<.*?>/, '')
56
63
  end
@@ -158,6 +165,10 @@ module Liquid
158
165
  return input.to_s
159
166
  end
160
167
 
168
+ if ((input.is_a?(String) && !/^\d+$/.match(input.to_s).nil?) || input.is_a?(Integer)) && input.to_i > 0
169
+ input = Time.at(input.to_i)
170
+ end
171
+
161
172
  date = input.is_a?(String) ? Time.parse(input) : input
162
173
 
163
174
  if date.respond_to?(:strftime)
@@ -14,7 +14,7 @@ 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
18
 
19
19
  # Ruby 1.9.2 introduces Object#respond_to_missing?, which is invoked by Object#respond_to?
20
20
  @@required_methods << :respond_to_missing? if Object.respond_to? :respond_to_missing?
@@ -26,7 +26,7 @@ module Liquid
26
26
 
27
27
  def render(context)
28
28
  output = super
29
- context.scopes.last[@to] = output.join
29
+ context.scopes.last[@to] = output
30
30
  ''
31
31
  end
32
32
  end
@@ -31,20 +31,16 @@ module Liquid
31
31
  context.stack do
32
32
  execute_else_block = true
33
33
 
34
- @blocks.inject([]) do |output, block|
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 += render_all(block.attachment, context)
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