tobi-liquid 2.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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,59 @@
1
+ module Liquid
2
+
3
+ # Cycle is usually used within a loop to alternate between values, like colors or DOM classes.
4
+ #
5
+ # {% for item in items %}
6
+ # <div class="{% cycle 'red', 'green', 'blue' %}"> {{ item }} </div>
7
+ # {% end %}
8
+ #
9
+ # <div class="red"> Item one </div>
10
+ # <div class="green"> Item two </div>
11
+ # <div class="blue"> Item three </div>
12
+ # <div class="red"> Item four </div>
13
+ # <div class="green"> Item five</div>
14
+ #
15
+ class Cycle < Tag
16
+ SimpleSyntax = /^#{QuotedFragment}+/
17
+ NamedSyntax = /^(#{QuotedFragment})\s*\:\s*(.*)/
18
+
19
+ def initialize(tag_name, markup, tokens)
20
+ case markup
21
+ when NamedSyntax
22
+ @variables = variables_from_string($2)
23
+ @name = $1
24
+ when SimpleSyntax
25
+ @variables = variables_from_string(markup)
26
+ @name = "'#{@variables.to_s}'"
27
+ else
28
+ raise SyntaxError.new("Syntax Error in 'cycle' - Valid syntax: cycle [name :] var [, var2, var3 ...]")
29
+ end
30
+ super
31
+ end
32
+
33
+ def render(context)
34
+ context.registers[:cycle] ||= Hash.new(0)
35
+
36
+ context.stack do
37
+ key = context[@name]
38
+ iteration = context.registers[:cycle][key]
39
+ result = context[@variables[iteration]]
40
+ iteration += 1
41
+ iteration = 0 if iteration >= @variables.size
42
+ context.registers[:cycle][key] = iteration
43
+ result
44
+ end
45
+ end
46
+
47
+ private
48
+
49
+ def variables_from_string(markup)
50
+ markup.split(',').collect do |var|
51
+ var =~ /\s*(#{QuotedFragment})\s*/
52
+ $1 ? $1 : nil
53
+ end.compact
54
+ end
55
+
56
+ end
57
+
58
+ Template.register_tag('cycle', Cycle)
59
+ end
@@ -0,0 +1,136 @@
1
+ module Liquid
2
+
3
+ # "For" iterates over an array or collection.
4
+ # Several useful variables are available to you within the loop.
5
+ #
6
+ # == Basic usage:
7
+ # {% for item in collection %}
8
+ # {{ forloop.index }}: {{ item.name }}
9
+ # {% endfor %}
10
+ #
11
+ # == Advanced usage:
12
+ # {% for item in collection %}
13
+ # <div {% if forloop.first %}class="first"{% endif %}>
14
+ # Item {{ forloop.index }}: {{ item.name }}
15
+ # </div>
16
+ # {% endfor %}
17
+ #
18
+ # You can also define a limit and offset much like SQL. Remember
19
+ # that offset starts at 0 for the first item.
20
+ #
21
+ # {% for item in collection limit:5 offset:10 %}
22
+ # {{ item.name }}
23
+ # {% end %}
24
+ #
25
+ # To reverse the for loop simply use {% for item in collection reversed %}
26
+ #
27
+ # == Available variables:
28
+ #
29
+ # forloop.name:: 'item-collection'
30
+ # forloop.length:: Length of the loop
31
+ # forloop.index:: The current item's position in the collection;
32
+ # forloop.index starts at 1.
33
+ # This is helpful for non-programmers who start believe
34
+ # the first item in an array is 1, not 0.
35
+ # forloop.index0:: The current item's position in the collection
36
+ # where the first item is 0
37
+ # forloop.rindex:: Number of items remaining in the loop
38
+ # (length - index) where 1 is the last item.
39
+ # forloop.rindex0:: Number of items remaining in the loop
40
+ # where 0 is the last item.
41
+ # forloop.first:: Returns true if the item is the first item.
42
+ # forloop.last:: Returns true if the item is the last item.
43
+ #
44
+ class For < Block
45
+ Syntax = /(\w+)\s+in\s+(#{QuotedFragment}+)\s*(reversed)?/
46
+
47
+ def initialize(tag_name, markup, tokens)
48
+ if markup =~ Syntax
49
+ @variable_name = $1
50
+ @collection_name = $2
51
+ @name = "#{$1}-#{$2}"
52
+ @reversed = $3
53
+ @attributes = {}
54
+ markup.scan(TagAttributes) do |key, value|
55
+ @attributes[key] = value
56
+ end
57
+ else
58
+ raise SyntaxError.new("Syntax Error in 'for loop' - Valid syntax: for [item] in [collection]")
59
+ end
60
+
61
+ super
62
+ end
63
+
64
+ def render(context)
65
+ context.registers[:for] ||= Hash.new(0)
66
+
67
+ collection = context[@collection_name]
68
+ collection = collection.to_a if collection.is_a?(Range)
69
+
70
+ return '' unless collection.respond_to?(:each)
71
+
72
+ from = if @attributes['offset'] == 'continue'
73
+ context.registers[:for][@name].to_i
74
+ else
75
+ context[@attributes['offset']].to_i
76
+ end
77
+
78
+ limit = context[@attributes['limit']]
79
+ to = limit ? limit.to_i + from : nil
80
+
81
+
82
+ segment = slice_collection_using_each(collection, from, to)
83
+
84
+ return '' if segment.empty?
85
+
86
+ segment.reverse! if @reversed
87
+
88
+ result = []
89
+
90
+ length = segment.length
91
+
92
+ # Store our progress through the collection for the continue flag
93
+ context.registers[:for][@name] = from + segment.length
94
+
95
+ context.stack do
96
+ segment.each_with_index do |item, index|
97
+ context[@variable_name] = item
98
+ context['forloop'] = {
99
+ 'name' => @name,
100
+ 'length' => length,
101
+ 'index' => index + 1,
102
+ 'index0' => index,
103
+ 'rindex' => length - index,
104
+ 'rindex0' => length - index -1,
105
+ 'first' => (index == 0),
106
+ 'last' => (index == length - 1) }
107
+
108
+ result << render_all(@nodelist, context)
109
+ end
110
+ end
111
+ result
112
+ end
113
+
114
+ def slice_collection_using_each(collection, from, to)
115
+ segments = []
116
+ index = 0
117
+ yielded = 0
118
+ collection.each do |item|
119
+
120
+ if to && to <= index
121
+ break
122
+ end
123
+
124
+ if from <= index
125
+ segments << item
126
+ end
127
+
128
+ index += 1
129
+ end
130
+
131
+ segments
132
+ end
133
+ end
134
+
135
+ Template.register_tag('for', For)
136
+ end
@@ -0,0 +1,79 @@
1
+ module Liquid
2
+
3
+ # If is the conditional block
4
+ #
5
+ # {% if user.admin %}
6
+ # Admin user!
7
+ # {% else %}
8
+ # Not admin user
9
+ # {% endif %}
10
+ #
11
+ # There are {% if count < 5 %} less {% else %} more {% endif %} items than you need.
12
+ #
13
+ #
14
+ class If < Block
15
+ SyntaxHelp = "Syntax Error in tag 'if' - Valid syntax: if [expression]"
16
+ Syntax = /(#{QuotedFragment})\s*([=!<>a-z_]+)?\s*(#{QuotedFragment})?/
17
+
18
+ def initialize(tag_name, markup, tokens)
19
+
20
+ @blocks = []
21
+
22
+ push_block('if', markup)
23
+
24
+ super
25
+ end
26
+
27
+ def unknown_tag(tag, markup, tokens)
28
+ if ['elsif', 'else'].include?(tag)
29
+ push_block(tag, markup)
30
+ else
31
+ super
32
+ end
33
+ end
34
+
35
+ def render(context)
36
+ context.stack do
37
+ @blocks.each do |block|
38
+ if block.evaluate(context)
39
+ return render_all(block.attachment, context)
40
+ end
41
+ end
42
+ ''
43
+ end
44
+ end
45
+
46
+ private
47
+
48
+ def push_block(tag, markup)
49
+ block = if tag == 'else'
50
+ ElseCondition.new
51
+ else
52
+
53
+ expressions = markup.split(/\b(and|or)\b/).reverse
54
+ raise(SyntaxError, SyntaxHelp) unless expressions.shift =~ Syntax
55
+
56
+ condition = Condition.new($1, $2, $3)
57
+
58
+ while not expressions.empty?
59
+ operator = expressions.shift
60
+
61
+ raise(SyntaxError, SyntaxHelp) unless expressions.shift.to_s =~ Syntax
62
+
63
+ new_condition = Condition.new($1, $2, $3)
64
+ new_condition.send(operator.to_sym, condition)
65
+ condition = new_condition
66
+ end
67
+
68
+ condition
69
+ end
70
+
71
+ @blocks.push(block)
72
+ @nodelist = block.attach(Array.new)
73
+ end
74
+
75
+
76
+ end
77
+
78
+ Template.register_tag('if', If)
79
+ end
@@ -0,0 +1,20 @@
1
+ module Liquid
2
+ class Ifchanged < Block
3
+
4
+ def render(context)
5
+ context.stack do
6
+
7
+ output = render_all(@nodelist, context)
8
+
9
+ if output != context.registers[:ifchanged]
10
+ context.registers[:ifchanged] = output
11
+ output
12
+ else
13
+ ''
14
+ end
15
+ end
16
+ end
17
+ end
18
+
19
+ Template.register_tag('ifchanged', Ifchanged)
20
+ end
@@ -0,0 +1,55 @@
1
+ module Liquid
2
+ class Include < Tag
3
+ Syntax = /(#{QuotedFragment}+)(\s+(?:with|for)\s+(#{QuotedFragment}+))?/
4
+
5
+ def initialize(tag_name, markup, tokens)
6
+ if markup =~ Syntax
7
+
8
+ @template_name = $1
9
+ @variable_name = $3
10
+ @attributes = {}
11
+
12
+ markup.scan(TagAttributes) do |key, value|
13
+ @attributes[key] = value
14
+ end
15
+
16
+ else
17
+ raise SyntaxError.new("Error in tag 'include' - Valid syntax: include '[template]' (with|for) [object|collection]")
18
+ end
19
+
20
+ super
21
+ end
22
+
23
+ def parse(tokens)
24
+ end
25
+
26
+ def render(context)
27
+ source = Liquid::Template.file_system.read_template_file(context[@template_name])
28
+ partial = Liquid::Template.parse(source)
29
+
30
+ variable = context[@variable_name || @template_name[1..-2]]
31
+
32
+ context.stack do
33
+ @attributes.each do |key, value|
34
+ context[key] = context[value]
35
+ end
36
+
37
+ if variable.is_a?(Array)
38
+
39
+ variable.collect do |variable|
40
+ context[@template_name[1..-2]] = variable
41
+ partial.render(context)
42
+ end
43
+
44
+ else
45
+
46
+ context[@template_name[1..-2]] = variable
47
+ partial.render(context)
48
+
49
+ end
50
+ end
51
+ end
52
+ end
53
+
54
+ Template.register_tag('include', Include)
55
+ end
@@ -0,0 +1,33 @@
1
+ require File.dirname(__FILE__) + '/if'
2
+
3
+ module Liquid
4
+
5
+ # Unless is a conditional just like 'if' but works on the inverse logic.
6
+ #
7
+ # {% unless x < 0 %} x is greater than zero {% end %}
8
+ #
9
+ class Unless < If
10
+ def render(context)
11
+ context.stack do
12
+
13
+ # First condition is interpreted backwards ( if not )
14
+ block = @blocks.first
15
+ unless block.evaluate(context)
16
+ return render_all(block.attachment, context)
17
+ end
18
+
19
+ # After the first condition unless works just like if
20
+ @blocks[1..-1].each do |block|
21
+ if block.evaluate(context)
22
+ return render_all(block.attachment, context)
23
+ end
24
+ end
25
+
26
+ ''
27
+ end
28
+ end
29
+ end
30
+
31
+
32
+ Template.register_tag('unless', Unless)
33
+ end
@@ -0,0 +1,147 @@
1
+ module Liquid
2
+
3
+ # Templates are central to liquid.
4
+ # Interpretating templates is a two step process. First you compile the
5
+ # source code you got. During compile time some extensive error checking is performed.
6
+ # your code should expect to get some SyntaxErrors.
7
+ #
8
+ # After you have a compiled template you can then <tt>render</tt> it.
9
+ # You can use a compiled template over and over again and keep it cached.
10
+ #
11
+ # Example:
12
+ #
13
+ # template = Liquid::Template.parse(source)
14
+ # template.render('user_name' => 'bob')
15
+ #
16
+ class Template
17
+ attr_accessor :root
18
+ @@file_system = BlankFileSystem.new
19
+
20
+ class << self
21
+ def file_system
22
+ @@file_system
23
+ end
24
+
25
+ def file_system=(obj)
26
+ @@file_system = obj
27
+ end
28
+
29
+ def register_tag(name, klass)
30
+ tags[name.to_s] = klass
31
+ end
32
+
33
+ def tags
34
+ @tags ||= {}
35
+ end
36
+
37
+ # Pass a module with filter methods which should be available
38
+ # to all liquid views. Good for registering the standard library
39
+ def register_filter(mod)
40
+ Strainer.global_filter(mod)
41
+ end
42
+
43
+ # creates a new <tt>Template</tt> object from liquid source code
44
+ def parse(source)
45
+ template = Template.new
46
+ template.parse(source)
47
+ template
48
+ end
49
+ end
50
+
51
+ # creates a new <tt>Template</tt> from an array of tokens. Use <tt>Template.parse</tt> instead
52
+ def initialize
53
+ end
54
+
55
+ # Parse source code.
56
+ # Returns self for easy chaining
57
+ def parse(source)
58
+ @root = Document.new(tokenize(source))
59
+ self
60
+ end
61
+
62
+ def registers
63
+ @registers ||= {}
64
+ end
65
+
66
+ def assigns
67
+ @assigns ||= {}
68
+ end
69
+
70
+ def errors
71
+ @errors ||= []
72
+ end
73
+
74
+ # Render takes a hash with local variables.
75
+ #
76
+ # if you use the same filters over and over again consider registering them globally
77
+ # with <tt>Template.register_filter</tt>
78
+ #
79
+ # Following options can be passed:
80
+ #
81
+ # * <tt>filters</tt> : array with local filters
82
+ # * <tt>registers</tt> : hash with register variables. Those can be accessed from
83
+ # filters and tags and might be useful to integrate liquid more with its host application
84
+ #
85
+ def render(*args)
86
+ return '' if @root.nil?
87
+
88
+ context = case args.first
89
+ when Liquid::Context
90
+ args.shift
91
+ when Hash
92
+ a = args.shift
93
+ assigns.each { |k,v| a[k] = v unless a.has_key?(k) }
94
+ Context.new(a, registers, @rethrow_errors)
95
+ when nil
96
+ Context.new(assigns.dup, registers, @rethrow_errors)
97
+ else
98
+ raise ArgumentError, "Expect Hash or Liquid::Context as parameter"
99
+ end
100
+
101
+ case args.last
102
+ when Hash
103
+ options = args.pop
104
+
105
+ if options[:registers].is_a?(Hash)
106
+ self.registers.merge!(options[:registers])
107
+ end
108
+
109
+ if options[:filters]
110
+ context.add_filters(options[:filters])
111
+ end
112
+
113
+ when Module
114
+ context.add_filters(args.pop)
115
+ when Array
116
+ context.add_filters(args.pop)
117
+ end
118
+
119
+ begin
120
+ # render the nodelist.
121
+ # for performance reasons we get a array back here. join will make a string out of it
122
+ @root.render(context).join
123
+ ensure
124
+ @errors = context.errors
125
+ end
126
+ end
127
+
128
+ def render!(*args)
129
+ @rethrow_errors = true; render(*args)
130
+ end
131
+
132
+ private
133
+
134
+ # Uses the <tt>Liquid::TemplateParser</tt> regexp to tokenize the passed source
135
+ def tokenize(source)
136
+ source = source.source if source.respond_to?(:source)
137
+ return [] if source.to_s.empty?
138
+ tokens = source.split(TemplateParser)
139
+
140
+ # removes the rogue empty element at the beginning of the array
141
+ tokens.shift if tokens[0] and tokens[0].empty?
142
+
143
+ tokens
144
+ end
145
+
146
+ end
147
+ end