spinto-liquid 2.3.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (62) hide show
  1. data/History.md +56 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +44 -0
  4. data/lib/extras/liquid_view.rb +51 -0
  5. data/lib/liquid/block.rb +101 -0
  6. data/lib/liquid/condition.rb +120 -0
  7. data/lib/liquid/context.rb +245 -0
  8. data/lib/liquid/document.rb +17 -0
  9. data/lib/liquid/drop.rb +49 -0
  10. data/lib/liquid/errors.rb +11 -0
  11. data/lib/liquid/extensions.rb +62 -0
  12. data/lib/liquid/file_system.rb +62 -0
  13. data/lib/liquid/htmltags.rb +75 -0
  14. data/lib/liquid/module_ex.rb +62 -0
  15. data/lib/liquid/standardfilters.rb +241 -0
  16. data/lib/liquid/strainer.rb +54 -0
  17. data/lib/liquid/tag.rb +26 -0
  18. data/lib/liquid/tags/assign.rb +33 -0
  19. data/lib/liquid/tags/capture.rb +35 -0
  20. data/lib/liquid/tags/case.rb +79 -0
  21. data/lib/liquid/tags/comment.rb +9 -0
  22. data/lib/liquid/tags/cycle.rb +59 -0
  23. data/lib/liquid/tags/decrement.rb +39 -0
  24. data/lib/liquid/tags/for.rb +190 -0
  25. data/lib/liquid/tags/if.rb +79 -0
  26. data/lib/liquid/tags/ifchanged.rb +20 -0
  27. data/lib/liquid/tags/include.rb +65 -0
  28. data/lib/liquid/tags/increment.rb +35 -0
  29. data/lib/liquid/tags/raw.rb +21 -0
  30. data/lib/liquid/tags/unless.rb +33 -0
  31. data/lib/liquid/template.rb +150 -0
  32. data/lib/liquid/variable.rb +50 -0
  33. data/lib/liquid.rb +66 -0
  34. data/test/liquid/assign_test.rb +21 -0
  35. data/test/liquid/block_test.rb +58 -0
  36. data/test/liquid/capture_test.rb +40 -0
  37. data/test/liquid/condition_test.rb +127 -0
  38. data/test/liquid/context_test.rb +478 -0
  39. data/test/liquid/drop_test.rb +162 -0
  40. data/test/liquid/error_handling_test.rb +81 -0
  41. data/test/liquid/file_system_test.rb +29 -0
  42. data/test/liquid/filter_test.rb +106 -0
  43. data/test/liquid/module_ex_test.rb +87 -0
  44. data/test/liquid/output_test.rb +116 -0
  45. data/test/liquid/parsing_quirks_test.rb +52 -0
  46. data/test/liquid/regexp_test.rb +44 -0
  47. data/test/liquid/security_test.rb +41 -0
  48. data/test/liquid/standard_filter_test.rb +195 -0
  49. data/test/liquid/strainer_test.rb +25 -0
  50. data/test/liquid/tags/for_tag_test.rb +215 -0
  51. data/test/liquid/tags/html_tag_test.rb +39 -0
  52. data/test/liquid/tags/if_else_tag_test.rb +160 -0
  53. data/test/liquid/tags/include_tag_test.rb +139 -0
  54. data/test/liquid/tags/increment_tag_test.rb +24 -0
  55. data/test/liquid/tags/raw_tag_test.rb +15 -0
  56. data/test/liquid/tags/standard_tag_test.rb +295 -0
  57. data/test/liquid/tags/statements_test.rb +134 -0
  58. data/test/liquid/tags/unless_else_tag_test.rb +26 -0
  59. data/test/liquid/template_test.rb +74 -0
  60. data/test/liquid/variable_test.rb +170 -0
  61. data/test/test_helper.rb +29 -0
  62. metadata +136 -0
@@ -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
+ ExpressionsAndOperators = /(?:\b(?:\s?and\s?|\s?or\s?)\b|(?:\s*(?!\b(?:\s?and\s?|\s?or\s?)\b)(?:#{QuotedFragment}|\S+)\s*)+)/
18
+
19
+ def initialize(tag_name, markup, tokens)
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.scan(ExpressionsAndOperators).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).to_s.strip
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,65 @@
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 = _read_template_from_file_system(context)
28
+ partial = Liquid::Template.parse(source)
29
+ variable = context[@variable_name || @template_name[1..-2]]
30
+
31
+ context.stack do
32
+ @attributes.each do |key, value|
33
+ context[key] = context[value]
34
+ end
35
+
36
+ if variable.is_a?(Array)
37
+ variable.collect do |variable|
38
+ context[@template_name[1..-2]] = variable
39
+ partial.render(context)
40
+ end
41
+ else
42
+ context[@template_name[1..-2]] = variable
43
+ partial.render(context)
44
+ end
45
+ end
46
+ end
47
+
48
+ private
49
+ def _read_template_from_file_system(context)
50
+ file_system = context.registers[:file_system] || Liquid::Template.file_system
51
+
52
+ # make read_template_file call backwards-compatible.
53
+ case file_system.method(:read_template_file).arity
54
+ when 1
55
+ file_system.read_template_file(context[@template_name])
56
+ when 2
57
+ file_system.read_template_file(context[@template_name], context)
58
+ else
59
+ raise ArgumentError, "file_system.read_template_file expects two parameters: (template_name, context)"
60
+ end
61
+ end
62
+ end
63
+
64
+ Template.register_tag('include', Include)
65
+ end
@@ -0,0 +1,35 @@
1
+ module Liquid
2
+
3
+ # increment is used in a place where one needs to insert a counter
4
+ # into a template, and needs the counter to survive across
5
+ # multiple instantiations of the template.
6
+ # (To achieve the survival, the application must keep the context)
7
+ #
8
+ # if the variable does not exist, it is created with value 0.
9
+
10
+ # Hello: {% increment variable %}
11
+ #
12
+ # gives you:
13
+ #
14
+ # Hello: 0
15
+ # Hello: 1
16
+ # Hello: 2
17
+ #
18
+ class Increment < Tag
19
+ def initialize(tag_name, markup, tokens)
20
+ @variable = markup.strip
21
+
22
+ super
23
+ end
24
+
25
+ def render(context)
26
+ value = context.environments.first[@variable] ||= 0
27
+ context.environments.first[@variable] = value + 1
28
+ value.to_s
29
+ end
30
+
31
+ private
32
+ end
33
+
34
+ Template.register_tag('increment', Increment)
35
+ end
@@ -0,0 +1,21 @@
1
+ module Liquid
2
+ class Raw < Block
3
+ def parse(tokens)
4
+ @nodelist ||= []
5
+ @nodelist.clear
6
+
7
+ while token = tokens.shift
8
+ if token =~ FullToken
9
+ if block_delimiter == $1
10
+ end_tag
11
+ return
12
+ end
13
+ end
14
+ @nodelist << token if not token.empty?
15
+ end
16
+ end
17
+ end
18
+
19
+ Template.register_tag('raw', Raw)
20
+ end
21
+
@@ -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,150 @@
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 instance_assigns
71
+ @instance_assigns ||= {}
72
+ end
73
+
74
+ def errors
75
+ @errors ||= []
76
+ end
77
+
78
+ # Render takes a hash with local variables.
79
+ #
80
+ # if you use the same filters over and over again consider registering them globally
81
+ # with <tt>Template.register_filter</tt>
82
+ #
83
+ # Following options can be passed:
84
+ #
85
+ # * <tt>filters</tt> : array with local filters
86
+ # * <tt>registers</tt> : hash with register variables. Those can be accessed from
87
+ # filters and tags and might be useful to integrate liquid more with its host application
88
+ #
89
+ def render(*args)
90
+ return '' if @root.nil?
91
+
92
+ context = case args.first
93
+ when Liquid::Context
94
+ args.shift
95
+ when Hash
96
+ Context.new([args.shift, assigns], instance_assigns, registers, @rethrow_errors)
97
+ when nil
98
+ Context.new(assigns, instance_assigns, registers, @rethrow_errors)
99
+ else
100
+ raise ArgumentError, "Expect Hash or Liquid::Context as parameter"
101
+ end
102
+
103
+ case args.last
104
+ when Hash
105
+ options = args.pop
106
+
107
+ if options[:registers].is_a?(Hash)
108
+ self.registers.merge!(options[:registers])
109
+ end
110
+
111
+ if options[:filters]
112
+ context.add_filters(options[:filters])
113
+ end
114
+
115
+ when Module
116
+ context.add_filters(args.pop)
117
+ when Array
118
+ context.add_filters(args.pop)
119
+ end
120
+
121
+ begin
122
+ # render the nodelist.
123
+ # for performance reasons we get a array back here. join will make a string out of it
124
+ result = @root.render(context)
125
+ result.respond_to?(:join) ? result.join : result
126
+ ensure
127
+ @errors = context.errors
128
+ end
129
+ end
130
+
131
+ def render!(*args)
132
+ @rethrow_errors = true; render(*args)
133
+ end
134
+
135
+ private
136
+
137
+ # Uses the <tt>Liquid::TemplateParser</tt> regexp to tokenize the passed source
138
+ def tokenize(source)
139
+ source = source.source if source.respond_to?(:source)
140
+ return [] if source.to_s.empty?
141
+ tokens = source.split(TemplateParser)
142
+
143
+ # removes the rogue empty element at the beginning of the array
144
+ tokens.shift if tokens[0] and tokens[0].empty?
145
+
146
+ tokens
147
+ end
148
+
149
+ end
150
+ end
@@ -0,0 +1,50 @@
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*(?!(?:#{FilterSeparator}))(?:#{QuotedFragment}|\S+)\s*)+)/
15
+ attr_accessor :filters, :name
16
+
17
+ def initialize(markup)
18
+ @markup = markup
19
+ @name = nil
20
+ @filters = []
21
+ if match = markup.match(/\s*(#{QuotedFragment})(.*)/)
22
+ @name = match[1]
23
+ if match[2].match(/#{FilterSeparator}\s*(.*)/)
24
+ filters = Regexp.last_match(1).scan(FilterParser)
25
+ filters.each do |f|
26
+ if matches = f.match(/\s*(\w+)/)
27
+ filtername = matches[1]
28
+ filterargs = f.scan(/(?:#{FilterArgumentSeparator}|#{ArgumentSeparator})\s*(#{QuotedFragment})/).flatten
29
+ @filters << [filtername.to_sym, filterargs]
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
35
+
36
+ def render(context)
37
+ return '' if @name.nil?
38
+ @filters.inject(context[@name]) do |output, filter|
39
+ filterargs = filter[1].to_a.collect do |a|
40
+ context[a]
41
+ end
42
+ begin
43
+ output = context.invoke(filter[0], output, *filterargs)
44
+ rescue FilterNotFound
45
+ raise FilterNotFound, "Error - filter '#{filter[0]}' in '#{@markup.strip}' could not be found."
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end