hermeneutics 1.8

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.
@@ -0,0 +1,462 @@
1
+ #
2
+ # hermeneutics/html.rb -- smart HTML generation
3
+ #
4
+
5
+ require "hermeneutics/escape"
6
+ require "hermeneutics/contents"
7
+
8
+
9
+ module Hermeneutics
10
+
11
+ # = Example
12
+ #
13
+ # require "hermeneutics/color"
14
+ # require "hermeneutics/html"
15
+ #
16
+ # class MyHtml < Hermeneutics::Html
17
+ # def build
18
+ # html {
19
+ # head {
20
+ # title { "Example" }
21
+ # comment "created as an example, #{Time.now}"
22
+ # }
23
+ # body( :bgcolor => Hermeneutics::Color.from_s( "ffffef")) {
24
+ # h1 {
25
+ # pcdata "Ruby "
26
+ # a( :href => "www.w3.org") { "Html" }
27
+ # _ { " example" }
28
+ # }
29
+ # p { "Some text.\nBye." }
30
+ # p {
31
+ # self << "link"
32
+ # br
33
+ # a( :href => "www.w3.org") { "Html" }
34
+ # }
35
+ # }
36
+ # }
37
+ # end
38
+ # end
39
+ #
40
+ # Hermeneutics::Html.document
41
+ #
42
+ class Html
43
+
44
+ class <<self
45
+ attr_accessor :main
46
+ def inherited cls
47
+ Html.main = cls
48
+ end
49
+ def open out = nil
50
+ i = (@main||self).new
51
+ i.generate out do
52
+ yield i
53
+ end
54
+ end
55
+ def document *args, &block
56
+ open do |i|
57
+ i.document *args, &block
58
+ end
59
+ end
60
+ def write_file name = nil
61
+ name ||= (File.basename $0, ".rb") + ".html"
62
+ File.open name, "w" do |f|
63
+ open f do |i|
64
+ if block_given? then
65
+ yield i
66
+ else
67
+ i.document
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
73
+
74
+ def generate out = nil
75
+ g = @generator
76
+ begin
77
+ @generator = Generator.new out||$stdout
78
+ yield
79
+ ensure
80
+ @generator = g
81
+ end
82
+ end
83
+
84
+ def document *args, &block
85
+ doctype_header
86
+ build *args, &block
87
+ end
88
+
89
+ def doctype_header
90
+ @generator.doctype "html"
91
+ end
92
+
93
+ def build
94
+ html { body { h1 { "It works." } } }
95
+ end
96
+
97
+
98
+ def language
99
+ if ENV[ "LANG"] =~ /\A\w{2,}/ then
100
+ r = $&
101
+ r.gsub! /_/, "-"
102
+ r
103
+ end
104
+ end
105
+
106
+
107
+ class Generator
108
+ attr_accessor :close_standalone, :assign_attributes, :cdata_block
109
+ def initialize out
110
+ @out = out
111
+ @ent = Entities.new
112
+ @nl, @ind = true, [ ""]
113
+ end
114
+ def encoding
115
+ case @out
116
+ when IO then @out.external_encoding||Encoding.default_external
117
+ else @out.encoding
118
+ end
119
+ end
120
+ def file_path
121
+ @out.path
122
+ rescue NoMethodError
123
+ end
124
+ def plain str
125
+ do_ind
126
+ @out << (@ent.encode str)
127
+ end
128
+ # nls
129
+ # 0 = no newline
130
+ # 1 = newline after
131
+ # 2 = newline after both
132
+ # 3 = and advance indent
133
+ # 4 = Block without any indent
134
+ def tag tag, type, attrs = nil
135
+ tag = tag.to_s
136
+ nls = type & 0xf
137
+ if (type & 0x10).nonzero? then
138
+ brace nls>0 do
139
+ @out << tag
140
+ mkattrs attrs
141
+ @out << " /" if @close_standalone
142
+ end
143
+ else
144
+ begin
145
+ brk if nls>1
146
+ brace nls>1 do
147
+ @out << tag
148
+ mkattrs attrs
149
+ end
150
+ if nls >3 then
151
+ verbose_block yield
152
+ else
153
+ indent_if nls>2 do
154
+ if block_given? then
155
+ r = yield
156
+ plain r if String === r
157
+ end
158
+ end
159
+ end
160
+ ensure
161
+ brk if nls>1
162
+ brace nls>0 do
163
+ @out << "/" << tag
164
+ end
165
+ end
166
+ end
167
+ nil
168
+ end
169
+ # Processing Instruction
170
+ def pi_tag tag, attrs = nil
171
+ tag = tag.to_s
172
+ brace true do
173
+ begin
174
+ @out << "?" << tag
175
+ mkattrs attrs
176
+ ensure
177
+ @out << " ?"
178
+ end
179
+ end
180
+ end
181
+ def doctype *args
182
+ brace true do
183
+ @out << "!DOCTYPE"
184
+ args.each { |x|
185
+ @out << " "
186
+ if x =~ /\W/ then
187
+ @out << '"' << (@ent.encode x) << '"'
188
+ else
189
+ @out << x
190
+ end
191
+ }
192
+ end
193
+ end
194
+ def comment str
195
+ if str =~ /\A.*\z/ then
196
+ brace_comment do
197
+ @out << " " << str << " "
198
+ end
199
+ else
200
+ brace_comment do
201
+ brk
202
+ out_brk str
203
+ do_ind
204
+ end
205
+ end
206
+ end
207
+ def verbose_block str
208
+ if @cdata_block then
209
+ @out << "/* "
210
+ brace false do
211
+ @out << "![CDATA["
212
+ @out << " */" << $/
213
+ @out << str
214
+ @out << $/ << "/* "
215
+ @out << "]]"
216
+ end
217
+ @out << " */"
218
+ else
219
+ out_brk str
220
+ end
221
+ end
222
+ private
223
+ def brk
224
+ unless @nl then
225
+ @nl = true
226
+ @out << $/
227
+ end
228
+ end
229
+ def out_brk str
230
+ @out << str
231
+ @nl = str !~ /.\z/
232
+ brk
233
+ end
234
+ def do_ind
235
+ if @nl then
236
+ @out << @ind.last
237
+ @nl = false
238
+ end
239
+ end
240
+ def brace nl
241
+ do_ind
242
+ @out << "<"
243
+ yield
244
+ nil
245
+ ensure
246
+ @out << ">"
247
+ brk if nl
248
+ end
249
+ def brace_comment
250
+ brace true do
251
+ @out << "!--"
252
+ yield
253
+ @out << "--"
254
+ end
255
+ end
256
+ def indent_if flag
257
+ if flag then
258
+ indent do yield end
259
+ else
260
+ yield
261
+ end
262
+ end
263
+ INDENT = 2
264
+ def indent
265
+ @ind.push @ind.last + " "*INDENT
266
+ yield
267
+ ensure
268
+ @ind.pop
269
+ end
270
+ def mkattrs attrs
271
+ attrs or return
272
+ attrs.each { |k,v|
273
+ if Symbol === k then k = k.new_string ; k.gsub! /_/, "-" end
274
+ v = case v
275
+ when Array then v.compact.join " "
276
+ when true then k.to_s if @assign_attributes
277
+ when nil, false then next
278
+ else v.to_s
279
+ end
280
+ @out << " " << k
281
+ @out << "=\"" << (@ent.encode v) << "\"" if v.notempty?
282
+ }
283
+ end
284
+ end
285
+
286
+ def file_path ; @generator.file_path ; end
287
+
288
+ NBSP = Entities::NAMES[ "nbsp"]
289
+
290
+ TAGS = {
291
+ a:0, abbr:0, address:1, article:3, aside:3, audio:3, b:0, bdi:0, bdo:2,
292
+ blockquote:3, body:2, button:3, canvas:1, caption:1, cite:0, code:0,
293
+ colgroup:3, data:0, datalist:3, dd:1, del:0, details:3, dfn:0, dialog:0,
294
+ div:3, dl:3, dt:1, em:0, fieldset:3, figcaption:1, figure:3, footer:3,
295
+ form:3, h1:1, h2:1, h3:1, h4:1, h5:1, h6:1, head:3, header:3, html:2,
296
+ i:0, iframe:3, ins:0, kbd:0, label:0, legend:1, li:1, main:3, map:3,
297
+ mark:0, meter:0, nav:3, noscript:3, object:3, ol:3, optgroup:3,
298
+ option:1, output:0, p:2, picture:3, pre:1, progress:0, q:0, rp:0, rt:0,
299
+ ruby:2, s:0, samp:0, section:3, select:3, small:0, span:0, strong:0,
300
+ sub:0, summary:1, sup:0, svg:3, table:3, tbody:3, td:1, template:3,
301
+ textarea:1, tfoot:3, th:1, thead:3, time:0, title:1, tr:3, u:0, ul:3,
302
+ var:0, video:3,
303
+
304
+ # tags containing foreign code blocks
305
+ script:4, style:4,
306
+
307
+ # void tags
308
+ area:0x11, base:0x11, br:0x11, col:0x11, embed:0x11, hr:0x11, img:0x10,
309
+ input:0x10, keygen:0x11, link:0x11, meta:0x11, param:0x11, source:0x11,
310
+ track:0x11, wbr:0x10,
311
+ }
312
+
313
+ # remove Kernel methods of the same name: :p, :select, :sub
314
+ (TAGS.keys & (private_instance_methods +
315
+ protected_instance_methods +
316
+ instance_methods)).each { |m| undef_method m }
317
+
318
+ def tag? name
319
+ TAGS[ name]
320
+ end
321
+
322
+ def method_missing name, *args, &block
323
+ t = tag? name
324
+ t or super
325
+ if String === args.last then
326
+ b = args.pop
327
+ @generator.tag name, t, *args do b end
328
+ else
329
+ @generator.tag name, t, *args, &block
330
+ end
331
+ end
332
+
333
+ def pcdata *strs
334
+ strs.each { |s|
335
+ s or next
336
+ @generator.plain s
337
+ }
338
+ nil
339
+ end
340
+
341
+ def << str
342
+ @generator.plain str if str
343
+ self
344
+ end
345
+
346
+ def _ str = nil
347
+ @generator.plain str||yield
348
+ nil
349
+ end
350
+
351
+ def comment str
352
+ @generator.comment str
353
+ end
354
+
355
+ def javascript str = nil, &block
356
+ mime = { type: "text/javascript" }
357
+ script mime, str, &block
358
+ end
359
+
360
+ def html **attrs
361
+ attrs[ :"lang"] ||= language
362
+ method_missing :html, **attrs do yield end
363
+ end
364
+
365
+ def head attrs = nil
366
+ method_missing :head, attrs do
367
+ meta charset: @generator.encoding
368
+ yield
369
+ end
370
+ end
371
+
372
+ def form attrs, &block
373
+ attrs[ :method] ||= if attrs[ :enctype] == "multipart/form-data" then
374
+ "post"
375
+ else
376
+ "get"
377
+ end
378
+ @tabindex = 0
379
+ method_missing :form, attrs, &block
380
+ ensure
381
+ @tabindex = nil
382
+ end
383
+
384
+ Field = Struct[ :type, :attrs]
385
+
386
+ def field type, attrs
387
+ attrs[ :id] ||= attrs[ :name]
388
+ @tabindex += 1
389
+ attrs[ :tabindex] ||= @tabindex
390
+ Field[ type, attrs]
391
+ end
392
+
393
+ def label field = nil, attrs = nil, &block
394
+ if String === attrs then
395
+ label field do attrs end
396
+ else
397
+ if Field === field or attrs then
398
+ attrs = attrs.merge for: field.attrs[ :id]
399
+ else
400
+ attrs = field
401
+ end
402
+ method_missing :label, attrs, &block
403
+ end
404
+ end
405
+
406
+ def input arg, &block
407
+ if Field === arg then
408
+ case arg.type
409
+ when /select/i then
410
+ method_missing :select, arg.attrs, &block
411
+ when /textarea/i then
412
+ block and
413
+ raise ArgumentError, "Field textarea: use the value attribute."
414
+ v = arg.attrs.delete :value
415
+ method_missing :textarea, arg.attrs do v end
416
+ else
417
+ arg.attrs[ :type] ||= arg.type
418
+ method_missing :input, arg.attrs, &block
419
+ end
420
+ else
421
+ method_missing :input, arg, &block
422
+ end
423
+ end
424
+
425
+ end
426
+
427
+ class XHtml < Html
428
+
429
+ def html **attrs
430
+ attrs[ :xmlns] ||= "http://www.w3.org/1999/xhtml"
431
+ attrs[ :"xml:lang"] = language
432
+ attrs[ :lang] = ""
433
+ super
434
+ end
435
+
436
+ def a attrs = nil
437
+ attrs[ :name] ||= attrs[ :id] if attrs
438
+ super
439
+ end
440
+
441
+ private
442
+
443
+ def generate out
444
+ super do
445
+ @generator.close_standalone = true
446
+ @generator.assign_attributes = true
447
+ @generator.cdata_block = true
448
+ yield
449
+ end
450
+ end
451
+
452
+ def doctype_header
453
+ prop = { version: "1.0", encoding: @generator.encoding }
454
+ @generator.pi_tag :xml, prop
455
+ @generator.doctype "html", "PUBLIC", "-//W3C//DTD XHTML 1.1//EN",
456
+ "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"
457
+ end
458
+
459
+ end
460
+
461
+ end
462
+