nanoc 3.4.3 → 3.5.0b1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (85) hide show
  1. data/CONTRIBUTING.md +25 -0
  2. data/Gemfile +3 -1
  3. data/Gemfile.lock +22 -13
  4. data/NEWS.md +27 -0
  5. data/README.md +3 -1
  6. data/lib/nanoc.rb +2 -2
  7. data/lib/nanoc/base/compilation/compiler_dsl.rb +19 -0
  8. data/lib/nanoc/base/core_ext/array.rb +18 -8
  9. data/lib/nanoc/base/core_ext/hash.rb +18 -8
  10. data/lib/nanoc/base/core_ext/pathname.rb +2 -8
  11. data/lib/nanoc/base/plugin_registry.rb +57 -19
  12. data/lib/nanoc/base/result_data/item_rep.rb +3 -15
  13. data/lib/nanoc/base/source_data/item.rb +1 -1
  14. data/lib/nanoc/base/source_data/layout.rb +1 -1
  15. data/lib/nanoc/base/source_data/site.rb +15 -19
  16. data/lib/nanoc/cli.rb +28 -23
  17. data/lib/nanoc/cli/command_runner.rb +4 -0
  18. data/lib/nanoc/cli/commands/autocompile.rb +15 -6
  19. data/lib/nanoc/cli/commands/check.rb +47 -0
  20. data/lib/nanoc/cli/commands/compile.rb +271 -195
  21. data/lib/nanoc/cli/commands/create-site.rb +5 -5
  22. data/lib/nanoc/cli/commands/deploy.rb +16 -4
  23. data/lib/nanoc/cli/commands/prune.rb +3 -3
  24. data/lib/nanoc/cli/commands/show-data.rb +73 -58
  25. data/lib/nanoc/cli/commands/show-rules.rb +1 -1
  26. data/lib/nanoc/cli/commands/validate-css.rb +2 -3
  27. data/lib/nanoc/cli/commands/validate-html.rb +2 -3
  28. data/lib/nanoc/cli/commands/validate-links.rb +5 -11
  29. data/lib/nanoc/cli/commands/view.rb +1 -1
  30. data/lib/nanoc/cli/commands/watch.rb +38 -20
  31. data/lib/nanoc/cli/error_handler.rb +122 -122
  32. data/lib/nanoc/data_sources.rb +2 -0
  33. data/lib/nanoc/data_sources/filesystem_unified.rb +1 -1
  34. data/lib/nanoc/data_sources/filesystem_verbose.rb +1 -1
  35. data/lib/nanoc/data_sources/static.rb +60 -0
  36. data/lib/nanoc/extra.rb +2 -0
  37. data/lib/nanoc/extra/checking.rb +16 -0
  38. data/lib/nanoc/extra/checking/check.rb +33 -0
  39. data/lib/nanoc/extra/checking/checks.rb +19 -0
  40. data/lib/nanoc/extra/checking/checks/css.rb +23 -0
  41. data/lib/nanoc/extra/checking/checks/external_links.rb +149 -0
  42. data/lib/nanoc/extra/checking/checks/html.rb +24 -0
  43. data/lib/nanoc/extra/checking/checks/internal_links.rb +57 -0
  44. data/lib/nanoc/extra/checking/checks/stale.rb +23 -0
  45. data/lib/nanoc/extra/checking/dsl.rb +31 -0
  46. data/lib/nanoc/extra/checking/issue.rb +19 -0
  47. data/lib/nanoc/extra/checking/runner.rb +130 -0
  48. data/lib/nanoc/extra/link_collector.rb +57 -0
  49. data/lib/nanoc/extra/pruner.rb +1 -1
  50. data/lib/nanoc/extra/validators/links.rb +5 -262
  51. data/lib/nanoc/extra/validators/w3c.rb +8 -76
  52. data/lib/nanoc/filters/colorize_syntax.rb +1 -1
  53. data/lib/nanoc/filters/handlebars.rb +2 -2
  54. data/lib/nanoc/filters/less.rb +1 -1
  55. data/lib/nanoc/filters/redcarpet.rb +1 -1
  56. data/lib/nanoc/filters/rubypants.rb +1 -1
  57. data/lib/nanoc/filters/slim.rb +1 -1
  58. data/lib/nanoc/helpers/blogging.rb +163 -104
  59. data/lib/nanoc/helpers/xml_sitemap.rb +9 -3
  60. data/tasks/doc.rake +2 -1
  61. data/test/base/core_ext/array_spec.rb +4 -4
  62. data/test/base/core_ext/hash_spec.rb +7 -7
  63. data/test/base/core_ext/pathname_spec.rb +12 -2
  64. data/test/base/test_compiler_dsl.rb +24 -0
  65. data/test/base/test_item_rep.rb +58 -26
  66. data/test/cli/commands/test_watch.rb +0 -8
  67. data/test/data_sources/test_static.rb +66 -0
  68. data/test/extra/checking/checks/test_css.rb +40 -0
  69. data/test/extra/checking/checks/test_external_links.rb +76 -0
  70. data/test/extra/checking/checks/test_html.rb +40 -0
  71. data/test/extra/checking/checks/test_internal_links.rb +46 -0
  72. data/test/extra/checking/checks/test_stale.rb +49 -0
  73. data/test/extra/checking/test_check.rb +16 -0
  74. data/test/extra/checking/test_dsl.rb +20 -0
  75. data/test/extra/checking/test_runner.rb +15 -0
  76. data/test/extra/test_link_collector.rb +93 -0
  77. data/test/extra/validators/test_links.rb +0 -64
  78. data/test/extra/validators/test_w3c.rb +20 -26
  79. data/test/filters/test_colorize_syntax.rb +15 -0
  80. data/test/filters/test_less.rb +14 -0
  81. data/test/filters/test_pandoc.rb +5 -1
  82. data/test/helpers/test_blogging.rb +52 -8
  83. data/test/helpers/test_xml_sitemap.rb +68 -0
  84. data/test/test_gem.rb +1 -1
  85. metadata +31 -6
@@ -4,7 +4,7 @@ usage 'create-site [options] path'
4
4
  aliases :create_site, :cs
5
5
  summary 'create a site'
6
6
  description <<-EOS
7
- Create a new site at the given path. The site will use the filesystem_unified data source by default, but this can be changed using the --datasource commandline option.
7
+ Create a new site at the given path. The site will use the `filesystem_unified` data source by default, but this can be changed using the `--datasource` commandline option.
8
8
  EOS
9
9
 
10
10
  required :d, :datasource, 'specify the data source for the new site'
@@ -208,9 +208,9 @@ a:hover {
208
208
 
209
209
  #main p {
210
210
  margin: 20px 0;
211
-
211
+
212
212
  font-size: 15px;
213
-
213
+
214
214
  line-height: 20px;
215
215
  }
216
216
 
@@ -220,7 +220,7 @@ a:hover {
220
220
 
221
221
  #main li {
222
222
  font-size: 15px;
223
-
223
+
224
224
  line-height: 20px;
225
225
  }
226
226
 
@@ -273,7 +273,7 @@ EOS
273
273
  <head>
274
274
  <meta charset="utf-8">
275
275
  <title>A Brand New nanoc Site - <%= @item[:title] %></title>
276
- <link rel="stylesheet" type="text/css" href="/style.css" media="screen">
276
+ <link rel="stylesheet" href="/style.css">
277
277
 
278
278
  <!-- you don't need to keep this, but it's cool for stats! -->
279
279
  <meta name="generator" content="nanoc <%= Nanoc::VERSION %>">
@@ -9,15 +9,16 @@ deployer_names = deployers.keys.sort_by { |k| k.to_s }
9
9
  usage 'deploy [options]'
10
10
  summary 'deploy the compiled site'
11
11
  description <<-EOS
12
- Deploys the compiled site. The compiled site contents in the output directory will be uploaded to the destination, which is specified using the -t/--target option.
12
+ Deploys the compiled site. The compiled site contents in the output directory will be uploaded to the destination, which is specified using the `--target` option.
13
13
 
14
14
  Available deployers: #{deployer_names.join(', ')}
15
15
 
16
16
  EOS
17
17
 
18
- option :t, :target, 'specify the location to deploy to', :argument => :required
19
- flag :L, :list, 'list available locations to deploy to'
20
- option :n, :'dry-run', 'show what would be deployed'
18
+ option :t, :target, 'specify the location to deploy to (default: `default`)', :argument => :required
19
+ flag :C, :'no-check', 'do not run the issue checks marked for deployment'
20
+ flag :L, :list, 'list available locations to deploy to'
21
+ option :n, :'dry-run', 'show what would be deployed'
21
22
 
22
23
  module Nanoc::CLI::Commands
23
24
 
@@ -57,6 +58,17 @@ module Nanoc::CLI::Commands
57
58
  raise Nanoc::Errors::GenericTrivial, "The specified deploy target has an unrecognised kind “#{name}” (expected one of #{names.join(', ')})."
58
59
  end
59
60
 
61
+ # Check
62
+ unless options[:'no-check']
63
+ puts "Running issue checks…"
64
+ ok = Nanoc::Extra::Checking::Runner.new(site).run_for_deploy
65
+ if !ok
66
+ puts "Issues found, deploy aborted."
67
+ return
68
+ end
69
+ puts "No issues found. Deploying!"
70
+ end
71
+
60
72
  # Run
61
73
  deployer = deployer_class.new(
62
74
  site.config[:output_dir],
@@ -5,10 +5,10 @@ summary 'remove files not managed by nanoc from the output directory'
5
5
  description <<-EOS
6
6
  Find all files in the output directory that do not correspond to an item
7
7
  managed by nanoc and remove them. Since this is a hazardous operation, an
8
- additional --yes flag is needed as confirmation.
8
+ additional `--yes` flag is needed as confirmation.
9
9
 
10
- Also see the auto_prune site configuration option in config.yaml, which will
11
- automatically prune after compilation.
10
+ Also see the `auto_prune` site configuration option in `config.yamlz, which
11
+ will automatically prune after compilation.
12
12
  EOS
13
13
 
14
14
  flag :y, :yes, 'confirm deletion'
@@ -13,26 +13,59 @@ module Nanoc::CLI::Commands
13
13
  class ShowData < ::Nanoc::CLI::CommandRunner
14
14
 
15
15
  def run
16
- # Make sure we are in a nanoc site directory
17
- print "Loading site data... "
18
16
  self.require_site
19
- puts "done"
20
- puts
21
17
 
22
18
  # Get data
23
- items = self.site.items
24
- reps = items.map { |i| i.reps }.flatten
25
- layouts = self.site.layouts
19
+ items = self.site.items
20
+ item_reps = items.map { |i| i.reps }.flatten
21
+ layouts = self.site.layouts
26
22
 
27
23
  # Get dependency tracker
28
24
  compiler = self.site.compiler
29
25
  compiler.load
30
26
  dependency_tracker = compiler.dependency_tracker
31
27
 
32
- # Print item dependencies
33
- puts '=== Item dependencies ======================================================='
34
- puts
28
+ # Print data
29
+ self.print_item_dependencies(items, dependency_tracker)
30
+ self.print_item_rep_paths(items)
31
+ self.print_item_rep_outdatedness(items, compiler)
32
+ self.print_layouts(layouts, compiler)
33
+ end
34
+
35
+ protected
36
+
37
+ def sorted_with_prev(objects)
38
+ prev = nil
39
+ objects.sort_by { |o| o.identifier }.each do |object|
40
+ yield(object, prev)
41
+ prev = object
42
+ end
43
+ end
44
+
45
+ def sorted_reps_with_prev(items)
46
+ prev = nil
35
47
  items.sort_by { |i| i.identifier }.each do |item|
48
+ item.reps.sort_by { |r| r.name.to_s }.each do |rep|
49
+ yield(rep, prev)
50
+ prev = rep
51
+ end
52
+ end
53
+ end
54
+
55
+ def print_header(title)
56
+ header = '=' * 78
57
+ header[3..(title.length+5)] = " #{title} "
58
+
59
+ puts
60
+ puts header
61
+ puts
62
+ end
63
+
64
+ def print_item_dependencies(items, dependency_tracker)
65
+ self.print_header('Item dependencies')
66
+
67
+ self.sorted_with_prev(items) do |item, prev|
68
+ puts if prev
36
69
  puts "item #{item.identifier} depends on:"
37
70
  predecessors = dependency_tracker.objects_causing_outdatedness_of(item).sort_by { |i| i ? i.identifier : '' }
38
71
  predecessors.each do |pred|
@@ -43,63 +76,45 @@ module Nanoc::CLI::Commands
43
76
  end
44
77
  end
45
78
  puts " (nothing)" if predecessors.empty?
46
- puts
47
79
  end
80
+ end
48
81
 
49
- # Print representation paths
50
- puts '=== Representation paths ===================================================='
51
- puts
52
- items.sort_by { |i| i.identifier }.each do |item|
53
- item.reps.sort_by { |r| r.name.to_s }.each do |rep|
54
- puts "item #{item.identifier}, rep #{rep.name}:"
55
- if rep.raw_paths.empty?
56
- puts " (not written)"
57
- end
58
- length = rep.raw_paths.keys.map { |s| s.to_s.length }.max
59
- rep.raw_paths.each do |snapshot_name, raw_path|
60
- puts " [ %-#{length}s ] %s" % [ snapshot_name, raw_path ]
61
- end
62
- end
63
- puts
64
- end
82
+ def print_item_rep_paths(items)
83
+ self.print_header('Item representation paths')
65
84
 
66
- # Print representation outdatedness
67
- puts '=== Representation outdatedness ============================================='
68
- puts
69
- items.sort_by { |i| i.identifier }.each do |item|
70
- item.reps.sort_by { |r| r.name.to_s }.each do |rep|
71
- puts "item #{item.identifier}, rep #{rep.name}:"
72
- outdatedness_reason = compiler.outdatedness_checker.outdatedness_reason_for(rep)
73
- if outdatedness_reason
74
- puts " is outdated: #{outdatedness_reason.message}"
75
- else
76
- puts " is not outdated"
77
- end
85
+ self.sorted_reps_with_prev(items) do |rep, prev|
86
+ puts if prev
87
+ puts "item #{rep.item.identifier}, rep #{rep.name}:"
88
+ if rep.raw_paths.empty?
89
+ puts " (not written)"
90
+ end
91
+ length = rep.raw_paths.keys.map { |s| s.to_s.length }.max
92
+ rep.raw_paths.each do |snapshot_name, raw_path|
93
+ puts " [ %-#{length}s ] %s" % [ snapshot_name, raw_path ]
78
94
  end
79
- puts
80
95
  end
96
+ end
81
97
 
82
- # Print layout dependencies
83
- puts '=== Layout dependencies ====================================================='
84
- puts
85
- layouts.sort_by { |l| l.identifier }.each do |layout|
86
- puts "layout #{layout.identifier} depends on:"
87
- predecessors = dependency_tracker.objects_causing_outdatedness_of(layout).sort_by { |i| i ? i.identifier : '' }
88
- predecessors.each do |pred|
89
- if pred
90
- puts " [ #{format '%6s', pred.type} ] #{pred.identifier}"
91
- else
92
- puts " ( removed item )"
93
- end
98
+ def print_item_rep_outdatedness(items, compiler)
99
+ self.print_header('Item representation outdatedness')
100
+
101
+ self.sorted_reps_with_prev(items) do |rep, prev|
102
+ puts if prev
103
+ puts "item #{rep.item.identifier}, rep #{rep.name}:"
104
+ outdatedness_reason = compiler.outdatedness_checker.outdatedness_reason_for(rep)
105
+ if outdatedness_reason
106
+ puts " is outdated: #{outdatedness_reason.message}"
107
+ else
108
+ puts " is not outdated"
94
109
  end
95
- puts " (nothing)" if predecessors.empty?
96
- puts
97
110
  end
111
+ end
98
112
 
99
- # Print layouts
100
- puts '=== Layouts'
101
- puts
102
- layouts.sort_by { |l| l.identifier }.each do |layout|
113
+ def print_layouts(layouts, compiler)
114
+ self.print_header('Layouts')
115
+
116
+ self.sorted_with_prev(layouts) do |layout, prev|
117
+ puts if prev
103
118
  puts "layout #{layout.identifier}:"
104
119
  outdatedness_reason = compiler.outdatedness_checker.outdatedness_reason_for(layout)
105
120
  if outdatedness_reason
@@ -4,7 +4,7 @@ usage 'show-rules [thing]'
4
4
  aliases :explain
5
5
  summary 'describe the rules for each item'
6
6
  description <<-EOS
7
- Prints the rules used for all items and layouts in the current site. An argument can be given, which can be either an item identifier (e.g. /foo/), the path to the source file (e.g. content/foo.html) or the path to the output file (e.g. output/foo.html).
7
+ Prints the rules used for all items and layouts in the current site.
8
8
  EOS
9
9
 
10
10
  module Nanoc::CLI::Commands
@@ -3,6 +3,7 @@
3
3
  usage 'validate-css [options]'
4
4
  aliases :validate_css, :vcss
5
5
  summary 'validate the site’s CSS'
6
+ be_hidden
6
7
  description <<-EOS
7
8
  Validates the site’s CSS files.
8
9
  EOS
@@ -12,9 +13,7 @@ module Nanoc::CLI::Commands
12
13
  class ValidateCSS < ::Nanoc::CLI::CommandRunner
13
14
 
14
15
  def run
15
- require_site
16
- validator = ::Nanoc::Extra::Validators::W3C.new(site.config[:output_dir], [ :css ])
17
- validator.run
16
+ Nanoc::CLI.run %w( check css )
18
17
  end
19
18
 
20
19
  end
@@ -3,6 +3,7 @@
3
3
  usage 'validate-html [options]'
4
4
  aliases :validate_html, :vhtml
5
5
  summary 'validate the site’s HTML'
6
+ be_hidden
6
7
  description <<-EOS
7
8
  Validates the site’s HTML files.
8
9
  EOS
@@ -12,9 +13,7 @@ module Nanoc::CLI::Commands
12
13
  class ValidateHTML < ::Nanoc::CLI::CommandRunner
13
14
 
14
15
  def run
15
- require_site
16
- validator = ::Nanoc::Extra::Validators::W3C.new(site.config[:output_dir], [ :html ])
17
- validator.run
16
+ Nanoc::CLI.run %w( check html )
18
17
  end
19
18
 
20
19
  end
@@ -3,6 +3,7 @@
3
3
  usage 'validate-links [options]'
4
4
  aliases :validate_links, :vlink
5
5
  summary 'validate links in site'
6
+ be_hidden
6
7
  description <<-EOS
7
8
  Validates the site’s links. By default, both internal and external links will be checked.
8
9
  EOS
@@ -15,17 +16,10 @@ module Nanoc::CLI::Commands
15
16
  class ValidateLinks < ::Nanoc::CLI::CommandRunner
16
17
 
17
18
  def run
18
- require_site
19
-
20
- dir = site.config[:output_dir]
21
- index_filenames = site.config[:index_filenames]
22
-
23
- validator = ::Nanoc::Extra::Validators::Links.new(
24
- dir,
25
- index_filenames,
26
- :internal => (options[:external] ? false : true),
27
- :external => (options[:internal] ? false : true))
28
- validator.run
19
+ checks = []
20
+ checks << 'ilinks' if options[:internal]
21
+ checks << 'elinks' if options[:external]
22
+ Nanoc::CLI.run [ 'check', checks ].flatten
29
23
  end
30
24
 
31
25
  end
@@ -3,7 +3,7 @@
3
3
  usage 'view [options]'
4
4
  summary 'start the web server that serves static files'
5
5
  description <<-EOS
6
- Start the static web server. Unless specified, the web server will run on port 3000 and listen on all IP addresses. Running this static web server requires 'adsf' (not 'asdf'!).
6
+ Start the static web server. Unless specified, the web server will run on port 3000 and listen on all IP addresses. Running this static web server requires `adsf` (not `asdf`!).
7
7
  EOS
8
8
 
9
9
  required :H, :handler, 'specify the handler to use (webrick/mongrel/...)'
@@ -11,7 +11,7 @@ module Nanoc::CLI::Commands
11
11
  class Watch < ::Nanoc::CLI::CommandRunner
12
12
 
13
13
  def run
14
- require 'fssm'
14
+ require 'listen'
15
15
  require 'pathname'
16
16
 
17
17
  require_site
@@ -20,12 +20,12 @@ module Nanoc::CLI::Commands
20
20
  @notifier = Notifier.new
21
21
 
22
22
  # Define rebuilder
23
- rebuilder = lambda do |base, relative|
23
+ rebuilder = lambda do |file_path|
24
24
  # Determine filename
25
- if base.nil? || relative.nil?
25
+ if file_path.nil?
26
26
  filename = nil
27
27
  else
28
- filename = ::Pathname.new(File.join([ base, relative ])).relative_path_from(::Pathname.new(Dir.getwd)).to_s
28
+ filename = ::Pathname.new(file_path).relative_path_from(::Pathname.new(Dir.getwd)).to_s
29
29
  end
30
30
 
31
31
  # Notify
@@ -63,24 +63,33 @@ module Nanoc::CLI::Commands
63
63
  end
64
64
 
65
65
  # Rebuild once
66
- rebuilder.call(nil, nil)
66
+ rebuilder.call(nil)
67
67
 
68
68
  # Get directories to watch
69
- dirs_to_watch = watcher_config[:dirs_to_watch] || %w( content layouts lib )
70
- files_to_watch = watcher_config[:files_to_watch] || %w( config.yaml Rules rules Rules.rb rules.rb' )
71
- files_to_watch.delete_if { |f| !File.file?(f) }
69
+ dirs_to_watch = watcher_config[:dirs_to_watch] || ['content', 'layouts', 'lib']
70
+ files_to_watch = watcher_config[:files_to_watch] || ['config.yaml', 'Rules', 'rules', 'Rules.rb', 'rules.rb']
71
+ files_to_watch = Regexp.new(files_to_watch.map { |name| "#{Regexp.quote(name)}$"}.join("|"))
72
+ ignore_dir = Regexp.new(Dir.glob("*").map{|dir| dir if File::ftype(dir) == "directory" }.compact.join("|"))
72
73
 
73
74
  # Watch
74
75
  puts "Watching for changes…"
75
- watcher = lambda do |*args|
76
- update(&rebuilder)
77
- delete(&rebuilder)
78
- create(&rebuilder)
79
- end
80
- monitor = FSSM::Monitor.new
81
- dirs_to_watch.each { |dir| monitor.path(dir, '**/*', &watcher) }
82
- files_to_watch.each { |filename| monitor.file(filename, &watcher) }
83
- monitor.run
76
+
77
+ callback = Proc.new do |modified, added, removed|
78
+ rebuilder.call(modified[0]) if modified[0]
79
+ rebuilder.call(added[0]) if added[0]
80
+ rebuilder.call(removed[0]) if removed[0]
81
+ end
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)
85
+
86
+ begin
87
+ listener_root.start(false)
88
+ listener.start
89
+ rescue Interrupt
90
+ listener.stop
91
+ listener_root.stop
92
+ end
84
93
  end
85
94
 
86
95
  # Allows sending user notifications in a cross-platform way.
@@ -105,12 +114,21 @@ module Nanoc::CLI::Commands
105
114
 
106
115
  def tool
107
116
  @tool ||= begin
108
- TOOLS.find { |t| !`#{FIND_BINARY_COMMAND} #{t}`.empty? }
109
- rescue Errno::ENOENT
110
- nil
117
+ require 'terminal-notifier'
118
+ 'terminal-notify'
119
+ rescue LoadError
120
+ begin
121
+ TOOLS.find { |t| !`#{FIND_BINARY_COMMAND} #{t}`.empty? }
122
+ rescue Errno::ENOENT
123
+ nil
124
+ end
111
125
  end
112
126
  end
113
127
 
128
+ def terminal_notify(message)
129
+ TerminalNotifier.notify(message, :title => "nanoc")
130
+ end
131
+
114
132
  def growlnotify(message)
115
133
  system('growlnotify', '-m', message)
116
134
  end
@@ -58,6 +58,10 @@ module Nanoc::CLI
58
58
  exit!(0)
59
59
  end
60
60
  end
61
+ Signal.trap('USR1') do
62
+ puts "Caught USR1; dumping a stack trace"
63
+ puts caller.map { |i| " #{i}" }.join("\n")
64
+ end
61
65
 
62
66
  # Run
63
67
  yield
@@ -89,7 +93,12 @@ module Nanoc::CLI
89
93
  # @return [void]
90
94
  def print_error(error)
91
95
  write_compact_error(error, $stderr)
92
- File.open('crash.log', 'w') { |io| write_verbose_error(error, io) }
96
+
97
+ File.open('crash.log', 'w') do |io|
98
+ cio = Nanoc::CLI.wrap_in_cleaning_stream(io)
99
+ cio.add_stream_cleaner(::Nanoc::CLI::StreamCleaners::ANSIColors)
100
+ write_verbose_error(error, cio)
101
+ end
93
102
  end
94
103
 
95
104
  # Writes a compact representation of the error, suitable for a terminal, on
@@ -107,48 +116,13 @@ module Nanoc::CLI
107
116
  stream.puts
108
117
  stream.puts "Captain! We’ve been hit!"
109
118
 
110
- # Exception and resolution (if any)
111
- stream.puts
112
- stream.puts format_title('Message:')
113
- stream.puts
114
- stream.puts "#{error.class}: #{error.message}"
115
- resolution = self.resolution_for(error)
116
- stream.puts "#{resolution}" if resolution
117
-
118
- # Compilation stack
119
- stream.puts
120
- stream.puts format_title('Compilation stack:')
121
- stream.puts
122
- if self.stack.empty?
123
- stream.puts " (empty)"
124
- else
125
- self.stack.reverse.each do |obj|
126
- if obj.is_a?(Nanoc::ItemRep)
127
- stream.puts " - [item] #{obj.item.identifier} (rep #{obj.name})"
128
- else # layout
129
- stream.puts " - [layout] #{obj.identifier}"
130
- end
131
- end
132
- end
133
-
134
- # Backtrace
135
- stream.puts
136
- stream.puts format_title('Stack trace:')
137
- stream.puts
138
- count = 10
139
- error.backtrace[0...count].each_with_index do |item, index|
140
- stream.puts " #{index}. #{item}"
141
- end
142
- if error.backtrace.size > count
143
- puts " ... #{error.backtrace.size - count} more lines omitted. See full crash log for details."
144
- end
119
+ # Sections
120
+ self.write_error_message( stream, error)
121
+ self.write_compilation_stack(stream, error)
122
+ self.write_stack_trace( stream, error)
145
123
 
146
124
  # Issue link
147
- stream.puts
148
- stream.puts "If you believe this is a bug in nanoc, please do report it at"
149
- stream.puts "-> https://github.com/ddfreyne/nanoc/issues/new <-"
150
- stream.puts
151
- stream.puts "A detailed crash log has been written to ./crash.log."
125
+ self.write_issue_link(stream)
152
126
  end
153
127
 
154
128
  # Writes a verbose representation of the error on the given stream.
@@ -161,86 +135,19 @@ module Nanoc::CLI
161
135
  #
162
136
  # @return [void]
163
137
  def write_verbose_error(error, stream)
164
- # Date and time
138
+ # Header
165
139
  stream.puts "Crashlog created at #{Time.now}"
166
- stream.puts
167
-
168
- # Exception and resolution (if any)
169
- stream.puts '=== MESSAGE:'
170
- stream.puts
171
- stream.puts "#{error.class}: #{error.message}"
172
- resolution = self.resolution_for(error)
173
- stream.puts "#{resolution}" if resolution
174
- stream.puts
175
-
176
- # Compilation stack
177
- stream.puts '=== COMPILATION STACK:'
178
- stream.puts
179
- if self.stack.empty?
180
- stream.puts " (empty)"
181
- else
182
- self.stack.reverse.each do |obj|
183
- if obj.is_a?(Nanoc::ItemRep)
184
- stream.puts " - [item] #{obj.item.identifier} (rep #{obj.name})"
185
- else # layout
186
- stream.puts " - [layout] #{obj.identifier}"
187
- end
188
- end
189
- end
190
- stream.puts
191
-
192
- # Backtrace
193
- stream.puts '=== BACKTRACE:'
194
- stream.puts
195
- stream.puts error.backtrace.to_enum(:each_with_index).map { |item, index| " #{index}. #{item}" }.join("\n")
196
- stream.puts
197
140
 
198
- # Version information
199
- stream.puts '=== VERSION INFORMATION:'
200
- stream.puts
201
- stream.puts Nanoc.version_information
202
- stream.puts
203
-
204
- # System information
205
- begin
206
- uname = `uname -a`
207
- stream.puts '=== SYSTEM INFORMATION:'
208
- stream.puts
209
- stream.puts uname
210
- stream.puts
211
- rescue Errno::ENOENT
212
- end
213
-
214
- # Installed gems
215
- stream.puts '=== INSTALLED GEMS:'
216
- stream.puts
217
- self.gems_and_versions.each do |g|
218
- stream.puts " #{g.first} #{g.last.join(', ')}"
219
- end
220
- stream.puts
221
-
222
- # Environment
223
- stream.puts '=== ENVIRONMENT:'
224
- stream.puts
225
- ENV.sort.each do |e|
226
- stream.puts "#{e.first} => #{e.last.inspect}"
227
- end
228
- stream.puts
229
-
230
- # Gemfile
231
- if File.exist?('Gemfile.lock')
232
- stream.puts '=== GEMFILE.LOCK:'
233
- stream.puts
234
- stream.puts File.read('Gemfile.lock')
235
- stream.puts
236
- end
237
-
238
- # Load paths
239
- stream.puts '=== $LOAD_PATH:'
240
- stream.puts
241
- $LOAD_PATH.each_with_index do |i, index|
242
- stream.puts " #{index}. #{i}"
243
- end
141
+ # Sections
142
+ self.write_error_message( stream, error, :verbose => true)
143
+ self.write_compilation_stack( stream, error, :verbose => true)
144
+ self.write_stack_trace( stream, error, :verbose => true)
145
+ self.write_version_information(stream, :verbose => true)
146
+ self.write_system_information( stream, :verbose => true)
147
+ self.write_installed_gems( stream, :verbose => true)
148
+ self.write_environment( stream, :verbose => true)
149
+ self.write_gemfile_lock( stream, :verbose => true)
150
+ self.write_load_paths( stream, :verbose => true)
244
151
  end
245
152
 
246
153
  protected
@@ -267,6 +174,7 @@ module Nanoc::CLI
267
174
  (compiler && compiler.stack) || []
268
175
  end
269
176
 
177
+ # @return [Hash<String, Array>] A hash containing the gem names as keys and gem versions as value
270
178
  def gems_and_versions
271
179
  gems = {}
272
180
  Gem::Specification.find_all.sort_by { |s| [ s.name, s.version ] }.each do |spec|
@@ -287,12 +195,12 @@ module Nanoc::CLI
287
195
  'erubis' => 'erubis',
288
196
  'escape' => 'escape',
289
197
  'fog' => 'fog',
290
- 'fssm' => 'fssm',
291
198
  'haml' => 'haml',
292
199
  'handlebars' => 'hbs',
293
200
  'json' => 'json',
294
201
  'kramdown' => 'kramdown',
295
202
  'less' => 'less',
203
+ 'listen' => 'listen',
296
204
  'markaby' => 'markaby',
297
205
  'maruku' => 'maruku',
298
206
  'mime/types' => 'mime-types',
@@ -338,8 +246,100 @@ module Nanoc::CLI
338
246
  end
339
247
  end
340
248
 
341
- def format_title(s)
342
- "\e[1m\e[31m" + s + "\e[0m"
249
+ def write_section_header(stream, title, params={})
250
+ stream.puts
251
+ if params[:verbose]
252
+ stream.puts '===== ' + title.upcase + ':'
253
+ else
254
+ stream.puts "\e[1m\e[31m" + title + ':' + "\e[0m"
255
+ end
256
+ stream.puts
257
+ end
258
+
259
+ def write_error_message(stream, error, params={})
260
+ self.write_section_header(stream, 'Message', params)
261
+
262
+ stream.puts "#{error.class}: #{error.message}"
263
+ resolution = self.resolution_for(error)
264
+ stream.puts "#{resolution}" if resolution
265
+ end
266
+
267
+ def write_compilation_stack(stream, error, params={})
268
+ self.write_section_header(stream, 'Compilation stack', params)
269
+
270
+ if self.stack.empty?
271
+ stream.puts " (empty)"
272
+ else
273
+ self.stack.reverse.each do |obj|
274
+ if obj.is_a?(Nanoc::ItemRep)
275
+ stream.puts " - [item] #{obj.item.identifier} (rep #{obj.name})"
276
+ else # layout
277
+ stream.puts " - [layout] #{obj.identifier}"
278
+ end
279
+ end
280
+ end
281
+ end
282
+
283
+ def write_stack_trace(stream, error, params={})
284
+ self.write_section_header(stream, 'Stack trace', params)
285
+
286
+ count = params[:verbose] ? -1 : 10
287
+ error.backtrace[0...count].each_with_index do |item, index|
288
+ stream.puts " #{index}. #{item}"
289
+ end
290
+ if error.backtrace.size > count
291
+ stream.puts " ... #{error.backtrace.size - count} more lines omitted. See full crash log for details."
292
+ end
293
+ end
294
+
295
+ def write_issue_link(stream, params={})
296
+ stream.puts
297
+ stream.puts "If you believe this is a bug in nanoc, please do report it at"
298
+ stream.puts "-> https://github.com/ddfreyne/nanoc/issues/new <-"
299
+ stream.puts
300
+ stream.puts "A detailed crash log has been written to ./crash.log."
301
+ end
302
+
303
+ def write_version_information(stream, params={})
304
+ self.write_section_header(stream, 'Version information', params)
305
+ stream.puts Nanoc.version_information
306
+ end
307
+
308
+ def write_system_information(stream, params={})
309
+ begin
310
+ uname = `uname -a`
311
+ self.write_section_header(stream, 'System information', params)
312
+ stream.puts uname
313
+ rescue Errno::ENOENT
314
+ end
315
+ end
316
+
317
+ def write_installed_gems(stream, params={})
318
+ self.write_section_header(stream, 'Installed gems', params)
319
+ self.gems_and_versions.each do |g|
320
+ stream.puts " #{g.first} #{g.last.join(', ')}"
321
+ end
322
+ end
323
+
324
+ def write_environment(stream, params={})
325
+ self.write_section_header(stream, 'Environment', params)
326
+ ENV.sort.each do |e|
327
+ stream.puts "#{e.first} => #{e.last.inspect}"
328
+ end
329
+ end
330
+
331
+ def write_gemfile_lock(stream, params={})
332
+ if File.exist?('Gemfile.lock')
333
+ self.write_section_header(stream, 'Gemfile.lock', params)
334
+ stream.puts File.read('Gemfile.lock')
335
+ end
336
+ end
337
+
338
+ def write_load_paths(stream, params={})
339
+ self.write_section_header(stream, 'Load paths', params)
340
+ $LOAD_PATH.each_with_index do |i, index|
341
+ stream.puts " #{index}. #{i}"
342
+ end
343
343
  end
344
344
 
345
345
  end