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
@@ -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
+