Module: Docscribe::InlineRewriter::DocBlock
- Defined in:
- lib/docscribe/inline_rewriter/doc_block.rb
Overview
Text-preserving doc-block parsing and tag sorting helpers.
This module operates on existing comment blocks and is used to:
-
preserve user-authored tag text exactly
-
append generated missing tag entries
-
sort only configured sortable tags
-
preserve boundaries such as prose comments and blank comment lines
Defined Under Namespace
Classes: Entry
Class Attribute Summary collapse
- .generated ⇒ Object
- .index ⇒ Object
- .kind ⇒ Object
- .lines ⇒ Object
- .option_owner ⇒ Object
- .subject ⇒ Object
- .tag ⇒ Object
Class Method Summary collapse
-
.blank_comment_line?(line) ⇒ Boolean
Whether a line is a blank comment separator such as ‘#`.
-
.build_groups(entries) ⇒ Array<Array<Entry>>
Group entries so related ‘@option` tags stay attached to their owning `@param`.
-
.build_priority(tag_order) ⇒ Hash{String=>Integer}
Build a tag priority map from configured order.
-
.comment_line?(line) ⇒ Boolean
Whether a line is any comment line.
-
.consume_tag_entry(lines, start_idx, index:, sortable_tags:) ⇒ Array<(Entry, Integer)>
Consume one sortable top-level tag entry and its continuation lines.
-
.continuation_comment_line?(line) ⇒ Boolean
Whether a comment line should be treated as a continuation of the previous tag entry.
-
.extract_option_owner(line) ⇒ String?
Extract the owning options-hash param name from an ‘@option` line.
-
.extract_param_name(line) ⇒ String?
Extract a parameter name from a ‘@param` line.
-
.extract_subject(line, tag) ⇒ String?
Extract the grouping subject for a sortable tag.
-
.extract_tag(line) ⇒ String?
Extract a top-level tag name without the leading ‘@`.
-
.group_priority(group, priority) ⇒ Integer
Compute the priority of a grouped sortable unit.
-
.merge(existing_lines, missing_lines:, sort_tags:, tag_order:) ⇒ Array<String>
Merge existing doc lines with newly generated missing tag lines.
-
.normalized_tag_order(tag_order) ⇒ Array<String>
Normalize configured tag names by removing leading ‘@`.
-
.parse(lines, tag_order:) ⇒ Array<Entry>
Parse a doc block into structured entries.
-
.parse_generated(lines, tag_order:) ⇒ Array<Entry>
Parse generated missing tag lines and mark them as generated entries.
-
.render(entries) ⇒ Array<String>
Render parsed entries back into comment lines.
-
.sort(entries, tag_order:) ⇒ Array<Entry>
Sort parsed entries by configured tag order, preserving boundaries between tag runs.
-
.sort_run(entries, priority:) ⇒ Array<Entry>
Sort one contiguous run of sortable tag entries.
-
.sortable_top_level_tag_line?(line, sortable_tags) ⇒ Boolean
Whether a line is a sortable top-level tag line.
-
.top_level_tag_line?(line) ⇒ Boolean
Whether a line begins a top-level YARD-style tag.
Class Attribute Details
.generated ⇒ Object
47 48 49 50 51 52 53 54 55 56 |
# File 'lib/docscribe/inline_rewriter/doc_block.rb', line 47 Entry = Struct.new( :kind, :tag, :lines, :subject, :option_owner, :generated, :index, keyword_init: true ) |
.index ⇒ Object
47 48 49 50 51 52 53 54 55 56 |
# File 'lib/docscribe/inline_rewriter/doc_block.rb', line 47 Entry = Struct.new( :kind, :tag, :lines, :subject, :option_owner, :generated, :index, keyword_init: true ) |
.kind ⇒ Object
47 48 49 50 51 52 53 54 55 56 |
# File 'lib/docscribe/inline_rewriter/doc_block.rb', line 47 Entry = Struct.new( :kind, :tag, :lines, :subject, :option_owner, :generated, :index, keyword_init: true ) |
.lines ⇒ Object
47 48 49 50 51 52 53 54 55 56 |
# File 'lib/docscribe/inline_rewriter/doc_block.rb', line 47 Entry = Struct.new( :kind, :tag, :lines, :subject, :option_owner, :generated, :index, keyword_init: true ) |
.option_owner ⇒ Object
47 48 49 50 51 52 53 54 55 56 |
# File 'lib/docscribe/inline_rewriter/doc_block.rb', line 47 Entry = Struct.new( :kind, :tag, :lines, :subject, :option_owner, :generated, :index, keyword_init: true ) |
.subject ⇒ Object
47 48 49 50 51 52 53 54 55 56 |
# File 'lib/docscribe/inline_rewriter/doc_block.rb', line 47 Entry = Struct.new( :kind, :tag, :lines, :subject, :option_owner, :generated, :index, keyword_init: true ) |
.tag ⇒ Object
47 48 49 50 51 52 53 54 55 56 |
# File 'lib/docscribe/inline_rewriter/doc_block.rb', line 47 Entry = Struct.new( :kind, :tag, :lines, :subject, :option_owner, :generated, :index, keyword_init: true ) |
Class Method Details
.blank_comment_line?(line) ⇒ Boolean
module_function: when included, also defines #blank_comment_line? (instance visibility: private)
Whether a line is a blank comment separator such as ‘#`.
369 370 371 |
# File 'lib/docscribe/inline_rewriter/doc_block.rb', line 369 def blank_comment_line?(line) !!(line =~ /^\s*#\s*$/) end |
.build_groups(entries) ⇒ Array<Array<Entry>>
module_function: when included, also defines #build_groups (instance visibility: private)
Group entries so related ‘@option` tags stay attached to their owning `@param`.
187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 |
# File 'lib/docscribe/inline_rewriter/doc_block.rb', line 187 def build_groups(entries) groups = [] i = 0 while i < entries.length entry = entries[i] if entry.tag == 'param' group = [entry] i += 1 while i < entries.length && entries[i].tag == 'option' && entries[i].option_owner && entries[i].option_owner == entry.subject group << entries[i] i += 1 end groups << group else groups << [entry] i += 1 end end groups end |
.build_priority(tag_order) ⇒ Hash{String=>Integer}
module_function: when included, also defines #build_priority (instance visibility: private)
Build a tag priority map from configured order.
231 232 233 |
# File 'lib/docscribe/inline_rewriter/doc_block.rb', line 231 def build_priority(tag_order) normalized_tag_order(tag_order).each_with_index.to_h end |
.comment_line?(line) ⇒ Boolean
module_function: when included, also defines #comment_line? (instance visibility: private)
Whether a line is any comment line.
360 361 362 |
# File 'lib/docscribe/inline_rewriter/doc_block.rb', line 360 def comment_line?(line) !!(line =~ /^\s*#/) end |
.consume_tag_entry(lines, start_idx, index:, sortable_tags:) ⇒ Array<(Entry, Integer)>
module_function: when included, also defines #consume_tag_entry (instance visibility: private)
Consume one sortable top-level tag entry and its continuation lines.
Continuation lines are comment lines that belong to the same logical tag entry until a new sortable tag line or a blank comment separator is encountered.
255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 |
# File 'lib/docscribe/inline_rewriter/doc_block.rb', line 255 def consume_tag_entry(lines, start_idx, index:, sortable_tags:) first = lines[start_idx] tag = extract_tag(first) entry_lines = [first] i = start_idx + 1 while i < lines.length line = lines[i] break if sortable_top_level_tag_line?(line, ) break if blank_comment_line?(line) break unless continuation_comment_line?(line) entry_lines << line i += 1 end entry = Entry.new( kind: :tag, tag: tag, lines: entry_lines, subject: extract_subject(first, tag), option_owner: extract_option_owner(first), generated: false, index: index ) [entry, i] end |
.continuation_comment_line?(line) ⇒ Boolean
module_function: when included, also defines #continuation_comment_line? (instance visibility: private)
Whether a comment line should be treated as a continuation of the previous tag entry.
378 379 380 |
# File 'lib/docscribe/inline_rewriter/doc_block.rb', line 378 def continuation_comment_line?(line) !!(line =~ /^\s*#[ \t]{2,}\S/) end |
.extract_option_owner(line) ⇒ String?
module_function: when included, also defines #extract_option_owner (instance visibility: private)
Extract the owning options-hash param name from an ‘@option` line.
321 322 323 |
# File 'lib/docscribe/inline_rewriter/doc_block.rb', line 321 def extract_option_owner(line) line[/^\s*#\s*@option\b\s+(\S+)/, 1] end |
.extract_param_name(line) ⇒ String?
module_function: when included, also defines #extract_param_name (instance visibility: private)
Extract a parameter name from a ‘@param` line.
Supports both:
-
‘@param [Type] name`
-
‘@param name [Type]`
309 310 311 312 313 314 |
# File 'lib/docscribe/inline_rewriter/doc_block.rb', line 309 def extract_param_name(line) return Regexp.last_match(1) if line =~ /^\s*#\s*@param\b\s+\[[^\]]+\]\s+(\S+)/ return Regexp.last_match(1) if line =~ /^\s*#\s*@param\b\s+(\S+)\s+\[[^\]]+\]/ nil end |
.extract_subject(line, tag) ⇒ String?
module_function: when included, also defines #extract_subject (instance visibility: private)
Extract the grouping subject for a sortable tag.
Currently only ‘@param` entries carry a subject, used to keep `@option` tags attached.
293 294 295 296 297 298 |
# File 'lib/docscribe/inline_rewriter/doc_block.rb', line 293 def extract_subject(line, tag) case tag when 'param' extract_param_name(line) end end |
.extract_tag(line) ⇒ String?
module_function: when included, also defines #extract_tag (instance visibility: private)
Extract a top-level tag name without the leading ‘@`.
342 343 344 |
# File 'lib/docscribe/inline_rewriter/doc_block.rb', line 342 def extract_tag(line) line[/^\s*#\s*@(\w+)/, 1] end |
.group_priority(group, priority) ⇒ Integer
module_function: when included, also defines #group_priority (instance visibility: private)
Compute the priority of a grouped sortable unit.
222 223 224 |
# File 'lib/docscribe/inline_rewriter/doc_block.rb', line 222 def group_priority(group, priority) priority.fetch(group.first.tag, priority.length) end |
.merge(existing_lines, missing_lines:, sort_tags:, tag_order:) ⇒ Array<String>
module_function: when included, also defines #merge (instance visibility: private)
Merge existing doc lines with newly generated missing tag lines.
Existing text is preserved exactly. If sorting is enabled, only sortable tag runs are normalized according to the configured tag order.
69 70 71 72 73 74 75 76 77 |
# File 'lib/docscribe/inline_rewriter/doc_block.rb', line 69 def merge(existing_lines, missing_lines:, sort_tags:, tag_order:) existing_entries = parse(existing_lines, tag_order: tag_order) missing_entries = parse_generated(missing_lines, tag_order: tag_order) entries = existing_entries + missing_entries entries = sort(entries, tag_order: tag_order) if render(entries) end |
.normalized_tag_order(tag_order) ⇒ Array<String>
module_function: when included, also defines #normalized_tag_order (instance visibility: private)
Normalize configured tag names by removing leading ‘@`.
240 241 242 |
# File 'lib/docscribe/inline_rewriter/doc_block.rb', line 240 def normalized_tag_order(tag_order) Array(tag_order).map { |t| t.to_s.sub(/\A@/, '') } end |
.parse(lines, tag_order:) ⇒ Array<Entry>
module_function: when included, also defines #parse (instance visibility: private)
Parse a doc block into structured entries.
Only tags listed in ‘tag_order` are treated as sortable tag entries. Other lines become `:other` entries and act as sort boundaries.
101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 |
# File 'lib/docscribe/inline_rewriter/doc_block.rb', line 101 def parse(lines, tag_order:) = normalized_tag_order(tag_order) entries = [] i = 0 index = 0 while i < lines.length line = lines[i] if sortable_top_level_tag_line?(line, ) entry, i = consume_tag_entry(lines, i, index: index, sortable_tags: ) entries << entry else entries << Entry.new( kind: :other, lines: [line], generated: false, index: index ) i += 1 end index += 1 end entries end |
.parse_generated(lines, tag_order:) ⇒ Array<Entry>
module_function: when included, also defines #parse_generated (instance visibility: private)
Parse generated missing tag lines and mark them as generated entries.
85 86 87 88 89 90 |
# File 'lib/docscribe/inline_rewriter/doc_block.rb', line 85 def parse_generated(lines, tag_order:) parse(lines, tag_order: tag_order).map do |entry| entry.generated = true if entry.kind == :tag entry end end |
.render(entries) ⇒ Array<String>
module_function: when included, also defines #render (instance visibility: private)
Render parsed entries back into comment lines.
162 163 164 |
# File 'lib/docscribe/inline_rewriter/doc_block.rb', line 162 def render(entries) entries.flat_map(&:lines) end |
.sort(entries, tag_order:) ⇒ Array<Entry>
module_function: when included, also defines #sort (instance visibility: private)
Sort parsed entries by configured tag order, preserving boundaries between tag runs.
135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 |
# File 'lib/docscribe/inline_rewriter/doc_block.rb', line 135 def sort(entries, tag_order:) out = [] priority = build_priority(tag_order) i = 0 while i < entries.length if entries[i].kind == :tag run = [] while i < entries.length && entries[i].kind == :tag run << entries[i] i += 1 end out.concat(sort_run(run, priority: priority)) else out << entries[i] i += 1 end end out end |
.sort_run(entries, priority:) ⇒ Array<Entry>
module_function: when included, also defines #sort_run (instance visibility: private)
Sort one contiguous run of sortable tag entries.
172 173 174 175 176 177 178 179 180 |
# File 'lib/docscribe/inline_rewriter/doc_block.rb', line 172 def sort_run(entries, priority:) groups = build_groups(entries) groups .each_with_index .sort_by { |(group, idx)| [group_priority(group, priority), idx] } .map(&:first) .flatten end |
.sortable_top_level_tag_line?(line, sortable_tags) ⇒ Boolean
module_function: when included, also defines #sortable_top_level_tag_line? (instance visibility: private)
Whether a line is a sortable top-level tag line.
331 332 333 334 335 |
# File 'lib/docscribe/inline_rewriter/doc_block.rb', line 331 def sortable_top_level_tag_line?(line, ) return false unless top_level_tag_line?(line) .include?(extract_tag(line)) end |
.top_level_tag_line?(line) ⇒ Boolean
module_function: when included, also defines #top_level_tag_line? (instance visibility: private)
Whether a line begins a top-level YARD-style tag.
351 352 353 |
# File 'lib/docscribe/inline_rewriter/doc_block.rb', line 351 def top_level_tag_line?(line) !!(line =~ /^\s*#\s*@\w+/) end |