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.
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