nanoc-core 4.11.8 → 4.11.9
Sign up to get free protection for your applications and to get access to all the features.
- 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
|