coradoc-mirror 0.1.7 → 0.1.8

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.
Files changed (54) hide show
  1. checksums.yaml +4 -4
  2. data/lib/coradoc/mirror/core_model_to_mirror.rb +17 -0
  3. data/lib/coradoc/mirror/handlers/inline.rb +5 -1
  4. data/lib/coradoc/mirror/mirror_to_core_model.rb +21 -1
  5. data/lib/coradoc/mirror/node.rb +7 -0
  6. data/lib/coradoc/mirror/reverse_builder/abstract.rb +0 -4
  7. data/lib/coradoc/mirror/reverse_builder/admonition.rb +0 -4
  8. data/lib/coradoc/mirror/reverse_builder/biblio_entry.rb +0 -4
  9. data/lib/coradoc/mirror/reverse_builder/bibliography.rb +0 -4
  10. data/lib/coradoc/mirror/reverse_builder/blockquote.rb +0 -4
  11. data/lib/coradoc/mirror/reverse_builder/bullet_list.rb +0 -4
  12. data/lib/coradoc/mirror/reverse_builder/caption.rb +0 -4
  13. data/lib/coradoc/mirror/reverse_builder/code_block.rb +0 -4
  14. data/lib/coradoc/mirror/reverse_builder/definition_list.rb +0 -4
  15. data/lib/coradoc/mirror/reverse_builder/document.rb +0 -4
  16. data/lib/coradoc/mirror/reverse_builder/example.rb +0 -4
  17. data/lib/coradoc/mirror/reverse_builder/figure.rb +0 -4
  18. data/lib/coradoc/mirror/reverse_builder/footnote_entry.rb +0 -4
  19. data/lib/coradoc/mirror/reverse_builder/footnote_marker.rb +0 -4
  20. data/lib/coradoc/mirror/reverse_builder/footnotes.rb +0 -4
  21. data/lib/coradoc/mirror/reverse_builder/frontmatter.rb +0 -4
  22. data/lib/coradoc/mirror/reverse_builder/generic_block.rb +0 -4
  23. data/lib/coradoc/mirror/reverse_builder/hard_break.rb +0 -4
  24. data/lib/coradoc/mirror/reverse_builder/header.rb +0 -4
  25. data/lib/coradoc/mirror/reverse_builder/horizontal_rule.rb +0 -4
  26. data/lib/coradoc/mirror/reverse_builder/image.rb +0 -4
  27. data/lib/coradoc/mirror/reverse_builder/include.rb +0 -4
  28. data/lib/coradoc/mirror/reverse_builder/inline_text.rb +0 -4
  29. data/lib/coradoc/mirror/reverse_builder/list_item.rb +0 -4
  30. data/lib/coradoc/mirror/reverse_builder/literal_block.rb +0 -4
  31. data/lib/coradoc/mirror/reverse_builder/open_block.rb +0 -4
  32. data/lib/coradoc/mirror/reverse_builder/ordered_list.rb +0 -4
  33. data/lib/coradoc/mirror/reverse_builder/paragraph.rb +0 -4
  34. data/lib/coradoc/mirror/reverse_builder/partintro.rb +0 -4
  35. data/lib/coradoc/mirror/reverse_builder/pass_block.rb +0 -4
  36. data/lib/coradoc/mirror/reverse_builder/preamble.rb +0 -4
  37. data/lib/coradoc/mirror/reverse_builder/raw_inline.rb +0 -4
  38. data/lib/coradoc/mirror/reverse_builder/section.rb +0 -6
  39. data/lib/coradoc/mirror/reverse_builder/sections.rb +0 -4
  40. data/lib/coradoc/mirror/reverse_builder/sidebar.rb +0 -4
  41. data/lib/coradoc/mirror/reverse_builder/soft_break.rb +0 -4
  42. data/lib/coradoc/mirror/reverse_builder/stem_block.rb +0 -4
  43. data/lib/coradoc/mirror/reverse_builder/table.rb +0 -4
  44. data/lib/coradoc/mirror/reverse_builder/table_body.rb +0 -4
  45. data/lib/coradoc/mirror/reverse_builder/table_cell.rb +0 -4
  46. data/lib/coradoc/mirror/reverse_builder/table_head.rb +0 -4
  47. data/lib/coradoc/mirror/reverse_builder/table_row.rb +0 -4
  48. data/lib/coradoc/mirror/reverse_builder/text.rb +0 -4
  49. data/lib/coradoc/mirror/reverse_builder/toc.rb +0 -4
  50. data/lib/coradoc/mirror/reverse_builder/toc_entry.rb +0 -4
  51. data/lib/coradoc/mirror/reverse_builder/verse.rb +0 -4
  52. data/lib/coradoc/mirror/reverse_builder.rb +122 -75
  53. data/lib/coradoc/mirror/version.rb +1 -1
  54. metadata +1 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 29f073186a23485753658ba4782ec7022461c2869351d6bb273eda9f30fda92e
4
- data.tar.gz: aee881fe73fa219d89415deb8b6eb0e9a509cbd4b5b71b3f4ddfe6456ef114f8
3
+ metadata.gz: 0a1079e6734d1795dd52788ca44ac74475590dbf6ccb9a576a5fcf4ef92f020d
4
+ data.tar.gz: ab04156585e16f2b4fa61337e3a9eca6b0f758ea82db4233c38f4422686fe611
5
5
  SHA512:
6
- metadata.gz: beabd828aeb5e91a4a3c9e6049045123a2c4884ceef8a9887d4ec27874242a22e36661d2f807a67cbb7bc4a19b4616b8b578463d4ecfb763d98fcf56d5c5a591
7
- data.tar.gz: 23e77c6e5dc7605ba5e4ff8a4cab88efc7433c30554cb5afcb71cff4345d4dc6906ca2efa03d58034e07f6a594e543c7291e9dd88e02f2f458fb5c6c197cdee9
6
+ metadata.gz: d3e532a097d574ac76f76c3ba863ad82fefc8c4007572a30c7d4c4bcad66f14008236a0ef9e4aca550c4f55b91f5dbc75740afac67aa2bcc18bce5c9aae0f0b9
7
+ data.tar.gz: 7bf8e4d4c71e20cb6cb1f71ef4a6e9c5cb1109a81c4fc1b5e85a2b462af197e3f5801827ceb30bce5ed7ce5f897ca3c2300d9fa27db699ee5a9eb239da9d4272
@@ -164,9 +164,26 @@ module Coradoc
164
164
  value, concat = result
165
165
  return unless value
166
166
 
167
+ propagate_source_line(value, element)
167
168
  concat ? content.concat(Array(value)) : content << value
168
169
  end
169
170
 
171
+ # Copy parser-attached source_line from the CoreModel element onto
172
+ # every Mirror node the handler produced. Single touchpoint for the
173
+ # entire CoreModel → Mirror direction — handlers stay focused on
174
+ # per-type mapping and don't repeat this concern (DRY).
175
+ def propagate_source_line(value, element)
176
+ line = element.source_line
177
+ return unless line
178
+
179
+ Array(value).each do |node|
180
+ next unless node.is_a?(Node)
181
+ next if node.source_line
182
+
183
+ node.source_line = line
184
+ end
185
+ end
186
+
170
187
  def build_document_attrs(document)
171
188
  attrs = {}
172
189
  attrs[:title] = document.title if document.title
@@ -208,7 +208,11 @@ module Coradoc
208
208
  def extract_inline_text(element)
209
209
  return element.content.to_s if element.content && !element.content.to_s.empty?
210
210
 
211
- return element.nested_elements.map { |nested| extract_inline_text(nested) }.join if element.is_a?(CoreModel::InlineElement) && element.nested_elements
211
+ if element.is_a?(CoreModel::InlineElement) && element.nested_elements
212
+ return element.nested_elements.map do |nested|
213
+ extract_inline_text(nested)
214
+ end.join
215
+ end
212
216
 
213
217
  if (element.is_a?(CoreModel::InlineElement) || element.is_a?(CoreModel::Block)) && element.children && !element.children.empty?
214
218
  return element.children.map do |child|
@@ -19,7 +19,27 @@ module Coradoc
19
19
  builder_class = ReverseBuilder.lookup(node.type)
20
20
  raise Error, "Unknown mirror node type: #{node.type}" unless builder_class
21
21
 
22
- builder_class.new(self).build(node)
22
+ result = builder_class.new(self).build(node)
23
+ propagate_source_line(result, node)
24
+ result
25
+ end
26
+
27
+ # Copy parser-attached source_line from the Mirror node onto every
28
+ # CoreModel node the builder produced. Single touchpoint for the
29
+ # entire Mirror → CoreModel direction — builders stay focused on
30
+ # per-type mapping and don't repeat this concern (DRY).
31
+ def propagate_source_line(result, node)
32
+ return unless node.is_a?(Node)
33
+
34
+ line = node.source_line
35
+ return unless line
36
+
37
+ Array(result).each do |built|
38
+ next unless built.is_a?(CoreModel::Base)
39
+ next if built.source_line
40
+
41
+ built.source_line = line
42
+ end
23
43
  end
24
44
 
25
45
  # ── Shared helpers (single source of truth — used by every
@@ -29,6 +29,13 @@ module Coradoc
29
29
  attribute :type, :string, default: -> { self.class::PM_TYPE }
30
30
  attribute :content, Node, collection: true
31
31
  attribute :marks, Mark, collection: true
32
+ # Parser metadata, single source of truth on the wire side too.
33
+ # Carried in memory so CoreModel → Mirror → CoreModel round-trips
34
+ # preserve the source location. Intentionally absent from every
35
+ # subclass `key_value` block: source_line is not part of the
36
+ # ProseMirror JSON contract — editor consumers don't need it, and
37
+ # serializing it would leak parser internals onto the wire.
38
+ attribute :source_line, :integer
32
39
 
33
40
  key_value do
34
41
  map 'type', to: :type, render_default: true
@@ -1,13 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'base'
4
-
5
3
  module Coradoc
6
4
  module Mirror
7
5
  module ReverseBuilder
8
6
  class Abstract < Base
9
- registers 'abstract_block'
10
-
11
7
  def build(node)
12
8
  CoreModel::AbstractBlock.new(
13
9
  title: node.attrs&.title,
@@ -1,13 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'base'
4
-
5
3
  module Coradoc
6
4
  module Mirror
7
5
  module ReverseBuilder
8
6
  class Admonition < Base
9
- registers 'admonition'
10
-
11
7
  def build(node)
12
8
  CoreModel::AnnotationBlock.new(
13
9
  annotation_type: node.attrs&.admonition_type,
@@ -1,13 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'base'
4
-
5
3
  module Coradoc
6
4
  module Mirror
7
5
  module ReverseBuilder
8
6
  class BiblioEntry < Base
9
- registers 'biblio_entry'
10
-
11
7
  def build(node)
12
8
  attrs = node.attrs
13
9
  CoreModel::BibliographyEntry.new(
@@ -1,13 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'base'
4
-
5
3
  module Coradoc
6
4
  module Mirror
7
5
  module ReverseBuilder
8
6
  class Bibliography < Base
9
- registers 'bibliography'
10
-
11
7
  def build(node)
12
8
  entries = build_content(node).select { |c| c.is_a?(CoreModel::BibliographyEntry) }
13
9
  CoreModel::Bibliography.new(title: node.attrs&.title, entries: entries)
@@ -1,13 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'base'
4
-
5
3
  module Coradoc
6
4
  module Mirror
7
5
  module ReverseBuilder
8
6
  class Blockquote < Base
9
- registers 'quote'
10
-
11
7
  def build(node)
12
8
  CoreModel::QuoteBlock.new(
13
9
  attribution: node.attrs&.attribution,
@@ -1,13 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'base'
4
-
5
3
  module Coradoc
6
4
  module Mirror
7
5
  module ReverseBuilder
8
6
  class BulletList < Base
9
- registers 'bullet_list'
10
-
11
7
  def build(node)
12
8
  items = build_content(node).select { |c| c.is_a?(CoreModel::ListItem) }
13
9
  CoreModel::ListBlock.new(marker_type: 'unordered', items: items)
@@ -1,15 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'base'
4
-
5
3
  module Coradoc
6
4
  module Mirror
7
5
  module ReverseBuilder
8
6
  # Caption only appears as a Figure child. If encountered standalone,
9
7
  # extract its text as an inline element so it isn't lost.
10
8
  class Caption < Base
11
- registers 'caption'
12
-
13
9
  def build(node)
14
10
  CoreModel::InlineElement.new(content: extract_text(node))
15
11
  end
@@ -1,13 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'base'
4
-
5
3
  module Coradoc
6
4
  module Mirror
7
5
  module ReverseBuilder
8
6
  class CodeBlock < Base
9
- registers 'sourcecode'
10
-
11
7
  def build(node)
12
8
  attrs = node.attrs
13
9
  CoreModel::SourceBlock.new(
@@ -1,13 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'base'
4
-
5
3
  module Coradoc
6
4
  module Mirror
7
5
  module ReverseBuilder
8
6
  class DefinitionList < Base
9
- registers 'dl'
10
-
11
7
  def build(node)
12
8
  terms = []
13
9
  descriptions = []
@@ -1,13 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'base'
4
-
5
3
  module Coradoc
6
4
  module Mirror
7
5
  module ReverseBuilder
8
6
  class Document < Base
9
- registers 'doc'
10
-
11
7
  def build(node)
12
8
  attrs = node.attrs
13
9
  CoreModel::DocumentElement.new(
@@ -1,13 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'base'
4
-
5
3
  module Coradoc
6
4
  module Mirror
7
5
  module ReverseBuilder
8
6
  class Example < Base
9
- registers 'example'
10
-
11
7
  def build(node)
12
8
  CoreModel::ExampleBlock.new(
13
9
  title: node.attrs&.title,
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'base'
4
-
5
3
  module Coradoc
6
4
  module Mirror
7
5
  module ReverseBuilder
@@ -9,8 +7,6 @@ module Coradoc
9
7
  # caption. Reverse: collapse back to a single CoreModel::Image,
10
8
  # promoting the caption child to `caption:` if present.
11
9
  class Figure < Base
12
- registers 'figure'
13
-
14
10
  def build(node)
15
11
  image_child = node.content&.find { |c| c.is_a?(Node) && c.type == 'image' }
16
12
  return nil unless image_child
@@ -1,13 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'base'
4
-
5
3
  module Coradoc
6
4
  module Mirror
7
5
  module ReverseBuilder
8
6
  class FootnoteEntry < Base
9
- registers 'footnote_entry'
10
-
11
7
  def build(node)
12
8
  attrs = node.attrs
13
9
  CoreModel::Footnote.new(id: attrs&.id, content: extract_text(node))
@@ -1,15 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'base'
4
-
5
3
  module Coradoc
6
4
  module Mirror
7
5
  module ReverseBuilder
8
6
  # Inline footnote marker (JS `footnote_marker`). The CoreModel
9
7
  # FootnoteReference holds the same id/ref/number triple.
10
8
  class FootnoteMarker < Base
11
- registers 'footnote_marker'
12
-
13
9
  def build(node)
14
10
  attrs = node.attrs
15
11
  CoreModel::FootnoteReference.new(
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'base'
4
-
5
3
  module Coradoc
6
4
  module Mirror
7
5
  module ReverseBuilder
@@ -9,8 +7,6 @@ module Coradoc
9
7
  # no CoreModel equivalent (each entry is built separately). Returns
10
8
  # nil so build_content filters it out.
11
9
  class Footnotes < Base
12
- registers 'footnotes'
13
-
14
10
  def build(_node)
15
11
  nil
16
12
  end
@@ -1,13 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'base'
4
-
5
3
  module Coradoc
6
4
  module Mirror
7
5
  module ReverseBuilder
8
6
  class Frontmatter < Base
9
- registers 'frontmatter'
10
-
11
7
  def build(node)
12
8
  attrs = node.attrs
13
9
  CoreModel::FrontmatterBlock.new(
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'base'
4
-
5
3
  module Coradoc
6
4
  module Mirror
7
5
  module ReverseBuilder
@@ -9,8 +7,6 @@ module Coradoc
9
7
  # direction (`Node::GenericBlock`). Preserves the semantic_type so
10
8
  # downstream consumers can dispatch on it.
11
9
  class GenericBlock < Base
12
- registers 'generic_block'
13
-
14
10
  def build(node)
15
11
  attrs = node.attrs
16
12
  CoreModel::Block.new(
@@ -1,13 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'base'
4
-
5
3
  module Coradoc
6
4
  module Mirror
7
5
  module ReverseBuilder
8
6
  class HardBreak < Base
9
- registers 'hard_break'
10
-
11
7
  def build(_node)
12
8
  CoreModel::HardLineBreakElement.new(content: '')
13
9
  end
@@ -1,13 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'base'
4
-
5
3
  module Coradoc
6
4
  module Mirror
7
5
  module ReverseBuilder
8
6
  class Header < Base
9
- registers 'floating_title', 'heading'
10
-
11
7
  def build(node)
12
8
  attrs = node.attrs
13
9
  CoreModel::HeaderElement.new(
@@ -1,13 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'base'
4
-
5
3
  module Coradoc
6
4
  module Mirror
7
5
  module ReverseBuilder
8
6
  class HorizontalRule < Base
9
- registers 'horizontal_rule', 'thematic_break'
10
-
11
7
  def build(_node)
12
8
  CoreModel::HorizontalRuleBlock.new
13
9
  end
@@ -1,13 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'base'
4
-
5
3
  module Coradoc
6
4
  module Mirror
7
5
  module ReverseBuilder
8
6
  class Image < Base
9
- registers 'image'
10
-
11
7
  def build(node)
12
8
  attrs = node.attrs
13
9
  CoreModel::Image.new(
@@ -1,15 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'base'
4
-
5
3
  module Coradoc
6
4
  module Mirror
7
5
  module ReverseBuilder
8
6
  # Include directive: round-trips back to a CoreModel::Include link
9
7
  # node. The text graph is preserved through mirror_json → core.
10
8
  class Include < Base
11
- registers 'include'
12
-
13
9
  def build(node)
14
10
  attrs = node.attrs
15
11
  CoreModel::Include.new(
@@ -1,13 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'base'
4
-
5
3
  module Coradoc
6
4
  module Mirror
7
5
  module ReverseBuilder
8
6
  class InlineText < Base
9
- registers 'dt', 'dd'
10
-
11
7
  def build(node)
12
8
  children = build_inline_children(node)
13
9
  text = children.map { |c| c.is_a?(CoreModel::TextContent) ? c.text : '' }.join
@@ -1,13 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'base'
4
-
5
3
  module Coradoc
6
4
  module Mirror
7
5
  module ReverseBuilder
8
6
  class ListItem < Base
9
- registers 'list_item'
10
-
11
7
  LIST_TYPES = %w[bullet_list ordered_list].freeze
12
8
  private_constant :LIST_TYPES
13
9
 
@@ -1,13 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'base'
4
-
5
3
  module Coradoc
6
4
  module Mirror
7
5
  module ReverseBuilder
8
6
  class LiteralBlock < Base
9
- registers 'literal'
10
-
11
7
  def build(node)
12
8
  attrs = node.attrs
13
9
  CoreModel::LiteralBlock.new(
@@ -1,13 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'base'
4
-
5
3
  module Coradoc
6
4
  module Mirror
7
5
  module ReverseBuilder
8
6
  class OpenBlock < Base
9
- registers 'open_block'
10
-
11
7
  def build(node)
12
8
  CoreModel::OpenBlock.new(children: build_content(node))
13
9
  end
@@ -1,13 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'base'
4
-
5
3
  module Coradoc
6
4
  module Mirror
7
5
  module ReverseBuilder
8
6
  class OrderedList < Base
9
- registers 'ordered_list'
10
-
11
7
  def build(node)
12
8
  items = build_content(node).select { |c| c.is_a?(CoreModel::ListItem) }
13
9
  CoreModel::ListBlock.new(marker_type: 'ordered', items: items)
@@ -1,13 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'base'
4
-
5
3
  module Coradoc
6
4
  module Mirror
7
5
  module ReverseBuilder
8
6
  class Paragraph < Base
9
- registers 'paragraph'
10
-
11
7
  def build(node)
12
8
  CoreModel::ParagraphBlock.new(children: build_inline_children(node))
13
9
  end
@@ -1,13 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'base'
4
-
5
3
  module Coradoc
6
4
  module Mirror
7
5
  module ReverseBuilder
8
6
  class Partintro < Base
9
- registers 'partintro_block'
10
-
11
7
  def build(node)
12
8
  CoreModel::PartintroBlock.new(
13
9
  title: node.attrs&.title,
@@ -1,13 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'base'
4
-
5
3
  module Coradoc
6
4
  module Mirror
7
5
  module ReverseBuilder
8
6
  class PassBlock < Base
9
- registers 'pass'
10
-
11
7
  def build(node)
12
8
  attrs = node.attrs
13
9
  CoreModel::PassBlock.new(
@@ -1,13 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'base'
4
-
5
3
  module Coradoc
6
4
  module Mirror
7
5
  module ReverseBuilder
8
6
  class Preamble < Base
9
- registers 'preface'
10
-
11
7
  def build(node)
12
8
  CoreModel::PreambleElement.new(children: build_content(node))
13
9
  end
@@ -1,13 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'base'
4
-
5
3
  module Coradoc
6
4
  module Mirror
7
5
  module ReverseBuilder
8
6
  class RawInline < Base
9
- registers 'raw_inline'
10
-
11
7
  def build(node)
12
8
  CoreModel::RawInlineElement.new(content: node.text.to_s)
13
9
  end
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'base'
4
-
5
3
  module Coradoc
6
4
  module Mirror
7
5
  module ReverseBuilder
@@ -9,10 +7,6 @@ module Coradoc
9
7
  # information lives in the original AsciiDoc and is preserved only
10
8
  # on the forward side; the reverse side collapses them.
11
9
  class Section < Base
12
- registers 'section', 'clause', 'annex', 'content_section',
13
- 'abstract', 'foreword', 'introduction',
14
- 'acknowledgements', 'terms', 'definitions', 'references'
15
-
16
10
  def build(node)
17
11
  attrs = node.attrs
18
12
  CoreModel::SectionElement.new(
@@ -1,15 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'base'
4
-
5
3
  module Coradoc
6
4
  module Mirror
7
5
  module ReverseBuilder
8
6
  # `sections` is a structural container only — unwrap directly into
9
7
  # an array. MirrorToCoreModel#build_content flattens arrays.
10
8
  class Sections < Base
11
- registers 'sections'
12
-
13
9
  def build(node)
14
10
  build_content(node)
15
11
  end
@@ -1,13 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'base'
4
-
5
3
  module Coradoc
6
4
  module Mirror
7
5
  module ReverseBuilder
8
6
  class Sidebar < Base
9
- registers 'sidebar'
10
-
11
7
  def build(node)
12
8
  CoreModel::SidebarBlock.new(
13
9
  title: node.attrs&.title,
@@ -1,13 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'base'
4
-
5
3
  module Coradoc
6
4
  module Mirror
7
5
  module ReverseBuilder
8
6
  class SoftBreak < Base
9
- registers 'soft_break'
10
-
11
7
  def build(_node)
12
8
  CoreModel::LineBreakElement.new
13
9
  end
@@ -1,13 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'base'
4
-
5
3
  module Coradoc
6
4
  module Mirror
7
5
  module ReverseBuilder
8
6
  class StemBlock < Base
9
- registers 'stem'
10
-
11
7
  def build(node)
12
8
  attrs = node.attrs
13
9
  CoreModel::StemBlock.new(
@@ -1,13 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'base'
4
-
5
3
  module Coradoc
6
4
  module Mirror
7
5
  module ReverseBuilder
8
6
  class Table < Base
9
- registers 'table'
10
-
11
7
  def build(node)
12
8
  rows = []
13
9
  node.content&.each do |child|
@@ -1,13 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'base'
4
-
5
3
  module Coradoc
6
4
  module Mirror
7
5
  module ReverseBuilder
8
6
  class TableBody < Base
9
- registers 'table_body'
10
-
11
7
  def build(node)
12
8
  build_content(node).first || CoreModel::TableRow.new
13
9
  end
@@ -1,13 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'base'
4
-
5
3
  module Coradoc
6
4
  module Mirror
7
5
  module ReverseBuilder
8
6
  class TableCell < Base
9
- registers 'table_cell'
10
-
11
7
  def build(node)
12
8
  attrs = node.attrs
13
9
  CoreModel::TableCell.new(
@@ -1,13 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'base'
4
-
5
3
  module Coradoc
6
4
  module Mirror
7
5
  module ReverseBuilder
8
6
  class TableHead < Base
9
- registers 'table_head'
10
-
11
7
  def build(node)
12
8
  build_content(node).first || CoreModel::TableRow.new
13
9
  end
@@ -1,13 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'base'
4
-
5
3
  module Coradoc
6
4
  module Mirror
7
5
  module ReverseBuilder
8
6
  class TableRow < Base
9
- registers 'table_row'
10
-
11
7
  def build(node)
12
8
  cells = build_content(node).select { |c| c.is_a?(CoreModel::TableCell) }
13
9
  CoreModel::TableRow.new(cells: cells)
@@ -1,13 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'base'
4
-
5
3
  module Coradoc
6
4
  module Mirror
7
5
  module ReverseBuilder
8
6
  class Text < Base
9
- registers 'text'
10
-
11
7
  def build(node)
12
8
  text = node.text || ''
13
9
  marks = node.marks || []
@@ -1,13 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'base'
4
-
5
3
  module Coradoc
6
4
  module Mirror
7
5
  module ReverseBuilder
8
6
  class Toc < Base
9
- registers 'toc'
10
-
11
7
  def build(_node)
12
8
  CoreModel::Toc.new
13
9
  end
@@ -1,13 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'base'
4
-
5
3
  module Coradoc
6
4
  module Mirror
7
5
  module ReverseBuilder
8
6
  class TocEntry < Base
9
- registers 'toc_entry'
10
-
11
7
  def build(node)
12
8
  attrs = node.attrs
13
9
  CoreModel::TocEntry.new(id: attrs&.id, title: attrs&.title)
@@ -1,13 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'base'
4
-
5
3
  module Coradoc
6
4
  module Mirror
7
5
  module ReverseBuilder
8
6
  class Verse < Base
9
- registers 'verse'
10
-
11
7
  def build(node)
12
8
  CoreModel::VerseBlock.new(
13
9
  content: extract_text(node),
@@ -2,34 +2,117 @@
2
2
 
3
3
  module Coradoc
4
4
  module Mirror
5
- # OCP-compliant registry for Mirror node -> CoreModel transformation.
5
+ # OCP-compliant registry for Mirror node CoreModel transformation.
6
6
  #
7
- # Adding support for a new Mirror node type is purely additive:
7
+ # Single source of truth: the TYPE_TO_FILE table below maps every
8
+ # Mirror wire type string (and its aliases) to the file that
9
+ # implements its builder. Two things derive from this one table:
8
10
  #
9
- # # reverse_builder/<name>.rb
10
- # require_relative 'base'
11
- # module Coradoc::Mirror::ReverseBuilder
12
- # class Figure < Base
13
- # registers 'figure'
14
- # def build(node) = CoreModel::Image.new(...)
15
- # end
16
- # end
11
+ # 1. autoload declarations — one per unique file, so builders
12
+ # load lazily on first lookup (no 47-file eager load at boot).
13
+ # 2. lookup() — type → file → const_get, which triggers autoload.
17
14
  #
18
- # Then add a single `require_relative` for the new file below. No edits
19
- # to MirrorToCoreModel or any other existing class — the registry is
20
- # the single source of truth for "which type string maps to which
21
- # builder" (MECE).
15
+ # Adding a new built-in builder is purely additive:
22
16
  #
23
- # This file is the autoload target for the ReverseBuilder constant
24
- # (see coradoc/mirror.rb). Each Builder subclass lives in its own
25
- # file under reverse_builder/; eager-requiring them here populates
26
- # the REGISTRY at load time so every caller sees a full registry.
27
- # Mirror-level mark dispatch lives in MarkReverseBuilder
28
- # (mark_reverse_builder.rb).
17
+ # 1. Add `'<wire_type>' => '<file_basename>'` to TYPE_TO_FILE.
18
+ # 2. Create `reverse_builder/<file_basename>.rb` defining
19
+ # `class <CamelizedName> < Base; def build(node); ...; end; end`.
20
+ #
21
+ # No edits to this file beyond the table — autoload and lookup
22
+ # adapt automatically. Mirror-level mark dispatch lives in
23
+ # MarkReverseBuilder (mark_reverse_builder.rb).
24
+ #
25
+ # Third-party / runtime builders can still register via
26
+ # `ReverseBuilder.register(type, klass)`; they take precedence over
27
+ # built-in autoload entries.
29
28
  module ReverseBuilder
30
- # Not frozen: subclasses call `register` from their class body at
31
- # load time, and `registers` may fire late via autoload. Freezing
32
- # here breaks the first mirror-to-core round-trip after load.
29
+ autoload :Base, "#{__dir__}/reverse_builder/base"
30
+
31
+ # Wire type string file basename under reverse_builder/.
32
+ # Aliases (e.g. 'clause' and 'annex' both route to section) are
33
+ # expressed by mapping multiple type strings to the same file.
34
+ TYPE_TO_FILE = {
35
+ 'doc' => 'document',
36
+ 'section' => 'section',
37
+ 'clause' => 'section',
38
+ 'annex' => 'section',
39
+ 'content_section' => 'section',
40
+ 'abstract' => 'section',
41
+ 'foreword' => 'section',
42
+ 'introduction' => 'section',
43
+ 'acknowledgements' => 'section',
44
+ 'terms' => 'section',
45
+ 'definitions' => 'section',
46
+ 'references' => 'section',
47
+ 'sections' => 'sections',
48
+ 'preface' => 'preamble',
49
+ 'floating_title' => 'header',
50
+ 'heading' => 'header',
51
+ 'paragraph' => 'paragraph',
52
+ 'sourcecode' => 'code_block',
53
+ 'literal' => 'literal_block',
54
+ 'pass' => 'pass_block',
55
+ 'stem' => 'stem_block',
56
+ 'quote' => 'blockquote',
57
+ 'example' => 'example',
58
+ 'sidebar' => 'sidebar',
59
+ 'abstract_block' => 'abstract',
60
+ 'partintro_block' => 'partintro',
61
+ 'open_block' => 'open_block',
62
+ 'verse' => 'verse',
63
+ 'horizontal_rule' => 'horizontal_rule',
64
+ 'thematic_break' => 'horizontal_rule',
65
+ 'soft_break' => 'soft_break',
66
+ 'hard_break' => 'hard_break',
67
+ 'admonition' => 'admonition',
68
+ 'bullet_list' => 'bullet_list',
69
+ 'ordered_list' => 'ordered_list',
70
+ 'list_item' => 'list_item',
71
+ 'dl' => 'definition_list',
72
+ 'dt' => 'inline_text',
73
+ 'dd' => 'inline_text',
74
+ 'image' => 'image',
75
+ 'figure' => 'figure',
76
+ 'caption' => 'caption',
77
+ 'include' => 'include',
78
+ 'table' => 'table',
79
+ 'table_head' => 'table_head',
80
+ 'table_body' => 'table_body',
81
+ 'table_row' => 'table_row',
82
+ 'table_cell' => 'table_cell',
83
+ 'bibliography' => 'bibliography',
84
+ 'biblio_entry' => 'biblio_entry',
85
+ 'footnotes' => 'footnotes',
86
+ 'footnote_entry' => 'footnote_entry',
87
+ 'footnote_marker' => 'footnote_marker',
88
+ 'toc' => 'toc',
89
+ 'toc_entry' => 'toc_entry',
90
+ 'text' => 'text',
91
+ 'raw_inline' => 'raw_inline',
92
+ 'frontmatter' => 'frontmatter',
93
+ 'generic_block' => 'generic_block'
94
+ }.freeze
95
+
96
+ # File basename → Ruby constant name. Derived from the file name
97
+ # using the project-wide snake_case → CamelCase convention
98
+ # (every existing builder follows it). Declared once here so
99
+ # adding a builder that follows convention requires no extra
100
+ # wiring.
101
+ FILE_TO_CLASS = TYPE_TO_FILE.each_value.each_with_object({}) do |file, acc|
102
+ next if acc.key?(file)
103
+
104
+ acc[file] = file.split('_').map(&:capitalize).join
105
+ end.freeze
106
+
107
+ # Lazy-load each builder. Lookup triggers autoload via const_get.
108
+ FILE_TO_CLASS.each do |file, const_name|
109
+ autoload const_name.to_sym, "#{__dir__}/reverse_builder/#{file}"
110
+ end
111
+
112
+ # Runtime registrations from third-party / external builders.
113
+ # Takes precedence over built-in autoload entries so external
114
+ # code can override built-in behaviour (e.g. a custom Paragraph).
115
+ # Not frozen: register() writes here at runtime.
33
116
  REGISTRY = {}
34
117
 
35
118
  module_function
@@ -38,63 +121,27 @@ module Coradoc
38
121
  REGISTRY[type] = builder_class
39
122
  end
40
123
 
124
+ # DSL for subclasses to self-register at load time. Kept for
125
+ # backward compatibility with the existing OCP test pattern that
126
+ # creates synthetic builders via `Class.new(Base) { registers(...) }`.
127
+ # Built-in builders no longer call this — their dispatch is
128
+ # derived from TYPE_TO_FILE.
129
+ def registers(*types)
130
+ types.each { |t| register(t, self) }
131
+ end
132
+
41
133
  def lookup(type)
42
- REGISTRY[type]
134
+ return REGISTRY[type] if REGISTRY.key?(type)
135
+
136
+ file = TYPE_TO_FILE[type]
137
+ return nil unless file
138
+
139
+ const_get(FILE_TO_CLASS[file])
43
140
  end
44
141
 
45
142
  def registered_types
46
- REGISTRY.keys
143
+ (REGISTRY.keys + TYPE_TO_FILE.keys).uniq
47
144
  end
48
-
49
- # Base must load first — every subclass inherits from it. Then
50
- # eager-load each Builder so its `registers` call runs.
51
- require_relative 'reverse_builder/base'
52
- require_relative 'reverse_builder/document'
53
- require_relative 'reverse_builder/section'
54
- require_relative 'reverse_builder/header'
55
- require_relative 'reverse_builder/preamble'
56
- require_relative 'reverse_builder/sections'
57
- require_relative 'reverse_builder/paragraph'
58
- require_relative 'reverse_builder/code_block'
59
- require_relative 'reverse_builder/literal_block'
60
- require_relative 'reverse_builder/pass_block'
61
- require_relative 'reverse_builder/stem_block'
62
- require_relative 'reverse_builder/blockquote'
63
- require_relative 'reverse_builder/example'
64
- require_relative 'reverse_builder/sidebar'
65
- require_relative 'reverse_builder/abstract'
66
- require_relative 'reverse_builder/partintro'
67
- require_relative 'reverse_builder/open_block'
68
- require_relative 'reverse_builder/verse'
69
- require_relative 'reverse_builder/horizontal_rule'
70
- require_relative 'reverse_builder/frontmatter'
71
- require_relative 'reverse_builder/admonition'
72
- require_relative 'reverse_builder/bullet_list'
73
- require_relative 'reverse_builder/ordered_list'
74
- require_relative 'reverse_builder/list_item'
75
- require_relative 'reverse_builder/definition_list'
76
- require_relative 'reverse_builder/inline_text'
77
- require_relative 'reverse_builder/image'
78
- require_relative 'reverse_builder/figure'
79
- require_relative 'reverse_builder/caption'
80
- require_relative 'reverse_builder/include'
81
- require_relative 'reverse_builder/table'
82
- require_relative 'reverse_builder/table_head'
83
- require_relative 'reverse_builder/table_body'
84
- require_relative 'reverse_builder/table_row'
85
- require_relative 'reverse_builder/table_cell'
86
- require_relative 'reverse_builder/bibliography'
87
- require_relative 'reverse_builder/biblio_entry'
88
- require_relative 'reverse_builder/footnotes'
89
- require_relative 'reverse_builder/footnote_entry'
90
- require_relative 'reverse_builder/footnote_marker'
91
- require_relative 'reverse_builder/toc'
92
- require_relative 'reverse_builder/toc_entry'
93
- require_relative 'reverse_builder/text'
94
- require_relative 'reverse_builder/raw_inline'
95
- require_relative 'reverse_builder/soft_break'
96
- require_relative 'reverse_builder/hard_break'
97
- require_relative 'reverse_builder/generic_block'
98
145
  end
99
146
  end
100
147
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Coradoc
4
4
  module Mirror
5
- VERSION = '0.1.7'
5
+ VERSION = '0.1.8'
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: coradoc-mirror
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.7
4
+ version: 0.1.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.