coradoc 2.0.23 → 2.0.24
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/coradoc.rb +35 -429
- data/lib/coradoc/core_model/inline_content.rb +77 -0
- data/lib/coradoc/core_model/inline_element.rb +33 -27
- data/lib/coradoc/core_model.rb +1 -0
- data/lib/coradoc/dispatch.rb +95 -0
- data/lib/coradoc/format_catalog.rb +83 -0
- data/lib/coradoc/introspection/element_counter.rb +48 -0
- data/lib/coradoc/introspection.rb +72 -0
- data/lib/coradoc/pipeline.rb +108 -0
- data/lib/coradoc/version.rb +1 -1
- data/lib/coradoc.rb +5 -13
- metadata +7 -9
- data/lib/coradoc/document_builder.rb +0 -184
- data/lib/coradoc/document_manipulator.rb +0 -203
- data/lib/coradoc/input.rb +0 -22
- data/lib/coradoc/output.rb +0 -22
- data/lib/coradoc/processor_registry.rb +0 -50
- data/lib/coradoc/serializer/registry.rb +0 -150
- data/lib/coradoc/transform/base.rb +0 -21
- data/lib/coradoc/transform.rb +0 -7
|
@@ -1,184 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Coradoc
|
|
4
|
-
class DocumentBuilder
|
|
5
|
-
attr_reader :document
|
|
6
|
-
|
|
7
|
-
def self.build(&block)
|
|
8
|
-
builder = new
|
|
9
|
-
builder.instance_eval(&block) if block_given?
|
|
10
|
-
builder
|
|
11
|
-
end
|
|
12
|
-
|
|
13
|
-
def initialize
|
|
14
|
-
@document = CoreModel::DocumentElement.new(
|
|
15
|
-
children: []
|
|
16
|
-
)
|
|
17
|
-
@current_context = @document
|
|
18
|
-
@context_stack = []
|
|
19
|
-
end
|
|
20
|
-
|
|
21
|
-
def title(text)
|
|
22
|
-
@document.title = text
|
|
23
|
-
self
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
def section(title_text, level: 1, &block)
|
|
27
|
-
new_section = CoreModel::SectionElement.new(
|
|
28
|
-
level: level,
|
|
29
|
-
title: title_text,
|
|
30
|
-
children: []
|
|
31
|
-
)
|
|
32
|
-
|
|
33
|
-
@current_context.children << new_section
|
|
34
|
-
|
|
35
|
-
if block_given?
|
|
36
|
-
push_context(new_section)
|
|
37
|
-
instance_eval(&block)
|
|
38
|
-
pop_context
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
self
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
def paragraph(text)
|
|
45
|
-
@current_context.children << CoreModel::ParagraphBlock.new(
|
|
46
|
-
content: text
|
|
47
|
-
)
|
|
48
|
-
self
|
|
49
|
-
end
|
|
50
|
-
|
|
51
|
-
def code(code_text, language: nil)
|
|
52
|
-
@current_context.children << CoreModel::SourceBlock.new(
|
|
53
|
-
content: code_text,
|
|
54
|
-
language: language
|
|
55
|
-
)
|
|
56
|
-
self
|
|
57
|
-
end
|
|
58
|
-
|
|
59
|
-
def blockquote(text, attribution: nil)
|
|
60
|
-
block = CoreModel::QuoteBlock.new(
|
|
61
|
-
content: text,
|
|
62
|
-
attribution: attribution
|
|
63
|
-
)
|
|
64
|
-
@current_context.children << block
|
|
65
|
-
self
|
|
66
|
-
end
|
|
67
|
-
|
|
68
|
-
def list(type = :unordered, &block)
|
|
69
|
-
list_items = []
|
|
70
|
-
list_type = type
|
|
71
|
-
|
|
72
|
-
wrapper = Object.new
|
|
73
|
-
wrapper.define_singleton_method(:item) do |text|
|
|
74
|
-
marker = list_type == :ordered ? '1.' : '*'
|
|
75
|
-
list_items << CoreModel::ListItem.new(content: text, marker: marker)
|
|
76
|
-
wrapper
|
|
77
|
-
end
|
|
78
|
-
|
|
79
|
-
wrapper.instance_eval(&block) if block_given?
|
|
80
|
-
|
|
81
|
-
@current_context.children << CoreModel::ListBlock.new(
|
|
82
|
-
marker_type: type.to_s,
|
|
83
|
-
items: list_items
|
|
84
|
-
)
|
|
85
|
-
self
|
|
86
|
-
end
|
|
87
|
-
|
|
88
|
-
alias ordered_list list
|
|
89
|
-
alias unordered_list list
|
|
90
|
-
|
|
91
|
-
def bulleted_list(&block)
|
|
92
|
-
list(:unordered, &block)
|
|
93
|
-
end
|
|
94
|
-
|
|
95
|
-
def numbered_list(&block)
|
|
96
|
-
list(:ordered, &block)
|
|
97
|
-
end
|
|
98
|
-
|
|
99
|
-
def image(src, alt: '', title: nil)
|
|
100
|
-
img = CoreModel::Image.new(src: src, alt: alt)
|
|
101
|
-
img.title = title if title
|
|
102
|
-
@current_context.children << img
|
|
103
|
-
self
|
|
104
|
-
end
|
|
105
|
-
|
|
106
|
-
def table(headers = [], rows = [])
|
|
107
|
-
table_rows = []
|
|
108
|
-
|
|
109
|
-
if headers.any?
|
|
110
|
-
header_cells = headers.map { |h| CoreModel::TableCell.new(content: h, header: true) }
|
|
111
|
-
table_rows << CoreModel::TableRow.new(cells: header_cells)
|
|
112
|
-
end
|
|
113
|
-
|
|
114
|
-
rows.each do |row|
|
|
115
|
-
cells = row.map { |c| CoreModel::TableCell.new(content: c.to_s) }
|
|
116
|
-
table_rows << CoreModel::TableRow.new(cells: cells)
|
|
117
|
-
end
|
|
118
|
-
|
|
119
|
-
@current_context.children << CoreModel::Table.new(rows: table_rows)
|
|
120
|
-
self
|
|
121
|
-
end
|
|
122
|
-
|
|
123
|
-
def hr
|
|
124
|
-
@current_context.children << CoreModel::HorizontalRuleBlock.new
|
|
125
|
-
self
|
|
126
|
-
end
|
|
127
|
-
|
|
128
|
-
def text(text_content)
|
|
129
|
-
@current_context.children << CoreModel::Block.new(
|
|
130
|
-
content: text_content
|
|
131
|
-
)
|
|
132
|
-
self
|
|
133
|
-
end
|
|
134
|
-
|
|
135
|
-
def admonition(type, text)
|
|
136
|
-
@current_context.children << CoreModel::AnnotationBlock.new(
|
|
137
|
-
annotation_type: type.to_s,
|
|
138
|
-
content: text
|
|
139
|
-
)
|
|
140
|
-
self
|
|
141
|
-
end
|
|
142
|
-
|
|
143
|
-
%i[note warning tip important caution].each do |admonition_type|
|
|
144
|
-
define_method(admonition_type) do |text|
|
|
145
|
-
admonition(admonition_type, text)
|
|
146
|
-
end
|
|
147
|
-
end
|
|
148
|
-
|
|
149
|
-
def to_core
|
|
150
|
-
@document
|
|
151
|
-
end
|
|
152
|
-
|
|
153
|
-
def to(format, **options)
|
|
154
|
-
Coradoc.serialize(@document, to: format, **options)
|
|
155
|
-
end
|
|
156
|
-
|
|
157
|
-
def to_html(**options)
|
|
158
|
-
to(:html, **options)
|
|
159
|
-
end
|
|
160
|
-
|
|
161
|
-
def to_markdown(**options)
|
|
162
|
-
to(:markdown, **options)
|
|
163
|
-
end
|
|
164
|
-
|
|
165
|
-
def to_asciidoc(**options)
|
|
166
|
-
to(:asciidoc, **options)
|
|
167
|
-
end
|
|
168
|
-
|
|
169
|
-
private
|
|
170
|
-
|
|
171
|
-
def push_context(new_context)
|
|
172
|
-
@context_stack << @current_context
|
|
173
|
-
@current_context = new_context
|
|
174
|
-
end
|
|
175
|
-
|
|
176
|
-
def pop_context
|
|
177
|
-
@current_context = @context_stack.pop
|
|
178
|
-
end
|
|
179
|
-
end
|
|
180
|
-
|
|
181
|
-
def self.build(&block)
|
|
182
|
-
DocumentBuilder.build(&block)
|
|
183
|
-
end
|
|
184
|
-
end
|
|
@@ -1,203 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Coradoc
|
|
4
|
-
class DocumentManipulator
|
|
5
|
-
attr_reader :document
|
|
6
|
-
|
|
7
|
-
def initialize(document)
|
|
8
|
-
unless document.is_a?(Coradoc::CoreModel::Base)
|
|
9
|
-
raise ArgumentError,
|
|
10
|
-
"Expected CoreModel::Base, got #{document.class}"
|
|
11
|
-
end
|
|
12
|
-
|
|
13
|
-
@document = document
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
def query(selector)
|
|
17
|
-
Coradoc::Query.query(@document, selector).to_a
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
def select_sections(level: nil, title: nil)
|
|
21
|
-
filtered = filter_sections(@document, level: level, title: title)
|
|
22
|
-
DocumentManipulator.new(filtered)
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
def transform_text
|
|
26
|
-
return self unless block_given?
|
|
27
|
-
|
|
28
|
-
Visitor::Transformer.new do |element|
|
|
29
|
-
case element
|
|
30
|
-
when CoreModel::InlineElement
|
|
31
|
-
element.content = yield(element.content) if element.content.is_a?(String)
|
|
32
|
-
when CoreModel::Block
|
|
33
|
-
element.content = yield(element.content) if element.content.is_a?(String)
|
|
34
|
-
end
|
|
35
|
-
end.visit(@document)
|
|
36
|
-
self
|
|
37
|
-
end
|
|
38
|
-
|
|
39
|
-
def transform_headings
|
|
40
|
-
return self unless block_given?
|
|
41
|
-
|
|
42
|
-
Visitor::Transformer.new do |element|
|
|
43
|
-
element.title = yield(element.title) if element.is_a?(CoreModel::StructuralElement) && element.title.is_a?(String)
|
|
44
|
-
end.visit(@document)
|
|
45
|
-
self
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
def add_toc(levels: 3, position: :top)
|
|
49
|
-
sections = collect_sections(@document, max_level: levels)
|
|
50
|
-
toc = CoreModel::TocGenerator.generate(sections)
|
|
51
|
-
|
|
52
|
-
toc_element = CoreModel::Block.new(block_semantic_type: 'toc', content: toc)
|
|
53
|
-
case position
|
|
54
|
-
when :top
|
|
55
|
-
@document.children = [toc_element] + @document.children
|
|
56
|
-
when :bottom
|
|
57
|
-
@document.children = @document.children + [toc_element]
|
|
58
|
-
end
|
|
59
|
-
|
|
60
|
-
self
|
|
61
|
-
end
|
|
62
|
-
|
|
63
|
-
def remove_elements(element_type)
|
|
64
|
-
Visitor::Transformer.new do |element|
|
|
65
|
-
next unless element.is_a?(CoreModel::StructuralElement) && element.children
|
|
66
|
-
|
|
67
|
-
element.children.reject! do |child|
|
|
68
|
-
match_element_type?(child, element_type)
|
|
69
|
-
end
|
|
70
|
-
end.visit(@document)
|
|
71
|
-
self
|
|
72
|
-
end
|
|
73
|
-
|
|
74
|
-
def add_metadata(metadata)
|
|
75
|
-
metadata.each do |key, value|
|
|
76
|
-
@document.set_metadata(key.to_s, value.to_s)
|
|
77
|
-
end
|
|
78
|
-
self
|
|
79
|
-
end
|
|
80
|
-
|
|
81
|
-
def set_title(title)
|
|
82
|
-
@document.title = title
|
|
83
|
-
self
|
|
84
|
-
end
|
|
85
|
-
|
|
86
|
-
def set_id(id)
|
|
87
|
-
@document.id = id
|
|
88
|
-
self
|
|
89
|
-
end
|
|
90
|
-
|
|
91
|
-
def to_html(**options)
|
|
92
|
-
Coradoc.serialize(@document, to: :html, **options)
|
|
93
|
-
end
|
|
94
|
-
|
|
95
|
-
def to_markdown(**options)
|
|
96
|
-
Coradoc.serialize(@document, to: :markdown, **options)
|
|
97
|
-
end
|
|
98
|
-
|
|
99
|
-
def to_asciidoc(**options)
|
|
100
|
-
Coradoc.serialize(@document, to: :asciidoc, **options)
|
|
101
|
-
end
|
|
102
|
-
|
|
103
|
-
def to(format, **options)
|
|
104
|
-
Coradoc.serialize(@document, to: format, **options)
|
|
105
|
-
end
|
|
106
|
-
|
|
107
|
-
def to_core
|
|
108
|
-
@document
|
|
109
|
-
end
|
|
110
|
-
|
|
111
|
-
def clone
|
|
112
|
-
DocumentManipulator.new(deep_clone(@document))
|
|
113
|
-
end
|
|
114
|
-
|
|
115
|
-
private
|
|
116
|
-
|
|
117
|
-
def match_element_type?(child, element_type)
|
|
118
|
-
return false unless child.is_a?(CoreModel::Block)
|
|
119
|
-
|
|
120
|
-
case element_type
|
|
121
|
-
when :comment_line, :comment_block
|
|
122
|
-
child.is_a?(CoreModel::CommentBlock)
|
|
123
|
-
else
|
|
124
|
-
child.resolve_semantic_type == element_type.to_sym
|
|
125
|
-
end
|
|
126
|
-
end
|
|
127
|
-
|
|
128
|
-
def filter_sections(element, level: nil, title: nil)
|
|
129
|
-
if element.is_a?(CoreModel::StructuralElement) && element.children
|
|
130
|
-
element.children = element.children
|
|
131
|
-
.map { |child| filter_sections(child, level: level, title: title) }
|
|
132
|
-
.compact
|
|
133
|
-
end
|
|
134
|
-
|
|
135
|
-
return nil if element.is_a?(CoreModel::StructuralElement) && element.section? && !element.document? && !section_matches?(
|
|
136
|
-
element, level: level, title: title
|
|
137
|
-
)
|
|
138
|
-
|
|
139
|
-
element
|
|
140
|
-
end
|
|
141
|
-
|
|
142
|
-
def section_matches?(section, level: nil, title: nil)
|
|
143
|
-
if level
|
|
144
|
-
element_level = section.heading_level
|
|
145
|
-
case level
|
|
146
|
-
when Range then return false unless level.include?(element_level)
|
|
147
|
-
when Integer then return false unless element_level == level
|
|
148
|
-
end
|
|
149
|
-
end
|
|
150
|
-
|
|
151
|
-
if title
|
|
152
|
-
element_title = section.title || ''
|
|
153
|
-
case title
|
|
154
|
-
when String then return false unless element_title.include?(title)
|
|
155
|
-
when Regexp then return false unless element_title&.match?(title)
|
|
156
|
-
end
|
|
157
|
-
end
|
|
158
|
-
|
|
159
|
-
true
|
|
160
|
-
end
|
|
161
|
-
|
|
162
|
-
def collect_sections(element, max_level: 3, current_level: 1)
|
|
163
|
-
sections = []
|
|
164
|
-
return sections unless element.is_a?(CoreModel::StructuralElement)
|
|
165
|
-
|
|
166
|
-
element.children.each do |child|
|
|
167
|
-
next unless child.is_a?(CoreModel::StructuralElement) &&
|
|
168
|
-
child.section? && (current_level <= max_level)
|
|
169
|
-
|
|
170
|
-
sections << {
|
|
171
|
-
id: child.id,
|
|
172
|
-
title: child.title,
|
|
173
|
-
level: child.level || current_level,
|
|
174
|
-
children: collect_sections(child, max_level: max_level,
|
|
175
|
-
current_level: current_level + 1)
|
|
176
|
-
}
|
|
177
|
-
end
|
|
178
|
-
|
|
179
|
-
sections
|
|
180
|
-
end
|
|
181
|
-
|
|
182
|
-
def deep_clone(element)
|
|
183
|
-
case element
|
|
184
|
-
when CoreModel::Base
|
|
185
|
-
cloned = element.class.new
|
|
186
|
-
element.class.attributes.each_key do |name|
|
|
187
|
-
cloned.public_send("#{name}=", deep_clone(element.public_send(name)))
|
|
188
|
-
end
|
|
189
|
-
cloned
|
|
190
|
-
when Array
|
|
191
|
-
element.map { |item| deep_clone(item) }
|
|
192
|
-
when Hash
|
|
193
|
-
element.transform_values { |v| deep_clone(v) }
|
|
194
|
-
else
|
|
195
|
-
begin
|
|
196
|
-
element.dup
|
|
197
|
-
rescue StandardError
|
|
198
|
-
element
|
|
199
|
-
end
|
|
200
|
-
end
|
|
201
|
-
end
|
|
202
|
-
end
|
|
203
|
-
end
|
data/lib/coradoc/input.rb
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require_relative 'processor_registry'
|
|
4
|
-
|
|
5
|
-
module Coradoc
|
|
6
|
-
# Input module for document ingestion
|
|
7
|
-
#
|
|
8
|
-
# Provides a unified interface for document input processing.
|
|
9
|
-
# Format-specific input processors register themselves with this module.
|
|
10
|
-
#
|
|
11
|
-
# @example Registering an input processor
|
|
12
|
-
# Coradoc::Input.define(MyHtmlProcessor)
|
|
13
|
-
#
|
|
14
|
-
# @example Using a registered processor
|
|
15
|
-
# processor = Coradoc::Input.for(:html)
|
|
16
|
-
# document = processor.convert(html_content)
|
|
17
|
-
#
|
|
18
|
-
module Input
|
|
19
|
-
extend ProcessorRegistry
|
|
20
|
-
self.error_label = 'input processor'
|
|
21
|
-
end
|
|
22
|
-
end
|
data/lib/coradoc/output.rb
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require_relative 'processor_registry'
|
|
4
|
-
|
|
5
|
-
module Coradoc
|
|
6
|
-
# Output module for document serialization
|
|
7
|
-
#
|
|
8
|
-
# Provides a unified interface for document output processing.
|
|
9
|
-
# Format-specific output processors register themselves with this module.
|
|
10
|
-
#
|
|
11
|
-
# @example Registering an output processor
|
|
12
|
-
# Coradoc::Output.define(MyHtmlProcessor)
|
|
13
|
-
#
|
|
14
|
-
# @example Using a registered processor
|
|
15
|
-
# processor = Coradoc::Output.get(:html_static)
|
|
16
|
-
# html = processor.processor_execute(document, {})
|
|
17
|
-
#
|
|
18
|
-
module Output
|
|
19
|
-
extend ProcessorRegistry
|
|
20
|
-
self.error_label = 'output processor'
|
|
21
|
-
end
|
|
22
|
-
end
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Coradoc
|
|
4
|
-
# Module adapter that gives a module Registry-backed processor methods.
|
|
5
|
-
#
|
|
6
|
-
# Both Input and Output extend this module to get processor registration,
|
|
7
|
-
# lookup, and dispatch methods. All state is stored in a Registry instance.
|
|
8
|
-
#
|
|
9
|
-
# @api private
|
|
10
|
-
#
|
|
11
|
-
# @example
|
|
12
|
-
# module Input
|
|
13
|
-
# extend ProcessorRegistry
|
|
14
|
-
# self.error_label = "input processor"
|
|
15
|
-
# end
|
|
16
|
-
module ProcessorRegistry
|
|
17
|
-
def error_label=(label)
|
|
18
|
-
@error_label = label
|
|
19
|
-
end
|
|
20
|
-
|
|
21
|
-
def registry
|
|
22
|
-
@registry ||= Registry.new(error_label: @error_label)
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
def define(processor, **options)
|
|
26
|
-
registry.define(processor, **options)
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
def get(id)
|
|
30
|
-
registry.get(id)
|
|
31
|
-
end
|
|
32
|
-
alias [] get
|
|
33
|
-
|
|
34
|
-
def registered?(id)
|
|
35
|
-
registry.registered?(id)
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
def processors
|
|
39
|
-
registry.items
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
def for_file(filename)
|
|
43
|
-
registry.for_file(filename)
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
def process(content, options = {})
|
|
47
|
-
registry.process(content, options)
|
|
48
|
-
end
|
|
49
|
-
end
|
|
50
|
-
end
|
|
@@ -1,150 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Coradoc
|
|
4
|
-
module Serializer
|
|
5
|
-
# Registry for document element serializers
|
|
6
|
-
#
|
|
7
|
-
# Provides a registry-based pattern for looking up serializers
|
|
8
|
-
# for document element types. This allows users to register
|
|
9
|
-
# custom serializers for their own element types or override
|
|
10
|
-
# existing serializers.
|
|
11
|
-
#
|
|
12
|
-
# @example Registering a custom serializer
|
|
13
|
-
# Coradoc::Serializer::Registry.register(MyCustomElement, MyCustomSerializer)
|
|
14
|
-
#
|
|
15
|
-
# @example Looking up a serializer
|
|
16
|
-
# serializer = Coradoc::Serializer::Registry.lookup(element)
|
|
17
|
-
# output = serializer.serialize(element) if serializer
|
|
18
|
-
#
|
|
19
|
-
class Registry
|
|
20
|
-
class << self
|
|
21
|
-
# Get the global registry instance
|
|
22
|
-
#
|
|
23
|
-
# @return [Hash] the registry hash
|
|
24
|
-
def registry
|
|
25
|
-
@registry ||= {}
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
# Register a serializer for a model class
|
|
29
|
-
#
|
|
30
|
-
# @param model_class [Class] The model class to serialize
|
|
31
|
-
# @param serializer_class [Class] The serializer class to use
|
|
32
|
-
# @return [Class, nil] The previous serializer class, if any
|
|
33
|
-
def register(model_class, serializer_class)
|
|
34
|
-
key = class_key(model_class)
|
|
35
|
-
previous = registry[key]
|
|
36
|
-
registry[key] = serializer_class
|
|
37
|
-
previous
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
# Unregister a serializer for a model class
|
|
41
|
-
#
|
|
42
|
-
# @param model_class [Class] The model class to unregister
|
|
43
|
-
# @return [Class, nil] The removed serializer class, if any
|
|
44
|
-
def unregister(model_class)
|
|
45
|
-
key = class_key(model_class)
|
|
46
|
-
registry.delete(key)
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
# Look up a serializer for a model instance or class
|
|
50
|
-
#
|
|
51
|
-
# @param model [Object, Class] The model instance or class
|
|
52
|
-
# @return [Class, nil] The serializer class, or nil if not found
|
|
53
|
-
def lookup(model)
|
|
54
|
-
model_class = model.is_a?(Class) ? model : model.class
|
|
55
|
-
key = class_key(model_class)
|
|
56
|
-
|
|
57
|
-
# Direct lookup
|
|
58
|
-
return registry[key] if registry.key?(key)
|
|
59
|
-
|
|
60
|
-
# Try parent classes
|
|
61
|
-
model_class.ancestors.each do |ancestor|
|
|
62
|
-
next if ancestor == model_class || ancestor == Object
|
|
63
|
-
|
|
64
|
-
ancestor_key = class_key(ancestor)
|
|
65
|
-
return registry[ancestor_key] if registry.key?(ancestor_key)
|
|
66
|
-
end
|
|
67
|
-
|
|
68
|
-
nil
|
|
69
|
-
end
|
|
70
|
-
|
|
71
|
-
# Check if a serializer is registered for a model
|
|
72
|
-
#
|
|
73
|
-
# @param model [Object, Class] The model instance or class
|
|
74
|
-
# @return [Boolean] true if a serializer is registered
|
|
75
|
-
def registered?(model)
|
|
76
|
-
!lookup(model).nil?
|
|
77
|
-
end
|
|
78
|
-
|
|
79
|
-
# Get all registered model classes
|
|
80
|
-
#
|
|
81
|
-
# @return [Array<Class>] Array of registered model classes
|
|
82
|
-
def registered_models
|
|
83
|
-
registry.keys
|
|
84
|
-
end
|
|
85
|
-
|
|
86
|
-
# Clear all registered serializers
|
|
87
|
-
#
|
|
88
|
-
# @return [Hash] Empty registry
|
|
89
|
-
def clear
|
|
90
|
-
@registry = {}
|
|
91
|
-
end
|
|
92
|
-
|
|
93
|
-
# Serialize a model using its registered serializer
|
|
94
|
-
#
|
|
95
|
-
# @param model [Object] The model to serialize
|
|
96
|
-
# @param format [Symbol] The output format (:adoc, :html, :md, etc.)
|
|
97
|
-
# @param options [Hash] Additional serialization options
|
|
98
|
-
# @return [String, nil] The serialized output, or nil if no serializer found
|
|
99
|
-
def serialize(model, format: :adoc, **options)
|
|
100
|
-
serializer_class = lookup(model)
|
|
101
|
-
return nil unless serializer_class
|
|
102
|
-
|
|
103
|
-
serializer = serializer_class.is_a?(Class) ? serializer_class.new : serializer_class
|
|
104
|
-
|
|
105
|
-
if serializer.is_a?(Base)
|
|
106
|
-
serializer.serialize(model, format: format, **options)
|
|
107
|
-
else
|
|
108
|
-
serializer.to_s
|
|
109
|
-
end
|
|
110
|
-
end
|
|
111
|
-
|
|
112
|
-
private
|
|
113
|
-
|
|
114
|
-
# Generate a registry key for a class
|
|
115
|
-
def class_key(klass)
|
|
116
|
-
klass.name.to_s
|
|
117
|
-
end
|
|
118
|
-
end
|
|
119
|
-
end
|
|
120
|
-
|
|
121
|
-
# Base class for element serializers
|
|
122
|
-
#
|
|
123
|
-
# Provides a common interface for all serializers.
|
|
124
|
-
# Subclasses should implement #serialize method.
|
|
125
|
-
#
|
|
126
|
-
# @example Creating a custom serializer
|
|
127
|
-
# class MyElementSerializer < Coradoc::Serializer::Base
|
|
128
|
-
# def serialize(element, format: :adoc, **options)
|
|
129
|
-
# "Custom: #{element.text}"
|
|
130
|
-
# end
|
|
131
|
-
# end
|
|
132
|
-
#
|
|
133
|
-
class Base
|
|
134
|
-
# Serialize an element to the target format
|
|
135
|
-
#
|
|
136
|
-
# @param element [Object] The element to serialize
|
|
137
|
-
# @param format [Symbol] The output format
|
|
138
|
-
# @param options [Hash] Additional options
|
|
139
|
-
# @return [String] The serialized output
|
|
140
|
-
def serialize(element, format: :adoc, **_options)
|
|
141
|
-
element.to_s
|
|
142
|
-
end
|
|
143
|
-
|
|
144
|
-
# Class method for serialization
|
|
145
|
-
def self.serialize(element, **options)
|
|
146
|
-
new.serialize(element, **options)
|
|
147
|
-
end
|
|
148
|
-
end
|
|
149
|
-
end
|
|
150
|
-
end
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Coradoc
|
|
4
|
-
module Transform
|
|
5
|
-
module Base
|
|
6
|
-
def self.included(base)
|
|
7
|
-
base.extend(ClassMethods)
|
|
8
|
-
end
|
|
9
|
-
|
|
10
|
-
module ClassMethods
|
|
11
|
-
def transform(model)
|
|
12
|
-
raise NotImplementedError, "#{name} must implement .transform"
|
|
13
|
-
end
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
def transform(model)
|
|
17
|
-
self.class.transform(model)
|
|
18
|
-
end
|
|
19
|
-
end
|
|
20
|
-
end
|
|
21
|
-
end
|