Module: RedQuilt::List
- Defined in:
- lib/red_quilt/list.rb
Overview
CommonMark spec 5.2 lists.
Module-level functions are stateless predicates used by BlockParser’s dispatch and paragraph-interruption logic. ‘List::Parser` holds a cached reference to its owning BlockParser (for parse_lines recursion and shared helpers) but no per-list state — a single Parser instance is reused for every list in the document, including nested ones, and the per-call state lives in method locals so reentrant calls are safe.
Defined Under Namespace
Classes: Parser
Class Method Summary collapse
- .build_match(leading, marker_width, marker, spaces_after, body, ordered:, start_number:) ⇒ Object
-
.column_width(whitespace, start_col) ⇒ Object
Returns the column width of ‘whitespace` if it begins at the absolute column `start_col`, expanding tabs to the next tab stop of 4.
-
.interrupts_paragraph?(li_match) ⇒ Boolean
CommonMark spec: a list item can only interrupt an open paragraph if it has visible content, and (for ordered lists) only if the start number is 1.
-
.match(text) ⇒ Object
Recognises the start of a list item per CommonMark spec section 5.2.
- .same_group?(expected, actual) ⇒ Boolean
Class Method Details
.build_match(leading, marker_width, marker, spaces_after, body, ordered:, start_number:) ⇒ Object
111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 |
# File 'lib/red_quilt/list.rb', line 111 def build_match(leading, marker_width, marker, spaces_after, body, ordered:, start_number:) if body.empty? # Marker followed by EOL: empty item content. content_indent = leading + marker_width + 1 content = "" elsif spaces_after >= 5 # Indented-code form: keep (spaces_after - 1) of the spaces in # the content so the body of the item is recognised as an # indented code block. content_indent = leading + marker_width + 1 content = (" " * (spaces_after - 1)) + body else content_indent = leading + marker_width + spaces_after content = body end { ordered: ordered, start_number: start_number, marker: marker, content: content, content_start: leading + marker_width + 1, content_indent: content_indent, } end |
.column_width(whitespace, start_col) ⇒ Object
Returns the column width of ‘whitespace` if it begins at the absolute column `start_col`, expanding tabs to the next tab stop of 4. `whitespace` must contain only 0x20/0x09 bytes.
99 100 101 102 103 104 105 106 107 108 109 |
# File 'lib/red_quilt/list.rb', line 99 def column_width(whitespace, start_col) col = start_col whitespace.each_byte do |b| if b == 0x20 col += 1 elsif b == 0x09 col = ((col / 4) + 1) * 4 end end col - start_col end |
.interrupts_paragraph?(li_match) ⇒ Boolean
CommonMark spec: a list item can only interrupt an open paragraph if it has visible content, and (for ordered lists) only if the start number is 1.
89 90 91 92 93 94 |
# File 'lib/red_quilt/list.rb', line 89 def interrupts_paragraph?(li_match) return false if li_match[:content].empty? return false if li_match[:ordered] && li_match[:start_number] != 1 true end |
.match(text) ⇒ Object
Recognises the start of a list item per CommonMark spec section 5.2.
Returns nil if ‘text` is not a list-item start, otherwise a Hash:
ordered: true (1. / 1)) or false (- / + / *)
start_number: Integer (0 for unordered)
marker: String, the marker character (".", ")", "-", "+", "*")
content: String, the body of the line as it should appear
inside the item (may include leading whitespace
when the marker was followed by 5+ spaces -- that
is the indented-code form).
content_start: Integer, byte offset into `text` where `content`
begins. Always (leading + marker_width + 1) in
absolute terms, regardless of spec form.
content_indent: Integer, the spec's N -- the indent level all
subsequent continuation lines must reach to stay
inside this item.
33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 |
# File 'lib/red_quilt/list.rb', line 33 def match(text) # Fast reject before touching the regex engine: a list item is at # most 3 leading spaces followed by a bullet (`* + -`) or a digit. # This runs on every line, so bailing here avoids a MatchData (plus # a `rest` substring and two marker-regex attempts) for the common # non-list line. i = 0 i += 1 while i < 3 && text.getbyte(i) == 0x20 c = text.getbyte(i) return nil if c.nil? return nil unless c == 0x2A || c == 0x2B || c == 0x2D || (c >= 0x30 && c <= 0x39) m = /\A( {0,3})/.match(text) leading = m[1].length rest = text[leading..] if (bm = /\A([*+-])(?:([ \t]+)(.*)|([ \t]*)\z)/.match(rest)) marker = bm[1] if bm[2] # `spaces_after` is column width, not byte length, so a tab # after the marker is billed as the number of cols needed to # reach the next tab stop (CommonMark Tabs section). spaces_after = column_width(bm[2], leading + 1) body = bm[3] else spaces_after = 0 body = "" end return build_match(leading, 1, marker, spaces_after, body, ordered: false, start_number: 0) end if (om = /\A(\d{1,9})([.)])(?:([ \t]+)(.*)|([ \t]*)\z)/.match(rest)) digits = om[1] marker = om[2] if om[3] spaces_after = column_width(om[3], leading + digits.length + 1) body = om[4] else spaces_after = 0 body = "" end return build_match(leading, digits.length + 1, marker, spaces_after, body, ordered: true, start_number: digits.to_i) end nil end |
.same_group?(expected, actual) ⇒ Boolean
82 83 84 |
# File 'lib/red_quilt/list.rb', line 82 def same_group?(expected, actual) expected[:ordered] == actual[:ordered] && expected[:marker] == actual[:marker] end |