h2o 0.2 → 0.3.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.
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