merb_comatose 0.0.2

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.
@@ -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