jekyll-relationships 0.1.0.alpha → 0.2.0.alpha
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/jekyll-relationships/configuration/debug_setting.rb +72 -12
- data/lib/jekyll-relationships/configuration/defaults.rb +3 -1
- data/lib/jekyll-relationships/configuration/frontmatter.rb +28 -1
- data/lib/jekyll-relationships/configuration/parser.rb +20 -3
- data/lib/jekyll-relationships/configuration/prune_rule_settings.rb +33 -5
- data/lib/jekyll-relationships/configuration/tree_frontmatter.rb +8 -2
- data/lib/jekyll-relationships/configuration.rb +43 -8
- data/lib/jekyll-relationships/debug_logger.rb +94 -3
- data/lib/jekyll-relationships/definitions/normal_relationship.rb +13 -2
- data/lib/jekyll-relationships/definitions/prune_rule.rb +19 -3
- data/lib/jekyll-relationships/definitions/tree_relationship.rb +8 -2
- data/lib/jekyll-relationships/documents/registry.rb +182 -33
- data/lib/jekyll-relationships/engine/normal_seed.rb +222 -0
- data/lib/jekyll-relationships/engine/persisted_links.rb +365 -0
- data/lib/jekyll-relationships/engine/raw_path_state.rb +46 -116
- data/lib/jekyll-relationships/engine/relationship_state.rb +67 -56
- data/lib/jekyll-relationships/engine/session.rb +159 -19
- data/lib/jekyll-relationships/engine/write_back.rb +3 -42
- data/lib/jekyll-relationships/engine.rb +168 -46
- data/lib/jekyll-relationships/pruning/rule_pruner.rb +7 -7
- data/lib/jekyll-relationships/pruning/tree_phase.rb +128 -124
- data/lib/jekyll-relationships/pruning/tree_provenance.rb +1 -1
- data/lib/jekyll-relationships/references/accumulator.rb +87 -9
- data/lib/jekyll-relationships/references/template.rb +46 -11
- data/lib/jekyll-relationships/resolvers/base.rb +59 -2
- data/lib/jekyll-relationships/support/frontmatter_matcher.rb +76 -0
- data/lib/jekyll-relationships/support/placeholders.rb +1 -0
- data/lib/jekyll-relationships/trees/edge_builder.rb +43 -11
- data/lib/jekyll-relationships/trees/graph.rb +135 -17
- data/lib/jekyll-relationships/trees/root_distances.rb +44 -0
- data/lib/jekyll-relationships/version.rb +1 -1
- data/lib/jekyll-relationships.rb +1 -0
- data/readme.md +185 -26
- metadata +6 -2
|
@@ -0,0 +1,365 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'json'
|
|
4
|
+
|
|
5
|
+
module Jekyll
|
|
6
|
+
module Plugins
|
|
7
|
+
|
|
8
|
+
module Relationships
|
|
9
|
+
|
|
10
|
+
class Engine
|
|
11
|
+
|
|
12
|
+
# Stores resolver-promoted links that may be restored in later rounds.
|
|
13
|
+
#
|
|
14
|
+
# Persisted links are a fallback overlay, not part of the source-derived
|
|
15
|
+
# seeds. A later round only reapplies them after resolvers have had their own
|
|
16
|
+
# chance to recreate the same links naturally.
|
|
17
|
+
class PersistedLinks
|
|
18
|
+
# Builds one persisted-link store with the active duplicate-handling rules.
|
|
19
|
+
def initialize(engine:)
|
|
20
|
+
@engine = engine
|
|
21
|
+
@configuration = engine.configuration
|
|
22
|
+
@registry = engine.registry
|
|
23
|
+
@entries_by_state = Hash.new { |hash, key| hash[key] = [] }
|
|
24
|
+
@next_order = 0
|
|
25
|
+
@round_identifier = 0
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Marks the start of one new resolution round.
|
|
29
|
+
def start_round!
|
|
30
|
+
@round_identifier += 1
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Records one persisted link for the given directional relationship pair.
|
|
34
|
+
def persist_link(source_state:, target_document:, metadata:, count:)
|
|
35
|
+
persist_directional_link(
|
|
36
|
+
document: source_state.document,
|
|
37
|
+
to_collection: source_state.definition.to_collection,
|
|
38
|
+
target_document: target_document,
|
|
39
|
+
key: @registry.key_for(target_document, primary_path: source_state.definition.primary_path),
|
|
40
|
+
metadata: metadata,
|
|
41
|
+
count: count
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
return unless source_state.definition.bidirectional
|
|
45
|
+
|
|
46
|
+
persist_directional_link(
|
|
47
|
+
document: target_document,
|
|
48
|
+
to_collection: source_state.definition.from_collection,
|
|
49
|
+
target_document: source_state.document,
|
|
50
|
+
key: @registry.key_for(source_state.document, primary_path: source_state.definition.primary_path),
|
|
51
|
+
metadata: metadata,
|
|
52
|
+
count: count
|
|
53
|
+
)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Removes one persisted link from the given directional pair.
|
|
57
|
+
def clear_link(source_state:, target_document:)
|
|
58
|
+
clear_directional_link(
|
|
59
|
+
document: source_state.document,
|
|
60
|
+
to_collection: source_state.definition.to_collection,
|
|
61
|
+
target_document: target_document
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
return unless source_state.definition.bidirectional
|
|
65
|
+
|
|
66
|
+
clear_directional_link(
|
|
67
|
+
document: target_document,
|
|
68
|
+
to_collection: source_state.definition.from_collection,
|
|
69
|
+
target_document: source_state.document
|
|
70
|
+
)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Removes every persisted link for the given directional pair.
|
|
74
|
+
def clear_all(source_state:)
|
|
75
|
+
state_entries = @entries_by_state[[source_state.document.object_id, source_state.definition.to_collection]].dup
|
|
76
|
+
clear_directional_state(
|
|
77
|
+
document: source_state.document,
|
|
78
|
+
to_collection: source_state.definition.to_collection
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
return unless source_state.definition.bidirectional
|
|
82
|
+
|
|
83
|
+
state_entries.each do |entry|
|
|
84
|
+
clear_directional_link(
|
|
85
|
+
document: entry.fetch(:document),
|
|
86
|
+
to_collection: source_state.definition.from_collection,
|
|
87
|
+
target_document: source_state.document
|
|
88
|
+
)
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Drops any persisted links whose source or target documents no longer survive.
|
|
93
|
+
def remove_documents!(documents)
|
|
94
|
+
removed_document_ids = Array(documents).each_with_object({}) do |document, lookup|
|
|
95
|
+
lookup[document.object_id] = true
|
|
96
|
+
end
|
|
97
|
+
return if removed_document_ids.empty?
|
|
98
|
+
|
|
99
|
+
@entries_by_state.delete_if do |(source_document_id, _to_collection), _entries|
|
|
100
|
+
removed_document_ids.key?(source_document_id)
|
|
101
|
+
end
|
|
102
|
+
@entries_by_state.each_value do |entries|
|
|
103
|
+
entries.reject! do |entry|
|
|
104
|
+
removed_document_ids.key?(entry.fetch(:document).object_id)
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# Restores any still-valid persisted links that resolvers did not recreate.
|
|
110
|
+
def reapply_missing_links(state:)
|
|
111
|
+
state_entries = @entries_by_state[[state.document.object_id, state.definition.to_collection]]
|
|
112
|
+
return if state_entries.empty?
|
|
113
|
+
|
|
114
|
+
cleanup_dead_entries!(
|
|
115
|
+
state: state,
|
|
116
|
+
state_entries: state_entries
|
|
117
|
+
)
|
|
118
|
+
return if state_entries.empty?
|
|
119
|
+
|
|
120
|
+
missing_entries = missing_entries_for(state_entries: state_entries, current_entries: state.current_link_entries)
|
|
121
|
+
return if missing_entries.empty?
|
|
122
|
+
|
|
123
|
+
base_count = state.current_link_entries.length
|
|
124
|
+
reinsertions = []
|
|
125
|
+
missing_entries.each do |entry|
|
|
126
|
+
result = state.link(
|
|
127
|
+
entry.fetch(:document),
|
|
128
|
+
metadata: entry.fetch(:metadata),
|
|
129
|
+
count: entry.fetch(:count),
|
|
130
|
+
reflect: true,
|
|
131
|
+
origin: 'persisted overlay',
|
|
132
|
+
persist: false
|
|
133
|
+
)
|
|
134
|
+
next unless result.fetch(:action) == :added
|
|
135
|
+
|
|
136
|
+
reinsertions << {
|
|
137
|
+
entry: result.fetch(:entry),
|
|
138
|
+
position_ratio: entry.fetch(:position_ratio),
|
|
139
|
+
order: entry.fetch(:order)
|
|
140
|
+
}
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
state.reposition_entries!(reinsertions: reinsertions, base_count: base_count) unless reinsertions.empty?
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
# Refreshes stored position hints from the latest resolved ordering.
|
|
147
|
+
def refresh_positions(state:)
|
|
148
|
+
state_entries = @entries_by_state[[state.document.object_id, state.definition.to_collection]]
|
|
149
|
+
return if state_entries.empty?
|
|
150
|
+
|
|
151
|
+
current_entries = state.current_link_entries
|
|
152
|
+
return if current_entries.empty?
|
|
153
|
+
|
|
154
|
+
if @configuration.multiple_settings.keep?
|
|
155
|
+
update_keep_positions!(
|
|
156
|
+
state_entries: state_entries,
|
|
157
|
+
current_entries: current_entries
|
|
158
|
+
)
|
|
159
|
+
return
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
current_indexes = current_entries.each_with_index.each_with_object({}) do |(entry, index), indexes|
|
|
163
|
+
indexes[entry.fetch(:document).object_id] = {
|
|
164
|
+
index: index,
|
|
165
|
+
entry: entry
|
|
166
|
+
}
|
|
167
|
+
end
|
|
168
|
+
state_entries.each do |entry|
|
|
169
|
+
match = current_indexes[entry.fetch(:document).object_id]
|
|
170
|
+
next unless match
|
|
171
|
+
|
|
172
|
+
entry[:position_ratio] = position_ratio(index: match.fetch(:index), length: current_entries.length)
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
private
|
|
177
|
+
|
|
178
|
+
# Stores one directional persisted entry under the active duplicate mode.
|
|
179
|
+
def persist_directional_link(document:, to_collection:, target_document:, key:, metadata:, count:)
|
|
180
|
+
state_entries = @entries_by_state[[document.object_id, to_collection]]
|
|
181
|
+
if @configuration.multiple_settings.keep?
|
|
182
|
+
existing_entry = state_entries.find do |entry|
|
|
183
|
+
entry_signature(entry) == signature_for(document: target_document, metadata: metadata) &&
|
|
184
|
+
entry.fetch(:last_seen_round) != @round_identifier
|
|
185
|
+
end
|
|
186
|
+
if existing_entry
|
|
187
|
+
existing_entry[:last_seen_round] = @round_identifier
|
|
188
|
+
return
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
state_entries << build_entry(
|
|
192
|
+
document: target_document,
|
|
193
|
+
key: key,
|
|
194
|
+
metadata: metadata,
|
|
195
|
+
count: 1
|
|
196
|
+
)
|
|
197
|
+
return
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
existing_entry = state_entries.find do |entry|
|
|
201
|
+
entry.fetch(:document) == target_document
|
|
202
|
+
end
|
|
203
|
+
if existing_entry
|
|
204
|
+
return if @configuration.multiple_settings.drop?
|
|
205
|
+
|
|
206
|
+
existing_entry[:count] += normalise_count(count)
|
|
207
|
+
existing_entry[:metadata] = merge_metadata_under(
|
|
208
|
+
existing_metadata: existing_entry.fetch(:metadata),
|
|
209
|
+
incoming_metadata: metadata
|
|
210
|
+
)
|
|
211
|
+
return
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
state_entries << build_entry(
|
|
215
|
+
document: target_document,
|
|
216
|
+
key: key,
|
|
217
|
+
metadata: metadata,
|
|
218
|
+
count: @configuration.multiple_settings.count? ? normalise_count(count) : 1
|
|
219
|
+
)
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
# Removes one directional persisted entry.
|
|
223
|
+
def clear_directional_link(document:, to_collection:, target_document:)
|
|
224
|
+
state_entries = @entries_by_state[[document.object_id, to_collection]]
|
|
225
|
+
state_entries.reject! do |entry|
|
|
226
|
+
entry.fetch(:document) == target_document
|
|
227
|
+
end
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
# Removes every directional persisted entry for one pair.
|
|
231
|
+
def clear_directional_state(document:, to_collection:)
|
|
232
|
+
@entries_by_state.delete([document.object_id, to_collection])
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
# Builds one new persisted entry record.
|
|
236
|
+
def build_entry(document:, key:, metadata:, count:)
|
|
237
|
+
entry = {
|
|
238
|
+
document: document,
|
|
239
|
+
key: key,
|
|
240
|
+
metadata: normalise_metadata(metadata),
|
|
241
|
+
count: count,
|
|
242
|
+
position_ratio: nil,
|
|
243
|
+
order: @next_order,
|
|
244
|
+
last_seen_round: @round_identifier
|
|
245
|
+
}
|
|
246
|
+
@next_order += 1
|
|
247
|
+
entry
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
# Removes dead-source or dead-target entries before reuse.
|
|
251
|
+
def cleanup_dead_entries!(state:, state_entries:)
|
|
252
|
+
state_entries.reject! do |entry|
|
|
253
|
+
!state.active_document?(state.document) || !state.active_document?(entry.fetch(:document))
|
|
254
|
+
end
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
# Returns the subset of persisted entries that are still missing this round.
|
|
258
|
+
def missing_entries_for(state_entries:, current_entries:)
|
|
259
|
+
if @configuration.multiple_settings.keep?
|
|
260
|
+
return missing_keep_entries(state_entries: state_entries, current_entries: current_entries)
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
current_entries_by_document = current_entries.each_with_object({}) do |entry, grouped_entries|
|
|
264
|
+
grouped_entries[entry.fetch(:document).object_id] = entry
|
|
265
|
+
end
|
|
266
|
+
state_entries.each_with_object([]) do |entry, missing_entries|
|
|
267
|
+
current_entry = current_entries_by_document[entry.fetch(:document).object_id]
|
|
268
|
+
if current_entry.nil?
|
|
269
|
+
missing_entries << entry
|
|
270
|
+
next
|
|
271
|
+
end
|
|
272
|
+
next unless @configuration.multiple_settings.count?
|
|
273
|
+
next unless current_entry.fetch(:count) < entry.fetch(:count)
|
|
274
|
+
|
|
275
|
+
missing_entries << entry.merge(
|
|
276
|
+
count: entry.fetch(:count) - current_entry.fetch(:count)
|
|
277
|
+
)
|
|
278
|
+
end
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
# Returns the missing persisted occurrences for keep mode.
|
|
282
|
+
def missing_keep_entries(state_entries:, current_entries:)
|
|
283
|
+
current_counts_by_signature = current_entries.each_with_object(Hash.new(0)) do |entry, counts|
|
|
284
|
+
counts[entry_signature(entry)] += 1
|
|
285
|
+
end
|
|
286
|
+
state_entries.each_with_object([]) do |entry, missing_entries|
|
|
287
|
+
signature = entry_signature(entry)
|
|
288
|
+
if current_counts_by_signature[signature].positive?
|
|
289
|
+
current_counts_by_signature[signature] -= 1
|
|
290
|
+
next
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
missing_entries << entry
|
|
294
|
+
end
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
# Updates keep-mode persisted positions by consuming current occurrences in order.
|
|
298
|
+
def update_keep_positions!(state_entries:, current_entries:)
|
|
299
|
+
current_indexes_by_signature = current_entries.each_with_index.each_with_object(Hash.new { |hash, key| hash[key] = [] }) do |(entry, index), indexes|
|
|
300
|
+
indexes[entry_signature(entry)] << index
|
|
301
|
+
end
|
|
302
|
+
state_entries.each do |entry|
|
|
303
|
+
indexes = current_indexes_by_signature[entry_signature(entry)]
|
|
304
|
+
next if indexes.empty?
|
|
305
|
+
|
|
306
|
+
entry[:position_ratio] = position_ratio(index: indexes.shift, length: current_entries.length)
|
|
307
|
+
end
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
# Builds one stable signature for keep-mode matching.
|
|
311
|
+
def entry_signature(entry)
|
|
312
|
+
signature_for(
|
|
313
|
+
document: entry.fetch(:document),
|
|
314
|
+
metadata: entry.fetch(:metadata)
|
|
315
|
+
)
|
|
316
|
+
end
|
|
317
|
+
|
|
318
|
+
# Builds one stable signature for one persisted keep-mode occurrence.
|
|
319
|
+
def signature_for(document:, metadata:)
|
|
320
|
+
[
|
|
321
|
+
document.object_id,
|
|
322
|
+
JSON.generate(normalise_metadata(metadata))
|
|
323
|
+
]
|
|
324
|
+
end
|
|
325
|
+
|
|
326
|
+
# Converts one final list index into a proportional reinsertion hint.
|
|
327
|
+
def position_ratio(index:, length:)
|
|
328
|
+
return 1.0 if length <= 1
|
|
329
|
+
|
|
330
|
+
index.to_f / (length - 1)
|
|
331
|
+
end
|
|
332
|
+
|
|
333
|
+
# Merges later metadata under the first-seen persisted metadata.
|
|
334
|
+
def merge_metadata_under(existing_metadata:, incoming_metadata:)
|
|
335
|
+
Jekyll::Plugins::Relationships::Support.hash_deep_merge(
|
|
336
|
+
normalise_metadata(incoming_metadata),
|
|
337
|
+
normalise_metadata(existing_metadata)
|
|
338
|
+
)
|
|
339
|
+
end
|
|
340
|
+
|
|
341
|
+
# Normalises metadata into a string-keyed hash.
|
|
342
|
+
def normalise_metadata(metadata)
|
|
343
|
+
Jekyll::Plugins::Relationships::Support.stringify_hash(metadata)
|
|
344
|
+
end
|
|
345
|
+
|
|
346
|
+
# Validates and normalises an incoming counted multiplicity.
|
|
347
|
+
def normalise_count(count)
|
|
348
|
+
integer_count = if count.is_a?(Integer)
|
|
349
|
+
count
|
|
350
|
+
elsif count.is_a?(String) && count.strip.match?(/\A\d+\z/)
|
|
351
|
+
count.to_i
|
|
352
|
+
else
|
|
353
|
+
raise ResolutionError, "Relationship count `#{count.inspect}` must be a positive integer."
|
|
354
|
+
end
|
|
355
|
+
raise ResolutionError, "Relationship count `#{count.inspect}` must be a positive integer." if integer_count < 1
|
|
356
|
+
|
|
357
|
+
integer_count
|
|
358
|
+
end
|
|
359
|
+
end
|
|
360
|
+
end
|
|
361
|
+
|
|
362
|
+
end
|
|
363
|
+
|
|
364
|
+
end
|
|
365
|
+
end
|
|
@@ -14,94 +14,50 @@ class Engine
|
|
|
14
14
|
class RawPathState
|
|
15
15
|
# Caches the parsed contents of one concrete leaf under a raw path.
|
|
16
16
|
#
|
|
17
|
-
# Each location keeps its own
|
|
18
|
-
#
|
|
17
|
+
# Each location keeps its own parsed entries so repeated resolution can
|
|
18
|
+
# reuse them without reparsing frontmatter.
|
|
19
19
|
class LocationState
|
|
20
20
|
# Builds one concrete-location cache.
|
|
21
|
-
def initialize(match:, string_array:, reference_template:,
|
|
22
|
-
@match = match
|
|
21
|
+
def initialize(match:, string_array:, reference_template:, reference_context: nil)
|
|
23
22
|
@string_array = string_array
|
|
24
23
|
@reference_template = reference_template
|
|
25
|
-
@
|
|
26
|
-
@
|
|
27
|
-
@entries = parse_entries(@match.value)
|
|
24
|
+
@reference_context = reference_context
|
|
25
|
+
@entries = parse_entries(match.value)
|
|
28
26
|
@resolution_cache = {}
|
|
29
|
-
@upgraded_value_cache = {}
|
|
30
27
|
end
|
|
31
28
|
|
|
32
|
-
# Resolves every parsed entry for one
|
|
33
|
-
def resolved_entries_for(primary_path:, &resolver)
|
|
34
|
-
signature =
|
|
29
|
+
# Resolves every parsed entry for one complete identity scheme.
|
|
30
|
+
def resolved_entries_for(primary_path:, scope_fields:, &resolver)
|
|
31
|
+
signature = identity_signature(primary_path: primary_path, scope_fields: scope_fields)
|
|
35
32
|
@resolution_cache[signature] ||= @entries.map do |entry|
|
|
36
33
|
resolver.call(entry)
|
|
37
34
|
end
|
|
38
35
|
end
|
|
39
36
|
|
|
40
|
-
# Builds the upgraded value for this exact location.
|
|
41
|
-
def upgraded_value(primary_path:, &resolver)
|
|
42
|
-
signature = primary_signature(primary_path)
|
|
43
|
-
@upgraded_value_cache[signature] ||= begin
|
|
44
|
-
accumulator = build_accumulator
|
|
45
|
-
|
|
46
|
-
resolved_entries_for(primary_path: primary_path, &resolver).each do |resolved_entry|
|
|
47
|
-
next unless resolved_entry
|
|
48
|
-
|
|
49
|
-
accumulator.add(
|
|
50
|
-
document: resolved_entry.fetch(:document),
|
|
51
|
-
key: resolved_entry.fetch(:key),
|
|
52
|
-
metadata: resolved_entry.fetch(:metadata),
|
|
53
|
-
count: resolved_entry.fetch(:count)
|
|
54
|
-
)
|
|
55
|
-
end
|
|
56
|
-
values = accumulator.references
|
|
57
|
-
|
|
58
|
-
(@shape == :array || values.length != 1) ? values : values.first
|
|
59
|
-
end
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
# Writes the upgraded value back onto the exact matched location.
|
|
63
|
-
def write_upgraded_value(primary_path:, &resolver)
|
|
64
|
-
@match.write(upgraded_value(primary_path: primary_path, &resolver))
|
|
65
|
-
end
|
|
66
|
-
|
|
67
37
|
private
|
|
68
38
|
|
|
69
39
|
# Parses one concrete raw value into individual reference entries.
|
|
70
40
|
def parse_entries(raw_value)
|
|
71
41
|
@string_array.interpret(raw_value, split: -1, flatten: true).map do |entry|
|
|
72
|
-
@reference_template.parse(entry)
|
|
42
|
+
@reference_template.parse(entry, context: @reference_context)
|
|
73
43
|
end.compact
|
|
74
44
|
end
|
|
75
45
|
|
|
76
|
-
#
|
|
77
|
-
def
|
|
78
|
-
|
|
79
|
-
reference_template: @reference_template,
|
|
80
|
-
multiple_settings: @multiple_settings
|
|
81
|
-
)
|
|
82
|
-
end
|
|
83
|
-
|
|
84
|
-
# Normalises the cache key for one primary-key scheme.
|
|
85
|
-
def primary_signature(primary_path)
|
|
86
|
-
primary_path.nil? ? '__relative_path__' : primary_path
|
|
87
|
-
end
|
|
88
|
-
|
|
89
|
-
# Tracks the original top-level shape of this concrete raw value.
|
|
90
|
-
def infer_shape(raw_value)
|
|
91
|
-
raw_value.is_a?(Array) ? :array : :single
|
|
46
|
+
# Normalises the cache key for one primary-key and scope scheme.
|
|
47
|
+
def identity_signature(primary_path:, scope_fields:)
|
|
48
|
+
[primary_path.nil? ? '__relative_path__' : primary_path, scope_fields.map(&:signature)]
|
|
92
49
|
end
|
|
93
50
|
end
|
|
94
51
|
|
|
95
52
|
attr_reader :document, :path, :raw_value
|
|
96
53
|
|
|
97
54
|
# Builds one raw-path cache for one document and path.
|
|
98
|
-
def initialize(document:, path:, data_path:, string_array:, reference_template:,
|
|
55
|
+
def initialize(document:, path:, data_path:, string_array:, reference_template:, reference_context: nil)
|
|
99
56
|
@document = document
|
|
100
57
|
@path = path
|
|
101
58
|
@data_path = data_path
|
|
102
59
|
@string_array = string_array
|
|
103
60
|
@reference_template = reference_template
|
|
104
|
-
@multiple_settings = multiple_settings
|
|
105
61
|
@read_result = @data_path.read_result(@document.data, @path)
|
|
106
62
|
@raw_value = @read_result.value
|
|
107
63
|
@present = @read_result.present?
|
|
@@ -110,10 +66,9 @@ class Engine
|
|
|
110
66
|
match: match,
|
|
111
67
|
string_array: @string_array,
|
|
112
68
|
reference_template: @reference_template,
|
|
113
|
-
|
|
69
|
+
reference_context: reference_context
|
|
114
70
|
)
|
|
115
71
|
end
|
|
116
|
-
@preferred_primary_path = nil
|
|
117
72
|
end
|
|
118
73
|
|
|
119
74
|
# Returns true when the source path existed in frontmatter.
|
|
@@ -126,46 +81,16 @@ class Engine
|
|
|
126
81
|
@read_result.array_traversed?
|
|
127
82
|
end
|
|
128
83
|
|
|
129
|
-
# Resolves every parsed entry for one
|
|
130
|
-
def resolved_entries_for(primary_path:, registry:, active_document_checker: nil)
|
|
131
|
-
@preferred_primary_path ||= primary_path
|
|
84
|
+
# Resolves every parsed entry for one complete identity scheme.
|
|
85
|
+
def resolved_entries_for(primary_path:, scope_fields: [], registry:, relationship: nil, active_document_checker: nil)
|
|
132
86
|
@location_states.flat_map do |location_state|
|
|
133
|
-
location_state.resolved_entries_for(primary_path: primary_path) do |entry|
|
|
87
|
+
location_state.resolved_entries_for(primary_path: primary_path, scope_fields: scope_fields) do |entry|
|
|
134
88
|
resolve_entry(
|
|
135
89
|
entry: entry,
|
|
136
90
|
primary_path: primary_path,
|
|
91
|
+
scope_fields: scope_fields,
|
|
137
92
|
registry: registry,
|
|
138
|
-
|
|
139
|
-
)
|
|
140
|
-
end
|
|
141
|
-
end
|
|
142
|
-
end
|
|
143
|
-
|
|
144
|
-
# Builds the upgraded path value while preserving its concrete locations.
|
|
145
|
-
def upgraded_raw_value(registry:, active_document_checker: nil)
|
|
146
|
-
return nil unless present?
|
|
147
|
-
|
|
148
|
-
upgraded_values = upgraded_location_values(
|
|
149
|
-
registry: registry,
|
|
150
|
-
active_document_checker: active_document_checker
|
|
151
|
-
)
|
|
152
|
-
return upgraded_values.first if upgraded_values.length == 1
|
|
153
|
-
|
|
154
|
-
upgraded_values
|
|
155
|
-
end
|
|
156
|
-
|
|
157
|
-
# Writes upgraded values back onto the exact matched locations.
|
|
158
|
-
def write_upgraded_input!(registry:, active_document_checker: nil)
|
|
159
|
-
upgraded_location_values(
|
|
160
|
-
registry: registry,
|
|
161
|
-
active_document_checker: active_document_checker
|
|
162
|
-
)
|
|
163
|
-
@location_states.each do |location_state|
|
|
164
|
-
location_state.write_upgraded_value(primary_path: @preferred_primary_path) do |entry|
|
|
165
|
-
resolve_entry(
|
|
166
|
-
entry: entry,
|
|
167
|
-
primary_path: @preferred_primary_path,
|
|
168
|
-
registry: registry,
|
|
93
|
+
relationship: relationship,
|
|
169
94
|
active_document_checker: active_document_checker
|
|
170
95
|
)
|
|
171
96
|
end
|
|
@@ -175,39 +100,44 @@ class Engine
|
|
|
175
100
|
private
|
|
176
101
|
|
|
177
102
|
# Resolves one parsed entry to a document and canonical key.
|
|
178
|
-
def resolve_entry(entry:, primary_path:, registry:, active_document_checker:)
|
|
103
|
+
def resolve_entry(entry:, primary_path:, scope_fields:, registry:, relationship:, active_document_checker:)
|
|
104
|
+
effective_scope = registry.effective_scope_for(
|
|
105
|
+
reference_scope: entry.scope,
|
|
106
|
+
referring_document: @document,
|
|
107
|
+
scope_fields: scope_fields,
|
|
108
|
+
relationship: relationship
|
|
109
|
+
)
|
|
179
110
|
document = if entry.document?
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
111
|
+
registry.validate_scope_match!(
|
|
112
|
+
document: entry.page,
|
|
113
|
+
scope: effective_scope,
|
|
114
|
+
scope_fields: scope_fields,
|
|
115
|
+
relationship: relationship,
|
|
116
|
+
referring_document: @document
|
|
117
|
+
)
|
|
118
|
+
entry.page
|
|
119
|
+
else
|
|
120
|
+
registry.lookup(
|
|
121
|
+
key: entry.key,
|
|
122
|
+
primary_path: primary_path,
|
|
123
|
+
collection: entry.collection.nil? ? nil : entry.collection.to_s,
|
|
124
|
+
scope_fields: scope_fields,
|
|
125
|
+
scope: effective_scope,
|
|
126
|
+
relationship: relationship,
|
|
127
|
+
referring_document: @document
|
|
128
|
+
)
|
|
129
|
+
end
|
|
188
130
|
return nil if active_document_checker && !active_document_checker.call(document)
|
|
189
131
|
|
|
190
132
|
{
|
|
191
133
|
document: document,
|
|
192
134
|
key: registry.key_for(document, primary_path: primary_path),
|
|
135
|
+
scope: effective_scope,
|
|
193
136
|
metadata: entry.metadata,
|
|
194
137
|
count: entry.count
|
|
195
138
|
}
|
|
196
139
|
end
|
|
197
140
|
|
|
198
|
-
# Builds upgraded values for every concrete location under this raw path.
|
|
199
|
-
def upgraded_location_values(registry:, active_document_checker:)
|
|
200
|
-
@location_states.map do |location_state|
|
|
201
|
-
location_state.upgraded_value(primary_path: @preferred_primary_path) do |entry|
|
|
202
|
-
resolve_entry(
|
|
203
|
-
entry: entry,
|
|
204
|
-
primary_path: @preferred_primary_path,
|
|
205
|
-
registry: registry,
|
|
206
|
-
active_document_checker: active_document_checker
|
|
207
|
-
)
|
|
208
|
-
end
|
|
209
|
-
end
|
|
210
|
-
end
|
|
211
141
|
end
|
|
212
142
|
end
|
|
213
143
|
|