Module: Coradoc::AsciiDoc::Parser::List
- Defined in:
- lib/coradoc/asciidoc/parser/list.rb
Constant Summary collapse
- LIST_BODY_INLINE_RULE_NAMES =
Single source of truth for "inline rules usable inside a list item body line". Mirrors INLINE_RULE_ORDER minus hard_line_break. If INLINE_RULE_ORDER changes, update this list (the list_body_inline_rule_names spec catches drift).
%i[ typographic_quote bold_unconstrained bold_constrained span_unconstrained span_constrained italic_unconstrained italic_constrained highlight_unconstrained highlight_constrained monospace_unconstrained monospace_constrained superscript subscript attribute_reference escaped_xref cross_reference term_inline term_inline2 footnote stem link inline_image inline_passthrough underline small ].freeze
Instance Method Summary collapse
-
#asterisk_marker(nesting_level) ⇒ Object
AsciiDoc standard bullet:
*,**,***, ... -
#dash_marker(nesting_level) ⇒ Object
Markdown-style dash bullet:
-. - #definition_list(_delimiter = nil) ⇒ Object
- #dlist_definition ⇒ Object
- #dlist_delimiter ⇒ Object
- #dlist_item(_delimiter = nil) ⇒ Object
- #dlist_term(_delimiter = nil) ⇒ Object
- #list(nesting_level = 1) ⇒ Object
-
#list_attached_block ⇒ Object
Single source of truth for "what block can attach to a list item via a
+continuation marker". - #list_body_hard_break_marker? ⇒ Boolean
- #list_body_inline_except_hard_break ⇒ Object
-
#list_body_line_ending ⇒ Object
Recognizes line endings for a list body source line, including the
+\nhard-break form. - #list_body_text_any ⇒ Object
-
#list_body_text_line ⇒ Object
Single source line of text for any list item body (dlist dd, ulist item, olist item).
- #list_body_text_unformatted ⇒ Object
- #list_continuation ⇒ Object
-
#list_item_attached ⇒ Object
Shared
+-continuation attachment for every list item type (ulist, olist, dlist). -
#list_item_continuation_lines ⇒ Object
Continuation lines of a list item's first paragraph.
-
#list_item_separator ⇒ Object
Single source of truth for "what can appear between consecutive list items without ending the list".
- #list_marker(nesting_level = 1) ⇒ Object
- #olist_item(nesting_level = 1) ⇒ Object
- #olist_marker(nesting_level = 1) ⇒ Object
- #ordered_list(nesting_level = 1) ⇒ Object
- #ulist_item(nesting_level = 1) ⇒ Object
- #ulist_marker(nesting_level = 1) ⇒ Object
- #unordered_list(nesting_level = 1) ⇒ Object
Instance Method Details
#asterisk_marker(nesting_level) ⇒ Object
AsciiDoc standard bullet: *, **, ***, ... matching the
nesting level. Excludes table delimiters (|===) and deeper
asterisk runs that belong to a sibling level.
115 116 117 118 119 |
# File 'lib/coradoc/asciidoc/parser/list.rb', line 115 def asterisk_marker(nesting_level) str('*' * nesting_level) >> str('*').absent? >> str('===').absent? end |
#dash_marker(nesting_level) ⇒ Object
Markdown-style dash bullet: -. Accepted only at the top level
because Markdown nests via indentation rather than multi-char
markers — deeper levels stay on the AsciiDoc * form. Guards
exclude em-dashes (--), delimited-block fences (----),
and negative-number runs (-1, -42) which are not list
markers in any common dialect.
127 128 129 130 131 132 133 |
# File 'lib/coradoc/asciidoc/parser/list.rb', line 127 def dash_marker(nesting_level) return match('').absent? unless nesting_level == 1 str('-') >> str('-').absent? >> match('[0-9]').absent? end |
#definition_list(_delimiter = nil) ⇒ Object
67 68 69 70 71 |
# File 'lib/coradoc/asciidoc/parser/list.rb', line 67 def definition_list(_delimiter = nil) (attribute_list >> newline).maybe >> (list_item_separator >> dlist_item).repeat(1).as(:definition_list) >> dlist_item.absent? end |
#dlist_definition ⇒ Object
182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 |
# File 'lib/coradoc/asciidoc/parser/list.rb', line 182 def dlist_definition # AsciiDoc convention: the definition body is indented relative # to the term. That leading whitespace is structural (marks # the line as a continuation of the dlist item), not content. # Consume it without capturing so downstream CoreModel text # doesn't carry the source indentation into HTML/Markdown. # # Multi-line definitions: AsciiDoc joins consecutive non-blank # lines into a single paragraph within the dd. We capture each # source line so the transformer can join them. # # Two load-bearing guards: # 1. `line_not_text?` — prevents matching a `+` (list # continuation marker) or `[example]` (block attribute) # as the dd's text content. # 2. `dlist_definition_line` (custom text_line variant) — # text_line's text_any is greedy and matches across # newlines via hard_line_break, swallowing the next # source line's content (e.g. the `+` marker on the # line after a hard break). The custom matcher excludes # hard_line_break from the inline set so each `:lines` # entry corresponds to exactly one source line. (line_not_text? >> match('[ \t]').repeat(0) >> (list_body_text_line >> list_item_continuation_lines).as(:lines) ) >> empty_line.repeat(0) end |
#dlist_delimiter ⇒ Object
165 166 167 168 169 170 171 172 173 |
# File 'lib/coradoc/asciidoc/parser/list.rb', line 165 def dlist_delimiter ( (str(':::::') >> match(':').absent?) | (str('::::') >> match(':').absent?) | (str(':::') >> match(':').absent?) | (str('::') >> match(':').absent?) | str(';;') ).as(:delimiter) end |
#dlist_item(_delimiter = nil) ⇒ Object
276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 |
# File 'lib/coradoc/asciidoc/parser/list.rb', line 276 def dlist_item(_delimiter = nil) # AST shape: { terms: [single dlist_term], delimiter: <delim>, # lines: <def or nil>, attached: [...] } # # ONE term per dlist_item. Multi-term `<dt>`'s and multi-level # nesting are both handled by the transformer's `build_dlist_tree`, # which inspects each item's `delimiter` to infer depth and # merges consecutive same-depth term-only items into multi-term # items. Keeping the parser's term-emission at one-per-item lets # `delimiter` carry the nesting signal unambiguously — a parser # that greedily groups `parent::` and `child:::` into one item # loses the depth change between them. # # `repeat(1, 1).as(:terms)` ensures `:terms` is captured as a # single-element Array (matching the shape transformer's `Array()` # wrap expects) rather than a bare Hash — bare Hashes confuse # `Array(hash)` into nested-pair conversion. # # Two forms: # 1. multi-line: term on its own line, optional def on next line(s) # 2. inline: term + def on same line multi_line = dlist_term.repeat(1, 1).as(:terms) >> line_ending >> empty_line.repeat(0) >> dlist_definition.maybe inline = dlist_term.repeat(1, 1).as(:terms) >> space >> dlist_definition item = multi_line | inline item >>= list_item_attached.maybe # A `+` line directly before a continuing dlist run is a # list-continuation marker (asciidoctor semantics): consume it # so the following items stay in the current list and nest by # delimiter depth. Without this, the `+` dangles and the run # splits off as a detached sibling list. item >>= (list_continuation >> dlist_item.present?).repeat(0) item.as(:definition_list_item) end |
#dlist_term(_delimiter = nil) ⇒ Object
175 176 177 178 179 180 |
# File 'lib/coradoc/asciidoc/parser/list.rb', line 175 def dlist_term(_delimiter = nil) term_chars = (dlist_delimiter.absent? >> match("[^\n]")).repeat(1) .as(:text) (element_id_inline.maybe >> term_chars).as(:dlist_term) >> dlist_delimiter end |
#list(nesting_level = 1) ⇒ Object
8 9 10 11 12 13 14 |
# File 'lib/coradoc/asciidoc/parser/list.rb', line 8 def list(nesting_level = 1) ( unordered_list(nesting_level) | ordered_list(nesting_level) | definition_list ).as(:list) end |
#list_attached_block ⇒ Object
Single source of truth for "what block can attach to a list
item via a + continuation marker". The + line is consumed
by #list_item_attached; this rule expresses only the block
that follows. Definition lists are intentionally excluded —
+ before a continuing dlist run is handled inside
dlist_item as a continuation marker so the run nests by
delimiter depth (see there).
38 39 40 |
# File 'lib/coradoc/asciidoc/parser/list.rb', line 38 def list_attached_block admonition_line | unordered_list(1) | ordered_list(1) | paragraph | block end |
#list_body_hard_break_marker? ⇒ Boolean
261 262 263 264 |
# File 'lib/coradoc/asciidoc/parser/list.rb', line 261 def list_body_hard_break_marker? (str(' +') >> str("\n")).present? | (str('\\') >> str("\n")).present? end |
#list_body_inline_except_hard_break ⇒ Object
228 229 230 |
# File 'lib/coradoc/asciidoc/parser/list.rb', line 228 def list_body_inline_except_hard_break LIST_BODY_INLINE_RULE_NAMES.map { |name| public_send(name) }.reduce(:|) end |
#list_body_line_ending ⇒ Object
Recognizes line endings for a list body source line, including
the +\n hard-break form. The hard_break marker is preserved
as a :hard_line_break capture so downstream can render it
as <br>.
270 271 272 273 274 |
# File 'lib/coradoc/asciidoc/parser/list.rb', line 270 def list_body_line_ending (str(' +').as(:hard_line_break) >> line_ending.as(:line_break)) | line_ending.as(:line_break) | eof? end |
#list_body_text_any ⇒ Object
223 224 225 226 |
# File 'lib/coradoc/asciidoc/parser/list.rb', line 223 def list_body_text_any (list_body_inline_except_hard_break | list_body_text_unformatted.as(:text)).repeat(1) end |
#list_body_text_line ⇒ Object
Single source line of text for any list item body (dlist dd,
ulist item, olist item). Like text_line(false, unguarded: true)
but uses LIST_BODY_INLINE_RULE_NAMES (excludes hard_line_break)
so hard_break's +\n consumption doesn't greedy-pull the next
source line's content (e.g. the + line-continuation marker).
Shared across list types — single source of truth for the "text inside a list item" grammar.
217 218 219 220 221 |
# File 'lib/coradoc/asciidoc/parser/list.rb', line 217 def list_body_text_line literal_space? >> list_body_text_any.as(:text) >> list_body_line_ending end |
#list_body_text_unformatted ⇒ Object
253 254 255 256 257 258 259 |
# File 'lib/coradoc/asciidoc/parser/list.rb', line 253 def list_body_text_unformatted (str('\\<<').absent? >> list_body_inline_except_hard_break.absent? >> list_body_hard_break_marker?.absent? >> match("[^\n]") ).repeat(1) end |
#list_continuation ⇒ Object
16 17 18 |
# File 'lib/coradoc/asciidoc/parser/list.rb', line 16 def list_continuation line_start? >> str("+\n") end |
#list_item_attached ⇒ Object
Shared +-continuation attachment for every list item type
(ulist, olist, dlist). A + line directly followed by an
attached block becomes a child of the current item. The
list_continuation.present? lookahead prevents greedy
over-match when the + actually belongs to surrounding
context.
48 49 50 51 52 53 |
# File 'lib/coradoc/asciidoc/parser/list.rb', line 48 def list_item_attached (list_continuation.present? >> list_continuation >> list_attached_block ).repeat(0).as(:attached) end |
#list_item_continuation_lines ⇒ Object
Continuation lines of a list item's first paragraph. AsciiDoc
joins consecutive non-blank lines into one paragraph within the
item, but the lines must not start a sibling construct (another
list marker, block delimiter, attribute list, section, element
id, table boundary, list continuation, or list prefix).
line_not_text? (from Paragraph) is that exact lookahead.
Uses list_body_text_line (not raw text_line) so a hard break
at end of one source line doesn't greedy-pull the next line's
content via text_any — same fix as dlist.
161 162 163 |
# File 'lib/coradoc/asciidoc/parser/list.rb', line 161 def list_item_continuation_lines (line_not_text? >> list_body_text_line).repeat(0) end |
#list_item_separator ⇒ Object
Single source of truth for "what can appear between consecutive
list items without ending the list". Asciidoctor treats blank
lines and // comment lines as non-terminating inside every
list type. Comments are consumed without capture — they are
structurally invisible inside a list (no CommentLine node is
emitted). Tag directives (// tag::) keep their separate
handling via comment_line_content's tag.absent? guard.
27 28 29 |
# File 'lib/coradoc/asciidoc/parser/list.rb', line 27 def list_item_separator (comment_line_content | empty_line).repeat(0) end |
#list_marker(nesting_level = 1) ⇒ Object
73 74 75 |
# File 'lib/coradoc/asciidoc/parser/list.rb', line 73 def list_marker(nesting_level = 1) olist_marker(nesting_level) | ulist_marker(nesting_level) end |
#olist_item(nesting_level = 1) ⇒ Object
88 89 90 91 92 93 94 95 96 97 98 99 100 101 |
# File 'lib/coradoc/asciidoc/parser/list.rb', line 88 def olist_item(nesting_level = 1) item = olist_marker(nesting_level).as(:marker) >> match("\n").absent? >> space >> (list_body_text_line >> list_item_continuation_lines).as(:lines) item >>= list_item_attached.maybe if nesting_level <= 4 item >>= (list_marker(nesting_level + 1).present? >> list(nesting_level + 1)).repeat(0).as(:nested) end olist_marker(nesting_level).present? >> item.as(:list_item) end |
#olist_marker(nesting_level = 1) ⇒ Object
77 78 79 80 81 82 83 84 85 86 |
# File 'lib/coradoc/asciidoc/parser/list.rb', line 77 def olist_marker(nesting_level = 1) # Don't match table cell format specs like ".2+^.^|" line_start? >> (nesting_level > 1 ? literal_space.maybe : str('')) >> str('.' * nesting_level) >> str('.').absent? >> ( (match['0-9.<>^'] | str('+')).repeat(0, 3) >> str('|') ).absent? end |
#ordered_list(nesting_level = 1) ⇒ Object
55 56 57 58 59 |
# File 'lib/coradoc/asciidoc/parser/list.rb', line 55 def ordered_list(nesting_level = 1) attrs = (attribute_list >> newline).maybe r = olist_item(nesting_level) attrs >> (list_item_separator >> r).repeat(1).as(:ordered) end |
#ulist_item(nesting_level = 1) ⇒ Object
135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 |
# File 'lib/coradoc/asciidoc/parser/list.rb', line 135 def ulist_item(nesting_level = 1) item = ulist_marker(nesting_level).as(:marker) >> str(' [[[').absent? >> match("\n").absent? >> space >> (list_body_text_line >> list_item_continuation_lines).as(:lines) item >>= list_item_attached.maybe if nesting_level <= 4 item >>= (list_marker(nesting_level + 1).present? >> list(nesting_level + 1)).repeat(0).as(:nested) end ulist_marker(nesting_level).present? >> item.as(:list_item) end |
#ulist_marker(nesting_level = 1) ⇒ Object
103 104 105 106 107 108 109 110 |
# File 'lib/coradoc/asciidoc/parser/list.rb', line 103 def ulist_marker(nesting_level = 1) line_start? >> (nesting_level > 1 ? literal_space.maybe : str('')) >> ( asterisk_marker(nesting_level) | dash_marker(nesting_level) ) end |
#unordered_list(nesting_level = 1) ⇒ Object
61 62 63 64 65 |
# File 'lib/coradoc/asciidoc/parser/list.rb', line 61 def unordered_list(nesting_level = 1) attrs = (attribute_list >> newline).maybe r = ulist_item(nesting_level) attrs >> (list_item_separator >> r).repeat(1).as(:unordered) end |