nanoc 3.6.2 → 3.6.3

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.
@@ -33,6 +33,18 @@ module Nanoc::CLI::Commands
33
33
  # @abstract Subclasses must override {#start} and may override {#stop}.
34
34
  class Listener
35
35
 
36
+ def initialize(params={})
37
+ end
38
+
39
+ # @param [Nanoc::CLI::CommandRunner] command_runner The command runner for this listener
40
+ #
41
+ # @return [Boolean] true if this listener should be enabled for the given command runner, false otherwise
42
+ #
43
+ # @abstract Returns `true` by default, but subclasses may override this.
44
+ def self.enable_for?(command_runner)
45
+ true
46
+ end
47
+
36
48
  # Starts the listener. Subclasses should override this method and set up listener notifications.
37
49
  #
38
50
  # @return [void]
@@ -53,6 +65,11 @@ module Nanoc::CLI::Commands
53
65
  # Generates diffs for every output file written
54
66
  class DiffGenerator < Listener
55
67
 
68
+ # @see Listener#enable_for?
69
+ def self.enable_for?(command_runner)
70
+ command_runner.site.config[:enable_output_diff]
71
+ end
72
+
56
73
  # @see Listener#start
57
74
  def start
58
75
  require 'tempfile'
@@ -138,6 +155,11 @@ module Nanoc::CLI::Commands
138
155
  # Records the time spent per filter and per item representation
139
156
  class TimingRecorder < Listener
140
157
 
158
+ # @see Listener#enable_for?
159
+ def self.enable_for?(command_runner)
160
+ command_runner.options.fetch(:verbose, false)
161
+ end
162
+
141
163
  # @option params [Array<Nanoc::ItemRep>] :reps The list of item representations in the site
142
164
  def initialize(params={})
143
165
  @filter_times = {}
@@ -205,7 +227,7 @@ module Nanoc::CLI::Commands
205
227
  tot = format('%5.2f', tot)
206
228
 
207
229
  # Output stats
208
- filter_name = format("%#{max_filter_name_length}s", filter_name)
230
+ filter_name = format("%#{max}s", filter_name)
209
231
  puts "#{filter_name} | #{count} #{min}s #{avg}s #{max}s #{tot}s"
210
232
  end
211
233
 
@@ -214,7 +236,12 @@ module Nanoc::CLI::Commands
214
236
  # Controls garbage collection so that it only occurs once every 20 items
215
237
  class GCController < Listener
216
238
 
217
- def initialize
239
+ # @see Listener#enable_for?
240
+ def self.enable_for?(command_runner)
241
+ ! ENV.has_key?('TRAVIS')
242
+ end
243
+
244
+ def initialize(params={})
218
245
  @gc_count = 0
219
246
  end
220
247
 
@@ -241,6 +268,11 @@ module Nanoc::CLI::Commands
241
268
  # Prints debug information (compilation started/ended, filtering started/ended, …)
242
269
  class DebugPrinter < Listener
243
270
 
271
+ # @see Listener#enable_for?
272
+ def self.enable_for?(command_runner)
273
+ command_runner.debug?
274
+ end
275
+
244
276
  # @see Listener#start
245
277
  def start
246
278
  Nanoc::NotificationCenter.on(:compilation_started) do |rep|
@@ -315,15 +347,23 @@ module Nanoc::CLI::Commands
315
347
 
316
348
  end
317
349
 
350
+ def initialize(options, arguments, command, params={})
351
+ super(options, arguments, command)
352
+ @listener_classes = params.fetch(:listener_classes, self.default_listener_classes)
353
+ end
354
+
318
355
  def run
356
+ time_before = Time.now
357
+
319
358
  self.load_site
320
359
  self.check_for_deprecated_usage
321
- self.setup_listeners
322
360
 
323
361
  puts "Compiling site…"
324
- time_before = Time.now
325
- self.site.compile
326
- self.prune
362
+ self.run_listeners_while do
363
+ self.site.compile
364
+ self.prune
365
+ end
366
+
327
367
  time_after = Time.now
328
368
  puts
329
369
  puts "Site compiled in #{format('%.2f', time_after - time_before)}s."
@@ -337,28 +377,33 @@ module Nanoc::CLI::Commands
337
377
  end
338
378
  end
339
379
 
340
- def setup_listeners
341
- @listeners = []
342
-
343
- if self.site.config[:enable_output_diff]
344
- @listeners << Nanoc::CLI::Commands::Compile::DiffGenerator.new
345
- end
346
-
347
- if self.debug?
348
- @listeners << Nanoc::CLI::Commands::Compile::DebugPrinter.new
349
- end
380
+ def default_listener_classes
381
+ [
382
+ Nanoc::CLI::Commands::Compile::DiffGenerator,
383
+ Nanoc::CLI::Commands::Compile::DebugPrinter,
384
+ Nanoc::CLI::Commands::Compile::TimingRecorder,
385
+ Nanoc::CLI::Commands::Compile::GCController,
386
+ Nanoc::CLI::Commands::Compile::FileActionPrinter
387
+ ]
388
+ end
350
389
 
351
- if options.fetch(:verbose, false)
352
- @listeners << Nanoc::CLI::Commands::Compile::TimingRecorder.new(:reps => self.reps)
353
- end
390
+ def setup_listeners
391
+ @listeners = @listener_classes.
392
+ select { |klass| klass.enable_for?(self) }.
393
+ map { |klass| klass.new(:reps => self.reps) }
354
394
 
355
- unless ENV.has_key?('TRAVIS')
356
- @listeners << Nanoc::CLI::Commands::Compile::GCController.new
357
- end
395
+ @listeners.each { |s| s.start }
396
+ end
358
397
 
359
- @listeners << Nanoc::CLI::Commands::Compile::FileActionPrinter.new(:reps => self.reps)
398
+ def listeners
399
+ @listeners
400
+ end
360
401
 
361
- @listeners.each { |s| s.start }
402
+ def run_listeners_while
403
+ self.setup_listeners
404
+ yield
405
+ ensure
406
+ self.teardown_listeners
362
407
  end
363
408
 
364
409
  def teardown_listeners
@@ -80,12 +80,12 @@ module Nanoc::CLI::Commands
80
80
  rebuilder.call(removed[0]) if removed[0]
81
81
  end
82
82
 
83
- listener = Listen::MultiListener.new(*dirs_to_watch).change(&callback)
84
- listener_root = Listen::MultiListener.new('', :filter => files_to_watch, :ignore => ignore_dir).change(&callback)
83
+ listener = Listen::Listener.new(*dirs_to_watch).change(&callback)
84
+ listener_root = Listen::Listener.new('.', :filter => files_to_watch, :ignore => ignore_dir).change(&callback)
85
85
 
86
86
  begin
87
- listener_root.start(false)
88
- listener.start
87
+ listener_root.start
88
+ listener.start!
89
89
  rescue Interrupt
90
90
  listener.stop
91
91
  listener_root.stop
@@ -98,30 +98,47 @@ module Nanoc::CLI::Commands
98
98
  # A list of commandline tool names that can be used to send notifications
99
99
  TOOLS = %w( growlnotify notify-send ) unless defined? TOOLS
100
100
 
101
- # The tool to use for discovering binaries' locations
102
- FIND_BINARY_COMMAND = RUBY_PLATFORM =~ /mingw|mswin/ ? "where" : "which" unless defined? FIND_BINARY_COMMAND
103
-
104
101
  # Send a notification. If no notifier is found, no notification will be
105
102
  # created.
106
103
  #
107
104
  # @param [String] message The message to include in the notification
108
105
  def notify(message)
109
106
  return if tool.nil?
110
- send(tool.tr('-', '_'), message)
107
+ if tool == 'growlnotify' && self.on_windows?
108
+ self.growlnotify_windows(message)
109
+ else
110
+ send(tool.tr('-', '_'), message)
111
+ end
112
+ end
113
+
114
+ protected
115
+
116
+ def have_tool_nix?(tool)
117
+ !`which #{tool}`.empty?
118
+ rescue Errno::ENOENT
119
+ false
120
+ end
121
+
122
+ def have_tool_windows?(tool)
123
+ !`where #{tool} 2> nul`.empty?
124
+ rescue Errno::ENOENT
125
+ false
111
126
  end
112
127
 
113
- private
128
+ def have_tool?(tool)
129
+ if self.on_windows?
130
+ self.have_tool_windows?(tool)
131
+ else
132
+ self.have_tool_nix?(tool)
133
+ end
134
+ end
114
135
 
115
136
  def tool
116
137
  @tool ||= begin
117
138
  require 'terminal-notifier'
118
139
  'terminal-notify'
119
140
  rescue LoadError
120
- begin
121
- TOOLS.find { |t| !`#{FIND_BINARY_COMMAND} #{t}`.empty? }
122
- rescue Errno::ENOENT
123
- nil
124
- end
141
+ TOOLS.find { |t| have_tool?(t) }
125
142
  end
126
143
  end
127
144
 
@@ -129,14 +146,30 @@ module Nanoc::CLI::Commands
129
146
  TerminalNotifier.notify(message, :title => "nanoc")
130
147
  end
131
148
 
149
+ def growlnotify_cmd_for(message)
150
+ [ 'growlnotify', '-m', message ]
151
+ end
152
+
132
153
  def growlnotify(message)
133
- system('growlnotify', '-m', message)
154
+ system(*self.growlnotify_cmd_for(message))
155
+ end
156
+
157
+ def growlnotify_windows_cmd_for(message)
158
+ [ 'growlnotify', '/t:nanoc', message ]
159
+ end
160
+
161
+ def growlnotify_windows(message)
162
+ system(*self.growlnotify_windows_cmd_for(message))
134
163
  end
135
164
 
136
165
  def notify_send(message)
137
166
  system('notify-send', message)
138
167
  end
139
168
 
169
+ def on_windows?
170
+ !!(RUBY_PLATFORM =~ /(mingw|bccwin|wince|mswin32)/i)
171
+ end
172
+
140
173
  end
141
174
 
142
175
  end
@@ -52,7 +52,7 @@ module Nanoc::DataSources
52
52
  create_object('layouts', content, attributes, identifier, params)
53
53
  end
54
54
 
55
- private
55
+ protected
56
56
 
57
57
  # Creates a new object (item or layout) on disk in dir_name according to
58
58
  # the given identifier. The file will have its attributes taken from the
@@ -144,7 +144,7 @@ module Nanoc::DataSources
144
144
  # }
145
145
  def all_split_files_in(dir_name)
146
146
  # Get all good file names
147
- filenames = Dir[dir_name + '/**/*'].select { |i| File.file?(i) }
147
+ filenames = self.all_files_in(dir_name)
148
148
  filenames.reject! { |fn| fn =~ /(~|\.orig|\.rej|\.bak)$/ }
149
149
 
150
150
  # Group by identifier
@@ -173,6 +173,11 @@ module Nanoc::DataSources
173
173
  grouped_filenames
174
174
  end
175
175
 
176
+ # Returns all files in the given directory and directories below it.
177
+ def all_files_in(dir_name)
178
+ Nanoc::Extra::FilesystemTools.all_files_in(dir_name)
179
+ end
180
+
176
181
  # Returns the filename for the given base filename and the extension.
177
182
  #
178
183
  # If the extension is nil, this function should return nil as well.
@@ -26,24 +26,21 @@ module Nanoc::DataSources
26
26
  # exclude them from the Blogging helper's atom feed generator, among other
27
27
  # things.
28
28
  class Static < Nanoc::DataSource
29
+
29
30
  identifier :static
30
31
 
31
32
  def items
32
33
  # Get prefix
33
34
  prefix = config[:prefix] || 'static'
34
35
 
35
- # Get all files under prefix dir
36
- filenames = Dir[prefix + '/**/*'].select { |f| File.file?(f) }
37
-
38
36
  # Convert filenames to items
39
- filenames.map do |filename|
37
+ self.all_files_in(prefix).map do |filename|
40
38
  attributes = {
41
39
  :extension => File.extname(filename)[1..-1],
42
40
  :filename => filename,
43
41
  }
44
42
  attributes[:is_hidden] = true unless config[:hide_items] == false
45
43
  identifier = filename[(prefix.length+1)..-1] + '/'
46
-
47
44
  mtime = File.mtime(filename)
48
45
  checksum = Pathname.new(filename).checksum
49
46
 
@@ -56,5 +53,12 @@ module Nanoc::DataSources
56
53
  end
57
54
  end
58
55
 
56
+ protected
57
+
58
+ def all_files_in(dir_name)
59
+ Nanoc::Extra::FilesystemTools.all_files_in(dir_name)
60
+ end
61
+
59
62
  end
63
+
60
64
  end
@@ -5,6 +5,7 @@ module Nanoc::Extra
5
5
  autoload 'AutoCompiler', 'nanoc/extra/auto_compiler'
6
6
  autoload 'Checking', 'nanoc/extra/checking'
7
7
  autoload 'CHiCk', 'nanoc/extra/chick'
8
+ autoload 'FilesystemTools', 'nanoc/extra/filesystem_tools'
8
9
  autoload 'LinkCollector', 'nanoc/extra/link_collector.rb'
9
10
  autoload 'Pruner', 'nanoc/extra/pruner'
10
11
  autoload 'Validators', 'nanoc/extra/validators'
@@ -136,9 +136,22 @@ module ::Nanoc::Extra::Checking::Checks
136
136
  raise 'should not have gotten here'
137
137
  end
138
138
 
139
+ def path_for_url(url)
140
+ if url.path.nil? || url.path.empty?
141
+ path = '/'
142
+ else
143
+ path = url.path
144
+ end
145
+
146
+ if url.query
147
+ path << '?' << url.query
148
+ end
149
+
150
+ path
151
+ end
152
+
139
153
  def request_url_once(url, req_method = Net::HTTP::Head)
140
- path = (url.path.nil? || url.path.empty? ? '/' : url.path)
141
- req = req_method.new(path)
154
+ req = req_method.new(self.path_for_url(url))
142
155
  http = Net::HTTP.new(url.host, url.port)
143
156
  if url.instance_of? URI::HTTPS
144
157
  http.use_ssl = true
@@ -16,7 +16,7 @@ module Nanoc::Extra
16
16
  def freeze
17
17
  end
18
18
 
19
- def respond_to?(meth)
19
+ def respond_to?(meth, include_all=false)
20
20
  file_instance_methods.include?(meth.to_sym)
21
21
  end
22
22
 
@@ -0,0 +1,124 @@
1
+ # encoding: utf-8
2
+
3
+ module Nanoc::Extra
4
+
5
+ # Contains useful functions for managing the filesystem.
6
+ #
7
+ # @api private
8
+ module FilesystemTools
9
+
10
+ # Error that is raised when too many symlink indirections are encountered.
11
+ #
12
+ # @api private
13
+ class MaxSymlinkDepthExceededError < ::Nanoc::Errors::GenericTrivial
14
+
15
+ # @return [String] The last filename that was attempted to be
16
+ # resolved before giving up
17
+ attr_reader :filename
18
+
19
+ # @param [String] filename The last filename that was attempted to be
20
+ # resolved before giving up
21
+ def initialize(filename)
22
+ @filename = filename
23
+ super("Too many indirections while resolving symlinks. I gave up after finding out #{filename} was yet another symlink. Sorry!")
24
+ end
25
+
26
+ end
27
+
28
+ # Error that is raised when a file of an unknown type is encountered
29
+ # (something other than file, directory or link).
30
+ #
31
+ # @api private
32
+ class UnsupportedFileTypeError < ::Nanoc::Errors::GenericTrivial
33
+
34
+ # @return [String] The filename of the file whose type is not supported
35
+ attr_reader :filename
36
+
37
+ # @param [String] filename The filename of the file whose type is not
38
+ # supported
39
+ def initialize(filename)
40
+ @filename = filename
41
+ super("The file at #{filename} is of an unsupported type (expected file, directory or link, but it is #{File.ftype(filename)}")
42
+ end
43
+
44
+ end
45
+
46
+ # Returns all files in the given directory and directories below it,
47
+ # following symlinks up to a maximum of `recursion_limit` times.
48
+ #
49
+ # @param [String] dir_name The name of the directory whose contents to
50
+ # fetch
51
+ #
52
+ # @param [Integer] recursion_limit The maximum number of times to
53
+ # recurse into a symlink to a directory
54
+ #
55
+ # @return [Array<String>] A list of filenames
56
+ #
57
+ # @raise [MaxSymlinkDepthExceededError] if too many indirections are
58
+ # encountered while resolving symlinks
59
+ #
60
+ # @raise [UnsupportedFileTypeError] if a file of an unsupported type is
61
+ # detected (something other than file, directory or link)
62
+ def all_files_in(dir_name, recursion_limit=10)
63
+ Dir[dir_name + '/**/*'].map do |fn|
64
+ case File.ftype(fn)
65
+ when 'link'
66
+ if 0 == recursion_limit
67
+ raise MaxSymlinkDepthExceededError.new(fn)
68
+ else
69
+ absolute_target = self.resolve_symlink(fn)
70
+ if File.file?(absolute_target)
71
+ fn
72
+ else
73
+ all_files_in(absolute_target, recursion_limit-1).map do |sfn|
74
+ fn + sfn[absolute_target.size..-1]
75
+ end
76
+ end
77
+ end
78
+ when 'file'
79
+ fn
80
+ when 'directory'
81
+ nil
82
+ else
83
+ raise UnsupportedFileTypeError.new(fn)
84
+ end
85
+ end.compact.flatten
86
+ end
87
+ module_function :all_files_in
88
+
89
+ # Resolves the given symlink into an absolute path.
90
+ #
91
+ # @param [String] filename The filename of the symlink to resolve
92
+ #
93
+ # @param [Integer] recursion_limit The maximum number of times to recurse
94
+ # into a symlink
95
+ #
96
+ # @return [String] The absolute resolved filename of the symlink target
97
+ #
98
+ # @raise [MaxSymlinkDepthExceededError] if too many indirections are
99
+ # encountered while resolving symlinks
100
+ #
101
+ # @raise [UnsupportedFileTypeError] if a file of an unsupported type is
102
+ # detected (something other than file, directory or link)
103
+ def resolve_symlink(filename, recursion_limit=5)
104
+ target = File.readlink(filename)
105
+ absolute_target = File.expand_path(target, File.dirname(filename))
106
+
107
+ case File.ftype(absolute_target)
108
+ when 'link'
109
+ if 0 == recursion_limit
110
+ raise MaxSymlinkDepthExceededError.new(absolute_target)
111
+ else
112
+ self.resolve_symlink(absolute_target, recursion_limit-1)
113
+ end
114
+ when 'file', 'directory'
115
+ absolute_target
116
+ else
117
+ raise UnsupportedFileTypeError.new(absolute_target)
118
+ end
119
+ end
120
+ module_function :resolve_symlink
121
+
122
+ end
123
+
124
+ end