nanoc 3.4.3 → 3.5.0b1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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] || {}
|