coradoc 2.0.20 → 2.0.22
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 +26 -1
- data/lib/coradoc/coradoc.rb +66 -8
- data/lib/coradoc/core_model/children_content.rb +5 -0
- data/lib/coradoc/core_model/frontmatter/frontmatter_value.rb +61 -0
- data/lib/coradoc/core_model/has_children.rb +23 -0
- data/lib/coradoc/core_model/include.rb +43 -0
- data/lib/coradoc/core_model/include_level_offset.rb +71 -0
- data/lib/coradoc/core_model/include_options.rb +100 -0
- data/lib/coradoc/core_model/raw_inline_element.rb +16 -0
- data/lib/coradoc/core_model/structural_element.rb +5 -0
- data/lib/coradoc/core_model.rb +5 -0
- data/lib/coradoc/errors.rb +56 -0
- data/lib/coradoc/include_resolver/filesystem.rb +84 -0
- data/lib/coradoc/include_resolver.rb +67 -0
- data/lib/coradoc/include_selectors/indent.rb +54 -0
- data/lib/coradoc/include_selectors/level_offset.rb +86 -0
- data/lib/coradoc/include_selectors/lines.rb +60 -0
- data/lib/coradoc/include_selectors/tags.rb +138 -0
- data/lib/coradoc/include_selectors.rb +26 -0
- data/lib/coradoc/performance_regression.rb +19 -7
- data/lib/coradoc/resolve_includes.rb +202 -0
- data/lib/coradoc/version.rb +1 -1
- metadata +15 -1
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Coradoc
|
|
4
|
+
# Flat-mode include processor — the explicit "flatten" step.
|
|
5
|
+
#
|
|
6
|
+
# Walks a parsed CoreModel and expands every {CoreModel::Include} link
|
|
7
|
+
# node into the parsed content of its target, recursing into the result.
|
|
8
|
+
# The original CoreModel is NOT modified — a new subtree is constructed
|
|
9
|
+
# and spliced into place.
|
|
10
|
+
#
|
|
11
|
+
# Invoked via the public API +Coradoc.resolve_includes(doc, base_dir:)+.
|
|
12
|
+
# Callers control resolution strategy (filesystem, HTTP, custom),
|
|
13
|
+
# missing-include policy, recursion depth, and path-traversal safety.
|
|
14
|
+
#
|
|
15
|
+
# Honors:
|
|
16
|
+
# - +missing_include+ policy: :error (default) | :warn | :silent | :passthrough
|
|
17
|
+
# - +max_depth+ limit (raises Coradoc::IncludeDepthExceededError)
|
|
18
|
+
# - circular detection (raises Coradoc::CircularIncludeError)
|
|
19
|
+
# - tags/lines/indent selectors (applied to raw text before parse)
|
|
20
|
+
# - leveloffset selector (applied to parsed CoreModel)
|
|
21
|
+
# - +base_dir+ re-rooting (recursive includes resolve relative to
|
|
22
|
+
# the including file — SPEC 7.2)
|
|
23
|
+
class ResolveIncludes
|
|
24
|
+
DEFAULT_MAX_DEPTH = 64
|
|
25
|
+
|
|
26
|
+
class << self
|
|
27
|
+
def call(core, resolver:, base_dir:, **opts)
|
|
28
|
+
new(resolver: resolver, base_dir: base_dir, **opts).call(core)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# @param resolver [#call] anything responding to +#call(target:, base_dir:, options:, context:)+
|
|
33
|
+
# @param base_dir [String] absolute path to the document root directory
|
|
34
|
+
# @param missing_include [Symbol] :error | :warn | :silent | :passthrough
|
|
35
|
+
# @param max_depth [Integer] recursion cap
|
|
36
|
+
# @param parse_format [Symbol] format to use when re-parsing included content
|
|
37
|
+
def initialize(resolver:, base_dir:,
|
|
38
|
+
missing_include: :error,
|
|
39
|
+
max_depth: DEFAULT_MAX_DEPTH,
|
|
40
|
+
parse_format: :asciidoc)
|
|
41
|
+
@resolver = Coradoc::IncludeResolver.coerce(resolver, base_dir: base_dir)
|
|
42
|
+
@base_dir = base_dir
|
|
43
|
+
@missing_policy = missing_include
|
|
44
|
+
@max_depth = max_depth
|
|
45
|
+
@parse_format = parse_format
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Walk + transform. Returns a NEW CoreModel with includes expanded.
|
|
49
|
+
def call(core)
|
|
50
|
+
expand_node(core, base_dir: File.expand_path(@base_dir), chain: [], depth: 0)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
private
|
|
54
|
+
|
|
55
|
+
def expand_node(node, base_dir:, chain:, depth:)
|
|
56
|
+
return node unless node.is_a?(Coradoc::CoreModel::Base)
|
|
57
|
+
|
|
58
|
+
case node
|
|
59
|
+
when Coradoc::CoreModel::Include
|
|
60
|
+
expand_include(node, base_dir: base_dir, chain: chain, depth: depth)
|
|
61
|
+
when Coradoc::CoreModel::StructuralElement, Coradoc::CoreModel::Block
|
|
62
|
+
expand_container(node, base_dir: base_dir, chain: chain, depth: depth)
|
|
63
|
+
else
|
|
64
|
+
node
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def expand_container(node, base_dir:, chain:, depth:)
|
|
69
|
+
return node if node.children.nil? || node.children.empty?
|
|
70
|
+
|
|
71
|
+
expanded_children = node.children.flat_map do |child|
|
|
72
|
+
expanded = expand_node(child, base_dir: base_dir, chain: chain, depth: depth)
|
|
73
|
+
Array(expanded)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
return node if expanded_children.equal?(node.children) || same_children?(expanded_children, node.children)
|
|
77
|
+
|
|
78
|
+
duplicate_with_children(node, expanded_children)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# The processor must not mutate its input. Each container that has
|
|
82
|
+
# expanded includes is replaced by a shallow copy with a new
|
|
83
|
+
# +children+ array — the original document tree stays intact so the
|
|
84
|
+
# caller can re-resolve with different options.
|
|
85
|
+
def duplicate_with_children(node, new_children)
|
|
86
|
+
duplicate = node.dup
|
|
87
|
+
duplicate.children = new_children
|
|
88
|
+
duplicate
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def same_children?(expanded, original)
|
|
92
|
+
return false unless expanded.length == original.length
|
|
93
|
+
|
|
94
|
+
expanded.each_with_index.all? { |node, i| node.equal?(original[i]) }
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def expand_include(include_node, base_dir:, chain:, depth:)
|
|
98
|
+
enforce_depth!(include_node, depth)
|
|
99
|
+
enforce_cycle!(include_node, base_dir: base_dir, chain: chain)
|
|
100
|
+
|
|
101
|
+
target = include_node.target
|
|
102
|
+
new_chain = chain + [resolve_target_path(target, base_dir)]
|
|
103
|
+
|
|
104
|
+
content = fetch_content(include_node, base_dir: base_dir)
|
|
105
|
+
return replacement_for_missing(include_node) if missing_content?(content)
|
|
106
|
+
|
|
107
|
+
applied = apply_text_selectors(content, include_node.options)
|
|
108
|
+
parsed = parse_included(applied)
|
|
109
|
+
|
|
110
|
+
shifted = Coradoc::IncludeSelectors::LevelOffset.call(parsed, options: include_node.options)
|
|
111
|
+
|
|
112
|
+
new_base_dir = File.dirname(resolve_target_path(target, base_dir))
|
|
113
|
+
expand_subtree(shifted, base_dir: new_base_dir, chain: new_chain, depth: depth + 1)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def missing_content?(content)
|
|
117
|
+
content.nil? || content == :passthrough
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def fetch_content(include_node, base_dir:)
|
|
121
|
+
@resolver.call(
|
|
122
|
+
target: include_node.target,
|
|
123
|
+
base_dir: base_dir,
|
|
124
|
+
options: include_node.options,
|
|
125
|
+
context: {}
|
|
126
|
+
)
|
|
127
|
+
rescue Coradoc::IncludeNotFoundError => e
|
|
128
|
+
handle_missing(include_node, e)
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def handle_missing(include_node, error)
|
|
132
|
+
case @missing_policy
|
|
133
|
+
when :error then raise error
|
|
134
|
+
when :warn
|
|
135
|
+
Coradoc::Logger.warn("Include target not found: #{include_node.target}")
|
|
136
|
+
nil
|
|
137
|
+
when :silent then nil
|
|
138
|
+
when :passthrough then :passthrough
|
|
139
|
+
else raise error
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def replacement_for_missing(include_node)
|
|
144
|
+
return [include_node] if @missing_policy == :passthrough
|
|
145
|
+
|
|
146
|
+
[]
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def apply_text_selectors(text, options)
|
|
150
|
+
text = apply_lines_or_tags(text, options)
|
|
151
|
+
Coradoc::IncludeSelectors::Indent.call(text, options: options)
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def apply_lines_or_tags(text, options)
|
|
155
|
+
# lines wins when both specified (SPEC 3.5)
|
|
156
|
+
return Coradoc::IncludeSelectors::Lines.call(text, options: options) if options.lines?
|
|
157
|
+
|
|
158
|
+
Coradoc::IncludeSelectors::Tags.call(text, options: options)
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def parse_included(text)
|
|
162
|
+
return empty_core if text.nil? || text.empty?
|
|
163
|
+
|
|
164
|
+
Coradoc.parse(text, format: @parse_format)
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def empty_core
|
|
168
|
+
Coradoc::CoreModel::DocumentElement.new
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def expand_subtree(core, base_dir:, chain:, depth:)
|
|
172
|
+
expanded = expand_node(core, base_dir: base_dir, chain: chain, depth: depth)
|
|
173
|
+
return [expanded] unless expanded.is_a?(Coradoc::CoreModel::StructuralElement)
|
|
174
|
+
|
|
175
|
+
expanded.children || []
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
def enforce_depth!(include_node, depth)
|
|
179
|
+
return if depth < @max_depth
|
|
180
|
+
|
|
181
|
+
raise Coradoc::IncludeDepthExceededError.new(
|
|
182
|
+
target: include_node.target,
|
|
183
|
+
depth: depth,
|
|
184
|
+
max: @max_depth
|
|
185
|
+
)
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
def enforce_cycle!(include_node, base_dir:, chain:)
|
|
189
|
+
full = resolve_target_path(include_node.target, base_dir)
|
|
190
|
+
return unless chain.include?(full)
|
|
191
|
+
|
|
192
|
+
raise Coradoc::CircularIncludeError.new(
|
|
193
|
+
target: include_node.target,
|
|
194
|
+
chain: chain
|
|
195
|
+
)
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
def resolve_target_path(target, base_dir)
|
|
199
|
+
File.expand_path(target, base_dir)
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
end
|
data/lib/coradoc/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: coradoc
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.0.
|
|
4
|
+
version: 2.0.22
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ribose Inc.
|
|
@@ -73,11 +73,16 @@ files:
|
|
|
73
73
|
- lib/coradoc/core_model/frontmatter.rb
|
|
74
74
|
- lib/coradoc/core_model/frontmatter/codec.rb
|
|
75
75
|
- lib/coradoc/core_model/frontmatter/field_transform.rb
|
|
76
|
+
- lib/coradoc/core_model/frontmatter/frontmatter_value.rb
|
|
76
77
|
- lib/coradoc/core_model/frontmatter/schema_resolver.rb
|
|
77
78
|
- lib/coradoc/core_model/frontmatter/text_splitter.rb
|
|
79
|
+
- lib/coradoc/core_model/has_children.rb
|
|
78
80
|
- lib/coradoc/core_model/horizontal_rule_block.rb
|
|
79
81
|
- lib/coradoc/core_model/id_generator.rb
|
|
80
82
|
- lib/coradoc/core_model/image.rb
|
|
83
|
+
- lib/coradoc/core_model/include.rb
|
|
84
|
+
- lib/coradoc/core_model/include_level_offset.rb
|
|
85
|
+
- lib/coradoc/core_model/include_options.rb
|
|
81
86
|
- lib/coradoc/core_model/inline_element.rb
|
|
82
87
|
- lib/coradoc/core_model/list_block.rb
|
|
83
88
|
- lib/coradoc/core_model/list_item.rb
|
|
@@ -88,6 +93,7 @@ files:
|
|
|
88
93
|
- lib/coradoc/core_model/paragraph_block.rb
|
|
89
94
|
- lib/coradoc/core_model/pass_block.rb
|
|
90
95
|
- lib/coradoc/core_model/quote_block.rb
|
|
96
|
+
- lib/coradoc/core_model/raw_inline_element.rb
|
|
91
97
|
- lib/coradoc/core_model/reviewer_block.rb
|
|
92
98
|
- lib/coradoc/core_model/sidebar_block.rb
|
|
93
99
|
- lib/coradoc/core_model/source_block.rb
|
|
@@ -103,6 +109,13 @@ files:
|
|
|
103
109
|
- lib/coradoc/errors.rb
|
|
104
110
|
- lib/coradoc/format_module.rb
|
|
105
111
|
- lib/coradoc/hooks.rb
|
|
112
|
+
- lib/coradoc/include_resolver.rb
|
|
113
|
+
- lib/coradoc/include_resolver/filesystem.rb
|
|
114
|
+
- lib/coradoc/include_selectors.rb
|
|
115
|
+
- lib/coradoc/include_selectors/indent.rb
|
|
116
|
+
- lib/coradoc/include_selectors/level_offset.rb
|
|
117
|
+
- lib/coradoc/include_selectors/lines.rb
|
|
118
|
+
- lib/coradoc/include_selectors/tags.rb
|
|
106
119
|
- lib/coradoc/input.rb
|
|
107
120
|
- lib/coradoc/logger.rb
|
|
108
121
|
- lib/coradoc/output.rb
|
|
@@ -110,6 +123,7 @@ files:
|
|
|
110
123
|
- lib/coradoc/processor_registry.rb
|
|
111
124
|
- lib/coradoc/query.rb
|
|
112
125
|
- lib/coradoc/registry.rb
|
|
126
|
+
- lib/coradoc/resolve_includes.rb
|
|
113
127
|
- lib/coradoc/serializer/registry.rb
|
|
114
128
|
- lib/coradoc/transform.rb
|
|
115
129
|
- lib/coradoc/transform/base.rb
|