coradoc-adoc 2.0.19 → 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: 817eba65c55a5dea765ab8e80ee5542a1353741659c8a32ef4eb80e39f052dcb
4
- data.tar.gz: 25a07bc63696b7ba27933c9724c0a5ada466dc7cd0f37350ae4c9a4a475afbec
3
+ metadata.gz: fad1b11f8028b7157066b7a2d4474b2d20e3cab1e526e0347ca36015cdf3ad90
4
+ data.tar.gz: d143bb70ec7ab2e3a1656a5e9ed72d52bb0b2f1341b3566efa02ef44343a2044
5
5
  SHA512:
6
- metadata.gz: c3d26bcc8f91f799f455f051109933875545219b14e9556cc4d5774dcda16e9abdcb1f0e6a090f1d9aea35c49d4d7f5ed8c6bae2b1e47a76cc75a86270e47cd7
7
- data.tar.gz: 50a575b3666d1d6df0a8813a9761345ced5d895b2c73bf80347d8fb24e36c396389d4901fa22a6f34e1adcea6cb4d5b697bdab121dc495603da88740753e821d
6
+ metadata.gz: 7848b8f80f104e960265ed5dbd295d4dfc7ad4cecb401b81765ae21ba6d16a31e02024ebfd7c65c1c86e0fd433512788a92ab4600b348293d895980bf2b5b446
7
+ data.tar.gz: c6a55d7b4abecd086fcbde8015fd54b217eb3a442f86699bd1ab110affc1f1dc20f9ff54ef24668dcc77fdcb1cf609b09651308bc4122ea47a05535e18585879
@@ -105,12 +105,24 @@ module Coradoc
105
105
  end
106
106
 
107
107
  def block_content(n_deep = 3)
108
- 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
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.repeat(1)
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
- c = block_image
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
- alt = block_image
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)
@@ -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(
@@ -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
@@ -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
@@ -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 => Coradoc::CoreModel::QuoteBlock,
53
- Coradoc::AsciiDoc::Model::Block::Example => Coradoc::CoreModel::ExampleBlock,
54
- Coradoc::AsciiDoc::Model::Block::Side => Coradoc::CoreModel::SidebarBlock
55
- }.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|
56
61
  Registry.register_with_priority(
57
62
  block_class,
58
- ->(model) { Blk.transform_typed_block(model, core_model_class) },
63
+ ->(model) { Blk.public_send(method, model) },
59
64
  priority: 10
60
65
  )
61
66
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Coradoc
4
4
  module AsciiDoc
5
- VERSION = '2.0.19'
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.19
4
+ version: 2.0.20
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.
@@ -317,6 +317,7 @@ 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
322
323
  - lib/coradoc/asciidoc/transform/element_transformers/include_transformer.rb