hammer_builder 0.1.2 → 0.2.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.
@@ -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