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.
data/CHANGELOG.md CHANGED
@@ -1,16 +1,20 @@
1
1
  ## 0.1.2
2
+
2
3
  * 2011-05-17: Fixed gemspec and changelog fix #11
3
4
  * 2011-05-16: Rails bench corrected
5
+
4
6
  ## 0.1.1
5
7
  * 2011-05-16: Dynamic classes rewriten
6
8
  * 2011-05-11: doc update
7
9
  * 2011-05-11: doc update
10
+
8
11
  ## 0.1.0
9
12
  * 2011-05-11: doc update
10
13
  * 2011-05-11: doc update
11
14
  * 2011-05-11: Specs, Yardoc, minor updates
12
15
  * 2011-05-09: YARD added
13
16
  * 2011-05-09: repository clean up
17
+
14
18
  ## 0.0.0
15
19
  * 2011-05-09: better way for js #3
16
20
  * 2011-05-09: cdata added
data/README.md CHANGED
@@ -1,37 +1,32 @@
1
1
  # HammerBuilder
2
2
 
3
- [`HammerBuilder`](https://github.com/ruby-hammer/hammer-builder)
4
- is a xhtml5 builder written in and for Ruby 1.9.2. It does not introduce anything special, you just
5
- use Ruby to get your xhtml. [`HammerBuilder`](https://github.com/ruby-hammer/hammer-builder)
6
- has been written with three objectives:
7
-
8
- * Speed
9
- * Rich API
10
- * Extensibility
3
+ Fast Ruby xhtml5 renderer
11
4
 
12
5
  ## Links
13
6
 
14
- * Introduction:
15
- [http://hammer.pitr.ch/2011/05/11/HammerBuilder-introduction/](http://hammer.pitr.ch/2011/05/11/HammerBuilder-introduction/)
16
- * Yardoc: [http://hammer.pitr.ch/hammer-builder/](http://hammer.pitr.ch/hammer-builder/)
17
- * Issues: [https://github.com/ruby-hammer/hammer-builder/issues](https://github.com/ruby-hammer/hammer-builder/issues)
18
- * Changelog: [http://hammer.pitr.ch/hammer-builder/file.CHANGELOG.html](http://hammer.pitr.ch/hammer-builder/file.CHANGELOG.html)
19
- * Gem: [https://rubygems.org/gems/hammer_builder](https://rubygems.org/gems/hammer_builder)
7
+ * **Presentation**: <http://hammer.pitr.ch/hammer_builder/presentation/presentation.html>
8
+ * Gemcutter: <https://rubygems.org/gems/hammer_builder>
9
+ * Github: <https://github.com/ruby-hammer/hammer-builder>
10
+ * Yardoc: <http://rubydoc.info/github/ruby-hammer/hammer-builder/frames>
11
+ * Issues: <https://github.com/ruby-hammer/hammer-builder/issues>
12
+ * Changelog: <http://hammer.pitr.ch/hammer-builder/file.CHANGELOG.html>
13
+ * Gem: [https://rubygems.org/gems/hammer_builder](https://rubygems.org/gems/hammer_builder)
14
+ * Blog: <http://hammer.pitr.ch/>
20
15
 
21
16
  ## Syntax
22
17
 
23
- HammerBuilder::Formated.get.go_in do
18
+ HammerBuilder::Formated.new.go_in do
24
19
  xhtml5!
25
20
  html do
26
21
  head { title 'a title' }
27
22
  body do
28
- div.id('menu').class('left') do
23
+ div.menu!.left do
29
24
  ul do
30
25
  li 'home'
31
26
  li 'contacts', :class => 'active'
32
27
  end
33
28
  end
34
- div.id('content') do
29
+ div.content! do
35
30
  article.id 'article1' do
36
31
  h1 'header'
37
32
  p('some text').class('centered')
@@ -75,20 +70,18 @@ has been written with three objectives:
75
70
 
76
71
  ### Synthetic
77
72
 
78
- user system total real
79
- render 4.380000 0.000000 4.380000 ( 4.394127)
80
- render3 4.990000 0.000000 4.990000 ( 5.017267)
81
- HammerBuilder::Standard 5.590000 0.000000 5.590000 ( 5.929775)
82
- HammerBuilder::Formated 5.520000 0.000000 5.520000 ( 5.511297)
83
- erubis 7.340000 0.000000 7.340000 ( 7.345410)
84
- erubis-reuse 4.670000 0.000000 4.670000 ( 4.666334)
85
- fasterubis 7.700000 0.000000 7.700000 ( 7.689792)
86
- fasterubis-reuse 4.650000 0.000000 4.650000 ( 4.648017)
87
- tenjin 11.810000 0.280000 12.090000 ( 12.084124)
88
- tenjin-reuse 3.170000 0.010000 3.180000 ( 3.183110)
89
- erector 12.100000 0.000000 12.100000 ( 12.103520)
90
- markaby 20.750000 0.030000 20.780000 ( 21.371292)
91
- tagz 73.200000 0.140000 73.340000 ( 73.306450)
73
+ user system total real
74
+ tenjin-reuse 2.040000 0.000000 2.040000 ( 2.055140)
75
+ HammerBuilder::Standard 2.520000 0.000000 2.520000 ( 2.519284)
76
+ fasterubis-reuse 2.580000 0.000000 2.580000 ( 2.581407)
77
+ erubis-reuse 2.680000 0.000000 2.680000 ( 2.690176)
78
+ HammerBuilder::Formatted 2.780000 0.000000 2.780000 ( 2.794307)
79
+ erubis 5.180000 0.000000 5.180000 ( 5.183333)
80
+ fasterubis 5.210000 0.000000 5.210000 ( 5.219176)
81
+ tenjin 7.650000 0.160000 7.810000 ( 7.820490)
82
+ erector 9.450000 0.010000 9.460000 ( 9.471654)
83
+ markaby 14.300000 0.000000 14.300000 ( 14.318844)
84
+ tagz 33.430000 0.000000 33.430000 ( 33.483693)
92
85
 
93
86
  ### In Rails 3
94
87
 
@@ -122,13 +115,3 @@ has been written with three objectives:
122
115
  objects: 0
123
116
  gc_runs: 6
124
117
  gc_time: 0.20 ms
125
-
126
- ### Conclusion
127
-
128
- Template engines are slightly faster than [`HammerBuilder`](https://github.com/ruby-hammer/hammer-builder)
129
- when template does not content a lot of inserting or partials.
130
- On the other hand when partials are used, [`HammerBuilder`](https://github.com/ruby-hammer/hammer-builder)
131
- beats template engines.
132
- There is no overhead for partials in [`HammerBuilder`](https://github.com/ruby-hammer/hammer-builder)
133
- compared to using partials in template engine. The difference is significant for `Erubis`, `Tenjin` is
134
- not so bad, but I did not find any easy way to use `Tenjin` in Rails 3 (I did some hacking).
@@ -1,702 +1,6 @@
1
- require 'cgi'
2
- require 'active_support/core_ext/class/inheritable_attributes'
3
- require 'active_support/core_ext/string/inflections'
4
- require 'hammer_builder/dynamic_classes'
1
+ path = File.expand_path(File.dirname(__FILE__))
2
+ $: << path unless $:.include? path
5
3
 
6
- module HammerBuilder
7
- EXTRA_ATTRIBUTES = {
8
- "a" => ["href", "target", "ping", "rel", "media", "hreflang", "type"],
9
- "abbr" => [],
10
- "address" => [],
11
- "area" => ["alt", "coords", "shape", "href", "target", "ping", "rel", "media", "hreflang", "type"],
12
- "article" => [],
13
- "aside" => [],
14
- "audio" => ["src", "preload", "autoplay", "mediagroup", "loop", "controls"],
15
- "b" => [],
16
- "base" => ["href", "target"],
17
- "bdi" => [],
18
- "bdo" => [],
19
- "blockquote" => ["cite"],
20
- "body" => ["onafterprint", "onbeforeprint", "onbeforeunload", "onblur", "onerror", "onfocus", "onhashchange",
21
- "onload", "onmessage", "onoffline", "ononline", "onpagehide", "onpageshow", "onpopstate", "onredo", "onresize",
22
- "onscroll", "onstorage", "onundo", "onunload"],
23
- "br" => [],
24
- "button" => ["autofocus", "disabled", "form", "formaction", "formenctype", "formmethod", "formnovalidate",
25
- "formtarget", "name", "type", "value"],
26
- "canvas" => ["width", "height"],
27
- "caption" => [],
28
- "cite" => [],
29
- "code" => [],
30
- "col" => ["span"],
31
- "colgroup" => ["span"],
32
- "command" => ["type", "label", "icon", "disabled", "checked", "radiogroup"],
33
- "datalist" => ["option"],
34
- "dd" => [],
35
- "del" => ["cite", "datetime"],
36
- "details" => ["open"],
37
- "dfn" => [],
38
- "div" => [],
39
- "dl" => [],
40
- "dt" => [],
41
- "em" => [],
42
- "embed" => ["src", "type", "width", "height"],
43
- "fieldset" => ["disabled", "form", "name"],
44
- "figcaption" => [],
45
- "figure" => [],
46
- "footer" => [],
47
- "form" => ["action", "autocomplete", "enctype", "method", "name", "novalidate", "target", 'accept_charset'],
48
- "h1" => [],
49
- "h2" => [],
50
- "h3" => [],
51
- "h4" => [],
52
- "h5" => [],
53
- "h6" => [],
54
- "head" => [],
55
- "header" => [],
56
- "hgroup" => [],
57
- "hr" => [],
58
- "html" => ["manifest"],
59
- "i" => [],
60
- "iframe" => ["src", "srcdoc", "name", "sandbox", "seamless", "width", "height"],
61
- "img" => ["alt", "src", "usemap", "ismap", "width", "height"],
62
- "input" => ["accept", "alt", "autocomplete", "autofocus", "checked", "dirname", "disabled", "form", "formaction",
63
- "formenctype", "formmethod", "formnovalidate", "formtarget", "height", "list", "max", "maxlength", "min",
64
- "multiple", "name", "pattern", "placeholder", "readonly", "required", "size", "src", "step", "type", "value",
65
- "width"],
66
- "ins" => ["cite", "datetime"],
67
- "kbd" => [],
68
- "keygen" => ["autofocus", "challenge", "disabled", "form", "keytype", "name"],
69
- "label" => ["form", "for"],
70
- "legend" => [],
71
- "li" => ["value"],
72
- "link" => ["href", "rel", "media", "hreflang", "type", "sizes"],
73
- "map" => ["name"],
74
- "mark" => [],
75
- "menu" => ["type", "label"],
76
- "meta" => ["name", "content", "charset", "http_equiv"],
77
- "meter" => ["value", "min", "max", "low", "high", "optimum", "form"],
78
- "nav" => [],
79
- "noscript" => [],
80
- "object" => ["data", "type", "name", "usemap", "form", "width", "height"],
81
- "ol" => ["reversed", "start"],
82
- "optgroup" => ["disabled", "label"],
83
- "option" => ["disabled", "label", "selected", "value"],
84
- "output" => ["for", "form", "name"],
85
- "p" => [],
86
- "param" => ["name", "value"],
87
- "pre" => [],
88
- "progress" => ["value", "max", "form"],
89
- "q" => ["cite"],
90
- "rp" => [],
91
- "rt" => [],
92
- "ruby" => [],
93
- "s" => [],
94
- "samp" => [],
95
- "script" => ["src", "async", "defer", "type", "charset"],
96
- "section" => [],
97
- "select" => ["autofocus", "disabled", "form", "multiple", "name", "required", "size"],
98
- "small" => [],
99
- "source" => ["src", "type", "media"],
100
- "span" => [],
101
- "strong" => [],
102
- "style" => ["media", "type", "scoped"],
103
- "sub" => [],
104
- "summary" => [],
105
- "sup" => [],
106
- "table" => ["border"],
107
- "tbody" => [],
108
- "td" => ["colspan", "rowspan", "headers"],
109
- "textarea" => ["autofocus", "cols", "disabled", "form", "maxlength", "name", "placeholder", "readonly",
110
- "required", "rows", "wrap"],
111
- "tfoot" => [],
112
- "th" => ["colspan", "rowspan", "headers", "scope"],
113
- "thead" => [],
114
- "time" => ["datetime", "pubdate"],
115
- "title" => [],
116
- "tr" => [],
117
- "track" => ["default", "kind", "label", "src", "srclang"],
118
- "u" => [],
119
- "ul" => [],
120
- "var" => [],
121
- "video" => ["src", "poster", "preload", "autoplay", "mediagroup", "loop", "controls", "width", "height"],
122
- "wbr" => []
123
- }
124
-
125
- GLOBAL_ATTRIBUTES = [
126
- 'accesskey','class','contenteditable','contextmenu','dir','draggable','dropzone','hidden','id','lang',
127
- 'spellcheck','style','tabindex','title','onabort','onblur','oncanplay','oncanplaythrough','onchange',
128
- 'onclick','oncontextmenu','oncuechange','ondblclick','ondrag','ondragend','ondragenter','ondragleave',
129
- 'ondragover','ondragstart','ondrop','ondurationchange','onemptied','onended','onerror','onfocus','oninput',
130
- 'oninvalid','onkeydown','onkeypress','onkeyup','onload','onloadeddata','onloadedmetadata','onloadstart',
131
- 'onmousedown','onmousemove','onmouseout','onmouseover','onmouseup','onmousewheel','onpause','onplay',
132
- 'onplaying','onprogress','onratechange','onreadystatechange','onreset','onscroll','onseeked','onseeking',
133
- 'onselect','onshow','onstalled','onsubmit','onsuspend','ontimeupdate','onvolumechange','onwaiting'
134
- ]
4
+ require "hammer_builder/formatted"
135
5
 
136
- DOUBLE_TAGS = [
137
- 'a', 'abbr', 'article', 'aside', 'audio', 'address',
138
- 'b', 'bdo', 'blockquote', 'body', 'button',
139
- 'canvas', 'caption', 'cite', 'code', 'colgroup', 'command',
140
- 'datalist', 'dd', 'del', 'details', 'dfn', 'div', 'dl', 'dt',
141
- 'em',
142
- 'fieldset', 'figure', 'footer', 'form',
143
- 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'head', 'header', 'hgroup', 'html', 'i',
144
- 'iframe', 'ins', 'keygen', 'kbd', 'label', 'legend', 'li',
145
- 'map', 'mark', 'meter',
146
- 'nav', 'noscript',
147
- 'object', 'ol', 'optgroup', 'option',
148
- 'p', 'pre', 'progress',
149
- 'q', 'ruby', 'rt', 'rp', 's',
150
- 'samp', 'script', 'section', 'select', 'small', 'source', 'span',
151
- 'strong', 'style', 'sub', 'sup',
152
- 'table', 'tbody', 'td', 'textarea', 'tfoot',
153
- 'th', 'thead', 'time', 'title', 'tr',
154
- 'u', 'ul',
155
- 'var', 'video'
156
- ]
157
-
158
- EMPTY_TAGS = [
159
- 'area', 'base', 'br', 'col', 'embed',
160
- 'hr', 'img', 'input', 'link', 'meta', 'param'
161
- ]
162
-
163
- LT = '<'.freeze
164
- GT = '>'.freeze
165
- SLASH_LT = '</'.freeze
166
- SLASH_GT = ' />'.freeze
167
- SPACE = ' '.freeze
168
- MAX_LEVELS = 300
169
- SPACES = Array.new(MAX_LEVELS) {|i| (' ' * i).freeze }
170
- NEWLINE = "\n".freeze
171
- QUOTE = '"'.freeze
172
- EQL = '='.freeze
173
- EQL_QUOTE = EQL + QUOTE
174
- COMMENT_START = '<!--'.freeze
175
- COMMENT_END = '-->'.freeze
176
- CDATA_START = '<![CDATA['.freeze
177
- CDATA_END = ']]>'.freeze
178
-
179
- module Helper
180
- def self.included(base)
181
- super
182
- base.extend ClassMethods
183
- base.class_inheritable_array :builder_methods, :instance_writer => false, :instance_reader => false
184
- end
185
-
186
- module ClassMethods
187
-
188
- # adds instance method to the class. Method accepts any instance of builder and returns it after rendering.
189
- # @param [Symbol] method_name
190
- # @yield [self] builder_block is evaluated inside builder and accepts instance of a rendered object as parameter
191
- # @example
192
- # class User
193
- # # ...
194
- # include HammerBuilder::Helper
195
- #
196
- # builder :menu do |user|
197
- # li user.name
198
- # end
199
- # end
200
- #
201
- # User.new.menu(HammerBuilder::Standard.get).to_xhtml! #=> "<li>Name</li>"
202
- def builder(method_name, &builder_block)
203
- self.builder_methods = [method_name.to_sym]
204
- define_method(method_name) do |builder, *args|
205
- builder.go_in(self, *args, &builder_block)
206
- end
207
- end
208
- end
209
- end
210
-
211
- # Creating builder instances is expensive, therefore you can use Pool to go around that
212
- module Pool
213
- def self.included(base)
214
- super
215
- base.extend ClassMethods
216
- end
217
-
218
- module ClassMethods
219
- # This the preferred way of getting new Builder. If you forget to release it, it does not matter -
220
- # builder gets GCed after you lose reference
221
- # @return [Standard, Formated]
222
- def get
223
- mutex.synchronize do
224
- if free_builders.empty?
225
- new
226
- else
227
- free_builders.pop
228
- end
229
- end
230
- end
231
-
232
- # returns +builder+ back into pool *DONT* forget to lose the reference to the +builder+
233
- # @param [Standard, Formated]
234
- def release(builder)
235
- builder.reset
236
- mutex.synchronize do
237
- free_builders.push builder
238
- end
239
- nil
240
- end
241
-
242
- # @return [Fixnum] size of free builders
243
- def pool_size
244
- free_builders.size
245
- end
246
-
247
- private
248
-
249
- def mutex
250
- @mutex ||= Mutex.new
251
- end
252
-
253
- def free_builders
254
- @free_builders ||= []
255
- end
256
- end
257
-
258
- # instance version of ClassMethods.release
259
- # @see ClassMethods.release
260
- def release!
261
- self.class.release(self)
262
- end
263
- end
264
-
265
- # Abstract implementation of Builder
266
- class Abstract
267
- extend DynamicClasses
268
- include Pool
269
-
270
- # << faster then +
271
- # yield faster then block.call
272
- # accessing ivar and constant is faster then accesing hash or cvar
273
- # class_eval faster then define_method
274
- # beware of strings in methods -> creates a lot of garbage
275
-
276
- dc do
277
-
278
- define :AbstractTag do
279
- def initialize(builder)
280
- @builder = builder
281
- @output = builder.instance_eval { @output }
282
- @stack = builder.instance_eval { @stack }
283
- @classes = []
284
- set_tag
285
- end
286
-
287
- def open(attributes = nil)
288
- @output << LT << @tag
289
- @builder.current = self
290
- attributes(attributes)
291
- default
292
- self
293
- end
294
-
295
- # @example
296
- # div.attributes :id => 'id' # => <div id="id"></div>
297
- def attributes(attrs)
298
- return self unless attrs
299
- attrs.each do |attr, value|
300
- __send__(attr, *value)
301
- end
302
- self
303
- end
304
-
305
- # @example
306
- # div.attribute :id, 'id' # => <div id="id"></div>
307
- # @deprecated Please use {#attributes} instead
308
- def attribute(attribute, content) # TODO lose the method in 0.2
309
- warn ("method #attribute is deprecated use #attributes instead, called from:#{caller[0]}" )
310
- @output << SPACE << attribute.to_s << EQL_QUOTE << CGI.escapeHTML(content.to_s) << QUOTE
311
- end
312
-
313
- alias_method(:rclass, :class)
314
-
315
- class_inheritable_array :_attributes, :instance_writer => false, :instance_reader => false
316
-
317
- def self.attributes
318
- self._attributes
319
- end
320
-
321
- class_eval <<-RUBYCODE, __FILE__, __LINE__ + 1
322
- # allows data-* attributes
323
- def method_missing(method, *args, &block)
324
- if method.to_s =~ /data_([a-z_]+)/
325
- self.rclass.attributes = [method.to_s]
326
- self.send method, *args, &block
327
- else
328
- super
329
- end
330
- end
331
- RUBYCODE
332
-
333
- protected
334
-
335
- # sets the right tag in descendants
336
- def self.set_tag(tag)
337
- class_eval <<-RUBYCODE, __FILE__, __LINE__ + 1
338
- def set_tag
339
- @tag = '#{tag}'.freeze
340
- end
341
- RUBYCODE
342
- end
343
-
344
- set_tag 'abstract'
345
-
346
- # this method is called on each tag opening, useful for default attributes
347
- # @example html tag uses this to add xmlns attr.
348
- # html # => <html xmlns="http://www.w3.org/1999/xhtml"></html>
349
- def default
350
- end
351
-
352
- # defines dynamically methods for attributes
353
- def self.define_attributes
354
- attributes.each do |attr|
355
- next if instance_methods.include?(attr.to_sym)
356
- class_eval <<-RUBYCODE, __FILE__, __LINE__ + 1
357
- def #{attr}(content)
358
- @output << ATTR_#{attr.upcase} << CGI.escapeHTML(content.to_s) << QUOTE
359
- self
360
- end
361
- RUBYCODE
362
- end
363
- define_attribute_constants
364
- end
365
-
366
- # defines constant strings not to make garbage
367
- def self.define_attribute_constants
368
- attributes.each do |attr|
369
- const = "attr_#{attr}".upcase
370
- HammerBuilder.const_set const, " #{attr.gsub('_', '-')}=\"".freeze unless HammerBuilder.const_defined?(const)
371
- end
372
- end
373
-
374
- # adds attribute to class, triggers dynamical creation of needed instance methods etc.
375
- def self.attributes=(attributes)
376
- self._attributes = attributes
377
- define_attributes
378
- end
379
-
380
- # flushes classes to output
381
- def flush_classes
382
- unless @classes.empty?
383
- @output << ATTR_CLASS << CGI.escapeHTML(@classes.join(SPACE)) << QUOTE
384
- @classes.clear
385
- end
386
- end
387
-
388
- public
389
-
390
- # global HTML5 attributes
391
- self.attributes = GLOBAL_ATTRIBUTES
392
-
393
- alias :[] :id
394
-
395
- class_eval <<-RUBYCODE, __FILE__, __LINE__ + 1
396
- def class(*classes)
397
- @classes.push(*classes)
398
- self
399
- end
400
- RUBYCODE
401
- end
402
-
403
- define :AbstractEmptyTag, :AbstractTag do
404
- def flush
405
- flush_classes
406
- @output << SLASH_GT
407
- nil
408
- end
409
- end
410
-
411
- define :AbstractDoubleTag, :AbstractTag do
412
- # defined by class_eval because there is a super calling, causing error:
413
- # super from singleton method that is defined to multiple classes is not supported;
414
- # this will be fixed in 1.9.3 or later (NotImplementedError)
415
- class_eval <<-RUBYCODE, __FILE__, __LINE__ + 1
416
- def initialize(builder)
417
- super
418
- @content = nil
419
- end
420
-
421
- def open(*args, &block)
422
- attributes = if args.last.is_a?(Hash)
423
- args.pop
424
- end
425
- content args[0]
426
- super attributes
427
- @stack << @tag
428
- if block
429
- with &block
430
- else
431
- self
432
- end
433
- end
434
- RUBYCODE
435
-
436
- def flush
437
- flush_classes
438
- @output << GT
439
- @output << CGI.escapeHTML(@content) if @content
440
- @output << SLASH_LT << @stack.pop << GT
441
- @content = nil
442
- end
443
-
444
- # sets content of the double tag
445
- def content(content)
446
- @content = content.to_s
447
- self
448
- end
449
-
450
- # renders content of the double tag with block
451
- def with
452
- flush_classes
453
- @output << GT
454
- @content = nil
455
- @builder.current = nil
456
- yield
457
- # if (content = yield).is_a?(String)
458
- # @output << CGI.escapeHTML(content)
459
- # end
460
- @builder.flush
461
- @output << SLASH_LT << @stack.pop << GT
462
- nil
463
- end
464
-
465
- protected
466
-
467
- def self.define_attributes
468
- attributes.each do |attr|
469
- next if instance_methods(false).include?(attr.to_sym)
470
- if instance_methods.include?(attr.to_sym)
471
- class_eval <<-RUBYCODE, __FILE__, __LINE__ + 1
472
- def #{attr}(*args, &block)
473
- super(*args, &nil)
474
- return with(&block) if block
475
- self
476
- end
477
- RUBYCODE
478
- else
479
- class_eval <<-RUBYCODE, __FILE__, __LINE__ + 1
480
- def #{attr}(content, &block)
481
- @output << ATTR_#{attr.upcase} << CGI.escapeHTML(content.to_s) << QUOTE
482
- return with(&block) if block
483
- self
484
- end
485
- RUBYCODE
486
- end
487
- end
488
- define_attribute_constants
489
- end
490
- end
491
- end
492
-
493
- class_inheritable_accessor :tags, :instance_writer => false
494
- self.tags = {}
495
-
496
- protected
497
-
498
- # defines instance method for +tag+ in builder
499
- def self.define_tag(tag)
500
- tag = tag.to_s
501
- class_eval <<-RUBYCODE, __FILE__, __LINE__ + 1
502
- def #{tag}(*args, &block)
503
- flush
504
- @#{tag}.open(*args, &block)
505
- end
506
- RUBYCODE
507
- self.tags[tag] = tag
508
- end
509
-
510
- public
511
-
512
- attr_accessor :current
513
-
514
- def initialize()
515
- @output = ""
516
- @stack = []
517
- @current = nil
518
- # tag classes initialization
519
- tags.values.each do |klass|
520
- instance_variable_set(:"@#{klass}", self.class.dc[klass.camelize.to_sym].new(self))
521
- end
522
- end
523
-
524
- # escapes +text+ to output
525
- def text(text)
526
- flush
527
- @output << CGI.escapeHTML(text.to_s)
528
- end
529
-
530
- # unescaped +text+ to output
531
- def raw(text)
532
- flush
533
- @output << text.to_s
534
- end
535
-
536
- # inserts +comment+
537
- def comment(comment)
538
- flush
539
- @output << COMMENT_START << comment.to_s << COMMENT_END
540
- end
541
-
542
- # insersts CDATA with +content+
543
- def cdata(content)
544
- flush
545
- @output << CDATA_START << content.to_s << CDATA_END
546
- end
547
-
548
- def xml_version(version = '1.0', encoding = 'UTF-8')
549
- flush
550
- @output << "<?xml version=\"#{version}\" encoding=\"#{encoding}\"?>"
551
- end
552
-
553
- def doctype
554
- flush
555
- @output << "<!DOCTYPE html>"
556
- end
557
-
558
- # inserts xhtml5 header
559
- def xhtml5!
560
- xml_version
561
- doctype
562
- end
563
-
564
- # resets the builder to the state after creation - much faster then creating a new one
565
- def reset
566
- flush
567
- @output.clear
568
- @stack.clear
569
- self
570
- end
571
-
572
- # enables you to evaluate +block+ inside the builder with +variables+
573
- # @example
574
- # HammerBuilder::Formated.get.freeze.go_in('asd') do |string|
575
- # div string
576
- # end.to_html! #=> "<div>asd</div>"
577
- #
578
- def go_in(*variables, &block)
579
- instance_exec *variables, &block
580
- self
581
- end
582
-
583
- def set_variables(instance_variables)
584
- instance_variables.each {|name,value| instance_variable_set("@#{name}", value) }
585
- yield
586
- instance_variables.each {|name,_| remove_instance_variable("@#{name}") }
587
- self
588
- end
589
-
590
- # @return [String] output
591
- def to_xhtml()
592
- flush
593
- @output.clone
594
- end
595
-
596
- # @return [String] output and releases the builder to pool
597
- def to_xhtml!
598
- r = to_xhtml
599
- release!
600
- r
601
- end
602
-
603
- def flush
604
- if @current
605
- @current.flush
606
- @current = nil
607
- end
608
- end
609
- end
610
-
611
- # Builder implementation without formating (one line)
612
- class Standard < Abstract
613
-
614
- dc do
615
- (DOUBLE_TAGS - ['html']).each do |tag|
616
- define tag.camelize.to_sym , :AbstractDoubleTag do
617
- set_tag tag
618
- self.attributes = EXTRA_ATTRIBUTES[tag]
619
- end
620
-
621
- base.define_tag(tag)
622
- end
623
-
624
- define :Html, :AbstractDoubleTag do
625
- set_tag 'html'
626
- self.attributes = ['xmlns'] + EXTRA_ATTRIBUTES['html']
627
-
628
- def default
629
- xmlns('http://www.w3.org/1999/xhtml')
630
- end
631
- end
632
- base.define_tag('html')
633
-
634
- EMPTY_TAGS.each do |tag|
635
- define tag.camelize.to_sym, :AbstractEmptyTag do
636
- set_tag tag
637
- self.attributes = EXTRA_ATTRIBUTES[tag]
638
- end
639
-
640
- base.define_tag(tag)
641
- end
642
- end
643
-
644
- def js(js , options = {})
645
- flush
646
- script({:type => "text/javascript"}.merge(options)) { cdata js }
647
- end
648
-
649
- def join(collection, glue, &it)
650
- flush
651
- glue_block = if glue.is_a? String
652
- lambda { text glue }
653
- else
654
- glue
655
- end
656
-
657
- collection.each_with_index do |obj, i|
658
- glue_block.call() if i > 0
659
- it.call(obj)
660
- end
661
- end
662
-
663
- end
664
-
665
- # Builder implementation with formating (indented by ' ')
666
- # Slow down is less then 1%
667
- class Formated < Standard
668
-
669
- dc do
670
- extend :AbstractTag do
671
- def open(attributes = nil)
672
- @output << NEWLINE << SPACES.fetch(@stack.size, SPACE) << LT << @tag
673
- @builder.current = self
674
- attributes(attributes)
675
- default
676
- self
677
- end
678
- end
679
-
680
- extend :AbstractDoubleTag do
681
- def with
682
- flush_classes
683
- @output << GT
684
- @content = nil
685
- @builder.current = nil
686
- yield
687
- # if (content = yield).is_a?(String)
688
- # @output << CGI.escapeHTML(content)
689
- # end
690
- @builder.flush
691
- @output << NEWLINE << SPACES.fetch(@stack.size-1, SPACE) << SLASH_LT << @stack.pop << GT
692
- nil
693
- end
694
- end
695
- end
696
-
697
- def comment(comment)
698
- @output << NEWLINE << SPACES.fetch(@stack.size, SPACE) << COMMENT_START << comment.to_s << COMMENT_END
699
- end
700
- end
701
- end
702
6