roda-tags 0.1.1 → 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.
@@ -1,18 +1,17 @@
1
+ # frozen_string_literal: false
2
+
1
3
  require 'roda'
4
+
2
5
  require_relative '../../core_ext/hash' unless {}.respond_to?(:to_html_attributes)
3
6
  require_relative '../../core_ext/blank' unless Object.new.respond_to?(:blank?)
4
7
 
5
-
6
8
  class Roda
7
-
8
- #
9
+ # add module documentation
9
10
  module RodaPlugins
10
-
11
11
  # TODO: Add documentation here
12
12
  #
13
13
  #
14
14
  module RodaTags
15
-
16
15
  # default options
17
16
  OPTS = {
18
17
  # toggle for XHTML formatted output in case of legacy
@@ -20,433 +19,690 @@ class Roda
20
19
  # toggle for adding newlines after output
21
20
  tag_add_newlines_after_tags: true
22
21
  }.freeze
23
-
22
+
24
23
  # Tags that should be rendered in multiple lines, like...
25
- #
24
+ #
26
25
  # <body>
27
26
  # <snip...>
28
27
  # </body>
29
28
  #
30
- MULTI_LINE_TAGS = %w(
31
- a address applet bdo big blockquote body button caption center
32
- colgroup dd dir div dl dt fieldset form frameset head html iframe
33
- map noframes noscript object ol optgroup pre script section select small
34
- style table tbody td tfoot th thead title tr tt ul
35
- )
36
-
29
+ MULTI_LINE_TAGS = %w[
30
+ a address applet bdo big blockquote body button caption center colgroup dd dir div dl dt
31
+ fieldset form frameset head html iframe map noframes noscript object ol optgroup pre
32
+ script section select small style table tbody td tfoot th thead title tr tt ul
33
+ ].freeze
34
+
37
35
  # Self closing tags, like...
38
- #
36
+ #
39
37
  # <hr> or <hr />
40
38
  #
41
- SELF_CLOSING_TAGS = %w( area base br col frame hr img input link meta param )
42
-
39
+ SELF_CLOSING_TAGS = %w[
40
+ area base br col frame hr img input link meta param
41
+ ].freeze
42
+
43
43
  # Tags that should be rendered in a single line, like...
44
- #
44
+ #
45
45
  # <h1>Header</h1>
46
46
  #
47
- SINGLE_LINE_TAGS = %w(
48
- abbr acronym b cite code del dfn em h1 h2 h3 h4 h5 h6 i kbd
47
+ SINGLE_LINE_TAGS = %w[
48
+ abbr acronym b cite code del dfn em h1 h2 h3 h4 h5 h6 i kbd
49
49
  label legend li option p q samp span strong sub sup var
50
- )
51
-
50
+ ].freeze
51
+
52
52
  # Boolean attributes, ie: attributes like...
53
- #
53
+ #
54
54
  # <option value="a" selected="selected">A</option>
55
55
  #
56
- BOOLEAN_ATTRIBUTES = %w(autofocus checked disabled multiple readonly required selected)
57
-
58
-
56
+ BOOLEAN_ATTRIBUTES = %w[
57
+ autofocus checked disabled multiple readonly required selected
58
+ ].freeze
59
+
59
60
  # Depend on the render plugin, since this plugin only makes
60
61
  # sense when the render plugin is used.
61
- def self.load_dependencies(app, opts = OPTS)
62
+ def self.load_dependencies(app, _opts = OPTS)
62
63
  app.plugin :render
63
64
  end
64
-
65
+
65
66
  def self.configure(app, opts = {})
66
- if app.opts[:tags]
67
- opts = app.opts[:tags][:orig_opts].merge(opts)
68
- else
69
- opts = OPTS.merge(opts)
70
- end
71
-
67
+ opts = if app.opts[:tags]
68
+ app.opts[:tags][:orig_opts].merge(opts)
69
+ else
70
+ OPTS.merge(opts)
71
+ end
72
+
72
73
  app.opts[:tags] = opts.dup
73
74
  app.opts[:tags][:orig_opts] = opts
74
75
  end
75
-
76
- #
76
+
77
+ # add module documentation
77
78
  module ClassMethods
78
-
79
- # Return the uitags options for this class.
79
+ # Returns the tags options hash for the current Roda class instance.
80
+ #
81
+ # @example
82
+ # tags_opts
83
+ # #=> { tag_output_format_is_xhtml: false, tag_add_newlines_after_tags: true }
84
+ #
80
85
  def tags_opts
81
86
  opts[:tags]
82
87
  end
83
-
84
88
  end
85
-
86
- #
89
+
90
+ # add module documentation
91
+ # rubocop:disable Metrics/ModuleLength
87
92
  module InstanceMethods
88
-
89
- # Returns markup for tag _name_.
90
- #
91
- # Optionally _contents_ may be passed, which is literal content for spanning tags such as
92
- # <tt>textarea</tt>, etc.
93
- #
94
- # A hash of _attrs_ may be passed as the *second* or *third* argument.
95
- #
96
- # Self closing tags such as <tt><br/></tt>, <tt><input/></tt>, etc are automatically closed
97
- # depending on output format, HTML vs XHTML.
98
- #
99
- # Boolean attributes like "<tt>selected</tt>", "<tt>checked</tt>" etc, are mirrored or
100
- # removed when <tt>true</tt> or <tt>false</tt>.
101
- #
102
- # ==== Examples
103
- #
104
- # Self closing tags:
105
- #
106
- # tag(:br)
107
- # # => <br> or <br/> if XHTML
108
- #
109
- # tag(:hr, class: "space")
110
- # # => <hr class="space">
111
- #
112
- # Multi line tags:
113
- #
114
- # tag(:div)
115
- # # => <div></div>
116
- #
117
- # tag(:div, 'content')
118
- # # => <div>content</div>
93
+ # Generates HTML tag markup based on the given name, content, and attributes
94
+ #
95
+ # @param name [String,Symbol] The tag name to generate (e.g. 'div', :span)
96
+ # @param content [String,nil] Optional content for the tag (ignored if block given)
97
+ # @param attrs [Hash] Optional HTML attributes hash that may include :newline toggle
98
+ # @yield Optional block providing content for the tag
99
+ #
100
+ # @return [String] The generated HTML tag markup
101
+ #
102
+ # @example Basic tag
103
+ # tag(:div) #=> <div></div>
104
+ #
105
+ # @example Tag with content
106
+ # tag(:p, "Hello") #=> <p>Hello</p>
107
+ #
108
+ # @example Tag with attributes
109
+ # tag(:div, class: 'btn', id: 'submit')
110
+ # #=> <div class="btn" id="submit"></div>
111
+ #
112
+ # @example Self closing tags:
113
+ # tag(:br) # => <br> / <br/>
114
+ #
115
+ # tag(:hr, class: "space") # => <hr class="space">
116
+ #
117
+ # @example Multi line tags:
118
+ # tag(:div, 'content') # => <div>content</div>
119
119
  #
120
120
  # tag(:div, 'content', id: 'comment')
121
- # # => <div id="comment">content</div>
121
+ # # => <div id="comment">content</div>
122
122
  #
123
123
  # tag(:div, id: 'comment') # NB! no content
124
- # # => <div id="comment"></div>
124
+ # # => <div id="comment"></div>
125
125
  #
126
- # Single line tags:
127
- #
126
+ # @example Single line tags:
128
127
  # tag(:h1,'Header')
129
- # # => <h1>Header</h1>
130
- #
128
+ # # => <h1>Header</h1>
129
+ #
131
130
  # tag(:abbr, 'WHO', :title => "World Health Organization")
132
- # # => <abbr title="World Health Organization">WHO</abbr>
133
- #
134
- # Working with blocks
135
- #
131
+ # # => <abbr title="World Health Organization">WHO</abbr>
132
+ #
133
+ # @example Tag with block
134
+ # tag(:div) { tag(:p, "Content") } #=> "<div><p>Content</p></div>"
135
+ #
136
+ # @example Working with blocks
136
137
  # tag(:div) do
137
138
  # tag(:p, 'Hello World')
138
139
  # end
139
- # # => <div><p>Hello World</p></div>
140
- #
140
+ # # => <div><p>Hello World</p></div>
141
+ #
141
142
  # <% tag(:div) do %>
142
143
  # <p>Paragraph 1</p>
143
144
  # <%= tag(:p, 'Paragraph 2') %>
144
145
  # <p>Paragraph 3</p>
145
146
  # <% end %>
146
- # # =>
147
- # <div>
148
- # <p>Paragraph 1</p>
149
- # <p>Paragraph 2</p>
150
- # <p>Paragraph 3</p>
151
- # </div>
152
- #
153
- #
154
- # # NB! ignored tag contents if given a block
147
+ # # => <div>
148
+ # # <p>Paragraph 1</p>
149
+ # # <p>Paragraph 2</p>
150
+ # # <p>Paragraph 3</p>
151
+ # # </div>
152
+ #
153
+ # NOTE! ignored tag contents if given a block
154
+ #
155
155
  # <% tag(:div, 'ignored tag-content') do %>
156
156
  # <%= tag(:label, 'Comments:', for: :comments) %>
157
157
  # <%= tag(:textarea,'textarea contents', id: :comments) %>
158
158
  # <% end %>
159
- # # =>
160
- # <div>
161
- # <label for="comments">Comments:</label>
162
- # <textarea id="comments">
163
- # textarea contents
164
- # </textarea>
165
- # </div>
166
- #
167
- #
168
- #
169
- # Boolean attributes:
170
- #
159
+ # # => <div>
160
+ # # <label for="comments">Comments:</label>
161
+ # # <textarea id="comments">
162
+ # # textarea contents
163
+ # # </textarea>
164
+ # # </div>
165
+ #
166
+ # @example Boolean attributes
171
167
  # tag(:input, type: :checkbox, checked: true)
172
- # # => <input type="checkbox" checked="checked">
173
- #
168
+ # # => <input type="checkbox" checked="checked">
169
+ #
174
170
  # tag(:option, 'Sinatra', value: "1", selected: true)
175
- # # => <option value="1" selected>Sinatra</option>
176
- #
171
+ # # => <option value="1" selected>Sinatra</option>
172
+ #
177
173
  # tag(:option, 'PHP', value: "0", selected: false)
178
- # # => <option value="0">PHP</option>
179
- #
174
+ # # => <option value="0">PHP</option>
175
+ #
176
+ # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
180
177
  def tag(*args, &block)
181
178
  name = args.first
182
179
  attrs = args.last.is_a?(Hash) ? args.pop : {}
183
180
  newline = attrs[:newline] # save before it gets tainted
184
-
185
- tag_content = block_given? ? capture_html(&block) : args[1] # content
186
-
181
+
182
+ tag_content = block_given? ? capture_html(&block) : args[1] # content
183
+
187
184
  if self_closing_tag?(name)
188
185
  tag_html = self_closing_tag(name, attrs)
189
186
  else
190
- tag_html = "#{open_tag(name, attrs)}#{tag_contents_for(name, tag_content, newline)}"
187
+ tag_html = "#{open_tag(name, attrs)}#{tag_contents_for(name, tag_content, newline)}"
191
188
  tag_html << closing_tag(name)
192
189
  end
193
190
  block_is_template?(block) ? concat_content(tag_html) : tag_html
194
191
  end
195
-
196
- # Update the +:class+ entry in the +attr+ hash with the given +classes+ and returns +attr+.
197
- #
198
- # attr = { class: 'alert', id: :idval }
199
- #
200
- # merge_attr_classes(attr, 'alert-info') #=> { class: 'alert alert-info', id: :idval }
201
- #
202
- # merge_attr_classes(attr, [:alert, 'alert-info'])
203
- # #=> { class: 'alert alert-info', id: :idval }
204
- #
192
+ # rubocop:enable Metrics/MethodLength, Metrics/AbcSize
193
+
194
+ # Updates the :class attribute in the given hash with additional class values.
195
+ # Takes a hash and any number of class values as arguments.
196
+ # Returns the modified hash with merged class values.
197
+ #
198
+ # @param attr [Hash] The attributes hash to modify
199
+ # @param classes [Array<String,Symbol,Array>] Additional class values to merge
200
+ #
201
+ # @return [Hash] The modified attributes hash
202
+ #
203
+ # @example
204
+ # attr = { class: 'btn', id: 'submit' }
205
+ # merge_attr_classes(attr, 'primary', 'large')
206
+ # #=> { class: 'btn large primary', id: 'submit' }
207
+ #
208
+ # attr = { id: 'submit' }
209
+ # merge_attr_classes(attr, ['btn', 'primary'])
210
+ # #=> { class: 'btn primary', id: 'submit' }
211
+ #
205
212
  def merge_attr_classes(attr, *classes)
206
213
  attr[:class] = [] if attr[:class].blank?
207
214
  attr[:class] = merge_classes(attr[:class], *classes)
208
215
  attr[:class] = nil if attr[:class] == '' # set to nil to remove from tag output
209
216
  attr
210
217
  end
211
-
212
- # Return an alphabetized string that includes all given class values.
213
- #
214
- # Handles a combination of arrays, strings & symbols being passed in.
215
- #
218
+
219
+ # Merges class values from multiple sources into a single sorted string
220
+ #
221
+ # @param classes [Array<String,Symbol,Array>] Class values to merge, which can be:
222
+ # - Symbols: Converted directly to strings
223
+ # - Strings: Split on whitespace into multiple classes
224
+ # - Arrays: Each element converted to string
225
+ #
226
+ # @return [String] Space-separated string of unique, sorted class names
227
+ #
228
+ # @example Passing a hash
216
229
  # attr = { class: 'alert', id: :idval }
217
- #
218
230
  # merge_classes(attr[:class], ['alert', 'alert-info']) #=> 'alert alert-info'
219
- #
231
+ #
220
232
  # merge_classes(attr[:class], :text) #=> 'alert text'
221
- #
222
- # merge_classes(attr[:class], [:text, :'alert-info']) #=> 'alert alert-info text'
223
- #
224
- #
233
+ #
234
+ # @example Passing a string, an array & symbol
235
+ # merge_classes('btn', ['primary', 'large'], :active) #=> "active btn large primary"
236
+ #
237
+ # @example Passing a string & :symbol
238
+ # merge_classes('alert alert-info', :text) #=> "alert alert-info text"
239
+ #
240
+ # rubocop:disable Metrics/AbcSize
225
241
  def merge_classes(*classes)
226
242
  klasses = []
227
243
  classes.each do |c|
228
244
  klasses << c.to_s if c.is_a?(Symbol)
229
- c.split(/\s+/).each { |x| klasses << x.to_s } if c.is_a?(String)
245
+ c.split(/\s+/).each { |x| klasses << x.to_s } if c.is_a?(String)
230
246
  c.each { |i| klasses << i.to_s } if c.is_a?(Array)
231
247
  end
232
248
  klasses.compact.uniq.sort.join(' ').strip
233
249
  end
234
-
235
-
250
+ # rubocop:enable Metrics/AbcSize
251
+
236
252
  ## HELPERS
237
-
238
-
239
- #
253
+
254
+ # Captures the content of a block with proper buffer handling
255
+ #
256
+ # @param block [String, Proc] The block to capture, defaults to empty string
257
+ #
258
+ # @return The captured content from the block
259
+ #
260
+ # @example Capturing a block's content
261
+ # capture { tag(:div, "content") } # => <div>content</div>
262
+ #
263
+ # @example Capturing with explicit block parameter
264
+ # capture(some_block) { yield } # => captured block content
265
+ #
240
266
  def capture(block = '') # :nodoc:
241
267
  buf_was = @output
242
- @output = block.is_a?(Proc) ? (eval('@_out_buf', block.binding) || @output) : block
268
+ @output = if block.is_a?(Proc)
269
+ eval('@_out_buf', block.binding, __FILE__, __LINE__ - 1) || @output
270
+ else
271
+ block
272
+ end
243
273
  yield
244
274
  ret = @output
245
275
  @output = buf_was
246
276
  ret
247
277
  end
248
278
 
249
- # Captures the html from a block of template code for erb or haml
250
- #
251
- # ==== Examples
252
- #
253
- # capture_html(&block) => "...html..."
254
- #
255
- def capture_html(*args, &block)
256
- if self.respond_to?(:is_haml?) && is_haml?
257
- block_is_haml?(block) ? capture_haml(*args, &block) : block.call
279
+ # Captures the content of a template block for Haml or ERB templates,
280
+ # returning the captured HTML
281
+ #
282
+ # @param args [Array] Arguments to pass to the block
283
+ # @param block [Proc] The template block to capture
284
+ #
285
+ # @return [String] The captured HTML content
286
+ #
287
+ # @example Capturing Haml content
288
+ # capture_html { tag :div, "Content" } # => <div>Content</div>
289
+ #
290
+ # @example Capturing ERB content
291
+ # capture_html { tag :p, "Content" } # => <p>Content</p>\n
292
+ #
293
+ # @example Direct block yield
294
+ # capture_html { "Content" } # => Content
295
+ #
296
+ def capture_html(*args, &block)
297
+ if respond_to?(:is_haml?) && is_haml?
298
+ block_is_haml?(block) ? capture_haml(*args, &block) : yield
258
299
  elsif erb_buffer?
259
300
  result_text = capture_block(*args, &block)
260
- result_text.present? ? result_text : (block_given? && block.call(*args))
301
+ result_text.present? ? result_text : (block_given? && yield(*args))
261
302
  else # theres no template to capture, invoke the block directly
262
- block.call(*args)
303
+ yield(*args)
263
304
  end
264
305
  end
265
306
 
266
- # Outputs the given text to the templates buffer directly.
267
- #
268
- # ==== Examples
269
- #
270
- # concat_content("This will be output to the template buffer in erb or haml")
271
- #
272
- def concat_content(text = '')
273
- if self.respond_to?(:is_haml?) && is_haml?
274
- haml_concat(text)
275
- elsif :erb_buffer?
276
- buffer_concat(text)
307
+ # Outputs the given text to the template buffer based on the template engine in use.
308
+ # For Haml templates, uses `haml_concat`. For ERB templates, uses `buffer_concat`.
309
+ # If no template engine is active, returns the text directly.
310
+ #
311
+ # @param text [String] The text to output, defaults to empty string
312
+ #
313
+ # @return [String] The text if no template engine is active
314
+ #
315
+ # @example With Haml template
316
+ # concat_content("Hello") # => Outputs "Hello" to Haml buffer
317
+ #
318
+ # @example With ERB template
319
+ # concat_content("World") # => Outputs "World" to ERB buffer
320
+ #
321
+ # @example With no template
322
+ # concat_content("Test") # => Returns "Test" string
323
+ #
324
+ def concat_content(text = '')
325
+ if respond_to?(:is_haml?) && is_haml?
326
+ haml_concat(text.to_s)
327
+ elsif erb_buffer?
328
+ buffer_concat(text.to_s)
277
329
  else # theres no template to concat, return the text directly
278
- text
330
+ text.to_s
279
331
  end
280
332
  end
281
333
 
282
- # Returns true if the block is from an ERB or HAML template; false otherwise.
283
- # Used to determine if html should be returned or concatenated to a view.
284
- #
285
- # ==== Examples
286
- #
287
- # block_is_template?(block)
288
- #
289
- def block_is_template?(block)
290
- block && (erb_block?(block) ||
291
- (self.respond_to?(:block_is_haml?) && block_is_haml?(block)))
334
+ # Returns true if the given block is from an ERB or HAML template
335
+ #
336
+ # @param block [Proc] The block to check
337
+ #
338
+ # @return [Boolean] true if block is from an ERB/HAML template, false otherwise
339
+ #
340
+ # @example
341
+ # # if block is from ERB template
342
+ # block_is_template?(some_block) #=> true
343
+ #
344
+ # # if block is from HAML template
345
+ # block_is_template?(some_block) #=> true
346
+ #
347
+ # block_is_template?(regular_block) #=> false
348
+ #
349
+ def block_is_template?(block)
350
+ block && (erb_block?(block) || (respond_to?(:block_is_haml?) && block_is_haml?(block)))
292
351
  end
293
-
294
- #
352
+
353
+ # Returns whether the current output format is XHTML based on tag configuration
354
+ #
355
+ # @return [Boolean] true if XHTML output is enabled, false for HTML output
356
+ #
357
+ # @example
358
+ # # if :tag_output_format_is_xhtml is true in config
359
+ # output_is_xhtml? #=> true
360
+ #
361
+ # # if :tag_output_format_is_xhtml is false in config
362
+ # output_is_xhtml? #=> false
363
+ #
295
364
  def output_is_xhtml?
296
365
  opts[:tags][:tag_output_format_is_xhtml]
297
366
  end
298
-
299
-
367
+
300
368
  private
301
-
302
-
303
- # Return an opening tag of _name_, with _attrs_.
304
- def open_tag(name, attrs = {})
369
+
370
+ # Returns an opening HTML tag string with the given name and optional attributes
371
+ #
372
+ # @param name [String,Symbol] The tag name (e.g. 'div', :span)
373
+ # @param attrs [Hash] Optional HTML attributes hash (e.g. {class: 'btn'})
374
+ #
375
+ # @return [String] The opening tag string (e.g. '<div class="btn">')
376
+ #
377
+ # @example Basic tag
378
+ # open_tag(:div) #=> "<div>"
379
+ #
380
+ # @example Tag with attributes
381
+ # open_tag(:div, class: 'btn', id: 'submit')
382
+ # #=> <div class="btn" id="submit">
383
+ #
384
+ def open_tag(name, attrs = {})
305
385
  "<#{name}#{normalize_html_attributes(attrs)}>"
306
386
  end
307
-
308
- # Return closing tag of _name_.
309
- def closing_tag(name)
387
+
388
+ # Returns a closing HTML tag string for the given tag name
389
+ #
390
+ # @param name [String,Symbol] The tag name to close (e.g. 'div', :span)
391
+ #
392
+ # @return [String] The closing tag string with optional newline
393
+ #
394
+ # @example Basic closing tag
395
+ # closing_tag(:div) #=> "</div>\n" # with newlines enabled
396
+ #
397
+ # @example Without newlines
398
+ # closing_tag(:span) #=> "</span>" # with newlines disabled
399
+ #
400
+ def closing_tag(name)
310
401
  "</#{name}>#{add_newline?}"
311
402
  end
312
-
313
- # Creates a self closing tag. Like <br/> or <img src="..."/>
314
- #
315
- # ==== Options
316
- # +name+ : the name of the tag to create
317
- # +attrs+ : a hash where all members will be mapped to key="value"
318
- #
319
- def self_closing_tag(name, attrs = {})
320
- newline = (attrs[:newline].nil?) ? nil : attrs.delete(:newline)
403
+
404
+ # Creates a self-closing HTML tag with optional attributes and newlines
405
+ #
406
+ # @param name [String,Symbol] The tag name (e.g. 'br', :img)
407
+ # @param attrs [Hash] Optional attrs hash including `{ newline: true } toggle
408
+ #
409
+ # @return [String] The self-closing tag string (e.g. '<br>' or '<br />' for XHTML)
410
+ #
411
+ # @example Basic self-closing tag
412
+ # self_closing_tag(:br) #=> "<br>" / "<br />" in XHTML
413
+ #
414
+ # @example With attributes and newlines
415
+ # self_closing_tag(:img, src: 'test.jpg', newline: true)
416
+ # #=> '<img src="test.jpg">\n' / '<img src="test.jpg" />\n'
417
+ #
418
+ def self_closing_tag(name, attrs = {})
419
+ newline = attrs[:newline].nil? ? nil : attrs.delete(:newline)
321
420
  "<#{name}#{normalize_html_attributes(attrs)}#{is_xhtml?}#{add_newline?(newline)}"
322
421
  end
323
-
324
- # Based upon the context, wraps the tag content in '\n' (newlines)
325
- #
326
- # ==== Examples
327
- #
328
- # tag_contents_for(:div, 'content', nil)
329
- # # => <div>content</div>
330
- #
331
- # tag_contents_for(:div, 'content', false)
332
- # # => <div>content</div>
333
- #
334
- # Single line tag
335
- # tag_contents_for(:option, 'content', true)
336
- # # => <option...>\ncontent\n</option>
337
- #
422
+
423
+ # Formats tag contents with appropriate newlines based on tag type and options
424
+ #
425
+ # @param name [String,Symbol] The tag name to check format rules against
426
+ # @param content [String] The content to be wrapped in the tag
427
+ # @param newline [Boolean,nil] Override flag for newline insertion, nil uses default
428
+ #
429
+ # @return [String] The formatted content with appropriate newlines
430
+ #
431
+ # @example Multi-line tag
432
+ # tag_contents_for(:div, 'content') #=> "\ncontent\n"
433
+ #
434
+ # @example Single-line tag with newlines
435
+ # tag_contents_for(:span, 'text', true) #=> "\ntext\n"
436
+ #
437
+ # @example Basic content
438
+ # tag_contents_for(:p, 'text') #=> "text"
439
+ #
338
440
  def tag_contents_for(name, content, newline = nil)
339
441
  if multi_line_tag?(name)
340
- "#{add_newline?(newline)}#{content}#{add_newline?(newline)}".gsub(/\n\n/, "\n")
442
+ "#{add_newline?(newline)}#{content}#{add_newline?(newline)}".gsub("\n\n", "\n")
341
443
  elsif single_line_tag?(name) && newline == true
342
444
  "#{add_newline?(newline)}#{content}#{add_newline?(newline)}"
343
445
  else
344
446
  content.to_s
345
447
  end
346
448
  end
347
-
348
- # Normalize _attrs_, replacing boolean keys with their mirrored values.
349
- def normalize_html_attributes(attrs = {})
449
+
450
+ # Normalizes HTML attributes handling special cases like data-* attributes
451
+ # and boolean attributes
452
+ #
453
+ # @param attrs [Hash] Hash of HTML attributes to normalize
454
+ #
455
+ # @return [String, nil] Normalized attributes string with leading space, nil if attrs empty
456
+ #
457
+ # @example Basic attributes
458
+ # normalize_html_attributes(class: 'btn', id: 'submit') #=> ' class="btn" id="submit"'
459
+ #
460
+ # @example Data attributes
461
+ # normalize_html_attributes(data: { value: 123 }) #=> ' data-value="123"'
462
+ #
463
+ # @example Boolean attributes
464
+ # normalize_html_attributes(checked: true, disabled: false) #=> ' checked="checked"'
465
+ #
466
+ # rubocop:disable Metrics/MethodLength, Metrics/CyclomaticComplexity
467
+ def normalize_html_attributes(attrs = {})
350
468
  return if attrs.blank?
469
+
351
470
  attrs.delete(:newline) # remove newline from attributes
352
471
  # look for data attrs
353
- if value = attrs.delete(:data)
472
+ if (value = attrs.delete(:data))
354
473
  # NB!! convert key to symbol for [].sort
355
- value.each { |k, v| attrs[:"data-#{k.to_s}"] = v }
474
+ value.each { |k, v| attrs[:"data-#{k}"] = v }
356
475
  end
357
476
  attrs.each do |name, val|
358
477
  if boolean_attribute?(name)
359
478
  val == true ? attrs[name] = name : attrs.delete(name)
360
479
  end
361
480
  end
362
- return attrs.empty? ? '' : ' ' + attrs.to_html_attributes
481
+ attrs.empty? ? '' : " #{attrs.to_html_attributes}"
363
482
  end
364
-
365
- # Check if _name_ is a boolean attribute.
366
- def boolean_attribute?(name)
483
+ # rubocop:enable Metrics/MethodLength, Metrics/CyclomaticComplexity
484
+
485
+ # Checks if the given attribute name is a boolean HTML attribute like checked, disabled, etc
486
+ #
487
+ # @param name [String,Symbol] The attribute name to check
488
+ #
489
+ # @return [Boolean] true if attribute is boolean, false otherwise
490
+ #
491
+ # @example Boolean attribute
492
+ # boolean_attribute?(:checked) #=> true
493
+ #
494
+ # @example Regular attribute
495
+ # boolean_attribute?(:class) #=> false
496
+ #
497
+ def boolean_attribute?(name)
367
498
  BOOLEAN_ATTRIBUTES.include?(name.to_s)
368
499
  end
369
-
370
- # Check if tag _name_ is a self-closing tag.
371
- def self_closing_tag?(name)
500
+
501
+ # Checks if the given tag name is a self-closing tag like <br>, <img>, etc
502
+ #
503
+ # @param name [String,Symbol] The tag name to check
504
+ #
505
+ # @return [Boolean] true if tag is self-closing, false otherwise
506
+ #
507
+ # @example Self-closing tag
508
+ # self_closing_tag?(:br) #=> true
509
+ #
510
+ # @example Regular tag
511
+ # self_closing_tag?(:div) #=> false
512
+ #
513
+ def self_closing_tag?(name)
372
514
  SELF_CLOSING_TAGS.include?(name.to_s)
373
515
  end
374
-
375
- # Check if tag _name_ is a single line tag.
376
- def single_line_tag?(name)
516
+
517
+ # Checks if the given tag name is a single-line tag that should be rendered without newlines
518
+ #
519
+ # @param name [String,Symbol] The tag name to check
520
+ #
521
+ # @return [Boolean] true if tag should be rendered on a single line, false otherwise
522
+ #
523
+ # @example Single-line tag
524
+ # single_line_tag?(:span) #=> true
525
+ #
526
+ # @example Multi-line tag
527
+ # single_line_tag?(:div) #=> false
528
+ #
529
+ def single_line_tag?(name)
377
530
  SINGLE_LINE_TAGS.include?(name.to_s)
378
531
  end
379
-
380
- # Check if tag _name_ is a multi line tag.
381
- def multi_line_tag?(name)
532
+
533
+ # Checks if the given tag name is a multi-line tag that should be rendered with newlines
534
+ #
535
+ # @param name [String,Symbol] The tag name to check
536
+ #
537
+ # @return [Boolean] true if tag should be rendered with multiple lines, false otherwise
538
+ #
539
+ # @example Multi-line tag
540
+ # multi_line_tag?(:div) #=> true
541
+ #
542
+ # @example Single-line tag
543
+ # multi_line_tag?(:span) #=> false
544
+ #
545
+ def multi_line_tag?(name)
382
546
  MULTI_LINE_TAGS.include?(name.to_s)
383
547
  end
384
-
385
- # Returns a '>' or ' />' string based on the output format used, ie: HTML vs XHTML
386
- def xhtml?
548
+
549
+ # Returns a string for closing self-closing tags based on HTML/XHTML format setting
550
+ #
551
+ # @return [String] Returns ' />' for XHTML format, '>' for HTML format
552
+ #
553
+ # @example With XHTML format
554
+ # # When tag_output_format_is_xhtml is true
555
+ # xhtml? #=> ' />'
556
+ #
557
+ # @example With HTML format
558
+ # # When tag_output_format_is_xhtml is false
559
+ # xhtml? #=> '>'
560
+ #
561
+ def xhtml?
387
562
  opts[:tags][:tag_output_format_is_xhtml] ? ' />' : '>'
388
563
  end
389
- alias_method :is_xhtml?, :xhtml?
390
-
391
- #
392
- def add_newline?(add_override = nil)
393
- add = (add_override.nil?) ? opts[:tags][:tag_add_newlines_after_tags] : add_override
564
+ alias is_xhtml? xhtml?
565
+
566
+ # Determines whether to add a newline based on override flag or default configuration
567
+ #
568
+ # @param add_override [Boolean, nil] Optional flag to override default newline behavior
569
+ # - When nil: Uses configured :tag_add_newlines_after_tags setting
570
+ # - When true/false: Uses override value directly
571
+ #
572
+ # @return [String] Returns "\n" for true, empty string for false
573
+ #
574
+ # @example Using default configuration
575
+ # add_newline? #=> "\n" # When tag_add_newlines_after_tags is true
576
+ #
577
+ # @example With override
578
+ # add_newline?(false) #=> "" # Forces no newline
579
+ #
580
+ def add_newline?(add_override = nil)
581
+ add = add_override.nil? ? opts[:tags][:tag_add_newlines_after_tags] : add_override
394
582
  add == true ? "\n" : ''
395
583
  end
396
-
397
- # concat contents to the buffer if present
398
- #
399
- # ==== Examples
400
- #
401
- # buffer_concat("Direct to buffer")
402
- #
584
+
585
+ # Appends text to the ERB output buffer if one exists
586
+ #
587
+ # @param txt [String] The text to append to the buffer
588
+ #
589
+ # @return [String, nil] The appended text if buffer exists, nil otherwise
590
+ #
591
+ # @example With active buffer
592
+ # buffer_concat("Hello") #=> "Hello" # Added to @_out_buf
593
+ #
594
+ # @example With no buffer
595
+ # buffer_concat("Hello") #=> nil # No buffer to append to
596
+ #
403
597
  def buffer_concat(txt)
404
598
  @_out_buf << txt if buffer?
405
599
  end
406
- alias_method :erb_concat, :buffer_concat
407
-
408
- # Used to capture the contents of html/ERB block
409
- #
410
- # ==== Examples
411
- #
412
- # capture_block(&block) => '...html...'
413
- #
414
- def capture_block(*args, &block)
415
- with_output_buffer { block_given? && block.call(*args) }
600
+ alias erb_concat buffer_concat
601
+
602
+ # Captures the contents of a given block by executing it with a temporary output buffer
603
+ #
604
+ # @param args [Array] Arguments to pass through to the yielded block
605
+ # @yield [*args] The block to capture contents from
606
+ #
607
+ # @return [String] The captured contents of the block, or nil if no block given
608
+ #
609
+ # @example Basic capture
610
+ # capture_block { "<div>content</div>" }
611
+ # #=> "<div>content</div>"
612
+ #
613
+ # @example With arguments
614
+ # capture_block("arg1", "arg2") { |a,b| "#{a} #{b}" }
615
+ # #=> "arg1 arg2"
616
+ #
617
+ def capture_block(*args)
618
+ with_output_buffer { block_given? && yield(*args) }
416
619
  end
417
- alias_method :capture_erb, :capture_block
418
-
419
- # Used to direct the buffer for the erb capture
620
+ alias capture_erb capture_block
621
+
622
+ # Temporarily swaps the output buffer with a new one during block execution
623
+ #
624
+ # @param buf [String] The new buffer to use temporarily, defaults to empty string
625
+ # @yield The block to execute with the temporary buffer
626
+ #
627
+ # @return [String] The contents of the temporary buffer after block execution
628
+ #
629
+ # @example Using temporary buffer
630
+ # with_output_buffer { @_out_buf << "content" } #=> "content"
631
+ #
632
+ # @example Restoring original buffer
633
+ # old_buf = @_out_buf
634
+ # with_output_buffer { "content" }
635
+ # @_out_buf == old_buf #=> true
636
+ #
420
637
  def with_output_buffer(buf = '')
421
- @_out_buf, old_buffer = buf, @_out_buf
638
+ old_buffer = @_out_buf
639
+ @_out_buf = buf
422
640
  yield
423
641
  @_out_buf
424
642
  ensure
425
643
  @_out_buf = old_buffer
426
644
  end
427
- alias_method :erb_with_output_buffer, :with_output_buffer
645
+ alias erb_with_output_buffer with_output_buffer
428
646
 
429
- # returns true if the buffer is not empty
430
- def buffer?
647
+ # Checks if an output buffer exists for template rendering
648
+ #
649
+ # @return [Boolean] true if @_out_buf is not nil, false otherwise
650
+ #
651
+ # @example With active buffer
652
+ # buffer? #=> true # when @_out_buf exists
653
+ #
654
+ # @example With no buffer
655
+ # buffer? #=> false # when @_out_buf is nil
656
+ #
657
+ def buffer?
431
658
  !@_out_buf.nil?
432
659
  end
433
- alias_method :have_buffer?, :buffer?
434
- alias_method :erb_buffer?, :buffer?
660
+ alias have_buffer? buffer?
661
+ alias erb_buffer? buffer?
435
662
 
663
+ # Checks if the given block is from an ERB template
664
+ #
665
+ # @param block [Proc] The block to check
666
+ #
667
+ # @return [Boolean] true if block is from ERB template or buffer exists, false otherwise
668
+ #
669
+ # @example With ERB template block
670
+ # erb_block?(erb_block) #=> true
671
+ #
672
+ # @example With regular block
673
+ # erb_block?(regular_block) #=> false
436
674
  #
437
675
  def erb_block?(block)
438
- have_buffer? || block && eval('defined? __in_erb_template', block.binding)
676
+ have_buffer? ||
677
+ (block && eval('defined? __in_erb_template', block.binding, __FILE__, __LINE__ - 1))
439
678
  end
440
- alias_method :is_erb_block?, :erb_block?
441
- alias_method :is_erb_template?, :erb_block?
442
-
443
-
444
- end # /InstanceMethods
445
-
446
- end # /RodaTags
447
-
679
+ alias is_erb_block? erb_block?
680
+ alias is_erb_template? erb_block?
681
+
682
+ # Checks if the given block is from a HAML template
683
+ #
684
+ # @param block [Proc] The block to check
685
+ #
686
+ # @return [Boolean] true if block is from HAML template, false otherwise
687
+ #
688
+ # @example With HAML template block
689
+ # haml_block?(haml_block) #=> true
690
+ #
691
+ # @example With regular block
692
+ # haml_block?(regular_block) #=> false
693
+ #
694
+ def haml_block?(block)
695
+ block && eval('defined? _hamlout', block.binding, __FILE__, __LINE__ - 1)
696
+ end
697
+ alias is_haml_block? haml_block?
698
+ alias is_haml_template? haml_block?
699
+ end
700
+ # rubocop:enable Metrics/ModuleLength
701
+ # /InstanceMethods
702
+ end
703
+ # /RodaTags
704
+
448
705
  register_plugin(:tags, RodaTags)
449
-
450
- end # /RodaPlugins
451
-
706
+ end
707
+ # /RodaPlugins
452
708
  end