spinto-liquid 2.3.0.1

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 (62) hide show
  1. data/History.md +56 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +44 -0
  4. data/lib/extras/liquid_view.rb +51 -0
  5. data/lib/liquid/block.rb +101 -0
  6. data/lib/liquid/condition.rb +120 -0
  7. data/lib/liquid/context.rb +245 -0
  8. data/lib/liquid/document.rb +17 -0
  9. data/lib/liquid/drop.rb +49 -0
  10. data/lib/liquid/errors.rb +11 -0
  11. data/lib/liquid/extensions.rb +62 -0
  12. data/lib/liquid/file_system.rb +62 -0
  13. data/lib/liquid/htmltags.rb +75 -0
  14. data/lib/liquid/module_ex.rb +62 -0
  15. data/lib/liquid/standardfilters.rb +241 -0
  16. data/lib/liquid/strainer.rb +54 -0
  17. data/lib/liquid/tag.rb +26 -0
  18. data/lib/liquid/tags/assign.rb +33 -0
  19. data/lib/liquid/tags/capture.rb +35 -0
  20. data/lib/liquid/tags/case.rb +79 -0
  21. data/lib/liquid/tags/comment.rb +9 -0
  22. data/lib/liquid/tags/cycle.rb +59 -0
  23. data/lib/liquid/tags/decrement.rb +39 -0
  24. data/lib/liquid/tags/for.rb +190 -0
  25. data/lib/liquid/tags/if.rb +79 -0
  26. data/lib/liquid/tags/ifchanged.rb +20 -0
  27. data/lib/liquid/tags/include.rb +65 -0
  28. data/lib/liquid/tags/increment.rb +35 -0
  29. data/lib/liquid/tags/raw.rb +21 -0
  30. data/lib/liquid/tags/unless.rb +33 -0
  31. data/lib/liquid/template.rb +150 -0
  32. data/lib/liquid/variable.rb +50 -0
  33. data/lib/liquid.rb +66 -0
  34. data/test/liquid/assign_test.rb +21 -0
  35. data/test/liquid/block_test.rb +58 -0
  36. data/test/liquid/capture_test.rb +40 -0
  37. data/test/liquid/condition_test.rb +127 -0
  38. data/test/liquid/context_test.rb +478 -0
  39. data/test/liquid/drop_test.rb +162 -0
  40. data/test/liquid/error_handling_test.rb +81 -0
  41. data/test/liquid/file_system_test.rb +29 -0
  42. data/test/liquid/filter_test.rb +106 -0
  43. data/test/liquid/module_ex_test.rb +87 -0
  44. data/test/liquid/output_test.rb +116 -0
  45. data/test/liquid/parsing_quirks_test.rb +52 -0
  46. data/test/liquid/regexp_test.rb +44 -0
  47. data/test/liquid/security_test.rb +41 -0
  48. data/test/liquid/standard_filter_test.rb +195 -0
  49. data/test/liquid/strainer_test.rb +25 -0
  50. data/test/liquid/tags/for_tag_test.rb +215 -0
  51. data/test/liquid/tags/html_tag_test.rb +39 -0
  52. data/test/liquid/tags/if_else_tag_test.rb +160 -0
  53. data/test/liquid/tags/include_tag_test.rb +139 -0
  54. data/test/liquid/tags/increment_tag_test.rb +24 -0
  55. data/test/liquid/tags/raw_tag_test.rb +15 -0
  56. data/test/liquid/tags/standard_tag_test.rb +295 -0
  57. data/test/liquid/tags/statements_test.rb +134 -0
  58. data/test/liquid/tags/unless_else_tag_test.rb +26 -0
  59. data/test/liquid/template_test.rb +74 -0
  60. data/test/liquid/variable_test.rb +170 -0
  61. data/test/test_helper.rb +29 -0
  62. metadata +136 -0
@@ -0,0 +1,17 @@
1
+ module Liquid
2
+ class Document < Block
3
+ # we don't need markup to open this block
4
+ def initialize(tokens)
5
+ parse(tokens)
6
+ end
7
+
8
+ # There isn't a real delimter
9
+ def block_delimiter
10
+ []
11
+ end
12
+
13
+ # Document blocks don't need to be terminated since they are not actually opened
14
+ def assert_missing_delimitation!
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,49 @@
1
+ module Liquid
2
+
3
+ # A drop in liquid is a class which allows you to export DOM like things to liquid.
4
+ # Methods of drops are callable.
5
+ # The main use for liquid drops is the implement lazy loaded objects.
6
+ # If you would like to make data available to the web designers which you don't want loaded unless needed then
7
+ # a drop is a great way to do that
8
+ #
9
+ # Example:
10
+ #
11
+ # class ProductDrop < Liquid::Drop
12
+ # def top_sales
13
+ # Shop.current.products.find(:all, :order => 'sales', :limit => 10 )
14
+ # end
15
+ # end
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.
19
+ #
20
+ # Your drop can either implement the methods sans any parameters or implement the before_method(name) method which is a
21
+ # catch all
22
+ class Drop
23
+ attr_writer :context
24
+
25
+ # Catch all for the method
26
+ def before_method(method)
27
+ nil
28
+ end
29
+
30
+ # called by liquid to invoke a drop
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)
34
+ else
35
+ before_method(method_or_key)
36
+ end
37
+ end
38
+
39
+ def has_key?(name)
40
+ true
41
+ end
42
+
43
+ def to_liquid
44
+ self
45
+ end
46
+
47
+ alias :[] :invoke_drop
48
+ end
49
+ end
@@ -0,0 +1,11 @@
1
+ module Liquid
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
10
+ class StackLevelError < Error; end
11
+ end
@@ -0,0 +1,62 @@
1
+ require 'time'
2
+ require 'date'
3
+
4
+ class String # :nodoc:
5
+ def to_liquid
6
+ self
7
+ end
8
+ end
9
+
10
+ class Array # :nodoc:
11
+ def to_liquid
12
+ self
13
+ end
14
+ end
15
+
16
+ class Hash # :nodoc:
17
+ def to_liquid
18
+ self
19
+ end
20
+ end
21
+
22
+ class Numeric # :nodoc:
23
+ def to_liquid
24
+ self
25
+ end
26
+ end
27
+
28
+ class Time # :nodoc:
29
+ def to_liquid
30
+ self
31
+ end
32
+ end
33
+
34
+ class DateTime < Date # :nodoc:
35
+ def to_liquid
36
+ self
37
+ end
38
+ end
39
+
40
+ class Date # :nodoc:
41
+ def to_liquid
42
+ self
43
+ end
44
+ end
45
+
46
+ class TrueClass
47
+ def to_liquid # :nodoc:
48
+ self
49
+ end
50
+ end
51
+
52
+ class FalseClass
53
+ def to_liquid # :nodoc:
54
+ self
55
+ end
56
+ end
57
+
58
+ class NilClass
59
+ def to_liquid # :nodoc:
60
+ self
61
+ end
62
+ end
@@ -0,0 +1,62 @@
1
+ module Liquid
2
+ # A Liquid file system is way to let your templates retrieve other templates for use with the include tag.
3
+ #
4
+ # You can implement subclasses that retrieve templates from the database, from the file system using a different
5
+ # path structure, you can provide them as hard-coded inline strings, or any manner that you see fit.
6
+ #
7
+ # You can add additional instance variables, arguments, or methods as needed.
8
+ #
9
+ # Example:
10
+ #
11
+ # Liquid::Template.file_system = Liquid::LocalFileSystem.new(template_path)
12
+ # liquid = Liquid::Template.parse(template)
13
+ #
14
+ # This will parse the template with a LocalFileSystem implementation rooted at 'template_path'.
15
+ class BlankFileSystem
16
+ # Called by Liquid to retrieve a template file
17
+ def read_template_file(template_path, context)
18
+ raise FileSystemError, "This liquid context does not allow includes."
19
+ end
20
+ end
21
+
22
+ # This implements an abstract file system which retrieves template files named in a manner similar to Rails partials,
23
+ # ie. with the template name prefixed with an underscore. The extension ".liquid" is also added.
24
+ #
25
+ # For security reasons, template paths are only allowed to contain letters, numbers, and underscore.
26
+ #
27
+ # Example:
28
+ #
29
+ # file_system = Liquid::LocalFileSystem.new("/some/path")
30
+ #
31
+ # file_system.full_path("mypartial") # => "/some/path/_mypartial.liquid"
32
+ # file_system.full_path("dir/mypartial") # => "/some/path/dir/_mypartial.liquid"
33
+ #
34
+ class LocalFileSystem
35
+ attr_accessor :root
36
+
37
+ def initialize(root)
38
+ @root = root
39
+ end
40
+
41
+ def read_template_file(template_path, context)
42
+ full_path = full_path(template_path)
43
+ raise FileSystemError, "No such template '#{template_path}'" unless File.exists?(full_path)
44
+
45
+ File.read(full_path)
46
+ end
47
+
48
+ def full_path(template_path)
49
+ raise FileSystemError, "Illegal template name '#{template_path}'" unless template_path =~ /^[^.\/][a-zA-Z0-9_\/]+$/
50
+
51
+ full_path = if template_path.include?('/')
52
+ File.join(root, File.dirname(template_path), "_#{File.basename(template_path)}.liquid")
53
+ else
54
+ File.join(root, "_#{template_path}.liquid")
55
+ end
56
+
57
+ raise FileSystemError, "Illegal template path '#{File.expand_path(full_path)}'" unless File.expand_path(full_path) =~ /^#{File.expand_path(root)}/
58
+
59
+ full_path
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,75 @@
1
+ module Liquid
2
+ class TableRow < Block
3
+ Syntax = /(\w+)\s+in\s+(#{QuotedFragment}+)/
4
+
5
+ def initialize(tag_name, markup, tokens)
6
+ if markup =~ Syntax
7
+ @variable_name = $1
8
+ @collection_name = $2
9
+ @attributes = {}
10
+ markup.scan(TagAttributes) do |key, value|
11
+ @attributes[key] = value
12
+ end
13
+ else
14
+ raise SyntaxError.new("Syntax Error in 'table_row loop' - Valid syntax: table_row [item] in [collection] cols=3")
15
+ end
16
+
17
+ super
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
+ 'col' => col + 1,
46
+ 'col0' => col,
47
+ 'index0' => index,
48
+ 'rindex' => length - index,
49
+ 'rindex0' => length - index -1,
50
+ 'first' => (index == 0),
51
+ 'last' => (index == length - 1),
52
+ 'col_first' => (col == 0),
53
+ 'col_last' => (col == cols - 1)
54
+ }
55
+
56
+
57
+ col += 1
58
+
59
+ result << "<td class=\"col#{col}\">" << render_all(@nodelist, context) << '</td>'
60
+
61
+ if col == cols and not (index == length - 1)
62
+ col = 0
63
+ row += 1
64
+ result << "</tr>\n<tr class=\"row#{row}\">"
65
+ end
66
+
67
+ end
68
+ end
69
+ result << "</tr>\n"
70
+ result
71
+ end
72
+ end
73
+
74
+ Template.register_tag('tablerow', TableRow)
75
+ 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
@@ -0,0 +1,241 @@
1
+ require 'cgi'
2
+
3
+ module Liquid
4
+
5
+ module StandardFilters
6
+
7
+ # Return the size of an array or of an string
8
+ def size(input)
9
+
10
+ input.respond_to?(:size) ? input.size : 0
11
+ end
12
+
13
+ # convert a input string to DOWNCASE
14
+ def downcase(input)
15
+ input.to_s.downcase
16
+ end
17
+
18
+ # convert a input string to UPCASE
19
+ def upcase(input)
20
+ input.to_s.upcase
21
+ end
22
+
23
+ # capitalize words in the input centence
24
+ def capitalize(input)
25
+ input.to_s.capitalize
26
+ end
27
+
28
+ def escape(input)
29
+ CGI.escapeHTML(input) rescue input
30
+ end
31
+
32
+ def escape_once(input)
33
+ ActionView::Helpers::TagHelper.escape_once(input)
34
+ rescue NameError
35
+ input
36
+ end
37
+
38
+ alias_method :h, :escape
39
+
40
+ # Truncate a string down to x characters
41
+ def truncate(input, length = 50, truncate_string = "...")
42
+ if input.nil? then return end
43
+ l = length.to_i - truncate_string.length
44
+ l = 0 if l < 0
45
+ input.length > length.to_i ? input[0...l] + truncate_string : input
46
+ end
47
+
48
+ def truncatewords(input, words = 15, truncate_string = "...")
49
+ if input.nil? then return end
50
+ wordlist = input.to_s.split
51
+ l = words.to_i - 1
52
+ l = 0 if l < 0
53
+ wordlist.length > l ? wordlist[0..l].join(" ") + truncate_string : input
54
+ end
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
+
61
+ def strip_html(input)
62
+ input.to_s.gsub(/<script.*?<\/script>/, '').gsub(/<.*?>/, '')
63
+ end
64
+
65
+ # Remove all newlines from the string
66
+ def strip_newlines(input)
67
+ input.to_s.gsub(/\n/, '')
68
+ end
69
+
70
+
71
+ # Join elements of the array with certain character between them
72
+ def join(input, glue = ' ')
73
+ [input].flatten.join(glue)
74
+ end
75
+
76
+ # Sort elements of the array
77
+ # provide optional property with which to sort an array of hashes or drops
78
+ def sort(input, property = nil)
79
+ ary = [input].flatten
80
+ if property.nil?
81
+ ary.sort
82
+ elsif ary.first.respond_to?('[]') and !ary.first[property].nil?
83
+ ary.sort {|a,b| a[property] <=> b[property] }
84
+ elsif ary.first.respond_to?(property)
85
+ ary.sort {|a,b| a.send(property) <=> b.send(property) }
86
+ end
87
+ end
88
+
89
+ # map/collect on a given property
90
+ def map(input, property)
91
+ ary = [input].flatten
92
+ if ary.first.respond_to?('[]') and !ary.first[property].nil?
93
+ ary.map {|e| e[property] }
94
+ elsif ary.first.respond_to?(property)
95
+ ary.map {|e| e.send(property) }
96
+ end
97
+ end
98
+
99
+ # Replace occurrences of a string with another
100
+ def replace(input, string, replacement = '')
101
+ input.to_s.gsub(string, replacement)
102
+ end
103
+
104
+ # Replace the first occurrences of a string with another
105
+ def replace_first(input, string, replacement = '')
106
+ input.to_s.sub(string, replacement)
107
+ end
108
+
109
+ # remove a substring
110
+ def remove(input, string)
111
+ input.to_s.gsub(string, '')
112
+ end
113
+
114
+ # remove the first occurrences of a substring
115
+ def remove_first(input, string)
116
+ input.to_s.sub(string, '')
117
+ end
118
+
119
+ # add one string to another
120
+ def append(input, string)
121
+ input.to_s + string.to_s
122
+ end
123
+
124
+ # prepend a string to another
125
+ def prepend(input, string)
126
+ string.to_s + input.to_s
127
+ end
128
+
129
+ # Add <br /> tags in front of all newlines in input string
130
+ def newline_to_br(input)
131
+ input.to_s.gsub(/\n/, "<br />\n")
132
+ end
133
+
134
+ # Reformat a date
135
+ #
136
+ # %a - The abbreviated weekday name (``Sun'')
137
+ # %A - The full weekday name (``Sunday'')
138
+ # %b - The abbreviated month name (``Jan'')
139
+ # %B - The full month name (``January'')
140
+ # %c - The preferred local date and time representation
141
+ # %d - Day of the month (01..31)
142
+ # %H - Hour of the day, 24-hour clock (00..23)
143
+ # %I - Hour of the day, 12-hour clock (01..12)
144
+ # %j - Day of the year (001..366)
145
+ # %m - Month of the year (01..12)
146
+ # %M - Minute of the hour (00..59)
147
+ # %p - Meridian indicator (``AM'' or ``PM'')
148
+ # %S - Second of the minute (00..60)
149
+ # %U - Week number of the current year,
150
+ # starting with the first Sunday as the first
151
+ # day of the first week (00..53)
152
+ # %W - Week number of the current year,
153
+ # starting with the first Monday as the first
154
+ # day of the first week (00..53)
155
+ # %w - Day of the week (Sunday is 0, 0..6)
156
+ # %x - Preferred representation for the date alone, no time
157
+ # %X - Preferred representation for the time alone, no date
158
+ # %y - Year without a century (00..99)
159
+ # %Y - Year with century
160
+ # %Z - Time zone name
161
+ # %% - Literal ``%'' character
162
+ def date(input, format)
163
+
164
+ if format.to_s.empty?
165
+ return input.to_s
166
+ end
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
+
172
+ date = input.is_a?(String) ? Time.parse(input) : input
173
+
174
+ if date.respond_to?(:strftime)
175
+ date.strftime(format.to_s)
176
+ else
177
+ input
178
+ end
179
+ rescue => e
180
+ input
181
+ end
182
+
183
+ # Get the first element of the passed in array
184
+ #
185
+ # Example:
186
+ # {{ product.images | first | to_img }}
187
+ #
188
+ def first(array)
189
+ array.first if array.respond_to?(:first)
190
+ end
191
+
192
+ # Get the last element of the passed in array
193
+ #
194
+ # Example:
195
+ # {{ product.images | last | to_img }}
196
+ #
197
+ def last(array)
198
+ array.last if array.respond_to?(:last)
199
+ end
200
+
201
+ # addition
202
+ def plus(input, operand)
203
+ to_number(input) + to_number(operand)
204
+ end
205
+
206
+ # subtraction
207
+ def minus(input, operand)
208
+ to_number(input) - to_number(operand)
209
+ end
210
+
211
+ # multiplication
212
+ def times(input, operand)
213
+ to_number(input) * to_number(operand)
214
+ end
215
+
216
+ # division
217
+ def divided_by(input, operand)
218
+ to_number(input) / to_number(operand)
219
+ end
220
+
221
+ def modulo(input, operand)
222
+ to_number(input) % to_number(operand)
223
+ end
224
+
225
+ private
226
+
227
+ def to_number(obj)
228
+ case obj
229
+ when Numeric
230
+ obj
231
+ when String
232
+ (obj.strip =~ /^\d+\.\d+$/) ? obj.to_f : obj.to_i
233
+ else
234
+ 0
235
+ end
236
+ end
237
+
238
+ end
239
+
240
+ Template.register_filter(StandardFilters)
241
+ end