locomotivecms-liquid 2.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. checksums.yaml +7 -0
  2. data/History.md +108 -0
  3. data/MIT-LICENSE +20 -0
  4. data/README.md +75 -0
  5. data/lib/extras/liquid_view.rb +51 -0
  6. data/lib/liquid/block.rb +160 -0
  7. data/lib/liquid/condition.rb +120 -0
  8. data/lib/liquid/context.rb +268 -0
  9. data/lib/liquid/document.rb +18 -0
  10. data/lib/liquid/drop.rb +74 -0
  11. data/lib/liquid/errors.rb +21 -0
  12. data/lib/liquid/extensions.rb +62 -0
  13. data/lib/liquid/file_system.rb +62 -0
  14. data/lib/liquid/htmltags.rb +74 -0
  15. data/lib/liquid/i18n.rb +39 -0
  16. data/lib/liquid/interrupts.rb +17 -0
  17. data/lib/liquid/lexer.rb +51 -0
  18. data/lib/liquid/locales/en.yml +25 -0
  19. data/lib/liquid/module_ex.rb +62 -0
  20. data/lib/liquid/parser.rb +89 -0
  21. data/lib/liquid/standardfilters.rb +285 -0
  22. data/lib/liquid/strainer.rb +53 -0
  23. data/lib/liquid/tag.rb +61 -0
  24. data/lib/liquid/tags/assign.rb +36 -0
  25. data/lib/liquid/tags/break.rb +21 -0
  26. data/lib/liquid/tags/capture.rb +40 -0
  27. data/lib/liquid/tags/case.rb +77 -0
  28. data/lib/liquid/tags/comment.rb +16 -0
  29. data/lib/liquid/tags/continue.rb +21 -0
  30. data/lib/liquid/tags/cycle.rb +61 -0
  31. data/lib/liquid/tags/decrement.rb +39 -0
  32. data/lib/liquid/tags/default_content.rb +21 -0
  33. data/lib/liquid/tags/extends.rb +79 -0
  34. data/lib/liquid/tags/for.rb +167 -0
  35. data/lib/liquid/tags/if.rb +100 -0
  36. data/lib/liquid/tags/ifchanged.rb +20 -0
  37. data/lib/liquid/tags/include.rb +97 -0
  38. data/lib/liquid/tags/increment.rb +36 -0
  39. data/lib/liquid/tags/inherited_block.rb +101 -0
  40. data/lib/liquid/tags/raw.rb +22 -0
  41. data/lib/liquid/tags/unless.rb +33 -0
  42. data/lib/liquid/template.rb +213 -0
  43. data/lib/liquid/utils.rb +30 -0
  44. data/lib/liquid/variable.rb +109 -0
  45. data/lib/liquid/version.rb +4 -0
  46. data/lib/liquid.rb +72 -0
  47. data/lib/locomotivecms-liquid.rb +1 -0
  48. metadata +94 -0
@@ -0,0 +1,36 @@
1
+ module Liquid
2
+ # increment is used in a place where one needs to insert a counter
3
+ # into a template, and needs the counter to survive across
4
+ # multiple instantiations of the template.
5
+ # (To achieve the survival, the application must keep the context)
6
+ #
7
+ # if the variable does not exist, it is created with value 0.
8
+ #
9
+ # Hello: {% increment variable %}
10
+ #
11
+ # gives you:
12
+ #
13
+ # Hello: 0
14
+ # Hello: 1
15
+ # Hello: 2
16
+ #
17
+ class Increment < Tag
18
+ def initialize(tag_name, markup, tokens, options)
19
+ @variable = markup.strip
20
+ super
21
+ end
22
+
23
+ def render(context)
24
+ value = context.environments.first[@variable] ||= 0
25
+ context.environments.first[@variable] = value + 1
26
+ value.to_s
27
+ end
28
+
29
+
30
+ def blank?
31
+ false
32
+ end
33
+ end
34
+
35
+ Template.register_tag('increment', Increment)
36
+ end
@@ -0,0 +1,101 @@
1
+ module Liquid
2
+
3
+ # Blocks are used with the Extends tag to define
4
+ # the content of blocks. Nested blocks are allowed.
5
+ #
6
+ # {% extends home %}
7
+ # {% block content }Hello world{% endblock %}
8
+ #
9
+ class InheritedBlock < Block
10
+ Syntax = /(#{QuotedFragment}+)/
11
+
12
+ attr_accessor :parent
13
+ attr_reader :name
14
+
15
+ def initialize(tag_name, markup, tokens, options)
16
+ if markup =~ Syntax
17
+ @name = $1.gsub(/["']/o, '').strip
18
+ else
19
+ raise(SyntaxError.new(options[:locale].t("errors.syntax.block")), options[:line])
20
+ end
21
+
22
+ self.set_full_name!(options)
23
+
24
+ (options[:block_stack] ||= []).push(self)
25
+ options[:current_block] = self
26
+
27
+ super if tokens
28
+ end
29
+
30
+ def render(context)
31
+ context.stack do
32
+ context['block'] = InheritedBlockDrop.new(self)
33
+ render_all(@nodelist, context)
34
+ end
35
+ end
36
+
37
+ def end_tag
38
+ self.register_current_block
39
+
40
+ options[:block_stack].pop
41
+ options[:current_block] = options[:block_stack].last
42
+ end
43
+
44
+ def call_super(context)
45
+ if parent
46
+ parent.render(context)
47
+ else
48
+ ''
49
+ end
50
+ end
51
+
52
+ def self.clone_block(block)
53
+ new_block = self.new(block.send(:instance_variable_get, :"@tag_name"), block.name, nil, {})
54
+ new_block.parent = block.parent
55
+ new_block.nodelist = block.nodelist
56
+ new_block
57
+ end
58
+
59
+ protected
60
+
61
+ def set_full_name!(options)
62
+ if options[:current_block]
63
+ @name = options[:current_block].name + '/' + @name
64
+ end
65
+ end
66
+
67
+ def register_current_block
68
+ options[:blocks] ||= {}
69
+
70
+ block = options[:blocks][@name]
71
+
72
+ if block
73
+ # copy the existing block in order to make it a parent of the parsed block
74
+ new_block = self.class.clone_block(block)
75
+
76
+ # replace the up-to-date version of the block in the parent template
77
+ block.parent = new_block
78
+ block.nodelist = @nodelist
79
+ end
80
+ end
81
+
82
+ end
83
+
84
+ class InheritedBlockDrop < Drop
85
+
86
+ def initialize(block)
87
+ @block = block
88
+ end
89
+
90
+ def name
91
+ @block.name
92
+ end
93
+
94
+ def super
95
+ @block.call_super(@context)
96
+ end
97
+
98
+ end
99
+
100
+ Template.register_tag('block', InheritedBlock)
101
+ end
@@ -0,0 +1,22 @@
1
+ module Liquid
2
+ class Raw < Block
3
+ FullTokenPossiblyInvalid = /^(.*)#{TagStart}\s*(\w+)\s*(.*)?#{TagEnd}$/o
4
+
5
+ def parse(tokens)
6
+ @nodelist ||= []
7
+ @nodelist.clear
8
+ while token = tokens.shift
9
+ if token =~ FullTokenPossiblyInvalid
10
+ @nodelist << $1 if $1 != ""
11
+ if block_delimiter == $2
12
+ end_tag
13
+ return
14
+ end
15
+ end
16
+ @nodelist << token if not token.empty?
17
+ end
18
+ end
19
+ end
20
+
21
+ Template.register_tag('raw', Raw)
22
+ 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
+ first_block = @blocks.first
15
+ unless first_block.evaluate(context)
16
+ return render_all(first_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,213 @@
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
+ DEFAULT_OPTIONS = {
18
+ :locale => I18n.new
19
+ }
20
+
21
+ attr_accessor :root, :resource_limits
22
+ @@file_system = BlankFileSystem.new
23
+
24
+ class << self
25
+ def file_system
26
+ @@file_system
27
+ end
28
+
29
+ def file_system=(obj)
30
+ @@file_system = obj
31
+ end
32
+
33
+ def register_tag(name, klass)
34
+ tags[name.to_s] = klass
35
+ end
36
+
37
+ def tags
38
+ @tags ||= {}
39
+ end
40
+
41
+ # Disabled (false) for better performance
42
+ def count_lines=(flag)
43
+ @count_lines = flag
44
+ end
45
+
46
+ def count_lines
47
+ @count_lines || false
48
+ end
49
+
50
+ # Sets how strict the parser should be.
51
+ # :lax acts like liquid 2.5 and silently ignores malformed tags in most cases.
52
+ # :warn is the default and will give deprecation warnings when invalid syntax is used.
53
+ # :strict will enforce correct syntax.
54
+ def error_mode=(mode)
55
+ @error_mode = mode
56
+ end
57
+
58
+ def error_mode
59
+ @error_mode || :lax
60
+ end
61
+
62
+ # Pass a module with filter methods which should be available
63
+ # to all liquid views. Good for registering the standard library
64
+ def register_filter(mod)
65
+ Strainer.global_filter(mod)
66
+ end
67
+
68
+ # creates a new <tt>Template</tt> object from liquid source code
69
+ def parse(source, options = {})
70
+ template = Template.new
71
+ template.parse(source, options)
72
+ template
73
+ end
74
+ end
75
+
76
+ # creates a new <tt>Template</tt> from an array of tokens. Use <tt>Template.parse</tt> instead
77
+ def initialize
78
+ @resource_limits = {}
79
+ end
80
+
81
+ # Parse source code.
82
+ # Returns self for easy chaining
83
+ def parse(source, options = {})
84
+ _options = { template: self }.merge(DEFAULT_OPTIONS).merge(options)
85
+ @root = Document.new(tokenize(source), _options)
86
+ @warnings = nil
87
+ self
88
+ end
89
+
90
+ def warnings
91
+ return [] unless @root
92
+ @warnings ||= @root.warnings
93
+ end
94
+
95
+ def registers
96
+ @registers ||= {}
97
+ end
98
+
99
+ def assigns
100
+ @assigns ||= {}
101
+ end
102
+
103
+ def instance_assigns
104
+ @instance_assigns ||= {}
105
+ end
106
+
107
+ def errors
108
+ @errors ||= []
109
+ end
110
+
111
+ # Render takes a hash with local variables.
112
+ #
113
+ # if you use the same filters over and over again consider registering them globally
114
+ # with <tt>Template.register_filter</tt>
115
+ #
116
+ # Following options can be passed:
117
+ #
118
+ # * <tt>filters</tt> : array with local filters
119
+ # * <tt>registers</tt> : hash with register variables. Those can be accessed from
120
+ # filters and tags and might be useful to integrate liquid more with its host application
121
+ #
122
+ def render(*args)
123
+ return '' if @root.nil?
124
+
125
+ context = case args.first
126
+ when Liquid::Context
127
+ args.shift
128
+ when Liquid::Drop
129
+ drop = args.shift
130
+ drop.context = Context.new([drop, assigns], instance_assigns, registers, @rethrow_errors, @resource_limits)
131
+ when Hash
132
+ Context.new([args.shift, assigns], instance_assigns, registers, @rethrow_errors, @resource_limits)
133
+ when nil
134
+ Context.new(assigns, instance_assigns, registers, @rethrow_errors, @resource_limits)
135
+ else
136
+ raise ArgumentError, "Expected Hash or Liquid::Context as parameter"
137
+ end
138
+
139
+ case args.last
140
+ when Hash
141
+ options = args.pop
142
+
143
+ if options[:registers].is_a?(Hash)
144
+ self.registers.merge!(options[:registers])
145
+ end
146
+
147
+ if options[:filters]
148
+ context.add_filters(options[:filters])
149
+ end
150
+
151
+ when Module
152
+ context.add_filters(args.pop)
153
+ when Array
154
+ context.add_filters(args.pop)
155
+ end
156
+
157
+ begin
158
+ # render the nodelist.
159
+ # for performance reasons we get an array back here. join will make a string out of it.
160
+ result = @root.render(context)
161
+ result.respond_to?(:join) ? result.join : result
162
+ rescue Liquid::MemoryError => e
163
+ context.handle_error(e)
164
+ ensure
165
+ @errors = context.errors
166
+ end
167
+ end
168
+
169
+ def render!(*args)
170
+ @rethrow_errors = true; render(*args)
171
+ end
172
+
173
+ def walk(memo = {}, &block)
174
+ # puts @root.nodelist.inspect
175
+ self._walk(@root.nodelist, memo, &block)
176
+ end
177
+
178
+ def _walk(list, memo = {}, &block)
179
+ list.each do |node|
180
+ saved_memo = memo.clone
181
+
182
+ # puts "fetch ! #{node.respond_to?(:name) ? node.name : 'String'} / #{node.respond_to?(:nodelist)}"
183
+ if block_given?
184
+ # puts "youpi ! #{node.name}"
185
+ _memo = yield(node, memo) || {}
186
+ memo.merge!(_memo)
187
+ end
188
+
189
+ if node.respond_to?(:nodelist) && !node.nodelist.blank?
190
+ self._walk(node.nodelist, memo, &block)
191
+ end
192
+
193
+ memo = saved_memo
194
+ end
195
+ memo
196
+ end
197
+
198
+ private
199
+
200
+ # Uses the <tt>Liquid::TemplateParser</tt> regexp to tokenize the passed source
201
+ def tokenize(source)
202
+ source = source.source if source.respond_to?(:source)
203
+ return [] if source.to_s.empty?
204
+ tokens = source.split(TemplateParser)
205
+
206
+ # removes the rogue empty element at the beginning of the array
207
+ tokens.shift if tokens[0] and tokens[0].empty?
208
+
209
+ tokens
210
+ end
211
+
212
+ end
213
+ end
@@ -0,0 +1,30 @@
1
+ module Liquid
2
+ module Utils
3
+ def self.slice_collection_using_each(collection, from, to)
4
+ segments = []
5
+ index = 0
6
+
7
+ # Maintains Ruby 1.8.7 String#each behaviour on 1.9
8
+ return [collection] if non_blank_string?(collection)
9
+
10
+ collection.each do |item|
11
+
12
+ if to && to <= index
13
+ break
14
+ end
15
+
16
+ if from <= index
17
+ segments << item
18
+ end
19
+
20
+ index += 1
21
+ end
22
+
23
+ segments
24
+ end
25
+
26
+ def self.non_blank_string?(collection)
27
+ collection.is_a?(String) && collection != ''
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,109 @@
1
+ module Liquid
2
+
3
+ # Holds variables. Variables are only loaded "just in time"
4
+ # and are not evaluated as part of the render stage
5
+ #
6
+ # {{ monkey }}
7
+ # {{ user.name }}
8
+ #
9
+ # Variables can be combined with filters:
10
+ #
11
+ # {{ user | link }}
12
+ #
13
+ class Variable
14
+ FilterParser = /(?:#{FilterSeparator}|(?:\s*(?:#{QuotedFragment}|#{ArgumentSeparator})\s*)+)/o
15
+ EasyParse = /^ *(\w+(?:\.\w+)*) *$/
16
+ attr_accessor :filters, :name, :warnings
17
+
18
+ def initialize(markup, options = {})
19
+ @markup = markup
20
+ @name = nil
21
+ @options = options || {}
22
+
23
+
24
+ case @options[:error_mode] || Template.error_mode
25
+ when :strict then strict_parse(markup)
26
+ when :lax then lax_parse(markup)
27
+ when :warn
28
+ begin
29
+ strict_parse(markup)
30
+ rescue SyntaxError => e
31
+ @warnings ||= []
32
+ @warnings << e
33
+ lax_parse(markup)
34
+ end
35
+ end
36
+ end
37
+
38
+ def lax_parse(markup)
39
+ @filters = []
40
+ if match = markup.match(/\s*(#{QuotedFragment})(.*)/o)
41
+ @name = match[1]
42
+ if match[2].match(/#{FilterSeparator}\s*(.*)/o)
43
+ filters = Regexp.last_match(1).scan(FilterParser)
44
+ filters.each do |f|
45
+ if matches = f.match(/\s*(\w+)/)
46
+ filtername = matches[1]
47
+ filterargs = f.scan(/(?:#{FilterArgumentSeparator}|#{ArgumentSeparator})\s*((?:\w+\s*\:\s*)?#{QuotedFragment})/o).flatten
48
+ @filters << [filtername, filterargs]
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
54
+
55
+ def strict_parse(markup)
56
+ # Very simple valid cases
57
+ if markup =~ EasyParse
58
+ @name = $1
59
+ @filters = []
60
+ return
61
+ end
62
+
63
+ @filters = []
64
+ p = Parser.new(markup)
65
+ # Could be just filters with no input
66
+ @name = p.look(:pipe) ? '' : p.expression
67
+ while p.consume?(:pipe)
68
+ filtername = p.consume(:id)
69
+ filterargs = p.consume?(:colon) ? parse_filterargs(p) : []
70
+ @filters << [filtername, filterargs]
71
+ end
72
+ p.consume(:end_of_string)
73
+ rescue SyntaxError => e
74
+ e.message << " in \"{{#{markup}}}\""
75
+ raise e
76
+ end
77
+
78
+ def parse_filterargs(p)
79
+ # first argument
80
+ filterargs = [p.argument]
81
+ # followed by comma separated others
82
+ while p.consume?(:comma)
83
+ filterargs << p.argument
84
+ end
85
+ filterargs
86
+ end
87
+
88
+ def render(context)
89
+ return '' if @name.nil?
90
+ @filters.inject(context[@name]) do |output, filter|
91
+ filterargs = []
92
+ keyword_args = {}
93
+ filter[1].to_a.each do |a|
94
+ if matches = a.match(/\A#{TagAttributes}\z/o)
95
+ keyword_args[matches[1]] = context[matches[2]]
96
+ else
97
+ filterargs << context[a]
98
+ end
99
+ end
100
+ filterargs << keyword_args unless keyword_args.empty?
101
+ begin
102
+ output = context.invoke(filter[0], output, *filterargs)
103
+ rescue FilterNotFound
104
+ raise FilterNotFound, "Error - filter '#{filter[0]}' in '#{@markup.strip}' could not be found."
105
+ end
106
+ end
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,4 @@
1
+ # encoding: utf-8
2
+ module Liquid
3
+ VERSION = "2.6.0"
4
+ end
data/lib/liquid.rb ADDED
@@ -0,0 +1,72 @@
1
+ # Copyright (c) 2005 Tobias Luetke
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining
4
+ # a copy of this software and associated documentation files (the
5
+ # "Software"), to deal in the Software without restriction, including
6
+ # without limitation the rights to use, copy, modify, merge, publish,
7
+ # distribute, sublicense, and/or sell copies of the Software, and to
8
+ # permit persons to whom the Software is furnished to do so, subject to
9
+ # the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be
12
+ # included in all copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
+
22
+ module Liquid
23
+ FilterSeparator = /\|/
24
+ ArgumentSeparator = ','
25
+ FilterArgumentSeparator = ':'
26
+ VariableAttributeSeparator = '.'
27
+ TagStart = /\{\%/
28
+ TagEnd = /\%\}/
29
+ VariableSignature = /\(?[\w\-\.\[\]]\)?/
30
+ VariableSegment = /[\w\-]/
31
+ VariableStart = /\{\{/
32
+ VariableEnd = /\}\}/
33
+ VariableIncompleteEnd = /\}\}?/
34
+ QuotedString = /"[^"]*"|'[^']*'/
35
+ QuotedFragment = /#{QuotedString}|(?:[^\s,\|'"]|#{QuotedString})+/o
36
+ StrictQuotedFragment = /"[^"]+"|'[^']+'|[^\s|:,]+/
37
+ FirstFilterArgument = /#{FilterArgumentSeparator}(?:#{StrictQuotedFragment})/o
38
+ OtherFilterArgument = /#{ArgumentSeparator}(?:#{StrictQuotedFragment})/o
39
+ SpacelessFilter = /^(?:'[^']+'|"[^"]+"|[^'"])*#{FilterSeparator}(?:#{StrictQuotedFragment})(?:#{FirstFilterArgument}(?:#{OtherFilterArgument})*)?/o
40
+ Expression = /(?:#{QuotedFragment}(?:#{SpacelessFilter})*)/o
41
+ TagAttributes = /(\w+)\s*\:\s*(#{QuotedFragment})/o
42
+ AnyStartingTag = /\{\{|\{\%/
43
+ PartialTemplateParser = /#{TagStart}.*?#{TagEnd}|#{VariableStart}.*?#{VariableIncompleteEnd}/o
44
+ TemplateParser = /(#{PartialTemplateParser}|#{AnyStartingTag})/o
45
+ VariableParser = /\[[^\]]+\]|#{VariableSegment}+\??/o
46
+ end
47
+
48
+ require "liquid/version"
49
+ require 'liquid/lexer'
50
+ require 'liquid/parser'
51
+ require 'liquid/i18n'
52
+ require 'liquid/drop'
53
+ require 'liquid/extensions'
54
+ require 'liquid/errors'
55
+ require 'liquid/interrupts'
56
+ require 'liquid/strainer'
57
+ require 'liquid/context'
58
+ require 'liquid/tag'
59
+ require 'liquid/block'
60
+ require 'liquid/document'
61
+ require 'liquid/variable'
62
+ require 'liquid/file_system'
63
+ require 'liquid/template'
64
+ require 'liquid/htmltags'
65
+ require 'liquid/standardfilters'
66
+ require 'liquid/condition'
67
+ require 'liquid/module_ex'
68
+ require 'liquid/utils'
69
+
70
+ # Load all the tags of the standard library
71
+ #
72
+ Dir[File.dirname(__FILE__) + '/liquid/tags/*.rb'].each { |f| require f }
@@ -0,0 +1 @@
1
+ require 'liquid'