nanoc-core 4.11.8 → 4.11.9
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/nanoc/core/action_sequence_store.rb +50 -0
- data/lib/nanoc/core/binary_compiled_content_cache.rb +128 -0
- data/lib/nanoc/core/checksum_store.rb +74 -0
- data/lib/nanoc/core/compiled_content_cache.rb +68 -0
- data/lib/nanoc/core/compiled_content_store.rb +77 -0
- data/lib/nanoc/core/configuration-schema.json +12 -0
- data/lib/nanoc/core/dependency_store.rb +203 -0
- data/lib/nanoc/core/dependency_tracker.rb +63 -0
- data/lib/nanoc/core/errors.rb +56 -0
- data/lib/nanoc/core/item_rep_repo.rb +37 -0
- data/lib/nanoc/core/outdatedness_reasons.rb +88 -0
- data/lib/nanoc/core/outdatedness_rule.rb +34 -0
- data/lib/nanoc/core/outdatedness_status.rb +27 -0
- data/lib/nanoc/core/outdatedness_store.rb +55 -0
- data/lib/nanoc/core/prefixed_data_source.rb +31 -0
- data/lib/nanoc/core/store.rb +114 -0
- data/lib/nanoc/core/textual_compiled_content_cache.rb +82 -0
- data/lib/nanoc/core/version.rb +1 -1
- data/lib/nanoc/core.rb +1 -0
- metadata +19 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b0c47871b0eb4f46b025c03db997f1b12971c30ca2a0ce5d5c00a6ad346bacd5
|
4
|
+
data.tar.gz: 558b36a88d1e4b943154368f7e2ee820dcf8ae8606cb5bb67b45dfaf2ba7cd07
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 701578c317c2879ceca2e40efc2029f2b43cde150f53bd80c65383c18d0c8f80580e130e191082968302f68d5d148fd995e04f98893e3e57f24f310eb65ef4d4
|
7
|
+
data.tar.gz: f8511f35512c5dc0e041482691ddd0283a9d686e101aac99336653aa5b404cfaebad74dc33487d887daadce850d946b9c33d427d3afa8dab2a7e6d6822122224
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Nanoc
|
4
|
+
module Core
|
5
|
+
# Stores action sequences for objects that can be run through a rule (item
|
6
|
+
# representations and layouts).
|
7
|
+
#
|
8
|
+
# @api private
|
9
|
+
class ActionSequenceStore < ::Nanoc::Core::Store
|
10
|
+
include Nanoc::Core::ContractsSupport
|
11
|
+
|
12
|
+
contract C::KeywordArgs[config: Nanoc::Core::Configuration] => C::Any
|
13
|
+
def initialize(config:)
|
14
|
+
super(Nanoc::Core::Store.tmp_path_for(config: config, store_name: 'rule_memory'), 1)
|
15
|
+
|
16
|
+
@action_sequences = {}
|
17
|
+
end
|
18
|
+
|
19
|
+
# @param [Nanoc::Core::ItemRep, Nanoc::Core::Layout] obj The item representation or
|
20
|
+
# the layout to get the action sequence for
|
21
|
+
#
|
22
|
+
# @return [Array] The action sequence for the given object
|
23
|
+
def [](obj)
|
24
|
+
@action_sequences[obj.reference]
|
25
|
+
end
|
26
|
+
|
27
|
+
# @param [Nanoc::Core::ItemRep, Nanoc::Core::Layout] obj The item representation or
|
28
|
+
# the layout to set the action sequence for
|
29
|
+
#
|
30
|
+
# @param [Array] action_sequence The new action sequence to be stored
|
31
|
+
#
|
32
|
+
# @return [void]
|
33
|
+
def []=(obj, action_sequence)
|
34
|
+
@action_sequences[obj.reference] = action_sequence
|
35
|
+
end
|
36
|
+
|
37
|
+
protected
|
38
|
+
|
39
|
+
# @see Nanoc::Core::Store#data
|
40
|
+
def data
|
41
|
+
@action_sequences
|
42
|
+
end
|
43
|
+
|
44
|
+
# @see Nanoc::Core::Store#data=
|
45
|
+
def data=(new_data)
|
46
|
+
@action_sequences = new_data
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,128 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Nanoc
|
4
|
+
module Core
|
5
|
+
# Represents a cache than can be used to store already compiled content,
|
6
|
+
# to prevent it from being needlessly recompiled.
|
7
|
+
#
|
8
|
+
# @api private
|
9
|
+
class BinaryCompiledContentCache < ::Nanoc::Core::Store
|
10
|
+
include Nanoc::Core::ContractsSupport
|
11
|
+
|
12
|
+
contract C::KeywordArgs[config: Nanoc::Core::Configuration] => C::Any
|
13
|
+
def initialize(config:)
|
14
|
+
super(Nanoc::Core::Store.tmp_path_for(config: config, store_name: 'binary_content'), 1)
|
15
|
+
|
16
|
+
@cache = {}
|
17
|
+
end
|
18
|
+
|
19
|
+
contract Nanoc::Core::ItemRep => C::Maybe[C::HashOf[Symbol => Nanoc::Core::Content]]
|
20
|
+
# Returns the cached compiled content for the given item representation.
|
21
|
+
#
|
22
|
+
# This cached compiled content is a hash where the keys are the snapshot
|
23
|
+
# names, and the values the compiled content at the given snapshot.
|
24
|
+
def [](rep)
|
25
|
+
item_cache = @cache[rep.item.identifier] || {}
|
26
|
+
|
27
|
+
rep_cache = item_cache[rep.name]
|
28
|
+
return nil if rep_cache.nil?
|
29
|
+
|
30
|
+
rep_cache.transform_values do |filename|
|
31
|
+
Nanoc::Core::Content.create(filename, binary: true)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
contract Nanoc::Core::ItemRep => C::Bool
|
36
|
+
def include?(rep)
|
37
|
+
item_cache = @cache[rep.item.identifier] || {}
|
38
|
+
item_cache.key?(rep.name)
|
39
|
+
end
|
40
|
+
|
41
|
+
contract Nanoc::Core::ItemRep, C::HashOf[Symbol => Nanoc::Core::BinaryContent] => C::HashOf[Symbol => Nanoc::Core::Content]
|
42
|
+
# Sets the compiled content for the given representation.
|
43
|
+
#
|
44
|
+
# This cached compiled content is a hash where the keys are the snapshot
|
45
|
+
# names, and the values the compiled content at the given snapshot.
|
46
|
+
def []=(rep, content)
|
47
|
+
@cache[rep.item.identifier] ||= {}
|
48
|
+
@cache[rep.item.identifier][rep.name] ||= {}
|
49
|
+
rep_cache = @cache[rep.item.identifier][rep.name]
|
50
|
+
|
51
|
+
content.each do |snapshot, binary_content|
|
52
|
+
filename = build_filename(rep, snapshot)
|
53
|
+
rep_cache[snapshot] = filename
|
54
|
+
|
55
|
+
# Avoid reassigning the same content if this binary cached content was
|
56
|
+
# already used, because it was available and the item wasn’t oudated.
|
57
|
+
next if binary_content.filename == filename
|
58
|
+
|
59
|
+
# Copy
|
60
|
+
#
|
61
|
+
# NOTE: hardlinking is not an option in this case, because hardlinking
|
62
|
+
# would make it possible for the content to be (inadvertently)
|
63
|
+
# changed outside of Nanoc.
|
64
|
+
FileUtils.mkdir_p(File.dirname(filename))
|
65
|
+
FileUtils.cp(binary_content.filename, filename)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def prune(items:)
|
70
|
+
item_identifiers = Set.new(items.map(&:identifier))
|
71
|
+
|
72
|
+
@cache.each_key do |key|
|
73
|
+
# TODO: remove unused item reps
|
74
|
+
next if item_identifiers.include?(key)
|
75
|
+
|
76
|
+
@cache.delete(key)
|
77
|
+
path = dirname_for_item_identifier(key)
|
78
|
+
FileUtils.rm_rf(path)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def data
|
83
|
+
@cache
|
84
|
+
end
|
85
|
+
|
86
|
+
def data=(new_data)
|
87
|
+
@cache = {}
|
88
|
+
|
89
|
+
new_data.each_pair do |item_identifier, content_per_rep|
|
90
|
+
@cache[item_identifier] ||= content_per_rep
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
private
|
95
|
+
|
96
|
+
def dirname
|
97
|
+
filename + '_data'
|
98
|
+
end
|
99
|
+
|
100
|
+
def string_to_path_component(string)
|
101
|
+
string.gsub(/[^a-zA-Z0-9]+/, '_') +
|
102
|
+
'-' +
|
103
|
+
Digest::SHA1.hexdigest(string)[0..9]
|
104
|
+
end
|
105
|
+
|
106
|
+
def dirname_for_item_identifier(item_identifier)
|
107
|
+
File.join(
|
108
|
+
dirname,
|
109
|
+
string_to_path_component(item_identifier.to_s),
|
110
|
+
)
|
111
|
+
end
|
112
|
+
|
113
|
+
def dirname_for_item_rep(rep)
|
114
|
+
File.join(
|
115
|
+
dirname_for_item_identifier(rep.item.identifier),
|
116
|
+
string_to_path_component(rep.name.to_s),
|
117
|
+
)
|
118
|
+
end
|
119
|
+
|
120
|
+
def build_filename(rep, snapshot_name)
|
121
|
+
File.join(
|
122
|
+
dirname_for_item_rep(rep),
|
123
|
+
string_to_path_component(snapshot_name.to_s),
|
124
|
+
)
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Nanoc
|
4
|
+
module Core
|
5
|
+
# Stores checksums for objects in order to be able to detect whether a file
|
6
|
+
# has changed since the last site compilation.
|
7
|
+
#
|
8
|
+
# @api private
|
9
|
+
class ChecksumStore < ::Nanoc::Core::Store
|
10
|
+
include Nanoc::Core::ContractsSupport
|
11
|
+
|
12
|
+
attr_writer :checksums
|
13
|
+
attr_accessor :objects
|
14
|
+
|
15
|
+
c_obj = C::Or[Nanoc::Core::Item, Nanoc::Core::Layout, Nanoc::Core::Configuration, Nanoc::Core::CodeSnippet]
|
16
|
+
|
17
|
+
contract C::KeywordArgs[config: Nanoc::Core::Configuration, objects: C::IterOf[c_obj]] => C::Any
|
18
|
+
def initialize(config:, objects:)
|
19
|
+
super(Nanoc::Core::Store.tmp_path_for(config: config, store_name: 'checksums'), 2)
|
20
|
+
|
21
|
+
@objects = objects
|
22
|
+
|
23
|
+
@checksums = {}
|
24
|
+
end
|
25
|
+
|
26
|
+
contract c_obj => C::Maybe[String]
|
27
|
+
def [](obj)
|
28
|
+
@checksums[obj.reference]
|
29
|
+
end
|
30
|
+
|
31
|
+
contract c_obj => self
|
32
|
+
def add(obj)
|
33
|
+
if obj.is_a?(Nanoc::Core::Document)
|
34
|
+
@checksums[[obj.reference, :content]] = Nanoc::Core::Checksummer.calc_for_content_of(obj)
|
35
|
+
end
|
36
|
+
|
37
|
+
if obj.is_a?(Nanoc::Core::Document) || obj.is_a?(Nanoc::Core::Configuration)
|
38
|
+
@checksums[[obj.reference, :each_attribute]] = Nanoc::Core::Checksummer.calc_for_each_attribute_of(obj)
|
39
|
+
end
|
40
|
+
|
41
|
+
@checksums[obj.reference] = Nanoc::Core::Checksummer.calc(obj)
|
42
|
+
|
43
|
+
self
|
44
|
+
end
|
45
|
+
|
46
|
+
contract c_obj => C::Maybe[String]
|
47
|
+
def content_checksum_for(obj)
|
48
|
+
@checksums[[obj.reference, :content]]
|
49
|
+
end
|
50
|
+
|
51
|
+
contract c_obj => C::Maybe[C::HashOf[Symbol, String]]
|
52
|
+
def attributes_checksum_for(obj)
|
53
|
+
@checksums[[obj.reference, :each_attribute]]
|
54
|
+
end
|
55
|
+
|
56
|
+
protected
|
57
|
+
|
58
|
+
def data
|
59
|
+
@checksums
|
60
|
+
end
|
61
|
+
|
62
|
+
def data=(new_data)
|
63
|
+
references = Set.new(@objects.map(&:reference))
|
64
|
+
|
65
|
+
@checksums = {}
|
66
|
+
new_data.each_pair do |key, checksum|
|
67
|
+
if references.include?(key) || references.include?(key.first)
|
68
|
+
@checksums[key] = checksum
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Nanoc
|
4
|
+
module Core
|
5
|
+
# Represents a cache than can be used to store already compiled content,
|
6
|
+
# to prevent it from being needlessly recompiled.
|
7
|
+
#
|
8
|
+
# @api private
|
9
|
+
class CompiledContentCache < ::Nanoc::Core::Store
|
10
|
+
include Nanoc::Core::ContractsSupport
|
11
|
+
|
12
|
+
contract C::KeywordArgs[config: Nanoc::Core::Configuration] => C::Any
|
13
|
+
def initialize(config:)
|
14
|
+
@textual_cache = Nanoc::Core::TextualCompiledContentCache.new(config: config)
|
15
|
+
@binary_cache = Nanoc::Core::BinaryCompiledContentCache.new(config: config)
|
16
|
+
|
17
|
+
@wrapped_caches = [@textual_cache, @binary_cache]
|
18
|
+
end
|
19
|
+
|
20
|
+
contract Nanoc::Core::ItemRep => C::Maybe[C::HashOf[Symbol => Nanoc::Core::Content]]
|
21
|
+
# Returns the cached compiled content for the given item representation.
|
22
|
+
#
|
23
|
+
# This cached compiled content is a hash where the keys are the snapshot
|
24
|
+
# names. and the values the compiled content at the given snapshot.
|
25
|
+
def [](rep)
|
26
|
+
textual_content_map = @textual_cache[rep]
|
27
|
+
binary_content_map = @binary_cache[rep]
|
28
|
+
|
29
|
+
# If either the textual or the binary content cache is nil, assume the
|
30
|
+
# cache is entirely absent.
|
31
|
+
#
|
32
|
+
# This is necessary to support the case where only textual content is
|
33
|
+
# cached (which was the case in older versions of Nanoc).
|
34
|
+
return nil if [textual_content_map, binary_content_map].any?(&:nil?)
|
35
|
+
|
36
|
+
textual_content_map.merge(binary_content_map)
|
37
|
+
end
|
38
|
+
|
39
|
+
contract Nanoc::Core::ItemRep, C::HashOf[Symbol => Nanoc::Core::Content] => C::Any
|
40
|
+
# Sets the compiled content for the given representation.
|
41
|
+
#
|
42
|
+
# This cached compiled content is a hash where the keys are the snapshot
|
43
|
+
# names and the values the compiled content at the given snapshot.
|
44
|
+
def []=(rep, content)
|
45
|
+
@textual_cache[rep] = content.select { |_key, c| c.textual? }
|
46
|
+
@binary_cache[rep] = content.select { |_key, c| c.binary? }
|
47
|
+
end
|
48
|
+
|
49
|
+
def prune(*args)
|
50
|
+
@wrapped_caches.each { |w| w.prune(*args) }
|
51
|
+
end
|
52
|
+
|
53
|
+
# True if there is cached compiled content available for this item, and
|
54
|
+
# all entries are present (either textual or binary).
|
55
|
+
def full_cache_available?(rep)
|
56
|
+
@textual_cache.include?(rep) && @binary_cache.include?(rep)
|
57
|
+
end
|
58
|
+
|
59
|
+
def load(*args)
|
60
|
+
@wrapped_caches.each { |w| w.load(*args) }
|
61
|
+
end
|
62
|
+
|
63
|
+
def store(*args)
|
64
|
+
@wrapped_caches.each { |w| w.store(*args) }
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Nanoc
|
4
|
+
module Core
|
5
|
+
# @api private
|
6
|
+
class CompiledContentStore
|
7
|
+
include Nanoc::Core::ContractsSupport
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
@contents = Hash.new { |hash, rep| hash[rep] = {} }
|
11
|
+
@current_content = {}
|
12
|
+
end
|
13
|
+
|
14
|
+
contract Nanoc::Core::ItemRep, Symbol => C::Maybe[Nanoc::Core::Content]
|
15
|
+
def get(rep, snapshot_name)
|
16
|
+
@contents[rep][snapshot_name]
|
17
|
+
end
|
18
|
+
|
19
|
+
contract Nanoc::Core::ItemRep => C::Maybe[Nanoc::Core::Content]
|
20
|
+
def get_current(rep)
|
21
|
+
@current_content[rep]
|
22
|
+
end
|
23
|
+
|
24
|
+
contract Nanoc::Core::ItemRep, Symbol, Nanoc::Core::Content => C::Any
|
25
|
+
def set(rep, snapshot_name, contents)
|
26
|
+
@contents[rep][snapshot_name] = contents
|
27
|
+
end
|
28
|
+
|
29
|
+
contract Nanoc::Core::ItemRep, Nanoc::Core::Content => C::Any
|
30
|
+
def set_current(rep, content)
|
31
|
+
@current_content[rep] = content
|
32
|
+
end
|
33
|
+
|
34
|
+
contract Nanoc::Core::ItemRep => C::HashOf[Symbol => Nanoc::Core::Content]
|
35
|
+
def get_all(rep)
|
36
|
+
@contents[rep]
|
37
|
+
end
|
38
|
+
|
39
|
+
contract Nanoc::Core::ItemRep, C::HashOf[Symbol => Nanoc::Core::Content] => C::Any
|
40
|
+
def set_all(rep, contents_per_snapshot)
|
41
|
+
@contents[rep] = contents_per_snapshot
|
42
|
+
end
|
43
|
+
|
44
|
+
contract C::KeywordArgs[rep: Nanoc::Core::ItemRep, snapshot: C::Optional[C::Maybe[Symbol]]] => Nanoc::Core::Content
|
45
|
+
def raw_compiled_content(rep:, snapshot: nil)
|
46
|
+
# Get name of last pre-layout snapshot
|
47
|
+
has_pre = rep.snapshot_defs.any? { |sd| sd.name == :pre }
|
48
|
+
snapshot_name = snapshot || (has_pre ? :pre : :last)
|
49
|
+
|
50
|
+
# Check existance of snapshot
|
51
|
+
snapshot_def = rep.snapshot_defs.reverse.find { |sd| sd.name == snapshot_name }
|
52
|
+
unless snapshot_def
|
53
|
+
raise Nanoc::Core::Errors::NoSuchSnapshot.new(rep, snapshot_name)
|
54
|
+
end
|
55
|
+
|
56
|
+
# Return content if it is available
|
57
|
+
content = get(rep, snapshot_name)
|
58
|
+
return content if content
|
59
|
+
|
60
|
+
# Content is unavailable; notify and try again
|
61
|
+
Fiber.yield(Nanoc::Core::Errors::UnmetDependency.new(rep, snapshot_name))
|
62
|
+
get(rep, snapshot_name)
|
63
|
+
end
|
64
|
+
|
65
|
+
contract C::KeywordArgs[rep: Nanoc::Core::ItemRep, snapshot: C::Optional[C::Maybe[Symbol]]] => String
|
66
|
+
def compiled_content(rep:, snapshot: nil)
|
67
|
+
snapshot_content = raw_compiled_content(rep: rep, snapshot: snapshot)
|
68
|
+
|
69
|
+
if snapshot_content.binary?
|
70
|
+
raise Nanoc::Core::Errors::CannotGetCompiledContentOfBinaryItem.new(rep)
|
71
|
+
end
|
72
|
+
|
73
|
+
snapshot_content.string
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -78,6 +78,18 @@
|
|
78
78
|
"checks": {
|
79
79
|
"type": "object",
|
80
80
|
"properties": {
|
81
|
+
"all": {
|
82
|
+
"type": "object",
|
83
|
+
"additionalProperties": false,
|
84
|
+
"properties": {
|
85
|
+
"exclude_files": {
|
86
|
+
"type": "array",
|
87
|
+
"items": {
|
88
|
+
"type": "string"
|
89
|
+
}
|
90
|
+
}
|
91
|
+
}
|
92
|
+
},
|
81
93
|
"internal_links": {
|
82
94
|
"type": "object",
|
83
95
|
"additionalProperties": false,
|
@@ -0,0 +1,203 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Nanoc
|
4
|
+
module Core
|
5
|
+
# @api private
|
6
|
+
class DependencyStore < ::Nanoc::Core::Store
|
7
|
+
include Nanoc::Core::ContractsSupport
|
8
|
+
|
9
|
+
C_ATTR = C::Or[C::IterOf[Symbol], C::Bool]
|
10
|
+
C_RAW_CONTENT = C::Or[C::IterOf[C::Or[String, Regexp]], C::Bool]
|
11
|
+
C_KEYWORD_PROPS = C::KeywordArgs[raw_content: C::Optional[C_RAW_CONTENT], attributes: C::Optional[C_ATTR], compiled_content: C::Optional[C::Bool], path: C::Optional[C::Bool]]
|
12
|
+
C_OBJ_SRC = Nanoc::Core::Item
|
13
|
+
C_OBJ_DST = C::Or[Nanoc::Core::Item, Nanoc::Core::Layout, Nanoc::Core::Configuration, Nanoc::Core::IdentifiableCollection]
|
14
|
+
|
15
|
+
attr_reader :items
|
16
|
+
attr_reader :layouts
|
17
|
+
|
18
|
+
contract Nanoc::Core::ItemCollection, Nanoc::Core::LayoutCollection, Nanoc::Core::Configuration => C::Any
|
19
|
+
def initialize(items, layouts, config)
|
20
|
+
super(Nanoc::Core::Store.tmp_path_for(config: config, store_name: 'dependencies'), 5)
|
21
|
+
|
22
|
+
@items = items
|
23
|
+
@layouts = layouts
|
24
|
+
|
25
|
+
@refs2objs = {}
|
26
|
+
items.each { |o| add_vertex_for(o) }
|
27
|
+
layouts.each { |o| add_vertex_for(o) }
|
28
|
+
add_vertex_for(config)
|
29
|
+
add_vertex_for(items)
|
30
|
+
add_vertex_for(layouts)
|
31
|
+
|
32
|
+
@new_objects = []
|
33
|
+
@graph = Nanoc::Core::DirectedGraph.new([nil] + objs2refs(@items) + objs2refs(@layouts))
|
34
|
+
end
|
35
|
+
|
36
|
+
contract C_OBJ_SRC => C::ArrayOf[Nanoc::Core::Dependency]
|
37
|
+
def dependencies_causing_outdatedness_of(object)
|
38
|
+
objects_causing_outdatedness_of(object).map do |other_object|
|
39
|
+
props = props_for(other_object, object)
|
40
|
+
|
41
|
+
Nanoc::Core::Dependency.new(
|
42
|
+
other_object,
|
43
|
+
object,
|
44
|
+
Nanoc::Core::DependencyProps.new(
|
45
|
+
raw_content: props.fetch(:raw_content, false),
|
46
|
+
attributes: props.fetch(:attributes, false),
|
47
|
+
compiled_content: props.fetch(:compiled_content, false),
|
48
|
+
path: props.fetch(:path, false),
|
49
|
+
),
|
50
|
+
)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def items=(items)
|
55
|
+
@items = items
|
56
|
+
items.each { |o| @refs2objs[obj2ref(o)] = o }
|
57
|
+
add_vertex_for(items)
|
58
|
+
end
|
59
|
+
|
60
|
+
def layouts=(layouts)
|
61
|
+
@layouts = layouts
|
62
|
+
layouts.each { |o| @refs2objs[obj2ref(o)] = o }
|
63
|
+
add_vertex_for(layouts)
|
64
|
+
end
|
65
|
+
|
66
|
+
def new_items
|
67
|
+
@new_objects.select { |o| o.is_a?(Nanoc::Core::Item) }
|
68
|
+
end
|
69
|
+
|
70
|
+
def new_layouts
|
71
|
+
@new_objects.select { |o| o.is_a?(Nanoc::Core::Layout) }
|
72
|
+
end
|
73
|
+
|
74
|
+
# Returns the direct dependencies for the given object.
|
75
|
+
#
|
76
|
+
# The direct dependencies of the given object include the items and
|
77
|
+
# layouts that, when outdated will cause the given object to be marked as
|
78
|
+
# outdated. Indirect dependencies will not be returned (e.g. if A depends
|
79
|
+
# on B which depends on C, then the direct dependencies of A do not
|
80
|
+
# include C).
|
81
|
+
#
|
82
|
+
# The direct predecessors can include nil, which indicates an item that is
|
83
|
+
# no longer present in the site.
|
84
|
+
#
|
85
|
+
# @param [Nanoc::Core::Item, Nanoc::Core::Layout] object The object for
|
86
|
+
# which to fetch the direct predecessors
|
87
|
+
#
|
88
|
+
# @return [Array<Nanoc::Core::Item, Nanoc::Core::Layout, nil>] The direct
|
89
|
+
# predecessors of
|
90
|
+
# the given object
|
91
|
+
def objects_causing_outdatedness_of(object)
|
92
|
+
refs2objs(@graph.direct_predecessors_of(obj2ref(object)))
|
93
|
+
end
|
94
|
+
|
95
|
+
contract C::Maybe[C_OBJ_SRC], C::Maybe[C_OBJ_DST], C_KEYWORD_PROPS => C::Any
|
96
|
+
# Records a dependency from `src` to `dst` in the dependency graph. When
|
97
|
+
# `dst` is oudated, `src` will also become outdated.
|
98
|
+
#
|
99
|
+
# @param [Nanoc::Core::Item, Nanoc::Core::Layout] src The source of the dependency,
|
100
|
+
# i.e. the object that will become outdated if dst is outdated
|
101
|
+
#
|
102
|
+
# @param [Nanoc::Core::Item, Nanoc::Core::Layout] dst The destination of the
|
103
|
+
# dependency, i.e. the object that will cause the source to become
|
104
|
+
# outdated if the destination is outdated
|
105
|
+
#
|
106
|
+
# @return [void]
|
107
|
+
def record_dependency(src, dst, raw_content: false, attributes: false, compiled_content: false, path: false)
|
108
|
+
return if src == dst
|
109
|
+
|
110
|
+
add_vertex_for(src)
|
111
|
+
add_vertex_for(dst)
|
112
|
+
|
113
|
+
src_ref = obj2ref(src)
|
114
|
+
dst_ref = obj2ref(dst)
|
115
|
+
|
116
|
+
existing_props = Nanoc::Core::DependencyProps.new(@graph.props_for(dst_ref, src_ref) || {})
|
117
|
+
new_props = Nanoc::Core::DependencyProps.new(raw_content: raw_content, attributes: attributes, compiled_content: compiled_content, path: path)
|
118
|
+
props = existing_props.merge(new_props)
|
119
|
+
|
120
|
+
@graph.add_edge(dst_ref, src_ref, props: props.to_h)
|
121
|
+
end
|
122
|
+
|
123
|
+
def add_vertex_for(obj)
|
124
|
+
@refs2objs[obj2ref(obj)] = obj
|
125
|
+
end
|
126
|
+
|
127
|
+
# Empties the list of dependencies for the given object. This is necessary
|
128
|
+
# before recompiling the given object, because otherwise old dependencies
|
129
|
+
# will stick around and new dependencies will appear twice. This function
|
130
|
+
# removes all incoming edges for the given vertex.
|
131
|
+
#
|
132
|
+
# @param [Nanoc::Core::Item, Nanoc::Core::Layout] object The object for which to
|
133
|
+
# forget all dependencies
|
134
|
+
#
|
135
|
+
# @return [void]
|
136
|
+
def forget_dependencies_for(object)
|
137
|
+
@graph.delete_edges_to(obj2ref(object))
|
138
|
+
end
|
139
|
+
|
140
|
+
protected
|
141
|
+
|
142
|
+
def obj2ref(obj)
|
143
|
+
obj&.reference
|
144
|
+
end
|
145
|
+
|
146
|
+
def ref2obj(reference)
|
147
|
+
if reference
|
148
|
+
@refs2objs[reference]
|
149
|
+
else
|
150
|
+
nil
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
def objs2refs(objs)
|
155
|
+
objs.map { |o| obj2ref(o) }
|
156
|
+
end
|
157
|
+
|
158
|
+
def refs2objs(refs)
|
159
|
+
refs.map { |r| ref2obj(r) }
|
160
|
+
end
|
161
|
+
|
162
|
+
def props_for(from, to)
|
163
|
+
props = @graph.props_for(obj2ref(from), obj2ref(to)) || {}
|
164
|
+
|
165
|
+
if props.values.any? { |v| v }
|
166
|
+
props
|
167
|
+
else
|
168
|
+
{ raw_content: true, attributes: true, compiled_content: true, path: true }
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
def data
|
173
|
+
{
|
174
|
+
edges: @graph.edges,
|
175
|
+
vertices: @graph.vertices,
|
176
|
+
}
|
177
|
+
end
|
178
|
+
|
179
|
+
def data=(new_data)
|
180
|
+
objects = Set.new(@items.to_a + @layouts.to_a)
|
181
|
+
refs = objs2refs(objects)
|
182
|
+
|
183
|
+
# Create new graph
|
184
|
+
@graph = Nanoc::Core::DirectedGraph.new([nil] + refs)
|
185
|
+
|
186
|
+
# Load vertices
|
187
|
+
previous_refs = new_data[:vertices]
|
188
|
+
previous_objects = Set.new(refs2objs(previous_refs))
|
189
|
+
|
190
|
+
# Load edges
|
191
|
+
new_data[:edges].each do |edge|
|
192
|
+
from_index, to_index, props = *edge
|
193
|
+
from = from_index && previous_refs[from_index]
|
194
|
+
to = to_index && previous_refs[to_index]
|
195
|
+
@graph.add_edge(from, to, props: props)
|
196
|
+
end
|
197
|
+
|
198
|
+
# Record dependency from all items on new items
|
199
|
+
@new_objects = objects - previous_objects
|
200
|
+
end
|
201
|
+
end
|
202
|
+
end
|
203
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Nanoc
|
4
|
+
module Core
|
5
|
+
# @api private
|
6
|
+
class DependencyTracker
|
7
|
+
include Nanoc::Core::ContractsSupport
|
8
|
+
|
9
|
+
C_OBJ = C::Or[Nanoc::Core::Item, Nanoc::Core::Layout, Nanoc::Core::Configuration, Nanoc::Core::IdentifiableCollection]
|
10
|
+
C_RAW_CONTENT = C::Or[C::IterOf[C::Or[String, Regexp]], C::Bool]
|
11
|
+
C_ATTR = C::Or[C::IterOf[Symbol], C::Bool]
|
12
|
+
C_ARGS = C::KeywordArgs[raw_content: C::Optional[C_RAW_CONTENT], attributes: C::Optional[C_ATTR], compiled_content: C::Optional[C::Bool], path: C::Optional[C::Bool]]
|
13
|
+
|
14
|
+
class Null
|
15
|
+
include Nanoc::Core::ContractsSupport
|
16
|
+
|
17
|
+
contract C_OBJ, C_ARGS => C::Any
|
18
|
+
def enter(_obj, raw_content: false, attributes: false, compiled_content: false, path: false); end
|
19
|
+
|
20
|
+
contract C_OBJ => C::Any
|
21
|
+
def exit; end
|
22
|
+
|
23
|
+
contract C_OBJ, C_ARGS => C::Any
|
24
|
+
def bounce(_obj, raw_content: false, attributes: false, compiled_content: false, path: false); end
|
25
|
+
end
|
26
|
+
|
27
|
+
attr_reader :dependency_store
|
28
|
+
|
29
|
+
def initialize(dependency_store)
|
30
|
+
@dependency_store = dependency_store
|
31
|
+
@stack = []
|
32
|
+
end
|
33
|
+
|
34
|
+
contract C_OBJ, C_ARGS => C::Any
|
35
|
+
def enter(obj, raw_content: false, attributes: false, compiled_content: false, path: false)
|
36
|
+
unless @stack.empty?
|
37
|
+
Nanoc::Core::NotificationCenter.post(:dependency_created, @stack.last, obj)
|
38
|
+
@dependency_store.record_dependency(
|
39
|
+
@stack.last,
|
40
|
+
obj,
|
41
|
+
raw_content: raw_content,
|
42
|
+
attributes: attributes,
|
43
|
+
compiled_content: compiled_content,
|
44
|
+
path: path,
|
45
|
+
)
|
46
|
+
end
|
47
|
+
|
48
|
+
@stack.push(obj)
|
49
|
+
end
|
50
|
+
|
51
|
+
contract C_OBJ => C::Any
|
52
|
+
def exit
|
53
|
+
@stack.pop
|
54
|
+
end
|
55
|
+
|
56
|
+
contract C_OBJ, C_ARGS => C::Any
|
57
|
+
def bounce(obj, raw_content: false, attributes: false, compiled_content: false, path: false)
|
58
|
+
enter(obj, raw_content: raw_content, attributes: attributes, compiled_content: compiled_content, path: path)
|
59
|
+
exit
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Nanoc
|
4
|
+
module Core
|
5
|
+
module Errors
|
6
|
+
# Error that is raised when the compiled content at a non-existing snapshot
|
7
|
+
# is requested.
|
8
|
+
class NoSuchSnapshot < ::Nanoc::Core::Error
|
9
|
+
# @return [Nanoc::Core::ItemRep] The item rep from which the compiled content
|
10
|
+
# was requested
|
11
|
+
attr_reader :item_rep
|
12
|
+
|
13
|
+
# @return [Symbol] The requested snapshot
|
14
|
+
attr_reader :snapshot
|
15
|
+
|
16
|
+
# @param [Nanoc::Core::ItemRep] item_rep The item rep from which the compiled
|
17
|
+
# content was requested
|
18
|
+
#
|
19
|
+
# @param [Symbol] snapshot The requested snapshot
|
20
|
+
def initialize(item_rep, snapshot)
|
21
|
+
@item_rep = item_rep
|
22
|
+
@snapshot = snapshot
|
23
|
+
super("The “#{item_rep.inspect}” item rep does not have a snapshot “#{snapshot.inspect}”")
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# Error that is raised when an rep cannot be compiled because it depends
|
28
|
+
# on other representations.
|
29
|
+
class UnmetDependency < ::Nanoc::Core::Error
|
30
|
+
# @return [Nanoc::Core::ItemRep] The item representation that cannot yet be
|
31
|
+
# compiled
|
32
|
+
attr_reader :rep
|
33
|
+
|
34
|
+
# @return [Symbol] The name of the snapshot that cannot yet be compiled
|
35
|
+
attr_reader :snapshot_name
|
36
|
+
|
37
|
+
# @param [Nanoc::Core::ItemRep] rep The item representation that cannot yet be
|
38
|
+
# compiled
|
39
|
+
def initialize(rep, snapshot_name)
|
40
|
+
@rep = rep
|
41
|
+
@snapshot_name = snapshot_name
|
42
|
+
|
43
|
+
super("The current item cannot be compiled yet because of an unmet dependency on the “#{rep.item.identifier}” item (rep “#{rep.name}”, snapshot “#{snapshot_name}”).")
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# Error that is raised when the compiled content of a binary item is attempted to be accessed.
|
48
|
+
class CannotGetCompiledContentOfBinaryItem < ::Nanoc::Core::Error
|
49
|
+
# @param [Nanoc::Core::ItemRep] rep The binary item representation whose compiled content was attempted to be accessed
|
50
|
+
def initialize(rep)
|
51
|
+
super("You cannot access the compiled content of a binary item representation (but you can access the path). The offending item rep is #{rep}.")
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Nanoc
|
4
|
+
module Core
|
5
|
+
# Stores item reps (in memory).
|
6
|
+
#
|
7
|
+
# @api private
|
8
|
+
class ItemRepRepo
|
9
|
+
include Enumerable
|
10
|
+
|
11
|
+
def initialize
|
12
|
+
@reps = []
|
13
|
+
@reps_by_item = {}
|
14
|
+
end
|
15
|
+
|
16
|
+
def <<(rep)
|
17
|
+
@reps << rep
|
18
|
+
|
19
|
+
@reps_by_item[rep.item] ||= []
|
20
|
+
@reps_by_item[rep.item] << rep
|
21
|
+
end
|
22
|
+
|
23
|
+
def to_a
|
24
|
+
@reps
|
25
|
+
end
|
26
|
+
|
27
|
+
def each(&block)
|
28
|
+
@reps.each(&block)
|
29
|
+
self
|
30
|
+
end
|
31
|
+
|
32
|
+
def [](item)
|
33
|
+
@reps_by_item.fetch(item, [])
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Nanoc
|
4
|
+
module Core
|
5
|
+
# Module that contains all outdatedness reasons.
|
6
|
+
#
|
7
|
+
# @api private
|
8
|
+
module OutdatednessReasons
|
9
|
+
# A generic outdatedness reason. An outdatedness reason is basically a
|
10
|
+
# descriptive message that explains why a given object is outdated.
|
11
|
+
class Generic
|
12
|
+
# @return [String] A descriptive message for this outdatedness reason
|
13
|
+
attr_reader :message
|
14
|
+
|
15
|
+
# @return [Nanoc::Core::DependencyProps]
|
16
|
+
attr_reader :props
|
17
|
+
|
18
|
+
# @param [String] message The descriptive message for this outdatedness
|
19
|
+
# reason
|
20
|
+
def initialize(message, props = Nanoc::Core::DependencyProps.new)
|
21
|
+
@message = message
|
22
|
+
@props = props
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
CodeSnippetsModified = Generic.new(
|
27
|
+
'The code snippets have been modified since the last time the site was compiled.',
|
28
|
+
Nanoc::Core::DependencyProps.new(raw_content: true, attributes: true, compiled_content: true, path: true),
|
29
|
+
)
|
30
|
+
|
31
|
+
DependenciesOutdated = Generic.new(
|
32
|
+
'This item uses content or attributes that have changed since the last time the site was compiled.',
|
33
|
+
)
|
34
|
+
|
35
|
+
NotWritten = Generic.new(
|
36
|
+
'This item representation has not yet been written to the output directory (but it does have a path).',
|
37
|
+
Nanoc::Core::DependencyProps.new(raw_content: true, attributes: true, compiled_content: true, path: true),
|
38
|
+
)
|
39
|
+
|
40
|
+
RulesModified = Generic.new(
|
41
|
+
'The rules file has been modified since the last time the site was compiled.',
|
42
|
+
Nanoc::Core::DependencyProps.new(compiled_content: true, path: true),
|
43
|
+
)
|
44
|
+
|
45
|
+
ContentModified = Generic.new(
|
46
|
+
'The content of this item has been modified since the last time the site was compiled.',
|
47
|
+
Nanoc::Core::DependencyProps.new(raw_content: true, compiled_content: true),
|
48
|
+
)
|
49
|
+
|
50
|
+
class DocumentCollectionExtended < Generic
|
51
|
+
attr_reader :objects
|
52
|
+
|
53
|
+
def initialize(objects)
|
54
|
+
super(
|
55
|
+
'New items/layouts have been added to the site.',
|
56
|
+
Nanoc::Core::DependencyProps.new(raw_content: true),
|
57
|
+
)
|
58
|
+
|
59
|
+
@objects = objects
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
class ItemCollectionExtended < DocumentCollectionExtended
|
64
|
+
end
|
65
|
+
|
66
|
+
class LayoutCollectionExtended < DocumentCollectionExtended
|
67
|
+
end
|
68
|
+
|
69
|
+
class AttributesModified < Generic
|
70
|
+
attr_reader :attributes
|
71
|
+
|
72
|
+
def initialize(attributes)
|
73
|
+
super(
|
74
|
+
'The attributes of this item have been modified since the last time the site was compiled.',
|
75
|
+
Nanoc::Core::DependencyProps.new(attributes: true, compiled_content: true),
|
76
|
+
)
|
77
|
+
|
78
|
+
@attributes = attributes
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
UsesAlwaysOutdatedFilter = Generic.new(
|
83
|
+
'This item rep uses one or more filters that cannot track dependencies, and will thus always be considered as outdated.',
|
84
|
+
Nanoc::Core::DependencyProps.new(raw_content: true, attributes: true, compiled_content: true),
|
85
|
+
)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Nanoc
|
4
|
+
module Core
|
5
|
+
# @api private
|
6
|
+
class OutdatednessRule
|
7
|
+
include Nanoc::Core::ContractsSupport
|
8
|
+
include Singleton
|
9
|
+
|
10
|
+
def call(obj, outdatedness_checker)
|
11
|
+
Nanoc::Core::Instrumentor.call(:outdatedness_rule_ran, self.class) do
|
12
|
+
apply(obj, outdatedness_checker)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def apply(_obj, _outdatedness_checker)
|
17
|
+
raise NotImplementedError.new('Nanoc::Core::OutdatednessRule subclasses must implement #apply')
|
18
|
+
end
|
19
|
+
|
20
|
+
contract C::None => String
|
21
|
+
def inspect
|
22
|
+
"#{self.class.name}(#{reason})"
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.affects_props(*names)
|
26
|
+
@affected_props = Set.new(names)
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.affected_props
|
30
|
+
@affected_props
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Nanoc
|
4
|
+
module Core
|
5
|
+
# @api private
|
6
|
+
class OutdatednessStatus
|
7
|
+
attr_reader :reasons
|
8
|
+
attr_reader :props
|
9
|
+
|
10
|
+
def initialize(reasons: [], props: Nanoc::Core::DependencyProps.new)
|
11
|
+
@reasons = reasons
|
12
|
+
@props = props
|
13
|
+
end
|
14
|
+
|
15
|
+
def useful_to_apply?(rule)
|
16
|
+
(rule.affected_props - @props.active).any?
|
17
|
+
end
|
18
|
+
|
19
|
+
def update(reason)
|
20
|
+
self.class.new(
|
21
|
+
reasons: @reasons + [reason],
|
22
|
+
props: @props.merge(reason.props),
|
23
|
+
)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Nanoc
|
4
|
+
module Core
|
5
|
+
# @api private
|
6
|
+
class OutdatednessStore < ::Nanoc::Core::Store
|
7
|
+
include Nanoc::Core::ContractsSupport
|
8
|
+
|
9
|
+
contract C::KeywordArgs[config: Nanoc::Core::Configuration] => C::Any
|
10
|
+
def initialize(config:)
|
11
|
+
super(Nanoc::Core::Store.tmp_path_for(config: config, store_name: 'outdatedness'), 1)
|
12
|
+
|
13
|
+
@outdated_refs = Set.new
|
14
|
+
end
|
15
|
+
|
16
|
+
contract Nanoc::Core::ItemRep => C::Bool
|
17
|
+
def include?(obj)
|
18
|
+
@outdated_refs.include?(obj.reference)
|
19
|
+
end
|
20
|
+
|
21
|
+
contract Nanoc::Core::ItemRep => self
|
22
|
+
def add(obj)
|
23
|
+
@outdated_refs << obj.reference
|
24
|
+
self
|
25
|
+
end
|
26
|
+
|
27
|
+
contract Nanoc::Core::ItemRep => self
|
28
|
+
def remove(obj)
|
29
|
+
@outdated_refs.delete(obj.reference)
|
30
|
+
self
|
31
|
+
end
|
32
|
+
|
33
|
+
contract C::None => C::Bool
|
34
|
+
def empty?
|
35
|
+
@outdated_refs.empty?
|
36
|
+
end
|
37
|
+
|
38
|
+
contract C::None => self
|
39
|
+
def clear
|
40
|
+
@outdated_refs = Set.new
|
41
|
+
self
|
42
|
+
end
|
43
|
+
|
44
|
+
protected
|
45
|
+
|
46
|
+
def data
|
47
|
+
@outdated_refs
|
48
|
+
end
|
49
|
+
|
50
|
+
def data=(new_data)
|
51
|
+
@outdated_refs = Set.new(new_data)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Nanoc
|
4
|
+
module Core
|
5
|
+
class PrefixedDataSource < Nanoc::Core::DataSource
|
6
|
+
def initialize(data_source, items_prefix, layouts_prefix)
|
7
|
+
super({}, '/', '/', {})
|
8
|
+
|
9
|
+
@data_source = data_source
|
10
|
+
@items_prefix = items_prefix
|
11
|
+
@layouts_prefix = layouts_prefix
|
12
|
+
end
|
13
|
+
|
14
|
+
def items
|
15
|
+
@data_source.items.map { |d| d.with_identifier_prefix(@items_prefix) }
|
16
|
+
end
|
17
|
+
|
18
|
+
def layouts
|
19
|
+
@data_source.layouts.map { |d| d.with_identifier_prefix(@layouts_prefix) }
|
20
|
+
end
|
21
|
+
|
22
|
+
def item_changes
|
23
|
+
@data_source.item_changes
|
24
|
+
end
|
25
|
+
|
26
|
+
def layout_changes
|
27
|
+
@data_source.layout_changes
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,114 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Nanoc
|
4
|
+
module Core
|
5
|
+
# An abstract superclass for classes that need to store data to the
|
6
|
+
# filesystem, such as checksums, cached compiled content and dependency
|
7
|
+
# graphs.
|
8
|
+
#
|
9
|
+
# Each store has a version number. When attempting to load data from a store
|
10
|
+
# that has an incompatible version number, no data will be loaded, but
|
11
|
+
# {#version_mismatch_detected} will be called.
|
12
|
+
#
|
13
|
+
# @abstract Subclasses must implement {#data} and {#data=}, and may
|
14
|
+
# implement {#no_data_found} and {#version_mismatch_detected}.
|
15
|
+
#
|
16
|
+
# @api private
|
17
|
+
class Store
|
18
|
+
include Nanoc::Core::ContractsSupport
|
19
|
+
|
20
|
+
# @return [String] The name of the file where data will be loaded from and
|
21
|
+
# stored to.
|
22
|
+
attr_reader :filename
|
23
|
+
|
24
|
+
# @return [Numeric] The version number corresponding to the file format
|
25
|
+
# the data is in. When the file format changes, the version number
|
26
|
+
# should be incremented.
|
27
|
+
attr_reader :version
|
28
|
+
|
29
|
+
# Creates a new store for the given filename.
|
30
|
+
#
|
31
|
+
# @param [String] filename The name of the file where data will be loaded
|
32
|
+
# from and stored to.
|
33
|
+
#
|
34
|
+
# @param [Numeric] version The version number corresponding to the file
|
35
|
+
# format the data is in. When the file format changes, the version
|
36
|
+
# number should be incremented.
|
37
|
+
def initialize(filename, version)
|
38
|
+
@filename = filename
|
39
|
+
@version = version
|
40
|
+
end
|
41
|
+
|
42
|
+
# Logic for building tmp path from active environment and store name
|
43
|
+
# @api private
|
44
|
+
contract C::KeywordArgs[config: Nanoc::Core::Configuration, store_name: String] => C::AbsolutePathString
|
45
|
+
def self.tmp_path_for(store_name:, config:)
|
46
|
+
File.absolute_path(
|
47
|
+
File.join(tmp_path_prefix(config.output_dir), store_name),
|
48
|
+
config.dir,
|
49
|
+
)
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.tmp_path_prefix(output_dir)
|
53
|
+
dir = Digest::SHA1.hexdigest(output_dir)[0..12]
|
54
|
+
File.join('tmp', 'nanoc', dir)
|
55
|
+
end
|
56
|
+
|
57
|
+
# @group Loading and storing data
|
58
|
+
|
59
|
+
# @return The data that should be written to the disk
|
60
|
+
#
|
61
|
+
# @abstract This method must be implemented by the subclass.
|
62
|
+
def data
|
63
|
+
raise NotImplementedError.new('Nanoc::Core::Store subclasses must implement #data and #data=')
|
64
|
+
end
|
65
|
+
|
66
|
+
# @param new_data The data that has been loaded from the disk
|
67
|
+
#
|
68
|
+
# @abstract This method must be implemented by the subclass.
|
69
|
+
#
|
70
|
+
# @return [void]
|
71
|
+
def data=(new_data) # rubocop:disable Lint/UnusedMethodArgument
|
72
|
+
raise NotImplementedError.new('Nanoc::Core::Store subclasses must implement #data and #data=')
|
73
|
+
end
|
74
|
+
|
75
|
+
# Loads the data from the filesystem into memory. This method will set the
|
76
|
+
# loaded data using the {#data=} method.
|
77
|
+
#
|
78
|
+
# @return [void]
|
79
|
+
def load
|
80
|
+
return unless File.file?(filename)
|
81
|
+
|
82
|
+
begin
|
83
|
+
pstore.transaction do
|
84
|
+
return if pstore[:version] != version
|
85
|
+
|
86
|
+
self.data = pstore[:data]
|
87
|
+
end
|
88
|
+
rescue
|
89
|
+
FileUtils.rm_f(filename)
|
90
|
+
load
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
# Stores the data contained in memory to the filesystem. This method will
|
95
|
+
# use the {#data} method to fetch the data that should be written.
|
96
|
+
#
|
97
|
+
# @return [void]
|
98
|
+
def store
|
99
|
+
FileUtils.mkdir_p(File.dirname(filename))
|
100
|
+
|
101
|
+
pstore.transaction do
|
102
|
+
pstore[:data] = data
|
103
|
+
pstore[:version] = version
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
private
|
108
|
+
|
109
|
+
def pstore
|
110
|
+
@pstore ||= PStore.new(filename)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Nanoc
|
4
|
+
module Core
|
5
|
+
# Represents a cache than can be used to store already compiled content,
|
6
|
+
# to prevent it from being needlessly recompiled.
|
7
|
+
#
|
8
|
+
# @api private
|
9
|
+
class TextualCompiledContentCache < ::Nanoc::Core::Store
|
10
|
+
include Nanoc::Core::ContractsSupport
|
11
|
+
|
12
|
+
contract C::KeywordArgs[config: Nanoc::Core::Configuration] => C::Any
|
13
|
+
def initialize(config:)
|
14
|
+
super(Nanoc::Core::Store.tmp_path_for(config: config, store_name: 'compiled_content'), 2)
|
15
|
+
|
16
|
+
@cache = {}
|
17
|
+
end
|
18
|
+
|
19
|
+
contract Nanoc::Core::ItemRep => C::Maybe[C::HashOf[Symbol => Nanoc::Core::Content]]
|
20
|
+
# Returns the cached compiled content for the given item representation.
|
21
|
+
#
|
22
|
+
# This cached compiled content is a hash where the keys are the snapshot
|
23
|
+
# names, and the values the compiled content at the given snapshot.
|
24
|
+
def [](rep)
|
25
|
+
item_cache = @cache[rep.item.identifier] || {}
|
26
|
+
item_cache[rep.name]
|
27
|
+
end
|
28
|
+
|
29
|
+
contract Nanoc::Core::ItemRep => C::Bool
|
30
|
+
def include?(rep)
|
31
|
+
item_cache = @cache[rep.item.identifier] || {}
|
32
|
+
item_cache.key?(rep.name)
|
33
|
+
end
|
34
|
+
|
35
|
+
contract Nanoc::Core::ItemRep, C::HashOf[Symbol => Nanoc::Core::Content] => C::Any
|
36
|
+
# Sets the compiled content for the given representation.
|
37
|
+
#
|
38
|
+
# This cached compiled content is a hash where the keys are the snapshot
|
39
|
+
# names, and the values the compiled content at the given snapshot.
|
40
|
+
def []=(rep, content)
|
41
|
+
# FIXME: once the binary content cache is properly enabled (no longer
|
42
|
+
# behind a feature flag), change contract to be TextualContent, rather
|
43
|
+
# than Content.
|
44
|
+
|
45
|
+
@cache[rep.item.identifier] ||= {}
|
46
|
+
@cache[rep.item.identifier][rep.name] = content
|
47
|
+
end
|
48
|
+
|
49
|
+
def prune(items:)
|
50
|
+
item_identifiers = Set.new(items.map(&:identifier))
|
51
|
+
|
52
|
+
@cache.each_key do |key|
|
53
|
+
# TODO: remove unused item reps
|
54
|
+
next if item_identifiers.include?(key)
|
55
|
+
|
56
|
+
@cache.delete(key)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
# True if there is cached compiled content available for this item, and
|
61
|
+
# all entries are textual.
|
62
|
+
def full_cache_available?(rep)
|
63
|
+
cache = self[rep]
|
64
|
+
cache ? cache.none? { |_snapshot_name, content| content.binary? } : false
|
65
|
+
end
|
66
|
+
|
67
|
+
protected
|
68
|
+
|
69
|
+
def data
|
70
|
+
@cache
|
71
|
+
end
|
72
|
+
|
73
|
+
def data=(new_data)
|
74
|
+
@cache = {}
|
75
|
+
|
76
|
+
new_data.each_pair do |item_identifier, content_per_rep|
|
77
|
+
@cache[item_identifier] ||= content_per_rep
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
data/lib/nanoc/core/version.rb
CHANGED
data/lib/nanoc/core.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: nanoc-core
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 4.11.
|
4
|
+
version: 4.11.9
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Denis Defreyne
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2019-08-
|
11
|
+
date: 2019-08-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: ddmemoize
|
@@ -121,11 +121,16 @@ files:
|
|
121
121
|
- lib/nanoc/core/action_provider.rb
|
122
122
|
- lib/nanoc/core/action_sequence.rb
|
123
123
|
- lib/nanoc/core/action_sequence_builder.rb
|
124
|
+
- lib/nanoc/core/action_sequence_store.rb
|
124
125
|
- lib/nanoc/core/aggregate_data_source.rb
|
126
|
+
- lib/nanoc/core/binary_compiled_content_cache.rb
|
125
127
|
- lib/nanoc/core/binary_content.rb
|
126
128
|
- lib/nanoc/core/checksum_collection.rb
|
129
|
+
- lib/nanoc/core/checksum_store.rb
|
127
130
|
- lib/nanoc/core/checksummer.rb
|
128
131
|
- lib/nanoc/core/code_snippet.rb
|
132
|
+
- lib/nanoc/core/compiled_content_cache.rb
|
133
|
+
- lib/nanoc/core/compiled_content_store.rb
|
129
134
|
- lib/nanoc/core/configuration-schema.json
|
130
135
|
- lib/nanoc/core/configuration.rb
|
131
136
|
- lib/nanoc/core/content.rb
|
@@ -137,9 +142,12 @@ files:
|
|
137
142
|
- lib/nanoc/core/data_source.rb
|
138
143
|
- lib/nanoc/core/dependency.rb
|
139
144
|
- lib/nanoc/core/dependency_props.rb
|
145
|
+
- lib/nanoc/core/dependency_store.rb
|
146
|
+
- lib/nanoc/core/dependency_tracker.rb
|
140
147
|
- lib/nanoc/core/directed_graph.rb
|
141
148
|
- lib/nanoc/core/document.rb
|
142
149
|
- lib/nanoc/core/error.rb
|
150
|
+
- lib/nanoc/core/errors.rb
|
143
151
|
- lib/nanoc/core/identifiable_collection.rb
|
144
152
|
- lib/nanoc/core/identifier.rb
|
145
153
|
- lib/nanoc/core/in_memory_data_source.rb
|
@@ -147,11 +155,17 @@ files:
|
|
147
155
|
- lib/nanoc/core/item.rb
|
148
156
|
- lib/nanoc/core/item_collection.rb
|
149
157
|
- lib/nanoc/core/item_rep.rb
|
158
|
+
- lib/nanoc/core/item_rep_repo.rb
|
150
159
|
- lib/nanoc/core/layout.rb
|
151
160
|
- lib/nanoc/core/layout_collection.rb
|
152
161
|
- lib/nanoc/core/lazy_value.rb
|
153
162
|
- lib/nanoc/core/notification_center.rb
|
163
|
+
- lib/nanoc/core/outdatedness_reasons.rb
|
164
|
+
- lib/nanoc/core/outdatedness_rule.rb
|
165
|
+
- lib/nanoc/core/outdatedness_status.rb
|
166
|
+
- lib/nanoc/core/outdatedness_store.rb
|
154
167
|
- lib/nanoc/core/pattern.rb
|
168
|
+
- lib/nanoc/core/prefixed_data_source.rb
|
155
169
|
- lib/nanoc/core/processing_action.rb
|
156
170
|
- lib/nanoc/core/processing_actions.rb
|
157
171
|
- lib/nanoc/core/processing_actions/filter.rb
|
@@ -160,8 +174,10 @@ files:
|
|
160
174
|
- lib/nanoc/core/regexp_pattern.rb
|
161
175
|
- lib/nanoc/core/site.rb
|
162
176
|
- lib/nanoc/core/snapshot_def.rb
|
177
|
+
- lib/nanoc/core/store.rb
|
163
178
|
- lib/nanoc/core/string_pattern.rb
|
164
179
|
- lib/nanoc/core/temp_filename_factory.rb
|
180
|
+
- lib/nanoc/core/textual_compiled_content_cache.rb
|
165
181
|
- lib/nanoc/core/textual_content.rb
|
166
182
|
- lib/nanoc/core/version.rb
|
167
183
|
homepage: https://nanoc.ws/
|
@@ -183,7 +199,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
183
199
|
- !ruby/object:Gem::Version
|
184
200
|
version: '0'
|
185
201
|
requirements: []
|
186
|
-
rubygems_version: 3.0.
|
202
|
+
rubygems_version: 3.0.6
|
187
203
|
signing_key:
|
188
204
|
specification_version: 4
|
189
205
|
summary: Core of Nanoc
|