tubby 1.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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +6 -0
- data/LICENSE.md +12 -0
- data/README.md +393 -0
- data/lib/tubby.rb +137 -0
- metadata +48 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA1:
|
|
3
|
+
metadata.gz: 23fa67b928c22ddfe8a88d2a31d9aec059babbb7
|
|
4
|
+
data.tar.gz: c04266360faf36cc54e517b264f978c61a47867b
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: a747e3bb0a99977634b781d7892c129194e931a3b3de8d133088d74a9e542be7f5fc4308c7151e90d8ab7774d709eae34e129783540fc97366debcbad3547b5f
|
|
7
|
+
data.tar.gz: 61e61741fee0eb09789521f77098c64ef4f4e12172da9a5aa48088fa09e120346e4849dba23778c9fd54eb157455d2168be4fbf89ea07a194f1cca546ed3d39c
|
data/CHANGELOG.md
ADDED
data/LICENSE.md
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
Copyright (C) 2018 Magnus Holm <judofyr@gmail.com>
|
|
2
|
+
|
|
3
|
+
Permission to use, copy, modify, and/or distribute this software for any
|
|
4
|
+
purpose with or without fee is hereby granted.
|
|
5
|
+
|
|
6
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
|
7
|
+
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
|
8
|
+
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
|
9
|
+
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
|
10
|
+
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
|
11
|
+
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
12
|
+
PERFORMANCE OF THIS SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,393 @@
|
|
|
1
|
+
# Tubby: Tags in Ruby
|
|
2
|
+
|
|
3
|
+
Tubby is a lightweight library for writing HTML components in plain Ruby.
|
|
4
|
+
|
|
5
|
+
```ruby
|
|
6
|
+
tmpl = Tubby.new { |t|
|
|
7
|
+
t.doctype!
|
|
8
|
+
|
|
9
|
+
t.h1("Hello #{current_user}!")
|
|
10
|
+
|
|
11
|
+
t << Avatar.new(current_user)
|
|
12
|
+
|
|
13
|
+
t.ul {
|
|
14
|
+
t.li("Tinky Winky")
|
|
15
|
+
t.li("Dipsy", class: "active")
|
|
16
|
+
t.li("Laa-Laa")
|
|
17
|
+
t.li("Po")
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
class Avatar
|
|
22
|
+
def initialize(user)
|
|
23
|
+
@user = user
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def url
|
|
27
|
+
# Calculate URL
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def to_tubby
|
|
31
|
+
Tubby.new { |t|
|
|
32
|
+
t.div(class: "avatar") {
|
|
33
|
+
t.img(src: url)
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
puts tmpl.to_s
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Table of contents:
|
|
43
|
+
|
|
44
|
+
- [Basic usage](#basic-usage)
|
|
45
|
+
- [Advanced usage](#advanced-usage)
|
|
46
|
+
- [Versioning](#versioning)
|
|
47
|
+
- [License](#license)
|
|
48
|
+
|
|
49
|
+
## Basic usage
|
|
50
|
+
|
|
51
|
+
### Creating templates
|
|
52
|
+
|
|
53
|
+
`Tubby.new` accepts a block and returns a `Tubby::Template`:
|
|
54
|
+
|
|
55
|
+
```ruby
|
|
56
|
+
tmpl = Tubby.new { |t|
|
|
57
|
+
# content inside here
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
The block will be executed once you call `#to_s` (or its alias `#to_html`):
|
|
62
|
+
|
|
63
|
+
```ruby
|
|
64
|
+
puts tmpl.to_s
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Writing HTML tags
|
|
68
|
+
|
|
69
|
+
The following forms are available for writing HTML inside a template:
|
|
70
|
+
|
|
71
|
+
```ruby
|
|
72
|
+
# Empty tag
|
|
73
|
+
t.h1
|
|
74
|
+
# => <h1></h1>
|
|
75
|
+
|
|
76
|
+
# Tag with content
|
|
77
|
+
t.h1("Welcome!")
|
|
78
|
+
# => <h1>Welcome!</h1>
|
|
79
|
+
|
|
80
|
+
# Tag with attributes
|
|
81
|
+
t.h1(class: "big")
|
|
82
|
+
# => <h1 class="big"></h1>
|
|
83
|
+
|
|
84
|
+
# Tag with attributes and content
|
|
85
|
+
t.h1("Welcome!", class: "big")
|
|
86
|
+
# => <h1 class="big">Welcome!</h1>
|
|
87
|
+
|
|
88
|
+
# Tag with block content
|
|
89
|
+
t.h1 {
|
|
90
|
+
t.span("Hello")
|
|
91
|
+
}
|
|
92
|
+
# => <h1><span>Hello</span></h1>
|
|
93
|
+
|
|
94
|
+
# Tag with block content and attributes
|
|
95
|
+
t.h1(class: "big") {
|
|
96
|
+
t.span("Hello")
|
|
97
|
+
}
|
|
98
|
+
# => <h1 class="big"><span>Hello</span></h1>
|
|
99
|
+
|
|
100
|
+
# Tag with block content, attributes and content
|
|
101
|
+
t.h1("Hello ", class: "big") {
|
|
102
|
+
t.span("world!")
|
|
103
|
+
}
|
|
104
|
+
# => <h1 class="big">Hello <span>world!</span></h1>
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
It's recommended to use `{ }`for nesting of tags and `do/end` for nesting of
|
|
108
|
+
control flow. At first it looks weird, but otherwise it becomes hard to
|
|
109
|
+
visualize the control flow:
|
|
110
|
+
|
|
111
|
+
```ruby
|
|
112
|
+
t.ul {
|
|
113
|
+
users.each do |user|
|
|
114
|
+
t.li {
|
|
115
|
+
t.a(user.name, href: user_path(user))
|
|
116
|
+
}
|
|
117
|
+
end
|
|
118
|
+
}
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### Writing attributes
|
|
122
|
+
|
|
123
|
+
Tubby supports various ways of writing attributes:
|
|
124
|
+
|
|
125
|
+
```ruby
|
|
126
|
+
# Plain attribute
|
|
127
|
+
t.input(value: "hello")
|
|
128
|
+
# => <input value="hello">
|
|
129
|
+
|
|
130
|
+
# nil/false values ignores the attribute
|
|
131
|
+
t.input(value: nil)
|
|
132
|
+
# => <input>
|
|
133
|
+
|
|
134
|
+
# A true value doesn't generate a value
|
|
135
|
+
t.input(checked: true)
|
|
136
|
+
# => <input checked>
|
|
137
|
+
|
|
138
|
+
# An array will be space-joined
|
|
139
|
+
t.input(class: ["form-control", "error"])
|
|
140
|
+
# => <input class="form-control error">
|
|
141
|
+
|
|
142
|
+
# ... but nil values are ignored
|
|
143
|
+
t.input(class: ["form-control", ("error" if error)])
|
|
144
|
+
# => <input class="form-control">
|
|
145
|
+
# => <input class="form-control error">
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### Writing plain text
|
|
149
|
+
|
|
150
|
+
Inside a template you can use `<<` to append text:
|
|
151
|
+
|
|
152
|
+
```ruby
|
|
153
|
+
t.h1 {
|
|
154
|
+
t << "Hello "
|
|
155
|
+
t.strong("world")
|
|
156
|
+
t << "!"
|
|
157
|
+
}
|
|
158
|
+
# => <h1>Hello <strong>world</strong>!</h1>
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
By default, `#to_s` will be called and the value will be escaped:
|
|
162
|
+
|
|
163
|
+
```ruby
|
|
164
|
+
t.h1 {
|
|
165
|
+
t << "Hello & world"
|
|
166
|
+
}
|
|
167
|
+
# => <h1>Hello & world</h1>
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
There are three ways to avoid escaping:
|
|
171
|
+
|
|
172
|
+
```ruby
|
|
173
|
+
class Other
|
|
174
|
+
def to_html
|
|
175
|
+
"<custom>"
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
# (1) Appending an object which implements #to_html. Tubby will call the method
|
|
180
|
+
# and append the result without escaping it
|
|
181
|
+
t << Other.new
|
|
182
|
+
|
|
183
|
+
# (2) If you're using Rails, html_safe? is respected
|
|
184
|
+
t << "<custom>".html_safe!
|
|
185
|
+
|
|
186
|
+
# (3) There's also a separate helper
|
|
187
|
+
t.raw!("<custom>")
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
In addition, there's a helper for writing a HTML5 doctype:
|
|
191
|
+
|
|
192
|
+
```ruby
|
|
193
|
+
t.doctype!
|
|
194
|
+
# => <!DOCTYPE html>
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
### Appending other templates
|
|
198
|
+
|
|
199
|
+
You can also append another template:
|
|
200
|
+
|
|
201
|
+
```ruby
|
|
202
|
+
content = Tubby.new { |t|
|
|
203
|
+
t.h1("Users")
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
main = Tubby.new { |t|
|
|
207
|
+
t.doctype!
|
|
208
|
+
t.head {
|
|
209
|
+
t.title("My App")
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
t.body {
|
|
213
|
+
t << content
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
This is the main building block for creating composable templates.
|
|
219
|
+
|
|
220
|
+
### Implementing `#to_tubby`
|
|
221
|
+
|
|
222
|
+
Before appending, Tubby will call the `#to_tubby` method if it exists:
|
|
223
|
+
|
|
224
|
+
```ruby
|
|
225
|
+
class Avatar
|
|
226
|
+
def initialize(user)
|
|
227
|
+
@user = user
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
def url
|
|
231
|
+
# Calculate URL
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
def to_tubby
|
|
235
|
+
Tubby.new { |t|
|
|
236
|
+
t.div(class: "avatar") {
|
|
237
|
+
t.img(src: url)
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
end
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
tmpl = Tubby.new { |t|
|
|
244
|
+
t << Avatar.new(user)
|
|
245
|
+
}
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
`#to_tubby` can return any value that `<<` accepts (i.e. strings that will be
|
|
249
|
+
escaped, objects that respond to `#to_html` and so on), but most of the time you
|
|
250
|
+
want to create a new template object.
|
|
251
|
+
|
|
252
|
+
## Advanced usage
|
|
253
|
+
|
|
254
|
+
The variable `t` in all of the examples above is an instance of
|
|
255
|
+
`Tubby::Renderer`. Calling `Tubby::Template#to_s` is a shortcut for the
|
|
256
|
+
following:
|
|
257
|
+
|
|
258
|
+
```ruby
|
|
259
|
+
tmpl = Tubby.new { |t|
|
|
260
|
+
# content inside here
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
# This:
|
|
264
|
+
puts tmpl.to_s
|
|
265
|
+
|
|
266
|
+
# ... is the same as:
|
|
267
|
+
target = String.new
|
|
268
|
+
t = Tubby::Renderer.new(target)
|
|
269
|
+
t << tmpl
|
|
270
|
+
puts target
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
Let's look at two ways we can customize Tubby.
|
|
274
|
+
|
|
275
|
+
### Custom target
|
|
276
|
+
|
|
277
|
+
The target object doesn't have to be a String, it must only be an object which
|
|
278
|
+
responds to `<<`. Using a custom target might be useful if you want stream the
|
|
279
|
+
HTML directly into a socket/file. For instance, this will print the HTML out to
|
|
280
|
+
the standard output:
|
|
281
|
+
|
|
282
|
+
```ruby
|
|
283
|
+
tmpl = Tubby.new { |t|
|
|
284
|
+
t.h1("Hello terminal!")
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
t = Tubby::Renderer.new($stdout)
|
|
288
|
+
t << tmpl
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
### Custom renderer
|
|
292
|
+
|
|
293
|
+
You are also free to subclass the Renderer to provide additional helpers/data:
|
|
294
|
+
|
|
295
|
+
```ruby
|
|
296
|
+
tmpl = Tubby.new { |t|
|
|
297
|
+
t.post_form(action: t.login_path) {
|
|
298
|
+
t.input(name: "username")
|
|
299
|
+
t.input(type: "password", name: "password")
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
class Renderer < Tubby::Renderer
|
|
304
|
+
include URLHelpers
|
|
305
|
+
|
|
306
|
+
attr_accessor :csrf_token
|
|
307
|
+
|
|
308
|
+
# Renders a <form>-tag with the csrf_token
|
|
309
|
+
def post_form(**opts)
|
|
310
|
+
form(method: "post", **opts) {
|
|
311
|
+
input(type: "hidden", name: "csrf_token", value: csrf_token)
|
|
312
|
+
yield
|
|
313
|
+
}
|
|
314
|
+
end
|
|
315
|
+
end
|
|
316
|
+
|
|
317
|
+
target = String.new
|
|
318
|
+
t = Renderer.new(target)
|
|
319
|
+
t.csrf_token = "hello"
|
|
320
|
+
t << tmpl
|
|
321
|
+
puts target
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
You should use this feature with care as it makes your components coupled to the
|
|
325
|
+
data you provide. For instance, it might be tempting to have access to the Rack
|
|
326
|
+
environment as `t.rack_env`, but this means you can no longer render any HTML
|
|
327
|
+
outside of a Rack context (e.g: generating email). For CSRF token it makes
|
|
328
|
+
sense: it's a value which is global for the whole page, you might need it deeply
|
|
329
|
+
nested inside a component, and it's a hassle to pass it along.
|
|
330
|
+
|
|
331
|
+
In general however you should prefer separate classes over custom renderer methods:
|
|
332
|
+
|
|
333
|
+
```ruby
|
|
334
|
+
# Do this:
|
|
335
|
+
|
|
336
|
+
class OkCancel
|
|
337
|
+
def initialize(cancel_link:)
|
|
338
|
+
@cancel_link = cancel_link
|
|
339
|
+
end
|
|
340
|
+
|
|
341
|
+
def to_tubby
|
|
342
|
+
Tubby.new { |t|
|
|
343
|
+
t.div(class: "btn-group") {
|
|
344
|
+
t.button("Save", class: "btn", type: "submit")
|
|
345
|
+
t.a("Cancel", class: "btn", href: @cancel_link)
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
end
|
|
349
|
+
end
|
|
350
|
+
|
|
351
|
+
tmpl = Tubby.new { |t|
|
|
352
|
+
t << OkCancel.new(cancel_link: "/users")
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
# Don't do this:
|
|
356
|
+
|
|
357
|
+
class Renderer < Tubby::Renderer
|
|
358
|
+
def ok_cancel(cancel_link:)
|
|
359
|
+
Tubby.new { |t|
|
|
360
|
+
t.div(class: "btn-group") {
|
|
361
|
+
t.button("Save", class: "btn", type: "submit")
|
|
362
|
+
t.a("Cancel", class: "btn", href: cancel_link)
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
end
|
|
366
|
+
end
|
|
367
|
+
|
|
368
|
+
tmpl = Tubby.new { |t|
|
|
369
|
+
t.ok_cancel(cancel_link: "/users")
|
|
370
|
+
}
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
## Versioning
|
|
374
|
+
|
|
375
|
+
Tubby uses version numbers on the form MAJOR.MINOR, and releases are backwards
|
|
376
|
+
compatible with earlier releases with the same MAJOR version.
|
|
377
|
+
|
|
378
|
+
## License
|
|
379
|
+
|
|
380
|
+
Tubby is is available under the 0BSD license:
|
|
381
|
+
|
|
382
|
+
> Copyright (C) 2018 Magnus Holm <judofyr@gmail.com>
|
|
383
|
+
>
|
|
384
|
+
> Permission to use, copy, modify, and/or distribute this software for any
|
|
385
|
+
> purpose with or without fee is hereby granted.
|
|
386
|
+
>
|
|
387
|
+
> THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
|
388
|
+
> REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
|
389
|
+
> AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
|
390
|
+
> INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
|
391
|
+
> LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
|
392
|
+
> OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
393
|
+
> PERFORMANCE OF THIS SOFTWARE.
|
data/lib/tubby.rb
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
require "cgi"
|
|
3
|
+
|
|
4
|
+
module Tubby
|
|
5
|
+
def self.new(&blk)
|
|
6
|
+
Template.new(&blk)
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
class Template
|
|
10
|
+
def initialize(&blk)
|
|
11
|
+
@blk = blk
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def to_s
|
|
15
|
+
target = String.new
|
|
16
|
+
renderer = Renderer.new(target)
|
|
17
|
+
render_with(renderer)
|
|
18
|
+
target
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def to_html
|
|
22
|
+
to_s
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def render_with(renderer)
|
|
26
|
+
@blk.call(renderer)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
class Renderer
|
|
31
|
+
def initialize(target)
|
|
32
|
+
@target = target
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def <<(obj)
|
|
36
|
+
obj = obj.to_tubby if obj.respond_to?(:to_tubby)
|
|
37
|
+
if obj.is_a?(Tubby::Template)
|
|
38
|
+
obj.render_with(self)
|
|
39
|
+
elsif obj.respond_to?(:to_html)
|
|
40
|
+
@target << obj.to_html
|
|
41
|
+
elsif obj.respond_to?(:html_safe?) && obj.html_safe?
|
|
42
|
+
@target << obj
|
|
43
|
+
else
|
|
44
|
+
@target << CGI.escape_html(obj.to_s)
|
|
45
|
+
end
|
|
46
|
+
self
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def raw!(text)
|
|
50
|
+
@target << text.to_s
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def doctype!
|
|
54
|
+
@target << "<!DOCTYPE html>"
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def __attrs!(attrs)
|
|
58
|
+
attrs.each do |key, value|
|
|
59
|
+
if value.is_a?(Array)
|
|
60
|
+
value = value.compact.join(" ")
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
if value
|
|
64
|
+
key = key.to_s.tr("_", "-")
|
|
65
|
+
|
|
66
|
+
if value == true
|
|
67
|
+
@target << " #{key}"
|
|
68
|
+
else
|
|
69
|
+
value = CGI.escape_html(value.to_s)
|
|
70
|
+
@target << " #{key}=\"#{value}\""
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def tag!(name, content = nil, **attrs)
|
|
77
|
+
@target << "<" << name
|
|
78
|
+
__attrs!(attrs)
|
|
79
|
+
@target << ">"
|
|
80
|
+
self << content if content
|
|
81
|
+
yield if block_given?
|
|
82
|
+
@target << "</" << name << ">"
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def self_closing_tag!(name, **attrs)
|
|
86
|
+
@target << "<" << name
|
|
87
|
+
__attrs!(attrs)
|
|
88
|
+
@target << ">"
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
TAGS = %w[
|
|
92
|
+
a abbr acronym address applet article aside audio b basefont bdi bdo big
|
|
93
|
+
blockquote body button canvas caption center cite code colgroup datalist
|
|
94
|
+
dd del details dfn dir div dl dt em fieldset figcaption figure font footer
|
|
95
|
+
form frame frameset h1 h2 h3 h4 h5 h6 head header hgroup html i iframe ins
|
|
96
|
+
kbd label legend li map mark math menu meter nav
|
|
97
|
+
object ol optgroup option output p pre progress q rp rt ruby s samp
|
|
98
|
+
section select small span strike strong style sub summary sup svg table
|
|
99
|
+
tbody td textarea tfoot th thead time title tr tt u ul var video xmp
|
|
100
|
+
]
|
|
101
|
+
|
|
102
|
+
SELF_CLOSING_TAGS = %w[
|
|
103
|
+
base link meta hr br wbr img embed param source track area col input
|
|
104
|
+
keygen command
|
|
105
|
+
]
|
|
106
|
+
|
|
107
|
+
TAGS.each do |name|
|
|
108
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
|
109
|
+
def #{name}(content = nil, **attrs, &blk)
|
|
110
|
+
tag!(#{name.inspect}, content, attrs, &blk)
|
|
111
|
+
end
|
|
112
|
+
RUBY
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
SELF_CLOSING_TAGS.each do |name|
|
|
116
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
|
117
|
+
def #{name}(**attrs)
|
|
118
|
+
self_closing_tag!(#{name.inspect}, attrs)
|
|
119
|
+
end
|
|
120
|
+
RUBY
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def script(content = nil, **attrs)
|
|
124
|
+
@target << "<script"
|
|
125
|
+
__attrs!(attrs)
|
|
126
|
+
@target << ">"
|
|
127
|
+
if content
|
|
128
|
+
if content =~ /<(!--|script|\/script)/
|
|
129
|
+
raise "script tags can not contain #$&"
|
|
130
|
+
end
|
|
131
|
+
@target << content
|
|
132
|
+
end
|
|
133
|
+
@target << "</script>"
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
|
metadata
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: tubby
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: '1.0'
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Magnus Holm
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2018-12-25 00:00:00.000000000 Z
|
|
12
|
+
dependencies: []
|
|
13
|
+
description:
|
|
14
|
+
email:
|
|
15
|
+
- judofyr@gmail.com
|
|
16
|
+
executables: []
|
|
17
|
+
extensions: []
|
|
18
|
+
extra_rdoc_files: []
|
|
19
|
+
files:
|
|
20
|
+
- CHANGELOG.md
|
|
21
|
+
- LICENSE.md
|
|
22
|
+
- README.md
|
|
23
|
+
- lib/tubby.rb
|
|
24
|
+
homepage: https://github.com/judofyr/tubby
|
|
25
|
+
licenses:
|
|
26
|
+
- 0BSD
|
|
27
|
+
metadata: {}
|
|
28
|
+
post_install_message:
|
|
29
|
+
rdoc_options: []
|
|
30
|
+
require_paths:
|
|
31
|
+
- lib
|
|
32
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
33
|
+
requirements:
|
|
34
|
+
- - ">="
|
|
35
|
+
- !ruby/object:Gem::Version
|
|
36
|
+
version: '0'
|
|
37
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
38
|
+
requirements:
|
|
39
|
+
- - ">="
|
|
40
|
+
- !ruby/object:Gem::Version
|
|
41
|
+
version: '0'
|
|
42
|
+
requirements: []
|
|
43
|
+
rubyforge_project:
|
|
44
|
+
rubygems_version: 2.6.11
|
|
45
|
+
signing_key:
|
|
46
|
+
specification_version: 4
|
|
47
|
+
summary: HTML templates as Ruby
|
|
48
|
+
test_files: []
|