liquid 1.7.0
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.
- data/CHANGELOG +38 -0
- data/MIT-LICENSE +20 -0
- data/Manifest.txt +60 -0
- data/README +38 -0
- data/Rakefile +24 -0
- data/example/server/example_servlet.rb +37 -0
- data/example/server/liquid_servlet.rb +28 -0
- data/example/server/server.rb +12 -0
- data/example/server/templates/index.liquid +6 -0
- data/example/server/templates/products.liquid +45 -0
- data/init.rb +6 -0
- data/lib/extras/liquid_view.rb +27 -0
- data/lib/liquid.rb +66 -0
- data/lib/liquid/block.rb +101 -0
- data/lib/liquid/condition.rb +91 -0
- data/lib/liquid/context.rb +216 -0
- data/lib/liquid/document.rb +17 -0
- data/lib/liquid/drop.rb +48 -0
- data/lib/liquid/errors.rb +7 -0
- data/lib/liquid/extensions.rb +56 -0
- data/lib/liquid/file_system.rb +62 -0
- data/lib/liquid/htmltags.rb +64 -0
- data/lib/liquid/standardfilters.rb +125 -0
- data/lib/liquid/strainer.rb +43 -0
- data/lib/liquid/tag.rb +25 -0
- data/lib/liquid/tags/assign.rb +22 -0
- data/lib/liquid/tags/capture.rb +22 -0
- data/lib/liquid/tags/case.rb +68 -0
- data/lib/liquid/tags/comment.rb +9 -0
- data/lib/liquid/tags/cycle.rb +46 -0
- data/lib/liquid/tags/for.rb +81 -0
- data/lib/liquid/tags/if.rb +51 -0
- data/lib/liquid/tags/ifchanged.rb +20 -0
- data/lib/liquid/tags/include.rb +56 -0
- data/lib/liquid/tags/unless.rb +29 -0
- data/lib/liquid/template.rb +150 -0
- data/lib/liquid/variable.rb +39 -0
- data/test/block_test.rb +50 -0
- data/test/context_test.rb +340 -0
- data/test/drop_test.rb +139 -0
- data/test/error_handling_test.rb +65 -0
- data/test/extra/breakpoint.rb +547 -0
- data/test/extra/caller.rb +80 -0
- data/test/file_system_test.rb +30 -0
- data/test/filter_test.rb +98 -0
- data/test/helper.rb +20 -0
- data/test/html_tag_test.rb +24 -0
- data/test/if_else_test.rb +95 -0
- data/test/include_tag_test.rb +91 -0
- data/test/output_test.rb +121 -0
- data/test/parsing_quirks_test.rb +14 -0
- data/test/regexp_test.rb +39 -0
- data/test/security_test.rb +41 -0
- data/test/standard_filter_test.rb +101 -0
- data/test/standard_tag_test.rb +336 -0
- data/test/statements_test.rb +137 -0
- data/test/strainer_test.rb +16 -0
- data/test/template_test.rb +26 -0
- data/test/unless_else_test.rb +19 -0
- data/test/variable_test.rb +135 -0
- metadata +114 -0
@@ -0,0 +1,51 @@
|
|
1
|
+
module Liquid
|
2
|
+
class If < Block
|
3
|
+
Syntax = /(#{QuotedFragment})\s*([=!<>]+)?\s*(#{QuotedFragment})?/
|
4
|
+
|
5
|
+
def initialize(markup, tokens)
|
6
|
+
@blocks = []
|
7
|
+
|
8
|
+
push_block('if', markup)
|
9
|
+
|
10
|
+
super
|
11
|
+
end
|
12
|
+
|
13
|
+
def unknown_tag(tag, markup, tokens)
|
14
|
+
if ['elsif', 'else'].include?(tag)
|
15
|
+
push_block(tag, markup)
|
16
|
+
else
|
17
|
+
super
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def render(context)
|
22
|
+
context.stack do
|
23
|
+
@blocks.each do |block|
|
24
|
+
if block.evaluate(context)
|
25
|
+
return render_all(block.attachment, context)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
''
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def push_block(tag, markup)
|
35
|
+
|
36
|
+
block = if tag == 'else'
|
37
|
+
ElseCondition.new
|
38
|
+
elsif markup =~ Syntax
|
39
|
+
Condition.new($1, $2, $3)
|
40
|
+
else
|
41
|
+
raise SyntaxError.new("Syntax Error in tag '#{tag}' - Valid syntax: #{tag} [condition]")
|
42
|
+
end
|
43
|
+
|
44
|
+
@blocks.push(block)
|
45
|
+
@nodelist = block.attach(Array.new)
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
|
50
|
+
Template.register_tag('if', If)
|
51
|
+
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,56 @@
|
|
1
|
+
module Liquid
|
2
|
+
class Include < Tag
|
3
|
+
Syntax = /(#{QuotedFragment}+)(\s+(?:with|for)\s+(#{QuotedFragment}+))?/
|
4
|
+
|
5
|
+
def initialize(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
|
+
|
31
|
+
variable = context[@variable_name]
|
32
|
+
|
33
|
+
context.stack do
|
34
|
+
@attributes.each do |key, value|
|
35
|
+
context[key] = context[value]
|
36
|
+
end
|
37
|
+
|
38
|
+
if variable.is_a?(Array)
|
39
|
+
|
40
|
+
variable.collect do |variable|
|
41
|
+
context[@template_name[1..-2]] = variable
|
42
|
+
partial.render(context)
|
43
|
+
end
|
44
|
+
|
45
|
+
else
|
46
|
+
|
47
|
+
context[@template_name[1..-2]] = variable
|
48
|
+
partial.render(context)
|
49
|
+
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
Template.register_tag('include', Include)
|
56
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/if'
|
2
|
+
|
3
|
+
module Liquid
|
4
|
+
|
5
|
+
class Unless < If
|
6
|
+
def render(context)
|
7
|
+
context.stack do
|
8
|
+
|
9
|
+
# First condition is interpreted backwards ( if not )
|
10
|
+
block = @blocks.shift
|
11
|
+
unless block.evaluate(context)
|
12
|
+
return render_all(block.attachment, context)
|
13
|
+
end
|
14
|
+
|
15
|
+
# After the first condition unless works just like if
|
16
|
+
@blocks.each do |block|
|
17
|
+
if block.evaluate(context)
|
18
|
+
return render_all(block.attachment, context)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
''
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
|
28
|
+
Template.register_tag('unless', Unless)
|
29
|
+
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 errors
|
71
|
+
@errors ||= []
|
72
|
+
end
|
73
|
+
|
74
|
+
def handle_error(e)
|
75
|
+
errors.push(e)
|
76
|
+
raise if @rethrow_errors
|
77
|
+
|
78
|
+
case e
|
79
|
+
when SyntaxError
|
80
|
+
"Liquid syntax error: #{e.message}"
|
81
|
+
else
|
82
|
+
"Liquid error: #{e.message}"
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
# Render takes a hash with local variables.
|
87
|
+
#
|
88
|
+
# if you use the same filters over and over again consider registering them globally
|
89
|
+
# with <tt>Template.register_filter</tt>
|
90
|
+
#
|
91
|
+
# Following options can be passed:
|
92
|
+
#
|
93
|
+
# * <tt>filters</tt> : array with local filters
|
94
|
+
# * <tt>registers</tt> : hash with register variables. Those can be accessed from
|
95
|
+
# filters and tags and might be useful to integrate liquid more with its host application
|
96
|
+
#
|
97
|
+
def render(*args)
|
98
|
+
return '' if @root.nil?
|
99
|
+
|
100
|
+
context = case args.first
|
101
|
+
when Liquid::Context
|
102
|
+
args.shift
|
103
|
+
when Hash
|
104
|
+
self.assigns.merge!(args.shift)
|
105
|
+
Context.new(self)
|
106
|
+
when nil
|
107
|
+
Context.new(self)
|
108
|
+
end
|
109
|
+
|
110
|
+
case args.last
|
111
|
+
when Hash
|
112
|
+
options = args.pop
|
113
|
+
|
114
|
+
if options[:registers].is_a?(Hash)
|
115
|
+
self.registers.merge!(options[:registers])
|
116
|
+
end
|
117
|
+
|
118
|
+
if options[:filters]
|
119
|
+
context.add_filters(options[:filters])
|
120
|
+
end
|
121
|
+
when Module
|
122
|
+
context.add_filters(args.pop)
|
123
|
+
when Array
|
124
|
+
context.add_filters(args.pop)
|
125
|
+
end
|
126
|
+
|
127
|
+
# render the nodelist.
|
128
|
+
# for performance reasons we get a array back here. to_s will make a string out of it
|
129
|
+
@root.render(context).to_s
|
130
|
+
end
|
131
|
+
|
132
|
+
def render!(*args)
|
133
|
+
@rethrow_errors = true; render(*args)
|
134
|
+
end
|
135
|
+
|
136
|
+
private
|
137
|
+
|
138
|
+
# Uses the <tt>Liquid::TemplateParser</tt> regexp to tokenize the passed source
|
139
|
+
def tokenize(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,39 @@
|
|
1
|
+
module Liquid
|
2
|
+
|
3
|
+
# Hols variables. Variables are only loaded "just in time"
|
4
|
+
# they are not evaluated as part of the render stage
|
5
|
+
class Variable
|
6
|
+
attr_accessor :filters, :name
|
7
|
+
|
8
|
+
def initialize(markup)
|
9
|
+
@markup = markup
|
10
|
+
@name = markup.match(/\s*(#{QuotedFragment})/)[1]
|
11
|
+
if markup.match(/#{FilterSperator}\s*(.*)/)
|
12
|
+
filters = Regexp.last_match(1).split(/#{FilterSperator}/)
|
13
|
+
|
14
|
+
@filters = filters.collect do |f|
|
15
|
+
filtername = f.match(/\s*(\w+)/)[1]
|
16
|
+
filterargs = f.scan(/(?:#{FilterArgumentSeparator}|#{ArgumentSeparator})\s*(#{QuotedFragment})/).flatten
|
17
|
+
[filtername.to_sym, filterargs]
|
18
|
+
end
|
19
|
+
else
|
20
|
+
@filters = []
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def render(context)
|
25
|
+
output = context[@name]
|
26
|
+
@filters.inject(output) do |output, filter|
|
27
|
+
filterargs = filter[1].to_a.collect do |a|
|
28
|
+
context[a]
|
29
|
+
end
|
30
|
+
begin
|
31
|
+
output = context.invoke(filter[0], output, *filterargs)
|
32
|
+
rescue FilterNotFound
|
33
|
+
raise FilterNotFound, "Error - filter '#{filter[0]}' in '#{@markup.strip}' could not be found."
|
34
|
+
end
|
35
|
+
end
|
36
|
+
output
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
data/test/block_test.rb
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/helper'
|
2
|
+
|
3
|
+
class VariableTest < Test::Unit::TestCase
|
4
|
+
include Liquid
|
5
|
+
|
6
|
+
def test_blankspace
|
7
|
+
template = Liquid::Template.parse(" ")
|
8
|
+
assert_equal [" "], template.root.nodelist
|
9
|
+
end
|
10
|
+
|
11
|
+
def test_variable_beginning
|
12
|
+
template = Liquid::Template.parse("{{funk}} ")
|
13
|
+
assert_equal 2, template.root.nodelist.size
|
14
|
+
assert_equal Variable, template.root.nodelist[0].class
|
15
|
+
assert_equal String, template.root.nodelist[1].class
|
16
|
+
end
|
17
|
+
|
18
|
+
def test_variable_end
|
19
|
+
template = Liquid::Template.parse(" {{funk}}")
|
20
|
+
assert_equal 2, template.root.nodelist.size
|
21
|
+
assert_equal String, template.root.nodelist[0].class
|
22
|
+
assert_equal Variable, template.root.nodelist[1].class
|
23
|
+
end
|
24
|
+
|
25
|
+
def test_variable_middle
|
26
|
+
template = Liquid::Template.parse(" {{funk}} ")
|
27
|
+
assert_equal 3, template.root.nodelist.size
|
28
|
+
assert_equal String, template.root.nodelist[0].class
|
29
|
+
assert_equal Variable, template.root.nodelist[1].class
|
30
|
+
assert_equal String, template.root.nodelist[2].class
|
31
|
+
end
|
32
|
+
|
33
|
+
def test_variable_many_embedded_fragments
|
34
|
+
template = Liquid::Template.parse(" {{funk}} {{so}} {{brother}} ")
|
35
|
+
assert_equal 7, template.root.nodelist.size
|
36
|
+
assert_equal [String, Variable, String, Variable, String, Variable, String], block_types(template.root.nodelist)
|
37
|
+
end
|
38
|
+
|
39
|
+
def test_with_block
|
40
|
+
template = Liquid::Template.parse(" {% comment %} {% endcomment %} ")
|
41
|
+
assert_equal [String, Comment, String], block_types(template.root.nodelist)
|
42
|
+
assert_equal 3, template.root.nodelist.size
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def block_types(nodelist)
|
48
|
+
nodelist.collect { |node| node.class }
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,340 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/helper'
|
2
|
+
class HundredCentes
|
3
|
+
def to_liquid
|
4
|
+
100
|
5
|
+
end
|
6
|
+
end
|
7
|
+
|
8
|
+
class CentsDrop < Liquid::Drop
|
9
|
+
def amount
|
10
|
+
HundredCentes.new
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
class ContextSensitiveDrop < Liquid::Drop
|
15
|
+
def test
|
16
|
+
@context['test']
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
|
21
|
+
class ContextTest < Test::Unit::TestCase
|
22
|
+
include Liquid
|
23
|
+
|
24
|
+
def setup
|
25
|
+
@template = Liquid::Template.new
|
26
|
+
@context = Liquid::Context.new(@template)
|
27
|
+
end
|
28
|
+
|
29
|
+
def test_variables
|
30
|
+
@context['string'] = 'string'
|
31
|
+
assert_equal 'string', @context['string']
|
32
|
+
|
33
|
+
@context['num'] = 5
|
34
|
+
assert_equal 5, @context['num']
|
35
|
+
|
36
|
+
@context['time'] = Time.parse('2006-06-06 12:00:00')
|
37
|
+
assert_equal Time.parse('2006-06-06 12:00:00'), @context['time']
|
38
|
+
|
39
|
+
@context['date'] = Date.today
|
40
|
+
assert_equal Date.today, @context['date']
|
41
|
+
|
42
|
+
now = DateTime.now
|
43
|
+
@context['datetime'] = now
|
44
|
+
assert_equal now, @context['datetime']
|
45
|
+
|
46
|
+
@context['bool'] = true
|
47
|
+
assert_equal true, @context['bool']
|
48
|
+
|
49
|
+
@context['bool'] = false
|
50
|
+
assert_equal false, @context['bool']
|
51
|
+
|
52
|
+
@context['nil'] = nil
|
53
|
+
assert_equal nil, @context['nil']
|
54
|
+
assert_equal nil, @context['nil']
|
55
|
+
end
|
56
|
+
|
57
|
+
def test_variables_not_existing
|
58
|
+
assert_equal nil, @context['does_not_exist']
|
59
|
+
end
|
60
|
+
|
61
|
+
def test_scoping
|
62
|
+
assert_nothing_raised do
|
63
|
+
@context.push
|
64
|
+
@context.pop
|
65
|
+
end
|
66
|
+
|
67
|
+
assert_raise(Liquid::ContextError) do
|
68
|
+
@context.pop
|
69
|
+
end
|
70
|
+
|
71
|
+
assert_raise(Liquid::ContextError) do
|
72
|
+
@context.push
|
73
|
+
@context.pop
|
74
|
+
@context.pop
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def test_length_query
|
79
|
+
|
80
|
+
@context['numbers'] = [1,2,3,4]
|
81
|
+
|
82
|
+
assert_equal 4, @context['numbers.size']
|
83
|
+
|
84
|
+
@context['numbers'] = {1 => 1,2 => 2,3 => 3,4 => 4}
|
85
|
+
|
86
|
+
assert_equal 4, @context['numbers.size']
|
87
|
+
|
88
|
+
@context['numbers'] = {1 => 1,2 => 2,3 => 3,4 => 4, 'size' => 1000}
|
89
|
+
|
90
|
+
assert_equal 1000, @context['numbers.size']
|
91
|
+
|
92
|
+
end
|
93
|
+
|
94
|
+
def test_add_filter
|
95
|
+
|
96
|
+
filter = Module.new do
|
97
|
+
def hi(output)
|
98
|
+
output + ' hi!'
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
context = Context.new(@template)
|
103
|
+
context.add_filters(filter)
|
104
|
+
assert_equal 'hi? hi!', context.invoke(:hi, 'hi?')
|
105
|
+
|
106
|
+
context = Context.new(@template)
|
107
|
+
assert_equal 'hi?', context.invoke(:hi, 'hi?')
|
108
|
+
|
109
|
+
context.add_filters(filter)
|
110
|
+
assert_equal 'hi? hi!', context.invoke(:hi, 'hi?')
|
111
|
+
|
112
|
+
end
|
113
|
+
|
114
|
+
def test_override_global_filter
|
115
|
+
global = Module.new do
|
116
|
+
def notice(output)
|
117
|
+
"Global #{output}"
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
local = Module.new do
|
122
|
+
def notice(output)
|
123
|
+
"Local #{output}"
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
Template.register_filter(global)
|
128
|
+
assert_equal 'Global test', Template.parse("{{'test' | notice }}").render
|
129
|
+
assert_equal 'Local test', Template.parse("{{'test' | notice }}").render({}, :filters => [local])
|
130
|
+
end
|
131
|
+
|
132
|
+
def test_only_intended_filters_make_it_there
|
133
|
+
|
134
|
+
filter = Module.new do
|
135
|
+
def hi(output)
|
136
|
+
output + ' hi!'
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
context = Context.new(@template)
|
141
|
+
methods = context.strainer.methods
|
142
|
+
context.add_filters(filter)
|
143
|
+
assert_equal (methods + ['hi']).sort, context.strainer.methods.sort
|
144
|
+
end
|
145
|
+
|
146
|
+
def test_add_item_in_outer_scope
|
147
|
+
@context['test'] = 'test'
|
148
|
+
@context.push
|
149
|
+
assert_equal 'test', @context['test']
|
150
|
+
@context.pop
|
151
|
+
assert_equal 'test', @context['test']
|
152
|
+
end
|
153
|
+
|
154
|
+
def test_add_item_in_inner_scope
|
155
|
+
@context.push
|
156
|
+
@context['test'] = 'test'
|
157
|
+
assert_equal 'test', @context['test']
|
158
|
+
@context.pop
|
159
|
+
assert_equal nil, @context['test']
|
160
|
+
end
|
161
|
+
|
162
|
+
def test_hierachical_data
|
163
|
+
@context['hash'] = {"name" => 'tobi'}
|
164
|
+
assert_equal 'tobi', @context['hash.name']
|
165
|
+
end
|
166
|
+
|
167
|
+
def test_keywords
|
168
|
+
assert_equal true, @context['true']
|
169
|
+
assert_equal false, @context['false']
|
170
|
+
end
|
171
|
+
|
172
|
+
def test_digits
|
173
|
+
assert_equal 100, @context['100']
|
174
|
+
assert_equal 100.00, @context['100.00']
|
175
|
+
end
|
176
|
+
|
177
|
+
def test_strings
|
178
|
+
assert_equal "hello!", @context['"hello!"']
|
179
|
+
assert_equal "hello!", @context["'hello!'"]
|
180
|
+
end
|
181
|
+
|
182
|
+
def test_merge
|
183
|
+
@context.merge({ "test" => "test" })
|
184
|
+
assert_equal 'test', @context['test']
|
185
|
+
@context.merge({ "test" => "newvalue", "foo" => "bar" })
|
186
|
+
assert_equal 'newvalue', @context['test']
|
187
|
+
assert_equal 'bar', @context['foo']
|
188
|
+
end
|
189
|
+
|
190
|
+
def test_array_notation
|
191
|
+
@context['test'] = [1,2,3,4,5]
|
192
|
+
|
193
|
+
assert_equal 1, @context['test[0]']
|
194
|
+
assert_equal 2, @context['test[1]']
|
195
|
+
assert_equal 3, @context['test[2]']
|
196
|
+
assert_equal 4, @context['test[3]']
|
197
|
+
assert_equal 5, @context['test[4]']
|
198
|
+
end
|
199
|
+
|
200
|
+
def test_recoursive_array_notation
|
201
|
+
@context['test'] = {'test' => [1,2,3,4,5]}
|
202
|
+
|
203
|
+
assert_equal 1, @context['test.test[0]']
|
204
|
+
|
205
|
+
@context['test'] = [{'test' => 'worked'}]
|
206
|
+
|
207
|
+
assert_equal 'worked', @context['test[0].test']
|
208
|
+
end
|
209
|
+
|
210
|
+
def test_try_first
|
211
|
+
@context['test'] = [1,2,3,4,5]
|
212
|
+
|
213
|
+
assert_equal 1, @context['test.first']
|
214
|
+
assert_equal 5, @context['test.last']
|
215
|
+
|
216
|
+
@context['test'] = {'test' => [1,2,3,4,5]}
|
217
|
+
|
218
|
+
assert_equal 1, @context['test.test.first']
|
219
|
+
assert_equal 5, @context['test.test.last']
|
220
|
+
|
221
|
+
@context['test'] = [1]
|
222
|
+
assert_equal 1, @context['test.first']
|
223
|
+
assert_equal 1, @context['test.last']
|
224
|
+
end
|
225
|
+
|
226
|
+
def test_access_hashes_with_hash_notation
|
227
|
+
|
228
|
+
@context['products'] = {'count' => 5, 'tags' => ['deepsnow', 'freestyle'] }
|
229
|
+
@context['product'] = {'variants' => [ {'title' => 'draft151cm'}, {'title' => 'element151cm'} ]}
|
230
|
+
|
231
|
+
|
232
|
+
assert_equal 5, @context['products[count]']
|
233
|
+
assert_equal 'deepsnow', @context['products[tags][0]']
|
234
|
+
assert_equal 'deepsnow', @context['products[tags].first']
|
235
|
+
assert_equal 'draft151cm', @context['product[variants][0][title]']
|
236
|
+
assert_equal 'element151cm', @context['product[variants][1][title]']
|
237
|
+
assert_equal 'draft151cm', @context['product[variants][0][title]']
|
238
|
+
assert_equal 'element151cm', @context['product[variants][last][title]']
|
239
|
+
end
|
240
|
+
|
241
|
+
|
242
|
+
def test_first_can_appear_in_middle_of_callchain
|
243
|
+
|
244
|
+
@context['product'] = {'variants' => [ {'title' => 'draft151cm'}, {'title' => 'element151cm'} ]}
|
245
|
+
|
246
|
+
assert_equal 'draft151cm', @context['product.variants[0].title']
|
247
|
+
assert_equal 'element151cm', @context['product.variants[1].title']
|
248
|
+
assert_equal 'draft151cm', @context['product.variants.first.title']
|
249
|
+
assert_equal 'element151cm', @context['product.variants.last.title']
|
250
|
+
|
251
|
+
end
|
252
|
+
|
253
|
+
def test_cents
|
254
|
+
@context.merge( "cents" => HundredCentes.new )
|
255
|
+
assert_equal 100, @context['cents']
|
256
|
+
end
|
257
|
+
|
258
|
+
def test_nested_cents
|
259
|
+
@context.merge( "cents" => { 'amount' => HundredCentes.new} )
|
260
|
+
assert_equal 100, @context['cents.amount']
|
261
|
+
|
262
|
+
@context.merge( "cents" => { 'cents' => { 'amount' => HundredCentes.new} } )
|
263
|
+
assert_equal 100, @context['cents.cents.amount']
|
264
|
+
end
|
265
|
+
|
266
|
+
def test_cents_through_drop
|
267
|
+
@context.merge( "cents" => CentsDrop.new )
|
268
|
+
assert_equal 100, @context['cents.amount']
|
269
|
+
end
|
270
|
+
|
271
|
+
def test_nested_cents_through_drop
|
272
|
+
@context.merge( "vars" => {"cents" => CentsDrop.new} )
|
273
|
+
assert_equal 100, @context['vars.cents.amount']
|
274
|
+
end
|
275
|
+
|
276
|
+
def test_context_from_within_drop
|
277
|
+
@context.merge( "test" => '123', "vars" => ContextSensitiveDrop.new )
|
278
|
+
assert_equal '123', @context['vars.test']
|
279
|
+
end
|
280
|
+
|
281
|
+
def test_nested_context_from_within_drop
|
282
|
+
@context.merge( "test" => '123', "vars" => {"local" => ContextSensitiveDrop.new } )
|
283
|
+
assert_equal '123', @context['vars.local.test']
|
284
|
+
end
|
285
|
+
|
286
|
+
def test_cents_through_drop_nestedly
|
287
|
+
@context.merge( "cents" => {"cents" => CentsDrop.new} )
|
288
|
+
assert_equal 100, @context['cents.cents.amount']
|
289
|
+
|
290
|
+
@context.merge( "cents" => { "cents" => {"cents" => CentsDrop.new}} )
|
291
|
+
assert_equal 100, @context['cents.cents.cents.amount']
|
292
|
+
end
|
293
|
+
|
294
|
+
def test_proc_as_variable
|
295
|
+
@context['dynamic'] = Proc.new { 'Hello' }
|
296
|
+
|
297
|
+
assert_equal 'Hello', @context['dynamic']
|
298
|
+
end
|
299
|
+
|
300
|
+
def test_lambda_as_variable
|
301
|
+
@context['dynamic'] = lambda { 'Hello' }
|
302
|
+
|
303
|
+
assert_equal 'Hello', @context['dynamic']
|
304
|
+
end
|
305
|
+
|
306
|
+
def test_nested_lambda_as_variable
|
307
|
+
@context['dynamic'] = { "lambda" => lambda { 'Hello' } }
|
308
|
+
|
309
|
+
assert_equal 'Hello', @context['dynamic.lambda']
|
310
|
+
end
|
311
|
+
|
312
|
+
def test_lambda_is_called_once
|
313
|
+
@context['callcount'] = lambda { @global ||= 0; @global += 1; @global.to_s }
|
314
|
+
|
315
|
+
assert_equal '1', @context['callcount']
|
316
|
+
assert_equal '1', @context['callcount']
|
317
|
+
assert_equal '1', @context['callcount']
|
318
|
+
|
319
|
+
@global = nil
|
320
|
+
end
|
321
|
+
|
322
|
+
def test_nested_lambda_is_called_once
|
323
|
+
@context['callcount'] = { "lambda" => lambda { @global ||= 0; @global += 1; @global.to_s } }
|
324
|
+
|
325
|
+
assert_equal '1', @context['callcount.lambda']
|
326
|
+
assert_equal '1', @context['callcount.lambda']
|
327
|
+
assert_equal '1', @context['callcount.lambda']
|
328
|
+
|
329
|
+
@global = nil
|
330
|
+
end
|
331
|
+
|
332
|
+
def test_access_to_context_from_proc
|
333
|
+
@context.registers[:magic] = 345392
|
334
|
+
|
335
|
+
@context['magic'] = lambda { @context.registers[:magic] }
|
336
|
+
|
337
|
+
assert_equal 345392, @context['magic']
|
338
|
+
end
|
339
|
+
|
340
|
+
end
|