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.
@@ -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
@@ -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
@@ -1,7 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Coradoc
4
- module Transform
5
- autoload :Base, "#{__dir__}/transform/base"
6
- end
7
- end