coradoc-adoc 2.0.29 → 2.0.31
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/coradoc/asciidoc/model/list/definition_item.rb +13 -0
- data/lib/coradoc/asciidoc/model/text_element.rb +19 -3
- data/lib/coradoc/asciidoc/parser/base.rb +8 -0
- data/lib/coradoc/asciidoc/parser/inline.rb +85 -76
- data/lib/coradoc/asciidoc/parser/list.rb +124 -22
- data/lib/coradoc/asciidoc/parser/rule_dispatcher.rb +4 -2
- data/lib/coradoc/asciidoc/parser.rb +1 -0
- data/lib/coradoc/asciidoc/serializer/serializers/document.rb +1 -3
- data/lib/coradoc/asciidoc/transform/element_transformers/block_transformer.rb +85 -73
- data/lib/coradoc/asciidoc/transform/element_transformers/document_transformer.rb +1 -1
- data/lib/coradoc/asciidoc/transform/element_transformers/inline_transformer.rb +27 -22
- data/lib/coradoc/asciidoc/transform/element_transformers/list_transformer.rb +61 -11
- data/lib/coradoc/asciidoc/transform/from_core_model.rb +27 -11
- data/lib/coradoc/asciidoc/transformer/inline_rules.rb +5 -5
- data/lib/coradoc/asciidoc/transformer/list_rules.rb +100 -30
- data/lib/coradoc/asciidoc/transformer/table_layout.rb +3 -1
- data/lib/coradoc/asciidoc/transformer.rb +73 -8
- data/lib/coradoc/asciidoc/typographic_quotes.rb +37 -0
- data/lib/coradoc/asciidoc/version.rb +1 -1
- data/lib/coradoc/asciidoc.rb +1 -0
- metadata +3 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 22eb5a515f97c8954148e99ef280dd4bb8ac3bed03d59d745291dc3d7ac302c2
|
|
4
|
+
data.tar.gz: 0435ded18a2f7895426d1de9aba45c5c3ccaeea442c287fb1f5cd6d14581b117
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: cc35eb17f93b7c78fc2f935cf71ef18d76af08eb36a52ab7040d90e735c72a4d1f990339fc414803ccbf767d0b57bf636be79d76940fa8a091e52bcd37091544
|
|
7
|
+
data.tar.gz: 8c93968c63d400dfb86d0855c6bb1a2ebc6bbe200a85b07da5d2861771baeacf2beb11cd811c136e912fae302d0003c8aa822f12c73ef2ae5d127ced9caea829
|
|
@@ -33,6 +33,19 @@ module Coradoc
|
|
|
33
33
|
attribute :terms, Coradoc::AsciiDoc::Model::Term, collection: true
|
|
34
34
|
attribute :contents, Coradoc::AsciiDoc::Model::TextElement, collection: true
|
|
35
35
|
attribute :delimiter, :string, default: -> { '::' }
|
|
36
|
+
# `+`-continuation blocks attached to this dd (paragraphs,
|
|
37
|
+
# admonitions, delimited blocks). Same shape as Model::List::Item's
|
|
38
|
+
# attached collection — populated by the parser when an AsciiDoc
|
|
39
|
+
# `+` line follows the dd.
|
|
40
|
+
attribute :attached,
|
|
41
|
+
Coradoc::AsciiDoc::Model::Attached,
|
|
42
|
+
polymorphic: [
|
|
43
|
+
Coradoc::AsciiDoc::Model::Admonition,
|
|
44
|
+
Coradoc::AsciiDoc::Model::Paragraph,
|
|
45
|
+
Coradoc::AsciiDoc::Model::Block::Core
|
|
46
|
+
],
|
|
47
|
+
collection: true,
|
|
48
|
+
initialize_empty: true
|
|
36
49
|
attribute :nested,
|
|
37
50
|
Coradoc::AsciiDoc::Model::Base,
|
|
38
51
|
polymorphic: [Coradoc::AsciiDoc::Model::List::Definition],
|
|
@@ -50,16 +50,21 @@ module Coradoc
|
|
|
50
50
|
end
|
|
51
51
|
end
|
|
52
52
|
|
|
53
|
-
# Get text content as string
|
|
53
|
+
# Get text content as a string. Handles the polymorphic shape of
|
|
54
|
+
# `content` (String, Array of mixed String/Model elements, nested
|
|
55
|
+
# Serializable). When the content is an Array, each element is
|
|
56
|
+
# rendered via its canonical source representation — Model::Base
|
|
57
|
+
# elements go through `to_adoc`, Strings pass through unchanged —
|
|
58
|
+
# so a downstream re-parse (e.g. by `ToCoreModel.parse_inline_text`)
|
|
59
|
+
# sees the original inline-mark syntax rather than `#<…>` dumps.
|
|
54
60
|
#
|
|
55
61
|
# @return [String] The text content
|
|
56
|
-
#
|
|
57
62
|
def to_s
|
|
58
63
|
case content
|
|
59
64
|
when String
|
|
60
65
|
content
|
|
61
66
|
when Array
|
|
62
|
-
content.map(
|
|
67
|
+
content.map { |element| element_to_s(element) }.join
|
|
63
68
|
when Coradoc::AsciiDoc::Model::Base
|
|
64
69
|
content.to_adoc
|
|
65
70
|
when Lutaml::Model::Serializable
|
|
@@ -82,6 +87,17 @@ module Coradoc
|
|
|
82
87
|
def text
|
|
83
88
|
to_s
|
|
84
89
|
end
|
|
90
|
+
|
|
91
|
+
private
|
|
92
|
+
|
|
93
|
+
def element_to_s(element)
|
|
94
|
+
case element
|
|
95
|
+
when Coradoc::AsciiDoc::Model::Base then element.to_adoc
|
|
96
|
+
when String then element
|
|
97
|
+
when nil then ''
|
|
98
|
+
else element.to_s
|
|
99
|
+
end
|
|
100
|
+
end
|
|
85
101
|
end
|
|
86
102
|
end
|
|
87
103
|
end
|
|
@@ -88,6 +88,14 @@ module Coradoc
|
|
|
88
88
|
def rule_dispatch(rule_name, *, **)
|
|
89
89
|
RuleDispatcher.dispatch(self, rule_name, *, **)
|
|
90
90
|
end
|
|
91
|
+
|
|
92
|
+
# Per-instance dispatch cache used by RuleDispatcher. Encapsulated
|
|
93
|
+
# accessor — the dispatcher reads this instead of poking
|
|
94
|
+
# instance_variable_get/set. Lazy-initialized; returns the same
|
|
95
|
+
# Hash across calls on the same instance.
|
|
96
|
+
def _rule_dispatch_cache
|
|
97
|
+
@_rule_dispatch_cache ||= {}
|
|
98
|
+
end
|
|
91
99
|
end
|
|
92
100
|
|
|
93
101
|
# Wrap every parser rule for Parslet memoization. Must run after all
|
|
@@ -4,25 +4,18 @@ module Coradoc
|
|
|
4
4
|
module AsciiDoc
|
|
5
5
|
module Parser
|
|
6
6
|
module Inline
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
#
|
|
11
|
-
#
|
|
12
|
-
# The patterns MUST be recognised before +monospace_constrained+
|
|
13
|
-
# in the +inline+ alternation, otherwise the lone backtick in
|
|
14
|
-
# +`"`+ or +`"``+ fires monospace and the surrounding quote
|
|
15
|
-
# collapses to straight ASCII quotes wrapped around a spurious
|
|
16
|
-
# code span.
|
|
17
|
-
TYPOGRAPHIC_QUOTE_PATTERNS = {
|
|
18
|
-
'"`' => "“", # U+201C left double
|
|
19
|
-
'`"' => "”", # U+201D right double
|
|
20
|
-
"'`" => "‘", # U+2018 left single
|
|
21
|
-
"`'" => "’" # U+2019 right single
|
|
22
|
-
}.freeze
|
|
7
|
+
# Typographic quote patterns are owned by the shared
|
|
8
|
+
# {Coradoc::AsciiDoc::TypographicQuotes} module so both the
|
|
9
|
+
# parser and the transformer reference the same source of
|
|
10
|
+
# truth without one layer reaching across the other.
|
|
23
11
|
|
|
24
12
|
def typographic_quote
|
|
25
|
-
|
|
13
|
+
patterns = AsciiDoc::TypographicQuotes::PATTERNS
|
|
14
|
+
combined = patterns.reduce(nil) do |acc, pat|
|
|
15
|
+
atom = str(pat)
|
|
16
|
+
acc ? (acc | atom) : atom
|
|
17
|
+
end
|
|
18
|
+
combined.as(:typographic_quote)
|
|
26
19
|
end
|
|
27
20
|
|
|
28
21
|
def attribute_reference
|
|
@@ -31,19 +24,54 @@ module Coradoc
|
|
|
31
24
|
str('}')
|
|
32
25
|
end
|
|
33
26
|
|
|
27
|
+
# ── Constrained / unconstrained mark builders ──
|
|
28
|
+
#
|
|
29
|
+
# Single source of truth for the four constrained + four
|
|
30
|
+
# unconstrained inline mark rules. Before this extraction each
|
|
31
|
+
# rule hand-rolled the same open → content → close → lookahead
|
|
32
|
+
# shape with subtly different guards (presence checks, newline
|
|
33
|
+
# exclusions, inner-marker allowances). Now each rule is a
|
|
34
|
+
# 1-line wrapper over one of two parameterised builders.
|
|
35
|
+
#
|
|
36
|
+
# The constrained builder adds a `marker.absent?` guard on both
|
|
37
|
+
# ends so single-marker constrained never matches when a
|
|
38
|
+
# double-marker unconstrained is intended (e.g. `*` defers to
|
|
39
|
+
# `**`). The alternation order in `inline` handles this too,
|
|
40
|
+
# but the guard is belt-and-suspenders — it keeps each rule
|
|
41
|
+
# self-contained for unit testing.
|
|
42
|
+
#
|
|
43
|
+
# The unconstrained builder allows newlines in the content
|
|
44
|
+
# (Asciidoctor's behaviour for `__`, `**`, `##`, ``` `` ``` — a
|
|
45
|
+
# mark can span line breaks). This was Bug 15B's fix scope;
|
|
46
|
+
# highlight_unconstrained was previously inconsistent (excluded
|
|
47
|
+
# newlines). All four are now uniform.
|
|
48
|
+
|
|
49
|
+
def constrained_mark(marker, reject_paragraph_break: false, content: nil)
|
|
50
|
+
open_guard = str(marker) >> str(marker).absent?
|
|
51
|
+
content_rule = content || default_constrained_content(marker)
|
|
52
|
+
close_guard = str(marker) >> str(marker).absent?
|
|
53
|
+
sequence = open_guard >> content_rule >> close_guard
|
|
54
|
+
sequence >>= str("\n\n").absent? if reject_paragraph_break
|
|
55
|
+
sequence
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def default_constrained_content(marker)
|
|
59
|
+
match("[^#{marker}\n]").repeat(1).as(:text).repeat(1, 1)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def unconstrained_mark(marker)
|
|
63
|
+
double = marker * 2
|
|
64
|
+
str(double) >>
|
|
65
|
+
match("[^#{marker}]").repeat(1).as(:text).repeat(1, 1) >>
|
|
66
|
+
str(double)
|
|
67
|
+
end
|
|
68
|
+
|
|
34
69
|
def bold_constrained
|
|
35
|
-
(
|
|
36
|
-
match('[^*\n]').repeat(1).as(:text).repeat(1, 1) >>
|
|
37
|
-
str('*') >> str('*').absent? >>
|
|
38
|
-
str("\n\n").absent?
|
|
39
|
-
).as(:bold_constrained)
|
|
70
|
+
constrained_mark('*', reject_paragraph_break: true).as(:bold_constrained)
|
|
40
71
|
end
|
|
41
72
|
|
|
42
73
|
def bold_unconstrained
|
|
43
|
-
(
|
|
44
|
-
match('[^*]').repeat(1).as(:text).repeat(1, 1) >>
|
|
45
|
-
str('**')
|
|
46
|
-
).as(:bold_unconstrained)
|
|
74
|
+
unconstrained_mark('*').as(:bold_unconstrained)
|
|
47
75
|
end
|
|
48
76
|
|
|
49
77
|
def span_constrained
|
|
@@ -63,45 +91,28 @@ module Coradoc
|
|
|
63
91
|
end
|
|
64
92
|
|
|
65
93
|
def italic_constrained
|
|
66
|
-
(
|
|
67
|
-
match('[^_\n]').repeat(1).as(:text).repeat(1, 1) >>
|
|
68
|
-
str('_') >> str('_').absent?
|
|
69
|
-
).as(:italic_constrained)
|
|
94
|
+
constrained_mark('_').as(:italic_constrained)
|
|
70
95
|
end
|
|
71
96
|
|
|
72
97
|
def italic_unconstrained
|
|
73
|
-
(
|
|
74
|
-
match('[^_]').repeat(1).as(:text).repeat(1, 1) >>
|
|
75
|
-
str('__')
|
|
76
|
-
).as(:italic_unconstrained)
|
|
98
|
+
unconstrained_mark('_').as(:italic_unconstrained)
|
|
77
99
|
end
|
|
78
100
|
|
|
79
101
|
def highlight_constrained
|
|
80
|
-
(
|
|
81
|
-
match('[^#\n]').repeat(1).as(:text).repeat(1, 1) >>
|
|
82
|
-
str('#') >> str('#').absent?
|
|
83
|
-
).as(:highlight_constrained)
|
|
102
|
+
constrained_mark('#').as(:highlight_constrained)
|
|
84
103
|
end
|
|
85
104
|
|
|
86
105
|
def highlight_unconstrained
|
|
87
|
-
(
|
|
88
|
-
match('[^#\n]').repeat(1).as(:text).repeat(1, 1) >>
|
|
89
|
-
str('##')
|
|
90
|
-
).as(:highlight_unconstrained)
|
|
106
|
+
unconstrained_mark('#').as(:highlight_unconstrained)
|
|
91
107
|
end
|
|
92
108
|
|
|
93
109
|
def monospace_constrained
|
|
94
|
-
(
|
|
95
|
-
|
|
96
|
-
str('`') >> str('`').absent?
|
|
97
|
-
).as(:monospace_constrained)
|
|
110
|
+
content = constrained_span_content('`').as(:text).repeat(1, 1)
|
|
111
|
+
constrained_mark('`', content: content).as(:monospace_constrained)
|
|
98
112
|
end
|
|
99
113
|
|
|
100
114
|
def monospace_unconstrained
|
|
101
|
-
(
|
|
102
|
-
match('[^\`]').repeat(1).as(:text).repeat(1, 1) >>
|
|
103
|
-
str('``')
|
|
104
|
-
).as(:monospace_unconstrained)
|
|
115
|
+
unconstrained_mark('`').as(:monospace_unconstrained)
|
|
105
116
|
end
|
|
106
117
|
|
|
107
118
|
def superscript
|
|
@@ -219,33 +230,31 @@ module Coradoc
|
|
|
219
230
|
(str('\\') >> str("\n"))).as(:hard_line_break)
|
|
220
231
|
end
|
|
221
232
|
|
|
233
|
+
# Priority-ordered registry of inline rules. The ORDER IS LOAD-BEARING:
|
|
234
|
+
# typographic_quote MUST come before monospace_constrained (Bug 14);
|
|
235
|
+
# each unconstrained rule MUST come before its constrained sibling
|
|
236
|
+
# (`` `` `` before `` ` ``, `**` before `*`, `__` before `_`).
|
|
237
|
+
# Adding a new inline rule = appending one symbol here (OCP).
|
|
238
|
+
INLINE_RULE_ORDER = %i[
|
|
239
|
+
typographic_quote
|
|
240
|
+
bold_unconstrained bold_constrained
|
|
241
|
+
span_unconstrained span_constrained
|
|
242
|
+
italic_unconstrained italic_constrained
|
|
243
|
+
highlight_unconstrained highlight_constrained
|
|
244
|
+
monospace_unconstrained monospace_constrained
|
|
245
|
+
superscript subscript
|
|
246
|
+
attribute_reference
|
|
247
|
+
escaped_xref cross_reference
|
|
248
|
+
term_inline term_inline2
|
|
249
|
+
footnote stem
|
|
250
|
+
link inline_image
|
|
251
|
+
inline_passthrough
|
|
252
|
+
underline small
|
|
253
|
+
hard_line_break
|
|
254
|
+
].freeze
|
|
255
|
+
|
|
222
256
|
def inline
|
|
223
|
-
|
|
224
|
-
bold_unconstrained |
|
|
225
|
-
bold_constrained |
|
|
226
|
-
span_unconstrained |
|
|
227
|
-
span_constrained |
|
|
228
|
-
italic_unconstrained |
|
|
229
|
-
italic_constrained |
|
|
230
|
-
highlight_unconstrained |
|
|
231
|
-
highlight_constrained |
|
|
232
|
-
monospace_unconstrained |
|
|
233
|
-
monospace_constrained |
|
|
234
|
-
superscript |
|
|
235
|
-
subscript |
|
|
236
|
-
attribute_reference |
|
|
237
|
-
escaped_xref |
|
|
238
|
-
cross_reference |
|
|
239
|
-
term_inline |
|
|
240
|
-
term_inline2 |
|
|
241
|
-
footnote |
|
|
242
|
-
stem |
|
|
243
|
-
link |
|
|
244
|
-
inline_image |
|
|
245
|
-
inline_passthrough |
|
|
246
|
-
underline |
|
|
247
|
-
small |
|
|
248
|
-
hard_line_break
|
|
257
|
+
INLINE_RULE_ORDER.map { |name| public_send(name) }.reduce(:|)
|
|
249
258
|
end
|
|
250
259
|
|
|
251
260
|
def text_unformatted
|
|
@@ -53,7 +53,7 @@ module Coradoc
|
|
|
53
53
|
def olist_item(nesting_level = 1)
|
|
54
54
|
item = olist_marker(nesting_level).as(:marker) >>
|
|
55
55
|
match("\n").absent? >> space >>
|
|
56
|
-
(
|
|
56
|
+
(list_body_text_line >>
|
|
57
57
|
list_item_continuation_lines).as(:lines)
|
|
58
58
|
|
|
59
59
|
att = (list_continuation.present? >>
|
|
@@ -105,7 +105,7 @@ module Coradoc
|
|
|
105
105
|
item = ulist_marker(nesting_level).as(:marker) >>
|
|
106
106
|
str(' [[[').absent? >>
|
|
107
107
|
match("\n").absent? >> space >>
|
|
108
|
-
(
|
|
108
|
+
(list_body_text_line >>
|
|
109
109
|
list_item_continuation_lines).as(:lines)
|
|
110
110
|
|
|
111
111
|
att = (list_continuation.present? >>
|
|
@@ -128,14 +128,11 @@ module Coradoc
|
|
|
128
128
|
# id, table boundary, list continuation, or list prefix).
|
|
129
129
|
# `line_not_text?` (from Paragraph) is that exact lookahead.
|
|
130
130
|
#
|
|
131
|
-
#
|
|
132
|
-
#
|
|
133
|
-
#
|
|
134
|
-
# Using `text_line(true, ...)` here would let `line_ending.repeat(1)`
|
|
135
|
-
# greedy-match across blank lines, silently absorbing the
|
|
136
|
-
# follow-up paragraph into the last list item's content.
|
|
131
|
+
# Uses `list_body_text_line` (not raw `text_line`) so a hard break
|
|
132
|
+
# at end of one source line doesn't greedy-pull the next line's
|
|
133
|
+
# content via text_any — same fix as dlist.
|
|
137
134
|
def list_item_continuation_lines
|
|
138
|
-
(line_not_text? >>
|
|
135
|
+
(line_not_text? >> list_body_text_line).repeat(0)
|
|
139
136
|
end
|
|
140
137
|
|
|
141
138
|
def dlist_delimiter
|
|
@@ -161,23 +158,128 @@ module Coradoc
|
|
|
161
158
|
# the line as a continuation of the dlist item), not content.
|
|
162
159
|
# Consume it without capturing so downstream CoreModel text
|
|
163
160
|
# doesn't carry the source indentation into HTML/Markdown.
|
|
164
|
-
|
|
165
|
-
|
|
161
|
+
#
|
|
162
|
+
# Multi-line definitions: AsciiDoc joins consecutive non-blank
|
|
163
|
+
# lines into a single paragraph within the dd. We capture each
|
|
164
|
+
# source line so the transformer can join them.
|
|
165
|
+
#
|
|
166
|
+
# Two load-bearing guards:
|
|
167
|
+
# 1. `line_not_text?` — prevents matching a `+` (list
|
|
168
|
+
# continuation marker) or `[example]` (block attribute)
|
|
169
|
+
# as the dd's text content.
|
|
170
|
+
# 2. `dlist_definition_line` (custom text_line variant) —
|
|
171
|
+
# text_line's text_any is greedy and matches across
|
|
172
|
+
# newlines via hard_line_break, swallowing the next
|
|
173
|
+
# source line's content (e.g. the `+` marker on the
|
|
174
|
+
# line after a hard break). The custom matcher excludes
|
|
175
|
+
# hard_line_break from the inline set so each `:lines`
|
|
176
|
+
# entry corresponds to exactly one source line.
|
|
177
|
+
(line_not_text? >> match('[ \t]').repeat(0) >>
|
|
178
|
+
(list_body_text_line >> list_item_continuation_lines).as(:lines)
|
|
179
|
+
) >> empty_line.repeat(0)
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
# Single source line of text for any list item body (dlist dd,
|
|
183
|
+
# ulist item, olist item). Like text_line(false, unguarded: true)
|
|
184
|
+
# but uses `LIST_BODY_INLINE_RULE_NAMES` (excludes hard_line_break)
|
|
185
|
+
# so hard_break's ` +\n` consumption doesn't greedy-pull the next
|
|
186
|
+
# source line's content (e.g. the `+` line-continuation marker).
|
|
187
|
+
#
|
|
188
|
+
# Shared across list types — single source of truth for the
|
|
189
|
+
# "text inside a list item" grammar.
|
|
190
|
+
def list_body_text_line
|
|
191
|
+
literal_space? >>
|
|
192
|
+
list_body_text_any.as(:text) >>
|
|
193
|
+
list_body_line_ending
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
def list_body_text_any
|
|
197
|
+
(list_body_inline_except_hard_break |
|
|
198
|
+
list_body_text_unformatted.as(:text)).repeat(1)
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
def list_body_inline_except_hard_break
|
|
202
|
+
LIST_BODY_INLINE_RULE_NAMES.map { |name| public_send(name) }.reduce(:|)
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
# Single source of truth for "inline rules usable inside a list
|
|
206
|
+
# item body line". Mirrors INLINE_RULE_ORDER minus hard_line_break.
|
|
207
|
+
# If INLINE_RULE_ORDER changes, update this list (the
|
|
208
|
+
# list_body_inline_rule_names spec catches drift).
|
|
209
|
+
LIST_BODY_INLINE_RULE_NAMES = %i[
|
|
210
|
+
typographic_quote
|
|
211
|
+
bold_unconstrained bold_constrained
|
|
212
|
+
span_unconstrained span_constrained
|
|
213
|
+
italic_unconstrained italic_constrained
|
|
214
|
+
highlight_unconstrained highlight_constrained
|
|
215
|
+
monospace_unconstrained monospace_constrained
|
|
216
|
+
superscript subscript
|
|
217
|
+
attribute_reference
|
|
218
|
+
escaped_xref cross_reference
|
|
219
|
+
term_inline term_inline2
|
|
220
|
+
footnote stem
|
|
221
|
+
link inline_image
|
|
222
|
+
inline_passthrough
|
|
223
|
+
underline small
|
|
224
|
+
].freeze
|
|
225
|
+
|
|
226
|
+
def list_body_text_unformatted
|
|
227
|
+
(str('\\<<').absent? >>
|
|
228
|
+
list_body_inline_except_hard_break.absent? >>
|
|
229
|
+
list_body_hard_break_marker?.absent? >>
|
|
230
|
+
match("[^\n]")
|
|
231
|
+
).repeat(1)
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
def list_body_hard_break_marker?
|
|
235
|
+
(str(' +') >> str("\n")).present? |
|
|
236
|
+
(str('\\') >> str("\n")).present?
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
# Recognizes line endings for a list body source line, including
|
|
240
|
+
# the ` +\n` hard-break form. The hard_break marker is preserved
|
|
241
|
+
# as a `:hard_line_break` capture so downstream can render it
|
|
242
|
+
# as `<br>`.
|
|
243
|
+
def list_body_line_ending
|
|
244
|
+
(str(' +').as(:hard_line_break) >> line_ending.as(:line_break)) |
|
|
245
|
+
line_ending.as(:line_break) |
|
|
246
|
+
eof?
|
|
166
247
|
end
|
|
167
248
|
|
|
168
249
|
def dlist_item(_delimiter = nil)
|
|
169
|
-
#
|
|
170
|
-
#
|
|
171
|
-
# so the transformer's definition_list_item rule matches uniformly.
|
|
250
|
+
# AST shape: { terms: [single dlist_term], delimiter: <delim>,
|
|
251
|
+
# lines: <def or nil>, attached: [...] }
|
|
172
252
|
#
|
|
173
|
-
#
|
|
174
|
-
#
|
|
175
|
-
#
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
253
|
+
# ONE term per dlist_item. Multi-term `<dt>`'s and multi-level
|
|
254
|
+
# nesting are both handled by the transformer's `build_dlist_tree`,
|
|
255
|
+
# which inspects each item's `delimiter` to infer depth and
|
|
256
|
+
# merges consecutive same-depth term-only items into multi-term
|
|
257
|
+
# items. Keeping the parser's term-emission at one-per-item lets
|
|
258
|
+
# `delimiter` carry the nesting signal unambiguously — a parser
|
|
259
|
+
# that greedily groups `parent::` and `child:::` into one item
|
|
260
|
+
# loses the depth change between them.
|
|
261
|
+
#
|
|
262
|
+
# `repeat(1, 1).as(:terms)` ensures `:terms` is captured as a
|
|
263
|
+
# single-element Array (matching the shape transformer's `Array()`
|
|
264
|
+
# wrap expects) rather than a bare Hash — bare Hashes confuse
|
|
265
|
+
# `Array(hash)` into nested-pair conversion.
|
|
266
|
+
#
|
|
267
|
+
# Two forms:
|
|
268
|
+
# 1. multi-line: term on its own line, optional def on next line(s)
|
|
269
|
+
# 2. inline: term + def on same line
|
|
270
|
+
multi_line = dlist_term.repeat(1, 1).as(:terms) >> line_ending >>
|
|
271
|
+
empty_line.repeat(0) >> dlist_definition.maybe
|
|
272
|
+
inline = dlist_term.repeat(1, 1).as(:terms) >> space >> dlist_definition
|
|
273
|
+
|
|
274
|
+
item = multi_line | inline
|
|
275
|
+
|
|
276
|
+
attached = (list_continuation.present? >>
|
|
277
|
+
list_continuation >>
|
|
278
|
+
(admonition_line | paragraph | block)
|
|
279
|
+
).repeat(0).as(:attached)
|
|
280
|
+
item >>= attached.maybe
|
|
281
|
+
|
|
282
|
+
item.as(:definition_list_item)
|
|
181
283
|
end
|
|
182
284
|
end
|
|
183
285
|
end
|
|
@@ -119,8 +119,10 @@ module Coradoc
|
|
|
119
119
|
end
|
|
120
120
|
|
|
121
121
|
def dispatch_cache(parser_instance)
|
|
122
|
-
|
|
123
|
-
|
|
122
|
+
# Per-instance memoization of dispatch rules. Exposed via
|
|
123
|
+
# the public `_rule_dispatch_cache` method on Parser::Base
|
|
124
|
+
# so we don't bypass encapsulation with instance_variable_*.
|
|
125
|
+
parser_instance._rule_dispatch_cache
|
|
124
126
|
end
|
|
125
127
|
|
|
126
128
|
def dispatch?
|
|
@@ -24,9 +24,7 @@ module Coradoc
|
|
|
24
24
|
# Frontmatter prefix, if present. The raw YAML text is
|
|
25
25
|
# already canonical (single source of truth is Codec), so we
|
|
26
26
|
# only wrap it with `---` delimiters.
|
|
27
|
-
if @model.frontmatter && !@model.frontmatter.strip.empty?
|
|
28
|
-
parts << "---\n#{@model.frontmatter.chomp}\n---\n\n"
|
|
29
|
-
end
|
|
27
|
+
parts << "---\n#{@model.frontmatter.chomp}\n---\n\n" if @model.frontmatter && !@model.frontmatter.strip.empty?
|
|
30
28
|
|
|
31
29
|
# Only add leading newline if we have sections but no header
|
|
32
30
|
parts << "\n" if @model.sections && !@model.sections.empty? && !@model.header
|