nanoc 4.7.0 → 4.7.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile.lock +4 -4
- data/NEWS.md +12 -1
- data/README.md +1 -1
- data/lib/nanoc/base/entities/directed_graph.rb +16 -0
- data/lib/nanoc/base/entities/identifiable_collection.rb +24 -8
- data/lib/nanoc/base/entities/outdatedness_reasons.rb +5 -0
- data/lib/nanoc/base/errors.rb +11 -6
- data/lib/nanoc/base/memoization.rb +6 -23
- data/lib/nanoc/base/services/compiler/phases/abstract.rb +3 -3
- data/lib/nanoc/base/services/compiler/phases/cache.rb +1 -3
- data/lib/nanoc/base/services/compiler/phases/mark_done.rb +1 -3
- data/lib/nanoc/base/services/compiler/phases/recalculate.rb +1 -3
- data/lib/nanoc/base/services/compiler/phases/resume.rb +1 -3
- data/lib/nanoc/base/services/compiler/phases/write.rb +1 -3
- data/lib/nanoc/base/services/filter.rb +15 -0
- data/lib/nanoc/base/services/item_rep_selector.rb +1 -1
- data/lib/nanoc/base/services/outdatedness_checker.rb +3 -1
- data/lib/nanoc/base/services/outdatedness_rule.rb +7 -0
- data/lib/nanoc/base/services/outdatedness_rules.rb +16 -0
- data/lib/nanoc/cli/commands/compile.rb +12 -411
- data/lib/nanoc/cli/commands/compile_listeners/abstract.rb +30 -0
- data/lib/nanoc/cli/commands/compile_listeners/debug_printer.rb +34 -0
- data/lib/nanoc/cli/commands/compile_listeners/diff_generator.rb +91 -0
- data/lib/nanoc/cli/commands/compile_listeners/file_action_printer.rb +61 -0
- data/lib/nanoc/cli/commands/compile_listeners/stack_prof_profiler.rb +22 -0
- data/lib/nanoc/cli/commands/compile_listeners/timing_recorder.rb +174 -0
- data/lib/nanoc/filters/xsl.rb +2 -0
- data/lib/nanoc/version.rb +1 -1
- data/spec/nanoc/base/directed_graph_spec.rb +54 -0
- data/spec/nanoc/base/errors/dependency_cycle_spec.rb +32 -0
- data/spec/nanoc/base/filter_spec.rb +36 -0
- data/spec/nanoc/base/services/compiler/phases/abstract_spec.rb +23 -11
- data/spec/nanoc/base/services/outdatedness_rules_spec.rb +41 -0
- data/spec/nanoc/cli/commands/compile/file_action_printer_spec.rb +1 -1
- data/spec/nanoc/cli/commands/compile/timing_recorder_spec.rb +124 -54
- data/spec/nanoc/cli/commands/compile_spec.rb +1 -1
- data/spec/nanoc/regressions/gh_1040_spec.rb +1 -1
- data/spec/nanoc/regressions/gh_924_spec.rb +89 -0
- data/test/base/test_compiler.rb +1 -1
- data/test/base/test_memoization.rb +6 -0
- data/test/cli/commands/test_compile.rb +2 -2
- metadata +12 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3697f07907752b5c0010bbf3fb01e701d6128bba
|
4
|
+
data.tar.gz: 70dfb18ac4973838113380a201ddde4468f2b13b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e8810bd89fcd0e3c65f647ec3b99a76454dc2fa35169b5b4e66317f178b260e2e8229686342aee8c83929e7de04b323c98137c8c8b690c7b1259fabed99e2147
|
7
|
+
data.tar.gz: 144aacfa4f5981109e514c8c33cde796598e86ec27c2b6d115ff2c4b497dc25b5d7a5950a071ad4e23dce5c68063b699b3fb42258f6b0f7c31ded9a81c311177
|
data/Gemfile.lock
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
GIT
|
2
2
|
remote: https://github.com/bbatsov/rubocop.git
|
3
|
-
revision:
|
3
|
+
revision: a047a11633e104d5ee9ca4d2957e4b1cf571f30b
|
4
4
|
specs:
|
5
5
|
rubocop (0.47.1)
|
6
6
|
parser (>= 2.3.3.1, < 3.0)
|
@@ -27,7 +27,7 @@ GIT
|
|
27
27
|
PATH
|
28
28
|
remote: .
|
29
29
|
specs:
|
30
|
-
nanoc (4.7.
|
30
|
+
nanoc (4.7.1)
|
31
31
|
cri (~> 2.3)
|
32
32
|
ddplugin (~> 1.0)
|
33
33
|
hamster (~> 3.0)
|
@@ -211,7 +211,7 @@ GEM
|
|
211
211
|
fog-voxel (0.1.0)
|
212
212
|
fog-core
|
213
213
|
fog-xml
|
214
|
-
fog-vsphere (1.
|
214
|
+
fog-vsphere (1.8.0)
|
215
215
|
fog-core
|
216
216
|
rbvmomi (~> 1.9)
|
217
217
|
fog-xenserver (0.3.0)
|
@@ -347,7 +347,7 @@ GEM
|
|
347
347
|
trollop (2.1.2)
|
348
348
|
typogruby (1.0.18)
|
349
349
|
rubypants
|
350
|
-
uglifier (3.1.
|
350
|
+
uglifier (3.1.8)
|
351
351
|
execjs (>= 0.3.0, < 3)
|
352
352
|
unicode-display_width (1.1.3)
|
353
353
|
url (0.3.2)
|
data/NEWS.md
CHANGED
@@ -1,10 +1,21 @@
|
|
1
1
|
# Nanoc news
|
2
2
|
|
3
|
+
## 4.7.1 (2017-03-19)
|
4
|
+
|
5
|
+
Fixes:
|
6
|
+
|
7
|
+
* Fixed issue with `:xsl` filter not recompiling when it should (#924, #1127)
|
8
|
+
|
9
|
+
Enhancements:
|
10
|
+
|
11
|
+
* Made `compile --verbose` print percentiles rather than averages (#1122)
|
12
|
+
* Improved dependency cycle error messages (#1123)
|
13
|
+
|
3
14
|
## 4.7 (2017-03-15)
|
4
15
|
|
5
16
|
Features:
|
6
17
|
|
7
|
-
* Added `:erubi` filter (#1103)
|
18
|
+
* Added `:erubi` filter (#1103) [Jan M. Faber]
|
8
19
|
* Added `write ext: 'something'` shortcut (#1079)
|
9
20
|
|
10
21
|
## 4.6.4 (2017-03-10)
|
data/README.md
CHANGED
@@ -19,4 +19,4 @@ Contributions are greatly appreciated! Consult the [Development guidelines](http
|
|
19
19
|
|
20
20
|
Many thanks to everyone who has contributed to Nanoc in one way or another:
|
21
21
|
|
22
|
-
Ale Muñoz, Alexander Mankuta, Andy Drop, Arnau Siches, Ben Armston, Bil Bas, Brian Candler, Bruno Dufour, Cédric Boutillier, Chris Chapman, Chris Eppstein, Christian Plessl, Colin Barrett, Colin Seymour, Croath Liu, Damien Pollet, Dan Callahan, Daniel Hofstetter, Daniel Mendler, Daniel Wollschlaeger, David Alexander, David Everitt, Denis Defreyne, Dennis Sutch, Devon Luke Buchanan, Dmitry Bilunov, Eric Sunshine, Erik Hollensbe, Fabian Buch, Felix Hanley, Garen Torikian, Go Maeda, Grégory Karékinian, Gregory Pakosz, Guilherme Garnier, Hugo Peixoto, Jack Chu, Jake Benilov, Jasper Van der Jeugt, Jeff Forcier, Jim Mendenhall, John Nishinaga, Justin Clift, Justin Hileman, Kevin Lynagh, Lorin Werthen, Louis T., Lucas Vuotto, Mathias Bynens, Matt Keveney, Matthew Frazier, Matthias Beyer, Matthias Reitinger, Matthias Vallentin, Micha Rosenbaum, Michal Cichra, Michal Papis, Mike Pennisi, Nelson Chen, Nicky Peeters, Nikhil Marathe, Oliver Byford, Paul Boone, Peter Aronoff, Raphael von der Grün, Rémi Barraquand, Remko Tronçon, Riley Goodside, Ruben Verborgh, Scott Vokes, Šime Ramov, Simon South, Spencer Whitt, Stanley Rost, Starr Horne, Stefan Bühler, Stuart Montgomery, Takashi Uchibe, Toon Willems, Tuomas Kareinen, Ursula Kallio, Vincent Driessen, Vlatko Kosturjak, whitequark, Xavier Shay, Yannick Ihmels, Zaiste de Grengolada
|
22
|
+
Ale Muñoz, Alexander Mankuta, Andy Drop, Arnau Siches, Ben Armston, Bil Bas, Brian Candler, Bruno Dufour, Cédric Boutillier, Chris Chapman, Chris Eppstein, Christian Plessl, Colin Barrett, Colin Seymour, Croath Liu, Damien Pollet, Dan Callahan, Daniel Hofstetter, Daniel Mendler, Daniel Wollschlaeger, David Alexander, David Everitt, Denis Defreyne, Dennis Sutch, Devon Luke Buchanan, Dmitry Bilunov, Eric Sunshine, Erik Hollensbe, Fabian Buch, Felix Hanley, Garen Torikian, Go Maeda, Grégory Karékinian, Gregory Pakosz, Guilherme Garnier, Hugo Peixoto, Jack Chu, Jake Benilov, Jan M. Faber, Jasper Van der Jeugt, Jeff Forcier, Jim Mendenhall, John Nishinaga, Justin Clift, Justin Hileman, Kevin Lynagh, Lorin Werthen, Louis T., Lucas Vuotto, Mathias Bynens, Matt Keveney, Matthew Frazier, Matthias Beyer, Matthias Reitinger, Matthias Vallentin, Micha Rosenbaum, Michal Cichra, Michal Papis, Mike Pennisi, Nelson Chen, Nicky Peeters, Nikhil Marathe, Oliver Byford, Paul Boone, Peter Aronoff, Raphael von der Grün, Rémi Barraquand, Remko Tronçon, Riley Goodside, Ruben Verborgh, Scott Vokes, Šime Ramov, Simon South, Spencer Whitt, Stanley Rost, Starr Horne, Stefan Bühler, Stuart Montgomery, Takashi Uchibe, Toon Willems, Tuomas Kareinen, Ursula Kallio, Vincent Driessen, Vlatko Kosturjak, whitequark, Xavier Shay, Yannick Ihmels, Zaiste de Grengolada
|
@@ -158,6 +158,22 @@ module Nanoc::Int
|
|
158
158
|
|
159
159
|
# @group Querying the graph
|
160
160
|
|
161
|
+
def any_cycle
|
162
|
+
path = [@vertices.keys.first]
|
163
|
+
|
164
|
+
loop do
|
165
|
+
nexts = direct_successors_of(path.last)
|
166
|
+
cycle_start_index = path.find_index { |node| nexts.include?(node) }
|
167
|
+
if cycle_start_index
|
168
|
+
break path[cycle_start_index..-1]
|
169
|
+
elsif nexts.empty?
|
170
|
+
break nil
|
171
|
+
else
|
172
|
+
path << nexts.sample
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
161
177
|
# Returns the direct predecessors of the given vertex, i.e. the vertices
|
162
178
|
# x where there is an edge from x to the given vertex y.
|
163
179
|
#
|
@@ -4,6 +4,7 @@ module Nanoc::Int
|
|
4
4
|
include Nanoc::Int::ContractsSupport
|
5
5
|
include Enumerable
|
6
6
|
|
7
|
+
extend Nanoc::Int::Memoization
|
7
8
|
extend Forwardable
|
8
9
|
|
9
10
|
def_delegator :@objects, :each
|
@@ -29,15 +30,10 @@ module Nanoc::Int
|
|
29
30
|
|
30
31
|
contract C::Any => C::Maybe[C::RespondTo[:identifier]]
|
31
32
|
def [](arg)
|
32
|
-
|
33
|
-
|
34
|
-
object_with_identifier(arg)
|
35
|
-
when String
|
36
|
-
object_with_identifier(arg) || object_matching_glob(arg)
|
37
|
-
when Regexp
|
38
|
-
@objects.find { |i| i.identifier.to_s =~ arg }
|
33
|
+
if frozen?
|
34
|
+
get_memoized(arg)
|
39
35
|
else
|
40
|
-
|
36
|
+
get_unmemoized(arg)
|
41
37
|
end
|
42
38
|
end
|
43
39
|
|
@@ -61,6 +57,26 @@ module Nanoc::Int
|
|
61
57
|
|
62
58
|
protected
|
63
59
|
|
60
|
+
contract C::Any => C::Maybe[C::RespondTo[:identifier]]
|
61
|
+
def get_unmemoized(arg)
|
62
|
+
case arg
|
63
|
+
when Nanoc::Identifier
|
64
|
+
object_with_identifier(arg)
|
65
|
+
when String
|
66
|
+
object_with_identifier(arg) || object_matching_glob(arg)
|
67
|
+
when Regexp
|
68
|
+
@objects.find { |i| i.identifier.to_s =~ arg }
|
69
|
+
else
|
70
|
+
raise ArgumentError, "don’t know how to fetch objects by #{arg.inspect}"
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
contract C::Any => C::Maybe[C::RespondTo[:identifier]]
|
75
|
+
def get_memoized(arg)
|
76
|
+
get_unmemoized(arg)
|
77
|
+
end
|
78
|
+
memoize :get_memoized
|
79
|
+
|
64
80
|
def object_with_identifier(identifier)
|
65
81
|
if frozen?
|
66
82
|
@mapping[identifier.to_s]
|
@@ -58,5 +58,10 @@ module Nanoc::Int
|
|
58
58
|
'One or more output paths of this item have been modified since the last time the site was compiled.',
|
59
59
|
Props.new(path: true),
|
60
60
|
)
|
61
|
+
|
62
|
+
UsesAlwaysOutdatedFilter = Generic.new(
|
63
|
+
'This item rep uses one or more filters that cannot track dependencies, and will thus always be considered as outdated.',
|
64
|
+
Props.new(raw_content: true, attributes: true, compiled_content: true),
|
65
|
+
)
|
61
66
|
end
|
62
67
|
end
|
data/lib/nanoc/base/errors.rb
CHANGED
@@ -68,12 +68,17 @@ module Nanoc::Int
|
|
68
68
|
|
69
69
|
# Error that is raised during site compilation when an item (directly or
|
70
70
|
# indirectly) includes its own item content, leading to endless recursion.
|
71
|
-
class
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
71
|
+
class DependencyCycle < Generic
|
72
|
+
def initialize(graph)
|
73
|
+
cycle = graph.any_cycle
|
74
|
+
|
75
|
+
msg_bits = []
|
76
|
+
msg_bits << 'The site cannot be compiled because there is a dependency cycle:'
|
77
|
+
msg_bits << ''
|
78
|
+
cycle.each.with_index { |r, i| msg_bits << " (#{i + 1}) item #{r.item.identifier}, rep #{r.name.inspect}, depends on" }
|
79
|
+
msg_bits.last << ' (1)'
|
80
|
+
|
81
|
+
super(msg_bits.map { |x| x + "\n" }.join(''))
|
77
82
|
end
|
78
83
|
end
|
79
84
|
|
@@ -5,23 +5,6 @@ module Nanoc::Int
|
|
5
5
|
#
|
6
6
|
# @api private
|
7
7
|
module Memoization
|
8
|
-
class Wrapper
|
9
|
-
attr_reader :ref
|
10
|
-
|
11
|
-
def initialize(ref)
|
12
|
-
@ref = ref
|
13
|
-
end
|
14
|
-
|
15
|
-
def inspect
|
16
|
-
obj = @ref.object
|
17
|
-
if obj
|
18
|
-
obj.inspect
|
19
|
-
else
|
20
|
-
'<garbage collected>'
|
21
|
-
end
|
22
|
-
end
|
23
|
-
end
|
24
|
-
|
25
8
|
class Value
|
26
9
|
attr_reader :value
|
27
10
|
|
@@ -69,20 +52,20 @@ module Nanoc::Int
|
|
69
52
|
original_method_name = '__nonmemoized_' + method_name.to_s
|
70
53
|
alias_method original_method_name, method_name
|
71
54
|
|
55
|
+
instance_cache = Hash.new { |hash, key| hash[key] = {} }
|
56
|
+
|
72
57
|
define_method(method_name) do |*args|
|
73
|
-
|
74
|
-
@__memoization_cache[method_name] ||= {}
|
75
|
-
method_cache = @__memoization_cache[method_name]
|
58
|
+
instance_method_cache = instance_cache[self]
|
76
59
|
|
77
60
|
value = NONE
|
78
|
-
if
|
79
|
-
object =
|
61
|
+
if instance_method_cache.key?(args)
|
62
|
+
object = instance_method_cache[args].object
|
80
63
|
value = object ? object.value : NONE
|
81
64
|
end
|
82
65
|
|
83
66
|
if value.equal?(NONE)
|
84
67
|
send(original_method_name, *args).tap do |r|
|
85
|
-
|
68
|
+
instance_method_cache[args] = Ref::SoftReference.new(Value.new(r))
|
86
69
|
end
|
87
70
|
else
|
88
71
|
value
|
@@ -2,8 +2,7 @@ module Nanoc::Int::Compiler::Phases
|
|
2
2
|
class Abstract
|
3
3
|
include Nanoc::Int::ContractsSupport
|
4
4
|
|
5
|
-
def initialize(wrapped
|
6
|
-
@name = name
|
5
|
+
def initialize(wrapped:)
|
7
6
|
@wrapped = wrapped
|
8
7
|
end
|
9
8
|
|
@@ -28,7 +27,8 @@ module Nanoc::Int::Compiler::Phases
|
|
28
27
|
private
|
29
28
|
|
30
29
|
def notify(sym, rep)
|
31
|
-
|
30
|
+
name = self.class.to_s.sub(/^.*::/, '')
|
31
|
+
Nanoc::Int::NotificationCenter.post(sym, name, rep)
|
32
32
|
end
|
33
33
|
end
|
34
34
|
end
|
@@ -4,10 +4,8 @@ module Nanoc::Int::Compiler::Phases
|
|
4
4
|
class Cache < Abstract
|
5
5
|
include Nanoc::Int::ContractsSupport
|
6
6
|
|
7
|
-
NAME = 'cache'.freeze
|
8
|
-
|
9
7
|
def initialize(wrapped:, compiled_content_cache:, snapshot_repo:)
|
10
|
-
super(wrapped: wrapped
|
8
|
+
super(wrapped: wrapped)
|
11
9
|
|
12
10
|
@compiled_content_cache = compiled_content_cache
|
13
11
|
@snapshot_repo = snapshot_repo
|
@@ -2,10 +2,8 @@ module Nanoc::Int::Compiler::Phases
|
|
2
2
|
class MarkDone < Abstract
|
3
3
|
include Nanoc::Int::ContractsSupport
|
4
4
|
|
5
|
-
NAME = 'mark_done'.freeze
|
6
|
-
|
7
5
|
def initialize(wrapped:, outdatedness_store:)
|
8
|
-
super(wrapped: wrapped
|
6
|
+
super(wrapped: wrapped)
|
9
7
|
|
10
8
|
@outdatedness_store = outdatedness_store
|
11
9
|
end
|
@@ -4,10 +4,8 @@ module Nanoc::Int::Compiler::Phases
|
|
4
4
|
class Recalculate < Abstract
|
5
5
|
include Nanoc::Int::ContractsSupport
|
6
6
|
|
7
|
-
NAME = 'recalculate'.freeze
|
8
|
-
|
9
7
|
def initialize(action_provider:, dependency_store:, compilation_context:)
|
10
|
-
super(wrapped: nil
|
8
|
+
super(wrapped: nil)
|
11
9
|
|
12
10
|
@action_provider = action_provider
|
13
11
|
@dependency_store = dependency_store
|
@@ -3,10 +3,8 @@ module Nanoc::Int::Compiler::Phases
|
|
3
3
|
class Resume < Abstract
|
4
4
|
include Nanoc::Int::ContractsSupport
|
5
5
|
|
6
|
-
NAME = 'resume'.freeze
|
7
|
-
|
8
6
|
def initialize(wrapped:)
|
9
|
-
super(wrapped: wrapped
|
7
|
+
super(wrapped: wrapped)
|
10
8
|
end
|
11
9
|
|
12
10
|
contract Nanoc::Int::ItemRep, C::KeywordArgs[is_outdated: C::Bool], C::Func[C::None => C::Any] => C::Any
|
@@ -2,10 +2,8 @@ module Nanoc::Int::Compiler::Phases
|
|
2
2
|
class Write < Abstract
|
3
3
|
include Nanoc::Int::ContractsSupport
|
4
4
|
|
5
|
-
NAME = 'write'.freeze
|
6
|
-
|
7
5
|
def initialize(snapshot_repo:, wrapped:)
|
8
|
-
super(wrapped: wrapped
|
6
|
+
super(wrapped: wrapped)
|
9
7
|
|
10
8
|
@snapshot_repo = snapshot_repo
|
11
9
|
end
|
@@ -93,6 +93,21 @@ module Nanoc
|
|
93
93
|
(@to || :text) == :binary
|
94
94
|
end
|
95
95
|
|
96
|
+
# @return [Boolean]
|
97
|
+
#
|
98
|
+
# @api private
|
99
|
+
def always_outdated?
|
100
|
+
@always_outdated || false
|
101
|
+
end
|
102
|
+
|
103
|
+
# Marks this filter as always causing the item rep to be outdated. This is useful for filters
|
104
|
+
# that cannot do dependency tracking properly.
|
105
|
+
#
|
106
|
+
# @return [void]
|
107
|
+
def always_outdated
|
108
|
+
@always_outdated = true
|
109
|
+
end
|
110
|
+
|
96
111
|
# @overload requires(*requires)
|
97
112
|
# Sets the required libraries for this filter.
|
98
113
|
# @param [Array<String>] requires A list of library names that are required
|
@@ -19,6 +19,7 @@ module Nanoc::Int
|
|
19
19
|
Rules::NotWritten,
|
20
20
|
Rules::CodeSnippetsModified,
|
21
21
|
Rules::ConfigurationModified,
|
22
|
+
Rules::UsesAlwaysOutdatedFilter,
|
22
23
|
].freeze
|
23
24
|
|
24
25
|
RULES_FOR_LAYOUT =
|
@@ -26,6 +27,7 @@ module Nanoc::Int
|
|
26
27
|
Rules::RulesModified,
|
27
28
|
Rules::ContentModified,
|
28
29
|
Rules::AttributesModified,
|
30
|
+
Rules::UsesAlwaysOutdatedFilter,
|
29
31
|
].freeze
|
30
32
|
|
31
33
|
contract C::KeywordArgs[outdatedness_checker: OutdatednessChecker, reps: Nanoc::Int::ItemRepRepo] => C::Any
|
@@ -56,7 +58,7 @@ module Nanoc::Int
|
|
56
58
|
rules.inject(status) do |acc, rule|
|
57
59
|
if !acc.useful_to_apply?(rule)
|
58
60
|
acc
|
59
|
-
elsif rule.instance.
|
61
|
+
elsif rule.instance.call(obj, @outdatedness_checker)
|
60
62
|
acc.update(rule.instance.reason)
|
61
63
|
else
|
62
64
|
acc
|
@@ -4,6 +4,13 @@ module Nanoc::Int
|
|
4
4
|
include Nanoc::Int::ContractsSupport
|
5
5
|
include Singleton
|
6
6
|
|
7
|
+
def call(obj, outdatedness_checker)
|
8
|
+
Nanoc::Int::NotificationCenter.post(:outdatedness_rule_started, self.class, obj)
|
9
|
+
apply(obj, outdatedness_checker)
|
10
|
+
ensure
|
11
|
+
Nanoc::Int::NotificationCenter.post(:outdatedness_rule_ended, self.class, obj)
|
12
|
+
end
|
13
|
+
|
7
14
|
def apply(_obj, _outdatedness_checker)
|
8
15
|
raise NotImplementedError.new('Nanoc::Int::OutdatednessRule subclasses must implement ##reason, and #apply')
|
9
16
|
end
|
@@ -117,5 +117,21 @@ module Nanoc::Int
|
|
117
117
|
paths_old != paths_new
|
118
118
|
end
|
119
119
|
end
|
120
|
+
|
121
|
+
class UsesAlwaysOutdatedFilter < OutdatednessRule
|
122
|
+
def reason
|
123
|
+
Nanoc::Int::OutdatednessReasons::UsesAlwaysOutdatedFilter
|
124
|
+
end
|
125
|
+
|
126
|
+
def apply(obj, outdatedness_checker)
|
127
|
+
mem = outdatedness_checker.action_provider.memory_for(obj)
|
128
|
+
|
129
|
+
mem
|
130
|
+
.select { |a| a.is_a?(Nanoc::Int::ProcessingActions::Filter) }
|
131
|
+
.map { |a| Nanoc::Filter.named(a.filter_name) }
|
132
|
+
.compact
|
133
|
+
.any?(&:always_outdated?)
|
134
|
+
end
|
135
|
+
end
|
120
136
|
end
|
121
137
|
end
|
@@ -5,414 +5,15 @@ Compile all items of the current site.
|
|
5
5
|
EOS
|
6
6
|
flag nil, :profile, 'profile compilation' if Nanoc::Feature.enabled?(Nanoc::Feature::PROFILER)
|
7
7
|
|
8
|
+
require_relative 'compile_listeners/abstract'
|
9
|
+
require_relative 'compile_listeners/debug_printer'
|
10
|
+
require_relative 'compile_listeners/diff_generator'
|
11
|
+
require_relative 'compile_listeners/file_action_printer'
|
12
|
+
require_relative 'compile_listeners/stack_prof_profiler'
|
13
|
+
require_relative 'compile_listeners/timing_recorder'
|
14
|
+
|
8
15
|
module Nanoc::CLI::Commands
|
9
16
|
class Compile < ::Nanoc::CLI::CommandRunner
|
10
|
-
# Listens to compilation events and reacts to them. This abstract class
|
11
|
-
# does not have a real implementation; subclasses should override {#start}
|
12
|
-
# and set up notifications to listen to.
|
13
|
-
#
|
14
|
-
# @abstract Subclasses must override {#start} and may override {#stop}.
|
15
|
-
class Listener
|
16
|
-
def initialize(*); end
|
17
|
-
|
18
|
-
# @param [Nanoc::CLI::CommandRunner] command_runner The command runner for this listener
|
19
|
-
#
|
20
|
-
# @return [Boolean] true if this listener should be enabled for the given command runner, false otherwise
|
21
|
-
#
|
22
|
-
# @abstract Returns `true` by default, but subclasses may override this.
|
23
|
-
def self.enable_for?(command_runner) # rubocop:disable Lint/UnusedMethodArgument
|
24
|
-
true
|
25
|
-
end
|
26
|
-
|
27
|
-
# Starts the listener. Subclasses should override this method and set up listener notifications.
|
28
|
-
#
|
29
|
-
# @return [void]
|
30
|
-
#
|
31
|
-
# @abstract
|
32
|
-
def start
|
33
|
-
raise NotImplementedError, 'Subclasses of Listener should implement #start'
|
34
|
-
end
|
35
|
-
|
36
|
-
# Stops the listener. The default implementation removes self from all notification center observers.
|
37
|
-
#
|
38
|
-
# @return [void]
|
39
|
-
def stop; end
|
40
|
-
|
41
|
-
# @api private
|
42
|
-
def start_safely
|
43
|
-
start
|
44
|
-
@_started = true
|
45
|
-
end
|
46
|
-
|
47
|
-
# @api private
|
48
|
-
def stop_safely
|
49
|
-
stop if @_started
|
50
|
-
@_started = false
|
51
|
-
end
|
52
|
-
end
|
53
|
-
|
54
|
-
# Generates diffs for every output file written
|
55
|
-
class DiffGenerator < Listener
|
56
|
-
# @see Listener#enable_for?
|
57
|
-
def self.enable_for?(command_runner)
|
58
|
-
command_runner.site.config[:enable_output_diff]
|
59
|
-
end
|
60
|
-
|
61
|
-
# @see Listener#start
|
62
|
-
def start
|
63
|
-
require 'tempfile'
|
64
|
-
setup_diffs
|
65
|
-
old_contents = {}
|
66
|
-
Nanoc::Int::NotificationCenter.on(:will_write_rep, self) do |rep, path|
|
67
|
-
old_contents[rep] = File.file?(path) ? File.read(path) : nil
|
68
|
-
end
|
69
|
-
Nanoc::Int::NotificationCenter.on(:rep_written, self) do |rep, binary, path, _is_created, _is_modified|
|
70
|
-
unless binary
|
71
|
-
new_contents = File.file?(path) ? File.read(path) : nil
|
72
|
-
if old_contents[rep] && new_contents
|
73
|
-
generate_diff_for(path, old_contents[rep], new_contents)
|
74
|
-
end
|
75
|
-
old_contents.delete(rep)
|
76
|
-
end
|
77
|
-
end
|
78
|
-
end
|
79
|
-
|
80
|
-
# @see Listener#stop
|
81
|
-
def stop
|
82
|
-
super
|
83
|
-
|
84
|
-
Nanoc::Int::NotificationCenter.remove(:will_write_rep, self)
|
85
|
-
Nanoc::Int::NotificationCenter.remove(:rep_written, self)
|
86
|
-
|
87
|
-
teardown_diffs
|
88
|
-
end
|
89
|
-
|
90
|
-
protected
|
91
|
-
|
92
|
-
def setup_diffs
|
93
|
-
@diff_lock = Mutex.new
|
94
|
-
@diff_threads = []
|
95
|
-
FileUtils.rm('output.diff') if File.file?('output.diff')
|
96
|
-
end
|
97
|
-
|
98
|
-
def teardown_diffs
|
99
|
-
@diff_threads.each(&:join)
|
100
|
-
end
|
101
|
-
|
102
|
-
def generate_diff_for(path, old_content, new_content)
|
103
|
-
return if old_content == new_content
|
104
|
-
|
105
|
-
@diff_threads << Thread.new do
|
106
|
-
# Generate diff
|
107
|
-
diff = diff_strings(old_content, new_content)
|
108
|
-
diff.sub!(/^--- .*/, '--- ' + path)
|
109
|
-
diff.sub!(/^\+\+\+ .*/, '+++ ' + path)
|
110
|
-
|
111
|
-
# Write diff
|
112
|
-
@diff_lock.synchronize do
|
113
|
-
File.open('output.diff', 'a') { |io| io.write(diff) }
|
114
|
-
end
|
115
|
-
end
|
116
|
-
end
|
117
|
-
|
118
|
-
def diff_strings(a, b)
|
119
|
-
require 'open3'
|
120
|
-
|
121
|
-
# Create files
|
122
|
-
Tempfile.open('old') do |old_file|
|
123
|
-
Tempfile.open('new') do |new_file|
|
124
|
-
# Write files
|
125
|
-
old_file.write(a)
|
126
|
-
old_file.flush
|
127
|
-
new_file.write(b)
|
128
|
-
new_file.flush
|
129
|
-
|
130
|
-
# Diff
|
131
|
-
cmd = ['diff', '-u', old_file.path, new_file.path]
|
132
|
-
Open3.popen3(*cmd) do |_stdin, stdout, _stderr|
|
133
|
-
result = stdout.read
|
134
|
-
return (result == '' ? nil : result)
|
135
|
-
end
|
136
|
-
end
|
137
|
-
end
|
138
|
-
rescue Errno::ENOENT
|
139
|
-
warn 'Failed to run `diff`, so no diff with the previously compiled ' \
|
140
|
-
'content will be available.'
|
141
|
-
nil
|
142
|
-
end
|
143
|
-
end
|
144
|
-
|
145
|
-
# Records the time spent per filter and per item representation
|
146
|
-
class TimingRecorder < Listener
|
147
|
-
# @see Listener#enable_for?
|
148
|
-
def self.enable_for?(command_runner)
|
149
|
-
command_runner.options.fetch(:verbose, false)
|
150
|
-
end
|
151
|
-
|
152
|
-
# @param [Enumerable<Nanoc::Int::ItemRep>] reps
|
153
|
-
def initialize(reps:)
|
154
|
-
@reps = reps
|
155
|
-
end
|
156
|
-
|
157
|
-
# @see Listener#start
|
158
|
-
def start
|
159
|
-
@telemetry = Nanoc::Telemetry.new
|
160
|
-
|
161
|
-
stage_stopwatch = Nanoc::Telemetry::Stopwatch.new
|
162
|
-
|
163
|
-
Nanoc::Int::NotificationCenter.on(:stage_started) do |_stage_name|
|
164
|
-
stage_stopwatch.start
|
165
|
-
end
|
166
|
-
|
167
|
-
Nanoc::Int::NotificationCenter.on(:stage_ended) do |stage_name|
|
168
|
-
stage_stopwatch.stop
|
169
|
-
@telemetry.summary(:stages).observe(stage_stopwatch.duration, stage_name.to_s)
|
170
|
-
stage_stopwatch = Nanoc::Telemetry::Stopwatch.new
|
171
|
-
end
|
172
|
-
|
173
|
-
filter_stopwatches = {}
|
174
|
-
|
175
|
-
Nanoc::Int::NotificationCenter.on(:filtering_started) do |rep, _filter_name|
|
176
|
-
stopwatch_stack = filter_stopwatches.fetch(rep) { filter_stopwatches[rep] = [] }
|
177
|
-
stopwatch_stack << Nanoc::Telemetry::Stopwatch.new
|
178
|
-
stopwatch_stack.last.start
|
179
|
-
end
|
180
|
-
|
181
|
-
Nanoc::Int::NotificationCenter.on(:filtering_ended) do |rep, filter_name|
|
182
|
-
stopwatch = filter_stopwatches.fetch(rep).pop
|
183
|
-
stopwatch.stop
|
184
|
-
|
185
|
-
@telemetry.summary(:filters).observe(stopwatch.duration, filter_name.to_s)
|
186
|
-
end
|
187
|
-
|
188
|
-
Nanoc::Int::NotificationCenter.on(:compilation_suspended) do |rep, _exception|
|
189
|
-
filter_stopwatches.fetch(rep).each(&:stop)
|
190
|
-
end
|
191
|
-
|
192
|
-
Nanoc::Int::NotificationCenter.on(:compilation_started) do |rep|
|
193
|
-
filter_stopwatches.fetch(rep, []).each(&:start)
|
194
|
-
end
|
195
|
-
|
196
|
-
phase_stopwatches = {}
|
197
|
-
|
198
|
-
Nanoc::Int::NotificationCenter.on(:phase_started) do |phase_name, rep|
|
199
|
-
stopwatches = phase_stopwatches.fetch(rep) { phase_stopwatches[rep] = {} }
|
200
|
-
stopwatches[phase_name] = Nanoc::Telemetry::Stopwatch.new.tap(&:start)
|
201
|
-
end
|
202
|
-
|
203
|
-
Nanoc::Int::NotificationCenter.on(:phase_ended) do |phase_name, rep|
|
204
|
-
stopwatch = phase_stopwatches.fetch(rep).fetch(phase_name)
|
205
|
-
stopwatch.stop
|
206
|
-
|
207
|
-
@telemetry.summary(:phases).observe(stopwatch.duration, phase_name)
|
208
|
-
end
|
209
|
-
|
210
|
-
Nanoc::Int::NotificationCenter.on(:phase_yielded) do |phase_name, rep|
|
211
|
-
stopwatch = phase_stopwatches.fetch(rep).fetch(phase_name)
|
212
|
-
stopwatch.stop
|
213
|
-
end
|
214
|
-
|
215
|
-
Nanoc::Int::NotificationCenter.on(:phase_resumed) do |phase_name, rep|
|
216
|
-
stopwatch = phase_stopwatches.fetch(rep).fetch(phase_name)
|
217
|
-
stopwatch.start if stopwatch.stopped?
|
218
|
-
end
|
219
|
-
|
220
|
-
Nanoc::Int::NotificationCenter.on(:phase_aborted) do |phase_name, rep|
|
221
|
-
stopwatch = phase_stopwatches.fetch(rep).fetch(phase_name)
|
222
|
-
stopwatch.stop if stopwatch.running?
|
223
|
-
|
224
|
-
@telemetry.summary(:phases).observe(stopwatch.duration, phase_name)
|
225
|
-
end
|
226
|
-
end
|
227
|
-
|
228
|
-
# @see Listener#stop
|
229
|
-
def stop
|
230
|
-
print_profiling_feedback
|
231
|
-
super
|
232
|
-
end
|
233
|
-
|
234
|
-
protected
|
235
|
-
|
236
|
-
def table_for_summary(name)
|
237
|
-
headers = [name.to_s, 'count', 'min', 'avg', 'max', 'tot']
|
238
|
-
|
239
|
-
rows = @telemetry.summary(name).map do |filter_name, summary|
|
240
|
-
count = summary.count
|
241
|
-
min = summary.min
|
242
|
-
avg = summary.avg
|
243
|
-
tot = summary.sum
|
244
|
-
max = summary.max
|
245
|
-
|
246
|
-
[filter_name, count.to_s] + [min, avg, max, tot].map { |r| "#{format('%4.2f', r)}s" }
|
247
|
-
end
|
248
|
-
|
249
|
-
[headers] + rows
|
250
|
-
end
|
251
|
-
|
252
|
-
def table_for_summary_durations(name)
|
253
|
-
headers = [name.to_s, 'tot']
|
254
|
-
|
255
|
-
rows = @telemetry.summary(:stages).map do |stage_name, summary|
|
256
|
-
[stage_name, "#{format('%4.2f', summary.sum)}s"]
|
257
|
-
end
|
258
|
-
|
259
|
-
[headers] + rows
|
260
|
-
end
|
261
|
-
|
262
|
-
def print_profiling_feedback
|
263
|
-
print_table_for_summary(:filters)
|
264
|
-
print_table_for_summary(:phases) if Nanoc::CLI.verbosity >= 2
|
265
|
-
print_table_for_summary_duration(:stages) if Nanoc::CLI.verbosity >= 2
|
266
|
-
end
|
267
|
-
|
268
|
-
def print_table_for_summary(name)
|
269
|
-
return if @telemetry.summary(name).empty?
|
270
|
-
|
271
|
-
puts
|
272
|
-
print_table(table_for_summary(name))
|
273
|
-
end
|
274
|
-
|
275
|
-
def print_table_for_summary_duration(name)
|
276
|
-
return if @telemetry.summary(name).empty?
|
277
|
-
|
278
|
-
puts
|
279
|
-
print_table(table_for_summary_durations(name))
|
280
|
-
end
|
281
|
-
|
282
|
-
def print_table(table)
|
283
|
-
lengths = table.transpose.map { |r| r.map(&:size).max }
|
284
|
-
|
285
|
-
print_row(table[0], lengths)
|
286
|
-
|
287
|
-
puts "#{'─' * lengths[0]}─┼─#{lengths[1..-1].map { |length| '─' * length }.join('───')}"
|
288
|
-
|
289
|
-
table[1..-1].each { |row| print_row(row, lengths) }
|
290
|
-
end
|
291
|
-
|
292
|
-
def print_row(row, lengths)
|
293
|
-
values = row.zip(lengths).map { |text, length| text.rjust length }
|
294
|
-
|
295
|
-
puts values[0] + ' │ ' + values[1..-1].join(' ')
|
296
|
-
end
|
297
|
-
end
|
298
|
-
|
299
|
-
# Prints debug information (compilation started/ended, filtering started/ended, …)
|
300
|
-
class DebugPrinter < Listener
|
301
|
-
# @see Listener#enable_for?
|
302
|
-
def self.enable_for?(command_runner)
|
303
|
-
command_runner.debug?
|
304
|
-
end
|
305
|
-
|
306
|
-
# @see Listener#start
|
307
|
-
def start
|
308
|
-
Nanoc::Int::NotificationCenter.on(:compilation_started) do |rep|
|
309
|
-
puts "*** Started compilation of #{rep.inspect}"
|
310
|
-
end
|
311
|
-
Nanoc::Int::NotificationCenter.on(:compilation_ended) do |rep|
|
312
|
-
puts "*** Ended compilation of #{rep.inspect}"
|
313
|
-
puts
|
314
|
-
end
|
315
|
-
Nanoc::Int::NotificationCenter.on(:compilation_suspended) do |rep, e|
|
316
|
-
puts "*** Suspended compilation of #{rep.inspect}: #{e.message}"
|
317
|
-
end
|
318
|
-
Nanoc::Int::NotificationCenter.on(:cached_content_used) do |rep|
|
319
|
-
puts "*** Used cached compiled content for #{rep.inspect} instead of recompiling"
|
320
|
-
end
|
321
|
-
Nanoc::Int::NotificationCenter.on(:filtering_started) do |rep, filter_name|
|
322
|
-
puts "*** Started filtering #{rep.inspect} with #{filter_name}"
|
323
|
-
end
|
324
|
-
Nanoc::Int::NotificationCenter.on(:filtering_ended) do |rep, filter_name|
|
325
|
-
puts "*** Ended filtering #{rep.inspect} with #{filter_name}"
|
326
|
-
end
|
327
|
-
Nanoc::Int::NotificationCenter.on(:dependency_created) do |src, dst|
|
328
|
-
puts "*** Dependency created from #{src.inspect} onto #{dst.inspect}"
|
329
|
-
end
|
330
|
-
end
|
331
|
-
end
|
332
|
-
|
333
|
-
# Prints file actions (created, updated, deleted, identical, skipped)
|
334
|
-
class FileActionPrinter < Listener
|
335
|
-
def initialize(reps:)
|
336
|
-
@start_times = {}
|
337
|
-
@acc_durations = {}
|
338
|
-
|
339
|
-
@reps = reps
|
340
|
-
end
|
341
|
-
|
342
|
-
# @see Listener#start
|
343
|
-
def start
|
344
|
-
Nanoc::Int::NotificationCenter.on(:compilation_started, self) do |rep|
|
345
|
-
@start_times[rep] = Time.now
|
346
|
-
@acc_durations[rep] ||= 0.0
|
347
|
-
end
|
348
|
-
|
349
|
-
Nanoc::Int::NotificationCenter.on(:compilation_suspended, self) do |rep|
|
350
|
-
@acc_durations[rep] += Time.now - @start_times[rep]
|
351
|
-
end
|
352
|
-
|
353
|
-
Nanoc::Int::NotificationCenter.on(:rep_written, self) do |rep, _binary, path, is_created, is_modified|
|
354
|
-
@acc_durations[rep] += Time.now - @start_times[rep]
|
355
|
-
duration = @acc_durations[rep]
|
356
|
-
|
357
|
-
action =
|
358
|
-
if is_created then :create
|
359
|
-
elsif is_modified then :update
|
360
|
-
else :identical
|
361
|
-
end
|
362
|
-
level =
|
363
|
-
if is_created then :high
|
364
|
-
elsif is_modified then :high
|
365
|
-
else :low
|
366
|
-
end
|
367
|
-
log(level, action, path, duration)
|
368
|
-
end
|
369
|
-
end
|
370
|
-
|
371
|
-
# @see Listener#stop
|
372
|
-
def stop
|
373
|
-
super
|
374
|
-
|
375
|
-
Nanoc::Int::NotificationCenter.remove(:compilation_started, self)
|
376
|
-
Nanoc::Int::NotificationCenter.remove(:compilation_suspended, self)
|
377
|
-
Nanoc::Int::NotificationCenter.remove(:rep_written, self)
|
378
|
-
|
379
|
-
@reps.reject(&:compiled?).each do |rep|
|
380
|
-
raw_paths = rep.raw_paths.values.flatten.uniq
|
381
|
-
raw_paths.each do |raw_path|
|
382
|
-
log(:low, :skip, raw_path, nil)
|
383
|
-
end
|
384
|
-
end
|
385
|
-
end
|
386
|
-
|
387
|
-
private
|
388
|
-
|
389
|
-
def log(level, action, path, duration)
|
390
|
-
Nanoc::CLI::Logger.instance.file(level, action, path, duration)
|
391
|
-
end
|
392
|
-
end
|
393
|
-
|
394
|
-
# Records a profile using StackProf
|
395
|
-
class StackProfProfiler < Listener
|
396
|
-
PROFILE_FILE = 'tmp/stackprof_profile'.freeze
|
397
|
-
|
398
|
-
# @see Listener#enable_for?
|
399
|
-
def self.enable_for?(command_runner)
|
400
|
-
command_runner.options.fetch(:profile, false)
|
401
|
-
end
|
402
|
-
|
403
|
-
# @see Listener#start
|
404
|
-
def start
|
405
|
-
require 'stackprof'
|
406
|
-
StackProf.start(mode: :cpu)
|
407
|
-
end
|
408
|
-
|
409
|
-
# @see Listener#stop
|
410
|
-
def stop
|
411
|
-
StackProf.stop
|
412
|
-
StackProf.results(PROFILE_FILE)
|
413
|
-
end
|
414
|
-
end
|
415
|
-
|
416
17
|
attr_accessor :listener_classes
|
417
18
|
|
418
19
|
def initialize(options, arguments, command)
|
@@ -439,11 +40,11 @@ module Nanoc::CLI::Commands
|
|
439
40
|
|
440
41
|
def default_listener_classes
|
441
42
|
[
|
442
|
-
Nanoc::CLI::Commands::
|
443
|
-
Nanoc::CLI::Commands::
|
444
|
-
Nanoc::CLI::Commands::
|
445
|
-
Nanoc::CLI::Commands::
|
446
|
-
Nanoc::CLI::Commands::
|
43
|
+
Nanoc::CLI::Commands::CompileListeners::StackProfProfiler,
|
44
|
+
Nanoc::CLI::Commands::CompileListeners::DiffGenerator,
|
45
|
+
Nanoc::CLI::Commands::CompileListeners::DebugPrinter,
|
46
|
+
Nanoc::CLI::Commands::CompileListeners::TimingRecorder,
|
47
|
+
Nanoc::CLI::Commands::CompileListeners::FileActionPrinter,
|
447
48
|
]
|
448
49
|
end
|
449
50
|
|