fun_html 0.1.3 → 0.2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3ce4bcf131736baea3507d1af6c7fd8941cffaf5b6a298553d19589e094be2de
4
- data.tar.gz: 6cf8a2fd455856969638bbe2ed3e16900d410d8da15d51e940e4297c3dd1d9d5
3
+ metadata.gz: 3e5e644d2ee9775409d54d3340ee692e557614bb1e85ef0c290317c0d00e9432
4
+ data.tar.gz: 4792b3bf1361a392a59221c43a937ce1922108d09b6f799a6c5a74466c41e760
5
5
  SHA512:
6
- metadata.gz: f191096f9f3944d20c119b4606a7df786ac18793fa756297dff5aaea3eb5d492a3121ea8329cf21d4261f471104a4ad556875f199071aea3b4ce45e8602f69c6
7
- data.tar.gz: 4fe8144236bb61b3ddfdd40d5bed1045fee093ea4bc0a2a337544dae0839acf894cb6d9904f58e51b56d31254368b94e7f787f7780269d39d00778da00583a73
6
+ metadata.gz: 808d8bb9950923c28e12b81c39d7be030aa2cf315d73e4b28efe157398aeaaacc349983bb785e167e59c6cad284656f6e310fff215d8022e15eebf89c1f51b81
7
+ data.tar.gz: a75a3249ea3253801467b1324b6da56c090ca919ea74309a900f80635d7a01dadfeab966844e98a20305a4b3cd0f96621ac929157309df861fddc3e511c424d4
@@ -0,0 +1,86 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require_relative 'spec_attributes'
5
+ require 'erb/escape'
6
+
7
+ module FunHtml
8
+ # Attribute generates HTML attributes. It is designed to prevent printing the
9
+ # same attribute twice. The Template elements expect an Attribute object.
10
+ #
11
+ # All attribute values are HTML escaped via ERB::Escape.
12
+ #
13
+ # It is typed to only allow the correct types to be passed to the attribute,
14
+ # if Sorbet is enabled.
15
+ #
16
+ # Attributes can be generated via a block, or chained.
17
+ #
18
+ # Block
19
+ # Attribute.new { _1.id "one" ; _1.klass "big" }
20
+ #
21
+ # Chained
22
+ # Attribute.new.id("one").klass("big")
23
+ #
24
+ # Boolean attributes
25
+ # Some attributes (async, autoplay, disabled) are boolean. When true is
26
+ # passed, the attribute prints, when false, the attribute will be blank.
27
+ #
28
+ # Attribute.new.disabled(true) -> " disabled"
29
+ # Attribute.new.disabled(false) -> " "
30
+ class Attribute
31
+ include FunHtml::SpecAttributes
32
+
33
+ # only allow nil or objects that respond to `safe_attribute`
34
+ def self.to_html(attr)
35
+ attr&.safe_attribute.to_s
36
+ end
37
+
38
+ # create a new Attribute object to create reuseable attributes
39
+ def initialize(buffer = {}, &block)
40
+ @__buffer = buffer
41
+ return unless block
42
+
43
+ yield self
44
+ end
45
+
46
+ # Custom data attributes
47
+ # The data attribute takes a suffix and the string value.
48
+ # Attribute.new.data('turbo','false') -> ' data-turbo="false"'
49
+ def data(suffix, value)
50
+ unless suffix.match?(/\A[a-z-]+\z/)
51
+ raise ArgumentError,
52
+ "suffix (#{suffix}) must be lowercase and only contain 'a' to 'z' or hyphens."
53
+ end
54
+
55
+ write(" data-#{suffix}=\"", value)
56
+ end
57
+
58
+ # CSS class name(s) for styling, the name changed to protect the Ruby.
59
+ def klass(value)
60
+ write(' class="', value)
61
+ end
62
+
63
+ # Merge another Attribute to create a new, combined, Attribute.
64
+ def merge(other)
65
+ self.class.new(@__buffer.merge(other.instance_variable_get(:@__buffer)))
66
+ end
67
+
68
+ # output the attributes as string
69
+ def safe_attribute
70
+ @__buffer.values.join
71
+ end
72
+
73
+ private
74
+
75
+ def write(name, value)
76
+ @__buffer[name] = "#{name}#{ERB::Escape.html_escape(value)}\""
77
+ self
78
+ end
79
+
80
+ # for boolean attributes, determin if to print the attribute or not
81
+ def write_boolean(name, print)
82
+ @__buffer[name] = print ? name : ''
83
+ self
84
+ end
85
+ end
86
+ end
@@ -2,7 +2,7 @@
2
2
 
3
3
  module FunHtml
4
4
  # HTML attributes autogenerated, do not edit
5
- module AttributeDefinitions
5
+ module SpecAttributes
6
6
  # Specifies file types browser will accept
7
7
  def accept(value) = write(' accept="', value)
8
8
  # Character encodings used for form submission
@@ -16,13 +16,13 @@ module FunHtml
16
16
  # Alternative text for images
17
17
  def alt(value) = write(' alt="', value)
18
18
  # Script should execute asynchronously
19
- def async(state = true) = write_empty(' async', state)
19
+ def async(value) = write_boolean(' async', value)
20
20
  # Form/input autocompletion
21
21
  def autocomplete(value) = write(' autocomplete="', value)
22
22
  # Element should be focused on page load
23
- def autofocus(state = true) = write_empty(' autofocus', state)
23
+ def autofocus(value) = write_boolean(' autofocus', value)
24
24
  # Media will start playing automatically
25
- def autoplay(state = true) = write_empty(' autoplay', state)
25
+ def autoplay(value) = write_boolean(' autoplay', value)
26
26
  # Background color of element
27
27
  def bgcolor(value) = write(' bgcolor="', value)
28
28
  # Border width in pixels
@@ -30,9 +30,7 @@ module FunHtml
30
30
  # Character encoding of document
31
31
  def charset(value) = write(' charset="', value)
32
32
  # Whether checkbox/radio button is selected
33
- def checked(state = true) = write_empty(' checked', state)
34
- # CSS class name(s) for styling
35
- def klass(value) = write(' class="', value)
33
+ def checked(value) = write_boolean(' checked', value)
36
34
  # Number of columns in textarea
37
35
  def cols(value) = write(' cols="', value)
38
36
  # Number of columns a cell spans
@@ -42,21 +40,19 @@ module FunHtml
42
40
  # Whether content is editable
43
41
  def contenteditable(value) = write(' contenteditable="', value)
44
42
  # Show media playback controls
45
- def controls(state = true) = write_empty(' controls', state)
43
+ def controls(value) = write_boolean(' controls', value)
46
44
  # Coordinates for image maps
47
45
  def coords(value) = write(' coords="', value)
48
- # Custom data attributes
49
- def data(value) = write(' data="', value)
50
46
  # Date/time of element content
51
47
  def datetime(value) = write(' datetime="', value)
52
48
  # Default track for media
53
- def default(state = true) = write_empty(' default', state)
49
+ def default(value) = write_boolean(' default', value)
54
50
  # Script should execute after parsing
55
- def defer(state = true) = write_empty(' defer', state)
51
+ def defer(value) = write_boolean(' defer', value)
56
52
  # Text direction
57
53
  def dir(value) = write(' dir="', value)
58
54
  # Element is disabled
59
- def disabled(state = true) = write_empty(' disabled', state)
55
+ def disabled(value) = write_boolean(' disabled', value)
60
56
  # Resource should be downloaded
61
57
  def download(value) = write(' download="', value)
62
58
  # Element can be dragged
@@ -74,7 +70,7 @@ module FunHtml
74
70
  # Height of element
75
71
  def height(value) = write(' height="', value)
76
72
  # Element is not displayed
77
- def hidden(state = true) = write_empty(' hidden', state)
73
+ def hidden(value) = write_boolean(' hidden', value)
78
74
  # Upper range of meter
79
75
  def high(value) = write(' high="', value)
80
76
  # URL of linked resource
@@ -86,7 +82,7 @@ module FunHtml
86
82
  # Subresource integrity hash
87
83
  def integrity(value) = write(' integrity="', value)
88
84
  # Image is server-side image map
89
- def ismap(state = true) = write_empty(' ismap', state)
85
+ def ismap(value) = write_boolean(' ismap', value)
90
86
  # Type of text track
91
87
  def kind(value) = write(' kind="', value)
92
88
  # Label for form control/option
@@ -96,7 +92,7 @@ module FunHtml
96
92
  # Links input to datalist options
97
93
  def list(value) = write(' list="', value)
98
94
  # Media will replay when finished
99
- def loop(state = true) = write_empty(' loop', state)
95
+ def loop(value) = write_boolean(' loop', value)
100
96
  # Lower range of meter
101
97
  def low(value) = write(' low="', value)
102
98
  # Maximum allowed value
@@ -110,15 +106,15 @@ module FunHtml
110
106
  # Minimum allowed value
111
107
  def min(value) = write(' min="', value)
112
108
  # Multiple values can be selected
113
- def multiple(state = true) = write_empty(' multiple', state)
109
+ def multiple(value) = write_boolean(' multiple', value)
114
110
  # Media is muted by default
115
- def muted(state = true) = write_empty(' muted', state)
111
+ def muted(value) = write_boolean(' muted', value)
116
112
  # Name of form control
117
113
  def name(value) = write(' name="', value)
118
114
  # Form validation is skipped
119
- def novalidate(state = true) = write_empty(' novalidate', state)
115
+ def novalidate(value) = write_boolean(' novalidate', value)
120
116
  # Details element is expanded
121
- def open(state = true) = write_empty(' open', state)
117
+ def open(value) = write_boolean(' open', value)
122
118
  # Optimal value for meter
123
119
  def optimum(value) = write(' optimum="', value)
124
120
  # Regular expression pattern
@@ -130,23 +126,25 @@ module FunHtml
130
126
  # How media should be loaded
131
127
  def preload(value) = write(' preload="', value)
132
128
  # Input field cannot be modified
133
- def readonly(state = true) = write_empty(' readonly', state)
129
+ def readonly(value) = write_boolean(' readonly', value)
134
130
  # Relationship of linked resource
135
131
  def rel(value) = write(' rel="', value)
136
132
  # Input must be filled out
137
- def required(state = true) = write_empty(' required', state)
133
+ def required(value) = write_boolean(' required', value)
138
134
  # List is numbered in reverse
139
- def reversed(state = true) = write_empty(' reversed', state)
135
+ def reversed(value) = write_boolean(' reversed', value)
140
136
  # Number of rows in textarea
141
137
  def rows(value) = write(' rows="', value)
142
138
  # Number of rows a cell spans
143
139
  def rowspan(value) = write(' rowspan="', value)
144
140
  # Security rules for iframe
145
141
  def sandbox(value) = write(' sandbox="', value)
142
+ # Specifies the number of consecutive columns the <colgroup> element spans. The value must be a positive integer greater than zero. If not present, its default value is 1.
143
+ def span(value) = write(' span="', value)
146
144
  # Cells header element relates to
147
145
  def scope(value) = write(' scope="', value)
148
146
  # Option is pre-selected
149
- def selected(state = true) = write_empty(' selected', state)
147
+ def selected(value) = write_boolean(' selected', value)
150
148
  # Shape of image map area
151
149
  def shape(value) = write(' shape="', value)
152
150
  # Size of input/select control
@@ -187,5 +185,70 @@ module FunHtml
187
185
  def width(value) = write(' width="', value)
188
186
  # How text wraps in textarea
189
187
  def wrap(value) = write(' wrap="', value)
188
+ def onabort(value) = write(' onabort="', value)
189
+ def onauxclick(value) = write(' onauxclick="', value)
190
+ def onbeforeinput(value) = write(' onbeforeinput="', value)
191
+ def onbeforematch(value) = write(' onbeforematch="', value)
192
+ def onbeforetoggle(value) = write(' onbeforetoggle="', value)
193
+ def oncancel(value) = write(' oncancel="', value)
194
+ def oncanplay(value) = write(' oncanplay="', value)
195
+ def oncanplaythrough(value) = write(' oncanplaythrough="', value)
196
+ def onchange(value) = write(' onchange="', value)
197
+ def onclick(value) = write(' onclick="', value)
198
+ def onclose(value) = write(' onclose="', value)
199
+ def oncontextlost(value) = write(' oncontextlost="', value)
200
+ def oncontextmenu(value) = write(' oncontextmenu="', value)
201
+ def oncontextrestored(value) = write(' oncontextrestored="', value)
202
+ def oncopy(value) = write(' oncopy="', value)
203
+ def oncuechange(value) = write(' oncuechange="', value)
204
+ def oncut(value) = write(' oncut="', value)
205
+ def ondblclick(value) = write(' ondblclick="', value)
206
+ def ondrag(value) = write(' ondrag="', value)
207
+ def ondragend(value) = write(' ondragend="', value)
208
+ def ondragenter(value) = write(' ondragenter="', value)
209
+ def ondragleave(value) = write(' ondragleave="', value)
210
+ def ondragover(value) = write(' ondragover="', value)
211
+ def ondragstart(value) = write(' ondragstart="', value)
212
+ def ondrop(value) = write(' ondrop="', value)
213
+ def ondurationchange(value) = write(' ondurationchange="', value)
214
+ def onemptied(value) = write(' onemptied="', value)
215
+ def onended(value) = write(' onended="', value)
216
+ def onformdata(value) = write(' onformdata="', value)
217
+ def oninput(value) = write(' oninput="', value)
218
+ def oninvalid(value) = write(' oninvalid="', value)
219
+ def onkeydown(value) = write(' onkeydown="', value)
220
+ def onkeypress(value) = write(' onkeypress="', value)
221
+ def onkeyup(value) = write(' onkeyup="', value)
222
+ def onloadeddata(value) = write(' onloadeddata="', value)
223
+ def onloadedmetadata(value) = write(' onloadedmetadata="', value)
224
+ def onloadstart(value) = write(' onloadstart="', value)
225
+ def onmousedown(value) = write(' onmousedown="', value)
226
+ def onmouseenter(value) = write(' onmouseenter="', value)
227
+ def onmouseleave(value) = write(' onmouseleave="', value)
228
+ def onmousemove(value) = write(' onmousemove="', value)
229
+ def onmouseout(value) = write(' onmouseout="', value)
230
+ def onmouseover(value) = write(' onmouseover="', value)
231
+ def onmouseup(value) = write(' onmouseup="', value)
232
+ def onpaste(value) = write(' onpaste="', value)
233
+ def onpause(value) = write(' onpause="', value)
234
+ def onplay(value) = write(' onplay="', value)
235
+ def onplaying(value) = write(' onplaying="', value)
236
+ def onprogress(value) = write(' onprogress="', value)
237
+ def onratechange(value) = write(' onratechange="', value)
238
+ def onreset(value) = write(' onreset="', value)
239
+ def onscrollend(value) = write(' onscrollend="', value)
240
+ def onsecuritypolicyviolation(value) = write(' onsecuritypolicyviolation="', value)
241
+ def onseeked(value) = write(' onseeked="', value)
242
+ def onseeking(value) = write(' onseeking="', value)
243
+ def onselect(value) = write(' onselect="', value)
244
+ def onslotchange(value) = write(' onslotchange="', value)
245
+ def onstalled(value) = write(' onstalled="', value)
246
+ def onsubmit(value) = write(' onsubmit="', value)
247
+ def onsuspend(value) = write(' onsuspend="', value)
248
+ def ontimeupdate(value) = write(' ontimeupdate="', value)
249
+ def ontoggle(value) = write(' ontoggle="', value)
250
+ def onvolumechange(value) = write(' onvolumechange="', value)
251
+ def onwaiting(value) = write(' onwaiting="', value)
252
+ def onwheel(value) = write(' onwheel="', value)
190
253
  end
191
254
  end
@@ -2,7 +2,7 @@
2
2
 
3
3
  module FunHtml
4
4
  # HTML nodes autogenerated, do not edit
5
- module NodeDefinitions
5
+ module SpecElements
6
6
  module HTMLHtmlElement
7
7
  def html(attributes = nil, &elements)
8
8
  write('<html', '</html>', attributes, &elements)
@@ -24,21 +24,21 @@ module FunHtml
24
24
  module HTMLBaseElement
25
25
  def base(attributes = nil)
26
26
  # no child elements allowed and no closing tag
27
- write('<base', '>', attributes)
27
+ write_void('<base', attributes)
28
28
  end
29
29
  end
30
30
 
31
31
  module HTMLLinkElement
32
32
  def link(attributes = nil)
33
33
  # no child elements allowed and no closing tag
34
- write('<link', '>', attributes)
34
+ write_void('<link', attributes)
35
35
  end
36
36
  end
37
37
 
38
38
  module HTMLMetaElement
39
39
  def meta(attributes = nil)
40
40
  # no child elements allowed and no closing tag
41
- write('<meta', '>', attributes)
41
+ write_void('<meta', attributes)
42
42
  end
43
43
  end
44
44
 
@@ -201,7 +201,7 @@ module FunHtml
201
201
 
202
202
  def wbr(attributes = nil)
203
203
  # no child elements allowed and no closing tag
204
- write('<wbr', '>', attributes)
204
+ write_void('<wbr', attributes)
205
205
  end
206
206
 
207
207
  def summary(attributes = nil, &elements)
@@ -300,7 +300,7 @@ module FunHtml
300
300
  module HTMLHRElement
301
301
  def hr(attributes = nil)
302
302
  # no child elements allowed and no closing tag
303
- write('<hr', '>', attributes)
303
+ write_void('<hr', attributes)
304
304
  end
305
305
  end
306
306
 
@@ -391,7 +391,7 @@ module FunHtml
391
391
  module HTMLBRElement
392
392
  def br(attributes = nil)
393
393
  # no child elements allowed and no closing tag
394
- write('<br', '>', attributes)
394
+ write_void('<br', attributes)
395
395
  end
396
396
  end
397
397
 
@@ -414,14 +414,14 @@ module FunHtml
414
414
  module HTMLSourceElement
415
415
  def source(attributes = nil)
416
416
  # no child elements allowed and no closing tag
417
- write('<source', '>', attributes)
417
+ write_void('<source', attributes)
418
418
  end
419
419
  end
420
420
 
421
421
  module HTMLImageElement
422
422
  def img(attributes = nil)
423
423
  # no child elements allowed and no closing tag
424
- write('<img', '>', attributes)
424
+ write_void('<img', attributes)
425
425
  end
426
426
  end
427
427
 
@@ -434,7 +434,7 @@ module FunHtml
434
434
  module HTMLEmbedElement
435
435
  def embed(attributes = nil)
436
436
  # no child elements allowed and no closing tag
437
- write('<embed', '>', attributes)
437
+ write_void('<embed', attributes)
438
438
  end
439
439
  end
440
440
 
@@ -459,7 +459,7 @@ module FunHtml
459
459
  module HTMLTrackElement
460
460
  def track(attributes = nil)
461
461
  # no child elements allowed and no closing tag
462
- write('<track', '>', attributes)
462
+ write_void('<track', attributes)
463
463
  end
464
464
  end
465
465
 
@@ -472,7 +472,7 @@ module FunHtml
472
472
  module HTMLAreaElement
473
473
  def area(attributes = nil)
474
474
  # no child elements allowed and no closing tag
475
- write('<area', '>', attributes)
475
+ write_void('<area', attributes)
476
476
  end
477
477
  end
478
478
 
@@ -495,7 +495,7 @@ module FunHtml
495
495
 
496
496
  def col(attributes = nil)
497
497
  # no child elements allowed and no closing tag
498
- write('<col', '>', attributes)
498
+ write_void('<col', attributes)
499
499
  end
500
500
  end
501
501
 
@@ -544,7 +544,7 @@ module FunHtml
544
544
  module HTMLInputElement
545
545
  def input(attributes = nil)
546
546
  # no child elements allowed and no closing tag
547
- write('<input', '>', attributes)
547
+ write_void('<input', attributes)
548
548
  end
549
549
  end
550
550
 
@@ -627,9 +627,6 @@ module FunHtml
627
627
  end
628
628
 
629
629
  module HTMLScriptElement
630
- def script(attributes = nil, &elements)
631
- write('<script', '</script>', attributes, &elements)
632
- end
633
630
  end
634
631
 
635
632
  module HTMLTemplateElement
@@ -0,0 +1,104 @@
1
+ # typed: strict
2
+
3
+ require_relative 'attribute'
4
+ require_relative 'writer'
5
+ require 'erb/escape'
6
+
7
+ module FunHtml
8
+ # FunHtml Template will generate typed HTML. Each tag and attribute has a
9
+ # match method that is typed via Sorbet (which is optional).
10
+ #
11
+ # The template is designed to allow subclassing or using `start` to generate
12
+ # templates without subclassing.
13
+ #
14
+ # When subclassing understand that `new` generatings a "buffer". Each time a
15
+ # tag(div, b, body, etc) is called it will be added to the buffer. Once
16
+ # `render` is called the buffer is is returned and then cleared.
17
+ #
18
+ # class Example < FunHtml::Template
19
+ # def initialize(name)
20
+ # super()
21
+ # @name = name
22
+ # end
23
+ #
24
+ # def view
25
+ # doctype
26
+ # html do
27
+ # body do
28
+ # h1 { text @name }
29
+ # end
30
+ # end
31
+ # end
32
+ # end
33
+ #
34
+ # puts Example.new('My Example').view.render
35
+ # <!DOCTYPE html><html><body><h1>My Example</h1></body></html>
36
+ #
37
+ # If you need to create custom tags, create a method that integrates with the
38
+ # Writer#write method.
39
+ class Template
40
+ include FunHtml::Writer
41
+ include FunHtml::SpecElements::HTMLAllElements
42
+
43
+ # To avoid subclassing a template, `start` can be used to yeild and return a Template.
44
+ #
45
+ # html = FunHtml::Template.start do |t|
46
+ # t.doctype
47
+ # t.html do
48
+ # t.body { t.h1 "Body" }
49
+ # end
50
+ # end
51
+ #
52
+ def self.start(&block)
53
+ obj = new
54
+ yield obj if block
55
+ obj
56
+ end
57
+
58
+ # join an array of other templates into this template.
59
+ def join(templates)
60
+ templates.each { @__buffer << _1.render }
61
+ self
62
+ end
63
+
64
+ # text will generate the text node, this is the only way to insert strings into the template.
65
+ #
66
+ # Template.start do |t|
67
+ # t.div { t.text "Hello" }
68
+ # end
69
+ #
70
+ def text(value)
71
+ @__buffer << ERB::Escape.html_escape(value)
72
+ self
73
+ end
74
+
75
+ # generate a comment block
76
+ def comment(comment_text = nil)
77
+ write('<!--', '-->', nil, closing_char: '') { text comment_text.to_s }
78
+ end
79
+
80
+ # generate the doctype
81
+ def doctype
82
+ @__buffer << '<!DOCTYPE html>'
83
+ self
84
+ end
85
+
86
+ # generate a script tag. The return the script code to the block.
87
+ # The code is not escaped.
88
+ def script(attributes = nil, &block) # rubocop:disable Lint/UnusedMethodArgument
89
+ body = yield
90
+ write('<script', '</script>', attributes) { send :unsafe_text, body }
91
+ end
92
+
93
+ # attr is short cut in the template to return a new Attribute
94
+ #
95
+ # Template.start { |t| t.div(t.attr.id('my-div'), t.text '...') }
96
+ def attr(&blk)
97
+ if blk
98
+ Attribute.new(&blk)
99
+ else
100
+ Attribute.new
101
+ end
102
+ end
103
+ end
104
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module FunHtml
4
- VERSION = '0.1.3'
4
+ VERSION = '0.2.0'
5
5
  end
@@ -0,0 +1,49 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require_relative 'spec_elements'
5
+ require 'erb/escape'
6
+
7
+ module FunHtml
8
+ # Writer collects the rendered elements and attributes into a string.
9
+ module Writer
10
+ include Kernel
11
+
12
+ def initialize
13
+ @__buffer = +''
14
+ end
15
+
16
+ # Render produces the HTML string and clears the buffer.
17
+ def render
18
+ @__buffer
19
+ ensure
20
+ # empty the buffer to prevent double rendering
21
+ @__buffer = +''
22
+ end
23
+
24
+ private
25
+
26
+ # used when the text should not be escaped, see `script`
27
+ def unsafe_text(value)
28
+ @__buffer << value
29
+ self
30
+ end
31
+
32
+ CLOSE = '>'
33
+ CLOSE_VOID = '/>'
34
+
35
+ def write(open, close, attr = nil, closing_char: CLOSE, &block)
36
+ @__buffer << open << Attribute.to_html(attr)
37
+
38
+ @__buffer << closing_char
39
+ yield self if block
40
+ @__buffer << close
41
+
42
+ self
43
+ end
44
+
45
+ def write_void(open, attr = nil)
46
+ @__buffer << open << Attribute.to_html(attr) << CLOSE_VOID
47
+ end
48
+ end
49
+ end