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,222 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Jekyll
|
|
4
|
+
module Plugins
|
|
5
|
+
|
|
6
|
+
module Relationships
|
|
7
|
+
module References
|
|
8
|
+
|
|
9
|
+
# Parses loose reference values and builds canonical relationship hashes.
|
|
10
|
+
#
|
|
11
|
+
# The template is configured from `relationships.references` and controls which
|
|
12
|
+
# property names hold the key, collection, page, and optional count values in
|
|
13
|
+
# output hashes.
|
|
14
|
+
class Template
|
|
15
|
+
|
|
16
|
+
# Represents one parsed reference before or after it is resolved.
|
|
17
|
+
#
|
|
18
|
+
# The `metadata` hash contains any non-reserved properties found on an
|
|
19
|
+
# existing reference hash.
|
|
20
|
+
class ParsedReference
|
|
21
|
+
attr_reader :key, :collection, :page, :count, :metadata, :original_value
|
|
22
|
+
|
|
23
|
+
# Captures the parsed reference fields in one immutable object.
|
|
24
|
+
def initialize(key:, collection:, page:, count:, metadata:, original_value:)
|
|
25
|
+
@key = key
|
|
26
|
+
@collection = collection
|
|
27
|
+
@page = page
|
|
28
|
+
@count = count
|
|
29
|
+
@metadata = metadata
|
|
30
|
+
@original_value = original_value
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Returns true when the parsed reference already points at a document.
|
|
34
|
+
def document?
|
|
35
|
+
@page.is_a?(Jekyll::Document)
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
attr_reader :key_property, :collection_property, :page_property, :count_property
|
|
40
|
+
|
|
41
|
+
# Builds the reference template from config and duplicate-handling settings.
|
|
42
|
+
def initialize(config:, count_enabled:)
|
|
43
|
+
@config = stringify_hash(config || {})
|
|
44
|
+
@count_enabled = !!count_enabled
|
|
45
|
+
@key_property = nil
|
|
46
|
+
@collection_property = nil
|
|
47
|
+
@page_property = nil
|
|
48
|
+
@count_property = nil
|
|
49
|
+
|
|
50
|
+
parse_config!
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Parses one raw reference value from frontmatter or resolver input.
|
|
54
|
+
def parse(value)
|
|
55
|
+
case value
|
|
56
|
+
when String
|
|
57
|
+
key = value.to_s.strip
|
|
58
|
+
return nil if key.empty?
|
|
59
|
+
|
|
60
|
+
ParsedReference.new(
|
|
61
|
+
key: key,
|
|
62
|
+
collection: nil,
|
|
63
|
+
page: nil,
|
|
64
|
+
count: 1,
|
|
65
|
+
metadata: {},
|
|
66
|
+
original_value: value
|
|
67
|
+
)
|
|
68
|
+
when Hash
|
|
69
|
+
parse_hash_reference(value)
|
|
70
|
+
when Jekyll::Document
|
|
71
|
+
ParsedReference.new(
|
|
72
|
+
key: nil,
|
|
73
|
+
collection: nil,
|
|
74
|
+
page: value,
|
|
75
|
+
count: 1,
|
|
76
|
+
metadata: {},
|
|
77
|
+
original_value: value
|
|
78
|
+
)
|
|
79
|
+
else
|
|
80
|
+
raise ConfigurationError, "Unsupported reference value `#{value.inspect}`."
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Builds one output reference hash for a resolved document.
|
|
85
|
+
def build(document:, key:, metadata: nil, count: 1, include_count: @count_enabled)
|
|
86
|
+
hash = {}
|
|
87
|
+
hash[@key_property] = key
|
|
88
|
+
hash[@collection_property] = document.collection.label if @collection_property
|
|
89
|
+
hash[@page_property] = document if @page_property
|
|
90
|
+
hash[@count_property] = normalise_count(count) if include_count && @count_property
|
|
91
|
+
|
|
92
|
+
filter_metadata(metadata).each do |property, value|
|
|
93
|
+
hash[property] = value
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
hash
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# Returns the configured property names that are reserved for the engine.
|
|
100
|
+
def reserved_properties
|
|
101
|
+
[@key_property, @collection_property, @page_property, @count_property].compact
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# Returns the input properties that should never survive into free-form metadata.
|
|
105
|
+
#
|
|
106
|
+
# `count` is always treated as reserved input so resolver metadata cannot
|
|
107
|
+
# smuggle explicit multiplicities into modes that do not support them.
|
|
108
|
+
def reserved_input_properties
|
|
109
|
+
(reserved_properties + ['count']).uniq
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
private
|
|
113
|
+
|
|
114
|
+
# Validates and captures the configured placeholder mappings.
|
|
115
|
+
def parse_config!
|
|
116
|
+
@config.each do |property, value|
|
|
117
|
+
case value.to_s
|
|
118
|
+
when Jekyll::Plugins::Relationships::Support::Placeholders::KEY
|
|
119
|
+
raise ConfigurationError, 'Reference config can only define one <key> property.' if @key_property
|
|
120
|
+
@key_property = property
|
|
121
|
+
when Jekyll::Plugins::Relationships::Support::Placeholders::COLLECTION
|
|
122
|
+
raise ConfigurationError, 'Reference config can only define one <collection> property.' if @collection_property
|
|
123
|
+
@collection_property = property
|
|
124
|
+
when Jekyll::Plugins::Relationships::Support::Placeholders::PAGE
|
|
125
|
+
raise ConfigurationError, 'Reference config can only define one <page> property.' if @page_property
|
|
126
|
+
@page_property = property
|
|
127
|
+
when Jekyll::Plugins::Relationships::Support::Placeholders::COUNT
|
|
128
|
+
raise ConfigurationError, 'Reference config can only define one <count> property.' if @count_property
|
|
129
|
+
@count_property = property
|
|
130
|
+
else
|
|
131
|
+
raise ConfigurationError, "Unsupported reference template value `#{value.inspect}`. Only placeholder keywords are supported."
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
raise ConfigurationError, 'Reference config must define exactly one <key> property.' unless @key_property
|
|
136
|
+
if @count_enabled && @count_property.nil?
|
|
137
|
+
raise ConfigurationError, 'Reference config must define exactly one <count> property when `relationships.multiple` is `count`.'
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# Parses one hash reference according to the configured property names.
|
|
142
|
+
def parse_hash_reference(hash)
|
|
143
|
+
key = fetch_hash_value(hash, @key_property)
|
|
144
|
+
collection = @collection_property ? fetch_hash_value(hash, @collection_property) : nil
|
|
145
|
+
page = @page_property ? fetch_hash_value(hash, @page_property) : nil
|
|
146
|
+
|
|
147
|
+
if key.nil? && !page.is_a?(Jekyll::Document)
|
|
148
|
+
raise ResolutionError, "Reference hash `#{hash.inspect}` does not include the configured key property `#{@key_property}`."
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
ParsedReference.new(
|
|
152
|
+
key: key,
|
|
153
|
+
collection: collection,
|
|
154
|
+
page: page,
|
|
155
|
+
count: parsed_count(hash),
|
|
156
|
+
metadata: filter_metadata(hash),
|
|
157
|
+
original_value: hash
|
|
158
|
+
)
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
# Returns the parsed count value for one reference hash.
|
|
162
|
+
def parsed_count(hash)
|
|
163
|
+
return 1 unless @count_enabled
|
|
164
|
+
return 1 unless @count_property
|
|
165
|
+
|
|
166
|
+
raw_count = fetch_hash_value(hash, @count_property)
|
|
167
|
+
return 1 if raw_count.nil?
|
|
168
|
+
|
|
169
|
+
normalise_count(raw_count)
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
# Removes reserved engine properties from a metadata hash.
|
|
173
|
+
def filter_metadata(hash)
|
|
174
|
+
return {} unless hash.is_a?(Hash)
|
|
175
|
+
|
|
176
|
+
metadata = {}
|
|
177
|
+
hash.each do |property, value|
|
|
178
|
+
string_property = property.to_s
|
|
179
|
+
next if reserved_input_properties.include?(string_property)
|
|
180
|
+
|
|
181
|
+
metadata[string_property] = value
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
metadata
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
# Reads one property from a string- or symbol-keyed hash.
|
|
188
|
+
def fetch_hash_value(hash, property)
|
|
189
|
+
return nil unless property
|
|
190
|
+
|
|
191
|
+
Jekyll::Plugins::Relationships::Support::FrontmatterPath.read_hash(hash, property)
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
# Converts one hash to a shallow string-keyed copy.
|
|
195
|
+
def stringify_hash(hash)
|
|
196
|
+
return {} unless hash.is_a?(Hash)
|
|
197
|
+
|
|
198
|
+
hash.each_with_object({}) do |(key, value), stringified|
|
|
199
|
+
stringified[key.to_s] = value
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
# Validates and normalises one reference count.
|
|
204
|
+
def normalise_count(value)
|
|
205
|
+
integer_count = if value.is_a?(Integer)
|
|
206
|
+
value
|
|
207
|
+
elsif value.is_a?(String) && value.strip.match?(/\A\d+\z/)
|
|
208
|
+
value.to_i
|
|
209
|
+
else
|
|
210
|
+
raise ResolutionError, "Relationship count `#{value.inspect}` must be a positive integer."
|
|
211
|
+
end
|
|
212
|
+
raise ResolutionError, "Relationship count `#{value.inspect}` must be a positive integer." if integer_count < 1
|
|
213
|
+
|
|
214
|
+
integer_count
|
|
215
|
+
end
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
end
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
end
|
|
222
|
+
end
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Jekyll
|
|
4
|
+
module Plugins
|
|
5
|
+
|
|
6
|
+
module Relationships
|
|
7
|
+
module Resolvers
|
|
8
|
+
|
|
9
|
+
# Base class for custom relationship resolvers.
|
|
10
|
+
#
|
|
11
|
+
# Subclass this in `_plugins`, declare `from` and `to`, then implement
|
|
12
|
+
# `resolve` to add or remove links while the engine is resolving one concrete
|
|
13
|
+
# document-to-collection pair.
|
|
14
|
+
class Base
|
|
15
|
+
|
|
16
|
+
@registered_subclasses = []
|
|
17
|
+
|
|
18
|
+
class << self
|
|
19
|
+
attr_reader :from_definition, :to_definition
|
|
20
|
+
|
|
21
|
+
# Tracks subclasses while their class bodies are still being declared.
|
|
22
|
+
def inherited(subclass)
|
|
23
|
+
Base.remove_registered_subclass(subclass: subclass)
|
|
24
|
+
super
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Declares or reads the resolver's `from` selector.
|
|
28
|
+
def from(value = nil)
|
|
29
|
+
unless value.nil?
|
|
30
|
+
@from_definition = value
|
|
31
|
+
Base.refresh_registered_subclass(subclass: self)
|
|
32
|
+
end
|
|
33
|
+
@from_definition
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Declares or reads the resolver's `to` selector.
|
|
37
|
+
def to(value = nil)
|
|
38
|
+
unless value.nil?
|
|
39
|
+
@to_definition = value
|
|
40
|
+
Base.refresh_registered_subclass(subclass: self)
|
|
41
|
+
end
|
|
42
|
+
@to_definition
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Returns every resolver subclass whose `from` and `to` selectors are complete.
|
|
46
|
+
def registered_subclasses
|
|
47
|
+
@registered_subclasses ||= []
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Synchronises one resolver subclass with the shared registry after class-level declarations change.
|
|
51
|
+
def refresh_registered_subclass(subclass:)
|
|
52
|
+
remove_registered_subclass(subclass: subclass)
|
|
53
|
+
return if subclass == Base
|
|
54
|
+
return if subclass.from_definition.nil? || subclass.to_definition.nil?
|
|
55
|
+
|
|
56
|
+
registered_subclasses << subclass
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Removes one resolver subclass from the shared registry while declaration is incomplete.
|
|
60
|
+
def remove_registered_subclass(subclass:)
|
|
61
|
+
registered_subclasses.delete(subclass)
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Builds one resolver instance for one resolving relationship state.
|
|
66
|
+
def initialize(engine:, state:)
|
|
67
|
+
@engine = engine
|
|
68
|
+
@state = state
|
|
69
|
+
@document = state.document
|
|
70
|
+
@key = @engine.registry.key_for(@document, primary_path: state.definition.primary_path)
|
|
71
|
+
@from = state.definition.from_collection
|
|
72
|
+
@to = state.definition.to_collection
|
|
73
|
+
@site = @engine.site
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Override this in subclasses to change relationship output.
|
|
77
|
+
def resolve
|
|
78
|
+
raise NotImplementedError, "#{self.class} must implement `resolve`."
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Returns current relationships for one document and target collection.
|
|
82
|
+
def relationships(reference = nil, to: nil, from: nil)
|
|
83
|
+
target_collection = to || @to
|
|
84
|
+
document = resolve_helper_document(reference, from)
|
|
85
|
+
result = if document == @document && target_collection == @to && @state.resolving?
|
|
86
|
+
@state.current_references
|
|
87
|
+
else
|
|
88
|
+
@engine.resolve_relationships(document, target_collection)
|
|
89
|
+
end
|
|
90
|
+
debug_helper('resolver_relationships', {
|
|
91
|
+
reference: reference,
|
|
92
|
+
from: from,
|
|
93
|
+
target_document: document,
|
|
94
|
+
to: target_collection,
|
|
95
|
+
result: result
|
|
96
|
+
})
|
|
97
|
+
result
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# Returns the referenced document, resolving its outgoing pairs if needed.
|
|
101
|
+
def document(reference = nil, from: nil)
|
|
102
|
+
document = resolve_helper_document(reference, from)
|
|
103
|
+
if document == @document
|
|
104
|
+
debug_helper('resolver_document', {
|
|
105
|
+
reference: reference,
|
|
106
|
+
from: from,
|
|
107
|
+
target_document: document
|
|
108
|
+
})
|
|
109
|
+
return document
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
@engine.resolve_document(document)
|
|
113
|
+
debug_helper('resolver_document', {
|
|
114
|
+
reference: reference,
|
|
115
|
+
from: from,
|
|
116
|
+
target_document: document
|
|
117
|
+
})
|
|
118
|
+
document
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# Returns ancestors for one document reference.
|
|
122
|
+
def ancestors(reference = nil, from: nil, min: 1, max: -1)
|
|
123
|
+
document = resolve_helper_document(reference, from)
|
|
124
|
+
result = @engine.tree_graph.ancestors_for(document, min: min, max: max)
|
|
125
|
+
debug_helper('resolver_ancestors', {
|
|
126
|
+
reference: reference,
|
|
127
|
+
from: from,
|
|
128
|
+
target_document: document,
|
|
129
|
+
min: min,
|
|
130
|
+
max: max,
|
|
131
|
+
result: result
|
|
132
|
+
})
|
|
133
|
+
result
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# Returns parents for one document reference.
|
|
137
|
+
def parents(reference = nil, from: nil)
|
|
138
|
+
ancestors(reference, from: from, min: 1, max: 1)
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# Returns descendants for one document reference.
|
|
142
|
+
def descendants(reference = nil, from: nil, min: 1, max: -1)
|
|
143
|
+
document = resolve_helper_document(reference, from)
|
|
144
|
+
result = @engine.tree_graph.descendants_for(document, min: min, max: max)
|
|
145
|
+
debug_helper('resolver_descendants', {
|
|
146
|
+
reference: reference,
|
|
147
|
+
from: from,
|
|
148
|
+
target_document: document,
|
|
149
|
+
min: min,
|
|
150
|
+
max: max,
|
|
151
|
+
result: result
|
|
152
|
+
})
|
|
153
|
+
result
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
# Returns children for one document reference.
|
|
157
|
+
def children(reference = nil, from: nil)
|
|
158
|
+
descendants(reference, from: from, min: 1, max: 1)
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
# Adds one relationship from the current document to the target collection.
|
|
162
|
+
def link(target_reference, reference: nil)
|
|
163
|
+
@state.link(
|
|
164
|
+
target_reference,
|
|
165
|
+
metadata: reference,
|
|
166
|
+
origin: "resolver #{self.class.name || self.class}"
|
|
167
|
+
)
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
# Removes one relationship, or all of them when no reference is given.
|
|
171
|
+
def unlink(reference = nil)
|
|
172
|
+
@state.unlink(
|
|
173
|
+
reference,
|
|
174
|
+
origin: "resolver #{self.class.name || self.class}"
|
|
175
|
+
)
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
private
|
|
179
|
+
|
|
180
|
+
# Resolves one resolver helper argument to a real document.
|
|
181
|
+
def resolve_helper_document(reference, from_collection)
|
|
182
|
+
return @document if reference.nil?
|
|
183
|
+
|
|
184
|
+
@engine.resolve_reference_document(
|
|
185
|
+
reference,
|
|
186
|
+
primary_path: @state.definition.primary_path,
|
|
187
|
+
collection_hint: from_collection
|
|
188
|
+
)
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
# Emits one debug line for one resolver helper call when enabled.
|
|
192
|
+
def debug_helper(event, details)
|
|
193
|
+
@engine.debug_logger.relationship_event(
|
|
194
|
+
document: @document,
|
|
195
|
+
definition: @state.definition,
|
|
196
|
+
area: 'resolvers',
|
|
197
|
+
event: event,
|
|
198
|
+
details: details
|
|
199
|
+
)
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
end
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
end
|
|
207
|
+
end
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Jekyll
|
|
4
|
+
module Plugins
|
|
5
|
+
|
|
6
|
+
module Relationships
|
|
7
|
+
|
|
8
|
+
# Emits the default high-level summary lines for relationship processing.
|
|
9
|
+
#
|
|
10
|
+
# This logger stays separate from debug logging so the engine can always report
|
|
11
|
+
# the configured relationship coverage, prune removals, and total run time
|
|
12
|
+
# without mixing that output into the lower-level trace areas.
|
|
13
|
+
class RunLogger
|
|
14
|
+
MAX_REMOVED_FILENAME_CHARACTERS = 50
|
|
15
|
+
NON_BREAKING_SPACE = "\u00a0"
|
|
16
|
+
|
|
17
|
+
# Builds one summary logger for one engine run.
|
|
18
|
+
def initialize
|
|
19
|
+
@started_at = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Logs one multi-line summary of the configured relationships.
|
|
23
|
+
def relationship_summary(entries:, relationship_count:)
|
|
24
|
+
log_lines(
|
|
25
|
+
header: "#{relationship_count} relationships defined.",
|
|
26
|
+
lines: justified_lines(entries)
|
|
27
|
+
)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Logs one multi-line summary of the documents removed by pruning.
|
|
31
|
+
def pruning_summary(removed_documents_by_collection:)
|
|
32
|
+
total_removed = removed_documents_by_collection.values.flatten.length
|
|
33
|
+
entries = removed_documents_by_collection.keys.sort.map do |collection|
|
|
34
|
+
documents = removed_documents_by_collection.fetch(collection)
|
|
35
|
+
{
|
|
36
|
+
label: "#{collection}:",
|
|
37
|
+
details: "#{documents.length} removed (#{removed_filenames(documents)})"
|
|
38
|
+
}
|
|
39
|
+
end
|
|
40
|
+
log_lines(
|
|
41
|
+
header: "Removed #{total_removed} items because of pruning rules.",
|
|
42
|
+
lines: justified_lines(entries)
|
|
43
|
+
)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Logs the total elapsed time for relationship processing.
|
|
47
|
+
def finish!
|
|
48
|
+
elapsed = Process.clock_gettime(Process::CLOCK_MONOTONIC) - @started_at
|
|
49
|
+
Jekyll.logger.info('Relationships:', format('Done in %.2f seconds.', elapsed))
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
private
|
|
53
|
+
|
|
54
|
+
# Writes one heading plus a tree-shaped list of lines.
|
|
55
|
+
def log_lines(header:, lines:)
|
|
56
|
+
Jekyll.logger.info('Relationships:', header)
|
|
57
|
+
lines.each_with_index do |line, index|
|
|
58
|
+
branch = index == lines.length - 1 ? '└─' : '├─'
|
|
59
|
+
Jekyll.logger.info('Relationships:', "#{branch} #{line}")
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Builds one truncated filename list for the removed-documents summary.
|
|
64
|
+
def removed_filenames(documents)
|
|
65
|
+
filenames = Array(documents).map do |document|
|
|
66
|
+
File.basename(document.relative_path)
|
|
67
|
+
end.sort
|
|
68
|
+
return '' if filenames.empty?
|
|
69
|
+
|
|
70
|
+
included_names = []
|
|
71
|
+
character_total = 0
|
|
72
|
+
|
|
73
|
+
filenames.each do |filename|
|
|
74
|
+
next_total = character_total + filename.length
|
|
75
|
+
break if !included_names.empty? && next_total > MAX_REMOVED_FILENAME_CHARACTERS
|
|
76
|
+
|
|
77
|
+
included_names << filename
|
|
78
|
+
character_total = next_total
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
text = included_names.join(', ')
|
|
82
|
+
return text if included_names.length == filenames.length
|
|
83
|
+
|
|
84
|
+
"#{text}, ..."
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Aligns summary details with non-breaking spaces so Jekyll preserves padding.
|
|
88
|
+
def justified_lines(entries)
|
|
89
|
+
maximum_label_length = Array(entries).map { |entry| entry.fetch(:label).length }.max || 0
|
|
90
|
+
Array(entries).map do |entry|
|
|
91
|
+
label = entry.fetch(:label)
|
|
92
|
+
padding_width = [maximum_label_length - label.length, 0].max + 1
|
|
93
|
+
"#{label}#{NON_BREAKING_SPACE * padding_width}#{entry.fetch(:details)}"
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
end
|
|
101
|
+
end
|