liquid 1.7.0 → 1.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. data/CHANGELOG +17 -15
  2. data/History.txt +44 -0
  3. data/MIT-LICENSE +2 -2
  4. data/Manifest.txt +6 -1
  5. data/{README → README.txt} +0 -0
  6. data/Rakefile +3 -3
  7. data/init.rb +5 -3
  8. data/lib/liquid.rb +8 -6
  9. data/lib/liquid/block.rb +6 -9
  10. data/lib/liquid/condition.rb +49 -17
  11. data/lib/liquid/context.rb +67 -41
  12. data/lib/liquid/errors.rb +8 -5
  13. data/lib/liquid/htmltags.rb +17 -7
  14. data/lib/liquid/module_ex.rb +62 -0
  15. data/lib/liquid/standardfilters.rb +39 -0
  16. data/lib/liquid/strainer.rb +20 -11
  17. data/lib/liquid/tag.rb +4 -3
  18. data/lib/liquid/tags/assign.rb +15 -4
  19. data/lib/liquid/tags/capture.rb +15 -2
  20. data/lib/liquid/tags/case.rb +51 -36
  21. data/lib/liquid/tags/cycle.rb +16 -2
  22. data/lib/liquid/tags/for.rb +45 -8
  23. data/lib/liquid/tags/if.rb +35 -7
  24. data/lib/liquid/tags/include.rb +2 -3
  25. data/lib/liquid/tags/unless.rb +6 -2
  26. data/lib/liquid/template.rb +13 -18
  27. data/lib/liquid/variable.rb +25 -12
  28. data/test/block_test.rb +8 -0
  29. data/test/condition_test.rb +109 -0
  30. data/test/context_test.rb +88 -10
  31. data/test/drop_test.rb +3 -1
  32. data/test/error_handling_test.rb +16 -3
  33. data/test/extra/breakpoint.rb +0 -0
  34. data/test/extra/caller.rb +0 -0
  35. data/test/filter_test.rb +3 -3
  36. data/test/html_tag_test.rb +7 -0
  37. data/test/if_else_test.rb +32 -0
  38. data/test/include_tag_test.rb +24 -1
  39. data/test/module_ex_test.rb +89 -0
  40. data/test/parsing_quirks_test.rb +15 -0
  41. data/test/regexp_test.rb +4 -3
  42. data/test/standard_filter_test.rb +27 -2
  43. data/test/standard_tag_test.rb +67 -20
  44. data/test/test_helper.rb +20 -0
  45. data/test/unless_else_test.rb +8 -0
  46. metadata +60 -46
data/lib/liquid/errors.rb CHANGED
@@ -1,7 +1,10 @@
1
1
  module Liquid
2
- class FilterNotFound < StandardError
3
- end
4
-
5
- class FileSystemError < StandardError
6
- end
2
+ class Error < ::StandardError; end
3
+
4
+ class ArgumentError < Error; end
5
+ class ContextError < Error; end
6
+ class FilterNotFound < Error; end
7
+ class FileSystemError < Error; end
8
+ class StandardError < Error; end
9
+ class SyntaxError < Error; end
7
10
  end
@@ -2,9 +2,7 @@ module Liquid
2
2
  class TableRow < Block
3
3
  Syntax = /(\w+)\s+in\s+(#{VariableSignature}+)/
4
4
 
5
- def initialize(markup, tokens)
6
- super
7
-
5
+ def initialize(tag_name, markup, tokens)
8
6
  if markup =~ Syntax
9
7
  @variable_name = $1
10
8
  @collection_name = $2
@@ -15,6 +13,8 @@ module Liquid
15
13
  else
16
14
  raise SyntaxError.new("Syntax Error in 'table_row loop' - Valid syntax: table_row [item] in [collection] cols=3")
17
15
  end
16
+
17
+ super
18
18
  end
19
19
 
20
20
  def render(context)
@@ -42,16 +42,26 @@ module Liquid
42
42
  'length' => length,
43
43
  'index' => index + 1,
44
44
  'index0' => index,
45
+ 'col' => col + 1,
46
+ 'col0' => col,
47
+ 'index0' => index,
45
48
  'rindex' => length - index,
46
49
  'rindex0' => length - index -1,
47
50
  'first' => (index == 0),
48
- 'last' => (index == length - 1) }
51
+ 'last' => (index == length - 1),
52
+ 'col_first' => (col == 0),
53
+ 'col_last' => (col == cols - 1)
54
+ }
55
+
56
+
57
+ col += 1
49
58
 
50
- result << ["<td class=\"col#{col += 1}\">"] + render_all(@nodelist, context) + ['</td>']
59
+ result << ["<td class=\"col#{col}\">"] + render_all(@nodelist, context) + ['</td>']
51
60
 
52
- if col == cols and not (index == length - 1)
61
+ if col == cols and not (index == length - 1)
53
62
  col = 0
54
- result << ["</tr>\n<tr class=\"row#{row += 1}\">"]
63
+ row += 1
64
+ result << ["</tr>\n<tr class=\"row#{row}\">"]
55
65
  end
56
66
 
57
67
  end
@@ -0,0 +1,62 @@
1
+ # Copyright 2007 by Domizio Demichelis
2
+ # This library is free software. It may be used, redistributed and/or modified
3
+ # under the same terms as Ruby itself
4
+ #
5
+ # This extension is usesd in order to expose the object of the implementing class
6
+ # to liquid as it were a Drop. It also limits the liquid-callable methods of the instance
7
+ # to the allowed method passed with the liquid_methods call
8
+ # Example:
9
+ #
10
+ # class SomeClass
11
+ # liquid_methods :an_allowed_method
12
+ #
13
+ # def an_allowed_method
14
+ # 'this comes from an allowed method'
15
+ # end
16
+ # def unallowed_method
17
+ # 'this will never be an output'
18
+ # end
19
+ # end
20
+ #
21
+ # if you want to extend the drop to other methods you can defines more methods
22
+ # in the class <YourClass>::LiquidDropClass
23
+ #
24
+ # class SomeClass::LiquidDropClass
25
+ # def another_allowed_method
26
+ # 'and this from another allowed method'
27
+ # end
28
+ # end
29
+ # end
30
+ #
31
+ # usage:
32
+ # @something = SomeClass.new
33
+ #
34
+ # template:
35
+ # {{something.an_allowed_method}}{{something.unallowed_method}} {{something.another_allowed_method}}
36
+ #
37
+ # output:
38
+ # 'this comes from an allowed method and this from another allowed method'
39
+ #
40
+ # You can also chain associations, by adding the liquid_method call in the
41
+ # association models.
42
+ #
43
+ class Module
44
+
45
+ def liquid_methods(*allowed_methods)
46
+ drop_class = eval "class #{self.to_s}::LiquidDropClass < Liquid::Drop; self; end"
47
+ define_method :to_liquid do
48
+ drop_class.new(self)
49
+ end
50
+ drop_class.class_eval do
51
+ def initialize(object)
52
+ @object = object
53
+ end
54
+ allowed_methods.each do |sym|
55
+ define_method sym do
56
+ @object.send sym
57
+ end
58
+ end
59
+ end
60
+ end
61
+
62
+ end
@@ -1,3 +1,5 @@
1
+ require 'cgi'
2
+
1
3
  module Liquid
2
4
 
3
5
  module StandardFilters
@@ -22,6 +24,12 @@ module Liquid
22
24
  def capitalize(input)
23
25
  input.to_s.capitalize
24
26
  end
27
+
28
+ def escape(input)
29
+ CGI.escapeHTML(input) rescue input
30
+ end
31
+
32
+ alias_method :h, :escape
25
33
 
26
34
  # Truncate a string down to x characters
27
35
  def truncate(input, length = 50, truncate_string = "...")
@@ -41,8 +49,14 @@ module Liquid
41
49
 
42
50
  def strip_html(input)
43
51
  input.to_s.gsub(/<.*?>/, '')
52
+ end
53
+
54
+ # Remove all newlines from the string
55
+ def strip_newlines(input)
56
+ input.to_s.gsub(/\n/, '')
44
57
  end
45
58
 
59
+
46
60
  # Join elements of the array with certain character between them
47
61
  def join(input, glue = ' ')
48
62
  [input].flatten.join(glue)
@@ -51,6 +65,31 @@ module Liquid
51
65
  # Sort elements of the array
52
66
  def sort(input)
53
67
  [input].flatten.sort
68
+ end
69
+
70
+ # Replace occurrences of a string with another
71
+ def replace(input, string, replacement = '')
72
+ input.to_s.gsub(string, replacement)
73
+ end
74
+
75
+ # Replace the first occurrences of a string with another
76
+ def replace_first(input, string, replacement = '')
77
+ input.to_s.sub(string, replacement)
78
+ end
79
+
80
+ # remove a substring
81
+ def remove(input, string)
82
+ input.to_s.gsub(string, '')
83
+ end
84
+
85
+ # remove the first occurrences of a substring
86
+ def remove_first(input, string)
87
+ input.to_s.sub(string, '')
88
+ end
89
+
90
+ # Add <br /> tags in front of all newlines in input string
91
+ def newline_to_br(input)
92
+ input.to_s.gsub(/\n/, "<br />\n")
54
93
  end
55
94
 
56
95
  # Reformat a date
@@ -1,33 +1,42 @@
1
+ require 'set'
2
+
1
3
  module Liquid
4
+
5
+
6
+ parent_object = if defined? BlankObject
7
+ BlankObject
8
+ else
9
+ Object
10
+ end
2
11
 
3
12
  # Strainer is the parent class for the filters system.
4
13
  # New filters are mixed into the strainer class which is then instanciated for each liquid template render run.
5
14
  #
6
15
  # One of the strainer's responsibilities is to keep malicious method calls out
7
- class Strainer
8
-
9
- @@required_methods = ["__send__", "__id__", "respond_to?", "extend", "methods"]
16
+ class Strainer < parent_object #:nodoc:
17
+ INTERNAL_METHOD = /^__/
18
+ @@required_methods = Set.new([:__send__, :__id__, :respond_to?, :extend, :methods, :class])
10
19
 
11
- @@filters = []
20
+ @@filters = {}
12
21
 
13
22
  def initialize(context)
14
23
  @context = context
15
24
  end
16
25
 
17
26
  def self.global_filter(filter)
18
- raise StandardError, "Passed filter is not a module" unless filter.is_a?(Module)
19
- @@filters << filter
27
+ raise ArgumentError, "Passed filter is not a module" unless filter.is_a?(Module)
28
+ @@filters[filter.name] = filter
20
29
  end
21
30
 
22
31
  def self.create(context)
23
32
  strainer = Strainer.new(context)
24
- @@filters.each { |m| strainer.extend(m) }
33
+ @@filters.each { |k,m| strainer.extend(m) }
25
34
  strainer
26
35
  end
27
36
 
28
37
  def respond_to?(method)
29
38
  method_name = method.to_s
30
- return false if method_name =~ /^__/
39
+ return false if method_name =~ INTERNAL_METHOD
31
40
  return false if @@required_methods.include?(method_name)
32
41
  super
33
42
  end
@@ -35,9 +44,9 @@ module Liquid
35
44
  # remove all standard methods from the bucket so circumvent security
36
45
  # problems
37
46
  instance_methods.each do |m|
38
- unless @@required_methods.include?(m)
39
- undef_method m
47
+ unless @@required_methods.include?(m.to_sym)
48
+ undef_method m
40
49
  end
41
50
  end
42
51
  end
43
- end
52
+ end
data/lib/liquid/tag.rb CHANGED
@@ -3,8 +3,9 @@ module Liquid
3
3
  class Tag
4
4
  attr_accessor :nodelist
5
5
 
6
- def initialize(markup, tokens)
7
- @markup = markup
6
+ def initialize(tag_name, markup, tokens)
7
+ @tag_name = tag_name
8
+ @markup = markup
8
9
  parse(tokens)
9
10
  end
10
11
 
@@ -14,7 +15,7 @@ module Liquid
14
15
  def name
15
16
  self.class.name.downcase
16
17
  end
17
-
18
+
18
19
  def render(context)
19
20
  ''
20
21
  end
@@ -1,19 +1,30 @@
1
1
  module Liquid
2
+
3
+ # Assign sets a variable in your template.
4
+ #
5
+ # {% assign foo = 'monkey' %}
6
+ #
7
+ # You can then use the variable later in the page.
8
+ #
9
+ # {{ monkey }}
10
+ #
2
11
  class Assign < Tag
3
- Syntax = /(\w+)\s*=\s*(#{QuotedFragment}+)/
12
+ Syntax = /(#{VariableSignature}+)\s*=\s*(#{QuotedFragment}+)/
4
13
 
5
- def initialize(markup, tokens)
14
+ def initialize(tag_name, markup, tokens)
6
15
  if markup =~ Syntax
7
16
  @to = $1
8
17
  @from = $2
9
18
  else
10
19
  raise SyntaxError.new("Syntax Error in 'assign' - Valid syntax: assign [var] = [source]")
11
20
  end
21
+
22
+ super
12
23
  end
13
24
 
14
25
  def render(context)
15
- context[@to] = context[@from]
16
- ''
26
+ context.scopes.last[@to.to_s] = context[@from]
27
+ ''
17
28
  end
18
29
 
19
30
  end
@@ -1,14 +1,27 @@
1
1
  module Liquid
2
+
3
+ # Capture stores the result of a block into a variable without rendering it inplace.
4
+ #
5
+ # {% capture heading %}
6
+ # Monkeys!
7
+ # {% endcapture %}
8
+ # ...
9
+ # <h1>{{ monkeys }}</h1>
10
+ #
11
+ # Capture is useful for saving content for use later in your template, such as
12
+ # in a sidebar or footer.
13
+ #
2
14
  class Capture < Block
3
15
  Syntax = /(\w+)/
4
16
 
5
- def initialize(markup, tokens)
17
+ def initialize(tag_name, markup, tokens)
6
18
  if markup =~ Syntax
7
19
  @to = $1
8
- super
9
20
  else
10
21
  raise SyntaxError.new("Syntax Error in 'capture' - Valid syntax: capture [var]")
11
22
  end
23
+
24
+ super
12
25
  end
13
26
 
14
27
  def render(context)
@@ -1,9 +1,9 @@
1
1
  module Liquid
2
2
  class Case < Block
3
3
  Syntax = /(#{QuotedFragment})/
4
- WhenSyntax = /(#{QuotedFragment})/
4
+ WhenSyntax = /(#{QuotedFragment})(?:(?:\s+or\s+|\s*\,\s*)(#{QuotedFragment}.*))?/
5
5
 
6
- def initialize(markup, tokens)
6
+ def initialize(tag_name, markup, tokens)
7
7
  @blocks = []
8
8
 
9
9
  if markup =~ Syntax
@@ -11,58 +11,73 @@ module Liquid
11
11
  else
12
12
  raise SyntaxError.new("Syntax Error in tag 'case' - Valid syntax: case [condition]")
13
13
  end
14
-
15
- push_block('case', markup)
16
-
14
+
17
15
  super
18
16
  end
19
17
 
20
18
  def unknown_tag(tag, markup, tokens)
21
- if ['when', 'else'].include?(tag)
22
- push_block(tag, markup)
19
+ @nodelist = []
20
+ case tag
21
+ when 'when'
22
+ record_when_condition(markup)
23
+ when 'else'
24
+ record_else_condition(markup)
23
25
  else
24
26
  super
25
27
  end
26
28
  end
27
29
 
28
- def render(context)
29
- @blocks.inject([]) do |output, block|
30
-
31
- if block.else?
32
- return render_all(block.attachment, context) if output.empty? || output.join !~ /\S/
33
- else
34
-
35
- if block.evaluate(context)
36
- context.stack do
37
- output += render_all(block.attachment, context)
38
- end
39
- end
40
-
41
- end
42
-
30
+ def render(context)
31
+ context.stack do
32
+ execute_else_block = true
43
33
 
44
- output
45
- end.join
34
+ @blocks.inject([]) do |output, block|
35
+
36
+ if block.else?
37
+
38
+ return render_all(block.attachment, context) if execute_else_block
39
+
40
+ elsif block.evaluate(context)
41
+
42
+ execute_else_block = false
43
+ output += render_all(block.attachment, context)
44
+ end
45
+
46
+ output
47
+ end
48
+ end
46
49
  end
47
50
 
48
51
  private
49
52
 
50
- def push_block(tag, markup)
51
-
52
- block = if tag == 'else'
53
- ElseCondition.new
54
- elsif markup =~ WhenSyntax
55
- Condition.new(@left, '==', $1)
56
- else
57
- raise SyntaxError.new("Syntax Error in tag 'case' - Valid when condition: when [condition] ")
53
+ def record_when_condition(markup)
54
+ while markup
55
+ # Create a new nodelist and assign it to the new block
56
+ if not markup =~ WhenSyntax
57
+ raise SyntaxError.new("Syntax Error in tag 'case' - Valid when condition: {% when [condition] [or condition2...] %} ")
58
+ end
59
+
60
+ markup = $2
61
+
62
+ block = Condition.new(@left, '==', $1)
63
+ block.attach(@nodelist)
64
+ @blocks.push(block)
58
65
  end
59
-
60
- @blocks.push(block)
61
- @nodelist = block.attach(Array.new)
66
+ end
67
+
68
+ def record_else_condition(markup)
69
+
70
+ if not markup.strip.empty?
71
+ raise SyntaxError.new("Syntax Error in tag 'case' - Valid else condition: {% else %} (no parameters) ")
72
+ end
73
+
74
+ block = ElseCondition.new
75
+ block.attach(@nodelist)
76
+ @blocks << block
62
77
  end
63
78
 
64
79
 
65
80
  end
66
81
 
67
82
  Template.register_tag('case', Case)
68
- end
83
+ end