nanoc 3.4.3 → 3.5.0b1
Sign up to get free protection for your applications and to get access to all the features.
- data/CONTRIBUTING.md +25 -0
- data/Gemfile +3 -1
- data/Gemfile.lock +22 -13
- data/NEWS.md +27 -0
- data/README.md +3 -1
- data/lib/nanoc.rb +2 -2
- data/lib/nanoc/base/compilation/compiler_dsl.rb +19 -0
- data/lib/nanoc/base/core_ext/array.rb +18 -8
- data/lib/nanoc/base/core_ext/hash.rb +18 -8
- data/lib/nanoc/base/core_ext/pathname.rb +2 -8
- data/lib/nanoc/base/plugin_registry.rb +57 -19
- data/lib/nanoc/base/result_data/item_rep.rb +3 -15
- data/lib/nanoc/base/source_data/item.rb +1 -1
- data/lib/nanoc/base/source_data/layout.rb +1 -1
- data/lib/nanoc/base/source_data/site.rb +15 -19
- data/lib/nanoc/cli.rb +28 -23
- data/lib/nanoc/cli/command_runner.rb +4 -0
- data/lib/nanoc/cli/commands/autocompile.rb +15 -6
- data/lib/nanoc/cli/commands/check.rb +47 -0
- data/lib/nanoc/cli/commands/compile.rb +271 -195
- data/lib/nanoc/cli/commands/create-site.rb +5 -5
- data/lib/nanoc/cli/commands/deploy.rb +16 -4
- data/lib/nanoc/cli/commands/prune.rb +3 -3
- data/lib/nanoc/cli/commands/show-data.rb +73 -58
- data/lib/nanoc/cli/commands/show-rules.rb +1 -1
- data/lib/nanoc/cli/commands/validate-css.rb +2 -3
- data/lib/nanoc/cli/commands/validate-html.rb +2 -3
- data/lib/nanoc/cli/commands/validate-links.rb +5 -11
- data/lib/nanoc/cli/commands/view.rb +1 -1
- data/lib/nanoc/cli/commands/watch.rb +38 -20
- data/lib/nanoc/cli/error_handler.rb +122 -122
- data/lib/nanoc/data_sources.rb +2 -0
- data/lib/nanoc/data_sources/filesystem_unified.rb +1 -1
- data/lib/nanoc/data_sources/filesystem_verbose.rb +1 -1
- data/lib/nanoc/data_sources/static.rb +60 -0
- data/lib/nanoc/extra.rb +2 -0
- data/lib/nanoc/extra/checking.rb +16 -0
- data/lib/nanoc/extra/checking/check.rb +33 -0
- data/lib/nanoc/extra/checking/checks.rb +19 -0
- data/lib/nanoc/extra/checking/checks/css.rb +23 -0
- data/lib/nanoc/extra/checking/checks/external_links.rb +149 -0
- data/lib/nanoc/extra/checking/checks/html.rb +24 -0
- data/lib/nanoc/extra/checking/checks/internal_links.rb +57 -0
- data/lib/nanoc/extra/checking/checks/stale.rb +23 -0
- data/lib/nanoc/extra/checking/dsl.rb +31 -0
- data/lib/nanoc/extra/checking/issue.rb +19 -0
- data/lib/nanoc/extra/checking/runner.rb +130 -0
- data/lib/nanoc/extra/link_collector.rb +57 -0
- data/lib/nanoc/extra/pruner.rb +1 -1
- data/lib/nanoc/extra/validators/links.rb +5 -262
- data/lib/nanoc/extra/validators/w3c.rb +8 -76
- data/lib/nanoc/filters/colorize_syntax.rb +1 -1
- data/lib/nanoc/filters/handlebars.rb +2 -2
- data/lib/nanoc/filters/less.rb +1 -1
- data/lib/nanoc/filters/redcarpet.rb +1 -1
- data/lib/nanoc/filters/rubypants.rb +1 -1
- data/lib/nanoc/filters/slim.rb +1 -1
- data/lib/nanoc/helpers/blogging.rb +163 -104
- data/lib/nanoc/helpers/xml_sitemap.rb +9 -3
- data/tasks/doc.rake +2 -1
- data/test/base/core_ext/array_spec.rb +4 -4
- data/test/base/core_ext/hash_spec.rb +7 -7
- data/test/base/core_ext/pathname_spec.rb +12 -2
- data/test/base/test_compiler_dsl.rb +24 -0
- data/test/base/test_item_rep.rb +58 -26
- data/test/cli/commands/test_watch.rb +0 -8
- data/test/data_sources/test_static.rb +66 -0
- data/test/extra/checking/checks/test_css.rb +40 -0
- data/test/extra/checking/checks/test_external_links.rb +76 -0
- data/test/extra/checking/checks/test_html.rb +40 -0
- data/test/extra/checking/checks/test_internal_links.rb +46 -0
- data/test/extra/checking/checks/test_stale.rb +49 -0
- data/test/extra/checking/test_check.rb +16 -0
- data/test/extra/checking/test_dsl.rb +20 -0
- data/test/extra/checking/test_runner.rb +15 -0
- data/test/extra/test_link_collector.rb +93 -0
- data/test/extra/validators/test_links.rb +0 -64
- data/test/extra/validators/test_w3c.rb +20 -26
- data/test/filters/test_colorize_syntax.rb +15 -0
- data/test/filters/test_less.rb +14 -0
- data/test/filters/test_pandoc.rb +5 -1
- data/test/helpers/test_blogging.rb +52 -8
- data/test/helpers/test_xml_sitemap.rb +68 -0
- data/test/test_gem.rb +1 -1
- metadata +31 -6
@@ -129,27 +129,15 @@ module Nanoc
|
|
129
129
|
# Check if file will be created
|
130
130
|
is_created = !File.file?(raw_path)
|
131
131
|
|
132
|
-
# Calculate characteristics of old content
|
133
|
-
if File.file?(raw_path)
|
134
|
-
hash_old = Pathname.new(raw_path).checksum
|
135
|
-
size_old = File.size(raw_path)
|
136
|
-
end
|
137
|
-
|
138
132
|
# Notify
|
139
133
|
Nanoc::NotificationCenter.post(:will_write_rep, self, snapshot)
|
140
134
|
|
141
135
|
if self.binary?
|
142
|
-
# Calculate characteristics of new content
|
143
|
-
size_new = File.size(temporary_filenames[:last])
|
144
|
-
hash_new = Pathname.new(temporary_filenames[:last]).checksum if size_old == size_new
|
145
|
-
|
146
136
|
# Check whether content was modified
|
147
|
-
is_modified = (
|
137
|
+
is_modified = !File.file?(raw_path) || !FileUtils.identical?(raw_path, temporary_filenames[:last])
|
148
138
|
|
149
|
-
#
|
150
|
-
|
151
|
-
FileUtils.cp(temporary_filenames[:last], raw_path)
|
152
|
-
end
|
139
|
+
# Always copy (time spent checking modification is not useful)
|
140
|
+
FileUtils.cp(temporary_filenames[:last], raw_path)
|
153
141
|
else
|
154
142
|
# Check whether content was modified
|
155
143
|
is_modified = (!File.file?(raw_path) || File.read(raw_path) != @content[:last])
|
@@ -35,7 +35,7 @@ module Nanoc
|
|
35
35
|
# attribute instead.
|
36
36
|
def initialize(raw_content, attributes, identifier, params=nil)
|
37
37
|
@raw_content = raw_content
|
38
|
-
@attributes = attributes.
|
38
|
+
@attributes = attributes.symbolize_keys_recursively
|
39
39
|
@identifier = identifier.cleaned_identifier.freeze
|
40
40
|
|
41
41
|
# Set mtime
|
@@ -181,25 +181,21 @@ module Nanoc
|
|
181
181
|
def setup_child_parent_links
|
182
182
|
teardown_child_parent_links
|
183
183
|
|
184
|
-
|
185
|
-
items.
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
(
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
184
|
+
item_map = {}
|
185
|
+
@items.each do |item|
|
186
|
+
item_map[item.identifier] = item
|
187
|
+
end
|
188
|
+
|
189
|
+
@items.each do |item|
|
190
|
+
parent_id_end = item.identifier.rindex('/', -2)
|
191
|
+
if parent_id_end
|
192
|
+
parent_id = item.identifier[0..parent_id_end]
|
193
|
+
parent = item_map[parent_id]
|
194
|
+
if parent
|
195
|
+
item.parent = parent
|
196
|
+
parent.children << item
|
196
197
|
end
|
197
198
|
end
|
198
|
-
next if parent.nil?
|
199
|
-
|
200
|
-
# Link
|
201
|
-
item.parent = parent
|
202
|
-
parent.children << item
|
203
199
|
end
|
204
200
|
end
|
205
201
|
|
@@ -346,8 +342,8 @@ module Nanoc
|
|
346
342
|
|
347
343
|
# Read config from config.yaml in given dir
|
348
344
|
config_path = File.join(dir_or_config_hash, 'config.yaml')
|
349
|
-
@config = DEFAULT_CONFIG.merge(YAML.load_file(config_path).
|
350
|
-
@config[:data_sources].map! { |ds| ds.
|
345
|
+
@config = DEFAULT_CONFIG.merge(YAML.load_file(config_path).symbolize_keys_recursively)
|
346
|
+
@config[:data_sources].map! { |ds| ds.symbolize_keys_recursively }
|
351
347
|
else
|
352
348
|
# Use passed config hash
|
353
349
|
@config = DEFAULT_CONFIG.merge(dir_or_config_hash)
|
data/lib/nanoc/cli.rb
CHANGED
@@ -53,6 +53,11 @@ module Nanoc::CLI
|
|
53
53
|
end
|
54
54
|
end
|
55
55
|
|
56
|
+
# @return [Cri::Command] The root command, i.e. the commandline tool itself
|
57
|
+
def self.root_command
|
58
|
+
@root_command
|
59
|
+
end
|
60
|
+
|
56
61
|
# Adds the given command to the collection of available commands.
|
57
62
|
#
|
58
63
|
# @param [Cri::Command] cmd The command to add
|
@@ -80,6 +85,10 @@ protected
|
|
80
85
|
# Reinit
|
81
86
|
@root_command = nil
|
82
87
|
|
88
|
+
# Add root command
|
89
|
+
filename = File.dirname(__FILE__) + "/cli/commands/nanoc.rb"
|
90
|
+
@root_command = self.load_command_at(filename)
|
91
|
+
|
83
92
|
# Add help command
|
84
93
|
help_cmd = Cri::Command.new_basic_help
|
85
94
|
self.add_command(help_cmd)
|
@@ -135,14 +144,6 @@ protected
|
|
135
144
|
cmd
|
136
145
|
end
|
137
146
|
|
138
|
-
# @return [Cri::Command] The root command, i.e. the commandline tool itself
|
139
|
-
def self.root_command
|
140
|
-
@root_command ||= begin
|
141
|
-
filename = File.dirname(__FILE__) + "/cli/commands/nanoc.rb"
|
142
|
-
self.load_command_at(filename)
|
143
|
-
end
|
144
|
-
end
|
145
|
-
|
146
147
|
# @return [Array] The directory contents
|
147
148
|
def self.recursive_contents_of(path)
|
148
149
|
return [] unless File.directory?(path)
|
@@ -151,28 +152,32 @@ protected
|
|
151
152
|
files
|
152
153
|
end
|
153
154
|
|
154
|
-
# Wraps
|
155
|
+
# Wraps the given stream in a cleaning stream. The cleaning streams will
|
156
|
+
# have the proper stream cleaners configured.
|
155
157
|
#
|
156
|
-
# @
|
157
|
-
|
158
|
-
|
159
|
-
|
158
|
+
# @param [IO] io The stream to wrap
|
159
|
+
#
|
160
|
+
# @return [::Nanoc::CLI::CleaningStream]
|
161
|
+
def self.wrap_in_cleaning_stream(io)
|
162
|
+
cio = ::Nanoc::CLI::CleaningStream.new(io)
|
160
163
|
|
161
|
-
if !self.enable_utf8?(
|
162
|
-
|
164
|
+
if !self.enable_utf8?(io)
|
165
|
+
cio.add_stream_cleaner(Nanoc::CLI::StreamCleaners::UTF8)
|
163
166
|
end
|
164
167
|
|
165
|
-
if !self.
|
166
|
-
|
168
|
+
if !self.enable_ansi_colors?(io)
|
169
|
+
cio.add_stream_cleaner(Nanoc::CLI::StreamCleaners::ANSIColors)
|
167
170
|
end
|
168
171
|
|
169
|
-
|
170
|
-
|
171
|
-
end
|
172
|
+
cio
|
173
|
+
end
|
172
174
|
|
173
|
-
|
174
|
-
|
175
|
-
|
175
|
+
# Wraps `$stdout` and `$stderr` in appropriate cleaning streams.
|
176
|
+
#
|
177
|
+
# @return [void]
|
178
|
+
def self.setup_cleaning_streams
|
179
|
+
$stdout = self.wrap_in_cleaning_stream($stdout)
|
180
|
+
$stderr = self.wrap_in_cleaning_stream($stderr)
|
176
181
|
end
|
177
182
|
|
178
183
|
# @return [Boolean] true if UTF-8 support is present, false if not
|
@@ -51,8 +51,12 @@ module Nanoc::CLI
|
|
51
51
|
#
|
52
52
|
# @return [void]
|
53
53
|
def require_site
|
54
|
+
print "Loading site data… "
|
54
55
|
if site.nil?
|
56
|
+
puts "error"
|
55
57
|
raise ::Nanoc::Errors::GenericTrivial, "The current working directory does not seem to be a nanoc site."
|
58
|
+
else
|
59
|
+
puts "done"
|
56
60
|
end
|
57
61
|
end
|
58
62
|
|
@@ -4,12 +4,20 @@ usage 'autocompile [options]'
|
|
4
4
|
summary 'start the autocompiler'
|
5
5
|
aliases :aco
|
6
6
|
description <<-EOS
|
7
|
-
Start the autocompiler web server. Unless
|
8
|
-
on port 3000 and listen on all
|
9
|
-
the
|
7
|
+
Start the autocompiler web server. Unless overridden with commandline options
|
8
|
+
or configuration entries, the web server will run on port 3000 and listen on all
|
9
|
+
IP addresses. Running the autocompiler requires the `mime/types` and `rack` gems.
|
10
|
+
|
11
|
+
To specify the host and/or port options in config.yaml, you can add either (or
|
12
|
+
both) of the following:
|
13
|
+
|
14
|
+
autocompile:
|
15
|
+
host: '10.0.2.0' # override the default host
|
16
|
+
port: 4000 # override the default port
|
17
|
+
|
10
18
|
EOS
|
11
19
|
|
12
|
-
required :H, :handler, 'specify the handler to use (webrick/mongrel
|
20
|
+
required :H, :handler, 'specify the handler to use (webrick/mongrel/…)'
|
13
21
|
required :o, :host, 'specify the host to listen on (default: 0.0.0.0)'
|
14
22
|
required :p, :port, 'specify the port to listen on (default: 3000)'
|
15
23
|
|
@@ -22,11 +30,12 @@ module Nanoc::CLI::Commands
|
|
22
30
|
|
23
31
|
# Make sure we are in a nanoc site directory
|
24
32
|
self.require_site
|
33
|
+
autocompile_config = self.site.config[:autocompile] || {}
|
25
34
|
|
26
35
|
# Set options
|
27
36
|
options_for_rack = {
|
28
|
-
:Port => (options[:port] || 3000).to_i,
|
29
|
-
:Host => (options[:host] || '0.0.0.0')
|
37
|
+
:Port => (options[:port] || autocompile_config[:port] || 3000).to_i,
|
38
|
+
:Host => (options[:host] || autocompile_config[:host] || '0.0.0.0')
|
30
39
|
}
|
31
40
|
|
32
41
|
# Guess which handler we should use
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
usage 'check [options] [names]'
|
4
|
+
summary 'run issue checks'
|
5
|
+
description <<-EOS
|
6
|
+
Run issue checks on the current site. If the `--all` option is passed, all available issue checks will be run. If the `--deploy` option is passed, the issue checks marked for deployment will be fun.
|
7
|
+
EOS
|
8
|
+
|
9
|
+
flag :a, :all, 'run all checks'
|
10
|
+
flag :L, :list, 'list all checks'
|
11
|
+
flag :d, :deploy, 'run checks for deployment'
|
12
|
+
|
13
|
+
module Nanoc::CLI::Commands
|
14
|
+
|
15
|
+
class Check < ::Nanoc::CLI::CommandRunner
|
16
|
+
|
17
|
+
def run
|
18
|
+
validate_options_and_arguments
|
19
|
+
self.require_site
|
20
|
+
|
21
|
+
runner = Nanoc::Extra::Checking::Runner.new(site)
|
22
|
+
if options[:list]
|
23
|
+
runner.list_checks
|
24
|
+
elsif options[:all]
|
25
|
+
runner.run_all
|
26
|
+
elsif options[:deploy]
|
27
|
+
runner.require_dsl
|
28
|
+
runner.run_for_deploy
|
29
|
+
else
|
30
|
+
runner.run_specific(arguments)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
protected
|
35
|
+
|
36
|
+
def validate_options_and_arguments
|
37
|
+
if arguments.empty? && !options[:all] && !options[:deploy] && !options[:list]
|
38
|
+
raise Nanoc::Errors::GenericTrivial,
|
39
|
+
"nothing to do (pass either --all, --deploy or --list or a list of checks)"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
|
47
|
+
runner Nanoc::CLI::Commands::Check
|
@@ -22,102 +22,227 @@ option :f, :force, '(ignored)'
|
|
22
22
|
|
23
23
|
module Nanoc::CLI::Commands
|
24
24
|
|
25
|
-
# FIXME this command is horribly long and complicated and does way too much. plz cleanup thx.
|
26
25
|
class Compile < ::Nanoc::CLI::CommandRunner
|
27
26
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
27
|
+
extend Nanoc::Memoization
|
28
|
+
|
29
|
+
# Listens to compilation events and reacts to them. This abstract class
|
30
|
+
# does not have a real implementation; subclasses should override {#start}
|
31
|
+
# and set up notifications to listen to.
|
32
|
+
#
|
33
|
+
# @abstract Subclasses must override {#start} and may override {#stop}.
|
34
|
+
class Listener
|
35
|
+
|
36
|
+
# Starts the listener. Subclasses should override this method and set up listener notifications.
|
37
|
+
#
|
38
|
+
# @return [void]
|
39
|
+
#
|
40
|
+
# @abstract
|
41
|
+
def start
|
42
|
+
raise NotImplementedError, "Subclasses of Listener should implement #start"
|
43
|
+
end
|
32
44
|
|
33
|
-
#
|
34
|
-
|
35
|
-
|
45
|
+
# Stops the listener. The default implementation removes self from all notification center observers.
|
46
|
+
#
|
47
|
+
# @return [void]
|
48
|
+
def stop
|
36
49
|
end
|
37
50
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
51
|
+
end
|
52
|
+
|
53
|
+
# Generates diffs for every output file written
|
54
|
+
class DiffGenerator < Listener
|
55
|
+
|
56
|
+
# @see Listener#start
|
57
|
+
def start
|
58
|
+
require 'tempfile'
|
59
|
+
self.setup_diffs
|
60
|
+
old_contents = {}
|
61
|
+
Nanoc::NotificationCenter.on(:will_write_rep) do |rep, snapshot|
|
62
|
+
path = rep.raw_path(:snapshot => snapshot)
|
63
|
+
old_contents[rep] = File.file?(path) ? File.read(path) : nil
|
64
|
+
end
|
65
|
+
Nanoc::NotificationCenter.on(:rep_written) do |rep, path, is_created, is_modified|
|
66
|
+
if !rep.binary?
|
67
|
+
new_contents = File.file?(path) ? File.read(path) : nil
|
68
|
+
if old_contents[rep] && new_contents
|
69
|
+
generate_diff_for(rep, old_contents[rep], new_contents)
|
70
|
+
end
|
71
|
+
old_contents.delete(rep)
|
72
|
+
end
|
73
|
+
end
|
43
74
|
end
|
44
75
|
|
45
|
-
#
|
46
|
-
|
76
|
+
# @see Listener#stop
|
77
|
+
def stop
|
78
|
+
super
|
79
|
+
self.teardown_diffs
|
80
|
+
end
|
47
81
|
|
48
|
-
|
49
|
-
time_before = Time.now
|
50
|
-
@rep_times = {}
|
51
|
-
@filter_times = {}
|
52
|
-
setup_notifications
|
82
|
+
protected
|
53
83
|
|
54
|
-
|
55
|
-
|
84
|
+
def setup_diffs
|
85
|
+
@diff_lock = Mutex.new
|
86
|
+
@diff_threads = []
|
87
|
+
FileUtils.rm('output.diff') if File.file?('output.diff')
|
88
|
+
end
|
56
89
|
|
57
|
-
|
58
|
-
|
59
|
-
|
90
|
+
def teardown_diffs
|
91
|
+
@diff_threads.each { |t| t.join }
|
92
|
+
end
|
60
93
|
|
61
|
-
|
62
|
-
|
94
|
+
def generate_diff_for(rep, old_content, new_content)
|
95
|
+
return if old_content == new_content
|
63
96
|
|
64
|
-
|
65
|
-
|
97
|
+
@diff_threads << Thread.new do
|
98
|
+
# Generate diff
|
99
|
+
diff = diff_strings(old_content, new_content)
|
100
|
+
diff.sub!(/^--- .*/, '--- ' + rep.raw_path)
|
101
|
+
diff.sub!(/^\+\+\+ .*/, '+++ ' + rep.raw_path)
|
66
102
|
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
duration = @rep_times[filename]
|
72
|
-
Nanoc::CLI::Logger.instance.file(:high, :skip, filename, duration)
|
103
|
+
# Write diff
|
104
|
+
@diff_lock.synchronize do
|
105
|
+
File.open('output.diff', 'a') { |io| io.write(diff) }
|
106
|
+
end
|
73
107
|
end
|
74
108
|
end
|
75
109
|
|
76
|
-
|
77
|
-
|
110
|
+
def diff_strings(a, b)
|
111
|
+
require 'open3'
|
112
|
+
|
113
|
+
# Create files
|
114
|
+
Tempfile.open('old') do |old_file|
|
115
|
+
Tempfile.open('new') do |new_file|
|
116
|
+
# Write files
|
117
|
+
old_file.write(a)
|
118
|
+
old_file.flush
|
119
|
+
new_file.write(b)
|
120
|
+
new_file.flush
|
121
|
+
|
122
|
+
# Diff
|
123
|
+
cmd = [ 'diff', '-u', old_file.path, new_file.path ]
|
124
|
+
Open3.popen3(*cmd) do |stdin, stdout, stderr|
|
125
|
+
result = stdout.read
|
126
|
+
return (result == '' ? nil : result)
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
rescue Errno::ENOENT
|
131
|
+
warn 'Failed to run `diff`, so no diff with the previously compiled ' \
|
132
|
+
'content will be available.'
|
133
|
+
nil
|
134
|
+
end
|
78
135
|
|
79
|
-
|
80
|
-
|
81
|
-
|
136
|
+
end
|
137
|
+
|
138
|
+
# Records the time spent per filter and per item representation
|
139
|
+
class TimingRecorder < Listener
|
140
|
+
|
141
|
+
# @option params [Array<Nanoc::ItemRep>] :reps The list of item representations in the site
|
142
|
+
def initialize(params={})
|
143
|
+
@filter_times = {}
|
144
|
+
|
145
|
+
@reps = params.fetch(:reps)
|
82
146
|
end
|
83
147
|
|
84
|
-
#
|
85
|
-
|
86
|
-
|
148
|
+
# @see Listener#start
|
149
|
+
def start
|
150
|
+
Nanoc::NotificationCenter.on(:filtering_started) do |rep, filter_name|
|
151
|
+
@filter_times[filter_name] ||= []
|
152
|
+
@filter_times[filter_name] << Time.now
|
153
|
+
end
|
154
|
+
Nanoc::NotificationCenter.on(:filtering_ended) do |rep, filter_name|
|
155
|
+
@filter_times[filter_name] << Time.now - @filter_times[filter_name].pop
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
# @see Listener#stop
|
160
|
+
def stop
|
161
|
+
super
|
162
|
+
self.print_profiling_feedback
|
163
|
+
end
|
164
|
+
|
165
|
+
protected
|
87
166
|
|
88
|
-
|
89
|
-
|
90
|
-
|
167
|
+
def print_profiling_feedback
|
168
|
+
# Get max filter length
|
169
|
+
max_filter_name_length = @filter_times.keys.map { |k| k.to_s.size }.max
|
170
|
+
return if max_filter_name_length.nil?
|
171
|
+
|
172
|
+
# Print warning if necessary
|
173
|
+
if @reps.any? { |r| !r.compiled? }
|
174
|
+
$stderr.puts
|
175
|
+
$stderr.puts "Warning: profiling information may not be accurate because " +
|
176
|
+
"some items were not compiled."
|
177
|
+
end
|
178
|
+
|
179
|
+
# Print header
|
180
|
+
puts
|
181
|
+
puts ' ' * max_filter_name_length + ' | count min avg max tot'
|
182
|
+
puts '-' * max_filter_name_length + '-+-----------------------------------'
|
183
|
+
|
184
|
+
@filter_times.to_a.sort_by { |r| r[1] }.each do |row|
|
185
|
+
self.print_row(row)
|
186
|
+
end
|
91
187
|
end
|
188
|
+
|
189
|
+
def print_row(row)
|
190
|
+
# Extract data
|
191
|
+
filter_name, samples = *row
|
192
|
+
|
193
|
+
# Calculate stats
|
194
|
+
count = samples.size
|
195
|
+
min = samples.min
|
196
|
+
tot = samples.inject { |memo, i| memo + i}
|
197
|
+
avg = tot/count
|
198
|
+
max = samples.max
|
199
|
+
|
200
|
+
# Format stats
|
201
|
+
count = format('%4d', count)
|
202
|
+
min = format('%4.2f', min)
|
203
|
+
avg = format('%4.2f', avg)
|
204
|
+
max = format('%4.2f', max)
|
205
|
+
tot = format('%5.2f', tot)
|
206
|
+
|
207
|
+
# Output stats
|
208
|
+
filter_name = format("%#{max_filter_name_length}s", filter_name)
|
209
|
+
puts "#{filter_name} | #{count} #{min}s #{avg}s #{max}s #{tot}s"
|
210
|
+
end
|
211
|
+
|
92
212
|
end
|
93
213
|
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
path = rep.raw_path(:snapshot => snapshot)
|
100
|
-
old_contents[rep] = File.file?(path) ? File.read(path) : nil
|
214
|
+
# Controls garbage collection so that it only occurs once every 20 items
|
215
|
+
class GCController < Listener
|
216
|
+
|
217
|
+
def initialize
|
218
|
+
@gc_count = 0
|
101
219
|
end
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
220
|
+
|
221
|
+
# @see Listener#start
|
222
|
+
def start
|
223
|
+
Nanoc::NotificationCenter.on(:compilation_started) do |rep|
|
224
|
+
if @gc_count % 20 == 0
|
225
|
+
GC.enable
|
226
|
+
GC.start
|
227
|
+
GC.disable
|
107
228
|
end
|
229
|
+
@gc_count += 1
|
108
230
|
end
|
109
231
|
end
|
110
232
|
|
111
|
-
#
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
duration = Time.now - @rep_times[rep.raw_path] if @rep_times[rep.raw_path]
|
116
|
-
Nanoc::CLI::Logger.instance.file(level, action, path, duration)
|
233
|
+
# @see Listener#stop
|
234
|
+
def stop
|
235
|
+
super
|
236
|
+
GC.enable
|
117
237
|
end
|
118
238
|
|
119
|
-
|
120
|
-
|
239
|
+
end
|
240
|
+
|
241
|
+
# Prints debug information (compilation started/ended, filtering started/ended, …)
|
242
|
+
class DebugPrinter < Listener
|
243
|
+
|
244
|
+
# @see Listener#start
|
245
|
+
def start
|
121
246
|
Nanoc::NotificationCenter.on(:compilation_started) do |rep|
|
122
247
|
puts "*** Started compilation of #{rep.inspect}"
|
123
248
|
end
|
@@ -148,165 +273,116 @@ module Nanoc::CLI::Commands
|
|
148
273
|
end
|
149
274
|
end
|
150
275
|
|
151
|
-
# Timing notifications
|
152
|
-
Nanoc::NotificationCenter.on(:compilation_started) do |rep|
|
153
|
-
if @gc_count % 20 == 0 && !ENV.has_key?('TRAVIS')
|
154
|
-
GC.enable
|
155
|
-
GC.start
|
156
|
-
GC.disable
|
157
|
-
end
|
158
|
-
@gc_count += 1
|
159
|
-
@rep_times[rep.raw_path] = Time.now
|
160
|
-
end
|
161
|
-
Nanoc::NotificationCenter.on(:compilation_ended) do |rep|
|
162
|
-
@rep_times[rep.raw_path] = Time.now - @rep_times[rep.raw_path]
|
163
|
-
end
|
164
|
-
Nanoc::NotificationCenter.on(:filtering_started) do |rep, filter_name|
|
165
|
-
@filter_times[filter_name] ||= []
|
166
|
-
@filter_times[filter_name] << Time.now
|
167
|
-
start_filter_progress(rep, filter_name)
|
168
|
-
end
|
169
|
-
Nanoc::NotificationCenter.on(:filtering_ended) do |rep, filter_name|
|
170
|
-
@filter_times[filter_name] << Time.now - @filter_times[filter_name].pop
|
171
|
-
stop_filter_progress(rep, filter_name)
|
172
|
-
end
|
173
276
|
end
|
174
277
|
|
175
|
-
|
176
|
-
|
177
|
-
@diff_threads = []
|
178
|
-
FileUtils.rm('output.diff') if File.file?('output.diff')
|
179
|
-
end
|
278
|
+
# Prints file actions (created, updated, deleted, identical, skipped)
|
279
|
+
class FileActionPrinter < Listener
|
180
280
|
|
181
|
-
|
182
|
-
|
183
|
-
|
281
|
+
# @option params [Array<Nanoc::ItemRep>] :reps The list of item representations in the site
|
282
|
+
def initialize(params={})
|
283
|
+
@rep_times = {}
|
184
284
|
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
@diff_threads << Thread.new do
|
189
|
-
# Generate diff
|
190
|
-
diff = diff_strings(old_content, new_content)
|
191
|
-
diff.sub!(/^--- .*/, '--- ' + rep.raw_path)
|
192
|
-
diff.sub!(/^\+\+\+ .*/, '+++ ' + rep.raw_path)
|
285
|
+
@reps = params.fetch(:reps)
|
286
|
+
end
|
193
287
|
|
194
|
-
|
195
|
-
|
196
|
-
|
288
|
+
# @see Listener#start
|
289
|
+
def start
|
290
|
+
Nanoc::NotificationCenter.on(:compilation_started) do |rep|
|
291
|
+
@rep_times[rep.raw_path] = Time.now
|
292
|
+
end
|
293
|
+
Nanoc::NotificationCenter.on(:compilation_ended) do |rep|
|
294
|
+
@rep_times[rep.raw_path] = Time.now - @rep_times[rep.raw_path]
|
295
|
+
end
|
296
|
+
Nanoc::NotificationCenter.on(:rep_written) do |rep, path, is_created, is_modified|
|
297
|
+
action = (is_created ? :create : (is_modified ? :update : :identical))
|
298
|
+
level = (is_created ? :high : (is_modified ? :high : :low))
|
299
|
+
duration = Time.now - @rep_times[rep.raw_path] if @rep_times[rep.raw_path]
|
300
|
+
Nanoc::CLI::Logger.instance.file(level, action, path, duration)
|
197
301
|
end
|
198
302
|
end
|
199
|
-
end
|
200
303
|
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
old_file.write(a)
|
210
|
-
old_file.flush
|
211
|
-
new_file.write(b)
|
212
|
-
new_file.flush
|
213
|
-
|
214
|
-
# Diff
|
215
|
-
cmd = [ 'diff', '-u', old_file.path, new_file.path ]
|
216
|
-
Open3.popen3(*cmd) do |stdin, stdout, stderr|
|
217
|
-
result = stdout.read
|
218
|
-
return (result == '' ? nil : result)
|
304
|
+
# @see Listener#stop
|
305
|
+
def stop
|
306
|
+
super
|
307
|
+
@reps.select { |r| !r.compiled? }.each do |rep|
|
308
|
+
rep.raw_paths.each do |snapshot_name, filename|
|
309
|
+
next if filename.nil?
|
310
|
+
duration = @rep_times[filename]
|
311
|
+
Nanoc::CLI::Logger.instance.file(:high, :skip, filename, duration)
|
219
312
|
end
|
220
313
|
end
|
221
314
|
end
|
222
|
-
rescue Errno::ENOENT
|
223
|
-
warn 'Failed to run `diff`, so no diff with the previously compiled ' \
|
224
|
-
'content will be available.'
|
225
|
-
nil
|
226
|
-
end
|
227
|
-
|
228
|
-
def start_filter_progress(rep, filter_name)
|
229
|
-
# Only show progress on terminals
|
230
|
-
return if !$stdout.tty?
|
231
315
|
|
232
|
-
|
233
|
-
delay = 1.0
|
234
|
-
step = 0
|
235
|
-
|
236
|
-
text = " running #{filter_name} filter… "
|
237
|
-
|
238
|
-
loop do
|
239
|
-
if Thread.current[:stopped]
|
240
|
-
# Clear
|
241
|
-
if delay < 0.1
|
242
|
-
$stdout.print ' ' * (text.length + 3) + "\r"
|
243
|
-
end
|
316
|
+
end
|
244
317
|
|
245
|
-
|
246
|
-
|
318
|
+
def run
|
319
|
+
self.require_site
|
320
|
+
self.check_for_deprecated_usage
|
321
|
+
self.setup_listeners
|
247
322
|
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
323
|
+
puts "Compiling site…"
|
324
|
+
time_before = Time.now
|
325
|
+
self.site.compile
|
326
|
+
self.prune
|
327
|
+
time_after = Time.now
|
328
|
+
puts
|
329
|
+
puts "Site compiled in #{format('%.2f', time_after - time_before)}s."
|
330
|
+
end
|
253
331
|
|
254
|
-
|
255
|
-
delay -= 0.1
|
256
|
-
end
|
332
|
+
protected
|
257
333
|
|
334
|
+
def prune
|
335
|
+
if self.site.config[:prune][:auto_prune]
|
336
|
+
Nanoc::Extra::Pruner.new(self.site, :exclude => self.prune_config_exclude).run
|
258
337
|
end
|
259
338
|
end
|
260
339
|
|
261
|
-
def
|
262
|
-
|
263
|
-
return if !$stdout.tty?
|
340
|
+
def setup_listeners
|
341
|
+
@listeners = []
|
264
342
|
|
265
|
-
|
266
|
-
|
343
|
+
if self.site.config[:enable_output_diff]
|
344
|
+
@listeners << Nanoc::CLI::Commands::Compile::DiffGenerator.new
|
345
|
+
end
|
267
346
|
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
return if max_filter_name_length.nil?
|
347
|
+
if self.debug?
|
348
|
+
@listeners << Nanoc::CLI::Commands::Compile::DebugPrinter.new
|
349
|
+
end
|
272
350
|
|
273
|
-
|
274
|
-
|
275
|
-
$stderr.puts
|
276
|
-
$stderr.puts "Warning: profiling information may not be accurate because " +
|
277
|
-
"some items were not compiled."
|
351
|
+
if options.fetch(:verbose, false)
|
352
|
+
@listeners << Nanoc::CLI::Commands::Compile::TimingRecorder.new(:reps => self.reps)
|
278
353
|
end
|
279
354
|
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
puts '-' * max_filter_name_length + '-+-----------------------------------'
|
355
|
+
unless ENV.has_key?('TRAVIS')
|
356
|
+
@listeners << Nanoc::CLI::Commands::Compile::GCController.new
|
357
|
+
end
|
284
358
|
|
285
|
-
@
|
286
|
-
# Extract data
|
287
|
-
filter_name, samples = *row
|
359
|
+
@listeners << Nanoc::CLI::Commands::Compile::FileActionPrinter.new(:reps => self.reps)
|
288
360
|
|
289
|
-
|
290
|
-
|
291
|
-
min = samples.min
|
292
|
-
tot = samples.inject { |memo, i| memo + i}
|
293
|
-
avg = tot/count
|
294
|
-
max = samples.max
|
361
|
+
@listeners.each { |s| s.start }
|
362
|
+
end
|
295
363
|
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
avg = format('%4.2f', avg)
|
300
|
-
max = format('%4.2f', max)
|
301
|
-
tot = format('%5.2f', tot)
|
364
|
+
def teardown_listeners
|
365
|
+
@listeners.each { |s| s.stop }
|
366
|
+
end
|
302
367
|
|
303
|
-
|
304
|
-
|
305
|
-
puts "#{filter_name} | #{count} #{min}s #{avg}s #{max}s #{tot}s"
|
306
|
-
end
|
368
|
+
def reps
|
369
|
+
self.site.items.map { |i| i.reps }.flatten
|
307
370
|
end
|
371
|
+
memoize :reps
|
308
372
|
|
309
|
-
|
373
|
+
def check_for_deprecated_usage
|
374
|
+
# Check presence of --all option
|
375
|
+
if options.has_key?(:all) || options.has_key?(:force)
|
376
|
+
$stderr.puts "Warning: the --force option (and its deprecated --all alias) are, as of nanoc 3.2, no longer supported and have no effect."
|
377
|
+
end
|
378
|
+
|
379
|
+
# Warn if trying to compile a single item
|
380
|
+
if arguments.size == 1
|
381
|
+
$stderr.puts '-' * 80
|
382
|
+
$stderr.puts 'Note: As of nanoc 3.2, it is no longer possible to compile a single item. When invoking the “compile” command, all items in the site will be compiled.'
|
383
|
+
$stderr.puts '-' * 80
|
384
|
+
end
|
385
|
+
end
|
310
386
|
|
311
387
|
def prune_config
|
312
388
|
self.site.config[:prune] || {}
|