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.
Files changed (30) hide show
  1. checksums.yaml +4 -4
  2. data/lib/jekyll-relationships/configuration/defaults.rb +1 -0
  3. data/lib/jekyll-relationships/configuration/frontmatter.rb +28 -1
  4. data/lib/jekyll-relationships/configuration/parser.rb +20 -3
  5. data/lib/jekyll-relationships/configuration/prune_rule_settings.rb +33 -5
  6. data/lib/jekyll-relationships/configuration.rb +43 -8
  7. data/lib/jekyll-relationships/definitions/normal_relationship.rb +8 -2
  8. data/lib/jekyll-relationships/definitions/prune_rule.rb +19 -3
  9. data/lib/jekyll-relationships/definitions/tree_relationship.rb +3 -2
  10. data/lib/jekyll-relationships/documents/registry.rb +182 -33
  11. data/lib/jekyll-relationships/engine/normal_seed.rb +9 -4
  12. data/lib/jekyll-relationships/engine/raw_path_state.rb +44 -22
  13. data/lib/jekyll-relationships/engine/relationship_state.rb +12 -0
  14. data/lib/jekyll-relationships/engine/session.rb +89 -15
  15. data/lib/jekyll-relationships/engine/write_back.rb +1 -0
  16. data/lib/jekyll-relationships/engine.rb +1 -1
  17. data/lib/jekyll-relationships/pruning/rule_pruner.rb +7 -7
  18. data/lib/jekyll-relationships/pruning/tree_phase.rb +29 -11
  19. data/lib/jekyll-relationships/pruning/tree_provenance.rb +1 -1
  20. data/lib/jekyll-relationships/references/accumulator.rb +18 -6
  21. data/lib/jekyll-relationships/references/template.rb +46 -11
  22. data/lib/jekyll-relationships/resolvers/base.rb +3 -0
  23. data/lib/jekyll-relationships/support/frontmatter_matcher.rb +76 -0
  24. data/lib/jekyll-relationships/support/placeholders.rb +1 -0
  25. data/lib/jekyll-relationships/trees/edge_builder.rb +43 -11
  26. data/lib/jekyll-relationships/trees/graph.rb +30 -13
  27. data/lib/jekyll-relationships/version.rb +1 -1
  28. data/lib/jekyll-relationships.rb +1 -0
  29. data/readme.md +154 -23
  30. 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, "Unsupported reference value `#{value.inspect}`."
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, "Reference hash `#{hash.inspect}` does not include the configured key property `#{@key_property}`."
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
@@ -10,6 +10,7 @@ module Support
10
10
  module Placeholders
11
11
  KEY = '<key>'.freeze
12
12
  COLLECTION = '<collection>'.freeze
13
+ SCOPE = '<scope>'.freeze
13
14
  PAGE = '<page>'.freeze
14
15
  COUNT = '<count>'.freeze
15
16
  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, primary_path: definition.primary_path)
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, primary_path: definition.primary_path)
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(entry)
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:, primary_path:)
173
- return reference.page if reference.document?
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
- @parents[document].map { |parent| build_reference(parent, primary_path: resolved_primary_path) }
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
- @children[document].map { |child| build_reference(child, primary_path: resolved_primary_path) }
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?
@@ -4,7 +4,7 @@ module Jekyll
4
4
  module Plugins
5
5
 
6
6
  module Relationships
7
- VERSION = '0.1.1.alpha'
7
+ VERSION = '0.2.0.alpha'
8
8
  end
9
9
 
10
10
  end
@@ -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'