hammer_builder 0.1.2 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,40 @@
1
+ require 'hammer_builder/formatted'
2
+
3
+ warn '"hammer_builder/rails" is very early experiment'
4
+
5
+ module HammerBuilder::Rails
6
+ class AbstractBuilder
7
+ extend HammerBuilder::Helper
8
+
9
+ attr_reader :controller
10
+
11
+ def initialize(controller)
12
+ @controller = controller
13
+ end
14
+
15
+ end
16
+
17
+ ActionController::Renderers.add :hb do |klass_or_obj, options|
18
+ obj = case
19
+ when klass_or_obj.kind_of?(Class)
20
+ klass_or_obj.new(self)
21
+ when klass_or_obj.nil? || klass_or_obj == self
22
+ self.class.to_s.gsub(/Controller/, 'Builder').constantize.new(self)
23
+ else
24
+ klass_or_obj
25
+ end
26
+
27
+ $hammer_builder_pool ||= HammerBuilder::SynchronizedPool.new(HammerBuilder::Formatted) # FIXME
28
+
29
+ render(
30
+ :text => $hammer_builder_pool.get.
31
+ go_in { render obj, "#{options[:method] || options[:template]}" }.to_html!,
32
+ :layout => true)
33
+ end
34
+
35
+
36
+ end
37
+
38
+
39
+
40
+
@@ -0,0 +1,48 @@
1
+ require 'hammer_builder/abstract'
2
+
3
+ module HammerBuilder
4
+
5
+ # Builder implementation without formating (one line output)
6
+ class Standard < Abstract
7
+
8
+ dynamic_classes do
9
+ extend_class :AbstractTag do
10
+ # add global HTML5 attributes
11
+ self.add_attributes Data::HTML5.abstract_attributes
12
+ end
13
+
14
+ Data::HTML5.double_tags.each do |tag|
15
+ next if tag.name == :html
16
+
17
+ def_class tag.name.to_s.camelize.to_sym, :AbstractDoubleTag do
18
+ set_tag tag.name
19
+ self.add_attributes tag.attributes
20
+ end
21
+
22
+ base.define_tag(tag.name)
23
+ end
24
+
25
+ html_tag = Data::HTML5.double_tags.find { |t| t.name == :html }
26
+ def_class :Html, :AbstractDoubleTag do
27
+ set_tag html_tag.name
28
+ self.add_attributes html_tag.attributes
29
+
30
+ def default
31
+ attribute :xmlns ,'http://www.w3.org/1999/xhtml'
32
+ end
33
+ end
34
+ base.define_tag(html_tag.name)
35
+
36
+ Data::HTML5.single_tags.each do |tag|
37
+ def_class tag.name.to_s.camelize.to_sym, :AbstractSingleTag do
38
+ set_tag tag.name
39
+ self.add_attributes tag.attributes
40
+ end
41
+
42
+ base.define_tag(tag.name)
43
+ end
44
+ end
45
+
46
+ end
47
+ end
48
+
@@ -0,0 +1,29 @@
1
+ module HammerBuilder
2
+ module Strings
3
+ def self.add(name, value)
4
+ name = name.to_s.upcase
5
+ if const_defined?(name)
6
+ raise "values are different for const #{name}: #{[const_get(name), value].inspect}" if const_get(name) != value
7
+ else
8
+ const_set(name, value.freeze)
9
+ end
10
+ end
11
+
12
+ add :lt, '<'
13
+ add :gt, '>'
14
+ add :slash_lt, '</'
15
+ add :slash_gt, ' />'
16
+ add :underscore, '_'
17
+ add :space, ' '
18
+ add :max_levels, 300
19
+ add :spaces, Array.new(MAX_LEVELS) { |i| (' ' * i).freeze }
20
+ add :newline, "\n"
21
+ add :quote, '"'
22
+ add :eql, '='
23
+ add :eql_quote, EQL + QUOTE
24
+ add :comment_start, '<!--'
25
+ add :comment_end, '-->'
26
+ add :cdata_start, '<![CDATA['
27
+ add :cdata_end, ']]>'
28
+ end
29
+ end
@@ -1,185 +1,278 @@
1
- require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
-
1
+ require 'spec_helper'
3
2
 
4
3
  describe HammerBuilder do
5
- def super_subject
6
- self.class.ancestors[1].allocate.subject
4
+ shared_examples "pool" do
5
+ it('should return correct instance') { pool.get.should be_kind_of(HammerBuilder::Formatted) }
6
+ it "should return instance into pool" do
7
+ pool.get.release
8
+ pool.size.should == 1
9
+ pool.get.to_html!
10
+ pool.size.should == 1
11
+ end
12
+ end
13
+
14
+ describe HammerBuilder::Pool do
15
+ let(:pool) { HammerBuilder::Pool.new HammerBuilder::Formatted }
16
+ include_examples 'pool'
17
+ end
18
+
19
+ describe HammerBuilder::SynchronizedPool do
20
+ let(:pool) { HammerBuilder::SynchronizedPool.new HammerBuilder::Formatted }
21
+ include_examples 'pool'
7
22
  end
8
23
 
9
24
  describe 'object with HammerBuilder::Helper' do
10
- class AObject
11
- include HammerBuilder::Helper
25
+ let(:pool) { HammerBuilder::Pool.new HammerBuilder::Standard }
12
26
 
13
- builder :render do |obj|
14
- @obj = obj
15
- div 'a'
16
- end
17
- end
27
+ class User
28
+ extend HammerBuilder::Helper
18
29
 
19
- subject { AObject.new }
30
+ builder :detail do |_, arg|
31
+ div arg
32
+ end
20
33
 
21
- describe 'methods' do
22
- subject { super_subject.class.instance_methods }
23
- it { should include(:render) }
34
+ def detail2(b, arg)
35
+ b.div { b.text arg }
36
+ end
24
37
  end
25
38
 
26
- describe '#render(builder)' do
27
- subject { super_subject.render(HammerBuilder::Standard.get).to_xhtml! }
28
- it { should == "<div>a</div>"}
39
+ describe 'User.new' do
40
+ it { User.new.should be_respond_to(:detail) }
41
+ it "should render correctly" do
42
+ pool.get.dive(User.new) do |user|
43
+ render user, :detail, 'content'
44
+ render user, :detail2, 'content'
45
+ end.to_html!.should == "<div>content</div><div>content</div>"
46
+ end
29
47
  end
48
+
30
49
  end
31
50
 
32
- pending 'RedefinableClassTree'
33
-
34
- # describe 'RedefinableClassTree' do
35
- # let :klass do
36
- # Class.new do
37
- # extend HammerBuilder::RedefinableClassTree
38
- # end
39
- # end
40
- #
41
- # describe '.define_class' do
42
- # before do
43
- # klass.define_class(:AClass) do
44
- # def a_method
45
- # end
46
- # end
47
- # end
48
- #
49
- # it { }
50
- # end
51
- # end
51
+ describe HammerBuilder::DynamicClasses do
52
+ class Parent
53
+ extend HammerBuilder::DynamicClasses
54
+ dc do
55
+ def_class :LeftEye do
56
+ def to_s;
57
+ 'left eye';
58
+ end
59
+ end
52
60
 
53
- describe HammerBuilder::Standard do
54
- describe 'Pool methods' do
55
- describe '.get' do
56
- it { HammerBuilder::Standard.get.should be_an_instance_of(HammerBuilder::Standard) }
57
- it { HammerBuilder::Formated.get.should be_an_instance_of(HammerBuilder::Formated) }
61
+ def_class :RightEye, :LeftEye do
62
+ class_eval <<-RUBYCODE, __FILE__, __LINE__+1
63
+ def to_s; 'next to ' + super; end
64
+ RUBYCODE
65
+ end
58
66
  end
67
+ end
59
68
 
60
- describe '#release!' do
61
- before do
62
- (@builder = HammerBuilder::Standard.get).release!
63
- end
69
+ class AChild < Parent
70
+ end
64
71
 
65
- it 'should be same object' do
66
- @builder.should == HammerBuilder::Standard.get
72
+ class AMutant < Parent
73
+ dc do
74
+ extend_class :LeftEye do
75
+ def to_s;
76
+ 'laser ' + super;
77
+ end
67
78
  end
68
79
  end
80
+ end
81
+
82
+ it '#to_s should print correct values' do
83
+ Parent.dc[:LeftEye].new.to_s.should == 'left eye'
84
+ Parent.dc[:RightEye].new.to_s.should == 'next to left eye'
85
+ AChild.dc[:LeftEye].new.to_s.should == 'left eye'
86
+ AChild.dc[:RightEye].new.to_s.should == 'next to left eye'
87
+ AMutant.dc[:LeftEye].new.to_s.should == 'laser left eye'
88
+ AMutant.dc[:RightEye].new.to_s.should == 'next to laser left eye'
89
+ end
90
+
91
+ it 'should create different classes for each carrying class' do
92
+ Parent.dc[:LeftEye].should_not == AChild.dc[:LeftEye]
93
+ Parent.dc[:LeftEye].should_not == AMutant.dc[:LeftEye]
94
+ end
69
95
 
70
- describe 'pools does not mix' do
71
- before { HammerBuilder::Standard.get.release! }
72
- it { HammerBuilder::Standard.pool_size.should == 1 }
73
- it { HammerBuilder::Formated.get.should be_an_instance_of(HammerBuilder::Formated) }
96
+ describe 'Parent.dc.class_names' do
97
+ it do
98
+ Parent.dc.class_names.should include(:LeftEye, :RightEye)
99
+ Parent.dc.class_names.should have(2).items
74
100
  end
75
101
  end
102
+ end
103
+
104
+ describe HammerBuilder::Standard do
105
+ let(:pool) { HammerBuilder::Pool.new HammerBuilder::Standard }
106
+ let(:builder) { pool.get }
107
+
108
+ def quick_render &block
109
+ builder.dive(&block).to_html!
110
+ end
111
+
112
+ it 'should render #content' do
113
+ quick_render { div 'content' }.should == '<div>content</div>'
114
+ quick_render { div.content 'content' }.should == '<div>content</div>'
115
+ quick_render { div :content => 'content' }.should == '<div>content</div>'
116
+ quick_render { div { text 'content' } }.should == '<div>content</div>'
117
+ quick_render { div.with { text 'content' } }.should == '<div>content</div>'
118
+ end
119
+
120
+ it 'should render #id' do
121
+ quick_render { div.id :an_id }.should == '<div id="an_id"></div>'
122
+ quick_render { div.an_id! }.should == '<div id="an_id"></div>'
123
+ quick_render { div.an_id! { text 'content' } }.should == '<div id="an_id">content</div>'
124
+ quick_render { div.an_id! 'content' }.should == '<div id="an_id">content</div>'
125
+ quick_render { div.an_id! }.should == '<div id="an_id"></div>'
126
+ quick_render { div :id => 12 }.should == '<div id="12"></div>'
127
+ quick_render { div 'asd', :id => 12 }.should == '<div id="12">asd</div>'
128
+ quick_render { hr.id 'an_id' }.should == '<hr id="an_id" />'
129
+ quick_render { hr.an_id! }.should == '<hr id="an_id" />'
130
+ quick_render { hr :id => 'an_id' }.should == '<hr id="an_id" />'
131
+
132
+ quick_render { hr.id 'an', 'id', nil, false }.should == '<hr id="an_id" />'
133
+ quick_render { div.id 'an', 'id', nil, false }.should == '<div id="an_id"></div>'
134
+ end
76
135
 
77
- describe 'available methods' do
78
- subject { HammerBuilder::Standard.instance_methods }
136
+ it 'should render #class' do
137
+ #noinspection RubyArgCount
138
+ quick_render { div.class 'an_class' }.should == '<div class="an_class"></div>'
139
+ quick_render { div.an_class }.should == '<div class="an_class"></div>'
140
+ quick_render { div :class => 'an_class' }.should == '<div class="an_class"></div>'
141
+ #noinspection RubyArgCount
142
+ quick_render { hr.class 'an_class' }.should == '<hr class="an_class" />'
143
+ quick_render { hr.an_class }.should == '<hr class="an_class" />'
144
+ quick_render { hr :class => 'an_class' }.should == '<hr class="an_class" />'
79
145
 
80
- (HammerBuilder::DOUBLE_TAGS + HammerBuilder::EMPTY_TAGS).each do |tag|
81
- it "should have method #{tag}" do
82
- should include(tag.to_sym)
146
+ quick_render { div.an_class.another_class }.should == '<div class="an_class another_class"></div>'
147
+ #noinspection RubyArgCount
148
+ quick_render { div.class 'an_class', 'another_class' }.should == '<div class="an_class another_class"></div>'
149
+ quick_render { div :class => ['an_class', 'another_class'] }.should == '<div class="an_class another_class"></div>'
150
+ #noinspection RubyArgCount
151
+ quick_render { div.class(false, nil, 'an_class', true && 'another_class') }.should ==
152
+ '<div class="an_class another_class"></div>'
153
+ end
154
+
155
+ it "#attribute" do
156
+ quick_render { div.attribute 'xml:ns', 'gibris' }.should == '<div xml:ns="gibris"></div>'
157
+ quick_render { div.attribute(:class, 'a') { text 'asd' } }.should == '<div class="a">asd</div>'
158
+ end
159
+
160
+ it '#[]' do
161
+ obj = Object.new
162
+ quick_render { div[obj] }.should == %Q(<div id="object_#{obj.object_id}" class="object"></div>)
163
+
164
+ class AnObject
165
+ def self.hammer_builder_ref
166
+ "a"
83
167
  end
84
168
 
85
- describe tag do
86
- before { @builder = HammerBuilder::Standard.get }
87
- after { @builder.release! }
88
- subject { @builder.send(tag).methods }
89
- it "should include its attribute methods" do
90
- attrs = (HammerBuilder::GLOBAL_ATTRIBUTES + HammerBuilder::EXTRA_ATTRIBUTES[tag]).
91
- map {|attr| attr.to_sym}
92
- should include(*attrs)
93
- end
169
+ def hammer_builder_ref
170
+ 'b'
94
171
  end
95
172
  end
173
+ obj = AnObject.new
174
+ quick_render { div[obj] }.should == %Q(<div id="b" class="a"></div>)
96
175
 
176
+ obj = Object.new.extend(Module.new do
177
+ def id;
178
+ "an_id";
179
+ end
180
+ end)
181
+ quick_render { div[obj] }.should == %Q(<div id="object_an_id" class="object"></div>)
182
+ quick_render { div.mimic(obj) { text 'a' } }.should == %Q(<div id="object_an_id" class="object">a</div>)
97
183
  end
98
184
 
99
- CONTENT = :'cc<>&cc'
100
-
101
- describe 'rendering' do
102
- describe '1' do
103
- subject do
104
- HammerBuilder::Formated.get.go_in do
105
- xhtml5!
106
- html do
107
- head { title }
108
- body do
109
- div CONTENT
110
- meta.http_equiv CONTENT
111
- p.content CONTENT
112
- div.id CONTENT
113
- div.data_id CONTENT
114
- div :id => CONTENT, :content => CONTENT
115
- div.attributes :id => CONTENT, :content => CONTENT
116
- div.attribute :newone, CONTENT
117
- div { text CONTENT }
118
- div[CONTENT].with { article CONTENT }
119
- js 'var < 1;'
120
- div do
121
- strong :content
122
- text :content
123
- end
124
- end
125
- end
126
- end.to_xhtml!.strip
127
- end
185
+ it "#data-.*" do
186
+ quick_render { div('a').data_secret("I won't tell.") }.should == '<div data-secret="I won\'t tell.">a</div>'
187
+ end
188
+
189
+ it '#data' do
190
+ quick_render { hr.data(:secret => true) }.should == '<hr data-secret="true" />'
191
+ quick_render { div('a', :data => { :secret => "I won't tell." }) }.should ==
192
+ '<div data-secret="I won\'t tell.">a</div>'
193
+ quick_render { div('a').data(:secret => "I won't tell.") { text 'a' } }.should ==
194
+ '<div data-secret="I won\'t tell.">a</div>'
195
+ end
128
196
 
129
- it { should_not match(/cc<>&cc/) }
130
- it 'should render corectly' do
131
- should == (<<STR).strip
132
- <?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html>
133
- <html xmlns="http://www.w3.org/1999/xhtml">
134
- <head>
135
- <title></title>
136
- </head>
137
- <body>
138
- <div>cc&lt;&gt;&amp;cc</div>
139
- <meta http-equiv="cc&lt;&gt;&amp;cc" />
140
- <p>cc&lt;&gt;&amp;cc</p>
141
- <div id="cc&lt;&gt;&amp;cc"></div>
142
- <div data-id="cc&lt;&gt;&amp;cc"></div>
143
- <div id="cc&lt;&gt;&amp;cc">cc&lt;&gt;&amp;cc</div>
144
- <div id="cc&lt;&gt;&amp;cc">cc&lt;&gt;&amp;cc</div>
145
- <div newone="cc&lt;&gt;&amp;cc"></div>
146
- <div>cc&lt;&gt;&amp;cc
147
- </div>
148
- <div id="cc&lt;&gt;&amp;cc">
149
- <article>cc&lt;&gt;&amp;cc</article>
150
- </div>
151
- <script type="text/javascript"><![CDATA[var < 1;]]>
152
- </script>
153
- <div>
154
- <strong>content</strong>content
155
- </div>
156
- </body>
157
- </html>
158
- STR
197
+ it 'tags should have all the attributes' do
198
+ builder.tags.each do |tag|
199
+ builder.should be_respond_to(tag)
200
+ tag_instance = builder.send(tag)
201
+ (HammerBuilder::Data::HTML5.abstract_attributes.map(&:name) +
202
+ (HammerBuilder::Data::HTML5.single_tags + HammerBuilder::Data::HTML5.double_tags).
203
+ find { |t| t.name.to_s == tag }.attributes.map(&:name)).each do |attr|
204
+ tag_instance.should be_respond_to(attr)
159
205
  end
160
206
  end
161
- describe '2' do
162
- subject do
163
- HammerBuilder::Formated.get.go_in do
164
- html do
165
- body do
166
- comment CONTENT
167
- cdata CONTENT
168
- end
207
+ end
208
+
209
+ it "boolean attributes should render correctly" do
210
+ quick_render { input.readonly }.should == '<input readonly="readonly" />'
211
+ quick_render { option.selected { text 'asd' } }.should == '<option selected="selected">asd</option>'
212
+ quick_render { option.selected(true) }.should == '<option selected="selected"></option>'
213
+ quick_render { option.selected(1) }.should == '<option selected="selected"></option>'
214
+ quick_render { option.selected(false) }.should == '<option></option>'
215
+ quick_render { div.hidden('asd') }.should == '<div hidden="hidden"></div>'
216
+ end
217
+
218
+ it "should render correctly" do
219
+ quick_render do
220
+ html5
221
+ html do
222
+ head do
223
+ title.an_id! 'a_title'
224
+ meta.charset "utf-8"
225
+ end
226
+ body.id 'content' do
227
+ text 'asd'
228
+ div.left.style nil do
229
+ raw 'asd'
230
+ hr
169
231
  end
170
- end.to_xhtml!.strip
232
+ br
233
+ div.left do
234
+ hr
235
+ js 'asd'
236
+ js 'asd', :cdata => true
237
+ end
238
+ comment 'asd'
239
+ end
240
+ comment 'asd'
171
241
  end
242
+ end.should == '<!DOCTYPE html>' + "\n" +
243
+ '<html xmlns="http://www.w3.org/1999/xhtml"><head><title id="an_id">a_title</title><meta charset="utf-8" />'+
244
+ '</head><body id="content">asd<div style="" class="left">asd<hr /></div><br /><div class="left"><hr />'+
245
+ '<script type="text/javascript">asd</script><script type="text/javascript"><![CDATA[asd]]></script>'+
246
+ '</div><!--asd--></body><!--asd--></html>'
247
+ end
172
248
 
173
- it 'should render corectly' do
174
- should == (<<STR).strip
175
- <html xmlns="http://www.w3.org/1999/xhtml">
176
- <body>
177
- <!--cc<>&cc--><![CDATA[cc<>&cc]]>
178
- </body>
179
- </html>
180
- STR
181
- end
182
- end
249
+ it "#set variables" do
250
+ r = builder.set_variables(:a => 'a') do |b|
251
+ b.dive { p @a }
252
+ end.to_html!
253
+
254
+ r.should == '<p>a</p>'
255
+ builder.instance_variable_get(:@a).should be_nil
256
+ end
257
+
258
+ it "#join" do
259
+ quick_render { join([1, 2]) { |n| text n } }.should == '12'
260
+ quick_render { join([1, 2], 'a') { |n| text n } }.should == '1a2'
261
+ quick_render { join([lambda { text 1 }, 2], 'a') { |n| text n } }.should == '1a2'
262
+ quick_render { join([lambda { text 1 }, 2], lambda { text 'a' }) { |n| text n } }.should == '1a2'
263
+ end
264
+ end
265
+
266
+ describe HammerBuilder::Formatted do
267
+ let(:pool) { HammerBuilder::Pool.new HammerBuilder::Formatted }
268
+ let(:builder) { pool.get }
269
+
270
+ def quick_render &block
271
+ builder.dive(&block).to_html!
272
+ end
273
+
274
+ it "should be formatted" do
275
+ quick_render { div { comment 'asd'; br }; p }.should == "\n<div>\n <!--asd-->\n <br />\n</div>\n<p></p>"
183
276
  end
184
277
  end
185
- end
278
+ end