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
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require 'jekyll-relationships/trees/edge_builder'
|
|
4
|
+
require 'jekyll-relationships/trees/root_distances'
|
|
4
5
|
|
|
5
6
|
module Jekyll
|
|
6
7
|
module Plugins
|
|
@@ -27,12 +28,16 @@ class Graph
|
|
|
27
28
|
@child_edge_entries = Hash.new { |hash, key| hash[key] = [] }
|
|
28
29
|
@ancestor_cache = {}
|
|
29
30
|
@descendant_cache = {}
|
|
31
|
+
@root_distance_cache = nil
|
|
30
32
|
@primary_path_by_collection = {}
|
|
33
|
+
@scope_fields_by_collection = {}
|
|
31
34
|
@tree_collections = @configuration.tree_relationships.each_with_object(Set.new) do |definition, collections|
|
|
32
35
|
collections << definition.from_collection
|
|
33
36
|
collections << definition.to_collection
|
|
34
37
|
@primary_path_by_collection[definition.from_collection] ||= definition.primary_path
|
|
35
38
|
@primary_path_by_collection[definition.to_collection] ||= definition.primary_path
|
|
39
|
+
@scope_fields_by_collection[definition.from_collection] ||= definition.scope_fields
|
|
40
|
+
@scope_fields_by_collection[definition.to_collection] ||= definition.scope_fields
|
|
36
41
|
end
|
|
37
42
|
@edge_builder = EdgeBuilder.new(
|
|
38
43
|
graph: self,
|
|
@@ -48,6 +53,35 @@ class Graph
|
|
|
48
53
|
@edge_builder.build!
|
|
49
54
|
end
|
|
50
55
|
|
|
56
|
+
# Returns one deep copy of the current tree graph state.
|
|
57
|
+
def deep_dup
|
|
58
|
+
duplicated_graph = self.class.new(
|
|
59
|
+
site: @site,
|
|
60
|
+
configuration: @configuration,
|
|
61
|
+
registry: @registry,
|
|
62
|
+
data_path: @data_path,
|
|
63
|
+
debug_logger: @debug_logger,
|
|
64
|
+
active_document_ids: @active_document_ids.nil? ? nil : @active_document_ids.keys
|
|
65
|
+
)
|
|
66
|
+
duplicated_graph.instance_variable_set(
|
|
67
|
+
:@parents,
|
|
68
|
+
duplicate_document_adjacency(@parents)
|
|
69
|
+
)
|
|
70
|
+
duplicated_graph.instance_variable_set(
|
|
71
|
+
:@children,
|
|
72
|
+
duplicate_document_adjacency(@children)
|
|
73
|
+
)
|
|
74
|
+
duplicated_graph.instance_variable_set(
|
|
75
|
+
:@parent_edge_entries,
|
|
76
|
+
duplicate_edge_entries(@parent_edge_entries)
|
|
77
|
+
)
|
|
78
|
+
duplicated_graph.instance_variable_set(
|
|
79
|
+
:@child_edge_entries,
|
|
80
|
+
duplicate_edge_entries(@child_edge_entries)
|
|
81
|
+
)
|
|
82
|
+
duplicated_graph
|
|
83
|
+
end
|
|
84
|
+
|
|
51
85
|
# Returns true when one document is part of any tree relationship.
|
|
52
86
|
def participating?(document)
|
|
53
87
|
@tree_collections.include?(document.collection.label)
|
|
@@ -68,19 +102,21 @@ class Graph
|
|
|
68
102
|
end
|
|
69
103
|
|
|
70
104
|
# Returns immediate parents as canonical reference hashes.
|
|
71
|
-
def parents_for(document, primary_path: nil)
|
|
105
|
+
def parents_for(document, primary_path: nil, scope_fields: nil)
|
|
72
106
|
return [] unless active_document?(document)
|
|
73
107
|
|
|
74
108
|
resolved_primary_path = resolve_primary_path(document: document, primary_path: primary_path)
|
|
75
|
-
|
|
109
|
+
resolved_scope_fields = resolve_scope_fields(document: document, scope_fields: scope_fields)
|
|
110
|
+
@parents[document].map { |parent| build_reference(parent, primary_path: resolved_primary_path, scope_fields: resolved_scope_fields) }
|
|
76
111
|
end
|
|
77
112
|
|
|
78
113
|
# Returns immediate children as canonical reference hashes.
|
|
79
|
-
def children_for(document, primary_path: nil)
|
|
114
|
+
def children_for(document, primary_path: nil, scope_fields: nil)
|
|
80
115
|
return [] unless active_document?(document)
|
|
81
116
|
|
|
82
117
|
resolved_primary_path = resolve_primary_path(document: document, primary_path: primary_path)
|
|
83
|
-
|
|
118
|
+
resolved_scope_fields = resolve_scope_fields(document: document, scope_fields: scope_fields)
|
|
119
|
+
@children[document].map { |child| build_reference(child, primary_path: resolved_primary_path, scope_fields: resolved_scope_fields) }
|
|
84
120
|
end
|
|
85
121
|
|
|
86
122
|
# Returns the immediate parent documents in deterministic insertion order.
|
|
@@ -112,31 +148,42 @@ class Graph
|
|
|
112
148
|
end
|
|
113
149
|
|
|
114
150
|
# Returns ancestor references filtered by minimum and maximum distance.
|
|
115
|
-
def ancestors_for(document, primary_path: nil, min: 0, max: -1)
|
|
151
|
+
def ancestors_for(document, primary_path: nil, scope_fields: nil, min: 0, max: -1)
|
|
116
152
|
return [] unless active_document?(document)
|
|
117
153
|
|
|
118
154
|
resolved_primary_path = resolve_primary_path(document: document, primary_path: primary_path)
|
|
155
|
+
resolved_scope_fields = resolve_scope_fields(document: document, scope_fields: scope_fields)
|
|
119
156
|
filter_distances(
|
|
120
157
|
distance_map_for(document: document, direction: :up),
|
|
121
158
|
primary_path: resolved_primary_path,
|
|
159
|
+
scope_fields: resolved_scope_fields,
|
|
122
160
|
min: min,
|
|
123
161
|
max: max
|
|
124
162
|
)
|
|
125
163
|
end
|
|
126
164
|
|
|
127
165
|
# Returns descendant references filtered by minimum and maximum distance.
|
|
128
|
-
def descendants_for(document, primary_path: nil, min: 0, max: -1)
|
|
166
|
+
def descendants_for(document, primary_path: nil, scope_fields: nil, min: 0, max: -1)
|
|
129
167
|
return [] unless active_document?(document)
|
|
130
168
|
|
|
131
169
|
resolved_primary_path = resolve_primary_path(document: document, primary_path: primary_path)
|
|
170
|
+
resolved_scope_fields = resolve_scope_fields(document: document, scope_fields: scope_fields)
|
|
132
171
|
filter_distances(
|
|
133
172
|
distance_map_for(document: document, direction: :down),
|
|
134
173
|
primary_path: resolved_primary_path,
|
|
174
|
+
scope_fields: resolved_scope_fields,
|
|
135
175
|
min: min,
|
|
136
176
|
max: max
|
|
137
177
|
)
|
|
138
178
|
end
|
|
139
179
|
|
|
180
|
+
# Returns the shortest number of edges from the document to any root.
|
|
181
|
+
def root_distance_for(document)
|
|
182
|
+
return nil unless active_document?(document)
|
|
183
|
+
|
|
184
|
+
root_distances_for_graph[document.object_id]
|
|
185
|
+
end
|
|
186
|
+
|
|
140
187
|
# Adds one parent-child edge unless it would break tree guarantees.
|
|
141
188
|
def add_edge(parent_document:, child_document:, tree_settings:, definition:, source_description:)
|
|
142
189
|
return unless active_document?(parent_document) && active_document?(child_document)
|
|
@@ -220,7 +267,7 @@ class Graph
|
|
|
220
267
|
tree_settings: tree_settings,
|
|
221
268
|
source_description: source_description
|
|
222
269
|
)
|
|
223
|
-
|
|
270
|
+
clear_traversal_cache
|
|
224
271
|
debug_tree_event(
|
|
225
272
|
document: child_document,
|
|
226
273
|
definition: definition,
|
|
@@ -240,6 +287,32 @@ class Graph
|
|
|
240
287
|
end
|
|
241
288
|
end
|
|
242
289
|
|
|
290
|
+
# Removes one document from the graph along with every adjacent edge.
|
|
291
|
+
def remove_document!(document)
|
|
292
|
+
return false unless active_document?(document)
|
|
293
|
+
|
|
294
|
+
@active_document_ids.delete(document.object_id) unless @active_document_ids.nil?
|
|
295
|
+
@children[document].dup.each do |child_document|
|
|
296
|
+
remove_parent_edge(parent_document: document, child_document: child_document)
|
|
297
|
+
end
|
|
298
|
+
@parents[document].dup.each do |parent_document|
|
|
299
|
+
remove_parent_edge(parent_document: parent_document, child_document: document)
|
|
300
|
+
end
|
|
301
|
+
@children.delete(document)
|
|
302
|
+
@parents.delete(document)
|
|
303
|
+
@child_edge_entries.delete(document)
|
|
304
|
+
@parent_edge_entries.delete(document)
|
|
305
|
+
clear_traversal_cache
|
|
306
|
+
true
|
|
307
|
+
end
|
|
308
|
+
|
|
309
|
+
# Removes many documents from the graph.
|
|
310
|
+
def remove_documents!(documents)
|
|
311
|
+
Array(documents).each do |document|
|
|
312
|
+
remove_document!(document)
|
|
313
|
+
end
|
|
314
|
+
end
|
|
315
|
+
|
|
243
316
|
# Returns every document participating in tree relationships.
|
|
244
317
|
def tree_documents
|
|
245
318
|
@tree_collections.to_a.flat_map { |collection| documents_for(collection) }.uniq
|
|
@@ -252,10 +325,11 @@ class Graph
|
|
|
252
325
|
frontmatter = definition.tree_settings.frontmatter
|
|
253
326
|
|
|
254
327
|
definition_documents(definition).each do |document|
|
|
255
|
-
parent_values = parents_for(document, primary_path: definition.primary_path)
|
|
256
|
-
child_values = children_for(document, primary_path: definition.primary_path)
|
|
257
|
-
ancestor_values = ancestors_for(document, primary_path: definition.primary_path, min: 0)
|
|
258
|
-
descendant_values = descendants_for(document, primary_path: definition.primary_path, min: 0)
|
|
328
|
+
parent_values = parents_for(document, primary_path: definition.primary_path, scope_fields: definition.scope_fields)
|
|
329
|
+
child_values = children_for(document, primary_path: definition.primary_path, scope_fields: definition.scope_fields)
|
|
330
|
+
ancestor_values = ancestors_for(document, primary_path: definition.primary_path, scope_fields: definition.scope_fields, min: 0)
|
|
331
|
+
descendant_values = descendants_for(document, primary_path: definition.primary_path, scope_fields: definition.scope_fields, min: 0)
|
|
332
|
+
depth_value = root_distance_for(document)
|
|
259
333
|
|
|
260
334
|
if frontmatter.output_path
|
|
261
335
|
@data_path.write(
|
|
@@ -268,6 +342,7 @@ class Graph
|
|
|
268
342
|
children_value: child_values,
|
|
269
343
|
ancestors_value: ancestor_values,
|
|
270
344
|
descendants_value: descendant_values,
|
|
345
|
+
depth_value: depth_value,
|
|
271
346
|
max_parents: definition.tree_settings.max_parents,
|
|
272
347
|
max_children: definition.tree_settings.max_children
|
|
273
348
|
)
|
|
@@ -300,6 +375,7 @@ class Graph
|
|
|
300
375
|
)
|
|
301
376
|
@data_path.write(document.data, frontmatter.ancestors_output_path, ancestor_values)
|
|
302
377
|
@data_path.write(document.data, frontmatter.descendants_output_path, descendant_values)
|
|
378
|
+
@data_path.write(document.data, frontmatter.depth_output_path, depth_value)
|
|
303
379
|
debug_tree_event(
|
|
304
380
|
document: document,
|
|
305
381
|
definition: definition,
|
|
@@ -312,7 +388,9 @@ class Graph
|
|
|
312
388
|
ancestors_path: frontmatter.ancestors_output_path,
|
|
313
389
|
ancestors_value: ancestor_values,
|
|
314
390
|
descendants_path: frontmatter.descendants_output_path,
|
|
315
|
-
descendants_value: descendant_values
|
|
391
|
+
descendants_value: descendant_values,
|
|
392
|
+
depth_path: frontmatter.depth_output_path,
|
|
393
|
+
depth_value: depth_value
|
|
316
394
|
}
|
|
317
395
|
)
|
|
318
396
|
end
|
|
@@ -333,11 +411,30 @@ class Graph
|
|
|
333
411
|
@primary_path_by_collection[document.collection.label]
|
|
334
412
|
end
|
|
335
413
|
|
|
414
|
+
# Chooses explicit scope fields, or the collection's first tree scope scheme for public helper calls.
|
|
415
|
+
def resolve_scope_fields(document:, scope_fields:)
|
|
416
|
+
return scope_fields unless scope_fields.nil?
|
|
417
|
+
|
|
418
|
+
@scope_fields_by_collection[document.collection.label] || []
|
|
419
|
+
end
|
|
420
|
+
|
|
336
421
|
# Returns true when one edge already exists.
|
|
337
422
|
def edge_exists?(parent_document:, child_document:)
|
|
338
423
|
@parents[child_document].include?(parent_document)
|
|
339
424
|
end
|
|
340
425
|
|
|
426
|
+
# Removes one stored parent-child edge and its metadata in both directions.
|
|
427
|
+
def remove_parent_edge(parent_document:, child_document:)
|
|
428
|
+
@parents[child_document].delete(parent_document)
|
|
429
|
+
@children[parent_document].delete(child_document)
|
|
430
|
+
@parent_edge_entries[child_document].reject! do |entry|
|
|
431
|
+
entry.fetch(:document) == parent_document
|
|
432
|
+
end
|
|
433
|
+
@child_edge_entries[parent_document].reject! do |entry|
|
|
434
|
+
entry.fetch(:document) == child_document
|
|
435
|
+
end
|
|
436
|
+
end
|
|
437
|
+
|
|
341
438
|
# Returns true when one collection has already reached its configured cap.
|
|
342
439
|
def maximum_reached?(maximum:, items:)
|
|
343
440
|
maximum != -1 && items.length >= maximum
|
|
@@ -384,21 +481,27 @@ class Graph
|
|
|
384
481
|
cache[document] = ordered
|
|
385
482
|
end
|
|
386
483
|
|
|
484
|
+
# Computes one shortest-root-distance map for the current graph.
|
|
485
|
+
def root_distances_for_graph
|
|
486
|
+
@root_distance_cache ||= Trees::RootDistances.for(graph: self)
|
|
487
|
+
end
|
|
488
|
+
|
|
387
489
|
# Filters one distance map to the requested range and builds references.
|
|
388
|
-
def filter_distances(distance_map, primary_path:, min:, max:)
|
|
490
|
+
def filter_distances(distance_map, primary_path:, scope_fields:, min:, max:)
|
|
389
491
|
distance_map.each_with_object([]) do |(document, distance), references|
|
|
390
492
|
next if distance < min
|
|
391
493
|
next if max != -1 && distance > max
|
|
392
494
|
|
|
393
|
-
references << build_reference(document, primary_path: primary_path, distance: distance)
|
|
495
|
+
references << build_reference(document, primary_path: primary_path, scope_fields: scope_fields, distance: distance)
|
|
394
496
|
end
|
|
395
497
|
end
|
|
396
498
|
|
|
397
499
|
# Builds one canonical reference hash, optionally including distance.
|
|
398
|
-
def build_reference(document, primary_path:, distance: nil)
|
|
500
|
+
def build_reference(document, primary_path:, scope_fields:, distance: nil)
|
|
399
501
|
reference = @configuration.reference_template.build(
|
|
400
502
|
document: document,
|
|
401
503
|
key: @registry.key_for(document, primary_path: primary_path),
|
|
504
|
+
scope: @registry.scope_for(document, scope_fields: scope_fields),
|
|
402
505
|
include_count: false
|
|
403
506
|
)
|
|
404
507
|
reference['distance'] = distance unless distance.nil?
|
|
@@ -414,10 +517,11 @@ class Graph
|
|
|
414
517
|
end
|
|
415
518
|
end
|
|
416
519
|
|
|
417
|
-
# Clears cached
|
|
418
|
-
def
|
|
520
|
+
# Clears cached graph traversals after the tree changes.
|
|
521
|
+
def clear_traversal_cache
|
|
419
522
|
@ancestor_cache.clear
|
|
420
523
|
@descendant_cache.clear
|
|
524
|
+
@root_distance_cache = nil
|
|
421
525
|
end
|
|
422
526
|
|
|
423
527
|
# Captures the metadata needed to understand one stored edge later on.
|
|
@@ -445,6 +549,20 @@ class Graph
|
|
|
445
549
|
details: details
|
|
446
550
|
)
|
|
447
551
|
end
|
|
552
|
+
|
|
553
|
+
# Duplicates one adjacency hash keyed by document.
|
|
554
|
+
def duplicate_document_adjacency(adjacency)
|
|
555
|
+
adjacency.each_with_object(Hash.new { |hash, key| hash[key] = [] }) do |(document, neighbours), duplicated_adjacency|
|
|
556
|
+
duplicated_adjacency[document] = neighbours.dup
|
|
557
|
+
end
|
|
558
|
+
end
|
|
559
|
+
|
|
560
|
+
# Duplicates one edge-entry hash keyed by document.
|
|
561
|
+
def duplicate_edge_entries(edge_entries)
|
|
562
|
+
edge_entries.each_with_object(Hash.new { |hash, key| hash[key] = [] }) do |(document, entries), duplicated_edge_entries|
|
|
563
|
+
duplicated_edge_entries[document] = entries.map(&:dup)
|
|
564
|
+
end
|
|
565
|
+
end
|
|
448
566
|
end
|
|
449
567
|
|
|
450
568
|
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Jekyll
|
|
4
|
+
module Plugins
|
|
5
|
+
|
|
6
|
+
module Relationships
|
|
7
|
+
module Trees
|
|
8
|
+
|
|
9
|
+
# Calculates the shortest distance from every tree node to any current root.
|
|
10
|
+
#
|
|
11
|
+
# The supplied graph-like object must expose `tree_documents`,
|
|
12
|
+
# `parent_documents_for`, and `child_documents_for`. Keeping this traversal in
|
|
13
|
+
# one helper ensures tree write-back and tree pruning interpret depth the same
|
|
14
|
+
# way even though they operate on different graph wrappers.
|
|
15
|
+
class RootDistances
|
|
16
|
+
# Returns one map of document object id to shortest root distance.
|
|
17
|
+
def self.for(graph:)
|
|
18
|
+
root_distances = {}
|
|
19
|
+
queue = graph.tree_documents.select do |document|
|
|
20
|
+
graph.parent_documents_for(document).empty?
|
|
21
|
+
end.sort_by(&:relative_path).map do |root_document|
|
|
22
|
+
[root_document, 0]
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
until queue.empty?
|
|
26
|
+
document, distance = queue.shift
|
|
27
|
+
existing_distance = root_distances[document.object_id]
|
|
28
|
+
next if !existing_distance.nil? && existing_distance <= distance
|
|
29
|
+
|
|
30
|
+
root_distances[document.object_id] = distance
|
|
31
|
+
graph.child_documents_for(document).each do |child_document|
|
|
32
|
+
queue << [child_document, distance + 1]
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
root_distances
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
end
|
|
44
|
+
end
|
data/lib/jekyll-relationships.rb
CHANGED
|
@@ -7,6 +7,7 @@ require 'jekyll-relationships/version'
|
|
|
7
7
|
require 'jekyll-relationships/errors'
|
|
8
8
|
require 'jekyll-relationships/support/string_array'
|
|
9
9
|
require 'jekyll-relationships/support/frontmatter_path'
|
|
10
|
+
require 'jekyll-relationships/support/frontmatter_matcher'
|
|
10
11
|
require 'jekyll-relationships/support/data_path'
|
|
11
12
|
require 'jekyll-relationships/support/hash_deep_merge'
|
|
12
13
|
require 'jekyll-relationships/support/placeholders'
|