nanoc3 3.1.9 → 3.2.0a1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (136) hide show
  1. data/LICENSE +1 -1
  2. data/NEWS.md +0 -50
  3. data/README.md +3 -15
  4. data/bin/nanoc3 +2 -0
  5. data/lib/nanoc3/base/checksummer.rb +40 -0
  6. data/lib/nanoc3/base/code_snippet.rb +30 -12
  7. data/lib/nanoc3/base/compiled_content_cache.rb +86 -0
  8. data/lib/nanoc3/base/compiler.rb +134 -95
  9. data/lib/nanoc3/base/compiler_dsl.rb +12 -11
  10. data/lib/nanoc3/base/core_ext/string.rb +2 -2
  11. data/lib/nanoc3/base/data_source.rb +17 -16
  12. data/lib/nanoc3/base/dependency_tracker.rb +102 -121
  13. data/lib/nanoc3/base/directed_graph.rb +65 -3
  14. data/lib/nanoc3/base/errors.rb +20 -16
  15. data/lib/nanoc3/base/item.rb +58 -50
  16. data/lib/nanoc3/base/item_rep.rb +177 -150
  17. data/lib/nanoc3/base/layout.rb +51 -18
  18. data/lib/nanoc3/base/notification_center.rb +8 -8
  19. data/lib/nanoc3/base/plugin_registry.rb +9 -9
  20. data/lib/nanoc3/base/rule.rb +18 -9
  21. data/lib/nanoc3/base/rule_context.rb +5 -5
  22. data/lib/nanoc3/base/site.rb +135 -47
  23. data/lib/nanoc3/base.rb +21 -19
  24. data/lib/nanoc3/cli/base.rb +51 -74
  25. data/lib/nanoc3/cli/commands/autocompile.rb +3 -0
  26. data/lib/nanoc3/cli/commands/compile.rb +35 -74
  27. data/lib/nanoc3/cli/commands/create_site.rb +17 -5
  28. data/lib/nanoc3/cli/commands/debug.rb +11 -4
  29. data/lib/nanoc3/cli/commands/view.rb +0 -1
  30. data/lib/nanoc3/cli/commands/watch.rb +148 -0
  31. data/lib/nanoc3/cli/commands.rb +1 -0
  32. data/lib/nanoc3/cli/logger.rb +15 -21
  33. data/lib/nanoc3/data_sources/deprecated/twitter.rb +0 -1
  34. data/lib/nanoc3/data_sources/filesystem.rb +11 -40
  35. data/lib/nanoc3/data_sources/filesystem_unified.rb +22 -22
  36. data/lib/nanoc3/extra/auto_compiler.rb +1 -1
  37. data/lib/nanoc3/extra/chick.rb +8 -8
  38. data/lib/nanoc3/extra/deployers/rsync.rb +2 -3
  39. data/lib/nanoc3/extra/validators/links.rb +32 -51
  40. data/lib/nanoc3/extra/validators/w3c.rb +2 -2
  41. data/lib/nanoc3/extra/vcs.rb +1 -1
  42. data/lib/nanoc3/filters/colorize_syntax.rb +15 -19
  43. data/lib/nanoc3/filters/erb.rb +1 -5
  44. data/lib/nanoc3/filters/erubis.rb +1 -5
  45. data/lib/nanoc3/filters/haml.rb +1 -2
  46. data/lib/nanoc3/filters/less.rb +2 -51
  47. data/lib/nanoc3/filters/mustache.rb +21 -0
  48. data/lib/nanoc3/filters/rdiscount.rb +1 -2
  49. data/lib/nanoc3/filters/relativize_paths.rb +3 -2
  50. data/lib/nanoc3/filters/sass.rb +50 -56
  51. data/lib/nanoc3/filters.rb +2 -0
  52. data/lib/nanoc3/helpers/blogging.rb +22 -29
  53. data/lib/nanoc3/helpers/breadcrumbs.rb +1 -1
  54. data/lib/nanoc3/helpers/capturing.rb +1 -1
  55. data/lib/nanoc3/helpers/filtering.rb +1 -1
  56. data/lib/nanoc3/helpers/link_to.rb +10 -21
  57. data/lib/nanoc3/helpers/rendering.rb +5 -24
  58. data/lib/nanoc3/helpers/tagging.rb +6 -6
  59. data/lib/nanoc3/helpers/text.rb +2 -2
  60. data/lib/nanoc3.rb +1 -1
  61. metadata +35 -93
  62. data/.gemtest +0 -0
  63. data/doc/yardoc_templates/default/layout/html/footer.erb +0 -10
  64. data/nanoc3.gemspec +0 -41
  65. data/tasks/clean.rake +0 -11
  66. data/tasks/doc.rake +0 -14
  67. data/tasks/gem.rake +0 -13
  68. data/tasks/test.rake +0 -38
  69. data/test/base/core_ext/array_spec.rb +0 -23
  70. data/test/base/core_ext/hash_spec.rb +0 -41
  71. data/test/base/core_ext/string_spec.rb +0 -27
  72. data/test/base/test_code_snippet.rb +0 -33
  73. data/test/base/test_compiler.rb +0 -410
  74. data/test/base/test_compiler_dsl.rb +0 -121
  75. data/test/base/test_context.rb +0 -33
  76. data/test/base/test_data_source.rb +0 -48
  77. data/test/base/test_dependency_tracker.rb +0 -510
  78. data/test/base/test_directed_graph.rb +0 -91
  79. data/test/base/test_filter.rb +0 -85
  80. data/test/base/test_item.rb +0 -141
  81. data/test/base/test_item_rep.rb +0 -953
  82. data/test/base/test_layout.rb +0 -44
  83. data/test/base/test_notification_center.rb +0 -36
  84. data/test/base/test_plugin.rb +0 -32
  85. data/test/base/test_rule.rb +0 -21
  86. data/test/base/test_rule_context.rb +0 -63
  87. data/test/base/test_site.rb +0 -366
  88. data/test/cli/commands/test_compile.rb +0 -12
  89. data/test/cli/commands/test_create_item.rb +0 -12
  90. data/test/cli/commands/test_create_layout.rb +0 -28
  91. data/test/cli/commands/test_create_site.rb +0 -24
  92. data/test/cli/commands/test_help.rb +0 -12
  93. data/test/cli/commands/test_info.rb +0 -12
  94. data/test/cli/commands/test_update.rb +0 -12
  95. data/test/cli/test_logger.rb +0 -12
  96. data/test/data_sources/test_filesystem.rb +0 -420
  97. data/test/data_sources/test_filesystem_unified.rb +0 -538
  98. data/test/data_sources/test_filesystem_verbose.rb +0 -359
  99. data/test/extra/core_ext/test_enumerable.rb +0 -32
  100. data/test/extra/core_ext/test_time.rb +0 -17
  101. data/test/extra/deployers/test_rsync.rb +0 -234
  102. data/test/extra/test_auto_compiler.rb +0 -482
  103. data/test/extra/test_file_proxy.rb +0 -21
  104. data/test/extra/test_vcs.rb +0 -24
  105. data/test/extra/validators/test_links.rb +0 -53
  106. data/test/extra/validators/test_w3c.rb +0 -49
  107. data/test/filters/test_bluecloth.rb +0 -20
  108. data/test/filters/test_coderay.rb +0 -46
  109. data/test/filters/test_colorize_syntax.rb +0 -84
  110. data/test/filters/test_erb.rb +0 -72
  111. data/test/filters/test_erubis.rb +0 -72
  112. data/test/filters/test_haml.rb +0 -98
  113. data/test/filters/test_kramdown.rb +0 -20
  114. data/test/filters/test_less.rb +0 -118
  115. data/test/filters/test_markaby.rb +0 -26
  116. data/test/filters/test_maruku.rb +0 -20
  117. data/test/filters/test_rainpress.rb +0 -31
  118. data/test/filters/test_rdiscount.rb +0 -33
  119. data/test/filters/test_rdoc.rb +0 -18
  120. data/test/filters/test_redcloth.rb +0 -20
  121. data/test/filters/test_relativize_paths.rb +0 -231
  122. data/test/filters/test_rubypants.rb +0 -20
  123. data/test/filters/test_sass.rb +0 -235
  124. data/test/gem_loader.rb +0 -11
  125. data/test/helper.rb +0 -99
  126. data/test/helpers/test_blogging.rb +0 -808
  127. data/test/helpers/test_breadcrumbs.rb +0 -83
  128. data/test/helpers/test_capturing.rb +0 -42
  129. data/test/helpers/test_filtering.rb +0 -108
  130. data/test/helpers/test_html_escape.rb +0 -18
  131. data/test/helpers/test_link_to.rb +0 -251
  132. data/test/helpers/test_rendering.rb +0 -109
  133. data/test/helpers/test_tagging.rb +0 -89
  134. data/test/helpers/test_text.rb +0 -26
  135. data/test/helpers/test_xml_sitemap.rb +0 -69
  136. data/test/tasks/test_clean.rb +0 -71
@@ -4,9 +4,39 @@ module Nanoc3::CLI
4
4
 
5
5
  class Base < Cri::Base
6
6
 
7
+ # A hash that contains the name of the gem for a given required file. If a
8
+ # {#require} fails, the gem name is looked up in this hash.
9
+ GEM_NAMES = {
10
+ 'adsf' => 'adsf',
11
+ 'bluecloth' => 'bluecloth',
12
+ 'builder' => 'builder',
13
+ 'coderay' => 'coderay',
14
+ 'cri' => 'cri',
15
+ 'erubis' => 'erubis',
16
+ 'fssm' => 'fssm',
17
+ 'haml' => 'haml',
18
+ 'json' => 'json',
19
+ 'kramdown' => 'kramdown',
20
+ 'less' => 'less',
21
+ 'markaby' => 'markaby',
22
+ 'maruku' => 'maruku',
23
+ 'mime/types' => 'mime-types',
24
+ 'nokogiri' => 'nokogiri',
25
+ 'rack' => 'rack',
26
+ 'rack/cache' => 'rack-cache',
27
+ 'rainpress' => 'rainpress',
28
+ 'rdiscount' => 'rdiscount',
29
+ 'redcloth' => 'redcloth',
30
+ 'rubypants' => 'rubypants',
31
+ 'sass' => 'sass',
32
+ 'w3c_validators' => 'w3c_validators'
33
+ }
34
+
7
35
  def initialize
8
36
  super('nanoc3')
9
37
 
38
+ @debug = false
39
+
10
40
  # Add help command
11
41
  self.help_command = Nanoc3::CLI::Commands::Help.new
12
42
  add_command(self.help_command)
@@ -21,22 +51,20 @@ module Nanoc3::CLI
21
51
  add_command(Nanoc3::CLI::Commands::Info.new)
22
52
  add_command(Nanoc3::CLI::Commands::Update.new)
23
53
  add_command(Nanoc3::CLI::Commands::View.new)
54
+ add_command(Nanoc3::CLI::Commands::Watch.new)
24
55
  end
25
56
 
26
- # Returns a fully initialised base instance. It is recommended to use this
27
- # shared instance than to create new ones, as this will be the instance
28
- # that will be used when reading all code from the `lib/` directory.
29
- #
30
- # @return [Nanoc3::CLI::Base]
31
57
  def self.shared_base
32
58
  @shared_base ||= Nanoc3::CLI::Base.new
33
59
  end
34
60
 
35
- # Asserts that the current working directory contains a site
36
- # ({Nanoc3::Site} instance). If no site is present, prints an error
37
- # message and exits.
38
- #
39
- # @return [void]
61
+ # @return [Boolean] true if debug output is enabled, false if not
62
+ def debug?
63
+ @debug
64
+ end
65
+
66
+ # Helper function which can be called when a command is executed that
67
+ # requires a site, such as the compile command.
40
68
  def require_site
41
69
  if site.nil?
42
70
  $stderr.puts 'The current working directory does not seem to be a ' +
@@ -45,10 +73,7 @@ module Nanoc3::CLI
45
73
  end
46
74
  end
47
75
 
48
- # Gets the site ({Nanoc3::Site} instance) in the current directory and
49
- # loads its data.
50
- #
51
- # @return [Nanoc3::Site] The site in the current working directory
76
+ # Gets the site (Nanoc3::Site) in the current directory and loads its data.
52
77
  def site
53
78
  # Load site if possible
54
79
  if File.file?('config.yaml') && (!self.instance_variable_defined?(:@site) || @site.nil?)
@@ -63,16 +88,8 @@ module Nanoc3::CLI
63
88
  @site
64
89
  end
65
90
 
66
- # @see ::Cri::Base#run
91
+ # Inherited from ::Cri::Base
67
92
  def run(args)
68
- # Set exit handler
69
- [ 'INT', 'TERM' ].each do |signal|
70
- Signal.trap(signal) do
71
- puts
72
- exit!(0)
73
- end
74
- end
75
-
76
93
  super(args)
77
94
  rescue Interrupt => e
78
95
  exit(1)
@@ -81,12 +98,8 @@ module Nanoc3::CLI
81
98
  exit(1)
82
99
  end
83
100
 
84
- # Prints the given error to stderr. Includes message, possible resolution
85
- # (see {#resolution_for}), compilation stack, backtrace, etc.
86
- #
87
- # @param [Error] error The error that should be described
88
- #
89
- # @return [void]
101
+ # Prints the given error to stderr. Includes message, possible resolution,
102
+ # compilation stack, backtrace, etc.
90
103
  def print_error(error)
91
104
  $stderr.puts
92
105
 
@@ -130,44 +143,14 @@ module Nanoc3::CLI
130
143
  $stderr.puts error.backtrace.to_enum(:each_with_index).map { |item, index| " #{index}. #{item}" }.join("\n")
131
144
  end
132
145
 
133
- # Attempts to find a resolution for the given error, or nil if no
134
- # resolution can be automatically obtained.
135
- #
136
- # @param [Error] error The error to find a resolution for
137
- #
138
- # @return [String] The resolution for the given error
146
+ # Returns a string containing hints for resolving the given error, or nil
147
+ # if no resolution can be automatically obtained.
139
148
  def resolution_for(error)
140
- # FIXME this should probably go somewhere else so that 3rd-party code can add other gem names too
141
- gem_names = {
142
- 'adsf' => 'adsf',
143
- 'bluecloth' => 'bluecloth',
144
- 'builder' => 'builder',
145
- 'coderay' => 'coderay',
146
- 'cri' => 'cri',
147
- 'erubis' => 'erubis',
148
- 'haml' => 'haml',
149
- 'json' => 'json',
150
- 'less' => 'less',
151
- 'markaby' => 'markaby',
152
- 'maruku' => 'maruku',
153
- 'mime/types' => 'mime-types',
154
- 'rack' => 'rack',
155
- 'rack/cache' => 'rack-cache',
156
- 'rainpress' => 'rainpress',
157
- 'rdiscount' => 'rdiscount',
158
- 'redcloth' => 'redcloth',
159
- 'rubypants' => 'rubypants',
160
- 'sass' => 'sass',
161
- 'w3c_validators' => 'w3c_validators'
162
- }
163
-
164
149
  case error
165
150
  when LoadError
166
151
  # Get gem name
167
- matches = error.message.match(/no such file to load -- ([^\s]+)/)
168
- return nil if matches.empty?
169
- lib_name = matches[1]
170
- gem_name = gem_names[$1]
152
+ lib_name = error.message.match(/no such file to load -- ([^\s]+)/)[1]
153
+ gem_name = GEM_NAMES[$1]
171
154
 
172
155
  # Build message
173
156
  if gem_name
@@ -179,10 +162,6 @@ module Nanoc3::CLI
179
162
  # Sets the data source's VCS to the VCS with the given name. Does nothing
180
163
  # when the site's data source does not support VCSes (i.e. does not
181
164
  # implement #vcs=).
182
- #
183
- # @param [String] vcs_name The name of the VCS that should be used
184
- #
185
- # @return [void]
186
165
  def set_vcs(vcs_name)
187
166
  # Skip if not possible
188
167
  return if vcs_name.nil? || site.nil?
@@ -203,7 +182,7 @@ module Nanoc3::CLI
203
182
  end
204
183
  end
205
184
 
206
- # @return [Array] The list of global option definitions
185
+ # Returns the list of global option definitionss.
207
186
  def global_option_definitions
208
187
  [
209
188
  {
@@ -224,7 +203,7 @@ module Nanoc3::CLI
224
203
  },
225
204
  {
226
205
  :long => 'debug', :short => 'd', :argument => :forbidden,
227
- :desc => 'enable debugging (set $DEBUG to true)'
206
+ :desc => 'enable debugging'
228
207
  },
229
208
  {
230
209
  :long => 'warn', :short => 'w', :argument => :forbidden,
@@ -233,20 +212,18 @@ module Nanoc3::CLI
233
212
  ]
234
213
  end
235
214
 
236
- # @see Cri::Base#handle_option
237
215
  def handle_option(option)
238
216
  case option
239
217
  when :version
240
218
  gem_info = defined?(Gem) ? "with RubyGems #{Gem::VERSION}" : "without RubyGems"
241
- engine = defined?(RUBY_ENGINE) ? RUBY_ENGINE : "ruby"
242
219
 
243
- puts "nanoc #{Nanoc3::VERSION} (c) 2007-2011 Denis Defreyne."
244
- puts "Running #{engine} #{RUBY_VERSION} (#{RUBY_RELEASE_DATE}) on #{RUBY_PLATFORM} #{gem_info}"
220
+ puts "nanoc #{Nanoc3::VERSION} (c) 2007-2010 Denis Defreyne."
221
+ puts "Ruby #{RUBY_VERSION} (#{RUBY_RELEASE_DATE}) running on #{RUBY_PLATFORM} #{gem_info}"
245
222
  exit 0
246
223
  when :verbose
247
224
  Nanoc3::CLI::Logger.instance.level = :low
248
225
  when :debug
249
- $DEBUG = true
226
+ @debug = true
250
227
  when :warn
251
228
  $-w = true
252
229
  when :'no-color'
@@ -49,6 +49,9 @@ module Nanoc3::CLI::Commands
49
49
  def run(options, arguments)
50
50
  require 'rack'
51
51
 
52
+ # Warn
53
+ warn 'WARNING: As of nanoc 3.2, the autocompiler is deprecated. Consider using the new and much faster “nanoc watch” command that recompiles the site on change rather than on request.'
54
+
52
55
  # Make sure we are in a nanoc site directory
53
56
  @base.require_site
54
57
 
@@ -76,8 +76,8 @@ module Nanoc3::CLI::Commands
76
76
 
77
77
  # Initialize profiling stuff
78
78
  time_before = Time.now
79
- @filter_times ||= {}
80
- @times_stack ||= []
79
+ @rep_times = {}
80
+ @filter_times = {}
81
81
  setup_notifications
82
82
 
83
83
  # Compile
@@ -91,9 +91,11 @@ module Nanoc3::CLI::Commands
91
91
 
92
92
  # Show skipped reps
93
93
  reps.select { |r| !r.compiled? }.each do |rep|
94
- next if rep.raw_path.nil?
95
- duration = @rep_times[rep.raw_path]
96
- Nanoc3::CLI::Logger.instance.file(:high, :skip, rep.raw_path, duration)
94
+ rep.raw_paths.each do |snapshot_name, filename|
95
+ next if filename.nil?
96
+ duration = @rep_times[filename]
97
+ Nanoc3::CLI::Logger.instance.file(:high, :skip, filename, duration)
98
+ end
97
99
  end
98
100
 
99
101
  # Show diff
@@ -101,11 +103,10 @@ module Nanoc3::CLI::Commands
101
103
 
102
104
  # Give general feedback
103
105
  puts
104
- puts "No items were modified." unless reps.any? { |r| r.modified? }
105
106
  puts "#{item.nil? ? 'Site' : 'Item'} compiled in #{format('%.2f', Time.now - time_before)}s."
106
107
 
108
+ # Give detailed feedback
107
109
  if options.has_key?(:verbose)
108
- print_state_feedback(reps)
109
110
  print_profiling_feedback(reps)
110
111
  end
111
112
  end
@@ -113,17 +114,39 @@ module Nanoc3::CLI::Commands
113
114
  private
114
115
 
115
116
  def setup_notifications
117
+ # File notifications
118
+ Nanoc3::NotificationCenter.on(:rep_written) do |rep, path, is_created, is_modified|
119
+ action = (is_created ? :create : (is_modified ? :update : :identical))
120
+ duration = Time.now - @rep_times[rep.raw_path] if @rep_times[rep.raw_path]
121
+ Nanoc3::CLI::Logger.instance.file(:high, action, path, duration)
122
+ end
123
+
124
+ # Debug notifications
116
125
  Nanoc3::NotificationCenter.on(:compilation_started) do |rep|
117
- rep_compilation_started(rep)
126
+ puts "*** Started compilation of #{rep.inspect}" if @base.debug?
118
127
  end
119
128
  Nanoc3::NotificationCenter.on(:compilation_ended) do |rep|
120
- rep_compilation_ended(rep)
129
+ puts "*** Ended compilation of #{rep.inspect}" if @base.debug?
130
+ end
131
+ Nanoc3::NotificationCenter.on(:compilation_failed) do |rep|
132
+ puts "*** Suspended compilation of #{rep.inspect} due to unmet dependencies" if @base.debug?
133
+ end
134
+ Nanoc3::NotificationCenter.on(:cached_content_used) do |rep|
135
+ puts "*** Used cached compiled content for #{rep.inspect} instead of recompiling" if @base.debug?
136
+ end
137
+
138
+ # Timing notifications
139
+ Nanoc3::NotificationCenter.on(:compilation_started) do |rep|
140
+ @rep_times[rep.raw_path] = Time.now
141
+ end
142
+ Nanoc3::NotificationCenter.on(:compilation_ended) do |rep|
143
+ @rep_times[rep.raw_path] = Time.now - @rep_times[rep.raw_path]
121
144
  end
122
145
  Nanoc3::NotificationCenter.on(:filtering_started) do |rep, filter_name|
123
- rep_filtering_started(rep, filter_name)
146
+ @filter_times[filter_name] = Time.now
124
147
  end
125
148
  Nanoc3::NotificationCenter.on(:filtering_ended) do |rep, filter_name|
126
- rep_filtering_ended(rep, filter_name)
149
+ @filter_times[filter_name] = Time.now - @filter_times[filter_name]
127
150
  end
128
151
  end
129
152
 
@@ -141,6 +164,7 @@ module Nanoc3::CLI::Commands
141
164
  next if diff.nil?
142
165
 
143
166
  # Fix header
167
+ # FIXME this may break for other lines starting with --- or +++
144
168
  diff.sub!(/^--- .*/, '--- ' + rep.raw_path)
145
169
  diff.sub!(/^\+\+\+ .*/, '+++ ' + rep.raw_path)
146
170
 
@@ -152,24 +176,6 @@ module Nanoc3::CLI::Commands
152
176
  File.open('output.diff', 'w') { |io| io.write(full_diff) }
153
177
  end
154
178
 
155
- def print_state_feedback(reps)
156
- # Categorise reps
157
- rest = reps
158
- created, rest = *rest.partition { |r| r.created? }
159
- modified, rest = *rest.partition { |r| r.modified? }
160
- skipped, rest = *rest.partition { |r| !r.compiled? }
161
- not_written, rest = *rest.partition { |r| r.compiled? && !r.written? }
162
- identical = rest
163
-
164
- # Print
165
- puts
166
- puts format(' %4d created', created.size)
167
- puts format(' %4d modified', modified.size)
168
- puts format(' %4d skipped', skipped.size)
169
- puts format(' %4d not written', not_written.size)
170
- puts format(' %4d identical', identical.size)
171
- end
172
-
173
179
  def print_profiling_feedback(reps)
174
180
  # Get max filter length
175
181
  max_filter_name_length = @filter_times.keys.map { |k| k.to_s.size }.max
@@ -211,51 +217,6 @@ module Nanoc3::CLI::Commands
211
217
  end
212
218
  end
213
219
 
214
- def rep_compilation_started(rep)
215
- # Profile compilation
216
- @rep_times ||= {}
217
- @rep_times[rep.raw_path] = Time.now
218
- end
219
-
220
- def rep_compilation_ended(rep)
221
- # Profile compilation
222
- @rep_times ||= {}
223
- @rep_times[rep.raw_path] = Time.now - @rep_times[rep.raw_path]
224
-
225
- # Skip if not outputted
226
- return unless rep.written?
227
-
228
- # Get action and level
229
- action = if rep.created?
230
- :create
231
- elsif rep.modified?
232
- :update
233
- elsif !rep.compiled?
234
- nil
235
- else
236
- :identical
237
- end
238
-
239
- # Log
240
- unless action.nil?
241
- duration = @rep_times[rep.raw_path]
242
- Nanoc3::CLI::Logger.instance.file(:high, action, rep.raw_path, duration)
243
- end
244
- end
245
-
246
- def rep_filtering_started(rep, filter_name)
247
- @times_stack.push(Time.now)
248
- end
249
-
250
- def rep_filtering_ended(rep, filter_name)
251
- # Get last time
252
- time_start = @times_stack.pop
253
-
254
- # Update times
255
- @filter_times[filter_name.to_sym] ||= []
256
- @filter_times[filter_name.to_sym] << Time.now - time_start
257
- end
258
-
259
220
  end
260
221
 
261
222
  end
@@ -57,6 +57,20 @@ data_sources:
57
57
  # The path where layouts should be mounted. The layouts root behaves the
58
58
  # same as the items root, but applies to layouts rather than items.
59
59
  layouts_root: #{Nanoc3::Site::DEFAULT_DATA_SOURCE_CONFIG[:layouts_root]}
60
+
61
+ # Configuration for the “watch” command, which watches a site for changes and
62
+ # recompiles if necessary.
63
+ watcher:
64
+ # A list of directories to watch for changes. When editing this, make sure
65
+ # that the “output/” and “tmp/” directories are _not_ included in this list,
66
+ # because recompiling the site will cause these directories to change, which
67
+ # will cause the site to be recompiled, which will cause these directories
68
+ # to change, which will cause the site to be recompiled again, and so on.
69
+ dirs_to_watch: [ 'content', 'layouts', 'lib' ]
70
+
71
+ # A list of single files to watch for changes. As mentioned above, don’t put
72
+ # any files from the “output/” or “tmp/” directories in here.
73
+ files_to_watch: [ 'config.yaml', 'Rules' ]
60
74
  EOS
61
75
 
62
76
  DEFAULT_RULES = <<EOS
@@ -156,20 +170,18 @@ a:hover {
156
170
  line-height: 20px;
157
171
  }
158
172
 
159
- #main ul, #main ol {
173
+ #main ul {
160
174
  margin: 20px;
161
175
  }
162
176
 
163
177
  #main li {
178
+ list-style-type: square;
179
+
164
180
  font-size: 15px;
165
181
 
166
182
  line-height: 20px;
167
183
  }
168
184
 
169
- #main ul li {
170
- list-style-type: square;
171
- }
172
-
173
185
  #sidebar {
174
186
  position: absolute;
175
187
 
@@ -43,8 +43,7 @@ module Nanoc3::CLI::Commands
43
43
  layouts = @base.site.layouts
44
44
 
45
45
  # Get dependency tracker
46
- # FIXME clean this up
47
- dependency_tracker = @base.site.compiler.send(:dependency_tracker)
46
+ dependency_tracker = @base.site.compiler.dependency_tracker
48
47
  dependency_tracker.load_graph
49
48
 
50
49
  # Print item dependencies
@@ -66,7 +65,13 @@ module Nanoc3::CLI::Commands
66
65
  items.sort_by { |i| i.identifier }.each do |item|
67
66
  item.reps.sort_by { |r| r.name.to_s }.each do |rep|
68
67
  puts "item #{item.identifier}, rep #{rep.name}:"
69
- puts " #{rep.raw_path || '(not written)'}"
68
+ if rep.raw_paths.empty?
69
+ puts " (not written)"
70
+ end
71
+ length = rep.raw_paths.keys.map { |s| s.to_s.length }.max
72
+ rep.raw_paths.each do |snapshot_name, raw_path|
73
+ puts " [ %-#{length}s ] %s" % [ snapshot_name, raw_path ]
74
+ end
70
75
  end
71
76
  puts
72
77
  end
@@ -91,7 +96,9 @@ module Nanoc3::CLI::Commands
91
96
  puts '=== Layouts'
92
97
  puts
93
98
  layouts.each do |layout|
94
- puts "layout #{layout.identifier}"
99
+ puts "layout #{layout.identifier}:"
100
+ puts " is #{layout.outdated? ? '' : 'not '}outdated"
101
+ puts
95
102
  end
96
103
  end
97
104
 
@@ -72,7 +72,6 @@ module Nanoc3::CLI::Commands
72
72
  use Rack::CommonLogger
73
73
  use Rack::ShowExceptions
74
74
  use Rack::Lint
75
- use Rack::Head
76
75
  use Adsf::Rack::IndexFileFinder, :root => site.config[:output_dir]
77
76
  run Rack::File.new(site.config[:output_dir])
78
77
  end.to_app
@@ -0,0 +1,148 @@
1
+ # encoding: utf-8
2
+
3
+ module Nanoc3::CLI::Commands
4
+
5
+ class Watch < Cri::Command
6
+
7
+ def name
8
+ 'watch'
9
+ end
10
+
11
+ def aliases
12
+ [ ]
13
+ end
14
+
15
+ def short_desc
16
+ 'start the watcher'
17
+ end
18
+
19
+ def long_desc
20
+ 'Start the watcher. When a change is detected, the site will be ' \
21
+ 'recompiled.'
22
+ end
23
+
24
+ def usage
25
+ "nanoc3 watch"
26
+ end
27
+
28
+ def option_definitions
29
+ []
30
+ end
31
+
32
+ def run(options, arguments)
33
+ require 'fssm'
34
+
35
+ @notifier = Notifier.new
36
+
37
+ # Define rebuilder
38
+ rebuilder = lambda do |base, relative|
39
+ # Determine filename
40
+ if base.nil? || relative.nil?
41
+ filename = nil
42
+ else
43
+ filename = ::Pathname.new(File.join([ base, relative ])).relative_path_from(::Pathname.new(Dir.getwd)).to_s
44
+ end
45
+
46
+ # Notify
47
+ if filename
48
+ print "Change detected to #{filename}; recompiling… ".make_compatible_with_env
49
+ else
50
+ print "Watcher started; compiling the entire site… ".make_compatible_with_env
51
+ end
52
+
53
+ # Recompile
54
+ start = Time.now
55
+ site = Nanoc3::Site.new('.')
56
+ site.load_data
57
+ begin
58
+ site.compiler.run
59
+
60
+ # TODO include icon (--image misc/success-icon.png)
61
+ @notifier.notify('Compilation complete')
62
+
63
+ puts "done in #{((Time.now - start)*10000).round.to_f / 10}ms"
64
+ rescue Exception => e
65
+ # TODO include icon (--image misc/error-icon.png)
66
+ @notifier.notify('Compilation failed')
67
+
68
+ puts
69
+ @base.print_error(e)
70
+ puts
71
+ end
72
+ end
73
+
74
+ # Rebuild once
75
+ rebuilder.call(nil, nil)
76
+
77
+ # Get directories to watch
78
+ watcher_config = @base.site.config[:watcher] || {}
79
+ dirs_to_watch = watcher_config[:dirs_to_watch] || %w( content layouts lib )
80
+ files_to_watch = watcher_config[:files_to_watch] || %w( config.yaml Rules )
81
+
82
+ # Watch
83
+ puts "Watching for changes…".make_compatible_with_env
84
+ watcher = lambda do
85
+ update(&rebuilder)
86
+ delete(&rebuilder)
87
+ create(&rebuilder)
88
+ end
89
+ monitor = FSSM::Monitor.new
90
+ dirs_to_watch.each { |dir| monitor.path(dir, '**/*', &watcher) }
91
+ files_to_watch.each { |filename| monitor.file(filename, &watcher) }
92
+ monitor.run
93
+ end
94
+
95
+ # Allows sending user notifications in a cross-platform way.
96
+ class Notifier
97
+
98
+ # A list of commandline tool names that can be used to send notifications
99
+ TOOLS = %w( growlnotify notify_send )
100
+
101
+ # Error that is raised when no notifier can be found.
102
+ class NoNotifierFound < ::StandardError
103
+
104
+ def initialize
105
+ super("Could not find a notifier that works on this system. I tried to find #{CrossPlatformNotifier::TOOLS.join(', ')} but found nothing.")
106
+ end
107
+
108
+ end
109
+
110
+ # Send a notification.
111
+ #
112
+ # @param [String] message The message to include in the notification
113
+ #
114
+ # @option params [Boolean] :raise (true) true if this method should
115
+ # raise an exception if no notifier can be found, false otherwise
116
+ def notify(message, params={})
117
+ params[:raise] = true if !params.has_key?(:raise)
118
+
119
+ if tool.nil?
120
+ if params[:raise]
121
+ raise NoNotifierFound
122
+ else
123
+ return
124
+ end
125
+ end
126
+
127
+ send(tool, message, params)
128
+ end
129
+
130
+ private
131
+
132
+ def tool
133
+ @tool ||= TOOLS.find { |t| !`which #{t}`.empty? }
134
+ end
135
+
136
+ def growlnotify(message, params={})
137
+ system('growlnotify', '-m', message)
138
+ end
139
+
140
+ def notify_send(message, params={})
141
+ system('notify_send', messsage)
142
+ end
143
+
144
+ end
145
+
146
+ end
147
+
148
+ end
@@ -13,3 +13,4 @@ require 'nanoc3/cli/commands/help'
13
13
  require 'nanoc3/cli/commands/info'
14
14
  require 'nanoc3/cli/commands/update'
15
15
  require 'nanoc3/cli/commands/view'
16
+ require 'nanoc3/cli/commands/watch'