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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d8b381debf5d0742992318f775804846b9a2560e59dcff0dad3518999fcb39ad
4
- data.tar.gz: 5556652e925b9d3081562849790252900e9a2b2493ca7f6eb8ae023d888ed38a
3
+ metadata.gz: fad1b11f8028b7157066b7a2d4474b2d20e3cab1e526e0347ca36015cdf3ad90
4
+ data.tar.gz: d143bb70ec7ab2e3a1656a5e9ed72d52bb0b2f1341b3566efa02ef44343a2044
5
5
  SHA512:
6
- metadata.gz: 7bae7c856ed3b6c8c804c614e009a1efe36327c94cd8d1b5b48429b1fe849f75e99df68f1c85f719c63d57b05fd9584a707881bd01e8d232a5cf189bc9c7e69f
7
- data.tar.gz: 121f12dea70e459c11f7f91233fb7189dedf92bdc0ad8665063c427df67456a8bf0d0bfa035dec43f46919b851fad078bf48598cdac323ff2c86a02102b560f4
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
- (example_block(n_deep) |
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
- c = block_image
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.repeat(1)
125
+ c
81
126
  end
82
127
 
83
- # Block delimiter: 4+ identical characters (or 2 for open block)
84
- # Used by paragraph.rb to reject lines that look like block delimiters.
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)) >> # Exactly 2 for open block
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
- c = block_image
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
- alt = block_image
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 :source_code
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
- transform_admonition_from_open(block)
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
- ADMONITION_TYPES = %w[note tip warning caution important].freeze
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
- case first.value.to_s.downcase
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
- def transform_admonition_from_open(block)
94
- type = block.attributes.positional.first.value.to_s.downcase
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
- sections, frontmatter = extract_frontmatter(Array(element.children))
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(sections),
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 => Coradoc::CoreModel::QuoteBlock,
52
- Coradoc::AsciiDoc::Model::Block::Example => Coradoc::CoreModel::ExampleBlock,
53
- Coradoc::AsciiDoc::Model::Block::Side => Coradoc::CoreModel::SidebarBlock
54
- }.each do |block_class, core_model_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.transform_typed_block(model, core_model_class) },
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
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Coradoc
4
4
  module AsciiDoc
5
- VERSION = '2.0.18'
5
+ VERSION = '2.0.20'
6
6
  end
7
7
  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.18
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