dom 0.5.1 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (7) hide show
  1. checksums.yaml +4 -4
  2. data/CHART.html +429 -0
  3. data/MANUAL.html +414 -0
  4. data/lib/dom.rb +67 -49
  5. data/license +9 -0
  6. data/spec +387 -0
  7. metadata +15 -6
data/lib/dom.rb CHANGED
@@ -1,8 +1,55 @@
1
1
  # frozen_string_literal: true
2
+
3
+ # Copyright (c) 2016 sawa
4
+
2
5
  require "stringio"
3
6
  require "strscan"
4
7
  require "htmlentities"
5
8
 
9
+ class DomString < String; end
10
+
11
+ using (Module.new do
12
+ refine String do
13
+ AnsiColor = {
14
+ "1" => "bold",
15
+ "4" => "underline",
16
+ "30" => "black",
17
+ "31" => "red",
18
+ "32" => "green",
19
+ "33" => "yellow",
20
+ "34" => "blue",
21
+ "35" => "magenta",
22
+ "36" => "cyan",
23
+ "37" => "white",
24
+ "40" => "bg-black",
25
+ "41" => "bg-red",
26
+ "42" => "bg-green",
27
+ "43" => "bg-yellow",
28
+ "44" => "bg-blue",
29
+ "45" => "bg-magenta",
30
+ "46" => "bg-cyan",
31
+ "47" => "bg-white",
32
+ }
33
+ def dom_escape tag = nil
34
+ case tag; when :style, :script then self else Dom::Coder.encode(self) end
35
+ end
36
+ def _ansi2html
37
+ sc = StringScanner.new(self)
38
+ io = StringIO.new
39
+ io.print(
40
+ if sc.scan(/\e\[0?m/o) then '</span>'
41
+ elsif sc.scan(/\e\[0?(\d+)m/o) then '<span class="%s">' % AnsiColor[sc[1]]
42
+ end ||
43
+ sc.scan(/./mo)) until sc.eos?
44
+ io.string
45
+ end
46
+ end
47
+
48
+ refine DomString do
49
+ def dom_escape tag = nil; self end
50
+ end
51
+ end)
52
+
6
53
  class File
7
54
  def self.relativize f; f.sub(%r{\A/}o, "") end
8
55
  end
@@ -10,6 +57,7 @@ end
10
57
  module Dom
11
58
  Coder = HTMLEntities.new
12
59
  Indent = " "
60
+ private_constant :Indent
13
61
  def self.compact; singleton_class.class_eval{alias join join_compact} end
14
62
  def self.nested; singleton_class.class_eval{alias join join_nested} end
15
63
  def self.pre; singleton_class.class_eval{alias join join_pre} end
@@ -66,60 +114,29 @@ end
66
114
  class NilClass
67
115
  public :dom
68
116
  public :jsonml
117
+ def mounted; nil end
69
118
  end
70
119
 
71
120
  class String
72
- def dom tag, mounted: nil, **attr
73
- "<%s>%s</%s>".%(
74
- Dom.format(tag, attr)
75
- .insert(1, (block_given? ? yield(self) : self).dom_escape(tag)._ansi2html)
76
- )
121
+ def dom tag = nil, mounted: nil, **attr
122
+ if tag
123
+ "<%s>%s</%s>".%(
124
+ Dom.format(tag, attr)
125
+ .insert(1, (block_given? ? yield(self) : self).dom_escape(tag)._ansi2html)
126
+ )
127
+ else
128
+ dom_escape
129
+ end
77
130
  .dom_escaped.mounted_set(mounted)
78
131
  end
79
- def jsonml tag, attr = nil
80
- [*Dom.json_format(tag, attr), self]
81
- end
132
+ def jsonml tag, attr = nil; [*Dom.json_format(tag, attr), self] end
82
133
  def mounted; nil end
83
- def dom_escape tag = nil
84
- case tag; when :style, :script then self else Dom::Coder.encode(self) end
85
- end
86
134
  def dom_escaped; DomString.new(self) end
87
- AnsiColor = {
88
- "1" => "bold",
89
- "4" => "underline",
90
- "30" => "black",
91
- "31" => "red",
92
- "32" => "green",
93
- "33" => "yellow",
94
- "34" => "blue",
95
- "35" => "magenta",
96
- "36" => "cyan",
97
- "37" => "white",
98
- "40" => "bg-black",
99
- "41" => "bg-red",
100
- "42" => "bg-green",
101
- "43" => "bg-yellow",
102
- "44" => "bg-blue",
103
- "45" => "bg-magenta",
104
- "46" => "bg-cyan",
105
- "47" => "bg-white",
106
- }
107
- def _ansi2html
108
- sc = StringScanner.new(self)
109
- io = StringIO.new
110
- io.print(
111
- if sc.scan(/\e\[0?m/o) then '</span>'
112
- elsif sc.scan(/\e\[0?(\d+)m/o) then '<span class="%s">' % AnsiColor[sc[1]]
113
- end ||
114
- sc.scan(/./mo)) until sc.eos?
115
- io.string
116
- end
117
135
  def ansi2html; _ansi2html.dom_escaped end
118
136
  end
119
137
 
120
138
  class DomString < String
121
139
  def to_s; self end
122
- def dom_escape tag = nil; self end
123
140
  def dom_escaped; self end
124
141
  def mounted_set *mounted
125
142
  mounted.compact!
@@ -138,14 +155,17 @@ class Array
138
155
  a = self
139
156
  a = block_given? ? map(&Proc.new) : flatten if recurse.empty?
140
157
  if recurse.length <= 1
141
- if e = a.find{|e| e.kind_of?(String).! and e.kind_of?(Array).!}
158
+ #!index: Using `index` instead of `find` to be able to detect `nil`.
159
+ # (Which turned out irrelevant.)
160
+ if i = a.index{|e| e.kind_of?(String).! and e.kind_of?(Array).! and e.nil?.!}
142
161
  raise ArgumentError
143
- .new("Expecting all array elements to be a string: `#{e.class}:#{e.inspect}'")
162
+ .new("Expecting all array elements to be a string: `#{a[i].class}:#{a[i].inspect}'")
144
163
  end
145
164
  else
146
- if e = a.find{|e| e.kind_of?(Array).!}
165
+ #!index:
166
+ if i = a.index{|e| e.kind_of?(Array).!}
147
167
  raise ArgumentError
148
- .new("Cannot apply tag `#{recurse[-2].inspect}' to `#{e.class}:#{e.inspect}'")
168
+ .new("Cannot apply tag `#{recurse[-2].inspect}' to `#{a[i].class}:#{a[i].inspect}'")
149
169
  end
150
170
  end
151
171
  a = a.map{|e| e.dom(*recurse, &(Proc.new if block_given?))} unless recurse.empty?
@@ -153,9 +173,7 @@ class Array
153
173
  s = "<%s>%s</%s>" % Dom.format(tag, attr).insert(1, s) unless tag.nil?
154
174
  s.dom_escaped.mounted_set(*a.map(&:mounted), mounted)
155
175
  end
156
- def jsonml tag, attr = nil
157
- [*Dom.json_format(attr), *self]
158
- end
176
+ def jsonml tag, attr = nil; [*Dom.json_format(attr), *self] end
159
177
  end
160
178
 
161
179
  Dom.compact
data/license ADDED
@@ -0,0 +1,9 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2013--2016 sawa
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6
+
7
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8
+
9
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/spec ADDED
@@ -0,0 +1,387 @@
1
+ #! frozen_string_literal: false
2
+ #!ruby
3
+
4
+ gemspec "dom.gemspec"
5
+ manage "lib/dom.rb"
6
+
7
+ spec "=License",
8
+ "The MIT License (MIT)",
9
+ "Copyright (c) 2013-2016 sawa",
10
+ "Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:",
11
+ "The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.",
12
+ "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.",
13
+ coda
14
+
15
+ spec "=Overview",
16
+ "The `\"dom\"` gem is a library to generate HTML/XML code from Ruby commands. It replaces conventional approcaches using template engines, or certain kind of libraries with a similar purpose.",
17
+ "With conventional template engines, you need to write HTML/XML code in a dedicated language in an external file or here document, which is then parsed. With the `\"dom\"` gem, you can describe HTML/XML structures in Ruby language seemlessly with other parts of Ruby code.",
18
+ "Some conventional libraries define command names that represent the type of DOM node, which may expect an input like this:",
19
+ <<~'RUBY'.code,
20
+ h1{"Hello World!"} #=> "<h1>Hello World!</h1>"
21
+ div{"Hello World!"} #=> "<div>Hello World!</div>"
22
+ RUBY
23
+ "Such notation lacks flexibility. For every type of node, the corresponding command needs to be defined in advance. A user, would also not be able to use an original node type that is not predefined in the library.",
24
+ "Unlike such systems, `\"dom\"` gem makes use of a fixed method name `dom`, which takes a symbol argument representing the node type. The equivalent of the input above in `\"dom\"` is:",
25
+ <<~'RUBY'.code,
26
+ "Hello World!".dom(:h1) #=> "<h1>Hello World!</h1>"
27
+ "Hello World!".dom(:div) #=> "<div>Hello World!</div>"
28
+ RUBY
29
+ "Some conventional libraries take the content of a node as a block. Nested nodes would then require nested blocks:",
30
+ <<~'RUBY'.code,
31
+ div{p{span{"Hello World!"}}}
32
+ #=> "<div><p><span>Hello World!</span></p></div>"
33
+ RUBY
34
+ "As the structure becomes complicated, it becomes more difficult to keep the block opening paired with a closing.",
35
+ "In `\"dom\"`, the content of a node is the receiver of a method. Node embedding is described as method chaining, which avoids unnecessary nesting, and confirms to the Rubyistic coding style.",
36
+ <<~'RUBY'.code,
37
+ "Hello World!".dom(:span).dom(:p).dom(:div)
38
+ #=> "<div><p><span>Hello World!</span></p></div>"
39
+ RUBY
40
+ coda
41
+
42
+ spec "=Usage",
43
+ "Require dom (usually at the beginning of a file) before using its methods:",
44
+ <<~'RUBY'.code,
45
+ require "dom"
46
+ RUBY
47
+ "To surround a string with a tag, use the `dom` method. Pass the tag name as a symbol, with an optional hash, whose keys are symbols, and values that are either `nil`, `true`, `false`, or other kinds of objects that have the `to_s` methods inmplemented.",
48
+ <<~'RUBY'.code,
49
+ "foo".dom(:span, class: "bar") # => "<span class=\"bar\">foo</span>"
50
+ "foo".dom(:span, data_1: nil, data_2: 3) # => "<span data-2=\"3\">foo</span>"
51
+ RUBY
52
+ "The `dom` method can be chained to generate nested structures.",
53
+ <<~'RUBY'.code,
54
+ "Hello World!".dom(:span).dom(:p).dom(:div)
55
+ #=> "<div><p><span>Hello World!</span></p></div>"
56
+ RUBY
57
+ "When `dom` applies to an array, its elements are joined.",
58
+ <<~'RUBY'.code,
59
+ ["foo", "bar"].dom(:div, class: "bar")
60
+ # => "<div class=\"bar\">foobar</div>"
61
+ RUBY
62
+ "When multiple tags are passed to a single `dom` method call, the tags are distributed and are percolated into the nested arrays.",
63
+ <<~'RUBY'.code,
64
+ [["foo1", "foo2"], ["bar1", "bar2"]].dom(:td, :tr, :table)
65
+ # => "<table><tr><td>foo1</td><td>foo2</td></tr><tr><td>bar1</td><td>bar2</td></tr></table>"
66
+ RUBY
67
+ coda
68
+
69
+ module Dom
70
+ hide spec "::Coder",
71
+ coda
72
+
73
+ hide spec "::Indent",
74
+ "! Indentation internally used in `join` whem the mode is `nested` or `pre`.",
75
+ UT =~ /\s+/,
76
+ coda
77
+
78
+ spec ".compact",
79
+ {"()" => Class},
80
+ "Sets the output to compact mode. Usually the best for production use. It is compressed, and is not convenient for human reading.",
81
+ UT.succeed?,
82
+ "Dom.compact".setup,
83
+ <<~'RUBY'.code,
84
+ Dom.compact
85
+ RUBY
86
+ coda
87
+
88
+ spec ".nested",
89
+ {"()" => Class},
90
+ "Sets the output to nested mode. Usually the best for development use. It may not be optimized for production use.",
91
+ UT.succeed?,
92
+ "Dom.compact".setup,
93
+ <<~'RUBY'.code,
94
+ Dom.nested
95
+ RUBY
96
+ coda
97
+
98
+ spec ".pre",
99
+ {"()" => Class},
100
+ "Sets the output to a mode that preserves the line numbers of the original by inserting HTML's comment expressions. This is for special usage. Normally, you will not need to to set to this mode.",
101
+ UT.succeed?,
102
+ "Dom.compact".setup,
103
+ <<~'RUBY'.code,
104
+ Dom.pre
105
+ RUBY
106
+ coda
107
+
108
+ hide spec ".join_compact",
109
+ "! Internally used `join` method for `compact` mode.",
110
+ coda
111
+
112
+ hide spec ".join_nested",
113
+ "! Internally used `join` method for `nested` mode.",
114
+ coda
115
+
116
+ hide spec ".join_pre",
117
+ "! Internally used `join` method for `pre` mode.",
118
+ coda
119
+
120
+ hide spec ".format",
121
+ "! Converts a hash into an HTML attribute. An attribute `:foo` with value `true` and `false` will be converted to the string `foo=\"\"` and `foo=\"none\"`, respectively. Key-value pairs with the value `nil` will be excluded from the output. Other values will be applied `to_s`. The pairs will be joined by a space, and the whole string will be prepended by a space.",
122
+ UT(:script, foo: "abcabc") == ["script foo=\"abcabc\"", "script"],
123
+ UT(:div, foo: nil) == ["div", "div"],
124
+ UT(:div, foo: false) == ["div foo=\"none\"", "div"],
125
+ UT(:script, foo: true) == ["script foo=\"\"", "script"],
126
+ UT(:div, foo: 3) == ["div foo=\"3\"", "div"],
127
+ UT(:div, foo1: "abcabc", foo2: nil, foo3: false, foo4: true, foo5: 3) ==
128
+ ["div foo1=\"abcabc\" foo3=\"none\" foo4=\"\" foo5=\"3\"", "div"],
129
+ coda
130
+
131
+ hide spec ".json_format",
132
+ "! Creates an array to be used in JSON.",
133
+ UT(:div, style: "foo") == ["div", {"style" => "foo"}],
134
+ coda
135
+
136
+ hide spec ".hyphenize",
137
+ "!Converts underscore into hyphen. Used in `json_format`.",
138
+ UT("FooBar_Baz-Foo") == "FooBar-Baz-Foo",
139
+ coda
140
+
141
+ hide spec ".join",
142
+ "! Joins strings in an array. Depending on the mode, either aliased to `join_compact`, `join_nested`, or `join_pre`.",
143
+ coda
144
+ end
145
+
146
+ class String
147
+ spec "#dom",
148
+ {"(tag, mounted: nil, **attr)" => DomString},
149
+ "When `dom` is called on a string, it will create a tag including that string as the content.",
150
+ <<~'RUBY'.code,
151
+ "foo".dom(:span, class: "bar") # => "<span class=\"bar\">foo</span>"
152
+ RUBY
153
+ "foo".UT(:span, class: "bar") == "<span class=\"bar\">foo</span>",
154
+ "Attributes are passed as a hash. A key `:foo` with value `true` or `false` will be converted to the string `foo=\"\"` or `foo=\"none\"`, respectively. Key-value pairs with the value `nil` will be excluded from the output. For other values, `to_s` will be applied. The pairs will be joined by a space, and the whole string will be prepended by a space.",
155
+ <<~'RUBY'.code,
156
+ "foo".dom(:span, data_1: nil, data_2: 3) # => "<span data-2=\"3\">foo</span>"
157
+ RUBY
158
+ "foo".UT(:span, data_1: nil, data_2: 3) == "<span data-2=\"3\">foo</span>",
159
+ "HTML escaping will be automatically applied unless the `tag` is `:script` or `:style`:",
160
+ <<~'RUBY'.code,
161
+ "<".dom(:span) # => "<span>&lt;</span>"
162
+ "if(3 < 5){alert('foo')};".dom(:script) # => "<script>if(3 < 5){alert('foo')};</script>"
163
+ RUBY
164
+ coda
165
+
166
+ hide spec "#mounted",
167
+ "! Gets the state of inner elements, namely whether they are mounted once. For `String`, this is `nil`.",
168
+ "fresh string".UT.nil?,
169
+ coda
170
+
171
+ # hide spec "#dom_escape",
172
+ # "<".UT == "&lt;",
173
+ # "&".UT == "&amp;",
174
+ # coda
175
+
176
+ spec "#dom_escaped",
177
+ {"()" => DomString},
178
+ "Marks `self` so that it will not be further HTML escaped. Internally, it is converted to an instance of `DomString` class, which is never further escaped.",
179
+ "When a string is already HTML escaped but has not been done so via the `dom` method, further applying `dom` to that string will incorrctly doublely apply HTML marking. The string must be marked with this method to avoid HTML escape. For example, without applying `dom_escaped`, an already-escaped string is doublely escaped, giving wrong results:",
180
+ <<~'RUBY'.code,
181
+ "3 &lt; 5".dom(:span)
182
+ # => "<span>3 &amp;lt; 5</span>"
183
+ ["a".dom(:span, class: "bold"), "b"].join.dom
184
+ #=> "&lt;span class=&quot;bold&quot;&gt;a&lt;/span&gt;b"
185
+ RUBY
186
+ "To get the correct results, `dom_escaped` must be applied before application of `dom`:",
187
+ <<~'RUBY'.code,
188
+ "3 &lt; 5".dom_escaped.dom(:span)
189
+ # => "<span>3 &lt; 5</span>"
190
+ ["a".dom(:span, class: "bold"), "b"].join.dom_escaped.dom
191
+ #=> "<span class=\"bold\">a</span>b"
192
+ RUBY
193
+ " 3 &lt; 5".UT.dom(:span) == " 3 &lt; 5".dom_escaped.dom(:span),
194
+ ["a".dom(:span, class: "bold"), "b"].join.UT.dom == "<span class=\"bold\">a</span>b",
195
+ "When the string is created by `dom` method, such consideration is unnecessary (although it will not harm if `dom_escaped` is redundantly applied).",
196
+ <<~'RUBY'.code,
197
+ ["a".dom(:span, class: "bold"), "b"].dom.dom
198
+ # => "<span class=\"bold\">a</span>b"
199
+ RUBY
200
+ " 3 &lt; 5".UT.dom(:span) == " 3 &lt; 5".dom_escaped.dom(:span),
201
+ ["a".dom(:span, class: "bold"), "b"].join.UT.dom == "<span class=\"bold\">a</span>b",
202
+ "foo".UT.instance_of?(DomString),
203
+ "a = \"foo\"".setup,
204
+ expr("a").UT == expr("a"),
205
+ expr("a").UT === expr("a"),
206
+ expr("a").UT.eql?(expr("a")),
207
+ coda
208
+
209
+ hide spec "#jsonml",
210
+ coda
211
+
212
+ hide spec "::AnsiColor",
213
+ coda
214
+
215
+ hide spec "#ansi2html",
216
+ "! Internally called by `ansi2html`.",
217
+ coda
218
+ end
219
+
220
+ hide spec "::DomString",
221
+ coda
222
+
223
+ class DomString
224
+ hide spec "#to_s",
225
+ "! A `DomString` instance behaves like a `String` except that dom escape does not modify it. This class is to mark that the instance has alrady gone under dom escaping so that further application of dom escaping does not have effect. It makes dom escaping idempotent.",
226
+ expr("DomString.new").UT.instance_of?(DomString),
227
+ "a = DomString.new(\"foo\")".setup,
228
+ expr("a").UT == expr("a"),
229
+ expr("a").UT === expr("a"),
230
+ expr("a").UT.eql?(expr("a")),
231
+ coda
232
+
233
+ # hide spec "#dom_escape",
234
+ # coda
235
+
236
+ spec "#dom",
237
+ "foo".dom_escaped.UT(:span, class: "bar") == "<span class=\"bar\">foo</span>",
238
+ coda
239
+
240
+ spec "#dom_escaped",
241
+ expr("DomString.new(\"foo\")").UT.instance_of?(DomString),
242
+ coda
243
+
244
+ hide spec "#mounted",
245
+ "! Gets the mounted state of `self`. This is a flag indicating whether the string-like object has once been mounted in the web browser.",
246
+ coda
247
+
248
+ hide spec "#mounted_set",
249
+ "! Sets the state of inner elements, namely whether they are mounted once.",
250
+ coda
251
+ end
252
+
253
+ module Kernel
254
+ spec "#dom",
255
+ {"(tag, mounted: nil, **attr)" => DomString},
256
+ "When `dom` is used without an explicit receiver, it will create a self-closing tag.",
257
+ <<~'RUBY'.code,
258
+ dom(:img, source: "/tmp/foo.png") # => "<img source=\"/tmp/foo.png\" />"
259
+ RUBY
260
+ "In the following example, `Kernel#dom` is called at the embedded level. See `Array#dom`.",
261
+ <<~'RUBY'.code,
262
+ Array.new(3).dom(:col, :colgroup) # => "<colgroup><col /><col /><col /></colgroup>"
263
+ RUBY
264
+ TOPLEVEL_BINDING.receiver.UT(:img, source: "/tmp/foo.png") == "<img source=\"/tmp/foo.png\" />",
265
+ nil.UT(:foo) == "<foo />",
266
+ coda
267
+
268
+ hide spec "#jsonml",
269
+ "! Generates a jsonml string.",
270
+ coda
271
+ end
272
+
273
+ hide spec "::NilClass",
274
+ coda
275
+
276
+ class NilClass
277
+ hide spec "#mounted",
278
+ "! Gets the state of inner elements, namely whether they are mounted once. For `NilClass`, this is `nil`.",
279
+ nil.UT.nil?,
280
+ coda
281
+ end
282
+
283
+ class Array
284
+ spec "#dom",
285
+ {"(tag, mounted: nil, **attr)" => DomString},
286
+ "Concatenates the elements and puts them inside the tag.",
287
+ <<~'RUBY'.code,
288
+ ["foo", "bar"].dom(:div, class: "bar") # => "<div class=\"bar\">foobar</div>"
289
+ RUBY
290
+ ["foo", "bar"].UT(:div, class: "bar") == "<div class=\"bar\">foobar</div>",
291
+ {"()" => DomString},
292
+ "When no argument is given or an explicit `nil` is given, it joins its elements.",
293
+ <<~'RUBY'.code,
294
+ ["foo", "bar"].dom # => "foobar",
295
+ ["foo", "bar"].dom(nil) # => "foobar",
296
+ RUBY
297
+ ["foo", "bar"].UT == "foobar",
298
+ RETURN.is_a?(DomString),
299
+ ["foo", "bar"].UT(nil) == "foobar",
300
+ RETURN.is_a?(DomString),
301
+ {"(*tags, mounted: nil, **attr)" => DomString},
302
+ "When more than one tag is provided, it applies `dom` to each of its elements with the tags passed except for the last tag (with no other parameters). The last tag and the other parameters are used to apply `dom` to the result.",
303
+ <<~'RUBY'.code,
304
+ [["foo1", "foo2"], ["bar1", "bar2"]].dom(:td, :tr, :table)
305
+ # => "<table><tr><td>foo1</td><td>foo2</td></tr><tr><td>bar1</td><td>bar2</td></tr></table>"
306
+
307
+ "Array.new(3)".dom(:col, :colgroup)
308
+ #=> "<colgroup><col /><col /><col /></colgroup>"
309
+
310
+ [["a", 1], ["b", "c"]].dom(:td, :tr)
311
+ # => ArgumentError
312
+
313
+ [[["a", "b"], "c"], "d"].dom(:bar, :foo)
314
+ # => "<foo><bar>abc</bar><bar>d</bar></foo>"
315
+
316
+ ["x"].dom(:a, :b, :c, :d)
317
+ # => ArgumentError
318
+ RUBY
319
+ [["foo1", "foo2"], ["bar1", "bar2"]].UT(:td, :tr, :table) ==
320
+ "<table><tr><td>foo1</td><td>foo2</td></tr><tr><td>bar1</td><td>bar2</td></tr></table>",
321
+ RETURN.is_a?(DomString),
322
+ expr("Array.new(3)").UT(:col, :colgroup) == "<colgroup><col /><col /><col /></colgroup>",
323
+ # note "flatten", "This is necessary so that `mounted` can be applied to all its elements, which is only defined for `String` and `DomString`."
324
+ [["a", 1], ["b", "c"]].UT(:td, :tr).raise?(ArgumentError),
325
+ [[["a", "b"], "c"], "d"].UT(:bar, :foo) == "<foo><bar>abc</bar><bar>d</bar></foo>",
326
+ ["x"].UT(:a, :b, :c, :d).raise?(ArgumentError, message: /Cannot apply tag/),
327
+ "Passing `nil` as the last argument in a sequence:",
328
+ <<~'RUBY'.code,
329
+ [["foo1", "foo2"], ["bar1", "bar2"]].dom(:td, :tr, nil)
330
+ # => "<tr><td>foo1</td><td>foo2</td></tr><tr><td>bar1</td><td>bar2</td></tr>"
331
+
332
+ [["a", "b"], ["c", "d"]].dom(:div, nil, nil)
333
+ # => "<div>a</div><div>b</div><div>c</div><div>d</div>"
334
+
335
+ [["a", "b"], ["c", "d"]].dom(:div, nil)
336
+ # => "<div>ab</div><div>cd</div>"
337
+ RUBY
338
+ [["foo1", "foo2"], ["bar1", "bar2"]].UT(:td, :tr, nil) ==
339
+ "<tr><td>foo1</td><td>foo2</td></tr><tr><td>bar1</td><td>bar2</td></tr>",
340
+ RETURN.is_a?(DomString),
341
+ [["a", "b"], ["c", "d"]].UT(:div, nil, nil) == "<div>a</div><div>b</div><div>c</div><div>d</div>",
342
+ [["a", "b"], ["c", "d"]].UT(:div, nil) == "<div>ab</div><div>cd</div>",
343
+ "Cf.",
344
+ <<~'RUBY'.code,
345
+ [["a", "b"], ["c", "d"]].dom(nil, :div)
346
+ # => "<div>abcd</div>"
347
+ RUBY
348
+ [["a", "b"], ["c", "d"]].UT(nil, :div) == "<div>abcd</div>",
349
+ {"(*tags, mounted: nil, **attr, &pr)" => DomString},
350
+ "When a block is passed, the array depth to which the block is applied depends on the class of the receiver that corresponds to the inner most tag.",
351
+ "When the receiver that corresponds to the inndermost tag is an array, the block is applied to each of its elements.",
352
+ <<~'RUBY'.code,
353
+ ["a", "b"].dom(:foo){|e| e * 2}
354
+ # => "<foo>aabb</foo>"
355
+
356
+ [["a", "b"], ["c", "d"]].dom(:bar, :foo){|e| e * 2}
357
+ # => "<foo><bar>aabb</bar><bar>ccdd</bar></foo>"
358
+ RUBY
359
+ ["foo", "bar"].UT(:div, class: "bar") == "<div class=\"bar\">foobar</div>",
360
+ RETURN.is_a?(DomString),
361
+ ["a", "b"].UT(:foo){|e| e * 2} == "<foo>aabb</foo>",
362
+ [["a", "b"], ["c", "d"]].UT(:bar, :foo){|e| e * 2} == "<foo><bar>aabb</bar><bar>ccdd</bar></foo>",
363
+
364
+ "When the receiver that corresponds to the innermost tag is a string, the block is applied to it.",
365
+ <<~'RUBY'.code,
366
+ [["a", "b"], ["c", "d"]].dom(:td, :tr, :tbody){|e| e * 2}
367
+ # => "<tbody><tr><td>aa</td><td>bb</td></tr><tr><td>cc</td><td>dd</td></tr></tbody>"
368
+ RUBY
369
+ [["a", "b"], ["c", "d"]].UT(:td, :tr, :tbody){|e| e * 2} == "<tbody><tr><td>aa</td><td>bb</td></tr><tr><td>cc</td><td>dd</td></tr></tbody>",
370
+ coda
371
+
372
+ hide spec "#jsonml",
373
+ coda
374
+ end
375
+
376
+ class File
377
+ spec ".relativize",
378
+ {"(string)" => String},
379
+ "Removes the initial slash if an absolute path is given. If a relative path is given, it has no effect.",
380
+ <<~'RUBY'.code,
381
+ File.relativize("/foo/bar/baz") #=> "foo/bar/baz"
382
+ File.relativize("foo/bar/baz") #=> "foo/bar/baz"
383
+ RUBY
384
+ UT("/foo/bar/baz") == "foo/bar/baz",
385
+ UT("foo/bar/baz") == "foo/bar/baz",
386
+ coda
387
+ end