merb_comatose 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,190 @@
1
+ module Liquid
2
+
3
+ class ContextError < StandardError
4
+ end
5
+
6
+ # Context keeps the variable stack and resolves variables, as well as keywords
7
+ #
8
+ # context['variable'] = 'testing'
9
+ # context['variable'] #=> 'testing'
10
+ # context['true'] #=> true
11
+ # context['10.2232'] #=> 10.2232
12
+ #
13
+ # context.stack do
14
+ # context['bob'] = 'bobsen'
15
+ # end
16
+ #
17
+ # context['bob'] #=> nil class Context
18
+ class Context
19
+ attr_reader :assigns
20
+ attr_accessor :registers
21
+
22
+ def initialize(assigns = {}, registers = nil)
23
+ @assigns = [assigns]
24
+ @registers = registers || {}
25
+ end
26
+
27
+ def strainer
28
+ @strainer ||= Strainer.create(self)
29
+ end
30
+
31
+ # adds filters to this context.
32
+ # this does not register the filters with the main Template object. see <tt>Template.register_filter</tt>
33
+ # for that
34
+ def add_filters(filter)
35
+ return unless filter.is_a?(Module)
36
+ strainer.extend(filter)
37
+ end
38
+
39
+ def invoke(method, *args)
40
+ if strainer.respond_to?(method)
41
+ strainer.__send__(method, *args)
42
+ else
43
+ return args[0]
44
+ end
45
+ end
46
+
47
+ # push new local scope on the stack. use <tt>Context#stack</tt> instead
48
+ def push
49
+ @assigns.unshift({})
50
+ end
51
+
52
+ # merge a hash of variables in the current local scope
53
+ def merge(new_assigns)
54
+ @assigns[0].merge!(new_assigns)
55
+ end
56
+
57
+ # pop from the stack. use <tt>Context#stack</tt> instead
58
+ def pop
59
+ raise ContextError if @assigns.size == 1
60
+ @assigns.shift
61
+ end
62
+
63
+ # pushes a new local scope on the stack, pops it at the end of the block
64
+ #
65
+ # Example:
66
+ #
67
+ # context.stack do
68
+ # context['var'] = 'hi'
69
+ # end
70
+ # context['var] #=> nil
71
+ #
72
+ def stack(&block)
73
+ push
74
+ begin
75
+ result = yield
76
+ ensure
77
+ pop
78
+ end
79
+ result
80
+ end
81
+
82
+ # Only allow String, Numeric, Hash, Array or <tt>Liquid::Drop</tt>
83
+ def []=(key, value)
84
+ @assigns[0][key] = value
85
+ end
86
+
87
+ def [](key)
88
+ resolve(key)
89
+ end
90
+
91
+ def has_key?(key)
92
+ resolve(key) != nil
93
+ end
94
+
95
+ private
96
+
97
+ # Look up variable, either resolve directly after considering the name. We can directly handle
98
+ # Strings, digits, floats and booleans (true,false). If no match is made we lookup the variable in the current scope and
99
+ # later move up to the parent blocks to see if we can resolve the variable somewhere up the tree.
100
+ # Some special keywords return symbols. Those symbols are to be called on the rhs object in expressions
101
+ #
102
+ # Example:
103
+ #
104
+ # products == empty #=> products.empty?
105
+ #
106
+ def resolve(key)
107
+ case key
108
+ when nil
109
+ nil
110
+ when 'true'
111
+ true
112
+ when 'false'
113
+ false
114
+ when 'empty'
115
+ :empty?
116
+ when 'nil', 'null'
117
+ nil
118
+ # Single quoted strings
119
+ when /^'(.*)'$/
120
+ $1.to_s
121
+ # Double quoted strings
122
+ when /^"(.*)"$/
123
+ $1.to_s
124
+ # Integer and floats
125
+ when /^(\d+)$/
126
+ $1.to_i
127
+ when /^(\d[\d\.]+)$/
128
+ $1.to_f
129
+ else
130
+ variable(key)
131
+ end
132
+ end
133
+
134
+ # fetches an object starting at the local scope and then moving up
135
+ # the hierachy
136
+ def fetch(key)
137
+ begin
138
+ for scope in @assigns
139
+ if scope.has_key?(key)
140
+ obj = scope[key]
141
+ if obj.is_a?(Liquid::Drop)
142
+ obj.context = self
143
+ end
144
+ return obj
145
+ end
146
+ end
147
+ rescue => e
148
+ raise ContextError, "Could not fetch key #{key} from context: " + e.message
149
+ end
150
+ nil
151
+ end
152
+
153
+ # resolves namespaced queries gracefully.
154
+ #
155
+ # Example
156
+ #
157
+ # @context['hash'] = {"name" => 'tobi'}
158
+ # assert_equal 'tobi', @context['hash.name']
159
+ def variable(key)
160
+ parts = key.split(VariableAttributeSeparator)
161
+
162
+
163
+ if object = fetch(parts.shift).to_liquid
164
+ object.context = self if object.is_a?(Liquid::Drop)
165
+
166
+ while not parts.size.zero?
167
+ next_part_name = parts.shift
168
+
169
+ # If the last part of the context variable is .size we just
170
+ # return the count of the objects in this object
171
+ if next_part_name == 'size' and parts.empty?
172
+ return object.size if object.is_a?(Array)
173
+ return object.size if object.is_a?(Hash) && !object.has_key?(next_part_name)
174
+ end
175
+
176
+ return nil if not object.respond_to?(:has_key?)
177
+ return nil if not object.has_key?(next_part_name)
178
+
179
+ object = object[next_part_name].to_liquid
180
+ object.context = self if object.is_a?(Liquid::Drop)
181
+ end
182
+
183
+ object
184
+ else
185
+ nil
186
+ end
187
+ end
188
+
189
+ end
190
+ end
@@ -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,48 @@
1
+ module Liquid
2
+
3
+ # A drop in liquid is a class which allows you to 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)
32
+ result = before_method(method)
33
+ result ||= send(method.to_sym) if self.class.public_instance_methods.include?(method.to_s)
34
+ result
35
+ end
36
+
37
+ def has_key?(name)
38
+ true
39
+ end
40
+
41
+ def to_liquid
42
+ self
43
+ end
44
+
45
+ alias :[] :invoke_drop
46
+ end
47
+
48
+ end
@@ -0,0 +1,7 @@
1
+ module Liquid
2
+ class FilterNotFound < StandardError
3
+ end
4
+
5
+ class FileSystemError < StandardError
6
+ end
7
+ end
@@ -0,0 +1,53 @@
1
+ class String
2
+ def to_liquid
3
+ self
4
+ end
5
+ end
6
+
7
+ class Array
8
+ def to_liquid
9
+ self
10
+ end
11
+ end
12
+
13
+ class Hash
14
+ def to_liquid
15
+ self
16
+ end
17
+ end
18
+
19
+ class Numeric
20
+ def to_liquid
21
+ self
22
+ end
23
+ end
24
+
25
+ class Time
26
+ def to_liquid
27
+ self
28
+ end
29
+ end
30
+
31
+ class DateTime
32
+ def to_liquid
33
+ self
34
+ end
35
+ end
36
+
37
+ class Date
38
+ def to_liquid
39
+ self
40
+ end
41
+ end
42
+
43
+ def true.to_liquid
44
+ self
45
+ end
46
+
47
+ def false.to_liquid
48
+ self
49
+ end
50
+
51
+ def nil.to_liquid
52
+ self
53
+ 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)
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)
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,64 @@
1
+ module Liquid
2
+ class TableRow < Block
3
+ Syntax = /(\w+)\s+in\s+(#{AllowedVariableCharacters}+)/
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,111 @@
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.downcase rescue input
14
+ end
15
+
16
+ # convert a input string to UPCASE
17
+ def upcase(input)
18
+ input.upcase rescue input
19
+ end
20
+
21
+ # Truncate a string down to x characters
22
+ def truncate(input, characters = 100)
23
+ if input.to_s.size > characters.to_i
24
+ input.to_s[0..characters.to_i] + '&hellip;'
25
+ else
26
+ input
27
+ end
28
+ end
29
+
30
+ # Truncate string down to x words
31
+ def truncatewords(input, words = 15)
32
+ wordlist = [input.to_s.split].flatten
33
+ if wordlist.size > words.to_i
34
+ wordlist[0..words.to_i].join(' ') + '&hellip;'
35
+ else
36
+ input
37
+ end
38
+ end
39
+
40
+ def strip_html(input)
41
+ input.to_s.gsub(/<.*?>/, '')
42
+ end
43
+
44
+ # Join elements of the array with certain character between them
45
+ def join(input, glue = ' ')
46
+ [input].flatten.join(glue)
47
+ end
48
+
49
+ # Sort elements of the array
50
+ def sort(input)
51
+ [input].flatten.sort
52
+ end
53
+
54
+ # Reformat a date
55
+ #
56
+ # %a - The abbreviated weekday name (``Sun'')
57
+ # %A - The full weekday name (``Sunday'')
58
+ # %b - The abbreviated month name (``Jan'')
59
+ # %B - The full month name (``January'')
60
+ # %c - The preferred local date and time representation
61
+ # %d - Day of the month (01..31)
62
+ # %H - Hour of the day, 24-hour clock (00..23)
63
+ # %I - Hour of the day, 12-hour clock (01..12)
64
+ # %j - Day of the year (001..366)
65
+ # %m - Month of the year (01..12)
66
+ # %M - Minute of the hour (00..59)
67
+ # %p - Meridian indicator (``AM'' or ``PM'')
68
+ # %S - Second of the minute (00..60)
69
+ # %U - Week number of the current year,
70
+ # starting with the first Sunday as the first
71
+ # day of the first week (00..53)
72
+ # %W - Week number of the current year,
73
+ # starting with the first Monday as the first
74
+ # day of the first week (00..53)
75
+ # %w - Day of the week (Sunday is 0, 0..6)
76
+ # %x - Preferred representation for the date alone, no time
77
+ # %X - Preferred representation for the time alone, no date
78
+ # %y - Year without a century (00..99)
79
+ # %Y - Year with century
80
+ # %Z - Time zone name
81
+ # %% - Literal ``%'' character
82
+ def date(input, format)
83
+ date = input
84
+ date = Time.parse(input) if input.is_a?(String)
85
+ date.strftime(format)
86
+ rescue => e
87
+ input
88
+ end
89
+
90
+ # Get the first element of the passed in array
91
+ #
92
+ # Example:
93
+ # {{ product.images | first | to_img }}
94
+ #
95
+ def first(array)
96
+ array.first if array.respond_to?(:first)
97
+ end
98
+
99
+ # Get the last element of the passed in array
100
+ #
101
+ # Example:
102
+ # {{ product.images | last | to_img }}
103
+ #
104
+ def last(array)
105
+ array.last if array.respond_to?(:last)
106
+ end
107
+
108
+ end
109
+
110
+ Template.register_filter(StandardFilters)
111
+ end