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

Class Method Summary collapse

Class Attribute Details

.generatedObject

Parameters:

  • value (Object)

Returns:

  • (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
)

.indexObject

Parameters:

  • value (Object)

Returns:

  • (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
)

.kindObject

Parameters:

  • value (Object)

Returns:

  • (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
)

.linesObject

Parameters:

  • value (Object)

Returns:

  • (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_ownerObject

Parameters:

  • value (Object)

Returns:

  • (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
)

.subjectObject

Parameters:

  • value (Object)

Returns:

  • (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
)

.tagObject

Parameters:

  • value (Object)

Returns:

  • (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

Note:

module_function: when included, also defines #blank_comment_line? (instance visibility: private)

Whether a line is a blank comment separator such as ‘#`.

Parameters:

  • line (String)

Returns:

  • (Boolean)


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>>

Note:

module_function: when included, also defines #build_groups (instance visibility: private)

Group entries so related ‘@option` tags stay attached to their owning `@param`.

Parameters:

Returns:



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}

Note:

module_function: when included, also defines #build_priority (instance visibility: private)

Build a tag priority map from configured order.

Parameters:

  • tag_order (Array<String>)

Returns:

  • (Hash{String=>Integer})


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

Note:

module_function: when included, also defines #comment_line? (instance visibility: private)

Whether a line is any comment line.

Parameters:

  • line (String)

Returns:

  • (Boolean)


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)>

Note:

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.

Parameters:

  • lines (Array<String>)
  • start_idx (Integer)
  • index (Integer)

    stable original index

  • sortable_tags (Array<String>)

Returns:

  • (Array<(Entry, Integer)>)

    parsed entry and next index



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, sortable_tags)
    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

Note:

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.

Parameters:

  • line (String)

Returns:

  • (Boolean)


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?

Note:

module_function: when included, also defines #extract_option_owner (instance visibility: private)

Extract the owning options-hash param name from an ‘@option` line.

Parameters:

  • line (String)

Returns:

  • (String, nil)


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?

Note:

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]`

Parameters:

  • line (String)

Returns:

  • (String, nil)


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?

Note:

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.

Parameters:

  • line (String)
  • tag (String)

Returns:

  • (String, nil)


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?

Note:

module_function: when included, also defines #extract_tag (instance visibility: private)

Extract a top-level tag name without the leading ‘@`.

Parameters:

  • line (String)

Returns:

  • (String, nil)


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

Note:

module_function: when included, also defines #group_priority (instance visibility: private)

Compute the priority of a grouped sortable unit.

Parameters:

  • group (Array<Entry>)
  • priority (Hash{String=>Integer})

Returns:

  • (Integer)


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>

Note:

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.

Parameters:

  • existing_lines (Array<String>)

    existing doc block lines

  • missing_lines (Array<String>)

    generated tag lines to add

  • sort_tags (Boolean)

    whether sortable tags should be reordered

  • tag_order (Array<String>)

    configured sortable tag order

Returns:

  • (Array<String>)


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 sort_tags

  render(entries)
end

.normalized_tag_order(tag_order) ⇒ Array<String>

Note:

module_function: when included, also defines #normalized_tag_order (instance visibility: private)

Normalize configured tag names by removing leading ‘@`.

Parameters:

  • tag_order (Array<String>)

Returns:

  • (Array<String>)


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>

Note:

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.

Parameters:

  • lines (Array<String>)

    comment block lines

  • tag_order (Array<String>)

    configured sortable tag order

Returns:



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:)
  sortable_tags = normalized_tag_order(tag_order)
  entries = []
  i = 0
  index = 0

  while i < lines.length
    line = lines[i]

    if sortable_top_level_tag_line?(line, sortable_tags)
      entry, i = consume_tag_entry(lines, i, index: index, sortable_tags: 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>

Note:

module_function: when included, also defines #parse_generated (instance visibility: private)

Parse generated missing tag lines and mark them as generated entries.

Parameters:

  • lines (Array<String>)

    generated lines

  • tag_order (Array<String>)

    configured sortable tag order

Returns:



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>

Note:

module_function: when included, also defines #render (instance visibility: private)

Render parsed entries back into comment lines.

Parameters:

Returns:

  • (Array<String>)


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>

Note:

module_function: when included, also defines #sort (instance visibility: private)

Sort parsed entries by configured tag order, preserving boundaries between tag runs.

Parameters:

  • entries (Array<Entry>)

    parsed entries

  • tag_order (Array<String>)

    configured sortable tag order

Returns:



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>

Note:

module_function: when included, also defines #sort_run (instance visibility: private)

Sort one contiguous run of sortable tag entries.

Parameters:

  • entries (Array<Entry>)

    contiguous tag run

  • priority (Hash{String=>Integer})

    tag priority map

Returns:



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

Note:

module_function: when included, also defines #sortable_top_level_tag_line? (instance visibility: private)

Whether a line is a sortable top-level tag line.

Parameters:

  • line (String)
  • sortable_tags (Array<String>)

Returns:

  • (Boolean)


331
332
333
334
335
# File 'lib/docscribe/inline_rewriter/doc_block.rb', line 331

def sortable_top_level_tag_line?(line, sortable_tags)
  return false unless top_level_tag_line?(line)

  sortable_tags.include?(extract_tag(line))
end

.top_level_tag_line?(line) ⇒ Boolean

Note:

module_function: when included, also defines #top_level_tag_line? (instance visibility: private)

Whether a line begins a top-level YARD-style tag.

Parameters:

  • line (String)

Returns:

  • (Boolean)


351
352
353
# File 'lib/docscribe/inline_rewriter/doc_block.rb', line 351

def top_level_tag_line?(line)
  !!(line =~ /^\s*#\s*@\w+/)
end