jekyll-relationships 0.1.1.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/defaults.rb +1 -0
- 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.rb +43 -8
- data/lib/jekyll-relationships/definitions/normal_relationship.rb +8 -2
- data/lib/jekyll-relationships/definitions/prune_rule.rb +19 -3
- data/lib/jekyll-relationships/definitions/tree_relationship.rb +3 -2
- data/lib/jekyll-relationships/documents/registry.rb +182 -33
- data/lib/jekyll-relationships/engine/normal_seed.rb +9 -4
- data/lib/jekyll-relationships/engine/raw_path_state.rb +44 -22
- data/lib/jekyll-relationships/engine/relationship_state.rb +12 -0
- data/lib/jekyll-relationships/engine/session.rb +89 -15
- data/lib/jekyll-relationships/engine/write_back.rb +1 -0
- data/lib/jekyll-relationships/engine.rb +1 -1
- data/lib/jekyll-relationships/pruning/rule_pruner.rb +7 -7
- data/lib/jekyll-relationships/pruning/tree_phase.rb +29 -11
- data/lib/jekyll-relationships/pruning/tree_provenance.rb +1 -1
- data/lib/jekyll-relationships/references/accumulator.rb +18 -6
- data/lib/jekyll-relationships/references/template.rb +46 -11
- data/lib/jekyll-relationships/resolvers/base.rb +3 -0
- 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 +30 -13
- data/lib/jekyll-relationships/version.rb +1 -1
- data/lib/jekyll-relationships.rb +1 -0
- data/readme.md +154 -23
- metadata +3 -2
|
@@ -9,7 +9,7 @@ module References
|
|
|
9
9
|
# Parses loose reference values and builds canonical relationship hashes.
|
|
10
10
|
#
|
|
11
11
|
# The template is configured from `relationships.references` and controls which
|
|
12
|
-
# property names hold the key, collection, page, and optional count values in
|
|
12
|
+
# property names hold the key, collection, scope, page, and optional count values in
|
|
13
13
|
# output hashes.
|
|
14
14
|
class Template
|
|
15
15
|
|
|
@@ -18,12 +18,13 @@ class Template
|
|
|
18
18
|
# The `metadata` hash contains any non-reserved properties found on an
|
|
19
19
|
# existing reference hash.
|
|
20
20
|
class ParsedReference
|
|
21
|
-
attr_reader :key, :collection, :page, :count, :metadata, :original_value
|
|
21
|
+
attr_reader :key, :collection, :scope, :page, :count, :metadata, :original_value
|
|
22
22
|
|
|
23
23
|
# Captures the parsed reference fields in one immutable object.
|
|
24
|
-
def initialize(key:, collection:, page:, count:, metadata:, original_value:)
|
|
24
|
+
def initialize(key:, collection:, scope: nil, page:, count:, metadata:, original_value:)
|
|
25
25
|
@key = key
|
|
26
26
|
@collection = collection
|
|
27
|
+
@scope = scope
|
|
27
28
|
@page = page
|
|
28
29
|
@count = count
|
|
29
30
|
@metadata = metadata
|
|
@@ -36,7 +37,7 @@ class Template
|
|
|
36
37
|
end
|
|
37
38
|
end
|
|
38
39
|
|
|
39
|
-
attr_reader :key_property, :collection_property, :page_property, :count_property
|
|
40
|
+
attr_reader :key_property, :collection_property, :scope_property, :page_property, :count_property
|
|
40
41
|
|
|
41
42
|
# Builds the reference template from config and duplicate-handling settings.
|
|
42
43
|
def initialize(config:, count_enabled:)
|
|
@@ -44,6 +45,7 @@ class Template
|
|
|
44
45
|
@count_enabled = !!count_enabled
|
|
45
46
|
@key_property = nil
|
|
46
47
|
@collection_property = nil
|
|
48
|
+
@scope_property = nil
|
|
47
49
|
@page_property = nil
|
|
48
50
|
@count_property = nil
|
|
49
51
|
|
|
@@ -51,7 +53,7 @@ class Template
|
|
|
51
53
|
end
|
|
52
54
|
|
|
53
55
|
# Parses one raw reference value from frontmatter or resolver input.
|
|
54
|
-
def parse(value)
|
|
56
|
+
def parse(value, context: nil)
|
|
55
57
|
case value
|
|
56
58
|
when String
|
|
57
59
|
key = value.to_s.strip
|
|
@@ -60,32 +62,35 @@ class Template
|
|
|
60
62
|
ParsedReference.new(
|
|
61
63
|
key: key,
|
|
62
64
|
collection: nil,
|
|
65
|
+
scope: nil,
|
|
63
66
|
page: nil,
|
|
64
67
|
count: 1,
|
|
65
68
|
metadata: {},
|
|
66
69
|
original_value: value
|
|
67
70
|
)
|
|
68
71
|
when Hash
|
|
69
|
-
parse_hash_reference(value)
|
|
72
|
+
parse_hash_reference(value, context: context)
|
|
70
73
|
when Jekyll::Document
|
|
71
74
|
ParsedReference.new(
|
|
72
75
|
key: nil,
|
|
73
76
|
collection: nil,
|
|
77
|
+
scope: nil,
|
|
74
78
|
page: value,
|
|
75
79
|
count: 1,
|
|
76
80
|
metadata: {},
|
|
77
81
|
original_value: value
|
|
78
82
|
)
|
|
79
83
|
else
|
|
80
|
-
raise ConfigurationError, "
|
|
84
|
+
raise ConfigurationError, "#{context_prefix(context)}unsupported reference value `#{value.inspect}`."
|
|
81
85
|
end
|
|
82
86
|
end
|
|
83
87
|
|
|
84
88
|
# Builds one output reference hash for a resolved document.
|
|
85
|
-
def build(document:, key:, metadata: nil, count: 1, include_count: @count_enabled)
|
|
89
|
+
def build(document:, key:, scope: nil, metadata: nil, count: 1, include_count: @count_enabled)
|
|
86
90
|
hash = {}
|
|
87
91
|
hash[@key_property] = key
|
|
88
92
|
hash[@collection_property] = document.collection.label if @collection_property
|
|
93
|
+
hash[@scope_property] = stringify_hash(scope) if @scope_property && !scope.nil?
|
|
89
94
|
hash[@page_property] = document if @page_property
|
|
90
95
|
hash[@count_property] = normalise_count(count) if include_count && @count_property
|
|
91
96
|
|
|
@@ -98,7 +103,7 @@ class Template
|
|
|
98
103
|
|
|
99
104
|
# Returns the configured property names that are reserved for the engine.
|
|
100
105
|
def reserved_properties
|
|
101
|
-
[@key_property, @collection_property, @page_property, @count_property].compact
|
|
106
|
+
[@key_property, @collection_property, @scope_property, @page_property, @count_property].compact
|
|
102
107
|
end
|
|
103
108
|
|
|
104
109
|
# Returns the input properties that should never survive into free-form metadata.
|
|
@@ -121,6 +126,9 @@ class Template
|
|
|
121
126
|
when Jekyll::Plugins::Relationships::Support::Placeholders::COLLECTION
|
|
122
127
|
raise ConfigurationError, 'Reference config can only define one <collection> property.' if @collection_property
|
|
123
128
|
@collection_property = property
|
|
129
|
+
when Jekyll::Plugins::Relationships::Support::Placeholders::SCOPE
|
|
130
|
+
raise ConfigurationError, 'Reference config can only define one <scope> property.' if @scope_property
|
|
131
|
+
@scope_property = property
|
|
124
132
|
when Jekyll::Plugins::Relationships::Support::Placeholders::PAGE
|
|
125
133
|
raise ConfigurationError, 'Reference config can only define one <page> property.' if @page_property
|
|
126
134
|
@page_property = property
|
|
@@ -139,18 +147,20 @@ class Template
|
|
|
139
147
|
end
|
|
140
148
|
|
|
141
149
|
# Parses one hash reference according to the configured property names.
|
|
142
|
-
def parse_hash_reference(hash)
|
|
150
|
+
def parse_hash_reference(hash, context: nil)
|
|
143
151
|
key = fetch_hash_value(hash, @key_property)
|
|
144
152
|
collection = @collection_property ? fetch_hash_value(hash, @collection_property) : nil
|
|
153
|
+
scope = parse_scope(hash, context: context)
|
|
145
154
|
page = @page_property ? fetch_hash_value(hash, @page_property) : nil
|
|
146
155
|
|
|
147
156
|
if key.nil? && !page.is_a?(Jekyll::Document)
|
|
148
|
-
raise ResolutionError, "
|
|
157
|
+
raise ResolutionError, "#{context_prefix(context)}reference hash `#{hash.inspect}` does not include the configured key property `#{@key_property}`."
|
|
149
158
|
end
|
|
150
159
|
|
|
151
160
|
ParsedReference.new(
|
|
152
161
|
key: key,
|
|
153
162
|
collection: collection,
|
|
163
|
+
scope: scope,
|
|
154
164
|
page: page,
|
|
155
165
|
count: parsed_count(hash),
|
|
156
166
|
metadata: filter_metadata(hash),
|
|
@@ -158,6 +168,19 @@ class Template
|
|
|
158
168
|
)
|
|
159
169
|
end
|
|
160
170
|
|
|
171
|
+
# Parses the reserved scope property without interpreting literal dotted field names as nested paths.
|
|
172
|
+
def parse_scope(hash, context: nil)
|
|
173
|
+
return nil unless @scope_property
|
|
174
|
+
return nil unless hash_property?(hash, @scope_property)
|
|
175
|
+
|
|
176
|
+
raw_scope = fetch_hash_value(hash, @scope_property)
|
|
177
|
+
unless raw_scope.is_a?(Hash)
|
|
178
|
+
raise ResolutionError, "#{context_prefix(context)}reference scope property `#{@scope_property}` must be a hash, but was `#{raw_scope.inspect}`."
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
stringify_hash(raw_scope)
|
|
182
|
+
end
|
|
183
|
+
|
|
161
184
|
# Returns the parsed count value for one reference hash.
|
|
162
185
|
def parsed_count(hash)
|
|
163
186
|
return 1 unless @count_enabled
|
|
@@ -191,6 +214,18 @@ class Template
|
|
|
191
214
|
Jekyll::Plugins::Relationships::Support::FrontmatterPath.read_hash(hash, property)
|
|
192
215
|
end
|
|
193
216
|
|
|
217
|
+
# Returns true when a string- or symbol-keyed hash contains one literal property.
|
|
218
|
+
def hash_property?(hash, property)
|
|
219
|
+
hash.key?(property) || hash.key?(property.to_s) || hash.key?(property.to_s.to_sym)
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
# Adds relationship and document details supplied by the caller to parsing diagnostics.
|
|
223
|
+
def context_prefix(context)
|
|
224
|
+
return '' if context.nil? || context.to_s.empty?
|
|
225
|
+
|
|
226
|
+
"#{context}: "
|
|
227
|
+
end
|
|
228
|
+
|
|
194
229
|
# Converts one hash to a shallow string-keyed copy.
|
|
195
230
|
def stringify_hash(hash)
|
|
196
231
|
return {} unless hash.is_a?(Hash)
|
|
@@ -238,6 +238,9 @@ class Base
|
|
|
238
238
|
@engine.resolve_reference_document(
|
|
239
239
|
reference,
|
|
240
240
|
primary_path: @state.definition.primary_path,
|
|
241
|
+
scope_fields: @state.definition.scope_fields,
|
|
242
|
+
referring_document: @document,
|
|
243
|
+
relationship: "#{@state.definition.from_collection} -> #{@state.definition.to_collection}",
|
|
241
244
|
collection_hint: from_collection
|
|
242
245
|
)
|
|
243
246
|
end
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Jekyll
|
|
4
|
+
module Plugins
|
|
5
|
+
module Relationships
|
|
6
|
+
|
|
7
|
+
module Support
|
|
8
|
+
|
|
9
|
+
# Matches exact values at dot-separated paths in document frontmatter.
|
|
10
|
+
#
|
|
11
|
+
# Paths traverse hashes only so arrays and hashes remain matchable terminal
|
|
12
|
+
# values. Lookup distinguishes a missing path from a present nil value and
|
|
13
|
+
# treats string and symbol hash keys as equivalent.
|
|
14
|
+
class FrontmatterMatcher
|
|
15
|
+
# Builds one matcher from normalised path and expected-value pairs.
|
|
16
|
+
def initialize(expected_values:)
|
|
17
|
+
@expected_values = expected_values
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Returns true when every configured path is present and matches exactly.
|
|
21
|
+
def matches?(data)
|
|
22
|
+
@expected_values.all? do |path, expected_value|
|
|
23
|
+
match = value_at(data: data, path: path)
|
|
24
|
+
match.fetch(:present) && type_sensitive_equal?(match.fetch(:value), expected_value)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
# Reads one hash-only path without collapsing a present nil into missing.
|
|
31
|
+
def value_at(data:, path:)
|
|
32
|
+
current_value = data
|
|
33
|
+
FrontmatterPath.split_path(path).each do |segment|
|
|
34
|
+
return missing_value unless current_value.is_a?(Hash)
|
|
35
|
+
|
|
36
|
+
resolved_key = resolve_hash_key(hash: current_value, segment: segment)
|
|
37
|
+
return missing_value if resolved_key.nil?
|
|
38
|
+
|
|
39
|
+
current_value = current_value[resolved_key]
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
{
|
|
43
|
+
present: true,
|
|
44
|
+
value: current_value
|
|
45
|
+
}
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Resolves string and symbol forms of one path segment.
|
|
49
|
+
def resolve_hash_key(hash:, segment:)
|
|
50
|
+
return segment if hash.key?(segment)
|
|
51
|
+
|
|
52
|
+
symbol_segment = segment.to_sym
|
|
53
|
+
return symbol_segment if hash.key?(symbol_segment)
|
|
54
|
+
|
|
55
|
+
nil
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Returns one fresh missing lookup result.
|
|
59
|
+
def missing_value
|
|
60
|
+
{
|
|
61
|
+
present: false,
|
|
62
|
+
value: nil
|
|
63
|
+
}
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Requires both the Ruby type and value to match.
|
|
67
|
+
def type_sensitive_equal?(actual_value, expected_value)
|
|
68
|
+
actual_value.class == expected_value.class && actual_value == expected_value
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
@@ -53,8 +53,8 @@ class EdgeBuilder
|
|
|
53
53
|
|
|
54
54
|
@graph.documents_for(child_collection).each do |child_document|
|
|
55
55
|
path_configuration.parent_input_paths(max_parents: definition.tree_settings.max_parents).each do |path|
|
|
56
|
-
parse_references(@data_path.read(child_document.data, path)).each do |reference|
|
|
57
|
-
parent_document = resolve_reference(reference: reference,
|
|
56
|
+
parse_references(@data_path.read(child_document.data, path), definition: definition, document: child_document).each do |reference|
|
|
57
|
+
parent_document = resolve_reference(reference: reference, definition: definition, referring_document: child_document)
|
|
58
58
|
next unless parent_document
|
|
59
59
|
next unless parent_document.collection.label == parent_collection
|
|
60
60
|
|
|
@@ -88,8 +88,8 @@ class EdgeBuilder
|
|
|
88
88
|
|
|
89
89
|
@graph.documents_for(parent_collection).each do |parent_document|
|
|
90
90
|
path_configuration.child_input_paths(max_children: definition.tree_settings.max_children).each do |path|
|
|
91
|
-
parse_references(@data_path.read(parent_document.data, path)).each do |reference|
|
|
92
|
-
child_document = resolve_reference(reference: reference,
|
|
91
|
+
parse_references(@data_path.read(parent_document.data, path), definition: definition, document: parent_document).each do |reference|
|
|
92
|
+
child_document = resolve_reference(reference: reference, definition: definition, referring_document: parent_document)
|
|
93
93
|
next unless child_document
|
|
94
94
|
next unless child_document.collection.label == child_collection
|
|
95
95
|
|
|
@@ -132,7 +132,7 @@ class EdgeBuilder
|
|
|
132
132
|
parents = fetch_nested(url_index, parent_collection, parent_parts)
|
|
133
133
|
next unless parents
|
|
134
134
|
|
|
135
|
-
parents.each do |parent_document|
|
|
135
|
+
parents.select { |parent_document| same_scope?(first_document: child_document, second_document: parent_document, definition: definition) }.each do |parent_document|
|
|
136
136
|
@graph.add_edge(
|
|
137
137
|
parent_document: parent_document,
|
|
138
138
|
child_document: child_document,
|
|
@@ -162,24 +162,56 @@ class EdgeBuilder
|
|
|
162
162
|
end
|
|
163
163
|
|
|
164
164
|
# Parses one tree frontmatter value into parsed references.
|
|
165
|
-
def parse_references(value)
|
|
165
|
+
def parse_references(value, definition:, document:)
|
|
166
166
|
@string_array.interpret(value, split: -1, flatten: true).map do |entry|
|
|
167
|
-
@configuration.reference_template.parse(
|
|
167
|
+
@configuration.reference_template.parse(
|
|
168
|
+
entry,
|
|
169
|
+
context: "Relationship `#{definition.from_collection} -> #{definition.to_collection}` reference on document `#{document.relative_path}`"
|
|
170
|
+
)
|
|
168
171
|
end.compact
|
|
169
172
|
end
|
|
170
173
|
|
|
171
174
|
# Resolves one parsed reference to a real document.
|
|
172
|
-
def resolve_reference(reference:,
|
|
173
|
-
|
|
175
|
+
def resolve_reference(reference:, definition:, referring_document:)
|
|
176
|
+
relationship = "#{definition.from_collection} -> #{definition.to_collection}"
|
|
177
|
+
effective_scope = @registry.effective_scope_for(
|
|
178
|
+
reference_scope: reference.scope,
|
|
179
|
+
referring_document: referring_document,
|
|
180
|
+
scope_fields: definition.scope_fields,
|
|
181
|
+
relationship: relationship
|
|
182
|
+
)
|
|
183
|
+
if reference.document?
|
|
184
|
+
@registry.validate_scope_match!(
|
|
185
|
+
document: reference.page,
|
|
186
|
+
scope: effective_scope,
|
|
187
|
+
scope_fields: definition.scope_fields,
|
|
188
|
+
relationship: relationship,
|
|
189
|
+
referring_document: referring_document
|
|
190
|
+
)
|
|
191
|
+
return reference.page
|
|
192
|
+
end
|
|
174
193
|
|
|
175
194
|
collection = reference.collection.to_s unless reference.collection.nil?
|
|
176
195
|
@registry.lookup(
|
|
177
196
|
key: reference.key,
|
|
178
|
-
primary_path: primary_path,
|
|
179
|
-
collection: collection
|
|
197
|
+
primary_path: definition.primary_path,
|
|
198
|
+
collection: collection,
|
|
199
|
+
scope_fields: definition.scope_fields,
|
|
200
|
+
scope: effective_scope,
|
|
201
|
+
relationship: relationship,
|
|
202
|
+
referring_document: referring_document
|
|
180
203
|
)
|
|
181
204
|
end
|
|
182
205
|
|
|
206
|
+
# Returns true when two URL-selected documents share the definition's complete scope.
|
|
207
|
+
def same_scope?(first_document:, second_document:, definition:)
|
|
208
|
+
return true if definition.scope_fields.empty?
|
|
209
|
+
|
|
210
|
+
relationship = "#{definition.from_collection} -> #{definition.to_collection}"
|
|
211
|
+
@registry.scope_for(first_document, scope_fields: definition.scope_fields, relationship: relationship) ==
|
|
212
|
+
@registry.scope_for(second_document, scope_fields: definition.scope_fields, relationship: relationship)
|
|
213
|
+
end
|
|
214
|
+
|
|
183
215
|
# Reads one nested hash value without creating a default entry.
|
|
184
216
|
def fetch_nested(hash, first_key, second_key)
|
|
185
217
|
first_hash = hash[first_key]
|
|
@@ -30,11 +30,14 @@ class Graph
|
|
|
30
30
|
@descendant_cache = {}
|
|
31
31
|
@root_distance_cache = nil
|
|
32
32
|
@primary_path_by_collection = {}
|
|
33
|
+
@scope_fields_by_collection = {}
|
|
33
34
|
@tree_collections = @configuration.tree_relationships.each_with_object(Set.new) do |definition, collections|
|
|
34
35
|
collections << definition.from_collection
|
|
35
36
|
collections << definition.to_collection
|
|
36
37
|
@primary_path_by_collection[definition.from_collection] ||= definition.primary_path
|
|
37
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
|
|
38
41
|
end
|
|
39
42
|
@edge_builder = EdgeBuilder.new(
|
|
40
43
|
graph: self,
|
|
@@ -99,19 +102,21 @@ class Graph
|
|
|
99
102
|
end
|
|
100
103
|
|
|
101
104
|
# Returns immediate parents as canonical reference hashes.
|
|
102
|
-
def parents_for(document, primary_path: nil)
|
|
105
|
+
def parents_for(document, primary_path: nil, scope_fields: nil)
|
|
103
106
|
return [] unless active_document?(document)
|
|
104
107
|
|
|
105
108
|
resolved_primary_path = resolve_primary_path(document: document, primary_path: primary_path)
|
|
106
|
-
|
|
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) }
|
|
107
111
|
end
|
|
108
112
|
|
|
109
113
|
# Returns immediate children as canonical reference hashes.
|
|
110
|
-
def children_for(document, primary_path: nil)
|
|
114
|
+
def children_for(document, primary_path: nil, scope_fields: nil)
|
|
111
115
|
return [] unless active_document?(document)
|
|
112
116
|
|
|
113
117
|
resolved_primary_path = resolve_primary_path(document: document, primary_path: primary_path)
|
|
114
|
-
|
|
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) }
|
|
115
120
|
end
|
|
116
121
|
|
|
117
122
|
# Returns the immediate parent documents in deterministic insertion order.
|
|
@@ -143,26 +148,30 @@ class Graph
|
|
|
143
148
|
end
|
|
144
149
|
|
|
145
150
|
# Returns ancestor references filtered by minimum and maximum distance.
|
|
146
|
-
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)
|
|
147
152
|
return [] unless active_document?(document)
|
|
148
153
|
|
|
149
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)
|
|
150
156
|
filter_distances(
|
|
151
157
|
distance_map_for(document: document, direction: :up),
|
|
152
158
|
primary_path: resolved_primary_path,
|
|
159
|
+
scope_fields: resolved_scope_fields,
|
|
153
160
|
min: min,
|
|
154
161
|
max: max
|
|
155
162
|
)
|
|
156
163
|
end
|
|
157
164
|
|
|
158
165
|
# Returns descendant references filtered by minimum and maximum distance.
|
|
159
|
-
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)
|
|
160
167
|
return [] unless active_document?(document)
|
|
161
168
|
|
|
162
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)
|
|
163
171
|
filter_distances(
|
|
164
172
|
distance_map_for(document: document, direction: :down),
|
|
165
173
|
primary_path: resolved_primary_path,
|
|
174
|
+
scope_fields: resolved_scope_fields,
|
|
166
175
|
min: min,
|
|
167
176
|
max: max
|
|
168
177
|
)
|
|
@@ -316,10 +325,10 @@ class Graph
|
|
|
316
325
|
frontmatter = definition.tree_settings.frontmatter
|
|
317
326
|
|
|
318
327
|
definition_documents(definition).each do |document|
|
|
319
|
-
parent_values = parents_for(document, primary_path: definition.primary_path)
|
|
320
|
-
child_values = children_for(document, primary_path: definition.primary_path)
|
|
321
|
-
ancestor_values = ancestors_for(document, primary_path: definition.primary_path, min: 0)
|
|
322
|
-
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)
|
|
323
332
|
depth_value = root_distance_for(document)
|
|
324
333
|
|
|
325
334
|
if frontmatter.output_path
|
|
@@ -402,6 +411,13 @@ class Graph
|
|
|
402
411
|
@primary_path_by_collection[document.collection.label]
|
|
403
412
|
end
|
|
404
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
|
+
|
|
405
421
|
# Returns true when one edge already exists.
|
|
406
422
|
def edge_exists?(parent_document:, child_document:)
|
|
407
423
|
@parents[child_document].include?(parent_document)
|
|
@@ -471,20 +487,21 @@ class Graph
|
|
|
471
487
|
end
|
|
472
488
|
|
|
473
489
|
# Filters one distance map to the requested range and builds references.
|
|
474
|
-
def filter_distances(distance_map, primary_path:, min:, max:)
|
|
490
|
+
def filter_distances(distance_map, primary_path:, scope_fields:, min:, max:)
|
|
475
491
|
distance_map.each_with_object([]) do |(document, distance), references|
|
|
476
492
|
next if distance < min
|
|
477
493
|
next if max != -1 && distance > max
|
|
478
494
|
|
|
479
|
-
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)
|
|
480
496
|
end
|
|
481
497
|
end
|
|
482
498
|
|
|
483
499
|
# Builds one canonical reference hash, optionally including distance.
|
|
484
|
-
def build_reference(document, primary_path:, distance: nil)
|
|
500
|
+
def build_reference(document, primary_path:, scope_fields:, distance: nil)
|
|
485
501
|
reference = @configuration.reference_template.build(
|
|
486
502
|
document: document,
|
|
487
503
|
key: @registry.key_for(document, primary_path: primary_path),
|
|
504
|
+
scope: @registry.scope_for(document, scope_fields: scope_fields),
|
|
488
505
|
include_count: false
|
|
489
506
|
)
|
|
490
507
|
reference['distance'] = distance unless distance.nil?
|
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'
|