coradoc-markdown 1.0.0 → 1.0.3

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 (102) hide show
  1. checksums.yaml +4 -4
  2. data/lib/coradoc/markdown/model/admonition.rb +35 -0
  3. data/lib/coradoc/markdown/model/attribute_list.rb +5 -16
  4. data/lib/coradoc/markdown/model/base.rb +2 -34
  5. data/lib/coradoc/markdown/model/comment.rb +14 -0
  6. data/lib/coradoc/markdown/model/cross_reference.rb +10 -0
  7. data/lib/coradoc/markdown/model/definition_list.rb +0 -12
  8. data/lib/coradoc/markdown/model/definition_term.rb +16 -6
  9. data/lib/coradoc/markdown/model/document.rb +9 -0
  10. data/lib/coradoc/markdown/model/example_block.rb +26 -0
  11. data/lib/coradoc/markdown/model/extension.rb +9 -18
  12. data/lib/coradoc/markdown/model/footnote_reference.rb +0 -5
  13. data/lib/coradoc/markdown/model/hard_line_break.rb +21 -0
  14. data/lib/coradoc/markdown/model/highlight.rb +0 -5
  15. data/lib/coradoc/markdown/model/horizontal_rule.rb +1 -6
  16. data/lib/coradoc/markdown/model/list_item.rb +1 -1
  17. data/lib/coradoc/markdown/model/literal.rb +21 -0
  18. data/lib/coradoc/markdown/model/math.rb +0 -14
  19. data/lib/coradoc/markdown/model/named_value.rb +18 -0
  20. data/lib/coradoc/markdown/model/open_block.rb +22 -0
  21. data/lib/coradoc/markdown/model/paragraph.rb +2 -2
  22. data/lib/coradoc/markdown/model/pass.rb +21 -0
  23. data/lib/coradoc/markdown/model/sidebar.rb +22 -0
  24. data/lib/coradoc/markdown/model/strikethrough.rb +0 -5
  25. data/lib/coradoc/markdown/model/subscript.rb +9 -0
  26. data/lib/coradoc/markdown/model/superscript.rb +9 -0
  27. data/lib/coradoc/markdown/model/underline.rb +9 -0
  28. data/lib/coradoc/markdown/model/verse.rb +27 -0
  29. data/lib/coradoc/markdown/parser/block_parser.rb +1 -1
  30. data/lib/coradoc/markdown/parser/frontmatter_parser.rb +23 -0
  31. data/lib/coradoc/markdown/serializer/builder.rb +57 -0
  32. data/lib/coradoc/markdown/serializer/config.rb +77 -0
  33. data/lib/coradoc/markdown/serializer/context.rb +66 -0
  34. data/lib/coradoc/markdown/serializer/element_serializer.rb +46 -0
  35. data/lib/coradoc/markdown/serializer/flavor.rb +82 -0
  36. data/lib/coradoc/markdown/serializer/registrations.rb +111 -0
  37. data/lib/coradoc/markdown/serializer/registry.rb +62 -0
  38. data/lib/coradoc/markdown/serializer/runner.rb +84 -0
  39. data/lib/coradoc/markdown/serializer/serializers/abbreviation.rb +19 -0
  40. data/lib/coradoc/markdown/serializer/serializers/admonition.rb +20 -0
  41. data/lib/coradoc/markdown/serializer/serializers/attribute_list.rb +25 -0
  42. data/lib/coradoc/markdown/serializer/serializers/blockquote.rb +19 -0
  43. data/lib/coradoc/markdown/serializer/serializers/code.rb +19 -0
  44. data/lib/coradoc/markdown/serializer/serializers/code_block.rb +19 -0
  45. data/lib/coradoc/markdown/serializer/serializers/comment.rb +31 -0
  46. data/lib/coradoc/markdown/serializer/serializers/cross_reference.rb +19 -0
  47. data/lib/coradoc/markdown/serializer/serializers/definition_list.rb +20 -0
  48. data/lib/coradoc/markdown/serializer/serializers/document.rb +22 -0
  49. data/lib/coradoc/markdown/serializer/serializers/emphasis.rb +19 -0
  50. data/lib/coradoc/markdown/serializer/serializers/example_block.rb +40 -0
  51. data/lib/coradoc/markdown/serializer/serializers/extension.rb +30 -0
  52. data/lib/coradoc/markdown/serializer/serializers/footnote.rb +20 -0
  53. data/lib/coradoc/markdown/serializer/serializers/footnote_reference.rb +19 -0
  54. data/lib/coradoc/markdown/serializer/serializers/hard_line_break.rb +22 -0
  55. data/lib/coradoc/markdown/serializer/serializers/heading.rb +19 -0
  56. data/lib/coradoc/markdown/serializer/serializers/highlight.rb +19 -0
  57. data/lib/coradoc/markdown/serializer/serializers/horizontal_rule.rb +19 -0
  58. data/lib/coradoc/markdown/serializer/serializers/image.rb +19 -0
  59. data/lib/coradoc/markdown/serializer/serializers/link.rb +29 -0
  60. data/lib/coradoc/markdown/serializer/serializers/list.rb +45 -0
  61. data/lib/coradoc/markdown/serializer/serializers/literal.rb +21 -0
  62. data/lib/coradoc/markdown/serializer/serializers/math.rb +23 -0
  63. data/lib/coradoc/markdown/serializer/serializers/open_block.rb +38 -0
  64. data/lib/coradoc/markdown/serializer/serializers/paragraph.rb +23 -0
  65. data/lib/coradoc/markdown/serializer/serializers/pass.rb +21 -0
  66. data/lib/coradoc/markdown/serializer/serializers/sidebar.rb +20 -0
  67. data/lib/coradoc/markdown/serializer/serializers/strikethrough.rb +19 -0
  68. data/lib/coradoc/markdown/serializer/serializers/strong.rb +19 -0
  69. data/lib/coradoc/markdown/serializer/serializers/subscript.rb +19 -0
  70. data/lib/coradoc/markdown/serializer/serializers/superscript.rb +19 -0
  71. data/lib/coradoc/markdown/serializer/serializers/table.rb +37 -0
  72. data/lib/coradoc/markdown/serializer/serializers/underline.rb +19 -0
  73. data/lib/coradoc/markdown/serializer/serializers/verse.rb +31 -0
  74. data/lib/coradoc/markdown/serializer/strategies/admonition/base.rb +30 -0
  75. data/lib/coradoc/markdown/serializer/strategies/admonition/container.rb +34 -0
  76. data/lib/coradoc/markdown/serializer/strategies/admonition/gfm_alert.rb +28 -0
  77. data/lib/coradoc/markdown/serializer/strategies/admonition/github.rb +29 -0
  78. data/lib/coradoc/markdown/serializer/strategies/admonition/html.rb +25 -0
  79. data/lib/coradoc/markdown/serializer/strategies/admonition/registry.rb +50 -0
  80. data/lib/coradoc/markdown/serializer/strategies/autolink/angle.rb +35 -0
  81. data/lib/coradoc/markdown/serializer/strategies/autolink/bare.rb +23 -0
  82. data/lib/coradoc/markdown/serializer/strategies/autolink/base.rb +33 -0
  83. data/lib/coradoc/markdown/serializer/strategies/autolink/none.rb +27 -0
  84. data/lib/coradoc/markdown/serializer/strategies/autolink/registry.rb +58 -0
  85. data/lib/coradoc/markdown/serializer/strategies/definition_list/base.rb +37 -0
  86. data/lib/coradoc/markdown/serializer/strategies/definition_list/flat.rb +48 -0
  87. data/lib/coradoc/markdown/serializer/strategies/definition_list/nested_html.rb +54 -0
  88. data/lib/coradoc/markdown/serializer/strategies/definition_list/registry.rb +37 -0
  89. data/lib/coradoc/markdown/serializer.rb +30 -181
  90. data/lib/coradoc/markdown/toc_generator.rb +2 -8
  91. data/lib/coradoc/markdown/transform/block_transformer.rb +163 -0
  92. data/lib/coradoc/markdown/transform/from_core_model.rb +205 -40
  93. data/lib/coradoc/markdown/transform/image_transformer.rb +20 -0
  94. data/lib/coradoc/markdown/transform/inline_transformer.rb +74 -0
  95. data/lib/coradoc/markdown/transform/list_transformer.rb +52 -0
  96. data/lib/coradoc/markdown/transform/structural_transformer.rb +94 -0
  97. data/lib/coradoc/markdown/transform/table_transformer.rb +40 -0
  98. data/lib/coradoc/markdown/transform/to_core_model.rb +36 -30
  99. data/lib/coradoc/markdown/transformer.rb +87 -2
  100. data/lib/coradoc/markdown/version.rb +1 -1
  101. data/lib/coradoc/markdown.rb +23 -20
  102. metadata +89 -10
@@ -1,198 +1,47 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'serializer/builder'
4
+ require_relative 'serializer/flavor'
5
+
3
6
  module Coradoc
4
7
  module Markdown
5
8
  # Serializer for Markdown Document models.
6
9
  #
7
- # This serializer converts Document model objects back into
8
- # Markdown text format.
10
+ # Two equivalent entry points:
11
+ #
12
+ # # Build a configured runner (preferred for non-default options)
13
+ # Serializer.build(:gfm) do |config|
14
+ # config.admonition_style = :container
15
+ # config.suppress_comments = false
16
+ # end.call(element)
17
+ #
18
+ # # One-shot with overrides
19
+ # Serializer.call(element, markdown_flavor: :vitepress)
9
20
  #
21
+ # The legacy `serialize(element, options = {})` class method is kept
22
+ # as a thin alias for `call` so existing callers don't break.
10
23
  class Serializer
11
- # Serialize a document model to Markdown string
12
- #
13
- # @param document [Coradoc::Markdown::Base] The document or element to serialize
14
- # @param options [Hash] Serialization options
15
- # @return [String] The Markdown output
16
- def self.serialize(document, options = {})
17
- new.serialize(document, options)
18
- end
19
-
20
- # Serialize a document model to Markdown string
21
- #
22
- # @param element [Coradoc::Markdown::Base] The element to serialize
23
- # @param options [Hash] Serialization options
24
- # @return [String] The Markdown output
25
- def serialize(element, _options = {})
26
- case element
27
- when Document
28
- serialize_document(element)
29
- when Heading
30
- serialize_heading(element)
31
- when Paragraph
32
- serialize_paragraph(element)
33
- when List
34
- serialize_list(element)
35
- when CodeBlock
36
- serialize_code_block(element)
37
- when Blockquote
38
- serialize_blockquote(element)
39
- when Link
40
- serialize_link(element)
41
- when Image
42
- serialize_image(element)
43
- when HorizontalRule
44
- serialize_horizontal_rule(element)
45
- when Table
46
- serialize_table(element)
47
- when Emphasis
48
- serialize_emphasis(element)
49
- when Strong
50
- serialize_strong(element)
51
- when Code
52
- serialize_code(element)
53
- when DefinitionList
54
- serialize_definition_list(element)
55
- when Footnote
56
- serialize_footnote(element)
57
- when FootnoteReference
58
- serialize_footnote_reference(element)
59
- when Abbreviation
60
- serialize_abbreviation(element)
61
- when Strikethrough
62
- element.to_md
63
- when Highlight
64
- element.to_md
65
- when AttributeList
66
- element.to_md
67
- when Math
68
- element.to_md
69
- when Extension
70
- element.to_md
71
- when String
72
- element
73
- else
74
- raise ArgumentError,
75
- "Unknown element type for serialization: #{element.class}. " \
76
- 'Expected a known Markdown model type.'
24
+ class << self
25
+ def build(flavor = Flavor::DEFAULT_FLAVOR, &block)
26
+ builder = Builder.new(flavor)
27
+ block&.call(builder)
28
+ builder.runner
77
29
  end
78
- end
79
-
80
- private
81
-
82
- def serialize_document(doc)
83
- doc.blocks.map { |block| serialize(block) }.join("\n\n")
84
- end
85
-
86
- def serialize_heading(heading)
87
- "#{'#' * heading.level} #{heading.text}"
88
- end
89
30
 
90
- def serialize_paragraph(para)
91
- if para.children.any?
92
- para.children.map { |child| serialize_inline_content(child) }.join
93
- else
94
- para.text.to_s
31
+ def call(element, **options)
32
+ flavor = options.delete(:markdown_flavor) || options.delete(:flavor) || Flavor::DEFAULT_FLAVOR
33
+ Builder.new(flavor).apply(options).call(element)
95
34
  end
96
- end
97
35
 
98
- def serialize_inline_content(element)
99
- case element
100
- when String
101
- element
102
- when Emphasis, Strong, Code, Link, Image, FootnoteReference, Math, Extension, Strikethrough, Highlight
103
- serialize(element)
104
- else
105
- if element.is_a?(Base)
106
- element.to_md
107
- else
108
- raise ArgumentError,
109
- "Cannot serialize inline content of type #{element.class}. " \
110
- 'Expected String, known inline model, or Base subclass.'
111
- end
36
+ def serialize(element, options = {})
37
+ call(element, **options)
112
38
  end
113
- end
114
-
115
- def serialize_list(list)
116
- marker = list.ordered ? '1.' : '-'
117
- list.items.map do |item|
118
- text = if item.children.any?
119
- item.children.map { |child| serialize_inline_content(child) }.join
120
- else
121
- item.text.to_s
122
- end
123
- if item.checked == true
124
- "- [x] #{text.sub(/^- \[[ x]\] /, '')}"
125
- elsif item.checked == false
126
- "- [ ] #{text.sub(/^- \[[ x]\] /, '')}"
127
- else
128
- "#{marker} #{text}"
129
- end
130
- end.join("\n")
131
- end
132
-
133
- def serialize_code_block(block)
134
- "```#{block.language}\n#{block.code}\n```"
135
- end
136
-
137
- def serialize_blockquote(quote)
138
- quote.content.to_s.lines.map { |line| "> #{line}" }.join
139
- end
140
-
141
- def serialize_link(link)
142
- "[#{link.text}](#{link.url}#{link.title ? " \"#{link.title}\"" : ''})"
143
- end
144
39
 
145
- def serialize_image(img)
146
- "![#{img.alt}](#{img.src}#{img.title ? " \"#{img.title}\"" : ''})"
147
- end
148
-
149
- def serialize_horizontal_rule(rule)
150
- rule.style || '---'
151
- end
152
-
153
- def serialize_table(table)
154
- return '' if table.headers.empty?
155
-
156
- header_row = "| #{table.headers.join(' | ')} |"
157
- separator = "| #{table.headers.map { |_| '---' }.join(' | ')} |"
158
- rows = table.rows.map { |row| "| #{Array(row).join(' | ')} |" }
159
-
160
- [header_row, separator, *rows].join("\n")
161
- end
162
-
163
- def serialize_emphasis(em)
164
- "*#{em.text}*"
165
- end
166
-
167
- def serialize_strong(strong)
168
- "**#{strong.text}**"
169
- end
170
-
171
- def serialize_code(code)
172
- "`#{code.text}`"
173
- end
174
-
175
- def serialize_definition_list(dl)
176
- dl.items.map do |term|
177
- lines = [term.text.to_s]
178
- term.definitions.each do |defn|
179
- lines << ": #{defn.content}"
180
- end
181
- lines.join("\n")
182
- end.join("\n\n")
183
- end
184
-
185
- def serialize_footnote(fn)
186
- content = fn.content.to_s
187
- "[^#{fn.id}]: #{content}"
188
- end
189
-
190
- def serialize_footnote_reference(ref)
191
- "[^#{ref.id}]"
192
- end
193
-
194
- def serialize_abbreviation(abbr)
195
- "*[#{abbr.term}]: #{abbr.definition}"
40
+ def new(*)
41
+ raise NoMethodError,
42
+ 'Coradoc::Markdown::Serializer is no longer instantiable. ' \
43
+ 'Use Serializer.build(:gfm) or Serializer.call(element).'
44
+ end
196
45
  end
197
46
  end
198
47
  end
@@ -2,12 +2,6 @@
2
2
 
3
3
  module Coradoc
4
4
  module Markdown
5
- module Model
6
- autoload :Base, "#{__dir__}/model/base"
7
- autoload :Heading, "#{__dir__}/model/heading"
8
- autoload :Document, "#{__dir__}/model/document"
9
- end
10
-
11
5
  # Table of Contents Generator
12
6
  #
13
7
  # Generates a table of contents from document headings.
@@ -41,12 +35,12 @@ module Coradoc
41
35
  class Entry
42
36
  attr_accessor :id, :text, :level, :children, :number
43
37
 
44
- def initialize(id:, text:, level:, number: nil)
38
+ def initialize(id:, text:, level:, number: nil, children: [])
45
39
  @id = id
46
40
  @text = text
47
41
  @level = level
48
42
  @number = number
49
- @children = []
43
+ @children = children
50
44
  end
51
45
 
52
46
  def to_markdown(indent: 0)
@@ -0,0 +1,163 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Coradoc
4
+ module Markdown
5
+ module Transform
6
+ module BlockTransformer
7
+ class << self
8
+ def transform_block(block)
9
+ semantic = block.resolve_semantic_type || markdown_delimiter_to_semantic(block.delimiter_type)
10
+
11
+ case semantic
12
+ when :paragraph
13
+ transform_paragraph(block)
14
+ when :comment
15
+ transform_comment_block(block)
16
+ when :source_code, :listing, :literal
17
+ transform_code_block(block)
18
+ when :quote
19
+ transform_blockquote(block)
20
+ when :verse
21
+ transform_verse_block(block)
22
+ when :horizontal_rule
23
+ transform_horizontal_rule(block)
24
+ when :pass
25
+ transform_pass_block(block)
26
+ when :example
27
+ transform_example_block(block)
28
+ when :sidebar
29
+ transform_sidebar_block(block)
30
+ when :open
31
+ transform_open_block(block)
32
+ when :reviewer
33
+ transform_reviewer_block(block)
34
+ else
35
+ transform_paragraph(block)
36
+ end
37
+ end
38
+
39
+ def transform_paragraph(block)
40
+ content = block.renderable_content
41
+ has_structured = content.is_a?(Array) && content.any? { |c| !c.is_a?(CoreModel::TextContent) }
42
+ if has_structured
43
+ children = content.map { |c| transform_content_node(c) }
44
+ Coradoc::Markdown::Paragraph.new(text: block.flat_text, children: children)
45
+ else
46
+ Coradoc::Markdown::Paragraph.new(text: block.flat_text)
47
+ end
48
+ end
49
+
50
+ # Transform a content node that could be inline text, an
51
+ # inline element, or a block-level element (e.g. a SourceBlock
52
+ # inside a list item via AsciiDoc continuation).
53
+ def transform_content_node(element)
54
+ case element
55
+ when CoreModel::InlineElement
56
+ InlineTransformer.transform_inline(element)
57
+ when CoreModel::TextContent
58
+ element.text
59
+ when CoreModel::Base
60
+ FromCoreModel.transform(element)
61
+ when String
62
+ element
63
+ else
64
+ element.to_s
65
+ end
66
+ end
67
+
68
+ # Kept as public alias for backward compat with existing callers
69
+ alias transform_inline_content transform_content_node
70
+
71
+ def markdown_delimiter_to_semantic(delimiter)
72
+ case delimiter
73
+ when '```', '~' then :source_code
74
+ when '>' then :quote
75
+ when '---', '***', '___' then :horizontal_rule
76
+ when '++++' then :pass
77
+ end
78
+ end
79
+
80
+ def transform_code_block(block)
81
+ Coradoc::Markdown::CodeBlock.new(
82
+ code: block.content.to_s,
83
+ language: block.language
84
+ )
85
+ end
86
+
87
+ def transform_blockquote(block)
88
+ content = block.flat_text
89
+ Coradoc::Markdown::Blockquote.new(content: content)
90
+ end
91
+
92
+ def transform_verse_block(block)
93
+ Coradoc::Markdown::Blockquote.new(content: block.flat_text)
94
+ end
95
+
96
+ def transform_horizontal_rule(_block)
97
+ Coradoc::Markdown::HorizontalRule.new
98
+ end
99
+
100
+ def transform_example_block(block)
101
+ content = block.flat_text
102
+ Coradoc::Markdown::Blockquote.new(content: content)
103
+ end
104
+
105
+ def transform_sidebar_block(block)
106
+ content = block.flat_text
107
+ Coradoc::Markdown::Paragraph.new(text: content)
108
+ end
109
+
110
+ def transform_open_block(block)
111
+ if block.children && !block.children.empty?
112
+ block.children.map { |c| FromCoreModel.transform(c) }
113
+ else
114
+ content = block.flat_text
115
+ Coradoc::Markdown::Paragraph.new(text: content)
116
+ end
117
+ end
118
+
119
+ def transform_reviewer_block(block)
120
+ text = block.flat_text
121
+ Coradoc::Markdown::Paragraph.new(
122
+ text: "**#{block.annotation_type}:** #{text}"
123
+ )
124
+ end
125
+
126
+ def transform_annotation_block(annotation)
127
+ text = annotation.flat_text
128
+ Coradoc::Markdown::Paragraph.new(
129
+ text: "**#{annotation.annotation_type}:** #{text}"
130
+ )
131
+ end
132
+
133
+ def transform_comment_block(block)
134
+ Coradoc::Markdown::Extension.comment(block.content.to_s)
135
+ end
136
+
137
+ def transform_pass_block(block)
138
+ Coradoc::Markdown::Extension.nomarkdown(block.content.to_s)
139
+ end
140
+ end
141
+
142
+ # Register subclasses first so they match before CoreModel::Block
143
+ FromCoreModel.register(CoreModel::AnnotationBlock, method(:transform_annotation_block))
144
+ FromCoreModel.register(CoreModel::ParagraphBlock, method(:transform_paragraph))
145
+ FromCoreModel.register(CoreModel::SourceBlock, method(:transform_code_block))
146
+ FromCoreModel.register(CoreModel::ListingBlock, method(:transform_code_block))
147
+ FromCoreModel.register(CoreModel::LiteralBlock, method(:transform_code_block))
148
+ FromCoreModel.register(CoreModel::QuoteBlock, method(:transform_blockquote))
149
+ FromCoreModel.register(CoreModel::VerseBlock, method(:transform_verse_block))
150
+ FromCoreModel.register(CoreModel::HorizontalRuleBlock, method(:transform_horizontal_rule))
151
+ FromCoreModel.register(CoreModel::PassBlock, method(:transform_pass_block))
152
+ FromCoreModel.register(CoreModel::ExampleBlock, method(:transform_example_block))
153
+ FromCoreModel.register(CoreModel::SidebarBlock, method(:transform_sidebar_block))
154
+ FromCoreModel.register(CoreModel::OpenBlock, method(:transform_open_block))
155
+ FromCoreModel.register(CoreModel::ReviewerBlock, method(:transform_reviewer_block))
156
+ FromCoreModel.register(CoreModel::CommentBlock, method(:transform_comment_block))
157
+
158
+ # Generic block fallback
159
+ FromCoreModel.register(CoreModel::Block, method(:transform_block))
160
+ end
161
+ end
162
+ end
163
+ end