coradoc-adoc 2.0.19 → 2.0.21
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 +16 -14
- data/lib/coradoc/asciidoc/serializer/element_registry.rb +20 -50
- data/lib/coradoc/asciidoc/serializer/registrations.rb +7 -90
- data/lib/coradoc/asciidoc/serializer/serializers/inline/passthrough.rb +25 -0
- data/lib/coradoc/asciidoc/serializer/serializers/inline.rb +1 -0
- 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.rb +1 -0
- data/lib/coradoc/asciidoc/transform/from_core_model.rb +33 -4
- data/lib/coradoc/asciidoc/transform/to_core_model.rb +2 -2
- data/lib/coradoc/asciidoc/transform/to_core_model_registrations.rb +10 -5
- 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: 1c4b5749f4fd1a691eb76fe9f7848b1d4bc2cd2e43cd2ee0a5f1afdcb697459c
|
|
4
|
+
data.tar.gz: 90e6bf66da9a7c4dba7c7eafbdabf378259e4b9792a93d4c88209d52bd991876
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e421d05a3a2c43387471224818a5261229478c1539ddade33a30957be8d7fb0775c9b0bb95b59afa32cb4e84f0adab07b62164113a521635fd5e70cdbe66ecec
|
|
7
|
+
data.tar.gz: dd6c2ea15fcb4c3e521a929006ba2e7ebbff5f6548902a2d7947cdac04a9005a792a9ae4fb07d68f53e30c844de9cbd00bd62a0fa034fa9f9c298209fe50ec7e
|
|
@@ -105,12 +105,24 @@ module Coradoc
|
|
|
105
105
|
end
|
|
106
106
|
|
|
107
107
|
def block_content(n_deep = 3)
|
|
108
|
-
|
|
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
|
|
109
121
|
c |= block(n_deep - 1) if n_deep.positive?
|
|
110
122
|
c |= list
|
|
111
123
|
c |= text_line(false, unguarded: true)
|
|
112
124
|
c |= empty_line.as(:line_break)
|
|
113
|
-
c
|
|
125
|
+
c
|
|
114
126
|
end
|
|
115
127
|
|
|
116
128
|
# Block delimiter: 4+ identical characters, or 2 dashes for open
|
|
@@ -155,12 +167,7 @@ module Coradoc
|
|
|
155
167
|
text_line(false, unguarded: true, verbatim: true) |
|
|
156
168
|
empty_line.as(:line_break)
|
|
157
169
|
else
|
|
158
|
-
|
|
159
|
-
c |= block(n_deep - 1) if n_deep.positive?
|
|
160
|
-
c |= list
|
|
161
|
-
c |= text_line(false, unguarded: true)
|
|
162
|
-
c |= empty_line.as(:line_break)
|
|
163
|
-
c
|
|
170
|
+
block_container_content(n_deep)
|
|
164
171
|
end
|
|
165
172
|
|
|
166
173
|
(closing_pattern.absent? >> content).repeat(1)
|
|
@@ -213,12 +220,7 @@ module Coradoc
|
|
|
213
220
|
text_line(false, unguarded: true, verbatim: true) |
|
|
214
221
|
empty_line.as(:line_break)
|
|
215
222
|
else
|
|
216
|
-
|
|
217
|
-
alt |= block(n_deep - 1) if n_deep.positive?
|
|
218
|
-
alt |= list
|
|
219
|
-
alt |= text_line(false, unguarded: true)
|
|
220
|
-
alt |= empty_line.as(:line_break)
|
|
221
|
-
alt
|
|
223
|
+
block_container_content(n_deep)
|
|
222
224
|
end
|
|
223
225
|
|
|
224
226
|
(closing_pattern.absent? >> content).repeat(1)
|
|
@@ -4,9 +4,12 @@ module Coradoc
|
|
|
4
4
|
module AsciiDoc
|
|
5
5
|
module Serializer
|
|
6
6
|
# Registry for mapping Coradoc model classes to their AsciiDoc serializers.
|
|
7
|
-
# This is the authoritative source for model→serializer mappings.
|
|
8
7
|
#
|
|
9
|
-
#
|
|
8
|
+
# Thin layer over `Coradoc::Dispatch.strict`: each public method
|
|
9
|
+
# delegates to a single Dispatch instance so the dispatch mechanism
|
|
10
|
+
# (storage, miss-handling, override semantics) lives in one place.
|
|
11
|
+
# Adoc-specific concerns (the ArgumentError wording on miss, the
|
|
12
|
+
# `registered_models` name) stay here.
|
|
10
13
|
#
|
|
11
14
|
# @example Registering a custom serializer
|
|
12
15
|
# ElementRegistry.override(Model::Paragraph, CustomParagraphSerializer)
|
|
@@ -15,78 +18,45 @@ module Coradoc
|
|
|
15
18
|
# original = ElementRegistry.get(Model::Paragraph)
|
|
16
19
|
# ElementRegistry.override(Model::Paragraph, WrapperSerializer.new(original))
|
|
17
20
|
class ElementRegistry
|
|
21
|
+
DISPATCH = Coradoc::Dispatch.strict
|
|
22
|
+
|
|
18
23
|
class << self
|
|
19
|
-
# Register a serializer for a model class
|
|
20
|
-
# @param model_class [Class] The Coradoc model class
|
|
21
|
-
# @param serializer_class [Class] The serializer class
|
|
22
24
|
def register(model_class, serializer_class)
|
|
23
|
-
|
|
25
|
+
DISPATCH.register(model_class, serializer_class)
|
|
24
26
|
end
|
|
25
27
|
|
|
26
|
-
# Override a serializer for a model class
|
|
27
|
-
# This is an alias for register that makes the intent explicit
|
|
28
|
-
# @param model_class [Class] The Coradoc model class
|
|
29
|
-
# @param serializer_class [Class] The new serializer class
|
|
30
|
-
# @return [Class, nil] The previous serializer class, or nil if none
|
|
31
28
|
def override(model_class, serializer_class)
|
|
32
|
-
|
|
33
|
-
registry[model_class] = serializer_class
|
|
34
|
-
previous
|
|
29
|
+
DISPATCH.override(model_class, serializer_class)
|
|
35
30
|
end
|
|
36
31
|
|
|
37
|
-
# Unregister a serializer for a model class
|
|
38
|
-
# @param model_class [Class] The model class to unregister
|
|
39
|
-
# @return [Class, nil] The removed serializer class, or nil if none
|
|
40
32
|
def unregister(model_class)
|
|
41
|
-
|
|
33
|
+
DISPATCH.unregister(model_class)
|
|
42
34
|
end
|
|
43
35
|
|
|
44
|
-
# Get the serializer for a model class without raising
|
|
45
|
-
# @param model_class [Class] The model class
|
|
46
|
-
# @return [Class, nil] The serializer class, or nil if not registered
|
|
47
36
|
def get(model_class)
|
|
48
|
-
|
|
37
|
+
DISPATCH.lookup(model_class)
|
|
49
38
|
end
|
|
50
39
|
|
|
51
|
-
# Lookup serializer for a model class
|
|
52
|
-
# @param model_class [Class] The model class
|
|
53
|
-
# @return [Class] The serializer class
|
|
54
|
-
# @raise [ArgumentError] If no serializer is registered
|
|
55
40
|
def lookup(model_class)
|
|
56
|
-
serializer_class =
|
|
57
|
-
|
|
58
|
-
unless serializer_class
|
|
59
|
-
raise ArgumentError,
|
|
60
|
-
"No serializer registered for #{model_class.name}. " \
|
|
61
|
-
'Please register a serializer in ElementRegistry, or the serializer ' \
|
|
62
|
-
'may not have been loaded yet (check Registrations.load_all!)'
|
|
63
|
-
end
|
|
41
|
+
serializer_class = DISPATCH.lookup(model_class)
|
|
42
|
+
return serializer_class if serializer_class
|
|
64
43
|
|
|
65
|
-
|
|
44
|
+
raise ArgumentError,
|
|
45
|
+
"No serializer registered for #{model_class.name}. " \
|
|
46
|
+
'Please register a serializer in ElementRegistry, or the serializer ' \
|
|
47
|
+
'may not have been loaded yet (check Registrations.load_all!)'
|
|
66
48
|
end
|
|
67
49
|
|
|
68
|
-
# Get all registered model classes
|
|
69
|
-
# @return [Array<Class>] Array of registered model classes
|
|
70
50
|
def registered_models
|
|
71
|
-
|
|
51
|
+
DISPATCH.registered_keys
|
|
72
52
|
end
|
|
73
53
|
|
|
74
|
-
# Check if a model class has a registered serializer
|
|
75
|
-
# @param model_class [Class] The model class
|
|
76
|
-
# @return [Boolean] True if registered
|
|
77
54
|
def registered?(model_class)
|
|
78
|
-
|
|
55
|
+
DISPATCH.registered?(model_class)
|
|
79
56
|
end
|
|
80
57
|
|
|
81
|
-
# Clear all registrations (mainly for testing)
|
|
82
58
|
def clear!
|
|
83
|
-
|
|
84
|
-
end
|
|
85
|
-
|
|
86
|
-
# Get the registry hash
|
|
87
|
-
# @return [Hash] Model class => Serializer class mapping
|
|
88
|
-
def registry
|
|
89
|
-
@@registry ||= {}
|
|
59
|
+
DISPATCH.clear!
|
|
90
60
|
end
|
|
91
61
|
end
|
|
92
62
|
end
|
|
@@ -3,104 +3,21 @@
|
|
|
3
3
|
module Coradoc
|
|
4
4
|
module AsciiDoc
|
|
5
5
|
module Serializer
|
|
6
|
-
# Trigger loading of all serializer registrations
|
|
6
|
+
# Trigger loading of all serializer registrations.
|
|
7
7
|
#
|
|
8
|
-
# Each serializer file self-registers when loaded via
|
|
9
|
-
#
|
|
10
|
-
#
|
|
8
|
+
# Each serializer file self-registers when loaded via
|
|
9
|
+
# `ElementRegistry.register(...)` at file bottom. Walking the
|
|
10
|
+
# serializers directory is the single source of truth for "which
|
|
11
|
+
# serializers exist" — adding a file is the only step required for
|
|
12
|
+
# it to register.
|
|
11
13
|
module Registrations
|
|
12
14
|
class << self
|
|
13
|
-
# Load all serializers to trigger their registration
|
|
14
|
-
# rubocop:disable Lint/Void - Constants are referenced to trigger autoload
|
|
15
15
|
def load_all!
|
|
16
|
-
#
|
|
17
|
-
Serializers::Base
|
|
18
|
-
Serializers::Admonition
|
|
19
|
-
Serializers::Attribute
|
|
20
|
-
Serializers::AttributeList
|
|
21
|
-
Serializers::AttributeListAttribute
|
|
22
|
-
Serializers::Audio
|
|
23
|
-
Serializers::Author
|
|
24
|
-
Serializers::Bibliography
|
|
25
|
-
Serializers::BibliographyEntry
|
|
26
|
-
Serializers::Break
|
|
27
|
-
Serializers::CommentBlock
|
|
28
|
-
Serializers::CommentLine
|
|
29
|
-
Serializers::Document
|
|
30
|
-
Serializers::DocumentAttributes
|
|
31
|
-
Serializers::Header
|
|
32
|
-
Serializers::Highlight
|
|
33
|
-
Serializers::Include
|
|
34
|
-
Serializers::LineBreak
|
|
35
|
-
Serializers::List
|
|
36
|
-
Serializers::NamedAttribute
|
|
37
|
-
Serializers::Paragraph
|
|
38
|
-
Serializers::ReviewerNote
|
|
39
|
-
Serializers::Revision
|
|
40
|
-
Serializers::Section
|
|
41
|
-
Serializers::Tag
|
|
42
|
-
Serializers::TableCell
|
|
43
|
-
Serializers::TableRow
|
|
44
|
-
Serializers::Table
|
|
45
|
-
Serializers::Term
|
|
46
|
-
Serializers::TextElement
|
|
47
|
-
Serializers::Title
|
|
48
|
-
Serializers::Video
|
|
49
|
-
|
|
50
|
-
# Block serializers
|
|
51
|
-
Serializers::Block
|
|
52
|
-
Serializers::Block::Core
|
|
53
|
-
Serializers::Block::Example
|
|
54
|
-
Serializers::Block::Listing
|
|
55
|
-
Serializers::Block::Literal
|
|
56
|
-
Serializers::Block::Open
|
|
57
|
-
Serializers::Block::Pass
|
|
58
|
-
Serializers::Block::Quote
|
|
59
|
-
Serializers::Block::ReviewerComment
|
|
60
|
-
Serializers::Block::Side
|
|
61
|
-
Serializers::Block::SourceCode
|
|
62
|
-
|
|
63
|
-
# Image serializers
|
|
64
|
-
Serializers::Image
|
|
65
|
-
Serializers::Image::Core
|
|
66
|
-
|
|
67
|
-
# Inline serializers
|
|
68
|
-
Serializers::Inline
|
|
69
|
-
Serializers::Inline::Anchor
|
|
70
|
-
Serializers::Inline::AttributeReference
|
|
71
|
-
Serializers::Inline::Bold
|
|
72
|
-
Serializers::Inline::CrossReference
|
|
73
|
-
Serializers::Inline::CrossReferenceArg
|
|
74
|
-
Serializers::Inline::Footnote
|
|
75
|
-
Serializers::Inline::HardLineBreak
|
|
76
|
-
Serializers::Inline::Highlight
|
|
77
|
-
Serializers::Inline::Italic
|
|
78
|
-
Serializers::Inline::Link
|
|
79
|
-
Serializers::Inline::Monospace
|
|
80
|
-
Serializers::Inline::Quotation
|
|
81
|
-
Serializers::Inline::Small
|
|
82
|
-
Serializers::Inline::Span
|
|
83
|
-
Serializers::Inline::Stem
|
|
84
|
-
Serializers::Inline::Strikethrough
|
|
85
|
-
Serializers::Inline::Subscript
|
|
86
|
-
Serializers::Inline::Superscript
|
|
87
|
-
Serializers::Inline::Underline
|
|
88
|
-
|
|
89
|
-
# List serializers
|
|
90
|
-
Serializers::List
|
|
91
|
-
Serializers::List::Core
|
|
92
|
-
Serializers::List::Definition
|
|
93
|
-
Serializers::List::DefinitionItem
|
|
94
|
-
Serializers::List::Item
|
|
95
|
-
Serializers::List::Ordered
|
|
96
|
-
Serializers::List::Unordered
|
|
97
|
-
|
|
16
|
+
Dir["#{__dir__}/serializers/**/*.rb"].sort.each { |path| require path }
|
|
98
17
|
true
|
|
99
18
|
end
|
|
100
|
-
# rubocop:enable Lint/Void
|
|
101
19
|
end
|
|
102
20
|
|
|
103
|
-
# Auto-load all on module inclusion
|
|
104
21
|
load_all!
|
|
105
22
|
end
|
|
106
23
|
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Coradoc
|
|
4
|
+
module AsciiDoc
|
|
5
|
+
module Serializer
|
|
6
|
+
module Serializers
|
|
7
|
+
module Inline
|
|
8
|
+
# Serializer for the inline passthrough (`+++raw content+++`).
|
|
9
|
+
# Wraps the raw payload in triple-plus delimiters so downstream
|
|
10
|
+
# consumers know to skip inline substitution.
|
|
11
|
+
class Passthrough < Base
|
|
12
|
+
def to_adoc(model, _options = {})
|
|
13
|
+
content = serialize_content(model.content)
|
|
14
|
+
return '' if content.empty?
|
|
15
|
+
|
|
16
|
+
"+++#{content}+++"
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
ElementRegistry.register(Coradoc::AsciiDoc::Model::Inline::Passthrough, Inline::Passthrough)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -17,6 +17,7 @@ module Coradoc
|
|
|
17
17
|
autoload :Italic, 'coradoc/asciidoc/serializer/serializers/inline/italic'
|
|
18
18
|
autoload :Link, 'coradoc/asciidoc/serializer/serializers/inline/link'
|
|
19
19
|
autoload :Monospace, 'coradoc/asciidoc/serializer/serializers/inline/monospace'
|
|
20
|
+
autoload :Passthrough, 'coradoc/asciidoc/serializer/serializers/inline/passthrough'
|
|
20
21
|
autoload :Quotation, 'coradoc/asciidoc/serializer/serializers/inline/quotation'
|
|
21
22
|
autoload :Small, 'coradoc/asciidoc/serializer/serializers/inline/small'
|
|
22
23
|
autoload :Span, 'coradoc/asciidoc/serializer/serializers/inline/span'
|
|
@@ -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(
|
|
@@ -11,6 +11,7 @@ module Coradoc
|
|
|
11
11
|
autoload :TableTransformer, "#{__dir__}/element_transformers/table_transformer"
|
|
12
12
|
autoload :OtherTransformer, "#{__dir__}/element_transformers/other_transformer"
|
|
13
13
|
autoload :IncludeTransformer, "#{__dir__}/element_transformers/include_transformer"
|
|
14
|
+
autoload :AdmonitionStyles, "#{__dir__}/element_transformers/admonition_styles"
|
|
14
15
|
end
|
|
15
16
|
end
|
|
16
17
|
end
|
|
@@ -5,8 +5,6 @@ module Coradoc
|
|
|
5
5
|
module Transform
|
|
6
6
|
# Transforms CoreModel to AsciiDoc models
|
|
7
7
|
class FromCoreModel
|
|
8
|
-
include Coradoc::Transform::Base
|
|
9
|
-
|
|
10
8
|
@registered = false
|
|
11
9
|
|
|
12
10
|
class << self
|
|
@@ -40,12 +38,18 @@ module Coradoc
|
|
|
40
38
|
Coradoc::AsciiDoc::Model::Header.new(title: '')
|
|
41
39
|
end
|
|
42
40
|
|
|
43
|
-
|
|
41
|
+
# Pull FrontmatterBlock out first so strip_title_heading sees
|
|
42
|
+
# the body children in their natural order (frontmatter →
|
|
43
|
+
# title heading → body). The reverse-direction strip then
|
|
44
|
+
# matches on a level-0 HeaderElement wherever it sits among
|
|
45
|
+
# the remaining children.
|
|
46
|
+
without_frontmatter, frontmatter = extract_frontmatter(Array(element.children))
|
|
47
|
+
without_title = strip_title_heading(without_frontmatter, element.title)
|
|
44
48
|
|
|
45
49
|
Coradoc::AsciiDoc::Model::Document.new(
|
|
46
50
|
id: element.id,
|
|
47
51
|
header: header,
|
|
48
|
-
sections: flatten_children(
|
|
52
|
+
sections: flatten_children(without_title),
|
|
49
53
|
frontmatter: frontmatter
|
|
50
54
|
)
|
|
51
55
|
when CoreModel::SectionElement
|
|
@@ -64,6 +68,27 @@ module Coradoc
|
|
|
64
68
|
end
|
|
65
69
|
end
|
|
66
70
|
|
|
71
|
+
# The forward direction (DocumentTransformer#insert_title_heading_after_frontmatter)
|
|
72
|
+
# emits a level-0 HeaderElement after any FrontmatterBlock so
|
|
73
|
+
# consumers that walk the children see the title. On the reverse
|
|
74
|
+
# path, that HeaderElement would round-trip back as a separate
|
|
75
|
+
# body element and the title would be emitted twice (once via
|
|
76
|
+
# the document header, once via the heading child). Drop it
|
|
77
|
+
# before serialization when it carries the same text as the
|
|
78
|
+
# document title.
|
|
79
|
+
def strip_title_heading(children, document_title)
|
|
80
|
+
return children unless document_title && !document_title.strip.empty?
|
|
81
|
+
|
|
82
|
+
index = children.find_index do |child|
|
|
83
|
+
child.is_a?(CoreModel::HeaderElement) &&
|
|
84
|
+
child.level.to_i.zero? &&
|
|
85
|
+
child.title.to_s == document_title.to_s
|
|
86
|
+
end
|
|
87
|
+
return children unless index
|
|
88
|
+
|
|
89
|
+
children.reject.with_index { |_, i| i == index }
|
|
90
|
+
end
|
|
91
|
+
|
|
67
92
|
# Transforms each CoreModel child and flattens one level so a
|
|
68
93
|
# transform that returns multiple siblings (e.g. a source block
|
|
69
94
|
# followed by its re-expanded callout paragraphs) stays in
|
|
@@ -292,6 +317,8 @@ module Coradoc
|
|
|
292
317
|
type: inline.stem_type || 'latexmath',
|
|
293
318
|
content: inline.content
|
|
294
319
|
)
|
|
320
|
+
when 'raw_inline'
|
|
321
|
+
Coradoc::AsciiDoc::Model::Inline::Passthrough.new(content: inline.content)
|
|
295
322
|
else
|
|
296
323
|
Coradoc::AsciiDoc::Model::TextElement.new(content: inline.content)
|
|
297
324
|
end
|
|
@@ -540,6 +567,8 @@ module Coradoc
|
|
|
540
567
|
end
|
|
541
568
|
end
|
|
542
569
|
end
|
|
570
|
+
|
|
571
|
+
def transform(model) = self.class.transform(model)
|
|
543
572
|
end
|
|
544
573
|
end
|
|
545
574
|
end
|
|
@@ -4,8 +4,6 @@ module Coradoc
|
|
|
4
4
|
module AsciiDoc
|
|
5
5
|
module Transform
|
|
6
6
|
class ToCoreModel
|
|
7
|
-
include Coradoc::Transform::Base
|
|
8
|
-
|
|
9
7
|
@registered = false
|
|
10
8
|
|
|
11
9
|
class << self
|
|
@@ -133,6 +131,8 @@ module Coradoc
|
|
|
133
131
|
text
|
|
134
132
|
end
|
|
135
133
|
end
|
|
134
|
+
|
|
135
|
+
def transform(model) = self.class.transform(model)
|
|
136
136
|
end
|
|
137
137
|
end
|
|
138
138
|
end
|
|
@@ -48,14 +48,19 @@ module Coradoc
|
|
|
48
48
|
priority: 10
|
|
49
49
|
)
|
|
50
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.
|
|
51
56
|
{
|
|
52
|
-
Coradoc::AsciiDoc::Model::Block::Quote =>
|
|
53
|
-
Coradoc::AsciiDoc::Model::Block::Example =>
|
|
54
|
-
Coradoc::AsciiDoc::Model::Block::Side =>
|
|
55
|
-
}.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|
|
|
56
61
|
Registry.register_with_priority(
|
|
57
62
|
block_class,
|
|
58
|
-
->(model) { Blk.
|
|
63
|
+
->(model) { Blk.public_send(method, model) },
|
|
59
64
|
priority: 10
|
|
60
65
|
)
|
|
61
66
|
end
|
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.21
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ribose Inc.
|
|
@@ -283,6 +283,7 @@ files:
|
|
|
283
283
|
- lib/coradoc/asciidoc/serializer/serializers/inline/italic.rb
|
|
284
284
|
- lib/coradoc/asciidoc/serializer/serializers/inline/link.rb
|
|
285
285
|
- lib/coradoc/asciidoc/serializer/serializers/inline/monospace.rb
|
|
286
|
+
- lib/coradoc/asciidoc/serializer/serializers/inline/passthrough.rb
|
|
286
287
|
- lib/coradoc/asciidoc/serializer/serializers/inline/quotation.rb
|
|
287
288
|
- lib/coradoc/asciidoc/serializer/serializers/inline/small.rb
|
|
288
289
|
- lib/coradoc/asciidoc/serializer/serializers/inline/span.rb
|
|
@@ -317,6 +318,7 @@ files:
|
|
|
317
318
|
- lib/coradoc/asciidoc/transform/attribute_list_to_metadata.rb
|
|
318
319
|
- lib/coradoc/asciidoc/transform/callout_merger.rb
|
|
319
320
|
- lib/coradoc/asciidoc/transform/element_transformers.rb
|
|
321
|
+
- lib/coradoc/asciidoc/transform/element_transformers/admonition_styles.rb
|
|
320
322
|
- lib/coradoc/asciidoc/transform/element_transformers/block_transformer.rb
|
|
321
323
|
- lib/coradoc/asciidoc/transform/element_transformers/document_transformer.rb
|
|
322
324
|
- lib/coradoc/asciidoc/transform/element_transformers/include_transformer.rb
|