h2o 0.2 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
File without changes
@@ -0,0 +1,33 @@
1
+ module H2o
2
+ class Template
3
+ attr_reader :context
4
+
5
+ def initialize (file, env = {})
6
+ @file = file
7
+ @nodelist = Template.load(@file, env)
8
+ end
9
+
10
+ def render (context = {})
11
+ @context = Context.new(context)
12
+ output_stream = []
13
+ @nodelist.render(@context, output_stream)
14
+ output_stream.join
15
+ end
16
+
17
+ def self.parse source, env = {}
18
+ parser = Parser.new(source, false, env)
19
+ parsed = parser.parse
20
+ end
21
+
22
+ def self.load file, env = {}
23
+ unless H2o.loader
24
+ env[:searchpath] ||= File.expand_path('../', file)
25
+ H2o.loader = H2o::FileLoader.new(env[:searchpath])
26
+ end
27
+ source = H2o.loader.read(file)
28
+
29
+ parser = Parser.new(source, file, env)
30
+ parser.parse
31
+ end
32
+ end
33
+ end
File without changes
@@ -0,0 +1 @@
1
+ {{ hello }}
File without changes
File without changes
@@ -0,0 +1,134 @@
1
+ require 'spec_helper'
2
+
3
+ class User
4
+ h2o_expose :name, :age
5
+ attr_accessor :name, :age
6
+
7
+ def initialize name, age
8
+ @name, @age = name, age
9
+ end
10
+
11
+ def to_h2o
12
+ {
13
+ :name => self.name,
14
+ :age => self.age
15
+ }
16
+ end
17
+ end
18
+
19
+ describe H2o::Context do
20
+ before do
21
+ @scope = {
22
+ :person => {'name' => 'peter', 'age' => 18},
23
+ :weather => 'sunny and warm',
24
+ :items => ['apple', 'orange', 'pear'],
25
+ :user => User.new('taylor', 19)
26
+ }
27
+ @context = H2o::Context.new(@scope)
28
+ end
29
+
30
+
31
+ describe "Resolve name" do
32
+ it "should resolve name with either string or symbol" do
33
+ @context.resolve(:person).should == @scope[:person]
34
+ @context.resolve(:person).should == @scope[:person]
35
+ end
36
+
37
+ it "should return nil for non existing name context" do
38
+ @context.resolve(:something).should be_nil
39
+ @context.resolve(:where_is_that).should be_nil
40
+ end
41
+
42
+ it "should be able to resolve local variables" do
43
+ @context.resolve(:person).should_not be_nil
44
+ @context.resolve(:'person.name').should == 'peter'
45
+ @context.resolve(:'person.age').should == 18
46
+ end
47
+
48
+ it "should resolve array index using (dot)" do
49
+ @context.resolve(:'items.0').should == 'apple'
50
+ @context.resolve(:'items.1').should == 'orange'
51
+ end
52
+
53
+ it "should resolve array methods such as length, count ..." do
54
+ @context.resolve(:'items.length').should == @scope[:items].length
55
+ @context.resolve(:'items.first').should == 'apple'
56
+ @context.resolve(:'items.last').should == 'pear'
57
+ end
58
+
59
+ it "should resolve object methods" do
60
+
61
+ @context.resolve(:'user.name').should == 'taylor'
62
+ @context.resolve(:'user.age').should == 19
63
+ end
64
+
65
+
66
+ it "should resolve proc object and cache inline" do
67
+ @context.stack do
68
+ @context['procs'] = {
69
+ :test => lambda{ "testing" },
70
+ :generation => lambda{ Time.now }
71
+ }
72
+
73
+ # Resolve a proc object
74
+ @context.resolve(:'procs.test').should == 'testing'
75
+ result = @context.resolve(:'procs.generation')
76
+ result.should be_a(Time)
77
+
78
+ # Cached inline
79
+ @context.resolve(:'procs.generation').usec.should == result.usec
80
+ @context.resolve(:'procs.generation').usec.should == result.usec
81
+ end
82
+ end
83
+ end
84
+
85
+ describe "Local lookup" do
86
+ it "should allow local variable lookup with using symbol" do
87
+ @context[:person].should be_kind_of(Hash)
88
+ @context[:weather].should =~ /sunny/
89
+
90
+ end
91
+
92
+ it "should able to set new name and value into context" do
93
+ @context[:city] = 'five dock'
94
+ @context[:state] = 'nsw'
95
+
96
+ @context[:city].should == 'five dock'
97
+ @context[:state].should == 'nsw'
98
+ end
99
+ end
100
+
101
+ describe "Context stack" do
102
+ it "should allow pushing and popping local context layer in the stack" do
103
+ @context.push
104
+ @context[:ass] = 'hole'
105
+ @context[:ass].should == 'hole'
106
+ @context.pop
107
+
108
+ @context.push(:bowling => 'on sunday')
109
+ @context[:bowling].should_not be_nil
110
+ @context.pop
111
+ end
112
+
113
+ it "should allow using stack method to ease push/pop and remain in local context" do
114
+ @context.stack do
115
+ @context[:name] = 'a'
116
+
117
+ @context.stack do
118
+ @context[:name] = 'b'
119
+
120
+ @context.stack do
121
+ @context[:age] = 19
122
+ @context[:name] = 'c'
123
+ @context.resolve(:'name').should == 'c'
124
+ end
125
+
126
+ @context.resolve(:'age').should == nil
127
+ @context.resolve(:'name').should == 'b'
128
+ end
129
+
130
+ @context.resolve(:'name') == 'a'
131
+ end
132
+ end
133
+ end
134
+ end
@@ -0,0 +1,93 @@
1
+ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
2
+ <html xmlns="http://www.w3.org/1999/xhtml">
3
+ <head>
4
+ <meta content="text/html; charset=ISO-8859-1" http-equiv="content-type" />
5
+ <title>{{ site.title | capitalize }} - Design, Development and New Media Marketing</title>
6
+
7
+ {{ 'stylesheets/layout.css'| css_tag }}
8
+
9
+ <!--[if IE]>
10
+ <style type="text/css">
11
+ @import "ie_layout.css";
12
+ </style>
13
+ <![endif]-->
14
+ </head>
15
+ <body>
16
+ <div id="centered">
17
+ <div id="top">
18
+ <!-- Left column -->
19
+ <div id="left">
20
+ <!-- Logo -->
21
+ <a href="#" id="logo">jnCentral.com</a>
22
+ <!-- Mini navigation -->
23
+ <ul id="mininav">
24
+
25
+
26
+
27
+ {% if page.children %}
28
+ <li><a href="#" class="active"><span class="homepage">Homepage</span></a></li>
29
+ <ul>
30
+ {% for page in page.children %}
31
+ <li>{{ page.title | links_to page.url }}</li>
32
+ {% endfor %}
33
+ </ul>
34
+ {% endif %}
35
+
36
+ <li><a href="#"><span class="products">View our products catalog</span></a></li>
37
+ <li><a href="#"><span class="services">Check out our wide range of services</span></a></li>
38
+ <li><a href="#"><span class="about">Find out about our company</span></a></li>
39
+ <li><a href="#"><span class="contact">Get in contact with us</span></a></li>
40
+ </ul>
41
+ <!-- Gallery -->
42
+ <div id="gallery">
43
+ <div>
44
+ <ul>
45
+ <li><a href="#"><img src="images/img1.gif" alt="" /></a></li>
46
+ <li><a href="#"><img src="images/img2.gif" alt="" /></a></li>
47
+ <li><a href="#"><img src="images/img3.gif" alt="" /></a></li>
48
+ </ul>
49
+ <ul>
50
+ <li><a href="#"><img src="images/img5.gif" alt="" /></a></li>
51
+ <li><a href="#"><img src="images/img6.gif" alt="" /></a></li>
52
+ <li><a href="#"><img src="images/img7.gif" alt="" /></a></li>
53
+ </ul>
54
+ </div>
55
+ <img src="{{'images/mypics.gif'| base_url}}" alt="" class="floatright" />
56
+ </div>
57
+ <!-- Latest News -->
58
+ <div id="news">
59
+ <h2 id="latestnews">Latest News</h2>
60
+
61
+ {% for article in site.latest_articles %}
62
+ {{ article.title| links_to article.url }} <span class="date">{{article.published_at}}</span><br />
63
+ <p>{{ article.excerpt}}</p>
64
+ {% endfor %}
65
+
66
+ <br />
67
+ </div>
68
+ </div>
69
+ <!-- Right column -->
70
+ <div id="right">
71
+ <!-- Main navigation -->
72
+ <ul id="navigation">
73
+ <li><a href="#"><span>Home</span></a></li>
74
+ <li><a href="#"><span>Products</span></a></li>
75
+ <li><a href="#"><span>Services</span></a></li>
76
+ <li><a href="#"><span>Contact us</span></a></li>
77
+ </ul>
78
+ {{ content_for_layout }}
79
+ </div>
80
+ </div>
81
+ </div>
82
+ <!-- Footer -->
83
+ <div id="footer">
84
+ <div class="copyright">
85
+ <h4>Copyright © jnCentral.com.  All rights reserved.</h4>
86
+ <!-- Mini navigation -->
87
+ <div class="nav">
88
+ <a href="#">Home</a>     <a href="#">Products</a>     <a href="#">Services</a>     <a href="#">About us</a>     <a href="#">Contact</a>
89
+ </div>
90
+ </div>
91
+ </div>
92
+ </body>
93
+ </html>
@@ -0,0 +1,35 @@
1
+ require 'spec_helper'
2
+
3
+ describe H2o::FileLoader do
4
+ before do
5
+ path = File.expand_path("../../fixtures", __FILE__)
6
+ @loader = H2o::FileLoader.new(path)
7
+ end
8
+
9
+ it "search for template on a search path on file system" do
10
+ @loader.exist?('a.html').should == true
11
+ @loader.exist?('deep/folder/c.html').should == true
12
+ @loader.exist?('non-existence.html').should == false
13
+ end
14
+
15
+ it "read for files on the searchpath" do
16
+ @loader.read('a.html').should == '{{ hello }}'
17
+ end
18
+
19
+ it "raises error when template doesn't exist" do
20
+ expect { @loader.read('non-existence.html') }.should raise_error
21
+ end
22
+ end
23
+
24
+ describe "H2o::HashLoader" do
25
+ it "read file on the same namespace in a hash" do
26
+ H2o.loader = H2o::HashLoader.new(
27
+ 'base.html' => '{% block content %}test{% endblock %}',
28
+ 'a.html' => "{% extends 'base.html' %}{% block content %}test2{% endblock %}"
29
+ )
30
+ H2o.loader.read('base.html').should == '{% block content %}test{% endblock %}'
31
+
32
+ H2o::Template.new('base.html').render.should == 'test'
33
+ H2o::Template.new('a.html').render.should == 'test2'
34
+ end
35
+ end
@@ -0,0 +1,57 @@
1
+ require 'spec_helper'
2
+
3
+ describe H2o::Filters do
4
+ it "should pass input object as first param" do
5
+ H2o::Filters << TestFilters
6
+
7
+ # try a standard filter
8
+ render('{{ "something" | upper }}').should == 'SOMETHING'
9
+
10
+ # # try a array input object
11
+ list = ["man","women"]
12
+ render('{{ object| test_filter }}', :object => list).should == list.inspect
13
+
14
+ # Try a array subclass
15
+ list = CustomCollection.new(["man", "woman"])
16
+ render('{{ object| test_filter }}', :object => list).should == list.inspect
17
+ end
18
+
19
+ it "should be able to pass aditional parameters" do
20
+ render('{{ "test"| test_filter_2 1, 2 }}').should == 'test-1-2'
21
+ end
22
+ end
23
+
24
+ describe DefaultFilters do
25
+ it "should privide a set of default filters" do
26
+
27
+ render('{{ "test" |upper }}').should == 'TEST'
28
+
29
+ render('{{ "TEST" |lower }}').should == 'test'
30
+
31
+ render('{{ "test" |capitalize }}').should == 'Test'
32
+
33
+ render('{{ list |first }}', :list => [1,2]).should == "1"
34
+
35
+ render('{{ list |last }}', :list => [1,2]).should == "2"
36
+
37
+ render('{{ list |join }}', :list => [1,2]).should == "1, 2"
38
+ end
39
+ end
40
+
41
+ #
42
+ module TestFilters
43
+ def test_filter (input)
44
+ "#{input.inspect}"
45
+ end
46
+
47
+ def test_filter_2 (string, param1, param2)
48
+ "#{string}-#{param1}-#{param2}"
49
+ end
50
+ end
51
+
52
+ class CustomCollection < Array
53
+ end
54
+
55
+ def render(src, context = {})
56
+ H2o::Template.parse(src).render(context)
57
+ end
@@ -0,0 +1,78 @@
1
+ require 'spec_helper'
2
+
3
+ describe "Parser regex" do
4
+
5
+ it "should match whitespace" do
6
+ H2o::WHITESPACE_RE.should =~ ' '
7
+ H2o::WHITESPACE_RE.should =~ ' '
8
+ end
9
+
10
+ it "should match variable names" do
11
+ H2o::NAME_RE.should =~ 'variable'
12
+ H2o::NAME_RE.should =~ 'variable.property'
13
+ H2o::NAME_RE.should =~ 'variable.property'
14
+ end
15
+
16
+ it "should match string" do
17
+ H2o::STRING_RE.should =~ '"this is a string"'
18
+ H2o::STRING_RE.should =~ '"She has \"The thing\""'
19
+ H2o::STRING_RE.should =~ "'the doctor is good'"
20
+ H2o::STRING_RE.should =~ "'She can\'t do it'"
21
+ end
22
+
23
+ it "should match numeric values" do
24
+ H2o::NUMBER_RE.should =~ '1.2'
25
+ H2o::NUMBER_RE.should =~ '-3.2'
26
+ H2o::NUMBER_RE.should =~ '100000'
27
+ end
28
+
29
+ it "should match operators" do
30
+ %w(== > < >= <= != ! not and or).each do |operator|
31
+ H2o::OPERATOR_RE.should =~ operator
32
+ end
33
+ end
34
+
35
+ it "should match named arguments" do
36
+ named_args = ["name: 'peter'", 'name: object.property', 'price: 2.3', 'age: 29', 'alt: "my company logo"']
37
+ named_args.each do |arg|
38
+ H2o::NAMED_ARGS_RE.should =~ arg
39
+ end
40
+ end
41
+ end
42
+
43
+ describe 'H2o::Parser argument parsing' do
44
+
45
+ it "should parse named arguments" do
46
+ r = H2o::Parser.parse_arguments(
47
+ "something | filter 11, name: 'something', age: 18, var: variable, active: true"
48
+ )
49
+
50
+ r.should == [:something,
51
+ [:filter, 11, {
52
+ :name=> "something",
53
+ :age => 18,
54
+ :var=> :variable,
55
+ :active => true }
56
+ ]
57
+ ]
58
+ end
59
+ end
60
+
61
+ describe "Whitespace stripping syntax" do
62
+ it "should rstrip previous text node for {%- %}" do
63
+ H2o::Template.parse(' {%- if true %}{% endif %}').render.should == ''
64
+ end
65
+
66
+ it "should lstrip next text node for {% -%}" do
67
+ H2o::Template.parse('{% if true -%} {% endif %}').render.should == ''
68
+ end
69
+
70
+ it "should strip whitespace on both site with {%- -%}" do
71
+ H2o::Template.parse('
72
+ {%- if true -%}
73
+ hello
74
+ {%- endif -%}
75
+ ').render.should == 'hello'
76
+ end
77
+
78
+ end
@@ -0,0 +1,26 @@
1
+ require 'spec_helper'
2
+
3
+ describe H2o::Tags::Block do
4
+
5
+ it "should render block content" do
6
+ parse('{% block something %}content{% endblock %}').render.should == 'content'
7
+ end
8
+
9
+
10
+ context "block variable" do
11
+ it "should return block name" do
12
+ parse(block(:something, '{{ block.name }}')).render.should == 'something'
13
+ end
14
+
15
+ it "should return current block depth" do
16
+ source = block(:something, '{{ block.depth }}')
17
+ parse(source).render.should == "1"
18
+ end
19
+
20
+ it "should return parent block content"
21
+ end
22
+
23
+ def block(name, content)
24
+ "{% block #{name.to_s} %}#{content}{% endblock %}"
25
+ end
26
+ end