spinto-liquid 2.3.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.
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