garterbelt 0.0.2

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 (57) hide show
  1. data/.document +5 -0
  2. data/.rspec +1 -0
  3. data/Gemfile +16 -0
  4. data/Gemfile.lock +38 -0
  5. data/LICENSE.txt +20 -0
  6. data/README.rdoc +21 -0
  7. data/Rakefile +46 -0
  8. data/TODO +3 -0
  9. data/VERSION +1 -0
  10. data/garterbelt.gemspec +165 -0
  11. data/lib/garterbelt.rb +23 -0
  12. data/lib/page.rb +46 -0
  13. data/lib/renderers/cache.rb +35 -0
  14. data/lib/renderers/closed_tag.rb +60 -0
  15. data/lib/renderers/comment.rb +14 -0
  16. data/lib/renderers/content_rendering.rb +41 -0
  17. data/lib/renderers/content_tag.rb +36 -0
  18. data/lib/renderers/doctype.rb +24 -0
  19. data/lib/renderers/renderer.rb +33 -0
  20. data/lib/renderers/text.rb +28 -0
  21. data/lib/renderers/xml.rb +9 -0
  22. data/lib/stocking.rb +11 -0
  23. data/lib/support/string.rb +165 -0
  24. data/lib/view.rb +341 -0
  25. data/spec/benchmark/templates/erector.rb +37 -0
  26. data/spec/benchmark/templates/garterbelt.rb +37 -0
  27. data/spec/benchmark/vs_erector.rb +53 -0
  28. data/spec/garterbelt_spec.rb +49 -0
  29. data/spec/integration/expectations/general_view.html +17 -0
  30. data/spec/integration/expectations/variables/view_with_user_and_params.html +23 -0
  31. data/spec/integration/expectations/variables/view_with_user_email.html +23 -0
  32. data/spec/integration/expectations/view_partial_nest.html +24 -0
  33. data/spec/integration/expectations/view_with_tags.html +19 -0
  34. data/spec/integration/templates/view_partial_nest.rb +22 -0
  35. data/spec/integration/templates/view_with_cache.rb +30 -0
  36. data/spec/integration/templates/view_with_partial.rb +36 -0
  37. data/spec/integration/templates/view_with_partial_2.rb +36 -0
  38. data/spec/integration/templates/view_with_tags.rb +26 -0
  39. data/spec/integration/templates/view_with_vars.rb +32 -0
  40. data/spec/integration/view_spec.rb +57 -0
  41. data/spec/page_spec.rb +99 -0
  42. data/spec/renderers/cache_spec.rb +85 -0
  43. data/spec/renderers/closed_tag_spec.rb +172 -0
  44. data/spec/renderers/comment_spec.rb +68 -0
  45. data/spec/renderers/content_tag_spec.rb +150 -0
  46. data/spec/renderers/doctype_spec.rb +46 -0
  47. data/spec/renderers/text_spec.rb +68 -0
  48. data/spec/spec_helper.rb +17 -0
  49. data/spec/support/mock_view.rb +14 -0
  50. data/spec/support/puters.rb +10 -0
  51. data/spec/view/view_basics_spec.rb +106 -0
  52. data/spec/view/view_caching_spec.rb +132 -0
  53. data/spec/view/view_partial_spec.rb +63 -0
  54. data/spec/view/view_rails_type_helpers.rb +148 -0
  55. data/spec/view/view_render_spec.rb +408 -0
  56. data/spec/view/view_variables_spec.rb +159 -0
  57. metadata +367 -0
@@ -0,0 +1,17 @@
1
+ <div class="user_status">
2
+ <ul>
3
+ <li class="pro" title="You're a real pro.">
4
+ pro
5
+ </li>
6
+ <li>
7
+ <a href="/user/foo_id?selected=settings" id="settings_link" title="Reset your name or password, upload your photo, or adjust your email notifications">
8
+ settings
9
+ </a>
10
+ </li>
11
+ <li class="last">
12
+ <a class="logout_link" href="/logout" title="Get out of here!">
13
+ logout
14
+ </a>
15
+ </li>
16
+ </ul>
17
+ </div>
@@ -0,0 +1,23 @@
1
+ <div class="line">
2
+ <div class="unit size1of2">
3
+ <h4>
4
+ Login
5
+ </h4>
6
+ <form class="inner" action="/login">
7
+ <label class="input">
8
+ Username or Email
9
+ <input name="login" type="text" value="foobar">
10
+ </label>
11
+ <label class="input">
12
+ Password
13
+ <input name="password" type="password">
14
+ </label>
15
+ <label class="remember_me">
16
+ Remember Me
17
+ <input checked="true" name="remember_me" type="checkbox">
18
+ </label>
19
+ <hr class="light">
20
+ <input type="submit" value="Login">
21
+ </form>
22
+ </div>
23
+ </div>
@@ -0,0 +1,23 @@
1
+ <div class="line">
2
+ <div class="unit size1of2">
3
+ <h4>
4
+ Login
5
+ </h4>
6
+ <form class="inner" action="/login">
7
+ <label class="input">
8
+ Username or Email
9
+ <input name="login" type="text" value="foo@example.com">
10
+ </label>
11
+ <label class="input">
12
+ Password
13
+ <input name="password" type="password">
14
+ </label>
15
+ <label class="remember_me">
16
+ Remember Me
17
+ <input name="remember_me" type="checkbox">
18
+ </label>
19
+ <hr class="light">
20
+ <input type="submit" value="Login">
21
+ </form>
22
+ </div>
23
+ </div>
@@ -0,0 +1,24 @@
1
+ <body class="my_pagelet">
2
+ <div id="header">
3
+ <div class="user_status">
4
+ <ul>
5
+ <li class="pro" title="You're a real pro.">
6
+ pro
7
+ </li>
8
+ <li>
9
+ <a href="/user/foo_id?selected=settings" id="settings_link" title="Reset your name or password, upload your photo, or adjust your email notifications">
10
+ settings
11
+ </a>
12
+ </li>
13
+ <li class="last">
14
+ <a class="logout_link" href="/logout" title="Get out of here!">
15
+ logout
16
+ </a>
17
+ </li>
18
+ </ul>
19
+ </div>
20
+ </div>
21
+ <div id="wrapper">
22
+ my page here
23
+ </div>
24
+ </body>
@@ -0,0 +1,19 @@
1
+ <div class="line">
2
+ <div class="unit size1of2">
3
+ <h4>
4
+ Login
5
+ </h4>
6
+ <form class="inner" action="/login">
7
+ <label class="input">
8
+ Username or Email
9
+ <input name="login" type="text">
10
+ </label>
11
+ <label class="input">
12
+ Password
13
+ <input name="password" type="password">
14
+ </label>
15
+ <hr class="light">
16
+ <input type="submit" value="Login">
17
+ </form>
18
+ </div>
19
+ </div>
@@ -0,0 +1,22 @@
1
+ class MyPagelet < Garterbelt::View
2
+ needs :user
3
+
4
+ def content
5
+ body.c(:my_pagelet) do
6
+ partial Header, :user => user
7
+ div.id(:wrapper) do
8
+ text 'my page here'
9
+ end
10
+ end
11
+ end
12
+ end
13
+
14
+ class Header < Garterbelt::View
15
+ needs :user
16
+
17
+ def content
18
+ div.id(:header) do
19
+ partial ViewWithPartial, :user => user
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,30 @@
1
+ class ViewWithCache < Garterbelt::View
2
+ needs :user
3
+
4
+ def content
5
+ cache "user_#{user.id}" do
6
+ div :class => "user_status" do
7
+ ul do
8
+ if user.upgradeable?
9
+ li do
10
+ a 'upgrade', :href => "#", :class => 'upgrade_link'
11
+ end
12
+ else
13
+ li "pro", :class => 'pro', :title => "You're a real pro."
14
+ end
15
+
16
+ li do
17
+ a 'settings', :href => "/user/#{user.id}?selected=settings",
18
+ :id => "settings_link",
19
+ :title => "Reset your name or password, upload your photo, or adjust your email notifications"
20
+ end
21
+
22
+ li :class => 'last' do
23
+ a "logout", :href => "/logout", :title => "Get out of here!", :class => 'logout_link'
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+
30
+ end
@@ -0,0 +1,36 @@
1
+ class ViewWithPartial < Garterbelt::View
2
+ needs :user
3
+
4
+ def content
5
+ div :class => "user_status" do
6
+ ul do
7
+ if user.upgradeable?
8
+ li do
9
+ a 'upgrade', :href => "#", :class => 'upgrade_link'
10
+ end
11
+ else
12
+ li "pro", :class => 'pro', :title => "You're a real pro."
13
+ end
14
+
15
+ li do
16
+ partial SettingLink, :user => user
17
+ end
18
+
19
+ li :class => 'last' do
20
+ a "logout", :href => "/logout", :title => "Get out of here!", :class => 'logout_link'
21
+ end
22
+ end
23
+ end
24
+ end
25
+
26
+ end
27
+
28
+ class SettingLink < Garterbelt::View
29
+ needs :user
30
+
31
+ def content
32
+ a 'settings', :href => "/user/#{user.id}?selected=settings",
33
+ :id => "settings_link",
34
+ :title => "Reset your name or password, upload your photo, or adjust your email notifications"
35
+ end
36
+ end
@@ -0,0 +1,36 @@
1
+ class ViewWithPartial2 < Garterbelt::View
2
+ needs :user
3
+
4
+ def content
5
+ div :class => "user_status" do
6
+ ul do
7
+ if user.upgradeable?
8
+ li do
9
+ a 'upgrade', :href => "#", :class => 'upgrade_link'
10
+ end
11
+ else
12
+ li "pro", :class => 'pro', :title => "You're a real pro."
13
+ end
14
+
15
+ li do
16
+ partial SettingLink2.new(:user => user)
17
+ end
18
+
19
+ li :class => 'last' do
20
+ a "logout", :href => "/logout", :title => "Get out of here!", :class => 'logout_link'
21
+ end
22
+ end
23
+ end
24
+ end
25
+
26
+ end
27
+
28
+ class SettingLink2 < Garterbelt::View
29
+ needs :user
30
+
31
+ def content
32
+ a 'settings', :href => "/user/#{user.id}?selected=settings",
33
+ :id => "settings_link",
34
+ :title => "Reset your name or password, upload your photo, or adjust your email notifications"
35
+ end
36
+ end
@@ -0,0 +1,26 @@
1
+ class ViewWithContentTags < Garterbelt::View
2
+
3
+ def content
4
+ div.c(:line) do
5
+ div.c(:unit, :size1of2) do
6
+ h4 "Login"
7
+ form :action => "/login", :class => :inner do
8
+ label.c(:input) do
9
+ text "Username or Email"
10
+ input :name => 'login', :type => :text
11
+ end
12
+
13
+ label.c(:input) do
14
+ text "Password"
15
+ input :name => 'password', :type => :password
16
+ end
17
+
18
+ hr.c(:light)
19
+
20
+ input :type => :submit, :value => "Login"
21
+ end
22
+ end
23
+ end
24
+ end
25
+
26
+ end
@@ -0,0 +1,32 @@
1
+ class ViewWithVars < Garterbelt::View
2
+ needs :user, :params => {}
3
+
4
+ def content
5
+ div.c(:line) do
6
+ div.c(:unit, :size1of2) do
7
+ h4 "Login"
8
+ form :action => "/login", :class => :inner do
9
+ label.c(:input) do
10
+ text "Username or Email"
11
+ input :name => 'login', :type => :text, :value => user.email || user.name
12
+ end
13
+
14
+ label.c(:input) do
15
+ text "Password"
16
+ input :name => 'password', :type => :password
17
+ end
18
+
19
+ label.c(:remember_me) do
20
+ text "Remember Me"
21
+ input :name => 'remember_me', :type => :checkbox, :checked => params[:remember_me]
22
+ end
23
+
24
+ hr.c(:light)
25
+
26
+ input :type => :submit, :value => "Login"
27
+ end
28
+ end
29
+ end
30
+ end
31
+
32
+ end
@@ -0,0 +1,57 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+
3
+ describe Garterbelt::View, "Integration" do
4
+ def file(name)
5
+ File.read(File.dirname(__FILE__) + "/expectations/#{name}.html")
6
+ end
7
+
8
+ it 'views with tags should render and nest correctly' do
9
+ ViewWithContentTags.new.render.should == file("view_with_tags")
10
+ end
11
+
12
+ describe 'variables' do
13
+ it 'calls methods on passed objects' do
14
+ user = Hashie::Mash.new(:email => 'foo@example.com')
15
+ ViewWithVars.new(:user => user).render.should == file('variables/view_with_user_email')
16
+ end
17
+
18
+ it 'uses optional params' do
19
+ user = Hashie::Mash.new(:name => 'foobar')
20
+ ViewWithVars.new(:user => user, :params => {:remember_me => true}).render.should == file('variables/view_with_user_and_params')
21
+ end
22
+ end
23
+
24
+ describe 'caching' do
25
+ before do
26
+ Garterbelt.cache.clear
27
+ @user = Hashie::Mash.new(:id => 'foo_id', :upgradable? => true)
28
+ end
29
+
30
+ it 'renders it correctly the first time' do
31
+ ViewWithCache.new(:user => @user).render.should == file('general_view')
32
+ end
33
+
34
+ it 'renders correctly from the cache' do
35
+ ViewWithCache.new(:user => @user).render
36
+ ViewWithCache.new(:user => @user).render.should == file("general_view")
37
+ end
38
+ end
39
+
40
+ describe 'partials' do
41
+ before do
42
+ @user = Hashie::Mash.new(:id => 'foo_id', :upgradable? => true)
43
+ end
44
+
45
+ it 'render correctly with class arguments on the partial' do
46
+ ViewWithPartial.new(:user => @user).render.should == file("general_view")
47
+ end
48
+
49
+ it 'renders correctly with a view instance' do
50
+ ViewWithPartial2.new(:user => @user).render.should == file('general_view')
51
+ end
52
+
53
+ it 'nests deeply' do
54
+ MyPagelet.new(:user => @user).render.should == file('view_partial_nest')
55
+ end
56
+ end
57
+ end
data/spec/page_spec.rb ADDED
@@ -0,0 +1,99 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe Garterbelt::Page do
4
+ describe 'class level configuration' do
5
+ class CustomPage < Garterbelt::Page
6
+ self.doctype = :html5
7
+ self.html_attributes = {:lang => "en", "xml:lang" => "en"}
8
+ end
9
+
10
+ class NewCustom < CustomPage
11
+ end
12
+
13
+
14
+ describe 'doctype' do
15
+ it 'defaults to :transitional' do
16
+ Garterbelt::Page.doctype.should == :transitional
17
+ end
18
+
19
+ it 'can be customized' do
20
+ CustomPage.doctype.should == :html5
21
+ end
22
+
23
+ it 'default is inherited' do
24
+ NewCustom.doctype.should == :html5
25
+ end
26
+ end
27
+
28
+ describe 'html_attributes' do
29
+ it 'defaults to an empty hash' do
30
+ Garterbelt::Page.html_attributes.should == {}
31
+ end
32
+
33
+ it 'can be customized' do
34
+ CustomPage.html_attributes.should == {:lang => 'en', 'xml:lang' => 'en'}
35
+ end
36
+
37
+ it 'defaults are inherited' do
38
+ NewCustom.html_attributes.should == {:lang => 'en', 'xml:lang' => 'en'}
39
+ end
40
+ end
41
+ end
42
+
43
+ it 'is a View' do
44
+ BasicPage.new.is_a?(Garterbelt::View).should == true
45
+ end
46
+
47
+ describe 'rendering' do
48
+ class BasicPage < Garterbelt::Page
49
+ def head
50
+ page_title "Basicly a Page"
51
+ end
52
+
53
+ def body
54
+ p "Something should go here, yes?"
55
+ end
56
+ end
57
+
58
+ class SpecificPage < Garterbelt::Page
59
+ def body_attributes
60
+ {:class => [:my_controller_class, :my_view_class], :id => :some_id}
61
+ end
62
+ end
63
+
64
+ before do
65
+ @view = MockView.new
66
+ @view.level = 0
67
+ @output = BasicPage.new.render
68
+ end
69
+
70
+ it 'makes a default doctype of :transitional' do
71
+ @output.should include Garterbelt::Doctype.new(:type => :transitional, :view => @view).render
72
+ end
73
+
74
+ it 'wraps everything in html' do
75
+ @output.should match /<html>/
76
+ end
77
+
78
+ it 'makes a head section' do
79
+ @output.should match /<head>/
80
+ end
81
+
82
+ it 'renders the head content' do
83
+ @output.should match /<title>\W*Basicly a Page\W*<\/title>/
84
+ end
85
+
86
+ it 'makes a body section' do
87
+ @output.should match /<body>/
88
+ end
89
+
90
+ it 'adds the body attributes when defined' do
91
+ SpecificPage.new.render.should include "<body class=\"my_controller_class my_view_class\" id=\"some_id\">"
92
+ end
93
+
94
+ it 'renders the body content' do
95
+ @output.should match /<body>\W*<p>\W*Something should go here, yes\?\W*<\/p>\W*<\/body>/
96
+ end
97
+ end
98
+
99
+ end
@@ -0,0 +1,85 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+
3
+ describe Garterbelt::Cache do
4
+ before :all do
5
+ @view = MockView.new
6
+ end
7
+
8
+ def build_cache
9
+ Garterbelt::Cache.new(:key => 'good_deal', :view => @view) do
10
+ Garterbelt::ContentTag.new(:type => :p, :view => @view)
11
+ end
12
+ end
13
+
14
+ describe 'initialization' do
15
+ it 'requires block content' do
16
+ lambda{ Garterbelt::Cache.new(:key => 'foo_key', :view => @view)}.should raise_error(ArgumentError, "Block content required")
17
+ lambda{ Garterbelt::Cache.new(:key => 'other_foo', :view => @view, :content => 'content')}.should raise_error( ArgumentError, "Block content required")
18
+ end
19
+
20
+ it 'requires a key' do
21
+ lambda{ Garterbelt::Cache.new(:view => @view) {puts 'foo'} }.should raise_error(ArgumentError, ":key option required")
22
+ end
23
+
24
+ it 'otherwise is successful' do
25
+ lambda{ build_cache }.should_not raise_error
26
+ build_cache.is_a?(Garterbelt::Cache).should be_true
27
+ end
28
+
29
+ it 'stores the full key' do
30
+ build_cache.key.should == 'good_deal'
31
+ end
32
+ end
33
+
34
+ describe 'rendering' do
35
+ before do
36
+ @cache = build_cache
37
+ end
38
+
39
+ describe "diverting output" do
40
+ before do
41
+ @cache.cache_output = "cache output"
42
+ @view.output = 'view output; '
43
+ end
44
+
45
+ it '#head changes the view output to a local output' do
46
+ @cache.head
47
+ @view.output.should == "cache output"
48
+ end
49
+
50
+ it '#foot resets the output to the original view output' do
51
+ @cache.head
52
+ @view.output.should_not include "view output; "
53
+ @cache.foot
54
+ @view.output.should include "view output; "
55
+ end
56
+
57
+ it '#render calls #head and #foot' do
58
+ @cache.should_receive(:head).ordered
59
+ @cache.should_receive(:foot).ordered
60
+ @cache.render
61
+ end
62
+ end
63
+
64
+ describe '#render_content' do
65
+ it 'trys to get the content from the cache using the key' do
66
+ @view.cache_store.should_receive(:[]).with('good_deal').and_return("foo")
67
+ @cache.render
68
+ end
69
+
70
+ it 'puts the cache into the output when it hits' do
71
+ @view.output = "view output; "
72
+ @view.cache_store.stub(:[]).with('good_deal').and_return("foo")
73
+ @cache.render
74
+ @view.output.should include 'foo'
75
+ end
76
+
77
+ it 'renders the block when the cache misses' do
78
+ @view.output = "view output; "
79
+ @view.cache_store.should_receive(:[]).with('good_deal').and_return(nil)
80
+ @view.should_receive(:render_buffer)
81
+ @cache.render
82
+ end
83
+ end
84
+ end
85
+ end