coradoc-markdown 1.0.2 → 1.0.4

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 (89) hide show
  1. checksums.yaml +4 -4
  2. data/lib/coradoc/markdown/model/admonition.rb +41 -0
  3. data/lib/coradoc/markdown/model/comment.rb +14 -0
  4. data/lib/coradoc/markdown/model/definition_term.rb +16 -6
  5. data/lib/coradoc/markdown/model/document.rb +9 -0
  6. data/lib/coradoc/markdown/model/example_block.rb +26 -0
  7. data/lib/coradoc/markdown/model/hard_line_break.rb +21 -0
  8. data/lib/coradoc/markdown/model/horizontal_rule.rb +1 -6
  9. data/lib/coradoc/markdown/model/list_item.rb +1 -1
  10. data/lib/coradoc/markdown/model/literal.rb +21 -0
  11. data/lib/coradoc/markdown/model/open_block.rb +22 -0
  12. data/lib/coradoc/markdown/model/paragraph.rb +2 -2
  13. data/lib/coradoc/markdown/model/pass.rb +21 -0
  14. data/lib/coradoc/markdown/model/sidebar.rb +22 -0
  15. data/lib/coradoc/markdown/model/verse.rb +27 -0
  16. data/lib/coradoc/markdown/parser/block_parser.rb +1 -1
  17. data/lib/coradoc/markdown/parser/frontmatter_parser.rb +23 -0
  18. data/lib/coradoc/markdown/serializer/builder.rb +57 -0
  19. data/lib/coradoc/markdown/serializer/config.rb +77 -0
  20. data/lib/coradoc/markdown/serializer/context.rb +66 -0
  21. data/lib/coradoc/markdown/serializer/element_serializer.rb +46 -0
  22. data/lib/coradoc/markdown/serializer/flavor.rb +82 -0
  23. data/lib/coradoc/markdown/serializer/registrations.rb +111 -0
  24. data/lib/coradoc/markdown/serializer/registry.rb +62 -0
  25. data/lib/coradoc/markdown/serializer/runner.rb +84 -0
  26. data/lib/coradoc/markdown/serializer/serializers/abbreviation.rb +19 -0
  27. data/lib/coradoc/markdown/serializer/serializers/admonition.rb +20 -0
  28. data/lib/coradoc/markdown/serializer/serializers/attribute_list.rb +25 -0
  29. data/lib/coradoc/markdown/serializer/serializers/blockquote.rb +19 -0
  30. data/lib/coradoc/markdown/serializer/serializers/code.rb +19 -0
  31. data/lib/coradoc/markdown/serializer/serializers/code_block.rb +19 -0
  32. data/lib/coradoc/markdown/serializer/serializers/comment.rb +31 -0
  33. data/lib/coradoc/markdown/serializer/serializers/cross_reference.rb +19 -0
  34. data/lib/coradoc/markdown/serializer/serializers/definition_list.rb +20 -0
  35. data/lib/coradoc/markdown/serializer/serializers/document.rb +22 -0
  36. data/lib/coradoc/markdown/serializer/serializers/emphasis.rb +19 -0
  37. data/lib/coradoc/markdown/serializer/serializers/example_block.rb +40 -0
  38. data/lib/coradoc/markdown/serializer/serializers/extension.rb +30 -0
  39. data/lib/coradoc/markdown/serializer/serializers/footnote.rb +20 -0
  40. data/lib/coradoc/markdown/serializer/serializers/footnote_reference.rb +19 -0
  41. data/lib/coradoc/markdown/serializer/serializers/hard_line_break.rb +22 -0
  42. data/lib/coradoc/markdown/serializer/serializers/heading.rb +19 -0
  43. data/lib/coradoc/markdown/serializer/serializers/highlight.rb +19 -0
  44. data/lib/coradoc/markdown/serializer/serializers/horizontal_rule.rb +19 -0
  45. data/lib/coradoc/markdown/serializer/serializers/image.rb +19 -0
  46. data/lib/coradoc/markdown/serializer/serializers/link.rb +29 -0
  47. data/lib/coradoc/markdown/serializer/serializers/list.rb +45 -0
  48. data/lib/coradoc/markdown/serializer/serializers/literal.rb +21 -0
  49. data/lib/coradoc/markdown/serializer/serializers/math.rb +23 -0
  50. data/lib/coradoc/markdown/serializer/serializers/open_block.rb +38 -0
  51. data/lib/coradoc/markdown/serializer/serializers/paragraph.rb +23 -0
  52. data/lib/coradoc/markdown/serializer/serializers/pass.rb +21 -0
  53. data/lib/coradoc/markdown/serializer/serializers/sidebar.rb +20 -0
  54. data/lib/coradoc/markdown/serializer/serializers/strikethrough.rb +19 -0
  55. data/lib/coradoc/markdown/serializer/serializers/strong.rb +19 -0
  56. data/lib/coradoc/markdown/serializer/serializers/subscript.rb +19 -0
  57. data/lib/coradoc/markdown/serializer/serializers/superscript.rb +19 -0
  58. data/lib/coradoc/markdown/serializer/serializers/table.rb +37 -0
  59. data/lib/coradoc/markdown/serializer/serializers/underline.rb +19 -0
  60. data/lib/coradoc/markdown/serializer/serializers/verse.rb +31 -0
  61. data/lib/coradoc/markdown/serializer/strategies/admonition/base.rb +41 -0
  62. data/lib/coradoc/markdown/serializer/strategies/admonition/container.rb +34 -0
  63. data/lib/coradoc/markdown/serializer/strategies/admonition/gfm_alert.rb +28 -0
  64. data/lib/coradoc/markdown/serializer/strategies/admonition/github.rb +27 -0
  65. data/lib/coradoc/markdown/serializer/strategies/admonition/html.rb +25 -0
  66. data/lib/coradoc/markdown/serializer/strategies/admonition/registry.rb +50 -0
  67. data/lib/coradoc/markdown/serializer/strategies/autolink/angle.rb +35 -0
  68. data/lib/coradoc/markdown/serializer/strategies/autolink/bare.rb +23 -0
  69. data/lib/coradoc/markdown/serializer/strategies/autolink/base.rb +33 -0
  70. data/lib/coradoc/markdown/serializer/strategies/autolink/none.rb +27 -0
  71. data/lib/coradoc/markdown/serializer/strategies/autolink/registry.rb +58 -0
  72. data/lib/coradoc/markdown/serializer/strategies/definition_list/base.rb +37 -0
  73. data/lib/coradoc/markdown/serializer/strategies/definition_list/flat.rb +48 -0
  74. data/lib/coradoc/markdown/serializer/strategies/definition_list/nested_html.rb +54 -0
  75. data/lib/coradoc/markdown/serializer/strategies/definition_list/registry.rb +37 -0
  76. data/lib/coradoc/markdown/serializer.rb +29 -243
  77. data/lib/coradoc/markdown/toc_generator.rb +2 -2
  78. data/lib/coradoc/markdown/transform/block_transformer.rb +163 -0
  79. data/lib/coradoc/markdown/transform/from_core_model.rb +195 -28
  80. data/lib/coradoc/markdown/transform/image_transformer.rb +20 -0
  81. data/lib/coradoc/markdown/transform/inline_transformer.rb +74 -0
  82. data/lib/coradoc/markdown/transform/list_transformer.rb +52 -0
  83. data/lib/coradoc/markdown/transform/structural_transformer.rb +94 -0
  84. data/lib/coradoc/markdown/transform/table_transformer.rb +40 -0
  85. data/lib/coradoc/markdown/transform/to_core_model.rb +24 -3
  86. data/lib/coradoc/markdown/transformer.rb +87 -2
  87. data/lib/coradoc/markdown/version.rb +1 -1
  88. data/lib/coradoc/markdown.rb +16 -2
  89. metadata +75 -1
@@ -1,262 +1,48 @@
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)
9
17
  #
18
+ # # One-shot with overrides
19
+ # Serializer.call(element, markdown_flavor: :vitepress)
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
- serialize_strikethrough(element)
63
- when Highlight
64
- serialize_highlight(element)
65
- when Subscript
66
- serialize_subscript(element)
67
- when Superscript
68
- serialize_superscript(element)
69
- when Underline
70
- serialize_underline(element)
71
- when CrossReference
72
- serialize_cross_reference(element)
73
- when AttributeList
74
- serialize_attribute_list(element)
75
- when Math
76
- serialize_math(element)
77
- when Extension
78
- serialize_extension(element)
79
- when String
80
- element
81
- else
82
- raise ArgumentError,
83
- "Unknown element type for serialization: #{element.class}. " \
84
- '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
85
29
  end
86
- end
87
-
88
- private
89
-
90
- def serialize_document(doc)
91
- doc.blocks.map { |block| serialize(block) }.join("\n\n")
92
- end
93
-
94
- def serialize_heading(heading)
95
- "#{'#' * heading.level} #{heading.text}"
96
- end
97
30
 
98
- def serialize_paragraph(para)
99
- if para.children.any?
100
- para.children.map { |child| serialize_inline_content(child) }.join
101
- else
102
- 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)
103
34
  end
104
- end
105
35
 
106
- def serialize_inline_content(element)
107
- case element
108
- when String
109
- element
110
- when Emphasis, Strong, Code, Link, Image, FootnoteReference, Math, Extension,
111
- Strikethrough, Highlight, Subscript, Superscript, Underline, CrossReference,
112
- AttributeList, DefinitionList
113
- serialize(element)
114
- when Base
115
- serialize(element)
116
- else
117
- raise ArgumentError,
118
- "Cannot serialize inline content of type #{element.class}. " \
119
- 'Expected String, known inline model, or Base subclass.'
36
+ def serialize(element, options = {})
37
+ call(element, **options)
120
38
  end
121
- end
122
-
123
- def serialize_list(list)
124
- marker = list.ordered ? '1.' : '-'
125
- list.items.map do |item|
126
- text = if item.children.any?
127
- item.children.map { |child| serialize_inline_content(child) }.join
128
- else
129
- item.text.to_s
130
- end
131
- if item.checked == true
132
- "- [x] #{text.sub(/^- \[[ x]\] /, '')}"
133
- elsif item.checked == false
134
- "- [ ] #{text.sub(/^- \[[ x]\] /, '')}"
135
- else
136
- "#{marker} #{text}"
137
- end
138
- end.join("\n")
139
- end
140
-
141
- def serialize_code_block(block)
142
- "```#{block.language}\n#{block.code}\n```"
143
- end
144
-
145
- def serialize_blockquote(quote)
146
- quote.content.to_s.lines.map { |line| "> #{line}" }.join
147
- end
148
-
149
- def serialize_link(link)
150
- "[#{link.text}](#{link.url}#{link.title ? " \"#{link.title}\"" : ''})"
151
- end
152
-
153
- def serialize_image(img)
154
- "![#{img.alt}](#{img.src}#{img.title ? " \"#{img.title}\"" : ''})"
155
- end
156
-
157
- def serialize_horizontal_rule(rule)
158
- rule.style || '---'
159
- end
160
-
161
- def serialize_table(table)
162
- return '' if table.headers.empty?
163
-
164
- header_row = "| #{table.headers.join(' | ')} |"
165
- separator = "| #{table.headers.map { |_| '---' }.join(' | ')} |"
166
- rows = table.rows.map { |row| "| #{Array(row).join(' | ')} |" }
167
-
168
- [header_row, separator, *rows].join("\n")
169
- end
170
-
171
- def serialize_emphasis(em)
172
- "*#{em.text}*"
173
- end
174
39
 
175
- def serialize_strong(strong)
176
- "**#{strong.text}**"
177
- end
178
-
179
- def serialize_code(code)
180
- "`#{code.text}`"
181
- end
182
-
183
- def serialize_definition_list(dl)
184
- dl.items.map do |term|
185
- lines = [term.text.to_s]
186
- term.definitions.each do |defn|
187
- lines << ": #{defn.content}"
188
- end
189
- lines.join("\n")
190
- end.join("\n\n")
191
- end
192
-
193
- def serialize_footnote(fn)
194
- content = fn.content.to_s
195
- "[^#{fn.id}]: #{content}"
196
- end
197
-
198
- def serialize_footnote_reference(ref)
199
- "[^#{ref.id}]"
200
- end
201
-
202
- def serialize_abbreviation(abbr)
203
- "*[#{abbr.term}]: #{abbr.definition}"
204
- end
205
-
206
- def serialize_strikethrough(elem)
207
- "~~#{elem.text}~~"
208
- end
209
-
210
- def serialize_highlight(elem)
211
- "==#{elem.text}=="
212
- end
213
-
214
- def serialize_subscript(elem)
215
- "<sub>#{elem.text}</sub>"
216
- end
217
-
218
- def serialize_superscript(elem)
219
- "<sup>#{elem.text}</sup>"
220
- end
221
-
222
- def serialize_underline(elem)
223
- "<u>#{elem.text}</u>"
224
- end
225
-
226
- def serialize_cross_reference(elem)
227
- "[#{elem.text}](##{elem.target})"
228
- end
229
-
230
- def serialize_attribute_list(elem)
231
- return '' if elem.empty?
232
-
233
- parts = []
234
- parts << "##{elem.id}" if elem.id
235
- parts += elem.classes.map { |c| ".#{c}" }
236
- parts += elem.attributes.map { |nv| %(#{nv.name}="#{nv.value}") }
237
- "{:#{parts.join(' ')}}"
238
- end
239
-
240
- def serialize_math(elem)
241
- if elem.inline?
242
- "$$#{elem.content}$$"
243
- else
244
- "$$\n#{elem.content}\n$$"
40
+ def new(*)
41
+ raise NoMethodError,
42
+ 'Coradoc::Markdown::Serializer is no longer instantiable. ' \
43
+ 'Use Serializer.build(:gfm) or Serializer.call(element).'
245
44
  end
246
45
  end
247
-
248
- def serialize_extension(elem)
249
- opts = elem.options.empty? ? '' : " #{extension_options_to_s(elem.options)}"
250
- if elem.self_closing?
251
- "{::#{elem.name}#{opts} /}"
252
- else
253
- "{::#{elem.name}#{opts}}#{elem.content}{:/}"
254
- end
255
- end
256
-
257
- def extension_options_to_s(options)
258
- options.map { |nv| %(#{nv.name}="#{nv.value}") }.join(' ')
259
- end
260
46
  end
261
47
  end
262
48
  end
@@ -35,12 +35,12 @@ module Coradoc
35
35
  class Entry
36
36
  attr_accessor :id, :text, :level, :children, :number
37
37
 
38
- def initialize(id:, text:, level:, number: nil)
38
+ def initialize(id:, text:, level:, number: nil, children: [])
39
39
  @id = id
40
40
  @text = text
41
41
  @level = level
42
42
  @number = number
43
- @children = []
43
+ @children = children
44
44
  end
45
45
 
46
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