coradoc 2.0.27 → 2.0.29
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/cli.rb +9 -6
- data/lib/coradoc/configurable.rb +6 -3
- data/lib/coradoc/coradoc.rb +5 -2
- data/lib/coradoc/core_model/attribute_reference_resolver.rb +19 -7
- data/lib/coradoc/core_model/base.rb +45 -2
- data/lib/coradoc/core_model/comment_line.rb +1 -1
- data/lib/coradoc/core_model/definition_item.rb +22 -1
- data/lib/coradoc/core_model/horizontal_rule_block.rb +4 -0
- data/lib/coradoc/core_model/include_level_offset.rb +1 -1
- data/lib/coradoc/core_model/include_options.rb +1 -0
- data/lib/coradoc/core_model/inline_content.rb +1 -0
- data/lib/coradoc/core_model/list_block.rb +1 -1
- data/lib/coradoc/core_model/listing_block.rb +4 -0
- data/lib/coradoc/core_model/literal_block.rb +4 -0
- data/lib/coradoc/core_model/pass_block.rb +4 -0
- data/lib/coradoc/core_model/source_block.rb +4 -0
- data/lib/coradoc/core_model/stem_block.rb +4 -0
- data/lib/coradoc/dispatch.rb +1 -0
- data/lib/coradoc/errors.rb +19 -0
- data/lib/coradoc/format_catalog.rb +19 -0
- data/lib/coradoc/format_module.rb +10 -0
- data/lib/coradoc/hooks.rb +1 -1
- data/lib/coradoc/include_selectors/level_offset.rb +0 -2
- data/lib/coradoc/include_selectors/lines.rb +2 -2
- data/lib/coradoc/include_selectors/tags.rb +2 -4
- data/lib/coradoc/introspection/element_counter.rb +2 -2
- data/lib/coradoc/link_rewriter/visitor.rb +5 -14
- data/lib/coradoc/pipeline.rb +64 -5
- data/lib/coradoc/query.rb +8 -8
- data/lib/coradoc/reference/address/anchor.rb +48 -0
- data/lib/coradoc/reference/address/doi.rb +40 -0
- data/lib/coradoc/reference/address/isbn.rb +39 -0
- data/lib/coradoc/reference/address/path.rb +66 -0
- data/lib/coradoc/reference/address/scoped_path.rb +47 -0
- data/lib/coradoc/reference/address/url.rb +41 -0
- data/lib/coradoc/reference/address.rb +153 -0
- data/lib/coradoc/reference/catalog/composite.rb +59 -0
- data/lib/coradoc/reference/catalog/local.rb +97 -0
- data/lib/coradoc/reference/catalog/memory_index.rb +59 -0
- data/lib/coradoc/reference/catalog.rb +33 -0
- data/lib/coradoc/reference/edge/citation_options.rb +16 -0
- data/lib/coradoc/reference/edge/footnote_ref_options.rb +13 -0
- data/lib/coradoc/reference/edge/image_ref_options.rb +15 -0
- data/lib/coradoc/reference/edge/include_options.rb +15 -0
- data/lib/coradoc/reference/edge/kind.rb +72 -0
- data/lib/coradoc/reference/edge/link_options.rb +12 -0
- data/lib/coradoc/reference/edge/navigation_options.rb +12 -0
- data/lib/coradoc/reference/edge/options.rb +13 -0
- data/lib/coradoc/reference/edge.rb +80 -0
- data/lib/coradoc/reference/edge_search.rb +128 -0
- data/lib/coradoc/reference/materializer/base.rb +44 -0
- data/lib/coradoc/reference/materializer/passthrough.rb +31 -0
- data/lib/coradoc/reference/materializer/registry.rb +116 -0
- data/lib/coradoc/reference/materializer.rb +14 -0
- data/lib/coradoc/reference/presentation/base.rb +47 -0
- data/lib/coradoc/reference/presentation/custom_hierarchy.rb +104 -0
- data/lib/coradoc/reference/presentation/page.rb +20 -0
- data/lib/coradoc/reference/presentation/single_document.rb +35 -0
- data/lib/coradoc/reference/presentation/split_pages.rb +118 -0
- data/lib/coradoc/reference/presentation.rb +16 -0
- data/lib/coradoc/reference/resolution.rb +184 -0
- data/lib/coradoc/reference/resolver/base.rb +15 -0
- data/lib/coradoc/reference/resolver/caching.rb +37 -0
- data/lib/coradoc/reference/resolver/catalog.rb +69 -0
- data/lib/coradoc/reference/resolver/chain.rb +33 -0
- data/lib/coradoc/reference/resolver.rb +20 -0
- data/lib/coradoc/reference/result/ambiguous.rb +23 -0
- data/lib/coradoc/reference/result/base.rb +46 -0
- data/lib/coradoc/reference/result/missing.rb +14 -0
- data/lib/coradoc/reference/result/resolved.rb +22 -0
- data/lib/coradoc/reference/result.rb +20 -0
- data/lib/coradoc/reference.rb +65 -0
- data/lib/coradoc/registry.rb +6 -6
- data/lib/coradoc/resolve_includes.rb +2 -2
- data/lib/coradoc/validation.rb +44 -4
- data/lib/coradoc/version.rb +1 -1
- data/lib/coradoc.rb +1 -1
- metadata +45 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 72d8a4fe9a86a7ed8f5dc806492ea147f321367f7a470b3c934747de9ea2b305
|
|
4
|
+
data.tar.gz: '042824f44538b6b64252c0667eb8d56f2ed059de0dab8866631c84394bee6a64'
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 6a603de0773f6b27449ccb511c3ec53860e6ec37aeb55a62f63f5902b7d0c4c0e74c5681dd1749b5f09411bd04ca34ff7bca45e4da4f9e8d4bd74f4cec3972b9
|
|
7
|
+
data.tar.gz: ebbc0d4ae30878f6793964a437dfd62a0ba79aafdfa57f3307d6e0b5c865650627bb6f025aa94f2c18813b16973819e3358a1458eecd3d6d9db5ac47af99efb8
|
data/lib/coradoc/cli.rb
CHANGED
|
@@ -23,15 +23,18 @@ module Coradoc
|
|
|
23
23
|
option :section_number_levels, desc: 'Section numbering depth (1-6)', type: :numeric, default: 3
|
|
24
24
|
option :lang, desc: 'Document language code', type: :string, default: 'en'
|
|
25
25
|
option :resolve_includes, desc: 'Resolve include:: directives inline (default: leave as link nodes)',
|
|
26
|
-
|
|
26
|
+
type: :boolean, default: false
|
|
27
27
|
option :base_dir, desc: 'Base directory for include resolution (default: dirname of FILE)',
|
|
28
|
-
|
|
28
|
+
type: :string
|
|
29
29
|
option :missing_include, desc: 'Policy for missing includes: error, warn, silent, passthrough',
|
|
30
|
-
|
|
30
|
+
type: :string, default: 'error'
|
|
31
31
|
option :max_include_depth, desc: 'Maximum include nesting depth', type: :numeric,
|
|
32
|
-
|
|
32
|
+
default: 64
|
|
33
33
|
option :allow_unsafe_includes, desc: 'Disable path-traversal protection (asciidoctor :unsafe mode)',
|
|
34
|
-
|
|
34
|
+
type: :boolean, default: false
|
|
35
|
+
option :allow_unresolved_includes, desc: 'Allow serialization with unresolved includes ' \
|
|
36
|
+
'(formats that cannot represent them drop them)',
|
|
37
|
+
type: :boolean, default: false
|
|
35
38
|
def convert(file)
|
|
36
39
|
source_format = resolve_format(file, :from)
|
|
37
40
|
target_format = options[:to] ? Coradoc.normalize_format(options[:to]) : Coradoc.resolve_output_format(options[:output])
|
|
@@ -213,7 +216,7 @@ module Coradoc
|
|
|
213
216
|
|
|
214
217
|
CONVERT_OPTIONS = %i[
|
|
215
218
|
toc toc_levels section_numbers section_number_levels
|
|
216
|
-
lang theme asset_delivery
|
|
219
|
+
lang theme asset_delivery allow_unresolved_includes
|
|
217
220
|
].freeze
|
|
218
221
|
private_constant :CONVERT_OPTIONS
|
|
219
222
|
|
data/lib/coradoc/configurable.rb
CHANGED
|
@@ -99,7 +99,10 @@ module Coradoc
|
|
|
99
99
|
def apply_options(options)
|
|
100
100
|
options.each do |key, value|
|
|
101
101
|
setter = "#{key}="
|
|
102
|
-
|
|
102
|
+
# Use public_send (not instance_variable_set) so we route
|
|
103
|
+
# through the public setter method — encapsulation is
|
|
104
|
+
# preserved and any setter-side validation runs.
|
|
105
|
+
public_send(setter, value) if public_methods.include?(setter.to_sym)
|
|
103
106
|
end
|
|
104
107
|
end
|
|
105
108
|
end
|
|
@@ -521,7 +524,7 @@ module Coradoc
|
|
|
521
524
|
#
|
|
522
525
|
# @yield [Configuration]
|
|
523
526
|
# @return [void]
|
|
524
|
-
def self.configure(&
|
|
525
|
-
Configurable.configure(&
|
|
527
|
+
def self.configure(&)
|
|
528
|
+
Configurable.configure(&) if block_given?
|
|
526
529
|
end
|
|
527
530
|
end
|
data/lib/coradoc/coradoc.rb
CHANGED
|
@@ -56,8 +56,8 @@ module Coradoc
|
|
|
56
56
|
|
|
57
57
|
def registry = FormatCatalog.registry
|
|
58
58
|
|
|
59
|
-
def register_format(format_name, format_module, **
|
|
60
|
-
FormatCatalog.register_format(format_name, format_module, **
|
|
59
|
+
def register_format(format_name, format_module, **)
|
|
60
|
+
FormatCatalog.register_format(format_name, format_module, **)
|
|
61
61
|
end
|
|
62
62
|
|
|
63
63
|
def get_format(format_name) = FormatCatalog.get_format(format_name)
|
|
@@ -70,6 +70,8 @@ module Coradoc
|
|
|
70
70
|
|
|
71
71
|
def resolve_includes(document, **) = Pipeline.resolve_includes(document, **)
|
|
72
72
|
|
|
73
|
+
def resolve_references(document, **) = Pipeline.resolve_references(document, **)
|
|
74
|
+
|
|
73
75
|
def rewrite_links(...) = Pipeline.rewrite_links(...)
|
|
74
76
|
|
|
75
77
|
def convert(text, **) = Pipeline.convert(text, **)
|
|
@@ -150,6 +152,7 @@ module Coradoc
|
|
|
150
152
|
autoload :FormatCatalog, "#{__dir__}/format_catalog"
|
|
151
153
|
autoload :Introspection, "#{__dir__}/introspection"
|
|
152
154
|
autoload :Dispatch, "#{__dir__}/dispatch"
|
|
155
|
+
autoload :Reference, "#{__dir__}/reference"
|
|
153
156
|
end
|
|
154
157
|
|
|
155
158
|
# Format gems self-register via Coradoc.register_format when they are required.
|
|
@@ -61,7 +61,7 @@ module Coradoc
|
|
|
61
61
|
end
|
|
62
62
|
|
|
63
63
|
def rebuild_children(node)
|
|
64
|
-
return node unless node.
|
|
64
|
+
return node unless node.is_a?(CoreModel::HasChildren)
|
|
65
65
|
|
|
66
66
|
original = node.children
|
|
67
67
|
return node if original.nil?
|
|
@@ -79,23 +79,27 @@ module Coradoc
|
|
|
79
79
|
original = node.public_send(attr_name)
|
|
80
80
|
return node if original.nil?
|
|
81
81
|
|
|
82
|
-
updated = original.is_a?(Array)
|
|
83
|
-
original.map { |c| visit(c) }
|
|
82
|
+
updated = if original.is_a?(Array)
|
|
83
|
+
original.map { |c| visit(c) }
|
|
84
|
+
else
|
|
84
85
|
visit(original)
|
|
86
|
+
end
|
|
85
87
|
return node if updated == original
|
|
86
88
|
|
|
87
89
|
node.dup.tap { |copy| copy.public_send("#{attr_name}=", updated) }
|
|
88
90
|
end
|
|
89
91
|
|
|
90
92
|
def rebuild_block(node)
|
|
91
|
-
return node unless node.
|
|
93
|
+
return node unless node.is_a?(CoreModel::HasChildren)
|
|
92
94
|
|
|
93
95
|
original_children = node.children
|
|
94
96
|
return node if original_children.nil?
|
|
95
97
|
|
|
96
|
-
updated_children = original_children.is_a?(Array)
|
|
97
|
-
original_children.map { |c| visit(c) }
|
|
98
|
+
updated_children = if original_children.is_a?(Array)
|
|
99
|
+
original_children.map { |c| visit(c) }
|
|
100
|
+
else
|
|
98
101
|
visit(original_children)
|
|
102
|
+
end
|
|
99
103
|
|
|
100
104
|
if inline_resolvable?(node)
|
|
101
105
|
original_content = node.content
|
|
@@ -113,7 +117,15 @@ module Coradoc
|
|
|
113
117
|
end
|
|
114
118
|
|
|
115
119
|
def inline_resolvable?(node)
|
|
116
|
-
|
|
120
|
+
# Capability check: nodes that include ChildrenContent expose a
|
|
121
|
+
# `content` attribute alongside `children`. We resolve attribute
|
|
122
|
+
# references only when content is a String (plain-text inline
|
|
123
|
+
# content). Using is_a?(ChildrenContent) instead of
|
|
124
|
+
# respond_to?(:content) catches type drift early: if a node
|
|
125
|
+
# acquires string content without including ChildrenContent,
|
|
126
|
+
# this method returns false (no resolution) rather than
|
|
127
|
+
# silently resolving on a mis-typed node.
|
|
128
|
+
node.is_a?(CoreModel::ChildrenContent) && node.content.is_a?(String)
|
|
117
129
|
end
|
|
118
130
|
|
|
119
131
|
# Resolve attribute references inside a flat content string.
|
|
@@ -147,7 +147,7 @@ module Coradoc
|
|
|
147
147
|
end
|
|
148
148
|
|
|
149
149
|
def flat_text
|
|
150
|
-
|
|
150
|
+
''
|
|
151
151
|
end
|
|
152
152
|
|
|
153
153
|
# Flatten this element to a plain-text string.
|
|
@@ -160,7 +160,50 @@ module Coradoc
|
|
|
160
160
|
#
|
|
161
161
|
# @return [String]
|
|
162
162
|
def flat_text
|
|
163
|
-
|
|
163
|
+
''
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
# True when this node counts as real body content for empty-body
|
|
167
|
+
# detection and similar structural queries. Default is true;
|
|
168
|
+
# ephemeral and metadata nodes override to false.
|
|
169
|
+
#
|
|
170
|
+
# Override sites: CommentBlock, CommentLine, FrontmatterBlock.
|
|
171
|
+
# Adding a new "skip me" type = overriding this method locally —
|
|
172
|
+
# no central walker to edit (OCP).
|
|
173
|
+
#
|
|
174
|
+
# @return [Boolean]
|
|
175
|
+
def body_content?
|
|
176
|
+
true
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
# True when this node's text content should be processed by text
|
|
180
|
+
# consumers (link rewriters, spell checkers, search indexers, etc.).
|
|
181
|
+
# Default delegates to +body_content?+ — ephemeral/metadata nodes
|
|
182
|
+
# (CommentBlock, FrontmatterBlock, …) are not prose because they
|
|
183
|
+
# are not body content. Verbatim block subclasses (SourceBlock,
|
|
184
|
+
# ListingBlock, LiteralBlock, PassBlock, StemBlock) override this
|
|
185
|
+
# to false because their text is literal, not subject to
|
|
186
|
+
# substitution or spell-checking.
|
|
187
|
+
#
|
|
188
|
+
# Override sites for verbatim: SourceBlock, ListingBlock,
|
|
189
|
+
# LiteralBlock, PassBlock, StemBlock, HorizontalRuleBlock.
|
|
190
|
+
#
|
|
191
|
+
# Distinct from +body_content?+: a SourceBlock IS body content
|
|
192
|
+
# (the doc has real substance) but its text is not prose (a link
|
|
193
|
+
# shaped string inside source is literal text, not a link).
|
|
194
|
+
#
|
|
195
|
+
# @return [Boolean]
|
|
196
|
+
def prose?
|
|
197
|
+
body_content?
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
# True when this node is structurally present but carries no
|
|
201
|
+
# visible characters (e.g., a paragraph whose text strips to
|
|
202
|
+
# empty). Default is false; inline content and paragraphs override.
|
|
203
|
+
#
|
|
204
|
+
# @return [Boolean]
|
|
205
|
+
def whitespace_only?
|
|
206
|
+
false
|
|
164
207
|
end
|
|
165
208
|
|
|
166
209
|
# Accept a visitor to traverse this element
|
|
@@ -3,14 +3,35 @@
|
|
|
3
3
|
module Coradoc
|
|
4
4
|
module CoreModel
|
|
5
5
|
class DefinitionItem < Base
|
|
6
|
+
# Primary term (first term in source order). For multi-term `<dt>`'s
|
|
7
|
+
# (AsciiDoc `term1::\nterm2::`), use {#terms} which carries the
|
|
8
|
+
# full list. `term` is kept as the singular accessor for legacy
|
|
9
|
+
# consumers; new consumers should prefer {#terms}.
|
|
6
10
|
attribute :term, :string
|
|
11
|
+
|
|
12
|
+
# All terms on this `<dt>`. Single-element for the common case;
|
|
13
|
+
# multi-element for shared-`<dd>` AsciiDoc forms like
|
|
14
|
+
# term1::
|
|
15
|
+
# term2::
|
|
16
|
+
# shared definition
|
|
17
|
+
# Order matches source order. Renderers emit one `<dt>` per entry.
|
|
18
|
+
attribute :terms, :string, collection: true, default: []
|
|
19
|
+
|
|
7
20
|
attribute :definitions, :string, collection: true
|
|
8
21
|
attribute :nested, DefinitionList
|
|
22
|
+
# `+`-continuation blocks attached to this dd in AsciiDoc source.
|
|
23
|
+
# Each entry is a CoreModel block (ParagraphBlock, AdmonitionBlock,
|
|
24
|
+
# etc.) that followed the dd after a `+` line — rendered as
|
|
25
|
+
# additional children of the dd.
|
|
26
|
+
attribute :attached_children, Base, collection: true, initialize_empty: true
|
|
9
27
|
|
|
10
28
|
def initialize(args = {})
|
|
11
29
|
@term_children = args.delete(:term_children) || []
|
|
12
30
|
@definition_children = args.delete(:definition_children) || []
|
|
13
31
|
super
|
|
32
|
+
# Backward-compat: if caller passed only `term:` (no `terms:`),
|
|
33
|
+
# seed the collection so #terms is always consistent with #term.
|
|
34
|
+
self.terms = [term].compact if terms.empty? && !term.to_s.empty?
|
|
14
35
|
end
|
|
15
36
|
|
|
16
37
|
attr_reader :term_children, :definition_children
|
|
@@ -40,7 +61,7 @@ module Coradoc
|
|
|
40
61
|
private
|
|
41
62
|
|
|
42
63
|
def comparable_attributes
|
|
43
|
-
super + %i[term definitions nested term_children definition_children]
|
|
64
|
+
super + %i[term terms definitions nested term_children definition_children attached_children]
|
|
44
65
|
end
|
|
45
66
|
end
|
|
46
67
|
end
|
|
@@ -58,7 +58,7 @@ module Coradoc
|
|
|
58
58
|
private
|
|
59
59
|
|
|
60
60
|
def matched_offset(trimmed)
|
|
61
|
-
|
|
61
|
+
/\A(?<sign>[+-]?)(?<digits>\d+)\z/.match(trimmed) do |m|
|
|
62
62
|
digits = m[:digits].to_i
|
|
63
63
|
signed = m[:sign] == '-' ? -digits : digits
|
|
64
64
|
mode = m[:sign].empty? ? 'absolute' : 'relative'
|
|
@@ -119,7 +119,7 @@ module Coradoc
|
|
|
119
119
|
# end
|
|
120
120
|
#
|
|
121
121
|
# Returns self for chaining at the list level.
|
|
122
|
-
def add_item(marker:
|
|
122
|
+
def add_item(marker: marker_type == 'ordered' ? '.' : '*')
|
|
123
123
|
item = ListItem.build(marker: marker) { |li| yield li if block_given? }
|
|
124
124
|
self.items = Array(items) + [item]
|
|
125
125
|
self
|
data/lib/coradoc/dispatch.rb
CHANGED
data/lib/coradoc/errors.rb
CHANGED
|
@@ -295,6 +295,25 @@ module Coradoc
|
|
|
295
295
|
end
|
|
296
296
|
end
|
|
297
297
|
|
|
298
|
+
# Raised when a document still contains unresolved +include::+ edges
|
|
299
|
+
# at serialization time and the target format cannot represent them
|
|
300
|
+
# (it would silently drop the content). Resolve the graph first with
|
|
301
|
+
# +Coradoc.resolve_includes+, or opt out with
|
|
302
|
+
# +allow_unresolved_includes: true+.
|
|
303
|
+
class UnresolvedIncludesError < Error
|
|
304
|
+
attr_reader :targets
|
|
305
|
+
|
|
306
|
+
def initialize(targets)
|
|
307
|
+
@targets = targets
|
|
308
|
+
super(
|
|
309
|
+
"Document has #{targets.size} unresolved include directive(s): " \
|
|
310
|
+
"#{targets.join(', ')}. Resolve them before serializing with " \
|
|
311
|
+
'Coradoc.resolve_includes(doc, base_dir: ...), or pass ' \
|
|
312
|
+
'allow_unresolved_includes: true.'
|
|
313
|
+
)
|
|
314
|
+
end
|
|
315
|
+
end
|
|
316
|
+
|
|
298
317
|
# Error raised when an include chain exceeds the configured depth limit.
|
|
299
318
|
class IncludeDepthExceededError < Error
|
|
300
319
|
attr_reader :depth, :target
|
|
@@ -18,6 +18,25 @@ module Coradoc
|
|
|
18
18
|
end
|
|
19
19
|
|
|
20
20
|
def get_format(format_name)
|
|
21
|
+
registry.get(format_name) || lazy_load_format(format_name)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# If a format gem (e.g. coradoc-adoc) is in the bundle but has not
|
|
25
|
+
# been required yet, attempt to require it on first lookup. This
|
|
26
|
+
# removes the friction where `Coradoc.parse(text, format: :asciidoc)`
|
|
27
|
+
# blows up just because the user only wrote `require "coradoc"` and
|
|
28
|
+
# never explicitly required `coradoc/asciidoc`. The require is for
|
|
29
|
+
# an external gem, not internal library code, so it stays within
|
|
30
|
+
# the autoload-over-require_relative rule.
|
|
31
|
+
def lazy_load_format(format_name)
|
|
32
|
+
return nil if format_name.nil? || format_name.to_s.empty?
|
|
33
|
+
|
|
34
|
+
begin
|
|
35
|
+
require "coradoc/#{format_name}"
|
|
36
|
+
rescue LoadError
|
|
37
|
+
return nil
|
|
38
|
+
end
|
|
39
|
+
|
|
21
40
|
registry.get(format_name)
|
|
22
41
|
end
|
|
23
42
|
|
|
@@ -30,6 +30,16 @@ module Coradoc
|
|
|
30
30
|
true
|
|
31
31
|
end
|
|
32
32
|
|
|
33
|
+
# Whether the serializer can represent unresolved include edges
|
|
34
|
+
# (graph-mode CoreModel::Include nodes) without losing them.
|
|
35
|
+
# Formats that round-trip the directive natively (asciidoc,
|
|
36
|
+
# mirror) or preserve it explicitly (markdown comments) override
|
|
37
|
+
# this to true; formats that would silently drop the content keep
|
|
38
|
+
# the default false, and the pipeline raises instead of losing it.
|
|
39
|
+
def preserves_unresolved_includes?
|
|
40
|
+
false
|
|
41
|
+
end
|
|
42
|
+
|
|
33
43
|
# Parse a file to CoreModel, handling file-specific concerns like include resolution.
|
|
34
44
|
# Format modules that support include directives or file-relative references
|
|
35
45
|
# should override this method.
|
data/lib/coradoc/hooks.rb
CHANGED
|
@@ -89,7 +89,7 @@ module Coradoc
|
|
|
89
89
|
modified_args = invoke(hook_point, *args, **kwargs)
|
|
90
90
|
result = yield(*modified_args)
|
|
91
91
|
|
|
92
|
-
after_point = "after_#{hook_point.to_s.sub('before_', '')}"
|
|
92
|
+
after_point = :"after_#{hook_point.to_s.sub('before_', '')}"
|
|
93
93
|
result = invoke(after_point, result, **kwargs) if HOOK_POINTS.key?(after_point)
|
|
94
94
|
|
|
95
95
|
result
|
|
@@ -11,12 +11,12 @@ module Coradoc
|
|
|
11
11
|
# Out-of-bounds clamps gracefully (SPEC 3.4). One-based indexing
|
|
12
12
|
# (asciidoctor convention).
|
|
13
13
|
module Lines
|
|
14
|
-
SPEC_PART =
|
|
14
|
+
SPEC_PART = /
|
|
15
15
|
\A
|
|
16
16
|
(?<start>\d+)
|
|
17
17
|
(?:\.\.(?<finish>\d+))?
|
|
18
18
|
\z
|
|
19
|
-
|
|
19
|
+
/x
|
|
20
20
|
|
|
21
21
|
# @param text [String]
|
|
22
22
|
# @param options [Coradoc::CoreModel::IncludeOptions]
|
|
@@ -17,8 +17,8 @@ module Coradoc
|
|
|
17
17
|
# Markers may appear on their own line; they must be the first
|
|
18
18
|
# non-whitespace token on that line (asciidoctor convention).
|
|
19
19
|
module Tags
|
|
20
|
-
MARKER_OPEN =
|
|
21
|
-
MARKER_CLOSE =
|
|
20
|
+
MARKER_OPEN = %r{\A[[:space:]]*(?://+|#+)[[:space:]]*tag::([^\[\]]+)\[[[:space:]]*\]}
|
|
21
|
+
MARKER_CLOSE = %r{\A[[:space:]]*(?://+|#+)[[:space:]]*end::([^\[\]]+)\[[[:space:]]*\]}
|
|
22
22
|
|
|
23
23
|
# @param text [String] raw included file content
|
|
24
24
|
# @param options [Coradoc::CoreModel::IncludeOptions]
|
|
@@ -134,5 +134,3 @@ module Coradoc
|
|
|
134
134
|
end
|
|
135
135
|
end
|
|
136
136
|
end
|
|
137
|
-
|
|
138
|
-
require 'set'
|
|
@@ -17,10 +17,10 @@ module Coradoc
|
|
|
17
17
|
attr_reader :counts
|
|
18
18
|
|
|
19
19
|
def visit(element)
|
|
20
|
-
return super
|
|
20
|
+
return super unless element.is_a?(CoreModel::Base)
|
|
21
21
|
|
|
22
22
|
@counts[type_key_for(element)] += 1
|
|
23
|
-
super
|
|
23
|
+
super
|
|
24
24
|
end
|
|
25
25
|
|
|
26
26
|
private
|
|
@@ -12,23 +12,14 @@ module Coradoc
|
|
|
12
12
|
# new link-bearing subclass means overriding +link_kind+ on it,
|
|
13
13
|
# not editing a case/when here (OCP).
|
|
14
14
|
#
|
|
15
|
-
# Verbatim block
|
|
16
|
-
#
|
|
17
|
-
#
|
|
18
|
-
#
|
|
15
|
+
# Verbatim block recognition is delegated to +CoreModel::Base#prose?+
|
|
16
|
+
# — each block class answers "is my text live or literal?". Adding a
|
|
17
|
+
# new verbatim block type means overriding +prose?+ on it, not editing
|
|
18
|
+
# a list here. The visitor is closed for that change.
|
|
19
19
|
#
|
|
20
20
|
# Dispatch is class-based (no +respond_to?+ duck-typing). Unrecognized
|
|
21
21
|
# classes are returned unchanged — the visitor is closed by design.
|
|
22
22
|
class Visitor
|
|
23
|
-
# Verbatim block classes — content is raw, no link semantics.
|
|
24
|
-
VERBATIM_TYPES = [
|
|
25
|
-
Coradoc::CoreModel::SourceBlock,
|
|
26
|
-
Coradoc::CoreModel::ListingBlock,
|
|
27
|
-
Coradoc::CoreModel::LiteralBlock,
|
|
28
|
-
Coradoc::CoreModel::PassBlock,
|
|
29
|
-
Coradoc::CoreModel::StemBlock
|
|
30
|
-
].freeze
|
|
31
|
-
|
|
32
23
|
# Structural/container classes that own a child collection. Each
|
|
33
24
|
# entry maps the class to the reader method that exposes its
|
|
34
25
|
# children. MECE — every "recurse into the children" case lands
|
|
@@ -68,7 +59,7 @@ module Coradoc
|
|
|
68
59
|
private
|
|
69
60
|
|
|
70
61
|
def visit_subtree(node)
|
|
71
|
-
return node
|
|
62
|
+
return node unless node.prose?
|
|
72
63
|
return rewrite_inline(node) if node.is_a?(Coradoc::CoreModel::InlineElement)
|
|
73
64
|
|
|
74
65
|
reader = reader_for(node)
|
data/lib/coradoc/pipeline.rb
CHANGED
|
@@ -42,8 +42,65 @@ module Coradoc
|
|
|
42
42
|
)
|
|
43
43
|
end
|
|
44
44
|
|
|
45
|
-
def rewrite_links(document, rewriter: nil, &
|
|
46
|
-
Coradoc::LinkRewriter.rewrite(document, rewriter: rewriter, &
|
|
45
|
+
def rewrite_links(document, rewriter: nil, &)
|
|
46
|
+
Coradoc::LinkRewriter.rewrite(document, rewriter: rewriter, &)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Resolve every reference (xref, citation, link, include, image,
|
|
50
|
+
# footnote) in a parsed document using a unified content-graph
|
|
51
|
+
# model. Mirrors +resolve_includes+ in shape: two-step, immutable.
|
|
52
|
+
#
|
|
53
|
+
# Step one always runs: every Edge is resolved through the
|
|
54
|
+
# Resolver and the +missing+/+ambiguous+ policies are enforced
|
|
55
|
+
# (raise or warn). Step two is opt-in: with +materialize: true+
|
|
56
|
+
# the tree is rebuilt with each Edge replaced by the output of
|
|
57
|
+
# the Materializer registered for its
|
|
58
|
+
# [kind, presentation, format] tuple.
|
|
59
|
+
#
|
|
60
|
+
# The input document is never mutated. With +materialize: false+
|
|
61
|
+
# the input document itself is returned; with +materialize: true+
|
|
62
|
+
# a new document is returned that structurally shares untouched
|
|
63
|
+
# subtrees with the input (treat both as immutable). Nodes whose
|
|
64
|
+
# kind has no registered materializer are preserved unchanged.
|
|
65
|
+
#
|
|
66
|
+
# @param document [CoreModel::Base] parsed document
|
|
67
|
+
# @param catalog [Reference::Catalog::*] index of addressable Content
|
|
68
|
+
# @param presentation [Reference::Presentation::Base] slicing and ordering
|
|
69
|
+
# @param resolver [Reference::Resolver::Base, nil] defaults to CatalogResolver
|
|
70
|
+
# @param missing [Symbol] :warn (default), :silent, :error, :passthrough
|
|
71
|
+
# @param ambiguous [Symbol] :disambiguate (default), :first, :error
|
|
72
|
+
# @param materialize [Boolean] when true, replace edges with rendered inlines
|
|
73
|
+
# @param format [Symbol, nil] target format for materializer lookup
|
|
74
|
+
# (:html, :asciidoc, ...); nil matches format-agnostic materializers
|
|
75
|
+
# @return [CoreModel::Base] the input document (validation only) or
|
|
76
|
+
# a new materialized document
|
|
77
|
+
#
|
|
78
|
+
# @example Resolve cross-references into HTML links
|
|
79
|
+
# doc = Coradoc.parse(text, format: :asciidoc)
|
|
80
|
+
# catalog = Coradoc::Reference::Catalog::Local.from_doc(doc)
|
|
81
|
+
# presentation = Coradoc::Reference::Presentation::SingleDocument.new
|
|
82
|
+
# resolved = Coradoc.resolve_references(
|
|
83
|
+
# doc,
|
|
84
|
+
# catalog: catalog,
|
|
85
|
+
# presentation: presentation,
|
|
86
|
+
# materialize: true,
|
|
87
|
+
# format: :html
|
|
88
|
+
# )
|
|
89
|
+
def resolve_references(document, catalog:, presentation:,
|
|
90
|
+
resolver: nil,
|
|
91
|
+
missing: :warn,
|
|
92
|
+
ambiguous: :disambiguate,
|
|
93
|
+
materialize: false,
|
|
94
|
+
format: nil)
|
|
95
|
+
Coradoc::Reference::Resolution.new(
|
|
96
|
+
catalog: catalog,
|
|
97
|
+
presentation: presentation,
|
|
98
|
+
resolver: resolver,
|
|
99
|
+
missing: missing,
|
|
100
|
+
ambiguous: ambiguous,
|
|
101
|
+
materialize: materialize,
|
|
102
|
+
format: format
|
|
103
|
+
).call(document)
|
|
47
104
|
end
|
|
48
105
|
|
|
49
106
|
def convert(text, from:, to:, **)
|
|
@@ -63,17 +120,19 @@ module Coradoc
|
|
|
63
120
|
raise TransformationError, "No transformer found for #{model.class}"
|
|
64
121
|
end
|
|
65
122
|
|
|
66
|
-
def serialize(model, to:, **)
|
|
123
|
+
def serialize(model, to:, allow_unresolved_includes: false, **)
|
|
67
124
|
format_module = FormatCatalog.get_format(to)
|
|
68
125
|
raise UnsupportedFormatError.new(to, available: FormatCatalog.registered_formats) unless format_module
|
|
69
126
|
|
|
127
|
+
Coradoc::Validation.guard_unresolved_includes!(model, format_module) unless allow_unresolved_includes
|
|
128
|
+
|
|
70
129
|
model = Hooks.invoke(:before_serialize, model, format: to)
|
|
71
130
|
result = format_module.serialize(model, **)
|
|
72
131
|
Hooks.invoke(:after_serialize, result, format: to)
|
|
73
132
|
end
|
|
74
133
|
|
|
75
|
-
def build(&
|
|
76
|
-
CoreModel::DocumentElement.build(children: [], &
|
|
134
|
+
def build(&)
|
|
135
|
+
CoreModel::DocumentElement.build(children: [], &)
|
|
77
136
|
end
|
|
78
137
|
|
|
79
138
|
def parse_file(path, format: nil)
|