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 +4 -0
- data/README.md +24 -41
- data/lib/hammer_builder.rb +3 -699
- data/lib/hammer_builder/abstract.rb +205 -0
- data/lib/hammer_builder/abstract/abstract_double_tag.rb +148 -0
- data/lib/hammer_builder/abstract/abstract_single_tag.rb +18 -0
- data/lib/hammer_builder/abstract/abstract_tag.rb +233 -0
- data/lib/hammer_builder/data.rb +11 -0
- data/lib/hammer_builder/data/html5.rb +179 -0
- data/lib/hammer_builder/doc.rb +385 -0
- data/lib/hammer_builder/dynamic_classes.rb +136 -130
- data/lib/hammer_builder/formatted.rb +43 -0
- data/lib/hammer_builder/helper.rb +24 -0
- data/lib/hammer_builder/pool.rb +72 -0
- data/lib/hammer_builder/rails.rb +40 -0
- data/lib/hammer_builder/standard.rb +48 -0
- data/lib/hammer_builder/strings.rb +29 -0
- data/spec/hammer_builder_spec.rb +239 -146
- data/spec/spec_helper.rb +3 -1
- metadata +264 -96
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
|
-
|
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
|
-
*
|
15
|
-
|
16
|
-
*
|
17
|
-
*
|
18
|
-
*
|
19
|
-
*
|
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.
|
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.
|
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.
|
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
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
erubis
|
85
|
-
fasterubis
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
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).
|
data/lib/hammer_builder.rb
CHANGED
@@ -1,702 +1,6 @@
|
|
1
|
-
|
2
|
-
|
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
|
-
|
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
|
|