roda-tags 0.1.2 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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)
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)
275
327
  elsif erb_buffer?
276
- buffer_concat(text)
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