htmless 0.4
Sign up to get free protection for your applications and to get access to all the features.
- data/MIT-LICENSE +19 -0
- data/README.md +7 -0
- data/README_FULL.md +585 -0
- data/lib/copy_to_doc.rb +43 -0
- data/lib/htmless/abstract/abstract_double_tag.rb +144 -0
- data/lib/htmless/abstract/abstract_single_tag.rb +18 -0
- data/lib/htmless/abstract/abstract_tag.rb +246 -0
- data/lib/htmless/abstract.rb +252 -0
- data/lib/htmless/data/html5.rb +179 -0
- data/lib/htmless/data.rb +11 -0
- data/lib/htmless/doc.rb +394 -0
- data/lib/htmless/dynamic_classes.rb +216 -0
- data/lib/htmless/formatted.rb +43 -0
- data/lib/htmless/helper.rb +24 -0
- data/lib/htmless/pool.rb +72 -0
- data/lib/htmless/rails.rb +40 -0
- data/lib/htmless/standard.rb +48 -0
- data/lib/htmless/strings_injector.rb +43 -0
- data/lib/htmless.rb +3 -0
- data/lib/js.rb +176 -0
- data/spec/htmless_spec.rb +290 -0
- metadata +279 -0
data/MIT-LICENSE
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
Copyright (c) 2013 Petr Chalupa <git@pitr.ch>
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
5
|
+
in the Software without restriction, including without limitation the rights
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
8
|
+
furnished to do so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in
|
11
|
+
all copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
THE SOFTWARE.
|
data/README.md
ADDED
data/README_FULL.md
ADDED
@@ -0,0 +1,585 @@
|
|
1
|
+
# Htmless
|
2
|
+
|
3
|
+
Fast extensible html5 builder in pure Ruby
|
4
|
+
|
5
|
+
- Documentation: <http://blog.pitr.ch/htmless>
|
6
|
+
- Source: <https://github.com/pitr-ch/htmless>
|
7
|
+
- Blog: <http://blog.pitr.ch/blog/categories/htmless/>
|
8
|
+
|
9
|
+
## Why?
|
10
|
+
|
11
|
+
I needed html builder with these characteristics:
|
12
|
+
|
13
|
+
* Ruby
|
14
|
+
* Fast
|
15
|
+
* Extensibility for each tag
|
16
|
+
|
17
|
+
Disadvanteges of other options:
|
18
|
+
|
19
|
+
* Erector - quite high level, no tag extensibility
|
20
|
+
* Markaby - slow
|
21
|
+
* Wee::Brush - extensible but not a standalone gem
|
22
|
+
* Tagz - very slow
|
23
|
+
* Erubis - fast but temlate engine and no tag extensibility
|
24
|
+
* Tenjin - faster but temlate engine and no tag extensibility
|
25
|
+
|
26
|
+
## Quick syntax example
|
27
|
+
|
28
|
+
!!!ruby
|
29
|
+
Htmless::Formatted.new.go_in do
|
30
|
+
html5
|
31
|
+
html do
|
32
|
+
head { title 'my_page' }
|
33
|
+
body do
|
34
|
+
div.content! do
|
35
|
+
p.centered "my page's content"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end.to_html
|
40
|
+
|
41
|
+
returns
|
42
|
+
|
43
|
+
!!!html
|
44
|
+
<!DOCTYPE html>
|
45
|
+
<html xmlns="http://www.w3.org/1999/xhtml">
|
46
|
+
<head>
|
47
|
+
<title>my_page</title>
|
48
|
+
</head>
|
49
|
+
<body>
|
50
|
+
<div id="content">
|
51
|
+
<p class="centered">my page's content</p>
|
52
|
+
</div>
|
53
|
+
</body>
|
54
|
+
</html>
|
55
|
+
|
56
|
+
## Chaining
|
57
|
+
|
58
|
+
!!!ruby
|
59
|
+
div.class('left').id('menu').class('border').onclick('run();').with do
|
60
|
+
text 'hello'
|
61
|
+
end
|
62
|
+
|
63
|
+
prints
|
64
|
+
|
65
|
+
!!!html
|
66
|
+
<div class="left border" id="menu" onclick="run();">hello</div>
|
67
|
+
|
68
|
+
Content block must be last in the chain. No other calls are allowed after block.
|
69
|
+
|
70
|
+
!!!ruby
|
71
|
+
div do
|
72
|
+
text 'content'
|
73
|
+
end.id('an_id') # won't work
|
74
|
+
|
75
|
+
## Attributes
|
76
|
+
|
77
|
+
!!!ruby
|
78
|
+
div :id => 'menu', :class => 'left' # is shortcut for:
|
79
|
+
div.attributes :id => 'menu', :class => 'left'
|
80
|
+
|
81
|
+
* `#attributes` calls underlining methods `#id` and `#class`
|
82
|
+
* If you extend tag by a method, you can call it through `#attributes` or `#attribute`
|
83
|
+
|
84
|
+
Any attribute method you call is immediately appended to output with exception of classes. They are acumulating
|
85
|
+
until tag is closed.
|
86
|
+
|
87
|
+
!!!ruby
|
88
|
+
div(:class => 'left').class('center') # <div class='left center'></div>
|
89
|
+
div(:id => 1).id(2) # <div id="1" id="2"></div>
|
90
|
+
|
91
|
+
Udefined attribute can be rendered with.
|
92
|
+
|
93
|
+
!!!ruby
|
94
|
+
html.attribute :xmlns, 'http://www.w3.org/1999/xhtml'
|
95
|
+
# => <html xmlns="http://www.w3.org/1999/xhtml"></html>
|
96
|
+
|
97
|
+
If `#attribute` finds defined method for desired attribute, the method is called.
|
98
|
+
|
99
|
+
!!!ruby
|
100
|
+
div.attribute :class => 'left' # is equvivalent to:
|
101
|
+
div.class 'left'
|
102
|
+
|
103
|
+
## Content, Boolean attributes
|
104
|
+
|
105
|
+
!!!ruby
|
106
|
+
div 'content' # <div>content</div>
|
107
|
+
div.content 'content' # <div>content</div>
|
108
|
+
div :content => 'content' # <div>content</div>
|
109
|
+
div { text 'content' } # <div>content</div>
|
110
|
+
div.with { text 'content' } # <div>content</div>
|
111
|
+
div 'content', :id => :id # <div id="id">content</div>
|
112
|
+
div(:id => :id) { text 'content' } # <div id="id">content</div>
|
113
|
+
div.id :id do
|
114
|
+
text 'content'
|
115
|
+
end # <div id="id">content</div>
|
116
|
+
|
117
|
+
### Boolean attributes
|
118
|
+
|
119
|
+
Attributes like `checked` and `disabled` test value for true, if false they render nothing
|
120
|
+
|
121
|
+
!!!ruby
|
122
|
+
input.disabled am_i_disabled?
|
123
|
+
# => <input disabled="disabled" />
|
124
|
+
# or <input />
|
125
|
+
|
126
|
+
## Id, class and mimic
|
127
|
+
|
128
|
+
!!!ruby
|
129
|
+
div.menu!.left.hidden # => <div id="menu" class="left hidden"></div>
|
130
|
+
|
131
|
+
Content can be passed in the usual way:
|
132
|
+
|
133
|
+
!!!ruby
|
134
|
+
div.menu! 'content' # => <div id="menu">content</div>
|
135
|
+
div.menu! { p 'content' } # => <div id="menu"><p>content</p></div>
|
136
|
+
|
137
|
+
`#class` accepts an array, values are joined with spaces and `false`, `nil` values are ignored
|
138
|
+
|
139
|
+
!!!ruby
|
140
|
+
div.class('menu', 'big', hide? && 'hidden')
|
141
|
+
# => <div class="menu big hidden"></div>
|
142
|
+
# or <div class="menu big"></div>
|
143
|
+
|
144
|
+
`#id` accepts also an array, values are joined with '-' and `false`, `nil` values are ignored
|
145
|
+
|
146
|
+
!!!ruby
|
147
|
+
div.id('menu', 'big', a_failing_test && 'useless') # => <div id="menu-big"></div>
|
148
|
+
|
149
|
+
Object (model) can be used to set class and id
|
150
|
+
|
151
|
+
!!!ruby
|
152
|
+
user = User.new(:id => 1)
|
153
|
+
div(:class => 'model')[user].with { text 'data' }
|
154
|
+
# => <div id="user-1" class="model user">data</div>
|
155
|
+
|
156
|
+
`#mimic` which is aliased as `#[]` looks for `.htmless_ref` or it uses the class name to render the class part.
|
157
|
+
As id is used already determined class and `#htmless_ref`, `#id`, `#object_id` which one is found first.
|
158
|
+
|
159
|
+
## Data attributes, Join
|
160
|
+
|
161
|
+
### Data attributes
|
162
|
+
|
163
|
+
!!!ruby
|
164
|
+
div.data_secret("I won't tell.") # => <div data-secret="I won't tell."></div>
|
165
|
+
data :secret => "I won't tell." # => <div data-secret="I won't tell."></div>
|
166
|
+
|
167
|
+
|
168
|
+
### Join
|
169
|
+
|
170
|
+
`#join` enables easy rendering of collections
|
171
|
+
|
172
|
+
!!!ruby
|
173
|
+
join([1, 1.2], ->{ text ', ' }) {|n| b "#{n}cm" }
|
174
|
+
# => "<b>1cm</b>, <b>1.2cm</b>"
|
175
|
+
join([1, 1.2], ', ') {|n| b "#{n}cm" }
|
176
|
+
# => "<b>1cm</b>, <b>1.2cm</b>"
|
177
|
+
join([1, ->{ text 'missing' }], ', ') {|n| b "#{n}cm" }
|
178
|
+
# => "<b>1cm</b>, missing"
|
179
|
+
|
180
|
+
A block in the collection is rendered directly without iterator. This can be useful when menu with some delimiters
|
181
|
+
is rendered based on collection of some objects and you need to add one or more untypical menu items.
|
182
|
+
|
183
|
+
## '_' vs '-'
|
184
|
+
|
185
|
+
'_' in attributes is transformed to '-'
|
186
|
+
|
187
|
+
!!!ruby
|
188
|
+
meta.http_equiv 'Content-Type' # => <meta http-equip="Content-Type" />
|
189
|
+
|
190
|
+
'_' in class shortcut methods is transformed to '-'
|
191
|
+
|
192
|
+
!!!ruby
|
193
|
+
div.a_class.an_id! # => <div id="an-id" class="a-class"></div>
|
194
|
+
|
195
|
+
Ids generated by Arrays are joined with '-'
|
196
|
+
|
197
|
+
!!!ruby
|
198
|
+
div.id('an', 'id') # => <div id="an-id"></div>
|
199
|
+
|
200
|
+
|
201
|
+
## Tag's reprezentation
|
202
|
+
|
203
|
+
Each tag has its own class.
|
204
|
+
|
205
|
+
!!!ruby
|
206
|
+
div.rclass # => #<Class:0x00000001d449b8(Htmless::Formatted.dc[:Div])>
|
207
|
+
li.rclass # => #<Class:0x00000001d449b8(Htmless::Formatted.dc[:Li])>
|
208
|
+
|
209
|
+
`#rclass` is original ruby method `#class`
|
210
|
+
|
211
|
+
|
212
|
+
## Getting a builder
|
213
|
+
|
214
|
+
Creating new builder is relatively expensive. There is a pool of builders implemented.
|
215
|
+
|
216
|
+
!!!ruby
|
217
|
+
if am_i_smart?
|
218
|
+
pool = Htmless::Pool.new Htmless::Formatted # store pool somewhere globalish for reuse
|
219
|
+
builder = pool.get # => new builder from pool if there is one, or a newlly created one
|
220
|
+
# ... do yours stuff
|
221
|
+
builder.release # resets builder and returns it to the pool
|
222
|
+
else
|
223
|
+
b = Htmless::Formatted.new
|
224
|
+
# ... do yours stuff
|
225
|
+
# later on builder gets garbage collected
|
226
|
+
end
|
227
|
+
|
228
|
+
Be careful not to use builder after you have released it.
|
229
|
+
|
230
|
+
!!!ruby
|
231
|
+
if am_i_freaking_smart?
|
232
|
+
pool = Htmless::Pool.new Htmless::Formatted
|
233
|
+
xhtml = pool.get.go_in(your_data) do |data|
|
234
|
+
# render your data ...
|
235
|
+
end.to_html! # returns xhtml and releases the builder
|
236
|
+
end
|
237
|
+
|
238
|
+
This way builder doesn't get stored anywhere.
|
239
|
+
|
240
|
+
## How to use
|
241
|
+
|
242
|
+
The idea is that any object intended to rendering will have methods which renders the object into builder.
|
243
|
+
There is a `Htmless::Helper` and method `#render` (also aliased as `#r`) for that purpose.
|
244
|
+
|
245
|
+
!!!ruby
|
246
|
+
class User < Struct.new(:name, :login, :email)
|
247
|
+
extend Htmless::Helper
|
248
|
+
|
249
|
+
builder :detail do |user|
|
250
|
+
ul do
|
251
|
+
r user, :attribute, :name
|
252
|
+
r user, :attribute, :login
|
253
|
+
r user, :attribute, :email
|
254
|
+
end
|
255
|
+
end
|
256
|
+
|
257
|
+
def attribute(b, attribute)
|
258
|
+
b.li do
|
259
|
+
b.strong "#{attribute}: "
|
260
|
+
b.text self.send(attribute)
|
261
|
+
end
|
262
|
+
end
|
263
|
+
end
|
264
|
+
|
265
|
+
`.builder` is just shortcut to define method `User#detail` like this:
|
266
|
+
|
267
|
+
!!!ruby
|
268
|
+
def detail(b)
|
269
|
+
b.go_in(self) do |user| # this block is the same as the one passed
|
270
|
+
ul do # above to .builder
|
271
|
+
r user, :attribute, :name
|
272
|
+
r user, :attribute, :login
|
273
|
+
r user, :attribute, :email
|
274
|
+
end
|
275
|
+
end
|
276
|
+
end
|
277
|
+
|
278
|
+
is same as
|
279
|
+
|
280
|
+
!!!ruby
|
281
|
+
builder :detail do |user|
|
282
|
+
ul do
|
283
|
+
r user, :attribute, :name
|
284
|
+
r user, :attribute, :login
|
285
|
+
r user, :attribute, :email
|
286
|
+
end
|
287
|
+
end
|
288
|
+
|
289
|
+
and
|
290
|
+
|
291
|
+
!!!ruby
|
292
|
+
user = User.new("Peter", "peter", "peter@example.com")
|
293
|
+
pool.get.dive do
|
294
|
+
r user, :detail
|
295
|
+
end.to_html!
|
296
|
+
|
297
|
+
returns:
|
298
|
+
|
299
|
+
!!!html
|
300
|
+
<ul>
|
301
|
+
<li>
|
302
|
+
<strong>name: </strong>Peter
|
303
|
+
</li>
|
304
|
+
<li>
|
305
|
+
<strong>login: </strong>peter
|
306
|
+
</li>
|
307
|
+
<li>
|
308
|
+
<strong>email: </strong>peter@example.com
|
309
|
+
</li>
|
310
|
+
</ul>
|
311
|
+
|
312
|
+
## Contexts
|
313
|
+
|
314
|
+
Html can be rendered outside of builder's context
|
315
|
+
|
316
|
+
!!!ruby
|
317
|
+
class User
|
318
|
+
attr_reder :name, :age
|
319
|
+
def detail(b) # builder
|
320
|
+
b.ul { b.li name; b.li name }
|
321
|
+
end
|
322
|
+
end
|
323
|
+
|
324
|
+
or `#go_in` (also aliased as `#dive`) can be used to get into builder's context
|
325
|
+
|
326
|
+
!!!ruby
|
327
|
+
class User
|
328
|
+
extend Htmless::Helper
|
329
|
+
attr_reder :name, :age
|
330
|
+
builder :detail do |user|
|
331
|
+
ul { li user.name; li user.age }
|
332
|
+
end
|
333
|
+
end
|
334
|
+
# => <ul><li>john Doe</li><li>25</li></ul>
|
335
|
+
|
336
|
+
## Helpers
|
337
|
+
|
338
|
+
If they are needed they can be mixed directly into Builder's instance
|
339
|
+
|
340
|
+
!!!ruby
|
341
|
+
Htmless::Formatted.new.go_in do
|
342
|
+
extend ActionView::Helpers::NumberHelper
|
343
|
+
div number_with_precision(Math::PI, :precision => 4)
|
344
|
+
end.to_html # => <div>3.1416</div>
|
345
|
+
|
346
|
+
*Be careful when you are using this with `Pool`. Some instances may have helpers and some don't.*
|
347
|
+
|
348
|
+
Or new builder descendant can be made.
|
349
|
+
|
350
|
+
!!!ruby
|
351
|
+
class MyBuilder < Hammer::FormattedBuilder
|
352
|
+
include ActionView::Helpers::NumberHelper
|
353
|
+
end
|
354
|
+
|
355
|
+
MyBuilder.new.go_in do
|
356
|
+
div number_with_precision(Math::PI, :precision => 4)
|
357
|
+
end.to_html # => <div>3.1416</div>
|
358
|
+
|
359
|
+
## Implementation details - Tag's shared instances
|
360
|
+
|
361
|
+
There are no multiple instances for each tag.
|
362
|
+
Every tag of the same type share a same instance (unique within the instance of a builder).
|
363
|
+
|
364
|
+
!!!ruby
|
365
|
+
puts(pool.get.go_in do
|
366
|
+
puts div.object_id
|
367
|
+
puts div.object_id
|
368
|
+
end.to_html!)
|
369
|
+
# =>
|
370
|
+
# 10069200
|
371
|
+
# 10069200
|
372
|
+
# <div></div><div></div>
|
373
|
+
|
374
|
+
`Htmless` creates what he can prior to rendering and uses heavily meta-programming, because of that instantiating
|
375
|
+
the very first instance of builder triggers some magic staff taking about a one second. Creating new builders of the
|
376
|
+
same class is than much faster and getting builder from a pool is instant.
|
377
|
+
|
378
|
+
This won't work:
|
379
|
+
|
380
|
+
!!!ruby
|
381
|
+
puts(pool.get.go_in do
|
382
|
+
a = div 'a'
|
383
|
+
div 'b'
|
384
|
+
a.class 'class'
|
385
|
+
end.to_html!)
|
386
|
+
# => <div>a</div><div class="class">b</div>
|
387
|
+
|
388
|
+
because when `#class` is called the second div is being built.
|
389
|
+
|
390
|
+
## Implementation details - DynamicClasses
|
391
|
+
|
392
|
+
!!!ruby
|
393
|
+
class Parent
|
394
|
+
class LeftEye
|
395
|
+
def to_s
|
396
|
+
'left eye'
|
397
|
+
end
|
398
|
+
end
|
399
|
+
class RightEye < LeftEye
|
400
|
+
def to_s
|
401
|
+
'next to ' + super
|
402
|
+
end
|
403
|
+
end
|
404
|
+
end
|
405
|
+
class AChild < Parent
|
406
|
+
end
|
407
|
+
class AMutant
|
408
|
+
class LeftEye < superclass::LeftEye
|
409
|
+
def to_s
|
410
|
+
'laser ' + super
|
411
|
+
end
|
412
|
+
end
|
413
|
+
end
|
414
|
+
|
415
|
+
How to define `AMutant::RihtEye` to return `"next to laser left eye"` ?
|
416
|
+
|
417
|
+
!!!ruby
|
418
|
+
class Parent
|
419
|
+
extend DynamicClasses
|
420
|
+
dynamic_classes do
|
421
|
+
def_class :LeftEye do
|
422
|
+
def to_s; 'left eye'; end
|
423
|
+
end
|
424
|
+
def_class :RightEye, :LeftEye do
|
425
|
+
class_eval <<-RUBYCODE, __FILE__, __LINE__+1
|
426
|
+
def to_s; 'next to ' + super; end
|
427
|
+
RUBYCODE
|
428
|
+
end
|
429
|
+
end
|
430
|
+
end
|
431
|
+
|
432
|
+
class AChild < Parent
|
433
|
+
end
|
434
|
+
|
435
|
+
class AMutant < Parent
|
436
|
+
dynamic_classes do
|
437
|
+
extend_class :LeftEye do
|
438
|
+
def to_s; 'laser ' + super; end
|
439
|
+
end
|
440
|
+
end
|
441
|
+
end
|
442
|
+
|
443
|
+
Each class is a diferent object.
|
444
|
+
|
445
|
+
!!!ruby
|
446
|
+
Parent.dynamic_classes[:LeftEye] # => #<Class:0x00000001d449b8(A.dc[:LeftEye])>
|
447
|
+
AChild.dynamic_classes[:LeftEye] # => #<Class:0x00000001d42398(A.dc[:LeftEye])>
|
448
|
+
|
449
|
+
`AMutant.dc[:RightEye]` automaticaly inherits from extended `AMutant.dc[:LeftEye]`
|
450
|
+
|
451
|
+
!!!ruby
|
452
|
+
Parent.dc[:LeftEye].new.to_s # => 'left eye'
|
453
|
+
Parent.dc[:RightEye].new.to_s # => 'next to left eye'
|
454
|
+
|
455
|
+
AChild.dc[:LeftEye].new.to_s # => 'left eye'
|
456
|
+
AChild.dc[:RightEye].new.to_s # => 'next to left eye'
|
457
|
+
|
458
|
+
AMutant.dc[:LeftEye].new.to_s # => 'laser left eye'
|
459
|
+
AMutant.dc[:RightEye].new.to_s # => 'next to laser left eye'
|
460
|
+
|
461
|
+
## Extensibility
|
462
|
+
|
463
|
+
!!!ruby
|
464
|
+
class MyBuilder < Htmless::Formatted
|
465
|
+
dynamic_classes do
|
466
|
+
# define new method to all tags
|
467
|
+
extend_class :AbstractTag do
|
468
|
+
def hide!
|
469
|
+
self.class 'hidden'
|
470
|
+
end
|
471
|
+
end
|
472
|
+
|
473
|
+
# add pseudo tag
|
474
|
+
def_class :Component, :Div do
|
475
|
+
class_eval <<-RUBYCODE, __FILE__, __LINE__ + 1
|
476
|
+
def open(id, attributes = nil, &block)
|
477
|
+
super(attributes, &nil).id(id).class('component')
|
478
|
+
block ? with(&block) : self
|
479
|
+
end
|
480
|
+
RUBYCODE
|
481
|
+
end
|
482
|
+
end
|
483
|
+
|
484
|
+
define_tag :component
|
485
|
+
|
486
|
+
# if the class is not needed same can be done this way
|
487
|
+
def simple_component(id, attributes = {}, &block)
|
488
|
+
div.id(id).attributes attributes, &block
|
489
|
+
end
|
490
|
+
end
|
491
|
+
|
492
|
+
MyBuilder.new.go_in do
|
493
|
+
div.content!.with do
|
494
|
+
span.secret!.class('left').hide!
|
495
|
+
component('component-1') do
|
496
|
+
strong 'something'
|
497
|
+
end
|
498
|
+
simple_component 'component-1'
|
499
|
+
end
|
500
|
+
end.to_html
|
501
|
+
|
502
|
+
returns
|
503
|
+
|
504
|
+
!!!html
|
505
|
+
<div id="content">
|
506
|
+
<span id="secret" class="left hidden"></span>
|
507
|
+
<div id="component-1" class="component">
|
508
|
+
<strong>something</strong>
|
509
|
+
</div>
|
510
|
+
<div id="component-1"></div>
|
511
|
+
</div>
|
512
|
+
|
513
|
+
## Benchmarks
|
514
|
+
|
515
|
+
### Synthetic
|
516
|
+
|
517
|
+
Benchmatk can be found on github. It renders simple page with two collections. 'reuse' means
|
518
|
+
that template is precompiled and reused during benchmark.
|
519
|
+
|
520
|
+
user system total real
|
521
|
+
tenjin-reuse 2.040000 0.000000 2.040000 ( 2.055140)
|
522
|
+
Htmless::Standard 2.520000 0.000000 2.520000 ( 2.519284)
|
523
|
+
fasterubis-reuse 2.580000 0.000000 2.580000 ( 2.581407)
|
524
|
+
erubis-reuse 2.680000 0.000000 2.680000 ( 2.690176)
|
525
|
+
Htmless::Formatted 2.780000 0.000000 2.780000 ( 2.794307)
|
526
|
+
erubis 5.180000 0.000000 5.180000 ( 5.183333)
|
527
|
+
fasterubis 5.210000 0.000000 5.210000 ( 5.219176)
|
528
|
+
tenjin 7.650000 0.160000 7.810000 ( 7.820490)
|
529
|
+
erector 9.450000 0.010000 9.460000 ( 9.471654)
|
530
|
+
markaby 14.300000 0.000000 14.300000 ( 14.318844)
|
531
|
+
tagz 33.430000 0.000000 33.430000 ( 33.483693)
|
532
|
+
|
533
|
+
### Rails 3
|
534
|
+
|
535
|
+
Benchmatk can be found on github.
|
536
|
+
Single page with or without a partial which is rendered 200 times. Partials make no diffrence for Htmless.
|
537
|
+
|
538
|
+
BenchTest#test_erubis_partials (3.34 sec warmup)
|
539
|
+
wall_time: 3.56 sec
|
540
|
+
gc_runs: 15
|
541
|
+
gc_time: 0.53 ms
|
542
|
+
BenchTest#test_erubis_single (552 ms warmup)
|
543
|
+
wall_time: 544 ms
|
544
|
+
gc_runs: 4
|
545
|
+
gc_time: 0.12 ms
|
546
|
+
BenchTest#test_htmless (2.33 sec warmup)
|
547
|
+
wall_time: 847 ms
|
548
|
+
gc_runs: 5
|
549
|
+
gc_time: 0.17 ms
|
550
|
+
BenchTest#test_tenjin_partial (942 ms warmup)
|
551
|
+
wall_time: 1.21 sec
|
552
|
+
gc_runs: 7
|
553
|
+
gc_time: 0.25 ms
|
554
|
+
BenchTest#test_tenjin_single (531 ms warmup)
|
555
|
+
wall_time: 532 ms
|
556
|
+
gc_runs: 6
|
557
|
+
gc_time: 0.20 ms
|
558
|
+
|
559
|
+
## Why is it fast?
|
560
|
+
|
561
|
+
* Optimalization of garbage collecting.
|
562
|
+
* 10-15% improvment.
|
563
|
+
* Preinicialization (tag's instances, even strings).
|
564
|
+
* No string's `#+`, `#{}`. Just `#<<` to buffer.
|
565
|
+
* Precomputed spaces for indentation.
|
566
|
+
* Doing as less as posible when rendering.
|
567
|
+
* Magic by metaprograming not by `method_missing`. Magic is run on inicialization not when rendering.
|
568
|
+
* Number of micro optimalization.
|
569
|
+
* Data in constants or instance variables.
|
570
|
+
* Buffer.
|
571
|
+
* No `#define_method`.
|
572
|
+
* Method inlining.
|
573
|
+
* Probably no real effect :)
|
574
|
+
|
575
|
+
## Future plans
|
576
|
+
|
577
|
+
* compile rendering methods on objects to javascript which will render html form json
|
578
|
+
* Helpers for Sinatra, Rails 3
|
579
|
+
* Helpers for fragment caching
|
580
|
+
|
581
|
+
## Why use it?
|
582
|
+
|
583
|
+
* Its fast
|
584
|
+
* You can use inheritance (imposible with templates) and other goodness of Ruby
|
585
|
+
* You can use pure Ruby to write the html
|
data/lib/copy_to_doc.rb
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
# This copies insides of dynamic classes into doc.rb for documenting purposes only
|
2
|
+
|
3
|
+
require 'pp'
|
4
|
+
root = File.expand_path File.dirname(__FILE__)
|
5
|
+
$: << root
|
6
|
+
require "#{root}/htmless"
|
7
|
+
|
8
|
+
File.open "#{root}/htmless/doc.rb", 'w' do |out|
|
9
|
+
out.write "module Htmless\n"
|
10
|
+
out.write " module StubBuilderForDocumentation\n"
|
11
|
+
|
12
|
+
files = ["#{root}/htmless/abstract/abstract_tag.rb",
|
13
|
+
"#{root}/htmless/abstract/abstract_single_tag.rb",
|
14
|
+
"#{root}/htmless/abstract/abstract_double_tag.rb"]
|
15
|
+
files.each do |file_path|
|
16
|
+
source = File.open(file_path, 'r') { |f| f.read }
|
17
|
+
source.scan(/def_class\s+(:\w+)(|,\s*(:\w+))\s+do\s+###import(([^#]|#[^#]|##[^#])*)end\s+###import/m) do |match|
|
18
|
+
klass = match[0][1..-1]
|
19
|
+
parent = match[2] ? match[2][1..-1] : nil
|
20
|
+
content = match[3]
|
21
|
+
|
22
|
+
#content = content.lines.delete_if { |l| l =~ /\#\#\# remove/ }.join("\n")
|
23
|
+
|
24
|
+
out << " class #{klass}"
|
25
|
+
out << " < #{parent}" if parent
|
26
|
+
out << "\n"
|
27
|
+
out << content
|
28
|
+
out << " end\n"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
#out << " class AbstractTag\n" ff
|
33
|
+
#HammerBuilder::GLOBAL_ATTRIBUTES.each do |attr|
|
34
|
+
# out << " \#@method #{attr}(value)\n"
|
35
|
+
# out << " attribute :#{attr}\n"
|
36
|
+
#end
|
37
|
+
#out << " end\n"
|
38
|
+
|
39
|
+
out << " end\n"
|
40
|
+
out << "end\n"
|
41
|
+
end
|
42
|
+
|
43
|
+
|