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.
- checksums.yaml +4 -4
- data/lib/coradoc/markdown/model/admonition.rb +41 -0
- data/lib/coradoc/markdown/model/comment.rb +14 -0
- data/lib/coradoc/markdown/model/definition_term.rb +16 -6
- data/lib/coradoc/markdown/model/document.rb +9 -0
- data/lib/coradoc/markdown/model/example_block.rb +26 -0
- data/lib/coradoc/markdown/model/hard_line_break.rb +21 -0
- data/lib/coradoc/markdown/model/horizontal_rule.rb +1 -6
- data/lib/coradoc/markdown/model/list_item.rb +1 -1
- data/lib/coradoc/markdown/model/literal.rb +21 -0
- data/lib/coradoc/markdown/model/open_block.rb +22 -0
- data/lib/coradoc/markdown/model/paragraph.rb +2 -2
- data/lib/coradoc/markdown/model/pass.rb +21 -0
- data/lib/coradoc/markdown/model/sidebar.rb +22 -0
- data/lib/coradoc/markdown/model/verse.rb +27 -0
- data/lib/coradoc/markdown/parser/block_parser.rb +1 -1
- data/lib/coradoc/markdown/parser/frontmatter_parser.rb +23 -0
- data/lib/coradoc/markdown/serializer/builder.rb +57 -0
- data/lib/coradoc/markdown/serializer/config.rb +77 -0
- data/lib/coradoc/markdown/serializer/context.rb +66 -0
- data/lib/coradoc/markdown/serializer/element_serializer.rb +46 -0
- data/lib/coradoc/markdown/serializer/flavor.rb +82 -0
- data/lib/coradoc/markdown/serializer/registrations.rb +111 -0
- data/lib/coradoc/markdown/serializer/registry.rb +62 -0
- data/lib/coradoc/markdown/serializer/runner.rb +84 -0
- data/lib/coradoc/markdown/serializer/serializers/abbreviation.rb +19 -0
- data/lib/coradoc/markdown/serializer/serializers/admonition.rb +20 -0
- data/lib/coradoc/markdown/serializer/serializers/attribute_list.rb +25 -0
- data/lib/coradoc/markdown/serializer/serializers/blockquote.rb +19 -0
- data/lib/coradoc/markdown/serializer/serializers/code.rb +19 -0
- data/lib/coradoc/markdown/serializer/serializers/code_block.rb +19 -0
- data/lib/coradoc/markdown/serializer/serializers/comment.rb +31 -0
- data/lib/coradoc/markdown/serializer/serializers/cross_reference.rb +19 -0
- data/lib/coradoc/markdown/serializer/serializers/definition_list.rb +20 -0
- data/lib/coradoc/markdown/serializer/serializers/document.rb +22 -0
- data/lib/coradoc/markdown/serializer/serializers/emphasis.rb +19 -0
- data/lib/coradoc/markdown/serializer/serializers/example_block.rb +40 -0
- data/lib/coradoc/markdown/serializer/serializers/extension.rb +30 -0
- data/lib/coradoc/markdown/serializer/serializers/footnote.rb +20 -0
- data/lib/coradoc/markdown/serializer/serializers/footnote_reference.rb +19 -0
- data/lib/coradoc/markdown/serializer/serializers/hard_line_break.rb +22 -0
- data/lib/coradoc/markdown/serializer/serializers/heading.rb +19 -0
- data/lib/coradoc/markdown/serializer/serializers/highlight.rb +19 -0
- data/lib/coradoc/markdown/serializer/serializers/horizontal_rule.rb +19 -0
- data/lib/coradoc/markdown/serializer/serializers/image.rb +19 -0
- data/lib/coradoc/markdown/serializer/serializers/link.rb +29 -0
- data/lib/coradoc/markdown/serializer/serializers/list.rb +45 -0
- data/lib/coradoc/markdown/serializer/serializers/literal.rb +21 -0
- data/lib/coradoc/markdown/serializer/serializers/math.rb +23 -0
- data/lib/coradoc/markdown/serializer/serializers/open_block.rb +38 -0
- data/lib/coradoc/markdown/serializer/serializers/paragraph.rb +23 -0
- data/lib/coradoc/markdown/serializer/serializers/pass.rb +21 -0
- data/lib/coradoc/markdown/serializer/serializers/sidebar.rb +20 -0
- data/lib/coradoc/markdown/serializer/serializers/strikethrough.rb +19 -0
- data/lib/coradoc/markdown/serializer/serializers/strong.rb +19 -0
- data/lib/coradoc/markdown/serializer/serializers/subscript.rb +19 -0
- data/lib/coradoc/markdown/serializer/serializers/superscript.rb +19 -0
- data/lib/coradoc/markdown/serializer/serializers/table.rb +37 -0
- data/lib/coradoc/markdown/serializer/serializers/underline.rb +19 -0
- data/lib/coradoc/markdown/serializer/serializers/verse.rb +31 -0
- data/lib/coradoc/markdown/serializer/strategies/admonition/base.rb +41 -0
- data/lib/coradoc/markdown/serializer/strategies/admonition/container.rb +34 -0
- data/lib/coradoc/markdown/serializer/strategies/admonition/gfm_alert.rb +28 -0
- data/lib/coradoc/markdown/serializer/strategies/admonition/github.rb +27 -0
- data/lib/coradoc/markdown/serializer/strategies/admonition/html.rb +25 -0
- data/lib/coradoc/markdown/serializer/strategies/admonition/registry.rb +50 -0
- data/lib/coradoc/markdown/serializer/strategies/autolink/angle.rb +35 -0
- data/lib/coradoc/markdown/serializer/strategies/autolink/bare.rb +23 -0
- data/lib/coradoc/markdown/serializer/strategies/autolink/base.rb +33 -0
- data/lib/coradoc/markdown/serializer/strategies/autolink/none.rb +27 -0
- data/lib/coradoc/markdown/serializer/strategies/autolink/registry.rb +58 -0
- data/lib/coradoc/markdown/serializer/strategies/definition_list/base.rb +37 -0
- data/lib/coradoc/markdown/serializer/strategies/definition_list/flat.rb +48 -0
- data/lib/coradoc/markdown/serializer/strategies/definition_list/nested_html.rb +54 -0
- data/lib/coradoc/markdown/serializer/strategies/definition_list/registry.rb +37 -0
- data/lib/coradoc/markdown/serializer.rb +29 -243
- data/lib/coradoc/markdown/toc_generator.rb +2 -2
- data/lib/coradoc/markdown/transform/block_transformer.rb +163 -0
- data/lib/coradoc/markdown/transform/from_core_model.rb +195 -28
- data/lib/coradoc/markdown/transform/image_transformer.rb +20 -0
- data/lib/coradoc/markdown/transform/inline_transformer.rb +74 -0
- data/lib/coradoc/markdown/transform/list_transformer.rb +52 -0
- data/lib/coradoc/markdown/transform/structural_transformer.rb +94 -0
- data/lib/coradoc/markdown/transform/table_transformer.rb +40 -0
- data/lib/coradoc/markdown/transform/to_core_model.rb +24 -3
- data/lib/coradoc/markdown/transformer.rb +87 -2
- data/lib/coradoc/markdown/version.rb +1 -1
- data/lib/coradoc/markdown.rb +16 -2
- metadata +75 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 007d0f6cb59531d5677c8775118fa2e676f811b067b725170963a64d727235da
|
|
4
|
+
data.tar.gz: ca79c3da61050874894e5815cf03c5cce54711781ae51c5c4971cb5b56d36040
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e551c841bc950973c7a96274e77f31b450916cfcd4bc989dd6f425688d493898318f6ac545dc537b1cf584fa7f6d3d8d8fa9c7a00b47750d7407abf2419494b9
|
|
7
|
+
data.tar.gz: c75199d663605c880367231f1f3779480ac7582610d55983a0bedced93b513041bedf8d190066c8d6bacd3220ec6646b9a7b0e57ceb00bbde7ed138dd462cae0
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'base'
|
|
4
|
+
|
|
5
|
+
module Coradoc
|
|
6
|
+
module Markdown
|
|
7
|
+
# Admonition block — a callout like NOTE, WARNING, TIP, etc.
|
|
8
|
+
#
|
|
9
|
+
# Markdown has no native admonition syntax (GFM Alerts since Dec 2023
|
|
10
|
+
# notwithstanding — see admonition/gfm_alert strategy). The
|
|
11
|
+
# `admonition_style` config option selects the output form:
|
|
12
|
+
#
|
|
13
|
+
# :github → > **NOTE:** text (broad compat)
|
|
14
|
+
# :gfm_alert → > [!NOTE]\n> text (GFM native since 2024)
|
|
15
|
+
# :container → :::note\n... \n::: (VitePress / markdown-it)
|
|
16
|
+
# :html → <div class="note">...</div>
|
|
17
|
+
#
|
|
18
|
+
# Type is stored lowercase. Content is raw Markdown text (already
|
|
19
|
+
# serialized). Title is optional.
|
|
20
|
+
class Admonition < Base
|
|
21
|
+
ALLOWED_TYPES = %w[note tip warning important caution].freeze
|
|
22
|
+
|
|
23
|
+
attribute :admonition_type, :string
|
|
24
|
+
attribute :content, :string
|
|
25
|
+
attribute :title, :string
|
|
26
|
+
|
|
27
|
+
# Mixed inline content (strings and inline model objects) carried
|
|
28
|
+
# from the CoreModel children so serializers can preserve cross
|
|
29
|
+
# references, code spans, etc. When empty, fall back to `content`.
|
|
30
|
+
attr_reader :children
|
|
31
|
+
|
|
32
|
+
def initialize(admonition_type:, content:, title: nil, children: [], **rest)
|
|
33
|
+
super
|
|
34
|
+
@admonition_type = admonition_type.to_s.downcase
|
|
35
|
+
@content = content
|
|
36
|
+
@title = title
|
|
37
|
+
@children = Array(children)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Coradoc
|
|
4
|
+
module Markdown
|
|
5
|
+
# HTML comment in Markdown source (`<!-- text -->`).
|
|
6
|
+
#
|
|
7
|
+
# Markdown has no native comment syntax; HTML comments are the conventional
|
|
8
|
+
# mechanism for non-rendered authoring notes. Both AsciiDoc single-line
|
|
9
|
+
# (`//`) and block (`//// ... ////`) comments collapse to this type.
|
|
10
|
+
class Comment < Base
|
|
11
|
+
attribute :text, :string
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -7,15 +7,25 @@ module Coradoc
|
|
|
7
7
|
# A term can have multiple definitions and can span multiple lines.
|
|
8
8
|
# Terms can also have IAL attributes attached.
|
|
9
9
|
#
|
|
10
|
-
#
|
|
11
|
-
#
|
|
12
|
-
#
|
|
10
|
+
# A term can carry a nested DefinitionList when the original document
|
|
11
|
+
# had structured sub-entries (e.g. glossary sub-terms). When `nested`
|
|
12
|
+
# is present, the serializer's nested_html strategy kicks in to emit
|
|
13
|
+
# HTML `<dl>` structure that Markdown syntax cannot express.
|
|
13
14
|
class DefinitionTerm < Base
|
|
14
|
-
# The term text content
|
|
15
15
|
attribute :text, :string
|
|
16
|
-
|
|
17
|
-
# Definitions for this term
|
|
18
16
|
attribute :definitions, Coradoc::Markdown::DefinitionItem, collection: true, default: []
|
|
17
|
+
|
|
18
|
+
# Optional nested definition list under this term.
|
|
19
|
+
# When present, the flat-PHP-Markdown-Extra syntax is no longer
|
|
20
|
+
# sufficient and the serializer falls back to HTML <dl>/<dt>/<dd>.
|
|
21
|
+
attribute :nested, Coradoc::Markdown::DefinitionList
|
|
22
|
+
|
|
23
|
+
def initialize(text: '', definitions: [], nested: nil, **rest)
|
|
24
|
+
super
|
|
25
|
+
@text = text
|
|
26
|
+
@definitions = definitions
|
|
27
|
+
@nested = nested
|
|
28
|
+
end
|
|
19
29
|
end
|
|
20
30
|
end
|
|
21
31
|
end
|
|
@@ -18,6 +18,15 @@ module Coradoc
|
|
|
18
18
|
class Document < Base
|
|
19
19
|
attribute :blocks, Coradoc::Markdown::Base, collection: true
|
|
20
20
|
|
|
21
|
+
# Raw YAML frontmatter text, or nil if absent.
|
|
22
|
+
#
|
|
23
|
+
# Markdown treats frontmatter as opaque text — the YAML is only
|
|
24
|
+
# parsed/emitted at the CoreModel boundary by
|
|
25
|
+
# CoreModel::FrontmatterBlock::Codec (single source of truth).
|
|
26
|
+
# Storing raw text keeps Markdown's parser/serializer symmetric
|
|
27
|
+
# without dragging YAML semantics into the Markdown model (MECE).
|
|
28
|
+
attribute :frontmatter, :string
|
|
29
|
+
|
|
21
30
|
# @param [Integer] index The index of the block to retrieve
|
|
22
31
|
# @return [Coradoc::Markdown::Base] The block at the specified index
|
|
23
32
|
def [](index)
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'base'
|
|
4
|
+
|
|
5
|
+
module Coradoc
|
|
6
|
+
module Markdown
|
|
7
|
+
# Example block — an illustrative container, optionally with a caption.
|
|
8
|
+
#
|
|
9
|
+
# Markdown has no native example syntax. Default rendering is an
|
|
10
|
+
# HTML fallback that preserves the caption as an `<h4>` heading
|
|
11
|
+
# inside a `<div class="example">` wrapper.
|
|
12
|
+
#
|
|
13
|
+
# When `admonition_style == :container` (VitePress), the serializer
|
|
14
|
+
# emits `:::details Caption\n...\n:::`.
|
|
15
|
+
class ExampleBlock < Base
|
|
16
|
+
attribute :content, :string
|
|
17
|
+
attribute :caption, :string
|
|
18
|
+
|
|
19
|
+
def initialize(content:, caption: nil, **rest)
|
|
20
|
+
super
|
|
21
|
+
@content = content
|
|
22
|
+
@caption = caption
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'base'
|
|
4
|
+
|
|
5
|
+
module Coradoc
|
|
6
|
+
module Markdown
|
|
7
|
+
# Hard line break — forces a line break within a paragraph, distinct
|
|
8
|
+
# from a new paragraph.
|
|
9
|
+
#
|
|
10
|
+
# Two strategy modes (configurable via the serializer config):
|
|
11
|
+
# - `:trailing_space` (CommonMark default): two trailing spaces
|
|
12
|
+
# - `:backslash` (GFM alternative): backslash before newline
|
|
13
|
+
#
|
|
14
|
+
# AsciiDoc `+` line-continuation maps to this element.
|
|
15
|
+
class HardLineBreak < Base
|
|
16
|
+
def initialize(**_rest)
|
|
17
|
+
super
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -5,12 +5,7 @@ module Coradoc
|
|
|
5
5
|
# HorizontalRule model representing a Markdown horizontal rule (---, ***, ___).
|
|
6
6
|
#
|
|
7
7
|
class HorizontalRule < Base
|
|
8
|
-
attribute :style, :string, default: '---'
|
|
9
|
-
|
|
10
|
-
def initialize(style: '---')
|
|
11
|
-
super()
|
|
12
|
-
@style = style
|
|
13
|
-
end
|
|
8
|
+
attribute :style, :string, default: '---'
|
|
14
9
|
end
|
|
15
10
|
end
|
|
16
11
|
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'base'
|
|
4
|
+
|
|
5
|
+
module Coradoc
|
|
6
|
+
module Markdown
|
|
7
|
+
# Literal block — preformatted text with no syntax highlighting and
|
|
8
|
+
# no inline formatting. Distinct from a code block (which carries a
|
|
9
|
+
# language for highlighting).
|
|
10
|
+
#
|
|
11
|
+
# Markdown: indented code block (4 leading spaces per line).
|
|
12
|
+
class Literal < Base
|
|
13
|
+
attribute :content, :string
|
|
14
|
+
|
|
15
|
+
def initialize(content:, **rest)
|
|
16
|
+
super
|
|
17
|
+
@content = content
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'base'
|
|
4
|
+
|
|
5
|
+
module Coradoc
|
|
6
|
+
module Markdown
|
|
7
|
+
# Open block — generic container that groups content without semantic
|
|
8
|
+
# formatting. Used in AsciiDoc to apply IDs/attributes to a group.
|
|
9
|
+
#
|
|
10
|
+
# Markdown has no native container. The serializer's OpenBlock strategy:
|
|
11
|
+
# - Without id/classes → emit children as siblings (no wrapper)
|
|
12
|
+
# - With id/classes → emit `<div id="...">...</div>` wrapper
|
|
13
|
+
class OpenBlock < Base
|
|
14
|
+
attribute :children, Coradoc::Markdown::Base, collection: true, default: []
|
|
15
|
+
|
|
16
|
+
def initialize(children: [], **rest)
|
|
17
|
+
super
|
|
18
|
+
@children = Array(children)
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -14,10 +14,10 @@ module Coradoc
|
|
|
14
14
|
# @return [Array] mixed content array
|
|
15
15
|
attr_reader :children
|
|
16
16
|
|
|
17
|
-
def initialize(text: '', children:
|
|
17
|
+
def initialize(text: '', children: [])
|
|
18
18
|
super()
|
|
19
19
|
@text = text
|
|
20
|
-
@children = children
|
|
20
|
+
@children = children
|
|
21
21
|
end
|
|
22
22
|
|
|
23
23
|
def children=(value)
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'base'
|
|
4
|
+
|
|
5
|
+
module Coradoc
|
|
6
|
+
module Markdown
|
|
7
|
+
# Pass block — raw content passed through verbatim, never rendered as
|
|
8
|
+
# Markdown. Used for embedding HTML or other markup directly.
|
|
9
|
+
#
|
|
10
|
+
# Markdown has no native pass concept; the content is emitted as-is
|
|
11
|
+
# inside a `nomarkdown` kramdown extension or raw HTML passthrough.
|
|
12
|
+
class Pass < Base
|
|
13
|
+
attribute :content, :string
|
|
14
|
+
|
|
15
|
+
def initialize(content:, **rest)
|
|
16
|
+
super
|
|
17
|
+
@content = content
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'base'
|
|
4
|
+
|
|
5
|
+
module Coradoc
|
|
6
|
+
module Markdown
|
|
7
|
+
# Sidebar block — a tangential aside, visually distinct from main flow.
|
|
8
|
+
#
|
|
9
|
+
# Markdown has no native sidebar. Serialized as an HTML fallback:
|
|
10
|
+
# <div class="sidebar">...</div>
|
|
11
|
+
class Sidebar < Base
|
|
12
|
+
attribute :content, :string
|
|
13
|
+
attribute :title, :string
|
|
14
|
+
|
|
15
|
+
def initialize(content:, title: nil, **rest)
|
|
16
|
+
super
|
|
17
|
+
@content = content
|
|
18
|
+
@title = title
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'base'
|
|
4
|
+
|
|
5
|
+
module Coradoc
|
|
6
|
+
module Markdown
|
|
7
|
+
# Verse block — preformatted text preserving line breaks but allowing
|
|
8
|
+
# inline formatting. Distinct from a literal block (no formatting)
|
|
9
|
+
# or a code block (no formatting + language hint).
|
|
10
|
+
#
|
|
11
|
+
# Markdown has no native verse. Serialized as a blockquote with the
|
|
12
|
+
# understanding that verse semantics are lost but line breaks are
|
|
13
|
+
# preserved via hard line breaks.
|
|
14
|
+
class Verse < Base
|
|
15
|
+
attribute :content, :string
|
|
16
|
+
attribute :attribution, :string
|
|
17
|
+
attribute :citetitle, :string
|
|
18
|
+
|
|
19
|
+
def initialize(content:, attribution: nil, citetitle: nil, **rest)
|
|
20
|
+
super
|
|
21
|
+
@content = content
|
|
22
|
+
@attribution = attribution
|
|
23
|
+
@citetitle = citetitle
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Coradoc
|
|
4
|
+
module Markdown
|
|
5
|
+
module Parser
|
|
6
|
+
# Markdown frontmatter extractor.
|
|
7
|
+
#
|
|
8
|
+
# Delegates to the shared +Coradoc::CoreModel::FrontmatterBlock::TextSplitter+
|
|
9
|
+
# — the single source of truth for the `---\n...\n---\n` convention
|
|
10
|
+
# (DRY). Format gems retain a local parser module for discoverability
|
|
11
|
+
# and as the seam for any format-specific extensions should they arise.
|
|
12
|
+
module FrontmatterParser
|
|
13
|
+
class << self
|
|
14
|
+
def call(text)
|
|
15
|
+
Coradoc::CoreModel::FrontmatterBlock::TextSplitter.call(text)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
Result = Coradoc::CoreModel::FrontmatterBlock::TextSplitter::Result
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'config'
|
|
4
|
+
require_relative 'context'
|
|
5
|
+
require_relative 'registry'
|
|
6
|
+
require_relative 'registrations'
|
|
7
|
+
require_relative 'runner'
|
|
8
|
+
|
|
9
|
+
module Coradoc
|
|
10
|
+
module Markdown
|
|
11
|
+
class Serializer
|
|
12
|
+
# Fluent builder for a configured serializer runner.
|
|
13
|
+
#
|
|
14
|
+
# Usage:
|
|
15
|
+
#
|
|
16
|
+
# Serializer.build(:gfm) do |config|
|
|
17
|
+
# config.admonition_style = :container
|
|
18
|
+
# config.suppress_comments = false
|
|
19
|
+
# end.call(element)
|
|
20
|
+
#
|
|
21
|
+
# The block yields the Builder itself; assignments accumulate as
|
|
22
|
+
# overrides and are frozen into a Config when `runner` (or `call`)
|
|
23
|
+
# is invoked.
|
|
24
|
+
class Builder
|
|
25
|
+
attr_reader :flavor, :overrides
|
|
26
|
+
|
|
27
|
+
def initialize(flavor = Flavor::DEFAULT_FLAVOR)
|
|
28
|
+
@flavor = flavor
|
|
29
|
+
@overrides = {}
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
Config::ATTRIBUTES.each do |attr|
|
|
33
|
+
define_method("#{attr}=") do |value|
|
|
34
|
+
@overrides[attr] = value
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def apply(hash)
|
|
39
|
+
hash.each { |k, v| @overrides[k.to_sym] = v }
|
|
40
|
+
self
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def config
|
|
44
|
+
@config ||= Config.new(flavor: flavor, **overrides)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def runner
|
|
48
|
+
@runner ||= Runner.new(config: config, registry: Registrations.default_registry)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def call(element)
|
|
52
|
+
runner.call(element)
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'flavor'
|
|
4
|
+
|
|
5
|
+
module Coradoc
|
|
6
|
+
module Markdown
|
|
7
|
+
class Serializer
|
|
8
|
+
# Immutable serialization configuration.
|
|
9
|
+
#
|
|
10
|
+
# Created via `Serializer.build` with a flavor and overrides. Once
|
|
11
|
+
# built, the Config is frozen and resolves capability strategies
|
|
12
|
+
# (admonitions, autolinks, comments, etc.) by combining flavor
|
|
13
|
+
# defaults with caller overrides.
|
|
14
|
+
#
|
|
15
|
+
# SSOT: the 5 spec-mandated options live here and nowhere else.
|
|
16
|
+
# Strategy classes read their mode via `config.strategy_for(:capability)`.
|
|
17
|
+
class Config
|
|
18
|
+
ATTRIBUTES = %i[
|
|
19
|
+
markdown_flavor
|
|
20
|
+
admonition_style
|
|
21
|
+
definition_list_nested
|
|
22
|
+
suppress_comments
|
|
23
|
+
autolinks
|
|
24
|
+
].freeze
|
|
25
|
+
|
|
26
|
+
attr_reader(*ATTRIBUTES)
|
|
27
|
+
|
|
28
|
+
def initialize(flavor: :gfm, **overrides)
|
|
29
|
+
unless Flavor.known?(flavor)
|
|
30
|
+
raise ArgumentError, "Unknown markdown_flavor: #{flavor.inspect}. " \
|
|
31
|
+
"Known: #{Flavor.names.inspect}"
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
resolved = Flavor.resolve(flavor).merge(symbolize(overrides))
|
|
35
|
+
validate_options!(resolved)
|
|
36
|
+
|
|
37
|
+
@markdown_flavor = resolved.fetch(:markdown_flavor)
|
|
38
|
+
@admonition_style = resolved.fetch(:admonition_style)
|
|
39
|
+
@definition_list_nested = resolved.fetch(:definition_list_nested)
|
|
40
|
+
@suppress_comments = resolved.fetch(:suppress_comments)
|
|
41
|
+
@autolinks = resolved.fetch(:autolinks)
|
|
42
|
+
|
|
43
|
+
freeze
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def to_h
|
|
47
|
+
ATTRIBUTES.to_h { |k| [k, public_send(k)] }
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def with(overrides)
|
|
51
|
+
self.class.new(**to_h.merge(symbolize(overrides)))
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
private
|
|
55
|
+
|
|
56
|
+
def symbolize(hash)
|
|
57
|
+
hash.to_h { |k, v| [k.to_sym, v] }
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def validate_options!(resolved)
|
|
61
|
+
unless %i[github container html gfm_alert].include?(resolved.fetch(:admonition_style))
|
|
62
|
+
raise ArgumentError, "Unknown admonition_style: #{resolved[:admonition_style].inspect}"
|
|
63
|
+
end
|
|
64
|
+
unless %i[html flatten].include?(resolved.fetch(:definition_list_nested))
|
|
65
|
+
raise ArgumentError, "Unknown definition_list_nested: #{resolved[:definition_list_nested].inspect}"
|
|
66
|
+
end
|
|
67
|
+
unless [true, false].include?(resolved.fetch(:suppress_comments))
|
|
68
|
+
raise ArgumentError, 'suppress_comments must be boolean'
|
|
69
|
+
end
|
|
70
|
+
return if [true, false].include?(resolved.fetch(:autolinks))
|
|
71
|
+
|
|
72
|
+
raise ArgumentError, 'autolinks must be boolean'
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Coradoc
|
|
4
|
+
module Markdown
|
|
5
|
+
class Serializer
|
|
6
|
+
# Per-document mutable state threaded through every serializer call,
|
|
7
|
+
# AND the single dispatch interface element serializers use to
|
|
8
|
+
# recurse into child elements.
|
|
9
|
+
#
|
|
10
|
+
# Threading dispatch through the context (not the runner) means
|
|
11
|
+
# serializers stay stateless and don't need a back-reference to
|
|
12
|
+
# the runner — they call `ctx.serialize(child)` and `ctx.serialize_inline(child)`.
|
|
13
|
+
#
|
|
14
|
+
# Holds counters (footnotes, link references) and accumulators
|
|
15
|
+
# (footnote definitions emitted at document end, etc.). Created
|
|
16
|
+
# fresh per top-level `call` — never shared between documents.
|
|
17
|
+
class Context
|
|
18
|
+
attr_reader :config, :registry, :runner,
|
|
19
|
+
:footnote_defs, :link_refs,
|
|
20
|
+
:footnote_counter, :link_counter
|
|
21
|
+
|
|
22
|
+
def initialize(config:, registry:, runner:)
|
|
23
|
+
@config = config
|
|
24
|
+
@registry = registry
|
|
25
|
+
@runner = runner
|
|
26
|
+
@footnote_defs = []
|
|
27
|
+
@link_refs = []
|
|
28
|
+
@footnote_counter = 0
|
|
29
|
+
@link_counter = 0
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def serialize(element)
|
|
33
|
+
runner.serialize(element, self)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def serialize_inline(element)
|
|
37
|
+
runner.serialize_inline(element, self)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def serialize_inline_join(elements)
|
|
41
|
+
Array(elements).map { |e| serialize_inline(e) }.join
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def next_footnote_id
|
|
45
|
+
@footnote_counter += 1
|
|
46
|
+
"fn#{@footnote_counter}"
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def next_link_ref_id
|
|
50
|
+
@link_counter += 1
|
|
51
|
+
@link_counter.to_s
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def register_footnote_def(definition_text)
|
|
55
|
+
@footnote_defs << definition_text unless @footnote_defs.include?(definition_text)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def register_link_ref(id, url, title: nil)
|
|
59
|
+
@link_refs << LinkRef.new(id: id, url: url, title: title)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
LinkRef = Struct.new(:id, :url, :title)
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Coradoc
|
|
4
|
+
module Markdown
|
|
5
|
+
class Serializer
|
|
6
|
+
# Base class for element serializers. Subclasses declare:
|
|
7
|
+
# - `handles_type TypeClass` — the model class (or ancestor) they accept
|
|
8
|
+
# - `call(element, ctx)` — the actual serialization
|
|
9
|
+
#
|
|
10
|
+
# `handles?` defaults to `is_a?(handles_type)`; override for finer
|
|
11
|
+
# conditional dispatch (e.g. "I handle DefinitionList only when nested").
|
|
12
|
+
class ElementSerializer
|
|
13
|
+
class << self
|
|
14
|
+
def handles_type(klass = nil)
|
|
15
|
+
if klass
|
|
16
|
+
@handles_type = klass
|
|
17
|
+
self
|
|
18
|
+
else
|
|
19
|
+
@handles_type || (superclass.respond_to?(:handles_type) ? superclass.handles_type : nil)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def handles?(_element)
|
|
24
|
+
true
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def call(...)
|
|
28
|
+
new.call(...)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def call(_element, _ctx)
|
|
33
|
+
raise NotImplementedError, "#{self.class.name}#call not implemented"
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def handles?(element)
|
|
37
|
+
self.class.handles?(element)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def handles_type
|
|
41
|
+
self.class.handles_type
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|