coradoc-adoc 2.0.18 → 2.0.20
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/parser/block.rb +55 -18
- data/lib/coradoc/asciidoc/transform/element_transformers/admonition_styles.rb +50 -0
- data/lib/coradoc/asciidoc/transform/element_transformers/block_transformer.rb +71 -14
- data/lib/coradoc/asciidoc/transform/element_transformers/document_transformer.rb +26 -0
- data/lib/coradoc/asciidoc/transform/element_transformers/include_transformer.rb +56 -0
- data/lib/coradoc/asciidoc/transform/element_transformers.rb +2 -0
- data/lib/coradoc/asciidoc/transform/from_core_model.rb +72 -2
- data/lib/coradoc/asciidoc/transform/from_core_model_registrations.rb +5 -0
- data/lib/coradoc/asciidoc/transform/to_core_model_registrations.rb +20 -6
- data/lib/coradoc/asciidoc/transformer/block_rules.rb +4 -0
- data/lib/coradoc/asciidoc/transformer/block_type_classifier.rb +9 -1
- data/lib/coradoc/asciidoc/version.rb +1 -1
- 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: fad1b11f8028b7157066b7a2d4474b2d20e3cab1e526e0347ca36015cdf3ad90
|
|
4
|
+
data.tar.gz: d143bb70ec7ab2e3a1656a5e9ed72d52bb0b2f1341b3566efa02ef44343a2044
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7848b8f80f104e960265ed5dbd295d4dfc7ad4cecb401b81765ae21ba6d16a31e02024ebfd7c65c1c86e0fd433512788a92ab4600b348293d895980bf2b5b446
|
|
7
|
+
data.tar.gz: c6a55d7b4abecd086fcbde8015fd54b217eb3a442f86699bd1ab110affc1f1dc20f9ff54ef24668dcc77fdcb1cf609b09651308bc4122ea47a05535e18585879
|
|
@@ -22,7 +22,8 @@ module Coradoc
|
|
|
22
22
|
# - "|" separates cells within the table
|
|
23
23
|
|
|
24
24
|
def block(n_deep = 3)
|
|
25
|
-
(
|
|
25
|
+
(markdown_code_block(n_deep) |
|
|
26
|
+
example_block(n_deep) |
|
|
26
27
|
sidebar_block(n_deep) |
|
|
27
28
|
source_block(n_deep) |
|
|
28
29
|
quote_block(n_deep) |
|
|
@@ -60,6 +61,38 @@ module Coradoc
|
|
|
60
61
|
block_style_exact(n_deep, '-', 2)
|
|
61
62
|
end
|
|
62
63
|
|
|
64
|
+
# Markdown-style fenced code block: triple-backtick (or longer)
|
|
65
|
+
# fence with an optional language tag on the opening line. Behaves
|
|
66
|
+
# as a verbatim source block — same model as `[source,lang]\n----`.
|
|
67
|
+
# Pragmatic permissiveness for content that originates from (or is
|
|
68
|
+
# edited alongside) Markdown; not standard AsciiDoc but widely
|
|
69
|
+
# accepted (GitHub's renderer treats ``` as a listing delimiter).
|
|
70
|
+
def markdown_code_block(n_deep = 3)
|
|
71
|
+
capture_key = :"md_fence_#{n_deep}"
|
|
72
|
+
opening_fence = str('`').repeat(3).capture(capture_key)
|
|
73
|
+
closing_fence = dynamic do |_s, c|
|
|
74
|
+
str(c.captures[capture_key].to_s.strip)
|
|
75
|
+
end
|
|
76
|
+
language = (space? >> match("[A-Za-z0-9_+.-]").repeat(1).as(:language)).maybe
|
|
77
|
+
|
|
78
|
+
block_content_with_closing = dynamic do |_s, c|
|
|
79
|
+
fence_str = c.captures[capture_key].to_s.strip
|
|
80
|
+
closing_pattern = closing_fence >> space? >> newline
|
|
81
|
+
|
|
82
|
+
content = text_line(false, unguarded: true, verbatim: true) |
|
|
83
|
+
empty_line.as(:line_break)
|
|
84
|
+
|
|
85
|
+
(closing_pattern.absent? >> content).repeat(1)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
block_header >>
|
|
89
|
+
line_start? >>
|
|
90
|
+
opening_fence.as(:delimiter) >> language >> newline >>
|
|
91
|
+
block_content_with_closing.as(:lines) >>
|
|
92
|
+
line_start? >>
|
|
93
|
+
closing_fence >> space? >> newline
|
|
94
|
+
end
|
|
95
|
+
|
|
63
96
|
def block_title
|
|
64
97
|
(line_start? >> block_delimiter.absent?) >>
|
|
65
98
|
str('.') >> space.absent? >> text.as(:title) >> newline
|
|
@@ -72,16 +105,29 @@ module Coradoc
|
|
|
72
105
|
end
|
|
73
106
|
|
|
74
107
|
def block_content(n_deep = 3)
|
|
75
|
-
|
|
108
|
+
block_container_content(n_deep).repeat(1)
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# Single source of truth for "what can appear inside a non-verbatim
|
|
112
|
+
# block container". Used by block_content, block_style (non-verbatim
|
|
113
|
+
# branch), and block_style_exact (non-verbatim branch).
|
|
114
|
+
#
|
|
115
|
+
# `table` is listed before `text_line` so a leading `|===` is parsed
|
|
116
|
+
# as a table rather than mis-bracketed as text. The recursive `block`
|
|
117
|
+
# alternative handles every other delimited block.
|
|
118
|
+
def block_container_content(n_deep)
|
|
119
|
+
c = table.as(:table)
|
|
120
|
+
c |= block_image
|
|
76
121
|
c |= block(n_deep - 1) if n_deep.positive?
|
|
77
122
|
c |= list
|
|
78
123
|
c |= text_line(false, unguarded: true)
|
|
79
124
|
c |= empty_line.as(:line_break)
|
|
80
|
-
c
|
|
125
|
+
c
|
|
81
126
|
end
|
|
82
127
|
|
|
83
|
-
# Block delimiter: 4+ identical characters
|
|
84
|
-
#
|
|
128
|
+
# Block delimiter: 4+ identical characters, or 2 dashes for open
|
|
129
|
+
# blocks, or 3+ backticks for Markdown-style code fences. Used by
|
|
130
|
+
# paragraph.rb to reject lines that look like block delimiters.
|
|
85
131
|
# NOTE: repeat(4,) means 4 or more (not exactly 4)
|
|
86
132
|
def block_delimiter
|
|
87
133
|
line_start? >>
|
|
@@ -91,7 +137,8 @@ module Coradoc
|
|
|
91
137
|
str('+') |
|
|
92
138
|
str('.') |
|
|
93
139
|
str('-')).repeat(4) | # 4+ characters for most blocks
|
|
94
|
-
str('-').repeat(2, 2)
|
|
140
|
+
str('-').repeat(2, 2) | # Exactly 2 for open block
|
|
141
|
+
str('`').repeat(3)) >> # 3+ for Markdown code fences
|
|
95
142
|
newline
|
|
96
143
|
end
|
|
97
144
|
|
|
@@ -120,12 +167,7 @@ module Coradoc
|
|
|
120
167
|
text_line(false, unguarded: true, verbatim: true) |
|
|
121
168
|
empty_line.as(:line_break)
|
|
122
169
|
else
|
|
123
|
-
|
|
124
|
-
c |= block(n_deep - 1) if n_deep.positive?
|
|
125
|
-
c |= list
|
|
126
|
-
c |= text_line(false, unguarded: true)
|
|
127
|
-
c |= empty_line.as(:line_break)
|
|
128
|
-
c
|
|
170
|
+
block_container_content(n_deep)
|
|
129
171
|
end
|
|
130
172
|
|
|
131
173
|
(closing_pattern.absent? >> content).repeat(1)
|
|
@@ -178,12 +220,7 @@ module Coradoc
|
|
|
178
220
|
text_line(false, unguarded: true, verbatim: true) |
|
|
179
221
|
empty_line.as(:line_break)
|
|
180
222
|
else
|
|
181
|
-
|
|
182
|
-
alt |= block(n_deep - 1) if n_deep.positive?
|
|
183
|
-
alt |= list
|
|
184
|
-
alt |= text_line(false, unguarded: true)
|
|
185
|
-
alt |= empty_line.as(:line_break)
|
|
186
|
-
alt
|
|
223
|
+
block_container_content(n_deep)
|
|
187
224
|
end
|
|
188
225
|
|
|
189
226
|
(closing_pattern.absent? >> content).repeat(1)
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Coradoc
|
|
4
|
+
module AsciiDoc
|
|
5
|
+
module Transform
|
|
6
|
+
module ElementTransformers
|
|
7
|
+
# Admonition style registry. Single source of truth for "which
|
|
8
|
+
# positional attribute names map to an admonition block."
|
|
9
|
+
#
|
|
10
|
+
# Built-in AsciiDoc admonition styles are always recognized. Callers
|
|
11
|
+
# can register additional styles (e.g. +DANGER+, +SAFETY+) without
|
|
12
|
+
# modifying dispatch code — the registry is consulted by every
|
|
13
|
+
# block-form transformer that needs to decide between an admonition
|
|
14
|
+
# and the block's native type.
|
|
15
|
+
module AdmonitionStyles
|
|
16
|
+
BUILTIN = %w[note tip warning caution important editor todo].freeze
|
|
17
|
+
|
|
18
|
+
@custom = []
|
|
19
|
+
|
|
20
|
+
class << self
|
|
21
|
+
# True if +style+ (case-insensitive) is a known admonition style.
|
|
22
|
+
def admonition?(style)
|
|
23
|
+
return false if style.nil?
|
|
24
|
+
|
|
25
|
+
name = style.to_s.downcase
|
|
26
|
+
BUILTIN.include?(name) || custom.include?(name)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Register an additional admonition style. Open for extension.
|
|
30
|
+
def register(style)
|
|
31
|
+
name = style.to_s.downcase
|
|
32
|
+
@custom << name unless @custom.include?(name) || BUILTIN.include?(name)
|
|
33
|
+
self
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Reset custom registrations. Intended for specs.
|
|
37
|
+
def reset!
|
|
38
|
+
@custom = []
|
|
39
|
+
self
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def custom
|
|
43
|
+
@custom.dup
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -21,7 +21,7 @@ module Coradoc
|
|
|
21
21
|
# line; we join with "\n" so whitespace, indentation, and blank
|
|
22
22
|
# lines are preserved. Treating these as paragraphs would collapse
|
|
23
23
|
# whitespace and join consecutive lines into a single flowing text.
|
|
24
|
-
def transform_verbatim_block(block, klass)
|
|
24
|
+
def transform_verbatim_block(block, klass, language_override: nil)
|
|
25
25
|
non_break_lines = Array(block.lines).reject do |line|
|
|
26
26
|
line.is_a?(Coradoc::AsciiDoc::Model::LineBreak) ||
|
|
27
27
|
line.is_a?(Coradoc::AsciiDoc::Model::Break::PageBreak)
|
|
@@ -34,7 +34,7 @@ module Coradoc
|
|
|
34
34
|
id: block.id,
|
|
35
35
|
title: ToCoreModel.extract_title_text(block.title),
|
|
36
36
|
content: content_lines,
|
|
37
|
-
language: ToCoreModel.extract_block_language(block)
|
|
37
|
+
language: language_override || ToCoreModel.extract_block_language(block)
|
|
38
38
|
)
|
|
39
39
|
end
|
|
40
40
|
|
|
@@ -47,9 +47,54 @@ module Coradoc
|
|
|
47
47
|
end
|
|
48
48
|
|
|
49
49
|
def transform_pass_block(block)
|
|
50
|
+
style = first_positional_attr(block)
|
|
51
|
+
return transform_stem_block(block, style) if STEM_STYLES.key?(style.to_s.downcase)
|
|
52
|
+
|
|
50
53
|
transform_verbatim_block(block, Coradoc::CoreModel::PassBlock)
|
|
51
54
|
end
|
|
52
55
|
|
|
56
|
+
# AsciiDoc recognizes three STEM block styles. The default
|
|
57
|
+
# `[stem]` resolves to LaTeX; the other two name their
|
|
58
|
+
# interpreter explicitly. Single source of truth for the
|
|
59
|
+
# style→language mapping.
|
|
60
|
+
STEM_STYLES = {
|
|
61
|
+
'stem' => 'latex',
|
|
62
|
+
'latexmath' => 'latex',
|
|
63
|
+
'asciimath' => 'asciimath'
|
|
64
|
+
}.freeze
|
|
65
|
+
|
|
66
|
+
def transform_stem_block(block, style)
|
|
67
|
+
language = STEM_STYLES.fetch(style.to_s.downcase, 'latex')
|
|
68
|
+
transform_verbatim_block(block, Coradoc::CoreModel::StemBlock,
|
|
69
|
+
language_override: language)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Dispatch entry point for any block whose AsciiDoc model carries
|
|
73
|
+
# an attribute list (delimited blocks + open blocks). Per the
|
|
74
|
+
# AsciiDoc spec, every delimited block accepts admonition styles
|
|
75
|
+
# in its attribute line. Admonition style wins for example,
|
|
76
|
+
# sidebar, quote, and open blocks; verbatim blocks (source,
|
|
77
|
+
# listing) intentionally ignore it because their verbatim
|
|
78
|
+
# semantics are stronger than the annotation label.
|
|
79
|
+
def transform_with_admonition_check(block, native_class)
|
|
80
|
+
style = first_positional_attr(block)
|
|
81
|
+
return transform_admonition_block(block, style) if AdmonitionStyles.admonition?(style)
|
|
82
|
+
|
|
83
|
+
transform_typed_block(block, native_class)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def transform_example_block(block)
|
|
87
|
+
transform_with_admonition_check(block, Coradoc::CoreModel::ExampleBlock)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def transform_sidebar_block(block)
|
|
91
|
+
transform_with_admonition_check(block, Coradoc::CoreModel::SidebarBlock)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def transform_quote_block(block)
|
|
95
|
+
transform_with_admonition_check(block, Coradoc::CoreModel::QuoteBlock)
|
|
96
|
+
end
|
|
97
|
+
|
|
53
98
|
# Open blocks (`--`) are generic containers. AsciiDoc allows
|
|
54
99
|
# casting them to a different block type via positional
|
|
55
100
|
# attributes: verbatim types (`[source]`, `[listing]`,
|
|
@@ -60,41 +105,53 @@ module Coradoc
|
|
|
60
105
|
def transform_open_block(block)
|
|
61
106
|
semantic = open_block_semantic(block)
|
|
62
107
|
case semantic
|
|
63
|
-
when :
|
|
108
|
+
when :source
|
|
64
109
|
transform_source_block(block)
|
|
65
110
|
when :listing
|
|
66
111
|
transform_listing_from_open(block)
|
|
67
112
|
when :literal
|
|
68
113
|
transform_literal_from_open(block)
|
|
69
114
|
when :admonition
|
|
70
|
-
|
|
115
|
+
transform_admonition_block(block, first_positional_attr(block))
|
|
71
116
|
else
|
|
72
117
|
transform_typed_block(block, Coradoc::CoreModel::OpenBlock)
|
|
73
118
|
end
|
|
74
119
|
end
|
|
75
120
|
|
|
76
|
-
|
|
121
|
+
VERBATILE_CAST_STYLES = %w[source listing literal].freeze
|
|
77
122
|
|
|
78
123
|
def open_block_semantic(block)
|
|
124
|
+
style = first_positional_attr(block)
|
|
125
|
+
return nil unless style
|
|
126
|
+
|
|
127
|
+
case style.to_s.downcase
|
|
128
|
+
when *VERBATILE_CAST_STYLES then :"#{style.downcase}"
|
|
129
|
+
else :admonition if AdmonitionStyles.admonition?(style)
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# Returns the first positional attribute on the block's
|
|
134
|
+
# attribute list as a String, or nil if absent. Centralizes the
|
|
135
|
+
# shape-walking that was previously inlined in
|
|
136
|
+
# `transform_admonition_from_open` and `open_block_semantic`.
|
|
137
|
+
def first_positional_attr(block)
|
|
79
138
|
attrs = block.attributes
|
|
80
139
|
return nil unless attrs.is_a?(Coradoc::AsciiDoc::Model::AttributeList)
|
|
81
140
|
|
|
82
141
|
first = attrs.positional&.first
|
|
83
142
|
return nil unless first.is_a?(Coradoc::AsciiDoc::Model::AttributeListAttribute)
|
|
84
143
|
|
|
85
|
-
|
|
86
|
-
when 'source' then :source_code
|
|
87
|
-
when 'listing' then :listing
|
|
88
|
-
when 'literal' then :literal
|
|
89
|
-
when *ADMONITION_TYPES then :admonition
|
|
90
|
-
end
|
|
144
|
+
first.value.to_s
|
|
91
145
|
end
|
|
92
146
|
|
|
93
|
-
|
|
94
|
-
|
|
147
|
+
# Single admonition dispatch used by every block-form path
|
|
148
|
+
# (example / sidebar / quote / pass / open). Builds an
|
|
149
|
+
# AnnotationBlock with annotation_type set from the style and
|
|
150
|
+
# the block's body lines joined into a single content string.
|
|
151
|
+
def transform_admonition_block(block, type)
|
|
95
152
|
content_lines = Array(block.lines).map { |line| ToCoreModel.extract_text_content(line) }.join("\n")
|
|
96
153
|
Coradoc::CoreModel::AnnotationBlock.new(
|
|
97
|
-
annotation_type: type,
|
|
154
|
+
annotation_type: type.to_s.downcase,
|
|
98
155
|
content: content_lines,
|
|
99
156
|
title: ToCoreModel.extract_title_text(block.title)
|
|
100
157
|
)
|
|
@@ -12,6 +12,7 @@ module Coradoc
|
|
|
12
12
|
children = ToCoreModel.transform(doc.sections || doc.contents || [])
|
|
13
13
|
children = CalloutMerger.call(children)
|
|
14
14
|
children = prepend_frontmatter(children, doc.frontmatter)
|
|
15
|
+
children = insert_title_heading_after_frontmatter(children, title_text)
|
|
15
16
|
|
|
16
17
|
Coradoc::CoreModel::DocumentElement.new(
|
|
17
18
|
id: doc.id,
|
|
@@ -32,6 +33,31 @@ module Coradoc
|
|
|
32
33
|
[block, *children]
|
|
33
34
|
end
|
|
34
35
|
|
|
36
|
+
# Per the AsciiDoc spec, the document title (`= Title`) is the
|
|
37
|
+
# document's level-0 heading. Asciidoctor renders it as an
|
|
38
|
+
# <h1> at the top of the body by default. Emit a HeaderElement
|
|
39
|
+
# at level 0 so consumers that walk the body's children see the
|
|
40
|
+
# title (the standard ProseMirror/HTML rendering pattern)
|
|
41
|
+
# instead of having to read Document.title separately.
|
|
42
|
+
#
|
|
43
|
+
# The title attribute on DocumentElement is preserved for
|
|
44
|
+
# consumers that read it directly. The title heading is placed
|
|
45
|
+
# after any FrontmatterBlock (frontmatter is metadata that
|
|
46
|
+
# precedes the body).
|
|
47
|
+
def insert_title_heading_after_frontmatter(children, title_text)
|
|
48
|
+
return children if title_text.nil? || title_text.strip.empty?
|
|
49
|
+
|
|
50
|
+
title_heading = Coradoc::CoreModel::HeaderElement.new(
|
|
51
|
+
level: 0,
|
|
52
|
+
title: title_text,
|
|
53
|
+
content: title_text
|
|
54
|
+
)
|
|
55
|
+
frontmatter_count = children.count do |child|
|
|
56
|
+
child.is_a?(Coradoc::CoreModel::FrontmatterBlock)
|
|
57
|
+
end
|
|
58
|
+
children.insert(frontmatter_count, title_heading)
|
|
59
|
+
end
|
|
60
|
+
|
|
35
61
|
def transform_section(section, parent_id: nil)
|
|
36
62
|
title_text = ToCoreModel.extract_title_text(section.title)
|
|
37
63
|
section_id = section.id || Coradoc::CoreModel::IdGenerator.generate_from_title(
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Coradoc
|
|
4
|
+
module AsciiDoc
|
|
5
|
+
module Transform
|
|
6
|
+
module ElementTransformers
|
|
7
|
+
# Transforms AsciiDoc {Model::Include} into the canonical
|
|
8
|
+
# {CoreModel::Include}. The CoreModel node carries the parsed
|
|
9
|
+
# options as a typed {CoreModel::IncludeOptions} instance plus
|
|
10
|
+
# the raw bracket body for verbatim round-trip.
|
|
11
|
+
class IncludeTransformer
|
|
12
|
+
class << self
|
|
13
|
+
# @param model [Coradoc::AsciiDoc::Model::Include]
|
|
14
|
+
# @return [Coradoc::CoreModel::Include]
|
|
15
|
+
def transform_include(model)
|
|
16
|
+
options_hash = extract_options_hash(model.attributes)
|
|
17
|
+
options = CoreModel::IncludeOptions.from_hash(options_hash)
|
|
18
|
+
raw = extract_raw_options(model.attributes)
|
|
19
|
+
|
|
20
|
+
CoreModel::Include.new(
|
|
21
|
+
target: model.path.to_s,
|
|
22
|
+
options: options,
|
|
23
|
+
raw_options: raw,
|
|
24
|
+
line_break: model.line_break.to_s
|
|
25
|
+
)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
def extract_options_hash(attrs)
|
|
31
|
+
return {} unless attrs.is_a?(Coradoc::AsciiDoc::Model::AttributeList)
|
|
32
|
+
|
|
33
|
+
attrs.named.each_with_object({}) do |attr, hash|
|
|
34
|
+
hash[attr.name.to_s] = serialize_named_value(attr.value)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def serialize_named_value(value)
|
|
39
|
+
case value
|
|
40
|
+
when Array then value.map(&:to_s).join(';')
|
|
41
|
+
else value.to_s
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def extract_raw_options(attrs)
|
|
46
|
+
return '' unless attrs.is_a?(Coradoc::AsciiDoc::Model::AttributeList)
|
|
47
|
+
|
|
48
|
+
adoc = attrs.to_adoc(show_empty: false).to_s
|
|
49
|
+
adoc.gsub(/\A\[|\]\z/, '')
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
@@ -10,6 +10,8 @@ module Coradoc
|
|
|
10
10
|
autoload :InlineTransformer, "#{__dir__}/element_transformers/inline_transformer"
|
|
11
11
|
autoload :TableTransformer, "#{__dir__}/element_transformers/table_transformer"
|
|
12
12
|
autoload :OtherTransformer, "#{__dir__}/element_transformers/other_transformer"
|
|
13
|
+
autoload :IncludeTransformer, "#{__dir__}/element_transformers/include_transformer"
|
|
14
|
+
autoload :AdmonitionStyles, "#{__dir__}/element_transformers/admonition_styles"
|
|
13
15
|
end
|
|
14
16
|
end
|
|
15
17
|
end
|
|
@@ -40,12 +40,18 @@ module Coradoc
|
|
|
40
40
|
Coradoc::AsciiDoc::Model::Header.new(title: '')
|
|
41
41
|
end
|
|
42
42
|
|
|
43
|
-
|
|
43
|
+
# Pull FrontmatterBlock out first so strip_title_heading sees
|
|
44
|
+
# the body children in their natural order (frontmatter →
|
|
45
|
+
# title heading → body). The reverse-direction strip then
|
|
46
|
+
# matches on a level-0 HeaderElement wherever it sits among
|
|
47
|
+
# the remaining children.
|
|
48
|
+
without_frontmatter, frontmatter = extract_frontmatter(Array(element.children))
|
|
49
|
+
without_title = strip_title_heading(without_frontmatter, element.title)
|
|
44
50
|
|
|
45
51
|
Coradoc::AsciiDoc::Model::Document.new(
|
|
46
52
|
id: element.id,
|
|
47
53
|
header: header,
|
|
48
|
-
sections: flatten_children(
|
|
54
|
+
sections: flatten_children(without_title),
|
|
49
55
|
frontmatter: frontmatter
|
|
50
56
|
)
|
|
51
57
|
when CoreModel::SectionElement
|
|
@@ -64,6 +70,27 @@ module Coradoc
|
|
|
64
70
|
end
|
|
65
71
|
end
|
|
66
72
|
|
|
73
|
+
# The forward direction (DocumentTransformer#insert_title_heading_after_frontmatter)
|
|
74
|
+
# emits a level-0 HeaderElement after any FrontmatterBlock so
|
|
75
|
+
# consumers that walk the children see the title. On the reverse
|
|
76
|
+
# path, that HeaderElement would round-trip back as a separate
|
|
77
|
+
# body element and the title would be emitted twice (once via
|
|
78
|
+
# the document header, once via the heading child). Drop it
|
|
79
|
+
# before serialization when it carries the same text as the
|
|
80
|
+
# document title.
|
|
81
|
+
def strip_title_heading(children, document_title)
|
|
82
|
+
return children unless document_title && !document_title.strip.empty?
|
|
83
|
+
|
|
84
|
+
index = children.find_index do |child|
|
|
85
|
+
child.is_a?(CoreModel::HeaderElement) &&
|
|
86
|
+
child.level.to_i.zero? &&
|
|
87
|
+
child.title.to_s == document_title.to_s
|
|
88
|
+
end
|
|
89
|
+
return children unless index
|
|
90
|
+
|
|
91
|
+
children.reject.with_index { |_, i| i == index }
|
|
92
|
+
end
|
|
93
|
+
|
|
67
94
|
# Transforms each CoreModel child and flattens one level so a
|
|
68
95
|
# transform that returns multiple siblings (e.g. a source block
|
|
69
96
|
# followed by its re-expanded callout paragraphs) stays in
|
|
@@ -388,8 +415,51 @@ module Coradoc
|
|
|
388
415
|
)
|
|
389
416
|
end
|
|
390
417
|
|
|
418
|
+
def transform_include(include)
|
|
419
|
+
Coradoc::AsciiDoc::Model::Include.new(
|
|
420
|
+
path: include.target.to_s,
|
|
421
|
+
attributes: build_include_attributes(include),
|
|
422
|
+
line_break: include.line_break.to_s
|
|
423
|
+
)
|
|
424
|
+
end
|
|
425
|
+
|
|
391
426
|
private
|
|
392
427
|
|
|
428
|
+
def build_include_attributes(include)
|
|
429
|
+
list = Coradoc::AsciiDoc::Model::AttributeList.new
|
|
430
|
+
options = include.options
|
|
431
|
+
return list if options.nil?
|
|
432
|
+
|
|
433
|
+
add_tag_attribute(list, options)
|
|
434
|
+
add_simple_attribute(list, 'lines', options.lines_spec)
|
|
435
|
+
add_leveloffset_attribute(list, options)
|
|
436
|
+
add_simple_attribute(list, 'indent', options.indent&.to_s)
|
|
437
|
+
add_simple_attribute(list, 'encoding', options.file_encoding)
|
|
438
|
+
list
|
|
439
|
+
end
|
|
440
|
+
|
|
441
|
+
def add_simple_attribute(list, name, value)
|
|
442
|
+
return if value.nil? || value.to_s.empty?
|
|
443
|
+
|
|
444
|
+
list.add_named(name, value.to_s)
|
|
445
|
+
end
|
|
446
|
+
|
|
447
|
+
def add_tag_attribute(list, options)
|
|
448
|
+
if options.tags_wildcard
|
|
449
|
+
list.add_named('tags', '*')
|
|
450
|
+
elsif options.tags_inverted
|
|
451
|
+
list.add_named('tags', '**')
|
|
452
|
+
elsif options.tags.any?
|
|
453
|
+
list.add_named('tags', options.tags.join(';'))
|
|
454
|
+
end
|
|
455
|
+
end
|
|
456
|
+
|
|
457
|
+
def add_leveloffset_attribute(list, options)
|
|
458
|
+
return if options.leveloffset.nil?
|
|
459
|
+
|
|
460
|
+
list.add_named('leveloffset', options.leveloffset.to_s)
|
|
461
|
+
end
|
|
462
|
+
|
|
393
463
|
# If the first CoreModel child is a FrontmatterBlock, serialize
|
|
394
464
|
# it to YAML text via Codec (single source of truth) and pop it
|
|
395
465
|
# from the children list. Returns [remaining_children,
|
|
@@ -120,6 +120,11 @@ module Coradoc
|
|
|
120
120
|
Coradoc::CoreModel::CommentLine,
|
|
121
121
|
->(model) { FromCoreModel.transform_comment_line(model) }
|
|
122
122
|
)
|
|
123
|
+
|
|
124
|
+
Registry.register(
|
|
125
|
+
Coradoc::CoreModel::Include,
|
|
126
|
+
->(model) { FromCoreModel.transform_include(model) }
|
|
127
|
+
)
|
|
123
128
|
end
|
|
124
129
|
end
|
|
125
130
|
end
|
|
@@ -10,6 +10,7 @@ module Coradoc
|
|
|
10
10
|
Inl = ElementTransformers::InlineTransformer
|
|
11
11
|
Tbl = ElementTransformers::TableTransformer
|
|
12
12
|
Oth = ElementTransformers::OtherTransformer
|
|
13
|
+
Inc = ElementTransformers::IncludeTransformer
|
|
13
14
|
|
|
14
15
|
class << self
|
|
15
16
|
def register_all!
|
|
@@ -47,14 +48,19 @@ module Coradoc
|
|
|
47
48
|
priority: 10
|
|
48
49
|
)
|
|
49
50
|
|
|
51
|
+
# Quote / Example / Sidebar blocks all share the same dispatch:
|
|
52
|
+
# check the attribute style for an admonition label first
|
|
53
|
+
# (`[NOTE]\n====` → AnnotationBlock), fall back to the block's
|
|
54
|
+
# native CoreModel type. Single source of truth lives in
|
|
55
|
+
# BlockTransformer#transform_with_admonition_check.
|
|
50
56
|
{
|
|
51
|
-
Coradoc::AsciiDoc::Model::Block::Quote =>
|
|
52
|
-
Coradoc::AsciiDoc::Model::Block::Example =>
|
|
53
|
-
Coradoc::AsciiDoc::Model::Block::Side =>
|
|
54
|
-
}.each do |block_class,
|
|
57
|
+
Coradoc::AsciiDoc::Model::Block::Quote => :transform_quote_block,
|
|
58
|
+
Coradoc::AsciiDoc::Model::Block::Example => :transform_example_block,
|
|
59
|
+
Coradoc::AsciiDoc::Model::Block::Side => :transform_sidebar_block
|
|
60
|
+
}.each do |block_class, method|
|
|
55
61
|
Registry.register_with_priority(
|
|
56
62
|
block_class,
|
|
57
|
-
->(model) { Blk.
|
|
63
|
+
->(model) { Blk.public_send(method, model) },
|
|
58
64
|
priority: 10
|
|
59
65
|
)
|
|
60
66
|
end
|
|
@@ -231,8 +237,16 @@ module Coradoc
|
|
|
231
237
|
->(model) { Oth.transform_bibliography_entry(model) }
|
|
232
238
|
)
|
|
233
239
|
|
|
234
|
-
|
|
240
|
+
# Include directives become first-class CoreModel::Include nodes
|
|
241
|
+
# (link edges in a text graph). To splice their content inline,
|
|
242
|
+
# call +Coradoc.resolve_includes(doc, base_dir:)+ on the parsed
|
|
243
|
+
# document — that step is intentionally separate from parse.
|
|
244
|
+
Registry.register(
|
|
235
245
|
Coradoc::AsciiDoc::Model::Include,
|
|
246
|
+
->(model) { Inc.transform_include(model) }
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
[
|
|
236
250
|
Coradoc::AsciiDoc::Model::Audio,
|
|
237
251
|
Coradoc::AsciiDoc::Model::Video,
|
|
238
252
|
Coradoc::AsciiDoc::Model::ContentList,
|
|
@@ -25,6 +25,10 @@ module Coradoc
|
|
|
25
25
|
lines: lines,
|
|
26
26
|
ordering: ordering
|
|
27
27
|
}
|
|
28
|
+
# Markdown fences carry the language tag inline (```ruby);
|
|
29
|
+
# pass it through so the SourceCode classifier entry can set
|
|
30
|
+
# block.lang directly, which extract_block_language prefers.
|
|
31
|
+
opts[:lang] = block[:language].to_s if block.key?(:language) && !block[:language].nil?
|
|
28
32
|
BlockTypeClassifier.classify(delimiter, opts, attribute_list)
|
|
29
33
|
end
|
|
30
34
|
|
|
@@ -28,7 +28,15 @@ module Coradoc
|
|
|
28
28
|
['.', 4, nil, ->(opts, attrs) { Model::Block::Literal.new(**opts.merge(attributes: attrs)) }],
|
|
29
29
|
['_', 4, nil, ->(opts, attrs) { Model::Block::Quote.new(**opts.merge(attributes: attrs)) }],
|
|
30
30
|
['-', 4, nil, ->(opts, attrs) { Model::Block::SourceCode.new(**opts.merge(attributes: attrs)) }],
|
|
31
|
-
['-', 2, 2, ->(opts, attrs) { Model::Block::Open.new(**opts.merge(attributes: attrs)) }]
|
|
31
|
+
['-', 2, 2, ->(opts, attrs) { Model::Block::Open.new(**opts.merge(attributes: attrs)) }],
|
|
32
|
+
# Markdown-style triple-backtick fence: behaves as a SourceCode
|
|
33
|
+
# block. The language tag parsed from the opening fence is passed
|
|
34
|
+
# through opts[:lang]; extract_block_language prefers block.lang.
|
|
35
|
+
['`', 3, nil, ->(opts, attrs) {
|
|
36
|
+
model_opts = opts.merge(attributes: attrs, delimiter_char: '`')
|
|
37
|
+
model_opts[:lang] = opts[:lang] if opts.key?(:lang)
|
|
38
|
+
Model::Block::SourceCode.new(**model_opts)
|
|
39
|
+
}]
|
|
32
40
|
].freeze
|
|
33
41
|
|
|
34
42
|
module_function
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: coradoc-adoc
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.0.
|
|
4
|
+
version: 2.0.20
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ribose Inc.
|
|
@@ -317,8 +317,10 @@ files:
|
|
|
317
317
|
- lib/coradoc/asciidoc/transform/attribute_list_to_metadata.rb
|
|
318
318
|
- lib/coradoc/asciidoc/transform/callout_merger.rb
|
|
319
319
|
- lib/coradoc/asciidoc/transform/element_transformers.rb
|
|
320
|
+
- lib/coradoc/asciidoc/transform/element_transformers/admonition_styles.rb
|
|
320
321
|
- lib/coradoc/asciidoc/transform/element_transformers/block_transformer.rb
|
|
321
322
|
- lib/coradoc/asciidoc/transform/element_transformers/document_transformer.rb
|
|
323
|
+
- lib/coradoc/asciidoc/transform/element_transformers/include_transformer.rb
|
|
322
324
|
- lib/coradoc/asciidoc/transform/element_transformers/inline_transformer.rb
|
|
323
325
|
- lib/coradoc/asciidoc/transform/element_transformers/list_transformer.rb
|
|
324
326
|
- lib/coradoc/asciidoc/transform/element_transformers/other_transformer.rb
|