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
-
.add_continuation_lines(lines, start_idx, result, sortable_tags) ⇒ void
Append continuation lines to the result array until a non-continuation line is found.
-
.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_other_entry(line, index) ⇒ Entry
Create an :other entry for a non-tag line (prose, blank separators, etc.).
-
.build_param_group(entries, idx, entry) ⇒ Array<Entry>
Build a group starting with a @param entry and including its following @option entries.
-
.build_priority(tag_order) ⇒ Hash{String=>Integer}
Build a tag priority map from configured order.
-
.build_tag_entry(first, tag, entry_lines, index) ⇒ Entry
Build a tag Entry struct with metadata from the parsed tag line and continuation lines.
-
.collect_continuation_lines(lines, start_idx, first, sortable_tags) ⇒ Array<String>
Collect the first tag line and all continuation lines belonging to the same entry.
-
.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.
-
.consume_tag_run(entries, idx) ⇒ Array<(Array<Entry>, Integer)>
Collect a contiguous run of :tag entries starting at idx.
-
.continuation_candidate?(line, sortable_tags) ⇒ Boolean
Check whether a line can serve as a continuation of the current tag entry.
-
.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 ‘@`.
-
.filter_existing_entries(entries, filter_existing) ⇒ Array<Entry>
Remove existing entries matching the filter criteria (param names or return tag).
-
.filter_param_entry?(entry, param_names) ⇒ Boolean
Check whether an entry is a @param tag whose name is in the filter list.
-
.filter_return_entry?(entry, filter_return) ⇒ Boolean
Check whether an entry is a @return tag that should be filtered.
-
.group_entries_loop(entries, groups) ⇒ void
Iterate entries to build sorted groups, attaching @option entries to their @param.
-
.group_one_entry(entries, idx, groups) ⇒ Integer
Group a single entry, creating a param group with @option children if applicable.
-
.group_priority(group, priority) ⇒ Integer
Compute the priority of a grouped sortable unit.
-
.merge(existing_lines, missing_lines:, sort_tags:, tag_order:, filter_existing: {}) ⇒ 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.
-
.parse_lines(lines, sortable_tags, entries:, index:) ⇒ Array<Entry>
Iterate through all lines and parse each one into a structured entry.
-
.parse_one_line(lines, idx, sortable_tags, entries, index) ⇒ Integer
Parse a single line as a sortable tag entry or non-tag content.
-
.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_loop(entries, out, priority) ⇒ void
Iterate entries, sorting contiguous tag runs while preserving non-tag boundaries.
-
.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
.add_continuation_lines(lines, start_idx, result, sortable_tags) ⇒ void
module_function: when included, also defines #add_continuation_lines (instance visibility: private)
This method returns an undefined value.
Append continuation lines to the result array until a non-continuation line is found.
394 395 396 397 398 399 400 401 402 403 |
# File 'lib/docscribe/inline_rewriter/doc_block.rb', line 394 def add_continuation_lines(lines, start_idx, result, ) i = start_idx while i < lines.length line = lines[i] break unless continuation_candidate?(line, ) result << line i += 1 end end |
.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 ‘#`.
521 522 523 |
# File 'lib/docscribe/inline_rewriter/doc_block.rb', line 521 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`.
266 267 268 269 270 |
# File 'lib/docscribe/inline_rewriter/doc_block.rb', line 266 def build_groups(entries) groups = [] #: Array[untyped] group_entries_loop(entries, groups) groups end |
.build_other_entry(line, index) ⇒ Entry
module_function: when included, also defines #build_other_entry (instance visibility: private)
Create an :other entry for a non-tag line (prose, blank separators, etc.).
183 184 185 |
# File 'lib/docscribe/inline_rewriter/doc_block.rb', line 183 def build_other_entry(line, index) Entry.new(kind: :other, lines: [line], generated: false, index: index) end |
.build_param_group(entries, idx, entry) ⇒ Array<Entry>
module_function: when included, also defines #build_param_group (instance visibility: private)
Build a group starting with a @param entry and including its following @option entries.
309 310 311 312 313 314 315 316 317 318 319 320 321 322 |
# File 'lib/docscribe/inline_rewriter/doc_block.rb', line 309 def build_param_group(entries, idx, entry) group = [entry] idx += 1 while idx < entries.length && entries[idx].tag == 'option' && entries[idx].option_owner && entries[idx].option_owner == entry.subject group << entries[idx] idx += 1 end group 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.
339 340 341 |
# File 'lib/docscribe/inline_rewriter/doc_block.rb', line 339 def build_priority(tag_order) normalized_tag_order(tag_order).each_with_index.to_h end |
.build_tag_entry(first, tag, entry_lines, index) ⇒ Entry
module_function: when included, also defines #build_tag_entry (instance visibility: private)
Build a tag Entry struct with metadata from the parsed tag line and continuation lines.
425 426 427 428 429 430 431 432 433 434 435 |
# File 'lib/docscribe/inline_rewriter/doc_block.rb', line 425 def build_tag_entry(first, tag, entry_lines, index) Entry.new( kind: :tag, tag: tag, lines: entry_lines, subject: extract_subject(first, tag), option_owner: extract_option_owner(first), generated: false, index: index ) end |
.collect_continuation_lines(lines, start_idx, first, sortable_tags) ⇒ Array<String>
module_function: when included, also defines #collect_continuation_lines (instance visibility: private)
Collect the first tag line and all continuation lines belonging to the same entry.
380 381 382 383 384 |
# File 'lib/docscribe/inline_rewriter/doc_block.rb', line 380 def collect_continuation_lines(lines, start_idx, first, ) result = [first] add_continuation_lines(lines, start_idx, result, ) result end |
.comment_line?(line) ⇒ Boolean
module_function: when included, also defines #comment_line? (instance visibility: private)
Whether a line is any comment line.
512 513 514 |
# File 'lib/docscribe/inline_rewriter/doc_block.rb', line 512 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.
363 364 365 366 367 368 369 370 |
# File 'lib/docscribe/inline_rewriter/doc_block.rb', line 363 def consume_tag_entry(lines, start_idx, index:, sortable_tags:) first = lines[start_idx] tag = extract_tag(first) entry_lines = collect_continuation_lines(lines, start_idx + 1, first, ) i = entry_lines.length + start_idx entry = build_tag_entry(first, tag, entry_lines, index) [entry, i] end |
.consume_tag_run(entries, idx) ⇒ Array<(Array<Entry>, Integer)>
module_function: when included, also defines #consume_tag_run (instance visibility: private)
Collect a contiguous run of :tag entries starting at idx.
227 228 229 230 231 232 233 234 |
# File 'lib/docscribe/inline_rewriter/doc_block.rb', line 227 def consume_tag_run(entries, idx) run = [] #: Array[untyped] while idx < entries.length && entries[idx].kind == :tag run << entries[idx] idx += 1 end [run, idx] end |
.continuation_candidate?(line, sortable_tags) ⇒ Boolean
module_function: when included, also defines #continuation_candidate? (instance visibility: private)
Check whether a line can serve as a continuation of the current tag entry.
411 412 413 414 415 |
# File 'lib/docscribe/inline_rewriter/doc_block.rb', line 411 def continuation_candidate?(line, ) !sortable_top_level_tag_line?(line, ) && !blank_comment_line?(line) && continuation_comment_line?(line) 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.
530 531 532 |
# File 'lib/docscribe/inline_rewriter/doc_block.rb', line 530 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.
473 474 475 |
# File 'lib/docscribe/inline_rewriter/doc_block.rb', line 473 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]`
461 462 463 464 465 466 |
# File 'lib/docscribe/inline_rewriter/doc_block.rb', line 461 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.
445 446 447 448 449 450 |
# File 'lib/docscribe/inline_rewriter/doc_block.rb', line 445 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 ‘@`.
494 495 496 |
# File 'lib/docscribe/inline_rewriter/doc_block.rb', line 494 def extract_tag(line) line[/^\s*#\s*@(\w+)/, 1] end |
.filter_existing_entries(entries, filter_existing) ⇒ Array<Entry>
module_function: when included, also defines #filter_existing_entries (instance visibility: private)
Remove existing entries matching the filter criteria (param names or return tag).
98 99 100 101 102 103 104 |
# File 'lib/docscribe/inline_rewriter/doc_block.rb', line 98 def filter_existing_entries(entries, filter_existing) filter_param_names = filter_existing[:param_names] || [] filter_return = !!filter_existing[:return] entries.reject do |entry| filter_param_entry?(entry, filter_param_names) || filter_return_entry?(entry, filter_return) end end |
.filter_param_entry?(entry, param_names) ⇒ Boolean
module_function: when included, also defines #filter_param_entry? (instance visibility: private)
Check whether an entry is a @param tag whose name is in the filter list.
112 113 114 |
# File 'lib/docscribe/inline_rewriter/doc_block.rb', line 112 def filter_param_entry?(entry, param_names) entry.kind == :tag && entry.tag == 'param' && param_names.include?(entry.subject) end |
.filter_return_entry?(entry, filter_return) ⇒ Boolean
module_function: when included, also defines #filter_return_entry? (instance visibility: private)
Check whether an entry is a @return tag that should be filtered.
122 123 124 |
# File 'lib/docscribe/inline_rewriter/doc_block.rb', line 122 def filter_return_entry?(entry, filter_return) entry.kind == :tag && entry.tag == 'return' && filter_return end |
.group_entries_loop(entries, groups) ⇒ void
module_function: when included, also defines #group_entries_loop (instance visibility: private)
This method returns an undefined value.
Iterate entries to build sorted groups, attaching @option entries to their @param.
278 279 280 281 |
# File 'lib/docscribe/inline_rewriter/doc_block.rb', line 278 def group_entries_loop(entries, groups) idx = 0 idx = group_one_entry(entries, idx, groups) while idx < entries.length end |
.group_one_entry(entries, idx, groups) ⇒ Integer
module_function: when included, also defines #group_one_entry (instance visibility: private)
Group a single entry, creating a param group with @option children if applicable.
290 291 292 293 294 295 296 297 298 299 300 |
# File 'lib/docscribe/inline_rewriter/doc_block.rb', line 290 def group_one_entry(entries, idx, groups) entry = entries[idx] if entry.tag == 'param' group = build_param_group(entries, idx, entry) groups << group idx + group.size else groups << [entry] idx + 1 end 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.
330 331 332 |
# File 'lib/docscribe/inline_rewriter/doc_block.rb', line 330 def group_priority(group, priority) priority.fetch(group.first.tag, priority.length) end |
.merge(existing_lines, missing_lines:, sort_tags:, tag_order:, filter_existing: {}) ⇒ 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.
70 71 72 73 74 75 76 77 |
# File 'lib/docscribe/inline_rewriter/doc_block.rb', line 70 def merge(existing_lines, missing_lines:, sort_tags:, tag_order:, filter_existing: {}) existing_entries = parse(existing_lines, tag_order: tag_order) missing_entries = parse_generated(missing_lines, tag_order: tag_order) existing_entries = filter_existing_entries(existing_entries, filter_existing) 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 ‘@`.
348 349 350 |
# File 'lib/docscribe/inline_rewriter/doc_block.rb', line 348 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.
135 136 137 138 |
# File 'lib/docscribe/inline_rewriter/doc_block.rb', line 135 def parse(lines, tag_order:) = normalized_tag_order(tag_order) parse_lines(lines, , entries: [], index: 0) 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 |
.parse_lines(lines, sortable_tags, entries:, index:) ⇒ Array<Entry>
module_function: when included, also defines #parse_lines (instance visibility: private)
Iterate through all lines and parse each one into a structured entry.
148 149 150 151 152 153 154 155 |
# File 'lib/docscribe/inline_rewriter/doc_block.rb', line 148 def parse_lines(lines, , entries:, index:) idx = 0 while idx < lines.length idx = parse_one_line(lines, idx, , entries, index) index += 1 end entries end |
.parse_one_line(lines, idx, sortable_tags, entries, index) ⇒ Integer
module_function: when included, also defines #parse_one_line (instance visibility: private)
Parse a single line as a sortable tag entry or non-tag content.
166 167 168 169 170 171 172 173 174 175 |
# File 'lib/docscribe/inline_rewriter/doc_block.rb', line 166 def parse_one_line(lines, idx, , entries, index) if sortable_top_level_tag_line?(lines[idx], ) entry, idx = consume_tag_entry(lines, idx, index: index, sortable_tags: ) entries << entry else entries << build_other_entry(lines[idx], index) idx += 1 end idx end |
.render(entries) ⇒ Array<String>
module_function: when included, also defines #render (instance visibility: private)
Render parsed entries back into comment lines.
241 242 243 |
# File 'lib/docscribe/inline_rewriter/doc_block.rb', line 241 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.
193 194 195 196 197 198 |
# File 'lib/docscribe/inline_rewriter/doc_block.rb', line 193 def sort(entries, tag_order:) out = [] #: Array[untyped] priority = build_priority(tag_order) sort_loop(entries, out, priority) out end |
.sort_loop(entries, out, priority) ⇒ void
module_function: when included, also defines #sort_loop (instance visibility: private)
This method returns an undefined value.
Iterate entries, sorting contiguous tag runs while preserving non-tag boundaries.
207 208 209 210 211 212 213 214 215 216 217 218 219 |
# File 'lib/docscribe/inline_rewriter/doc_block.rb', line 207 def sort_loop(entries, out, priority) idx = 0 while idx < entries.length if entries[idx].kind == :tag run, idx = consume_tag_run(entries, idx) out.concat(sort_run(run, priority: priority)) else out << entries[idx] idx += 1 end end 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.
251 252 253 254 255 256 257 258 259 |
# File 'lib/docscribe/inline_rewriter/doc_block.rb', line 251 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.
483 484 485 486 487 |
# File 'lib/docscribe/inline_rewriter/doc_block.rb', line 483 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.
503 504 505 |
# File 'lib/docscribe/inline_rewriter/doc_block.rb', line 503 def top_level_tag_line?(line) !!(line =~ /^\s*#\s*@\w+/) end |