tobi-liquid 2.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.
@@ -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