jekyll-relationships 0.1.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 +7 -0
- data/lib/jekyll-relationships/configuration/debug_setting.rb +117 -0
- data/lib/jekyll-relationships/configuration/defaults.rb +54 -0
- data/lib/jekyll-relationships/configuration/frontmatter.rb +102 -0
- data/lib/jekyll-relationships/configuration/hash_utilities.rb +55 -0
- data/lib/jekyll-relationships/configuration/multiple_settings.rb +101 -0
- data/lib/jekyll-relationships/configuration/parser.rb +551 -0
- data/lib/jekyll-relationships/configuration/prune_rule_settings.rb +101 -0
- data/lib/jekyll-relationships/configuration/prune_settings.rb +82 -0
- data/lib/jekyll-relationships/configuration/tree_frontmatter.rb +244 -0
- data/lib/jekyll-relationships/configuration/tree_settings.rb +74 -0
- data/lib/jekyll-relationships/configuration.rb +250 -0
- data/lib/jekyll-relationships/debug_logger.rb +104 -0
- data/lib/jekyll-relationships/definitions/configured_relationship.rb +61 -0
- data/lib/jekyll-relationships/definitions/normal_relationship.rb +51 -0
- data/lib/jekyll-relationships/definitions/prune_rule.rb +67 -0
- data/lib/jekyll-relationships/definitions/tree_relationship.rb +48 -0
- data/lib/jekyll-relationships/documents/registry.rb +136 -0
- data/lib/jekyll-relationships/engine/raw_path_state.rb +217 -0
- data/lib/jekyll-relationships/engine/relationship_state.rb +251 -0
- data/lib/jekyll-relationships/engine/session.rb +187 -0
- data/lib/jekyll-relationships/engine/write_back.rb +154 -0
- data/lib/jekyll-relationships/engine.rb +228 -0
- data/lib/jekyll-relationships/errors.rb +30 -0
- data/lib/jekyll-relationships/generators/relationships.rb +27 -0
- data/lib/jekyll-relationships/pruning/normal_graph.rb +119 -0
- data/lib/jekyll-relationships/pruning/rule_pruner.rb +67 -0
- data/lib/jekyll-relationships/pruning/tree_phase.rb +351 -0
- data/lib/jekyll-relationships/pruning/tree_provenance.rb +32 -0
- data/lib/jekyll-relationships/references/accumulator.rb +185 -0
- data/lib/jekyll-relationships/references/template.rb +222 -0
- data/lib/jekyll-relationships/resolvers/base.rb +207 -0
- data/lib/jekyll-relationships/run_logger.rb +101 -0
- data/lib/jekyll-relationships/support/data_path.rb +194 -0
- data/lib/jekyll-relationships/support/frontmatter_path.rb +217 -0
- data/lib/jekyll-relationships/support/hash_deep_merge.rb +63 -0
- data/lib/jekyll-relationships/support/placeholders.rb +21 -0
- data/lib/jekyll-relationships/support/string_array.rb +157 -0
- data/lib/jekyll-relationships/trees/edge_builder.rb +196 -0
- data/lib/jekyll-relationships/trees/graph.rb +454 -0
- data/lib/jekyll-relationships/version.rb +11 -0
- data/lib/jekyll-relationships.rb +34 -0
- data/readme.md +509 -0
- metadata +189 -0
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Jekyll
|
|
4
|
+
module Plugins
|
|
5
|
+
|
|
6
|
+
module Relationships
|
|
7
|
+
|
|
8
|
+
class Configuration
|
|
9
|
+
|
|
10
|
+
# Encapsulates the global settings that control pruning behaviour.
|
|
11
|
+
#
|
|
12
|
+
# These settings determine whether expanded relationship entries should be
|
|
13
|
+
# combined into one prune rule per subject collection, how many prune rounds
|
|
14
|
+
# may run before the final rebuild pass, and how tree orphans should be
|
|
15
|
+
# handled after parent documents disappear.
|
|
16
|
+
class PruneSettings
|
|
17
|
+
DEFAULTS = {
|
|
18
|
+
'combine' => true,
|
|
19
|
+
'iterations' => 10,
|
|
20
|
+
'tree' => {
|
|
21
|
+
'orphans' => 'grandparents'
|
|
22
|
+
}.freeze
|
|
23
|
+
}.freeze
|
|
24
|
+
|
|
25
|
+
attr_reader :iterations, :tree_orphans
|
|
26
|
+
|
|
27
|
+
# Builds one prune-settings helper from raw configuration.
|
|
28
|
+
def initialize(raw_config:)
|
|
29
|
+
config = normalise_config(raw_config)
|
|
30
|
+
@combine = Configuration::HashUtilities.boolean_value(config.fetch('combine'), 'relationships.prune.combine')
|
|
31
|
+
@iterations = normalise_iterations(config.fetch('iterations'))
|
|
32
|
+
@tree_orphans = normalise_tree_orphans(Configuration::HashUtilities.fetch_hash_value(config, 'tree'))
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Returns true when expanded relationship members should combine by subject collection.
|
|
36
|
+
def combine?
|
|
37
|
+
@combine
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Returns the total number of prune rounds to run before the final rebuild pass.
|
|
41
|
+
def prune_rounds
|
|
42
|
+
@iterations + 1
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
private
|
|
46
|
+
|
|
47
|
+
# Converts the loose raw value into one normalised hash.
|
|
48
|
+
def normalise_config(raw_config)
|
|
49
|
+
return DEFAULTS if raw_config.nil?
|
|
50
|
+
raise ConfigurationError, '`relationships.prune` must be a hash.' unless raw_config.is_a?(Hash)
|
|
51
|
+
|
|
52
|
+
Configuration::HashUtilities.merge_hash(DEFAULTS, raw_config)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Clamps the configured prune-round iteration count into the supported range.
|
|
56
|
+
def normalise_iterations(raw_iterations)
|
|
57
|
+
interpreted_iterations = Configuration::HashUtilities.integer_value(raw_iterations, 'relationships.prune.iterations')
|
|
58
|
+
return 0 if interpreted_iterations < 0
|
|
59
|
+
return 100 if interpreted_iterations > 100
|
|
60
|
+
|
|
61
|
+
interpreted_iterations
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Resolves the configured orphan-handling mode for tree pruning.
|
|
65
|
+
def normalise_tree_orphans(raw_tree_config)
|
|
66
|
+
tree_config = raw_tree_config.is_a?(Hash) ? raw_tree_config : {}
|
|
67
|
+
raw_mode = Configuration::HashUtilities.hash_key?(tree_config, 'orphans') ? Configuration::HashUtilities.fetch_hash_value(tree_config, 'orphans') : DEFAULTS.fetch('tree').fetch('orphans')
|
|
68
|
+
string_mode = raw_mode.to_s.strip.downcase
|
|
69
|
+
string_mode = DEFAULTS.fetch('tree').fetch('orphans') if string_mode.empty?
|
|
70
|
+
return 'grandparents' if %w[grandparent grandparents].include?(string_mode)
|
|
71
|
+
return 'grandparents required' if %w[grandparent\ required grandparents\ required].include?(string_mode)
|
|
72
|
+
return string_mode if %w[prune orphan].include?(string_mode)
|
|
73
|
+
|
|
74
|
+
raise ConfigurationError, "Unsupported relationships.prune.tree.orphans value `#{raw_mode}`."
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
end
|
|
82
|
+
end
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Jekyll
|
|
4
|
+
module Plugins
|
|
5
|
+
|
|
6
|
+
module Relationships
|
|
7
|
+
|
|
8
|
+
class Configuration
|
|
9
|
+
|
|
10
|
+
# Resolves the tree-specific frontmatter locations for one effective config.
|
|
11
|
+
#
|
|
12
|
+
# Tree frontmatter inherits the active relationship frontmatter base at each
|
|
13
|
+
# override level, but may replace that base with its own `tree.frontmatter.base`.
|
|
14
|
+
# The resolved helper exposes explicit input and output paths so the tree
|
|
15
|
+
# graph never has to reimplement config merging rules.
|
|
16
|
+
class TreeFrontmatter
|
|
17
|
+
PATH_NAMES = %w[parent child parents children ancestors descendants].freeze
|
|
18
|
+
|
|
19
|
+
# Builds one tree-frontmatter helper from the resolved values.
|
|
20
|
+
def initialize(base:, raw_paths:, raw_output_path:, string_array:)
|
|
21
|
+
@base = normalise_base(base)
|
|
22
|
+
@raw_paths = stringify_hash(raw_paths)
|
|
23
|
+
@raw_output_path = raw_output_path
|
|
24
|
+
@string_array = string_array
|
|
25
|
+
validate_paths!
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Builds the built-in default tree frontmatter configuration.
|
|
29
|
+
def self.defaults(string_array:)
|
|
30
|
+
new(
|
|
31
|
+
base: Defaults::FRONTMATTER.fetch('base'),
|
|
32
|
+
raw_paths: Defaults::TREE.fetch('frontmatter'),
|
|
33
|
+
raw_output_path: nil,
|
|
34
|
+
string_array: string_array
|
|
35
|
+
)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Returns one cloned helper with one override level merged in.
|
|
39
|
+
#
|
|
40
|
+
# Relationship `frontmatter.base` is applied first for the level, then
|
|
41
|
+
# `tree.frontmatter.base` may replace it for tree paths only.
|
|
42
|
+
def merge_level(frontmatter_override:, tree_frontmatter_override:)
|
|
43
|
+
override_hash = tree_frontmatter_override.is_a?(Hash) ? tree_frontmatter_override : {}
|
|
44
|
+
next_base = base_after_overrides(frontmatter_override: frontmatter_override, tree_frontmatter_override: override_hash)
|
|
45
|
+
next_paths = @raw_paths.dup
|
|
46
|
+
next_output_path = @raw_output_path
|
|
47
|
+
|
|
48
|
+
PATH_NAMES.each do |name|
|
|
49
|
+
next unless Configuration::HashUtilities.hash_key?(override_hash, name)
|
|
50
|
+
|
|
51
|
+
next_paths[name] = Configuration::HashUtilities.fetch_hash_value(override_hash, name)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
if Configuration::HashUtilities.hash_key?(override_hash, 'output')
|
|
55
|
+
next_output_path = Configuration::HashUtilities.fetch_hash_value(override_hash, 'output')
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
self.class.new(
|
|
59
|
+
base: next_base,
|
|
60
|
+
raw_paths: next_paths,
|
|
61
|
+
raw_output_path: next_output_path,
|
|
62
|
+
string_array: @string_array
|
|
63
|
+
)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Returns the effective base path applied to tree frontmatter.
|
|
67
|
+
def base
|
|
68
|
+
@base
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Returns every absolute input path used to read parent references.
|
|
72
|
+
#
|
|
73
|
+
# Single-parent trees only read the singular parent keys, matching the
|
|
74
|
+
# documented contract that plural keys are ignored once the maximum is 1.
|
|
75
|
+
def parent_input_paths(max_parents:)
|
|
76
|
+
return paths_for('parent') if max_parents == 1
|
|
77
|
+
|
|
78
|
+
(paths_for('parent') + paths_for('parents')).uniq
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Returns every absolute input path used to read child references.
|
|
82
|
+
#
|
|
83
|
+
# Single-child trees mirror the parent-side behaviour and only read the
|
|
84
|
+
# singular child keys when the configured maximum is 1.
|
|
85
|
+
def child_input_paths(max_children:)
|
|
86
|
+
return paths_for('child') if max_children == 1
|
|
87
|
+
|
|
88
|
+
(paths_for('child') + paths_for('children')).uniq
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Returns the absolute prevailing singular parent output path.
|
|
92
|
+
def parent_output_path
|
|
93
|
+
first_path_for('parent')
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Returns the absolute prevailing singular child output path.
|
|
97
|
+
def child_output_path
|
|
98
|
+
first_path_for('child')
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# Returns the absolute prevailing plural parent output path.
|
|
102
|
+
def parents_output_path
|
|
103
|
+
first_path_for('parents')
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# Returns the absolute prevailing plural child output path.
|
|
107
|
+
def children_output_path
|
|
108
|
+
first_path_for('children')
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# Returns the absolute prevailing ancestors output path.
|
|
112
|
+
def ancestors_output_path
|
|
113
|
+
first_path_for('ancestors')
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# Returns the absolute prevailing descendants output path.
|
|
117
|
+
def descendants_output_path
|
|
118
|
+
first_path_for('descendants')
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# Returns the absolute output container path, if one is configured.
|
|
122
|
+
def output_path
|
|
123
|
+
return nil if blank_path?(@raw_output_path)
|
|
124
|
+
raise ConfigurationError, 'Tree frontmatter output must be one path, not an array.' if @raw_output_path.is_a?(Array)
|
|
125
|
+
|
|
126
|
+
apply_base(@raw_output_path.to_s.strip)
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# Builds the final tree-output hash for one document.
|
|
130
|
+
#
|
|
131
|
+
# The configured relative tree paths become nested keys within the output
|
|
132
|
+
# hash, while the configured output container path becomes the single
|
|
133
|
+
# frontmatter location on which that hash is written.
|
|
134
|
+
def output_payload(parent_value:, parents_value:, child_value:, children_value:, ancestors_value:, descendants_value:, max_parents:, max_children:)
|
|
135
|
+
payload = {}
|
|
136
|
+
data_path = Jekyll::Plugins::Relationships::Support::DataPath.new
|
|
137
|
+
|
|
138
|
+
if max_parents == 1
|
|
139
|
+
data_path.write(payload, first_relative_path_for('parent'), parent_value)
|
|
140
|
+
else
|
|
141
|
+
data_path.write(payload, first_relative_path_for('parents'), parents_value)
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
if max_children == 1
|
|
145
|
+
data_path.write(payload, first_relative_path_for('child'), child_value)
|
|
146
|
+
else
|
|
147
|
+
data_path.write(payload, first_relative_path_for('children'), children_value)
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
data_path.write(payload, first_relative_path_for('ancestors'), ancestors_value)
|
|
151
|
+
data_path.write(payload, first_relative_path_for('descendants'), descendants_value)
|
|
152
|
+
payload
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
private
|
|
156
|
+
|
|
157
|
+
# Resolves the next effective base for one override level.
|
|
158
|
+
def base_after_overrides(frontmatter_override:, tree_frontmatter_override:)
|
|
159
|
+
next_base = @base
|
|
160
|
+
|
|
161
|
+
if Configuration::HashUtilities.hash_key?(frontmatter_override, 'base')
|
|
162
|
+
next_base = normalise_base(Configuration::HashUtilities.fetch_hash_value(frontmatter_override, 'base'))
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
if Configuration::HashUtilities.hash_key?(tree_frontmatter_override, 'base')
|
|
166
|
+
next_base = normalise_base(Configuration::HashUtilities.fetch_hash_value(tree_frontmatter_override, 'base'))
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
next_base
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
# Returns the configured absolute paths for one tree property.
|
|
173
|
+
def paths_for(name)
|
|
174
|
+
configured_paths_for(name).map { |path| apply_base(path) }
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
# Returns the first prevailing absolute path for one tree property.
|
|
178
|
+
def first_path_for(name)
|
|
179
|
+
paths_for(name).first
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
# Returns the first prevailing relative path for one tree property.
|
|
183
|
+
def first_relative_path_for(name)
|
|
184
|
+
configured_paths_for(name).first
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
# Returns the configured relative paths for one property.
|
|
188
|
+
def configured_paths_for(name)
|
|
189
|
+
raw_value = @raw_paths[name]
|
|
190
|
+
paths = @string_array.interpret(raw_value, split: true, flatten: true).map do |path|
|
|
191
|
+
string_path = path.to_s.strip
|
|
192
|
+
raise ConfigurationError, "Tree frontmatter path `#{name}` cannot be blank." if string_path.empty?
|
|
193
|
+
|
|
194
|
+
string_path
|
|
195
|
+
end
|
|
196
|
+
raise ConfigurationError, "Tree frontmatter must define at least one path for `#{name}`." if paths.empty?
|
|
197
|
+
|
|
198
|
+
paths.uniq
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
# Applies the current base to one relative path.
|
|
202
|
+
def apply_base(path)
|
|
203
|
+
return path if @base.empty?
|
|
204
|
+
return @base if path.to_s.empty?
|
|
205
|
+
|
|
206
|
+
"#{@base}.#{path}"
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
# Ensures the helper only stores string-keyed frontmatter fields.
|
|
210
|
+
def stringify_hash(hash)
|
|
211
|
+
return {} unless hash.is_a?(Hash)
|
|
212
|
+
|
|
213
|
+
hash.each_with_object({}) do |(key, value), stringified|
|
|
214
|
+
stringified[key.to_s] = value
|
|
215
|
+
end
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
# Normalises nil bases to the empty string for path resolution.
|
|
219
|
+
def normalise_base(raw_base)
|
|
220
|
+
return '' if raw_base.nil?
|
|
221
|
+
|
|
222
|
+
raw_base.to_s
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
# Returns true when one configured path is blank or missing.
|
|
226
|
+
def blank_path?(path)
|
|
227
|
+
path.nil? || path.to_s.strip.empty?
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
# Validates only the known path-bearing properties eagerly.
|
|
231
|
+
def validate_paths!
|
|
232
|
+
PATH_NAMES.each { |name| configured_paths_for(name) }
|
|
233
|
+
return if blank_path?(@raw_output_path)
|
|
234
|
+
return unless @raw_output_path.is_a?(Array)
|
|
235
|
+
|
|
236
|
+
raise ConfigurationError, 'Tree frontmatter output must be one path, not an array.'
|
|
237
|
+
end
|
|
238
|
+
end
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
end
|
|
244
|
+
end
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Jekyll
|
|
4
|
+
module Plugins
|
|
5
|
+
|
|
6
|
+
module Relationships
|
|
7
|
+
|
|
8
|
+
class Configuration
|
|
9
|
+
|
|
10
|
+
# Encapsulates one effective tree configuration scope.
|
|
11
|
+
#
|
|
12
|
+
# Tree settings are merged level by level so relationship-level
|
|
13
|
+
# `frontmatter.base` overrides can replace higher-level tree bases before a
|
|
14
|
+
# lower-level `tree.frontmatter.base` optionally replaces them again.
|
|
15
|
+
class TreeSettings
|
|
16
|
+
attr_reader :frontmatter
|
|
17
|
+
|
|
18
|
+
# Builds one tree-settings helper from already-resolved values.
|
|
19
|
+
def initialize(frontmatter:, maximums:, url:)
|
|
20
|
+
@frontmatter = frontmatter
|
|
21
|
+
@maximums = Configuration::HashUtilities.merge_hash(Defaults::TREE.fetch('max'), maximums)
|
|
22
|
+
@url = !!url
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Builds the built-in default tree settings.
|
|
26
|
+
def self.defaults(string_array:)
|
|
27
|
+
new(
|
|
28
|
+
frontmatter: TreeFrontmatter.defaults(string_array: string_array),
|
|
29
|
+
maximums: Defaults::TREE.fetch('max'),
|
|
30
|
+
url: Defaults::TREE.fetch('url')
|
|
31
|
+
)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Returns one cloned helper with one override level merged in.
|
|
35
|
+
def merge_level(frontmatter_override:, tree_override:)
|
|
36
|
+
override_hash = tree_override.is_a?(Hash) ? tree_override : {}
|
|
37
|
+
next_frontmatter = @frontmatter.merge_level(
|
|
38
|
+
frontmatter_override: frontmatter_override,
|
|
39
|
+
tree_frontmatter_override: Configuration::HashUtilities.fetch_hash_value(override_hash, 'frontmatter')
|
|
40
|
+
)
|
|
41
|
+
next_maximums = Configuration::HashUtilities.merge_hash(
|
|
42
|
+
@maximums,
|
|
43
|
+
Configuration::HashUtilities.fetch_hash_value(override_hash, 'max')
|
|
44
|
+
)
|
|
45
|
+
next_url = Configuration::HashUtilities.hash_key?(override_hash, 'url') ? !!Configuration::HashUtilities.fetch_hash_value(override_hash, 'url') : @url
|
|
46
|
+
|
|
47
|
+
self.class.new(
|
|
48
|
+
frontmatter: next_frontmatter,
|
|
49
|
+
maximums: next_maximums,
|
|
50
|
+
url: next_url
|
|
51
|
+
)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Returns the configured maximum parent count.
|
|
55
|
+
def max_parents
|
|
56
|
+
Configuration::HashUtilities.integer_value(@maximums['parents'], 'tree.max.parents')
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Returns the configured maximum child count.
|
|
60
|
+
def max_children
|
|
61
|
+
Configuration::HashUtilities.integer_value(@maximums['children'], 'tree.max.children')
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Returns true when URL-derived tree inference is enabled.
|
|
65
|
+
def url?
|
|
66
|
+
@url
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
end
|
|
74
|
+
end
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'jekyll-relationships/definitions/normal_relationship'
|
|
4
|
+
require 'jekyll-relationships/definitions/tree_relationship'
|
|
5
|
+
require 'jekyll-relationships/configuration/defaults'
|
|
6
|
+
require 'jekyll-relationships/configuration/debug_setting'
|
|
7
|
+
require 'jekyll-relationships/configuration/hash_utilities'
|
|
8
|
+
require 'jekyll-relationships/configuration/frontmatter'
|
|
9
|
+
require 'jekyll-relationships/configuration/multiple_settings'
|
|
10
|
+
require 'jekyll-relationships/configuration/prune_settings'
|
|
11
|
+
require 'jekyll-relationships/configuration/prune_rule_settings'
|
|
12
|
+
require 'jekyll-relationships/configuration/tree_frontmatter'
|
|
13
|
+
require 'jekyll-relationships/configuration/tree_settings'
|
|
14
|
+
require 'jekyll-relationships/configuration/parser'
|
|
15
|
+
|
|
16
|
+
module Jekyll
|
|
17
|
+
module Plugins
|
|
18
|
+
|
|
19
|
+
module Relationships
|
|
20
|
+
|
|
21
|
+
# Normalises the site configuration into explicit relationship definitions.
|
|
22
|
+
#
|
|
23
|
+
# The object resolves defaults once, exposes the final keyword and frontmatter
|
|
24
|
+
# rules, and indexes the concrete relationship definitions used by the engine.
|
|
25
|
+
class Configuration
|
|
26
|
+
attr_reader :keywords, :multiple_settings, :reference_template, :tree_settings, :global_frontmatter, :debug, :prune_settings
|
|
27
|
+
|
|
28
|
+
# Builds the full configuration model from `site.config`.
|
|
29
|
+
def initialize(site_config)
|
|
30
|
+
@string_array = Jekyll::Plugins::Relationships::Support::StringArray.new
|
|
31
|
+
@raw_config = Configuration::HashUtilities.fetch_hash_value(site_config, 'relationships') || {}
|
|
32
|
+
@enabled = build_enabled_setting
|
|
33
|
+
|
|
34
|
+
if !enabled?
|
|
35
|
+
initialise_disabled_configuration
|
|
36
|
+
return
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
@debug = build_debug_setting
|
|
40
|
+
global_frontmatter_override = Configuration::HashUtilities.fetch_hash_value(@raw_config, 'frontmatter')
|
|
41
|
+
global_tree_override = Configuration::HashUtilities.fetch_hash_value(@raw_config, 'tree')
|
|
42
|
+
@keywords = build_keywords(Configuration::HashUtilities.fetch_hash_value(@raw_config, 'keywords'))
|
|
43
|
+
@multiple_settings = MultipleSettings.new(
|
|
44
|
+
raw_config: Configuration::HashUtilities.fetch_hash_value(@raw_config, 'multiple')
|
|
45
|
+
)
|
|
46
|
+
@prune_settings = PruneSettings.new(
|
|
47
|
+
raw_config: Configuration::HashUtilities.fetch_hash_value(@raw_config, 'prune')
|
|
48
|
+
)
|
|
49
|
+
@global_frontmatter = Frontmatter.new(
|
|
50
|
+
raw_config: Configuration::HashUtilities.merge_hash(
|
|
51
|
+
Defaults::FRONTMATTER,
|
|
52
|
+
global_frontmatter_override
|
|
53
|
+
),
|
|
54
|
+
string_array: @string_array
|
|
55
|
+
)
|
|
56
|
+
@reference_template = References::Template.new(
|
|
57
|
+
config: build_reference_config,
|
|
58
|
+
count_enabled: @multiple_settings.count?
|
|
59
|
+
)
|
|
60
|
+
@tree_settings = TreeSettings.defaults(
|
|
61
|
+
string_array: @string_array
|
|
62
|
+
).merge_level(
|
|
63
|
+
frontmatter_override: global_frontmatter_override,
|
|
64
|
+
tree_override: global_tree_override
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
parsed_relationships = Parser.new(
|
|
68
|
+
raw_config: @raw_config,
|
|
69
|
+
string_array: @string_array,
|
|
70
|
+
global_debug: @debug,
|
|
71
|
+
global_frontmatter: @global_frontmatter,
|
|
72
|
+
global_tree_settings: @tree_settings,
|
|
73
|
+
keywords: @keywords,
|
|
74
|
+
prune_settings: @prune_settings
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
parsed_result = parsed_relationships.parse
|
|
78
|
+
@normal_relationships = parsed_result.fetch(:normal_relationships)
|
|
79
|
+
@tree_relationships = parsed_result.fetch(:tree_relationships)
|
|
80
|
+
@collections = parsed_result.fetch(:collections)
|
|
81
|
+
@configured_relationships = parsed_result.fetch(:configured_relationships)
|
|
82
|
+
@normal_prune_rules = parsed_result.fetch(:normal_prune_rules)
|
|
83
|
+
@tree_prune_rules = parsed_result.fetch(:tree_prune_rules)
|
|
84
|
+
attach_resolvers!(parser: parsed_relationships)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Returns true when relationship processing is globally enabled.
|
|
88
|
+
def enabled?
|
|
89
|
+
@enabled
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Returns the concrete normal relationship definition for one pair.
|
|
93
|
+
def normal_relationship_for(from_collection:, to_collection:)
|
|
94
|
+
collection_relationships = @normal_relationships[from_collection]
|
|
95
|
+
return nil unless collection_relationships
|
|
96
|
+
|
|
97
|
+
collection_relationships[to_collection]
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# Returns all normal relationship definitions for one source collection.
|
|
101
|
+
def normal_relationships_for(collection)
|
|
102
|
+
(@normal_relationships[collection] || {}).values.sort_by(&:sequence)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# Returns all configured tree relationships.
|
|
106
|
+
def tree_relationships
|
|
107
|
+
@tree_relationships.sort_by(&:sequence)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Returns every collection that participates in any relationship.
|
|
111
|
+
def collections
|
|
112
|
+
@collections.dup
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# Returns every primary-key path used anywhere in the configuration.
|
|
116
|
+
def primary_paths
|
|
117
|
+
normal_paths = @normal_relationships.values.flat_map(&:values).map(&:primary_path)
|
|
118
|
+
tree_paths = @tree_relationships.map(&:primary_path)
|
|
119
|
+
(normal_paths + tree_paths).uniq
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
private
|
|
123
|
+
|
|
124
|
+
# Builds the active global enabled setting.
|
|
125
|
+
def build_enabled_setting
|
|
126
|
+
return Defaults::ENABLED unless Configuration::HashUtilities.hash_key?(@raw_config, 'enabled')
|
|
127
|
+
|
|
128
|
+
Configuration::HashUtilities.boolean_value(
|
|
129
|
+
Configuration::HashUtilities.fetch_hash_value(@raw_config, 'enabled'),
|
|
130
|
+
'relationships.enabled'
|
|
131
|
+
)
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# Sets up the inert configuration used when the plugin is globally disabled.
|
|
135
|
+
def initialise_disabled_configuration
|
|
136
|
+
@debug = Configuration::DebugSetting.disabled
|
|
137
|
+
@keywords = build_keywords(nil)
|
|
138
|
+
@multiple_settings = MultipleSettings.new(raw_config: nil)
|
|
139
|
+
@prune_settings = PruneSettings.new(raw_config: nil)
|
|
140
|
+
@global_frontmatter = Frontmatter.new(
|
|
141
|
+
raw_config: Defaults::FRONTMATTER,
|
|
142
|
+
string_array: @string_array
|
|
143
|
+
)
|
|
144
|
+
@reference_template = References::Template.new(
|
|
145
|
+
config: default_reference_config,
|
|
146
|
+
count_enabled: @multiple_settings.count?
|
|
147
|
+
)
|
|
148
|
+
@tree_settings = TreeSettings.defaults(string_array: @string_array)
|
|
149
|
+
@normal_relationships = {}
|
|
150
|
+
@tree_relationships = []
|
|
151
|
+
@collections = []
|
|
152
|
+
@configured_relationships = []
|
|
153
|
+
@normal_prune_rules = []
|
|
154
|
+
@tree_prune_rules = []
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
public
|
|
158
|
+
|
|
159
|
+
# Returns every configured forward-facing relationship member in sequence order.
|
|
160
|
+
def configured_relationships
|
|
161
|
+
@configured_relationships.sort_by(&:sequence)
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
# Returns every configured prune rule for normal relationships.
|
|
165
|
+
def normal_prune_rules
|
|
166
|
+
@normal_prune_rules.sort_by do |rule|
|
|
167
|
+
[rule.subject_collection, rule.entry_index, rule.members.first.sequence]
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
# Returns every configured prune rule for tree relationships.
|
|
172
|
+
def tree_prune_rules
|
|
173
|
+
@tree_prune_rules.sort_by do |rule|
|
|
174
|
+
[rule.subject_collection, rule.entry_index, rule.members.first.sequence]
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
# Returns true when any relationship entry defined a prune rule.
|
|
179
|
+
def pruning_enabled?
|
|
180
|
+
!@normal_prune_rules.empty? || !@tree_prune_rules.empty?
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
# Builds the active keyword table.
|
|
184
|
+
def build_keywords(raw_keywords)
|
|
185
|
+
Configuration::HashUtilities.merge_hash(Defaults::KEYWORDS, raw_keywords)
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
# Builds the active global debug setting.
|
|
189
|
+
def build_debug_setting
|
|
190
|
+
Configuration::DebugSetting.build(
|
|
191
|
+
value: Configuration::HashUtilities.hash_key?(@raw_config, 'debug') ? Configuration::HashUtilities.fetch_hash_value(@raw_config, 'debug') : Defaults::DEBUG,
|
|
192
|
+
string_array: @string_array,
|
|
193
|
+
context: 'relationships.debug'
|
|
194
|
+
)
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
# Builds the resolved reference template config.
|
|
198
|
+
def build_reference_config
|
|
199
|
+
default_references = default_reference_config
|
|
200
|
+
explicit_config = Configuration::HashUtilities.fetch_hash_value(@raw_config, 'references')
|
|
201
|
+
return explicit_config if explicit_config.is_a?(Hash)
|
|
202
|
+
|
|
203
|
+
nested_frontmatter = Configuration::HashUtilities.fetch_hash_value(
|
|
204
|
+
Configuration::HashUtilities.fetch_hash_value(@raw_config, 'frontmatter'),
|
|
205
|
+
'references'
|
|
206
|
+
)
|
|
207
|
+
if nested_frontmatter.is_a?(Hash)
|
|
208
|
+
raise ConfigurationError, '`relationships.references` must be configured directly under `relationships`, not under `relationships.frontmatter`.'
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
default_references
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
# Builds the default reference template shape for the active duplicate mode.
|
|
215
|
+
def default_reference_config
|
|
216
|
+
default_references = {
|
|
217
|
+
'id' => Jekyll::Plugins::Relationships::Support::Placeholders::KEY,
|
|
218
|
+
'collection' => Jekyll::Plugins::Relationships::Support::Placeholders::COLLECTION,
|
|
219
|
+
'page' => Jekyll::Plugins::Relationships::Support::Placeholders::PAGE
|
|
220
|
+
}
|
|
221
|
+
if @multiple_settings.count?
|
|
222
|
+
default_references['count'] = Jekyll::Plugins::Relationships::Support::Placeholders::COUNT
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
default_references
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
# Attaches registered resolver classes to matching concrete relationships.
|
|
229
|
+
def attach_resolvers!(parser:)
|
|
230
|
+
Resolvers::Base.registered_subclasses.each do |resolver_class|
|
|
231
|
+
parser.pairs_for(
|
|
232
|
+
from_definition: resolver_class.from_definition,
|
|
233
|
+
to_definition: resolver_class.to_definition
|
|
234
|
+
).each do |from_collection, to_collection|
|
|
235
|
+
definition = normal_relationship_for(
|
|
236
|
+
from_collection: from_collection,
|
|
237
|
+
to_collection: to_collection
|
|
238
|
+
)
|
|
239
|
+
next unless definition
|
|
240
|
+
|
|
241
|
+
definition.add_resolver(resolver_class)
|
|
242
|
+
end
|
|
243
|
+
end
|
|
244
|
+
end
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
end
|
|
250
|
+
end
|