htmless 0.4
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/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
|
+
|