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.
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
@@ -5,9 +5,11 @@ module Nanoc::DataSources
5
5
  autoload 'Filesystem', 'nanoc/data_sources/filesystem'
6
6
  autoload 'FilesystemUnified', 'nanoc/data_sources/filesystem_unified'
7
7
  autoload 'FilesystemVerbose', 'nanoc/data_sources/filesystem_verbose'
8
+ autoload 'Static', 'nanoc/data_sources/static'
8
9
 
9
10
  Nanoc::DataSource.register '::Nanoc::DataSources::FilesystemVerbose', :filesystem_verbose
10
11
  Nanoc::DataSource.register '::Nanoc::DataSources::FilesystemUnified', :filesystem_unified
12
+ Nanoc::DataSource.register '::Nanoc::DataSources::Static', :static
11
13
 
12
14
  # Deprecated; fetch data from online data sources manually instead
13
15
  # TODO [in nanoc 4.0] remove me
@@ -85,7 +85,7 @@ module Nanoc::DataSources
85
85
  # Write item
86
86
  FileUtils.mkdir_p(parent_path)
87
87
  File.open(path, 'w') do |io|
88
- meta = attributes.stringify_keys
88
+ meta = attributes.stringify_keys_recursively
89
89
  unless meta == {}
90
90
  io.write(YAML.dump(meta).strip + "\n")
91
91
  io.write("---\n\n")
@@ -63,7 +63,7 @@ module Nanoc::DataSources
63
63
 
64
64
  # Create files
65
65
  FileUtils.mkdir_p(dir_path)
66
- File.open(meta_filename, 'w') { |io| io.write(YAML.dump(attributes.stringify_keys)) }
66
+ File.open(meta_filename, 'w') { |io| io.write(YAML.dump(attributes.stringify_keys_recursively)) }
67
67
  File.open(content_filename, 'w') { |io| io.write(content) }
68
68
  end
69
69
 
@@ -0,0 +1,60 @@
1
+ # encoding: utf-8
2
+
3
+ module Nanoc::DataSources
4
+
5
+ # The static data source provides items from a single directory. Unlike the
6
+ # filesystem data sources, static provides no additional item metadata. In
7
+ # addition, all items are treated as 'binary', regardless of their extension
8
+ # or content. As such, it is most useful for simple assets, not for normal
9
+ # content.
10
+ #
11
+ # The identifier for static items is the full item path. For example, if your
12
+ # static data source item_root is `static`, an item named `foo.css` would have
13
+ # the identifier `/static/foo.css/`. Note that, unlike the filesystem data
14
+ # sources, `foo/index.html` and `foo.yaml` receive no special treatment. They
15
+ # are simple static items, just like `foo.css`.
16
+ #
17
+ # The default data source directory is `static/`, but this can be overridden
18
+ # in the data source configuration:
19
+ #
20
+ # data_sources:
21
+ # - type: static
22
+ # prefix: assets
23
+ #
24
+ # Unless the `hide_items` configuration attribute is false, items from static
25
+ # data sources will have the :is_hidden attribute set by default, which will
26
+ # exclude them from the Blogging helper's atom feed generator, among other
27
+ # things.
28
+ class Static < Nanoc::DataSource
29
+ identifier :static
30
+
31
+ def items
32
+ # Get prefix
33
+ prefix = config[:prefix] || 'static'
34
+
35
+ # Get all files under prefix dir
36
+ filenames = Dir[prefix + '/**/*'].select { |f| File.file?(f) }
37
+
38
+ # Convert filenames to items
39
+ filenames.map do |filename|
40
+ attributes = {
41
+ :extension => File.extname(filename)[1..-1],
42
+ :filename => filename,
43
+ }
44
+ attributes[:is_hidden] = true unless config[:hide_items] == false
45
+ identifier = filename[(prefix.length+1)..-1] + '/'
46
+
47
+ mtime = File.mtime(filename)
48
+ checksum = Pathname.new(filename).checksum
49
+
50
+ Nanoc::Item.new(
51
+ filename,
52
+ attributes,
53
+ identifier,
54
+ :binary => true, :mtime => mtime, :checksum => checksum
55
+ )
56
+ end
57
+ end
58
+
59
+ end
60
+ end
@@ -3,7 +3,9 @@
3
3
  module Nanoc::Extra
4
4
 
5
5
  autoload 'AutoCompiler', 'nanoc/extra/auto_compiler'
6
+ autoload 'Checking', 'nanoc/extra/checking'
6
7
  autoload 'CHiCk', 'nanoc/extra/chick'
8
+ autoload 'LinkCollector', 'nanoc/extra/link_collector.rb'
7
9
  autoload 'Pruner', 'nanoc/extra/pruner'
8
10
  autoload 'Validators', 'nanoc/extra/validators'
9
11
 
@@ -0,0 +1,16 @@
1
+ # encoding: utf-8
2
+
3
+ module Nanoc::Extra
4
+
5
+ module Checking
6
+
7
+ autoload 'Check', 'nanoc/extra/checking/check'
8
+ autoload 'DSL', 'nanoc/extra/checking/dsl'
9
+ autoload 'Runner', 'nanoc/extra/checking/runner.rb'
10
+ autoload 'Issue', 'nanoc/extra/checking/issue'
11
+
12
+ end
13
+
14
+ end
15
+
16
+ require 'nanoc/extra/checking/checks'
@@ -0,0 +1,33 @@
1
+ # encoding: utf-8
2
+
3
+ module Nanoc::Extra::Checking
4
+
5
+ class Check
6
+
7
+ extend Nanoc::PluginRegistry::PluginMethods
8
+
9
+ attr_reader :site
10
+ attr_reader :issues
11
+
12
+ def initialize(site)
13
+ @site = site
14
+ @issues = Set.new
15
+ end
16
+
17
+ def run
18
+ raise NotImplementedError.new("Nanoc::Extra::Checking::Check subclasses must implement #run")
19
+ end
20
+
21
+ def add_issue(desc, params={})
22
+ subject = params.fetch(:subject, nil)
23
+
24
+ @issues << Issue.new(desc, subject, self.class)
25
+ end
26
+
27
+ def output_filenames
28
+ Dir[@site.config[:output_dir] + '/**/*'].select{ |f| File.file?(f) }
29
+ end
30
+
31
+ end
32
+
33
+ end
@@ -0,0 +1,19 @@
1
+ # encoding: utf-8
2
+
3
+ module Nanoc::Extra::Checking::Checks
4
+
5
+ autoload 'CSS', 'nanoc/extra/checking/checks/css'
6
+ autoload 'ExternalLinks', 'nanoc/extra/checking/checks/external_links'
7
+ autoload 'HTML', 'nanoc/extra/checking/checks/html'
8
+ autoload 'InternalLinks', 'nanoc/extra/checking/checks/internal_links'
9
+ autoload 'Stale', 'nanoc/extra/checking/checks/stale'
10
+
11
+ Nanoc::Extra::Checking::Check.register '::Nanoc::Extra::Checking::Checks::CSS', :css
12
+ Nanoc::Extra::Checking::Check.register '::Nanoc::Extra::Checking::Checks::ExternalLinks', :external_links
13
+ Nanoc::Extra::Checking::Check.register '::Nanoc::Extra::Checking::Checks::ExternalLinks', :elinks
14
+ Nanoc::Extra::Checking::Check.register '::Nanoc::Extra::Checking::Checks::HTML', :html
15
+ Nanoc::Extra::Checking::Check.register '::Nanoc::Extra::Checking::Checks::InternalLinks', :internal_links
16
+ Nanoc::Extra::Checking::Check.register '::Nanoc::Extra::Checking::Checks::InternalLinks', :ilinks
17
+ Nanoc::Extra::Checking::Check.register '::Nanoc::Extra::Checking::Checks::Stale', :stale
18
+
19
+ end
@@ -0,0 +1,23 @@
1
+ # encoding: utf-8
2
+
3
+ module ::Nanoc::Extra::Checking::Checks
4
+
5
+ class CSS < ::Nanoc::Extra::Checking::Check
6
+
7
+ identifier :css
8
+
9
+ def run
10
+ require 'w3c_validators'
11
+
12
+ Dir[site.config[:output_dir] + '/**/*.css'].each do |filename|
13
+ results = ::W3CValidators::CSSValidator.new.validate_file(filename)
14
+ results.errors.each do |e|
15
+ desc = e.message.gsub(%r{\s+}, ' ').strip
16
+ self.add_issue(desc, :subject => filename)
17
+ end
18
+ end
19
+ end
20
+
21
+ end
22
+
23
+ end
@@ -0,0 +1,149 @@
1
+ # encoding: utf-8
2
+
3
+ require 'net/http'
4
+ require 'net/https'
5
+ require 'nokogiri'
6
+ require 'timeout'
7
+ require 'uri'
8
+
9
+ module ::Nanoc::Extra::Checking::Checks
10
+
11
+ # A validator that verifies that all external links point to a location that exists.
12
+ class ExternalLinks < ::Nanoc::Extra::Checking::Check
13
+
14
+ identifiers :external_links, :elinks
15
+
16
+ def run
17
+ # Find all broken external hrefs
18
+ # TODO de-duplicate this (duplicated in internal links check)
19
+ filenames = self.output_filenames.select { |f| File.extname(f) == '.html' }
20
+ hrefs_with_filenames = ::Nanoc::Extra::LinkCollector.new(filenames, :external).filenames_per_href
21
+ results = self.select_invalid(hrefs_with_filenames.keys)
22
+
23
+ # Report them
24
+ results.each do |res|
25
+ filenames = hrefs_with_filenames[res.href]
26
+ filenames.each do |filename|
27
+ self.add_issue(
28
+ "reference to #{res.href}: #{res.explanation}",
29
+ :subject => filename)
30
+ end
31
+ end
32
+ end
33
+
34
+ class Result
35
+
36
+ attr_reader :href
37
+ attr_reader :explanation
38
+
39
+ def initialize(href, explanation)
40
+ @href = href
41
+ @explanation = explanation
42
+ end
43
+
44
+ end
45
+
46
+ class ArrayEnumerator
47
+
48
+ def initialize(array)
49
+ @array = array
50
+ @index = 0
51
+ @mutex = Mutex.new
52
+ end
53
+
54
+ def next
55
+ @mutex.synchronize do
56
+ @index += 1
57
+ return @array[@index-1]
58
+ end
59
+ end
60
+
61
+ end
62
+
63
+ def select_invalid(hrefs)
64
+ enum = ArrayEnumerator.new(hrefs.sort)
65
+ mutex = Mutex.new
66
+ invalid = Set.new
67
+
68
+ threads = []
69
+ 10.times do
70
+ threads << Thread.new do
71
+ loop do
72
+ href = enum.next
73
+ break if href.nil?
74
+ res = self.validate(href)
75
+ if res
76
+ mutex.synchronize do
77
+ invalid << res
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
83
+ threads.each { |t| t.join }
84
+
85
+ invalid
86
+ end
87
+
88
+ def validate(href)
89
+ # Parse
90
+ url = nil
91
+ begin
92
+ url = URI.parse(href)
93
+ rescue URI::InvalidURIError
94
+ return Result.new(href, 'invalid URI')
95
+ end
96
+
97
+ # Skip non-HTTP URLs
98
+ return nil if url.scheme !~ /^https?$/
99
+
100
+ # Get status
101
+ res = nil
102
+ 5.times do |i|
103
+ begin
104
+ Timeout::timeout(10) do
105
+ res = request_url_once(url)
106
+ end
107
+ rescue => e
108
+ return Result.new(href, e.message)
109
+ end
110
+
111
+ if res.code =~ /^3..$/
112
+ if i == 4
113
+ return Result.new(href, 'too many redirects')
114
+ end
115
+
116
+ # Find proper location
117
+ location = res['Location']
118
+ if location !~ /^https?:\/\//
119
+ base_url = url.dup
120
+ base_url.path = (location =~ /^\// ? '' : '/')
121
+ base_url.query = nil
122
+ base_url.fragment = nil
123
+ location = base_url.to_s + location
124
+ end
125
+
126
+ url = URI.parse(location)
127
+ elsif res.code == '200'
128
+ return nil
129
+ else
130
+ return Result.new(href, res.code)
131
+ end
132
+ end
133
+ raise 'should not have gotten here'
134
+ end
135
+
136
+ def request_url_once(url)
137
+ path = (url.path.nil? || url.path.empty? ? '/' : url.path)
138
+ req = Net::HTTP::Head.new(path)
139
+ http = Net::HTTP.new(url.host, url.port)
140
+ if url.instance_of? URI::HTTPS
141
+ http.use_ssl = true
142
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
143
+ end
144
+ res = http.request(req)
145
+ end
146
+
147
+ end
148
+
149
+ end
@@ -0,0 +1,24 @@
1
+ # encoding: utf-8
2
+
3
+ module ::Nanoc::Extra::Checking::Checks
4
+
5
+ class HTML < ::Nanoc::Extra::Checking::Check
6
+
7
+ identifier :html
8
+
9
+ def run
10
+ require 'w3c_validators'
11
+
12
+ Dir[site.config[:output_dir] + '/**/*.{htm,html}'].each do |filename|
13
+ results = ::W3CValidators::MarkupValidator.new.validate_file(filename)
14
+ results.errors.each do |e|
15
+ desc = e.message.gsub(%r{\s+}, ' ').strip
16
+ self.add_issue(desc, :subject => filename)
17
+ end
18
+ end
19
+ end
20
+
21
+ end
22
+
23
+ end
24
+
@@ -0,0 +1,57 @@
1
+ # encoding: utf-8
2
+
3
+ module Nanoc::Extra::Checking::Checks
4
+
5
+ # A check that verifies that all internal links point to a location that exists.
6
+ class InternalLinks < ::Nanoc::Extra::Checking::Check
7
+
8
+ # Starts the validator. The results will be printed to stdout.
9
+ #
10
+ # @return [void]
11
+ def run
12
+ # TODO de-duplicate this (duplicated in external links check)
13
+ filenames = self.output_filenames.select { |f| File.extname(f) == '.html' }
14
+ hrefs_with_filenames = ::Nanoc::Extra::LinkCollector.new(filenames, :internal).filenames_per_href
15
+ hrefs_with_filenames.each_pair do |href, filenames|
16
+ filenames.each do |filename|
17
+ unless valid?(href, filename)
18
+ self.add_issue(
19
+ "reference to #{href}",
20
+ :subject => filename)
21
+ end
22
+ end
23
+ end
24
+ end
25
+
26
+ protected
27
+
28
+ def valid?(href, origin)
29
+ # Skip hrefs that point to self
30
+ # FIXME this is ugly and won’t always be correct
31
+ return true if href == '.'
32
+
33
+ # Remove target
34
+ path = href.sub(/#.*$/, '')
35
+ return true if path.empty?
36
+
37
+ # Make absolute
38
+ if path[0, 1] == '/'
39
+ path = @site.config[:output_dir] + path
40
+ else
41
+ path = ::File.expand_path(path, ::File.dirname(origin))
42
+ end
43
+
44
+ # Check whether file exists
45
+ return true if File.file?(path)
46
+
47
+ # Check whether directory with index file exists
48
+ return true if File.directory?(path) && @site.config[:index_filenames].any? { |fn| File.file?(File.join(path, fn)) }
49
+
50
+ # Nope :(
51
+ return false
52
+ end
53
+
54
+ end
55
+
56
+ end
57
+
@@ -0,0 +1,23 @@
1
+ # encoding: utf-8
2
+
3
+ module Nanoc::Extra::Checking::Checks
4
+
5
+ class Stale < ::Nanoc::Extra::Checking::Check
6
+
7
+ def run
8
+ require 'set'
9
+ item_rep_paths = Set.new(@site.items.collect { |i| i.reps }.flatten.collect { |r| r.raw_path })
10
+ self.output_filenames.each do |f|
11
+ if !item_rep_paths.include?(f)
12
+ self.add_issue(
13
+ "file without matching item",
14
+ :subject => f)
15
+ end
16
+ end
17
+ end
18
+
19
+ end
20
+
21
+ end
22
+
23
+