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.
- checksums.yaml +4 -4
- data/lib/coradoc/markdown/model/admonition.rb +35 -0
- data/lib/coradoc/markdown/model/attribute_list.rb +5 -16
- data/lib/coradoc/markdown/model/base.rb +2 -34
- data/lib/coradoc/markdown/model/comment.rb +14 -0
- data/lib/coradoc/markdown/model/cross_reference.rb +10 -0
- data/lib/coradoc/markdown/model/definition_list.rb +0 -12
- 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/extension.rb +9 -18
- data/lib/coradoc/markdown/model/footnote_reference.rb +0 -5
- data/lib/coradoc/markdown/model/hard_line_break.rb +21 -0
- data/lib/coradoc/markdown/model/highlight.rb +0 -5
- 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/math.rb +0 -14
- data/lib/coradoc/markdown/model/named_value.rb +18 -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/strikethrough.rb +0 -5
- data/lib/coradoc/markdown/model/subscript.rb +9 -0
- data/lib/coradoc/markdown/model/superscript.rb +9 -0
- data/lib/coradoc/markdown/model/underline.rb +9 -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 +30 -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 +29 -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 +30 -181
- data/lib/coradoc/markdown/toc_generator.rb +2 -8
- data/lib/coradoc/markdown/transform/block_transformer.rb +163 -0
- data/lib/coradoc/markdown/transform/from_core_model.rb +205 -40
- 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 +36 -30
- data/lib/coradoc/markdown/transformer.rb +87 -2
- data/lib/coradoc/markdown/version.rb +1 -1
- data/lib/coradoc/markdown.rb +23 -20
- metadata +89 -10
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../element_serializer'
|
|
4
|
+
|
|
5
|
+
module Coradoc
|
|
6
|
+
module Markdown
|
|
7
|
+
class Serializer
|
|
8
|
+
module Serializers
|
|
9
|
+
class Table < ElementSerializer
|
|
10
|
+
handles_type ::Coradoc::Markdown::Table
|
|
11
|
+
|
|
12
|
+
def call(element, _ctx)
|
|
13
|
+
cols = [element.headers.size, *element.rows.map { |r| row_cells(r).size }].max
|
|
14
|
+
return '' if cols.zero?
|
|
15
|
+
|
|
16
|
+
headers = element.headers.empty? ? Array.new(cols, '') : element.headers
|
|
17
|
+
header_row = "| #{headers.join(' | ')} |"
|
|
18
|
+
separator = "| #{headers.map { '---' }.join(' | ')} |"
|
|
19
|
+
rows = element.rows.map do |row|
|
|
20
|
+
cells = row_cells(row).fill('', row_cells(row).size...cols)
|
|
21
|
+
"| #{cells.join(' | ')} |"
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
[header_row, separator, *rows].join("\n")
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
private
|
|
28
|
+
|
|
29
|
+
# Rows are stored as either Array<String> (cells) or pipe-joined String
|
|
30
|
+
def row_cells(row)
|
|
31
|
+
row.is_a?(Array) ? row : row.to_s.split(' | ')
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../element_serializer'
|
|
4
|
+
|
|
5
|
+
module Coradoc
|
|
6
|
+
module Markdown
|
|
7
|
+
class Serializer
|
|
8
|
+
module Serializers
|
|
9
|
+
class Underline < ElementSerializer
|
|
10
|
+
handles_type ::Coradoc::Markdown::Underline
|
|
11
|
+
|
|
12
|
+
def call(element, _ctx)
|
|
13
|
+
"<u>#{element.text}</u>"
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../element_serializer'
|
|
4
|
+
|
|
5
|
+
module Coradoc
|
|
6
|
+
module Markdown
|
|
7
|
+
class Serializer
|
|
8
|
+
module Serializers
|
|
9
|
+
# Verse: blockquote with hard line breaks preserved. The verse
|
|
10
|
+
# semantic is lost but line breaks survive.
|
|
11
|
+
class Verse < ElementSerializer
|
|
12
|
+
handles_type ::Coradoc::Markdown::Verse
|
|
13
|
+
|
|
14
|
+
def call(element, _ctx)
|
|
15
|
+
body = element.content.to_s
|
|
16
|
+
attribution = element.attribution
|
|
17
|
+
citetitle = element.citetitle
|
|
18
|
+
attribution_line = if attribution && citetitle
|
|
19
|
+
"\n>\n> — #{attribution}, <cite>#{citetitle}</cite>"
|
|
20
|
+
elsif attribution
|
|
21
|
+
"\n>\n> — #{attribution}"
|
|
22
|
+
else
|
|
23
|
+
''
|
|
24
|
+
end
|
|
25
|
+
body.lines.map { |line| "> #{line}".rstrip }.join("\n").concat(attribution_line)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Coradoc
|
|
4
|
+
module Markdown
|
|
5
|
+
class Serializer
|
|
6
|
+
module Strategies
|
|
7
|
+
module Admonition
|
|
8
|
+
# Each strategy renders an admonition (type, content, optional
|
|
9
|
+
# title) for a specific output form. The active strategy is
|
|
10
|
+
# chosen by `config.admonition_style`.
|
|
11
|
+
#
|
|
12
|
+
# Strategies are stateless; all state flows through arguments.
|
|
13
|
+
# Adding a new admonition form = adding one file + one entry
|
|
14
|
+
# in Registry::MODES.
|
|
15
|
+
class Base
|
|
16
|
+
class << self
|
|
17
|
+
def render(_admonition, _ctx)
|
|
18
|
+
raise NotImplementedError
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def mode_name
|
|
22
|
+
name.split('::').last.gsub(/([a-z\d])([A-Z])/, '\1_\2').downcase.to_sym
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'base'
|
|
4
|
+
|
|
5
|
+
module Coradoc
|
|
6
|
+
module Markdown
|
|
7
|
+
class Serializer
|
|
8
|
+
module Strategies
|
|
9
|
+
module Admonition
|
|
10
|
+
# Container syntax (VitePress, markdown-it-container):
|
|
11
|
+
#
|
|
12
|
+
# :::note
|
|
13
|
+
# content
|
|
14
|
+
# :::
|
|
15
|
+
#
|
|
16
|
+
# Custom title:
|
|
17
|
+
#
|
|
18
|
+
# :::note[Custom Title]
|
|
19
|
+
# content
|
|
20
|
+
# :::
|
|
21
|
+
class Container < Base
|
|
22
|
+
class << self
|
|
23
|
+
def render(admonition, _ctx)
|
|
24
|
+
type = admonition.admonition_type.to_s
|
|
25
|
+
title_suffix = admonition.title ? "[#{admonition.title}]" : ''
|
|
26
|
+
":::#{type}#{title_suffix}\n#{admonition.content.to_s}\n:::"
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'base'
|
|
4
|
+
|
|
5
|
+
module Coradoc
|
|
6
|
+
module Markdown
|
|
7
|
+
class Serializer
|
|
8
|
+
module Strategies
|
|
9
|
+
module Admonition
|
|
10
|
+
# GFM Alerts (native since Dec 2023): `> [!TYPE]\n> content`.
|
|
11
|
+
# Recognized types: NOTE, TIP, IMPORTANT, WARNING, CAUTION.
|
|
12
|
+
# Source: https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax#alerts
|
|
13
|
+
class GfmAlert < Base
|
|
14
|
+
class << self
|
|
15
|
+
def render(admonition, _ctx)
|
|
16
|
+
type = admonition.admonition_type.to_s.capitalize
|
|
17
|
+
body = admonition.content.to_s
|
|
18
|
+
body = body.lines.map { |line| "> #{line}".rstrip }.join("\n")
|
|
19
|
+
title_suffix = admonition.title ? " \"#{admonition.title}\"" : ''
|
|
20
|
+
"> [!#{type}]#{title_suffix}\n#{body}"
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'base'
|
|
4
|
+
|
|
5
|
+
module Coradoc
|
|
6
|
+
module Markdown
|
|
7
|
+
class Serializer
|
|
8
|
+
module Strategies
|
|
9
|
+
module Admonition
|
|
10
|
+
# GitHub-style: a styled blockquote with `**TYPE:**` prefix.
|
|
11
|
+
# Renders correctly in GitHub, GitLab, and most renderers that
|
|
12
|
+
# recognize the bold-prefix pattern.
|
|
13
|
+
class Github < Base
|
|
14
|
+
class << self
|
|
15
|
+
def render(admonition, _ctx)
|
|
16
|
+
type = admonition.admonition_type.to_s.upcase
|
|
17
|
+
body = admonition.content.to_s
|
|
18
|
+
if admonition.title
|
|
19
|
+
body = "**#{admonition.title}**\n\n#{body}" unless admonition.title.to_s.strip.empty?
|
|
20
|
+
end
|
|
21
|
+
"> **#{type}:** #{body}"
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'base'
|
|
4
|
+
|
|
5
|
+
module Coradoc
|
|
6
|
+
module Markdown
|
|
7
|
+
class Serializer
|
|
8
|
+
module Strategies
|
|
9
|
+
module Admonition
|
|
10
|
+
# HTML fallback: a div with `class="<type>"` and optional
|
|
11
|
+
# `<div class="title">` for the title.
|
|
12
|
+
class Html < Base
|
|
13
|
+
class << self
|
|
14
|
+
def render(admonition, _ctx)
|
|
15
|
+
type = admonition.admonition_type.to_s
|
|
16
|
+
title_html = admonition.title ? %(<div class="title">#{admonition.title}</div>\n) : ''
|
|
17
|
+
%(<div class="#{type}">\n#{title_html}#{admonition.content.to_s}\n</div>)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'base'
|
|
4
|
+
require_relative 'github'
|
|
5
|
+
require_relative 'gfm_alert'
|
|
6
|
+
require_relative 'container'
|
|
7
|
+
require_relative 'html'
|
|
8
|
+
|
|
9
|
+
module Coradoc
|
|
10
|
+
module Markdown
|
|
11
|
+
class Serializer
|
|
12
|
+
module Strategies
|
|
13
|
+
module Admonition
|
|
14
|
+
# Resolves the active admonition strategy from `config.admonition_style`.
|
|
15
|
+
#
|
|
16
|
+
# Modes:
|
|
17
|
+
# :github → > **NOTE:** text (broad compat — DEFAULT for :gfm)
|
|
18
|
+
# :gfm_alert → > [!NOTE]\n> text (GFM native since 2024)
|
|
19
|
+
# :container → :::note\n... \n::: (VitePress / markdown-it)
|
|
20
|
+
# :html → <div class="note">...</div>
|
|
21
|
+
module Registry
|
|
22
|
+
MODES = {
|
|
23
|
+
github: Github,
|
|
24
|
+
gfm_alert: GfmAlert,
|
|
25
|
+
container: Container,
|
|
26
|
+
html: Html
|
|
27
|
+
}.freeze
|
|
28
|
+
|
|
29
|
+
class << self
|
|
30
|
+
def lookup(mode)
|
|
31
|
+
MODES.fetch(mode.to_sym) do
|
|
32
|
+
raise ArgumentError, "Unknown admonition mode: #{mode.inspect}. " \
|
|
33
|
+
"Known: #{MODES.keys.inspect}"
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def resolve(ctx:)
|
|
38
|
+
lookup(ctx.config.admonition_style)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def render(admonition, ctx:)
|
|
42
|
+
resolve(ctx: ctx).render(admonition, ctx)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'base'
|
|
4
|
+
|
|
5
|
+
module Coradoc
|
|
6
|
+
module Markdown
|
|
7
|
+
class Serializer
|
|
8
|
+
module Strategies
|
|
9
|
+
module Autolink
|
|
10
|
+
# CommonMark angle-bracket autolink: <https://example.com>
|
|
11
|
+
# Renders identically across all HTML-aware renderers.
|
|
12
|
+
class Angle < Base
|
|
13
|
+
class << self
|
|
14
|
+
def applies?(url, text, ctx)
|
|
15
|
+
ctx.config.autolinks && url_eql_text?(url, text)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def render(url, _text, _ctx)
|
|
19
|
+
"<#{url}>"
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
private
|
|
23
|
+
|
|
24
|
+
def url_eql_text?(url, text)
|
|
25
|
+
return false unless url && text
|
|
26
|
+
|
|
27
|
+
url.to_s.strip == text.to_s.strip
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'angle'
|
|
4
|
+
|
|
5
|
+
module Coradoc
|
|
6
|
+
module Markdown
|
|
7
|
+
class Serializer
|
|
8
|
+
module Strategies
|
|
9
|
+
module Autolink
|
|
10
|
+
# GFM bare-URL autolink: https://example.com (no brackets).
|
|
11
|
+
# Supported by GFM and most modern renderers; minimal noise.
|
|
12
|
+
class Bare < Angle
|
|
13
|
+
class << self
|
|
14
|
+
def render(url, _text, _ctx)
|
|
15
|
+
url.to_s
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Coradoc
|
|
4
|
+
module Markdown
|
|
5
|
+
class Serializer
|
|
6
|
+
module Strategies
|
|
7
|
+
module Autolink
|
|
8
|
+
# A link whose text equals its URL is a bare URL. Each strategy
|
|
9
|
+
# decides how to format it. `applies?` is checked first; if no
|
|
10
|
+
# strategy applies, the caller falls back to standard link syntax.
|
|
11
|
+
#
|
|
12
|
+
# Adding a new autolink form = adding one file + one entry in
|
|
13
|
+
# Registry::MODES. No call-site changes — Open/Closed.
|
|
14
|
+
class Base
|
|
15
|
+
class << self
|
|
16
|
+
def applies?(_url, _text, _ctx)
|
|
17
|
+
raise NotImplementedError
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def render(_url, _text, _ctx)
|
|
21
|
+
raise NotImplementedError
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def mode_name
|
|
25
|
+
name.split('::').last.gsub(/([a-z\d])([A-Z])/, '\1_\2').downcase.to_sym
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'base'
|
|
4
|
+
|
|
5
|
+
module Coradoc
|
|
6
|
+
module Markdown
|
|
7
|
+
class Serializer
|
|
8
|
+
module Strategies
|
|
9
|
+
module Autolink
|
|
10
|
+
# No autolink form — emit standard [text](url). The default
|
|
11
|
+
# when `autolinks: false` or when text differs from url.
|
|
12
|
+
class None < Base
|
|
13
|
+
class << self
|
|
14
|
+
def applies?(_url, _text, _ctx)
|
|
15
|
+
false
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def render(_url, _text, _ctx)
|
|
19
|
+
raise NotImplementedError, 'None strategy never renders'
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'base'
|
|
4
|
+
require_relative 'angle'
|
|
5
|
+
require_relative 'bare'
|
|
6
|
+
require_relative 'none'
|
|
7
|
+
|
|
8
|
+
module Coradoc
|
|
9
|
+
module Markdown
|
|
10
|
+
class Serializer
|
|
11
|
+
module Strategies
|
|
12
|
+
module Autolink
|
|
13
|
+
# Resolves an autolink strategy from a Config + (url, text).
|
|
14
|
+
#
|
|
15
|
+
# Mode lookup:
|
|
16
|
+
# autolinks == true → Angle (default, CommonMark-safe)
|
|
17
|
+
# autolinks == false → None (use standard link syntax)
|
|
18
|
+
#
|
|
19
|
+
# The angle form is preferred over bare because angle brackets
|
|
20
|
+
# disambiguate the URL in flowing text and render identically
|
|
21
|
+
# across HTML-aware Markdown processors.
|
|
22
|
+
#
|
|
23
|
+
# Use `Bare` explicitly for GFM-targeted output where minimal
|
|
24
|
+
# noise is preferred.
|
|
25
|
+
module Registry
|
|
26
|
+
MODES = {
|
|
27
|
+
angle: Angle,
|
|
28
|
+
bare: Bare,
|
|
29
|
+
none: None
|
|
30
|
+
}.freeze
|
|
31
|
+
|
|
32
|
+
class << self
|
|
33
|
+
def lookup(mode)
|
|
34
|
+
MODES.fetch(mode.to_sym) do
|
|
35
|
+
raise ArgumentError, "Unknown autolink mode: #{mode.inspect}. " \
|
|
36
|
+
"Known: #{MODES.keys.inspect}"
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def resolve(url:, text:, ctx:)
|
|
41
|
+
mode = ctx.config.autolinks ? :angle : :none
|
|
42
|
+
strategy = lookup(mode)
|
|
43
|
+
return nil unless strategy.applies?(url, text, ctx)
|
|
44
|
+
|
|
45
|
+
strategy
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def render_or_default(url:, text:, ctx:, default:)
|
|
49
|
+
strategy = resolve(url: url, text: text, ctx: ctx)
|
|
50
|
+
strategy ? strategy.render(url, text, ctx) : default
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Coradoc
|
|
4
|
+
module Markdown
|
|
5
|
+
class Serializer
|
|
6
|
+
module Strategies
|
|
7
|
+
module DefinitionList
|
|
8
|
+
# Each strategy renders a full DefinitionList (with all items,
|
|
9
|
+
# including nested ones) for a specific output form.
|
|
10
|
+
#
|
|
11
|
+
# Strategy is selected by inspecting the list: flat lists with
|
|
12
|
+
# no nesting use Flat (PHP Markdown Extra); lists containing
|
|
13
|
+
# any nesting use NestedHtml (HTML fallback per the spec).
|
|
14
|
+
#
|
|
15
|
+
# `config.definition_list_nested` overrides the auto choice:
|
|
16
|
+
# :html → always NestedHtml when any nesting exists
|
|
17
|
+
# :flatten → strip nesting (information loss; opt-in)
|
|
18
|
+
class Base
|
|
19
|
+
class << self
|
|
20
|
+
def applies?(_list, _ctx)
|
|
21
|
+
raise NotImplementedError
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def render(_list, _ctx)
|
|
25
|
+
raise NotImplementedError
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def mode_name
|
|
29
|
+
name.split('::').last.gsub(/([a-z\d])([A-Z])/, '\1_\2').downcase.to_sym
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'base'
|
|
4
|
+
|
|
5
|
+
module Coradoc
|
|
6
|
+
module Markdown
|
|
7
|
+
class Serializer
|
|
8
|
+
module Strategies
|
|
9
|
+
module DefinitionList
|
|
10
|
+
# PHP Markdown Extra flat syntax:
|
|
11
|
+
#
|
|
12
|
+
# term
|
|
13
|
+
# : definition
|
|
14
|
+
#
|
|
15
|
+
# term2
|
|
16
|
+
# : first
|
|
17
|
+
# : second
|
|
18
|
+
#
|
|
19
|
+
# Only applies when no item has a nested DefinitionList —
|
|
20
|
+
# the flat syntax has no nesting mechanism.
|
|
21
|
+
class Flat < Base
|
|
22
|
+
class << self
|
|
23
|
+
def applies?(list, _ctx)
|
|
24
|
+
list.items.none? { |term| term.nested }
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def render(list, _ctx)
|
|
28
|
+
list.items.map do |term|
|
|
29
|
+
lines = [term.text.to_s]
|
|
30
|
+
term.definitions.each do |defn|
|
|
31
|
+
content_str = defn.content.to_s
|
|
32
|
+
content_str.lines.each_with_index do |line, i|
|
|
33
|
+
stripped = line.rstrip
|
|
34
|
+
next if i.positive? && stripped.empty?
|
|
35
|
+
|
|
36
|
+
lines << ": #{stripped}"
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
lines.join("\n")
|
|
40
|
+
end.join("\n\n")
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'base'
|
|
4
|
+
require_relative 'flat'
|
|
5
|
+
|
|
6
|
+
module Coradoc
|
|
7
|
+
module Markdown
|
|
8
|
+
class Serializer
|
|
9
|
+
module Strategies
|
|
10
|
+
module DefinitionList
|
|
11
|
+
# HTML fallback for nested definition lists (Markdown syntax
|
|
12
|
+
# cannot express nesting). Renders `<dl>` with recursive
|
|
13
|
+
# `<dl>` inside `<dd>` for nested entries.
|
|
14
|
+
#
|
|
15
|
+
# For flat lists (no nesting), delegates to Flat to keep the
|
|
16
|
+
# output Markdown-native and editable.
|
|
17
|
+
class NestedHtml < Base
|
|
18
|
+
class << self
|
|
19
|
+
def applies?(list, _ctx)
|
|
20
|
+
list.items.any? { |term| term.nested }
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def render(list, ctx)
|
|
24
|
+
# When the caller asked to flatten, never emit HTML.
|
|
25
|
+
return Flat.render(list, ctx) if ctx.config.definition_list_nested == :flatten
|
|
26
|
+
|
|
27
|
+
render_dl(list)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
|
|
32
|
+
def render_dl(list)
|
|
33
|
+
items_html = list.items.map { |term| render_term(term) }.join
|
|
34
|
+
"<dl>\n#{items_html}</dl>"
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def render_term(term)
|
|
38
|
+
dt = "<dt>#{term.text.to_s}</dt>"
|
|
39
|
+
dds = term.definitions.map { |d| render_dd(d) }.join
|
|
40
|
+
nested = term.nested ? "\n #{render_dl(term.nested)}" : ''
|
|
41
|
+
"#{dt}\n#{dds}#{nested unless nested.empty?}"
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def render_dd(definition)
|
|
45
|
+
content_str = definition.content.to_s.strip
|
|
46
|
+
"<dd>\n #{content_str}\n</dd>"
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'base'
|
|
4
|
+
require_relative 'flat'
|
|
5
|
+
require_relative 'nested_html'
|
|
6
|
+
|
|
7
|
+
module Coradoc
|
|
8
|
+
module Markdown
|
|
9
|
+
class Serializer
|
|
10
|
+
module Strategies
|
|
11
|
+
module DefinitionList
|
|
12
|
+
# Picks the right strategy for a given list:
|
|
13
|
+
#
|
|
14
|
+
# 1. If no item has nesting → Flat (PHP Markdown Extra)
|
|
15
|
+
# 2. If any item has nesting → NestedHtml (HTML fallback)
|
|
16
|
+
# 3. If config says :flatten and list has nesting → Flat
|
|
17
|
+
# (drops nesting — explicit information-loss opt-in)
|
|
18
|
+
module Registry
|
|
19
|
+
STRATEGIES = [NestedHtml, Flat].freeze
|
|
20
|
+
|
|
21
|
+
class << self
|
|
22
|
+
def resolve(list, ctx)
|
|
23
|
+
return Flat if ctx.config.definition_list_nested == :flatten
|
|
24
|
+
|
|
25
|
+
STRATEGIES.find { |s| s.applies?(list, ctx) } || Flat
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def render(list, ctx)
|
|
29
|
+
resolve(list, ctx).render(list, ctx)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|