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
@@ -0,0 +1,64 @@
1
+ module Liquid
2
+ class TableRow < Block
3
+ Syntax = /(\w+)\s+in\s+(#{VariableSignature}+)/
4
+
5
+ def initialize(markup, tokens)
6
+ super
7
+
8
+ if markup =~ Syntax
9
+ @variable_name = $1
10
+ @collection_name = $2
11
+ @attributes = {}
12
+ markup.scan(TagAttributes) do |key, value|
13
+ @attributes[key] = value
14
+ end
15
+ else
16
+ raise SyntaxError.new("Syntax Error in 'table_row loop' - Valid syntax: table_row [item] in [collection] cols=3")
17
+ end
18
+ end
19
+
20
+ def render(context)
21
+ collection = context[@collection_name] or return ''
22
+
23
+ if @attributes['limit'] or @attributes['offset']
24
+ limit = context[@attributes['limit']] || -1
25
+ offset = context[@attributes['offset']] || 0
26
+ collection = collection[offset.to_i..(limit.to_i + offset.to_i - 1)]
27
+ end
28
+
29
+ length = collection.length
30
+
31
+ cols = context[@attributes['cols']].to_i
32
+
33
+ row = 1
34
+ col = 0
35
+
36
+ result = ["<tr class=\"row1\">\n"]
37
+ context.stack do
38
+
39
+ collection.each_with_index do |item, index|
40
+ context[@variable_name] = item
41
+ context['tablerowloop'] = {
42
+ 'length' => length,
43
+ 'index' => index + 1,
44
+ 'index0' => index,
45
+ 'rindex' => length - index,
46
+ 'rindex0' => length - index -1,
47
+ 'first' => (index == 0),
48
+ 'last' => (index == length - 1) }
49
+
50
+ result << ["<td class=\"col#{col += 1}\">"] + render_all(@nodelist, context) + ['</td>']
51
+
52
+ if col == cols and not (index == length - 1)
53
+ col = 0
54
+ result << ["</tr>\n<tr class=\"row#{row += 1}\">"]
55
+ end
56
+
57
+ end
58
+ end
59
+ result + ["</tr>\n"]
60
+ end
61
+ end
62
+
63
+ Template.register_tag('tablerow', TableRow)
64
+ end
@@ -0,0 +1,125 @@
1
+ module Liquid
2
+
3
+ module StandardFilters
4
+
5
+ # Return the size of an array or of an string
6
+ def size(input)
7
+
8
+ input.respond_to?(:size) ? input.size : 0
9
+ end
10
+
11
+ # convert a input string to DOWNCASE
12
+ def downcase(input)
13
+ input.to_s.downcase
14
+ end
15
+
16
+ # convert a input string to UPCASE
17
+ def upcase(input)
18
+ input.to_s.upcase
19
+ end
20
+
21
+ # capitalize words in the input centence
22
+ def capitalize(input)
23
+ input.to_s.capitalize
24
+ end
25
+
26
+ # Truncate a string down to x characters
27
+ def truncate(input, length = 50, truncate_string = "...")
28
+ if input.nil? then return end
29
+ l = length.to_i - truncate_string.length
30
+ l = 0 if l < 0
31
+ input.length > length.to_i ? input[0...l] + truncate_string : input
32
+ end
33
+
34
+ def truncatewords(input, words = 15, truncate_string = "...")
35
+ if input.nil? then return end
36
+ wordlist = input.to_s.split
37
+ l = words.to_i - 1
38
+ l = 0 if l < 0
39
+ wordlist.length > l ? wordlist[0..l].join(" ") + truncate_string : input
40
+ end
41
+
42
+ def strip_html(input)
43
+ input.to_s.gsub(/<.*?>/, '')
44
+ end
45
+
46
+ # Join elements of the array with certain character between them
47
+ def join(input, glue = ' ')
48
+ [input].flatten.join(glue)
49
+ end
50
+
51
+ # Sort elements of the array
52
+ def sort(input)
53
+ [input].flatten.sort
54
+ end
55
+
56
+ # Reformat a date
57
+ #
58
+ # %a - The abbreviated weekday name (``Sun'')
59
+ # %A - The full weekday name (``Sunday'')
60
+ # %b - The abbreviated month name (``Jan'')
61
+ # %B - The full month name (``January'')
62
+ # %c - The preferred local date and time representation
63
+ # %d - Day of the month (01..31)
64
+ # %H - Hour of the day, 24-hour clock (00..23)
65
+ # %I - Hour of the day, 12-hour clock (01..12)
66
+ # %j - Day of the year (001..366)
67
+ # %m - Month of the year (01..12)
68
+ # %M - Minute of the hour (00..59)
69
+ # %p - Meridian indicator (``AM'' or ``PM'')
70
+ # %S - Second of the minute (00..60)
71
+ # %U - Week number of the current year,
72
+ # starting with the first Sunday as the first
73
+ # day of the first week (00..53)
74
+ # %W - Week number of the current year,
75
+ # starting with the first Monday as the first
76
+ # day of the first week (00..53)
77
+ # %w - Day of the week (Sunday is 0, 0..6)
78
+ # %x - Preferred representation for the date alone, no time
79
+ # %X - Preferred representation for the time alone, no date
80
+ # %y - Year without a century (00..99)
81
+ # %Y - Year with century
82
+ # %Z - Time zone name
83
+ # %% - Literal ``%'' character
84
+ def date(input, format)
85
+
86
+ if format.to_s.empty?
87
+ return input.to_s
88
+ end
89
+
90
+ date = case input
91
+ when String
92
+ Time.parse(input)
93
+ when Date, Time, DateTime
94
+ input
95
+ else
96
+ return input
97
+ end
98
+
99
+ date.strftime(format.to_s)
100
+ rescue => e
101
+ input
102
+ end
103
+
104
+ # Get the first element of the passed in array
105
+ #
106
+ # Example:
107
+ # {{ product.images | first | to_img }}
108
+ #
109
+ def first(array)
110
+ array.first if array.respond_to?(:first)
111
+ end
112
+
113
+ # Get the last element of the passed in array
114
+ #
115
+ # Example:
116
+ # {{ product.images | last | to_img }}
117
+ #
118
+ def last(array)
119
+ array.last if array.respond_to?(:last)
120
+ end
121
+
122
+ end
123
+
124
+ Template.register_filter(StandardFilters)
125
+ end
@@ -0,0 +1,43 @@
1
+ module Liquid
2
+
3
+ # Strainer is the parent class for the filters system.
4
+ # New filters are mixed into the strainer class which is then instanciated for each liquid template render run.
5
+ #
6
+ # 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"]
10
+
11
+ @@filters = []
12
+
13
+ def initialize(context)
14
+ @context = context
15
+ end
16
+
17
+ def self.global_filter(filter)
18
+ raise StandardError, "Passed filter is not a module" unless filter.is_a?(Module)
19
+ @@filters << filter
20
+ end
21
+
22
+ def self.create(context)
23
+ strainer = Strainer.new(context)
24
+ @@filters.each { |m| strainer.extend(m) }
25
+ strainer
26
+ end
27
+
28
+ def respond_to?(method)
29
+ method_name = method.to_s
30
+ return false if method_name =~ /^__/
31
+ return false if @@required_methods.include?(method_name)
32
+ super
33
+ end
34
+
35
+ # remove all standard methods from the bucket so circumvent security
36
+ # problems
37
+ instance_methods.each do |m|
38
+ unless @@required_methods.include?(m)
39
+ undef_method m
40
+ end
41
+ end
42
+ end
43
+ end
data/lib/liquid/tag.rb ADDED
@@ -0,0 +1,25 @@
1
+ module Liquid
2
+
3
+ class Tag
4
+ attr_accessor :nodelist
5
+
6
+ def initialize(markup, tokens)
7
+ @markup = markup
8
+ parse(tokens)
9
+ end
10
+
11
+ def parse(tokens)
12
+ end
13
+
14
+ def name
15
+ self.class.name.downcase
16
+ end
17
+
18
+ def render(context)
19
+ ''
20
+ end
21
+ end
22
+
23
+
24
+ end
25
+
@@ -0,0 +1,22 @@
1
+ module Liquid
2
+ class Assign < Tag
3
+ Syntax = /(\w+)\s*=\s*(#{QuotedFragment}+)/
4
+
5
+ def initialize(markup, tokens)
6
+ if markup =~ Syntax
7
+ @to = $1
8
+ @from = $2
9
+ else
10
+ raise SyntaxError.new("Syntax Error in 'assign' - Valid syntax: assign [var] = [source]")
11
+ end
12
+ end
13
+
14
+ def render(context)
15
+ context[@to] = context[@from]
16
+ ''
17
+ end
18
+
19
+ end
20
+
21
+ Template.register_tag('assign', Assign)
22
+ end
@@ -0,0 +1,22 @@
1
+ module Liquid
2
+ class Capture < Block
3
+ Syntax = /(\w+)/
4
+
5
+ def initialize(markup, tokens)
6
+ if markup =~ Syntax
7
+ @to = $1
8
+ super
9
+ else
10
+ raise SyntaxError.new("Syntax Error in 'capture' - Valid syntax: capture [var]")
11
+ end
12
+ end
13
+
14
+ def render(context)
15
+ output = super
16
+ context[@to] = output.to_s
17
+ ''
18
+ end
19
+ end
20
+
21
+ Template.register_tag('capture', Capture)
22
+ end
@@ -0,0 +1,68 @@
1
+ module Liquid
2
+ class Case < Block
3
+ Syntax = /(#{QuotedFragment})/
4
+ WhenSyntax = /(#{QuotedFragment})/
5
+
6
+ def initialize(markup, tokens)
7
+ @blocks = []
8
+
9
+ if markup =~ Syntax
10
+ @left = $1
11
+ else
12
+ raise SyntaxError.new("Syntax Error in tag 'case' - Valid syntax: case [condition]")
13
+ end
14
+
15
+ push_block('case', markup)
16
+
17
+ super
18
+ end
19
+
20
+ def unknown_tag(tag, markup, tokens)
21
+ if ['when', 'else'].include?(tag)
22
+ push_block(tag, markup)
23
+ else
24
+ super
25
+ end
26
+ end
27
+
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
+
43
+
44
+ output
45
+ end.join
46
+ end
47
+
48
+ private
49
+
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] ")
58
+ end
59
+
60
+ @blocks.push(block)
61
+ @nodelist = block.attach(Array.new)
62
+ end
63
+
64
+
65
+ end
66
+
67
+ Template.register_tag('case', Case)
68
+ end
@@ -0,0 +1,9 @@
1
+ module Liquid
2
+ class Comment < Block
3
+ def render(context)
4
+ ''
5
+ end
6
+ end
7
+
8
+ Template.register_tag('comment', Comment)
9
+ end
@@ -0,0 +1,46 @@
1
+ module Liquid
2
+ class Cycle < Tag
3
+ SimpleSyntax = /#{QuotedFragment}/
4
+ NamedSyntax = /(#{QuotedFragment})\s*\:\s*(.*)/
5
+
6
+ def initialize(markup, tokens)
7
+ case markup
8
+ when NamedSyntax
9
+ @variables = variables_from_string($2)
10
+ @name = $1
11
+ when SimpleSyntax
12
+ @variables = variables_from_string(markup)
13
+ @name = "'#{@variables.to_s}'"
14
+ else
15
+ raise SyntaxError.new("Syntax Error in 'cycle' - Valid syntax: cycle [name :] var [, var2, var3 ...]")
16
+ end
17
+
18
+ end
19
+
20
+ def render(context)
21
+ context.registers[:cycle] ||= Hash.new(0)
22
+
23
+ context.stack do
24
+ key = context[@name]
25
+ iteration = context.registers[:cycle][key]
26
+ result = context[@variables[iteration]]
27
+ iteration += 1
28
+ iteration = 0 if iteration >= @variables.size
29
+ context.registers[:cycle][key] = iteration
30
+ result
31
+ end
32
+ end
33
+
34
+ private
35
+
36
+ def variables_from_string(markup)
37
+ markup.split(',').collect do |var|
38
+ var =~ /\s*(#{QuotedFragment})\s*/
39
+ $1 ? $1 : nil
40
+ end.compact
41
+ end
42
+
43
+ end
44
+
45
+ Template.register_tag('cycle', Cycle)
46
+ end
@@ -0,0 +1,81 @@
1
+ module Liquid
2
+ class For < Block
3
+ Syntax = /(\w+)\s+in\s+(#{VariableSignature}+)/
4
+
5
+ def initialize(markup, tokens)
6
+ super
7
+
8
+ if markup =~ Syntax
9
+ @variable_name = $1
10
+ @collection_name = $2
11
+ @name = "#{$1}-#{$2}"
12
+ @attributes = {}
13
+ markup.scan(TagAttributes) do |key, value|
14
+ @attributes[key] = value
15
+ end
16
+ else
17
+ raise SyntaxError.new("Syntax Error in 'for loop' - Valid syntax: for [item] in [collection]")
18
+ end
19
+ end
20
+
21
+ def render(context)
22
+ context.registers[:for] ||= Hash.new(0)
23
+
24
+ collection = context[@collection_name]
25
+
26
+ return '' if collection.nil? or collection.empty?
27
+
28
+ range = (0..collection.length)
29
+
30
+ if @attributes['limit'] or @attributes['offset']
31
+
32
+
33
+ offset = 0
34
+ if @attributes['offset'] == 'continue'
35
+ offset = context.registers[:for][@name]
36
+ else
37
+ offset = context[@attributes['offset']] || 0
38
+ end
39
+
40
+ limit = context[@attributes['limit']]
41
+
42
+ range_end = limit ? offset + limit : collection.length
43
+
44
+ range = (offset..range_end-1)
45
+
46
+ # Save the range end in the registers so that future calls to
47
+ # offset:continue have something to pick up
48
+ context.registers[:for][@name] = range_end
49
+ end
50
+
51
+ result = []
52
+ segment = collection[range]
53
+ return '' if segment.nil?
54
+
55
+ context.stack do
56
+ length = segment.length
57
+
58
+ segment.each_with_index do |item, index|
59
+ context[@variable_name] = item
60
+ context['forloop'] = {
61
+ 'name' => @name,
62
+ 'length' => length,
63
+ 'index' => index + 1,
64
+ 'index0' => index,
65
+ 'rindex' => length - index,
66
+ 'rindex0' => length - index -1,
67
+ 'first' => (index == 0),
68
+ 'last' => (index == length - 1) }
69
+
70
+ result << render_all(@nodelist, context)
71
+ end
72
+ end
73
+
74
+ # Store position of last element we rendered. This allows us to do
75
+
76
+ result
77
+ end
78
+ end
79
+
80
+ Template.register_tag('for', For)
81
+ end