[DRE-commits] [nanoc] 01/03: Imported Upstream version 3.8.0

Cédric Boutillier boutil at moszumanska.debian.org
Wed Jun 3 14:26:31 UTC 2015


This is an automated email from the git hooks/post-receive script.

boutil pushed a commit to branch master
in repository nanoc.

commit 86f27eeae15aa9ebe054e13eac356dc2faf7c863
Author: Cédric Boutillier <boutil at debian.org>
Date:   Wed Jun 3 14:25:28 2015 +0200

    Imported Upstream version 3.8.0
---
 ChangeLog                                          |   4 +-
 Gemfile                                            |  39 +--
 Gemfile.lock                                       | 227 -----------------
 LICENSE                                            |   2 +-
 NEWS.md                                            |  55 ++++-
 README.md                                          |   2 +-
 Rakefile                                           |   2 +-
 bin/nanoc                                          |   2 +-
 doc/yardoc_handlers/identifier.rb                  |   6 +-
 lib/nanoc.rb                                       |   6 +-
 lib/nanoc/base.rb                                  |   5 +-
 lib/nanoc/base/checksummer.rb                      |  24 +-
 lib/nanoc/base/compilation/checksum_store.rb       |   6 +-
 .../base/compilation/compiled_content_cache.rb     |   6 +-
 lib/nanoc/base/compilation/compiler.rb             |  79 +++---
 lib/nanoc/base/compilation/compiler_dsl.rb         |  18 +-
 lib/nanoc/base/compilation/dependency_tracker.rb   |  24 +-
 lib/nanoc/base/compilation/filter.rb               |   8 +-
 lib/nanoc/base/compilation/item_rep_proxy.rb       |  15 +-
 .../base/compilation/item_rep_recorder_proxy.rb    |  21 +-
 lib/nanoc/base/compilation/outdatedness_checker.rb |  16 +-
 lib/nanoc/base/compilation/outdatedness_reasons.rb |   6 -
 lib/nanoc/base/compilation/rule.rb                 |  20 +-
 lib/nanoc/base/compilation/rule_context.rb         |  18 +-
 .../base/compilation/rule_memory_calculator.rb     |   4 -
 lib/nanoc/base/compilation/rule_memory_store.rb    |   6 +-
 lib/nanoc/base/compilation/rules_collection.rb     |  24 +-
 lib/nanoc/base/context.rb                          |   5 +-
 lib/nanoc/base/core_ext.rb                         |   1 -
 lib/nanoc/base/core_ext/array.rb                   |   4 +-
 lib/nanoc/base/core_ext/date.rb                    |  29 ---
 lib/nanoc/base/core_ext/hash.rb                    |   4 +-
 lib/nanoc/base/core_ext/pathname.rb                |   2 -
 lib/nanoc/base/core_ext/string.rb                  |   2 -
 lib/nanoc/base/directed_graph.rb                   |  10 +-
 lib/nanoc/base/errors.rb                           |  40 +--
 lib/nanoc/base/memoization.rb                      |   6 +-
 lib/nanoc/base/notification_center.rb              |  10 +-
 lib/nanoc/base/ordered_hash.rb                     | 228 -----------------
 lib/nanoc/base/plugin_registry.rb                  |  18 +-
 lib/nanoc/base/result_data/item_rep.rb             |  83 ++++---
 lib/nanoc/base/source_data/code_snippet.rb         |  10 +-
 lib/nanoc/base/source_data/configuration.rb        |   4 -
 lib/nanoc/base/source_data/data_source.rb          |   9 +-
 lib/nanoc/base/source_data/item.rb                 |  26 +-
 lib/nanoc/base/source_data/item_array.rb           |  10 +-
 lib/nanoc/base/source_data/layout.rb               |  10 +-
 lib/nanoc/base/source_data/site.rb                 |  77 +++---
 lib/nanoc/base/store.rb                            |  10 +-
 lib/nanoc/base/temp_filename_factory.rb            |   8 +-
 lib/nanoc/cli.rb                                   |  43 ++--
 lib/nanoc/cli/ansi_string_colorizer.rb             |  16 +-
 lib/nanoc/cli/cleaning_stream.rb                   |  10 +-
 lib/nanoc/cli/command_runner.rb                    |  13 +-
 lib/nanoc/cli/commands/autocompile.rb              |  16 +-
 lib/nanoc/cli/commands/check.rb                    |  10 +-
 lib/nanoc/cli/commands/compile.rb                  |  79 +++---
 lib/nanoc/cli/commands/create-item.rb              |  16 +-
 lib/nanoc/cli/commands/create-layout.rb            |  32 ++-
 lib/nanoc/cli/commands/create-site.rb              |  26 +-
 lib/nanoc/cli/commands/deploy.rb                   |  26 +-
 lib/nanoc/cli/commands/nanoc.rb                    |   6 +-
 lib/nanoc/cli/commands/prune.rb                    |  14 +-
 lib/nanoc/cli/commands/shell.rb                    |  20 +-
 lib/nanoc/cli/commands/show-data.rb                |  18 +-
 lib/nanoc/cli/commands/show-plugins.rb             |  20 +-
 lib/nanoc/cli/commands/show-rules.rb               |  33 +--
 lib/nanoc/cli/commands/sync.rb                     |   6 +-
 lib/nanoc/cli/commands/update.rb                   |  34 ++-
 lib/nanoc/cli/commands/validate-css.rb             |  10 +-
 lib/nanoc/cli/commands/validate-html.rb            |  10 +-
 lib/nanoc/cli/commands/validate-links.rb           |  16 +-
 lib/nanoc/cli/commands/view.rb                     |  18 +-
 lib/nanoc/cli/commands/watch.rb                    |  42 ++--
 lib/nanoc/cli/error_handler.rb                     |  44 ++--
 lib/nanoc/cli/logger.rb                            |  22 +-
 lib/nanoc/cli/stream_cleaners.rb                   |   4 -
 lib/nanoc/cli/stream_cleaners/abstract.rb          |   6 +-
 lib/nanoc/cli/stream_cleaners/ansi_colors.rb       |   4 -
 lib/nanoc/cli/stream_cleaners/utf8.rb              |   6 +-
 lib/nanoc/data_sources.rb                          |   6 +-
 lib/nanoc/data_sources/deprecated/delicious.rb     |  16 +-
 lib/nanoc/data_sources/deprecated/last_fm.rb       |  28 +--
 lib/nanoc/data_sources/deprecated/twitter.rb       |  10 +-
 lib/nanoc/data_sources/filesystem.rb               |  57 ++---
 lib/nanoc/data_sources/filesystem_unified.rb       |   6 +-
 lib/nanoc/data_sources/filesystem_verbose.rb       |   6 +-
 lib/nanoc/data_sources/static.rb                   |  14 +-
 lib/nanoc/extra.rb                                 |   6 +-
 lib/nanoc/extra/auto_compiler.rb                   |  14 +-
 lib/nanoc/extra/checking.rb                        |   4 -
 lib/nanoc/extra/checking/check.rb                  |  14 +-
 lib/nanoc/extra/checking/checks.rb                 |   4 +-
 lib/nanoc/extra/checking/checks/css.rb             |  12 +-
 lib/nanoc/extra/checking/checks/external_links.rb  |  36 +--
 lib/nanoc/extra/checking/checks/html.rb            |  12 +-
 lib/nanoc/extra/checking/checks/internal_links.rb  |  16 +-
 lib/nanoc/extra/checking/checks/mixed_content.rb   |  31 +++
 lib/nanoc/extra/checking/checks/stale.rb           |  20 +-
 lib/nanoc/extra/checking/dsl.rb                    |   4 -
 lib/nanoc/extra/checking/issue.rb                  |   4 -
 lib/nanoc/extra/checking/runner.rb                 |  30 ++-
 lib/nanoc/extra/chick.rb                           |  28 +--
 lib/nanoc/extra/core_ext.rb                        |   1 -
 lib/nanoc/extra/core_ext/enumerable.rb             |  33 ---
 lib/nanoc/extra/core_ext/pathname.rb               |   4 -
 lib/nanoc/extra/core_ext/time.rb                   |   2 -
 lib/nanoc/extra/deployer.rb                        |   4 -
 lib/nanoc/extra/deployers.rb                       |   4 -
 lib/nanoc/extra/deployers/fog.rb                   |  46 +++-
 lib/nanoc/extra/deployers/rsync.rb                 |  12 +-
 lib/nanoc/extra/file_proxy.rb                      |  12 +-
 lib/nanoc/extra/filesystem_tools.rb                |  49 +++-
 lib/nanoc/extra/jruby_nokogiri_warner.rb           |   6 +-
 lib/nanoc/extra/link_collector.rb                  |  61 +++--
 lib/nanoc/extra/piper.rb                           |  10 +-
 lib/nanoc/extra/pruner.rb                          |  17 +-
 lib/nanoc/extra/validators.rb                      |   4 -
 lib/nanoc/extra/validators/links.rb                |   8 +-
 lib/nanoc/extra/validators/w3c.rb                  |   6 +-
 lib/nanoc/extra/vcs.rb                             |  14 +-
 lib/nanoc/extra/vcses.rb                           |   2 -
 lib/nanoc/extra/vcses/bazaar.rb                    |   4 -
 lib/nanoc/extra/vcses/dummy.rb                     |   6 +-
 lib/nanoc/extra/vcses/git.rb                       |   4 -
 lib/nanoc/extra/vcses/mercurial.rb                 |   4 -
 lib/nanoc/extra/vcses/subversion.rb                |   4 -
 lib/nanoc/filters.rb                               |   2 -
 lib/nanoc/filters/asciidoc.rb                      |   8 +-
 lib/nanoc/filters/bluecloth.rb                     |   4 +-
 lib/nanoc/filters/coderay.rb                       |   2 -
 lib/nanoc/filters/coffeescript.rb                  |   6 +-
 lib/nanoc/filters/colorize_syntax.rb               |  76 +++---
 lib/nanoc/filters/erb.rb                           |   4 +-
 lib/nanoc/filters/erubis.rb                        |   8 +-
 lib/nanoc/filters/haml.rb                          |   6 +-
 lib/nanoc/filters/handlebars.rb                    |   6 +-
 lib/nanoc/filters/kramdown.rb                      |  11 +-
 lib/nanoc/filters/less.rb                          |   8 +-
 lib/nanoc/filters/markaby.rb                       |   4 +-
 lib/nanoc/filters/maruku.rb                        |   2 -
 lib/nanoc/filters/mustache.rb                      |   8 +-
 lib/nanoc/filters/pandoc.rb                        |  29 ++-
 lib/nanoc/filters/rainpress.rb                     |   2 -
 lib/nanoc/filters/rdiscount.rb                     |   2 -
 lib/nanoc/filters/rdoc.rb                          |   4 +-
 lib/nanoc/filters/redcarpet.rb                     |   4 -
 lib/nanoc/filters/redcloth.rb                      |   2 -
 lib/nanoc/filters/relativize_paths.rb              |  16 +-
 lib/nanoc/filters/rubypants.rb                     |   4 +-
 lib/nanoc/filters/sass.rb                          |   6 +-
 lib/nanoc/filters/sass/sass_filesystem_importer.rb |   8 +-
 lib/nanoc/filters/slim.rb                          |   8 +-
 lib/nanoc/filters/typogruby.rb                     |   6 +-
 lib/nanoc/filters/uglify_js.rb                     |   2 -
 lib/nanoc/filters/xsl.rb                           |   8 +-
 lib/nanoc/filters/yui_compressor.rb                |   4 -
 lib/nanoc/helpers.rb                               |   2 -
 lib/nanoc/helpers/blogging.rb                      |  71 +++---
 lib/nanoc/helpers/breadcrumbs.rb                   |   4 -
 lib/nanoc/helpers/capturing.rb                     |  18 +-
 lib/nanoc/helpers/filtering.rb                     |   4 -
 lib/nanoc/helpers/html_escape.rb                   |  16 +-
 lib/nanoc/helpers/link_to.rb                       |   4 -
 lib/nanoc/helpers/rendering.rb                     |  20 +-
 lib/nanoc/helpers/tagging.rb                       |   8 +-
 lib/nanoc/helpers/text.rb                          |   8 +-
 lib/nanoc/helpers/xml_sitemap.rb                   |  18 +-
 lib/nanoc/tasks/clean.rake                         |   2 +-
 lib/nanoc/tasks/clean.rb                           |  10 +-
 lib/nanoc/tasks/deploy/rsync.rake                  |   6 +-
 lib/nanoc/tasks/validate.rake                      |   4 -
 lib/nanoc/version.rb                               |   4 +-
 metadata.yml                                       |  32 +--
 nanoc.gemspec                                      |  21 +-
 tasks/rubocop.rake                                 |   3 +-
 tasks/test.rake                                    |  13 +-
 test/base/checksummer_spec.rb                      |  72 +++---
 test/base/core_ext/array_spec.rb                   |  22 +-
 test/base/core_ext/date_spec.rb                    |  15 --
 test/base/core_ext/hash_spec.rb                    |  26 +-
 test/base/core_ext/pathname_spec.rb                |   6 +-
 test/base/core_ext/string_spec.rb                  |   4 -
 test/base/temp_filename_factory_spec.rb            |   8 -
 test/base/test_checksum_store.rb                   |   4 +-
 test/base/test_code_snippet.rb                     |   2 -
 test/base/test_compiler.rb                         |  42 ++--
 test/base/test_compiler_dsl.rb                     |  42 ++--
 test/base/test_context.rb                          |  10 +-
 test/base/test_data_source.rb                      |   2 -
 test/base/test_dependency_tracker.rb               |  86 ++++---
 test/base/test_directed_graph.rb                   | 210 ++++++++--------
 test/base/test_filter.rb                           |  12 +-
 test/base/test_item.rb                             |  64 ++---
 test/base/test_item_array.rb                       |  52 ++--
 test/base/test_item_rep.rb                         | 271 +++++++++++++--------
 test/base/test_item_rep_recorder_proxy.rb          |  19 ++
 test/base/test_layout.rb                           |  20 +-
 test/base/test_memoization.rb                      |  34 +--
 test/base/test_notification_center.rb              |   2 -
 test/base/test_outdatedness_checker.rb             |  74 +++---
 test/base/test_plugin.rb                           |   2 -
 test/base/test_rule.rb                             |  16 +-
 test/base/test_rule_context.rb                     |  12 +-
 test/base/test_site.rb                             |  34 +--
 test/base/test_store.rb                            |   8 +-
 test/cli/commands/test_check.rb                    |   4 +-
 test/cli/commands/test_compile.rb                  |  45 ++--
 test/cli/commands/test_create_item.rb              |   4 +-
 test/cli/commands/test_create_layout.rb            |   2 -
 test/cli/commands/test_create_site.rb              |  20 +-
 test/cli/commands/test_deploy.rb                   |  40 ++-
 test/cli/commands/test_help.rb                     |   2 -
 test/cli/commands/test_info.rb                     |   2 -
 test/cli/commands/test_prune.rb                    |  18 +-
 test/cli/commands/test_sync.rb                     |   4 +-
 test/cli/commands/test_update.rb                   |   2 -
 test/cli/commands/test_watch.rb                    |  24 +-
 test/cli/test_cleaning_stream.rb                   |  16 +-
 test/cli/test_cli.rb                               |  44 +++-
 test/cli/test_error_handler.rb                     |  18 +-
 test/cli/test_logger.rb                            |   2 -
 test/data_sources/test_filesystem.rb               |  48 ++--
 test/data_sources/test_filesystem_unified.rb       | 129 +++++-----
 test/data_sources/test_filesystem_verbose.rb       |  70 +++---
 test/data_sources/test_static.rb                   |  59 +++--
 test/extra/checking/checks/test_css.rb             |  25 +-
 test/extra/checking/checks/test_external_links.rb  |   6 +-
 test/extra/checking/checks/test_html.rb            |   7 +-
 test/extra/checking/checks/test_internal_links.rb  |   4 +-
 test/extra/checking/checks/test_mixed_content.rb   | 188 ++++++++++++++
 test/extra/checking/checks/test_stale.rb           |  26 +-
 test/extra/checking/test_check.rb                  |  12 +-
 test/extra/checking/test_dsl.rb                    |   6 +-
 test/extra/checking/test_runner.rb                 |   2 -
 test/extra/core_ext/test_enumerable.rb             |  28 ---
 test/extra/core_ext/test_pathname.rb               |   3 -
 test/extra/core_ext/test_time.rb                   |   2 -
 test/extra/deployers/test_fog.rb                   |  80 ++++--
 test/extra/deployers/test_rsync.rb                 |  14 +-
 test/extra/test_auto_compiler.rb                   |  92 ++++---
 test/extra/test_file_proxy.rb                      |   4 +-
 test/extra/test_filesystem_tools.rb                |  60 ++++-
 test/extra/test_link_collector.rb                  |  61 +++--
 test/extra/test_piper.rb                           |   8 +-
 test/extra/test_vcs.rb                             |   2 -
 test/extra/validators/test_links.rb                |   1 -
 test/extra/validators/test_w3c.rb                  |  16 +-
 test/filters/test_asciidoc.rb                      |   6 +-
 test/filters/test_bluecloth.rb                     |   4 +-
 test/filters/test_coderay.rb                       |   6 +-
 test/filters/test_coffeescript.rb                  |   4 +-
 test/filters/test_colorize_syntax.rb               |  65 ++---
 test/filters/test_erb.rb                           |  28 +--
 test/filters/test_erubis.rb                        |  10 +-
 test/filters/test_haml.rb                          |  16 +-
 test/filters/test_handlebars.rb                    |  22 +-
 test/filters/test_kramdown.rb                      |  16 +-
 test/filters/test_less.rb                          |  24 +-
 test/filters/test_markaby.rb                       |   6 +-
 test/filters/test_maruku.rb                        |   6 +-
 test/filters/test_mustache.rb                      |  10 +-
 test/filters/test_pandoc.rb                        |  26 +-
 test/filters/test_rainpress.rb                     |  10 +-
 test/filters/test_rdiscount.rb                     |   6 +-
 test/filters/test_rdoc.rb                          |   4 +-
 test/filters/test_redcarpet.rb                     |  20 +-
 test/filters/test_redcloth.rb                      |  12 +-
 test/filters/test_relativize_paths.rb              | 127 +++++-----
 test/filters/test_rubypants.rb                     |   6 +-
 test/filters/test_sass.rb                          |  43 ++--
 test/filters/test_slim.rb                          |  12 +-
 test/filters/test_typogruby.rb                     |   3 -
 test/filters/test_uglify_js.rb                     |   8 +-
 test/filters/test_xsl.rb                           |  34 ++-
 test/filters/test_yui_compressor.rb                |  14 +-
 .../fixtures/vcr_cassettes/css_run_parse_error.yml |  65 +++++
 test/gem_loader.rb                                 |   4 +-
 test/helper.rb                                     |  56 ++---
 test/helpers/test_blogging.rb                      | 208 +++++++++-------
 test/helpers/test_breadcrumbs.rb                   |  26 +-
 test/helpers/test_capturing.rb                     |  24 +-
 test/helpers/test_filtering.rb                     |  38 ++-
 test/helpers/test_html_escape.rb                   |   2 -
 test/helpers/test_link_to.rb                       |  15 +-
 test/helpers/test_rendering.rb                     |   8 +-
 test/helpers/test_tagging.rb                       |  30 ++-
 test/helpers/test_text.rb                          |  14 +-
 test/helpers/test_xml_sitemap.rb                   |  58 +++--
 test/tasks/test_clean.rb                           |  18 +-
 test/test_gem.rb                                   |   4 +-
 291 files changed, 3029 insertions(+), 3554 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index 17ad160..706bed1 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,3 @@
 For a list of all changes, please see the changelog on the project repository
-instead (http://projects.stoneship.org/hg/nanoc). For release notes, please
-see the NEWS file.
+instead (https://github.com/nanoc/nanoc). For release notes, please see the
+NEWS file.
diff --git a/Gemfile b/Gemfile
index ff5aab2..5cb36c9 100644
--- a/Gemfile
+++ b/Gemfile
@@ -1,45 +1,46 @@
-source "https://rubygems.org"
+source 'https://rubygems.org'
 
 gemspec
 
-# FIXME we may be missing some mswin dependencies here
-all_rubies = Bundler::Dependency::PLATFORM_MAP.keys
-ruby_19_plus               = [:ruby_19, :ruby_20, :ruby_21, :jruby] & all_rubies
-ruby_19_plus_without_jruby = [:ruby_19, :ruby_20, :ruby_21]         & all_rubies
+# FIXME: we may be missing some mswin dependencies here
 
 gem 'adsf'
-gem 'bluecloth', :platforms => :ruby
+gem 'bluecloth', platforms: :ruby
 gem 'builder'
 gem 'coderay'
 gem 'compass'
 gem 'coffee-script'
-gem 'coveralls', :require => false
+gem 'coveralls', require: false
 gem 'erubis'
-gem 'fog', :platforms => ruby_19_plus
+gem 'fog'
 gem 'haml'
-gem 'handlebars', :platforms => ruby_19_plus_without_jruby
+gem 'handlebars', platforms: :ruby
 gem 'kramdown'
-gem 'less', '~> 2.0', :platforms => :ruby
-gem 'listen', :platforms => ruby_19_plus
+gem 'less', '~> 2.0', platforms: :ruby
+gem 'listen'
 gem 'markaby'
 gem 'maruku'
-gem 'mime-types', :platforms => ruby_19_plus
-gem 'minitest', '~> 4.0'
+gem 'mime-types'
+gem 'minitest', '~> 5.0'
 gem 'mocha'
-gem 'mustache'
+if RUBY_VERSION >= '2.0.0'
+  gem 'mustache', '~> 1.0'
+else
+  gem 'mustache', '~> 0.99'
+end
 gem 'nokogiri', '~> 1.6'
 gem 'pandoc-ruby'
 gem 'pry'
-gem 'pygments.rb', :platforms => [:ruby, :mswin]
+gem 'pygments.rb', platforms: [:ruby, :mswin]
 gem 'rack'
 gem 'rake'
 gem 'rainpress'
-gem 'rdiscount', :platforms => [:ruby, :mswin]
+gem 'rdiscount', platforms: [:ruby, :mswin]
 gem 'rdoc'
-gem 'redcarpet', :platforms => ruby_19_plus_without_jruby + [:mswin]
+gem 'redcarpet', platforms: [:ruby, :mswin]
 gem 'RedCloth'
 gem 'rouge'
-gem 'rubocop', :platforms => ruby_19_plus
+gem 'rubocop'
 gem 'rubypants'
 gem 'sass', '~> 3.2.2'
 gem 'slim'
@@ -48,5 +49,5 @@ gem 'uglifier'
 gem 'vcr'
 gem 'w3c_validators'
 gem 'webmock'
-gem 'yuicompressor', :platforms => ruby_19_plus
+gem 'yuicompressor'
 gem 'yard'
diff --git a/Gemfile.lock b/Gemfile.lock
deleted file mode 100644
index a6a2bfc..0000000
--- a/Gemfile.lock
+++ /dev/null
@@ -1,227 +0,0 @@
-PATH
-  remote: .
-  specs:
-    nanoc (3.7.3)
-      cri (~> 2.3)
-
-GEM
-  remote: https://rubygems.org/
-  specs:
-    RedCloth (4.2.9)
-    addressable (2.3.6)
-    adsf (1.2.0)
-      rack (>= 1.0.0)
-    ast (2.0.0)
-    bluecloth (2.2.0)
-    builder (3.2.2)
-    celluloid (0.15.2)
-      timers (~> 1.1.0)
-    chunky_png (1.3.1)
-    coderay (1.1.0)
-    coffee-script (2.3.0)
-      coffee-script-source
-      execjs
-    coffee-script-source (1.8.0)
-    colored (1.2)
-    commonjs (0.2.7)
-    compass (0.12.7)
-      chunky_png (~> 1.2)
-      fssm (>= 0.2.7)
-      sass (~> 3.2.19)
-    coveralls (0.7.1)
-      multi_json (~> 1.3)
-      rest-client
-      simplecov (>= 0.7)
-      term-ansicolor
-      thor
-    crack (0.4.2)
-      safe_yaml (~> 1.0.0)
-    cri (2.6.1)
-      colored (~> 1.2)
-    docile (1.1.5)
-    erubis (2.7.0)
-    excon (0.39.5)
-    execjs (2.2.1)
-    ffi (1.9.3)
-    fog (1.23.0)
-      fog-brightbox
-      fog-core (~> 1.23)
-      fog-json
-      fog-softlayer
-      ipaddress (~> 0.5)
-      nokogiri (~> 1.5, >= 1.5.11)
-    fog-brightbox (0.4.1)
-      fog-core (~> 1.22)
-      fog-json
-      inflecto
-    fog-core (1.24.0)
-      builder
-      excon (~> 0.38)
-      formatador (~> 0.2)
-      mime-types
-      net-scp (~> 1.1)
-      net-ssh (>= 2.1.3)
-    fog-json (1.0.0)
-      multi_json (~> 1.0)
-    fog-softlayer (0.3.16)
-      fog-core
-      fog-json
-    formatador (0.2.5)
-    fssm (0.2.10)
-    haml (4.0.5)
-      tilt
-    handlebars (0.6.0)
-      handlebars-source (~> 1.3.0)
-      therubyracer (~> 0.12.0)
-    handlebars-source (1.3.0)
-    inflecto (0.0.2)
-    ipaddress (0.8.0)
-    json (1.8.1)
-    kramdown (1.4.1)
-    less (2.6.0)
-      commonjs (~> 0.2.7)
-    libv8 (3.16.14.3)
-    listen (2.7.9)
-      celluloid (>= 0.15.2)
-      rb-fsevent (>= 0.9.3)
-      rb-inotify (>= 0.9)
-    markaby (0.8.0)
-      builder
-    maruku (0.7.2)
-    metaclass (0.0.4)
-    method_source (0.8.2)
-    mime-types (2.3)
-    mini_portile (0.6.0)
-    minitest (4.7.5)
-    mocha (1.1.0)
-      metaclass (~> 0.0.1)
-    multi_json (1.10.1)
-    mustache (0.99.6)
-    net-scp (1.2.1)
-      net-ssh (>= 2.6.5)
-    net-ssh (2.9.1)
-    netrc (0.7.7)
-    nokogiri (1.6.3.1)
-      mini_portile (= 0.6.0)
-    pandoc-ruby (0.7.5)
-    parser (2.2.0.pre.4)
-      ast (>= 1.1, < 3.0)
-      slop (~> 3.4, >= 3.4.5)
-    posix-spawn (0.3.9)
-    powerpack (0.0.9)
-    pry (0.10.1)
-      coderay (~> 1.1.0)
-      method_source (~> 0.8.1)
-      slop (~> 3.4)
-    pygments.rb (0.6.0)
-      posix-spawn (~> 0.3.6)
-      yajl-ruby (~> 1.1.0)
-    rack (1.5.2)
-    rainbow (2.0.0)
-    rainpress (1.0)
-    rake (10.3.2)
-    rb-fsevent (0.9.4)
-    rb-inotify (0.9.5)
-      ffi (>= 0.5.0)
-    rdiscount (2.1.7.1)
-    rdoc (4.1.1)
-      json (~> 1.4)
-    redcarpet (3.1.2)
-    ref (1.0.5)
-    rest-client (1.7.2)
-      mime-types (>= 1.16, < 3.0)
-      netrc (~> 0.7)
-    rouge (1.6.2)
-    rubocop (0.25.0)
-      parser (>= 2.2.0.pre.4, < 3.0)
-      powerpack (~> 0.0.6)
-      rainbow (>= 1.99.1, < 3.0)
-      ruby-progressbar (~> 1.4)
-    ruby-progressbar (1.5.1)
-    rubypants (0.2.0)
-    safe_yaml (1.0.3)
-    sass (3.2.19)
-    simplecov (0.9.0)
-      docile (~> 1.1.0)
-      multi_json
-      simplecov-html (~> 0.8.0)
-    simplecov-html (0.8.0)
-    slim (2.0.3)
-      temple (~> 0.6.6)
-      tilt (>= 1.3.3, < 2.1)
-    slop (3.6.0)
-    temple (0.6.8)
-    term-ansicolor (1.3.0)
-      tins (~> 1.0)
-    therubyracer (0.12.1)
-      libv8 (~> 3.16.14.0)
-      ref
-    thor (0.19.1)
-    tilt (2.0.1)
-    timers (1.1.0)
-    tins (1.3.2)
-    typogruby (1.0.17)
-      rubypants
-    uglifier (2.5.3)
-      execjs (>= 0.3.0)
-      json (>= 1.8.0)
-    vcr (2.9.2)
-    w3c_validators (1.2)
-      json
-      nokogiri
-    webmock (1.18.0)
-      addressable (>= 2.3.6)
-      crack (>= 0.3.2)
-    yajl-ruby (1.1.0)
-    yard (0.8.7.4)
-    yuicompressor (1.3.3)
-
-PLATFORMS
-  ruby
-
-DEPENDENCIES
-  RedCloth
-  adsf
-  bluecloth
-  builder
-  bundler (~> 1.5)
-  coderay
-  coffee-script
-  compass
-  coveralls
-  erubis
-  fog
-  haml
-  handlebars
-  kramdown
-  less (~> 2.0)
-  listen
-  markaby
-  maruku
-  mime-types
-  minitest (~> 4.0)
-  mocha
-  mustache
-  nanoc!
-  nokogiri (~> 1.6)
-  pandoc-ruby
-  pry
-  pygments.rb
-  rack
-  rainpress
-  rake
-  rdiscount
-  rdoc
-  redcarpet
-  rouge
-  rubocop
-  rubypants
-  sass (~> 3.2.2)
-  slim
-  typogruby
-  uglifier
-  vcr
-  w3c_validators
-  webmock
-  yard
-  yuicompressor
diff --git a/LICENSE b/LICENSE
index 9858856..a519c95 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2007-2013 Denis Defreyne and contributors
+Copyright (c) 2007-2015 Denis Defreyne and contributors
 
 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
diff --git a/NEWS.md b/NEWS.md
index cfb7e46..6d4005d 100644
--- a/NEWS.md
+++ b/NEWS.md
@@ -1,5 +1,54 @@
 # nanoc news
 
+## 3.8.0 (2015-05-04)
+
+Features:
+
+* Added `mixed_content` check (#542, #543) [Mike Pennisi]
+* Added `commands_dirs` configuration option for specifying directories to read commands from (#475) [Gregory Pakosz]
+* Added `:cdn_id` option to fog deployer for invalidating CDN objects (#451) [Vlatko Kosturjak]
+* Add access to regular expressions group matches in rules (#478) [Michal Papis]
+* Allow filtering the items array by regex (#458) [Mike Pennisi]
+
+Enhancements:
+
+* Added `:preserve_order` option to preserve order in Atom feed (#533, #534)
+* Allowed accessing `:pre` snapshot from within item itself (#537, #538, #548)
+
+Fixes:
+
+* Allowed passing generic Pandoc options with :args (#526, #535)
+* Fix crash when compiling extensionless binary items (#524, #525)
+* Fix double snapshot creation error (#547)
+
+## 3.7.5 (2015-01-12)
+
+Enhancements:
+
+* Allowed extra patterns to be specified in the data source configuration, so that dotfiles are no longer necessary ignored (e.g. `extra_files: ['.htaccess']`) (#492, #498) [Andy Drop, Michal Papis]
+* Removed Ruby 1.8.x support ([details](https://groups.google.com/forum/#!topic/nanoc/pSL1i15EFz8)) (#517)
+* Improved CSS and HTML error messages (#484, #504)
+* Let kramdown filter print warnings (#459, #519)
+
+Fixes:
+
+* Fixed HTML class names for recent Rouge versions (#502)
+* Fixed crash when using items or layouts in attributes (#469, #518)
+
+## 3.7.4 (2014-11-23)
+
+Enhancements:
+
+* Made `check` command fail when output directory is missing (#472) [Mike Pennisi]
+* Made external links check timeouts start small and grow (#483) [Michal Papis]
+* Made code and API adhere much more closely to the Ruby style guide (#476)
+
+Fixes:
+
+* Fixed potential “parent directory is world writable” error (#465, #474)
+* Fixed retrying requests in the external link checker (#483) [Michal Papis]
+* Fixed issue with data sources not being unloaded (#491) [Michal Papis]
+
 ## 3.7.3 (2014-08-31)
 
 Fixes:
@@ -603,7 +652,7 @@ Removed:
 New:
 
 * `--pages` and `--assets` compiler options
-* `--no-color` commandline option
+* `--no-color` command-line option
 * `Filtering` helper
 * `#relative_path_to` function in `LinkTo` helper
 * `rainpress` filter ([Rainpress site](http://code.google.com/p/rainpress/))
@@ -613,7 +662,7 @@ New:
 
 Changed:
 
-* The commandline option parser is now a lot more reliable
+* The command-line option parser is now a lot more reliable
 * `#atom_feed` now takes optional `:content_proc`, `:excerpt_proc` and `:articles` parameters
 * The compile command show non-written items (those with `skip_output: true`)
 * The compile command compiles everything by default
@@ -668,7 +717,7 @@ New:
 * New `rdiscount` filter ([RDiscount site](http://github.com/rtomayko/rdiscount))
 * New `maruku` filter ([Maruku site](https://github.com/bhollis/maruku/))
 * New `erubis` filter ([Erubis site](http://www.kuwata-lab.com/erubis/))
-* A better commandline frontend
+* A better command-line frontend
 * A new filesystem data source named `filesystem_combined`
 * Routers, which decide where compiled pages should be written to
 * Page/layout mtimes can now be retrieved through `page.mtime`/`layout.mtime`
diff --git a/README.md b/README.md
index 104e4ea..6cc2028 100644
--- a/README.md
+++ b/README.md
@@ -2,7 +2,7 @@
 [![Build status](http://img.shields.io/travis/nanoc/nanoc.svg)](https://travis-ci.org/nanoc/nanoc)
 [![Code Climate](http://img.shields.io/codeclimate/github/nanoc/nanoc.svg)](https://codeclimate.com/github/nanoc/nanoc)
 [![Code Coverage](http://img.shields.io/coveralls/nanoc/nanoc.svg)](https://coveralls.io/r/nanoc/nanoc)
-[![Documentation Coverage](http://inch-pages.github.io/github/nanoc/nanoc.svg)](http://inch-pages.github.io/github/nanoc/nanoc/)
+[![Documentation Coverage](http://inch-ci.org/github/nanoc/nanoc.svg)](http://inch-ci.org/github/nanoc/nanoc/)
 
 ![nanoc logo](https://avatars1.githubusercontent.com/u/3260163?s=140)
 
diff --git a/Rakefile b/Rakefile
index d7dc5cb..463c8e5 100644
--- a/Rakefile
+++ b/Rakefile
@@ -11,4 +11,4 @@ require 'nanoc'
 Dir.glob('tasks/**/*.rake').each { |r| Rake.application.add_import r }
 
 # Set default task
-task :default => :test
+task default: :test
diff --git a/bin/nanoc b/bin/nanoc
index 2b01e1a..043e5fc 100755
--- a/bin/nanoc
+++ b/bin/nanoc
@@ -12,7 +12,7 @@ begin
   end
 rescue LoadError
   # no problem
-end
+end if File.file?('Gemfile')
 
 # Add lib to load path
 $LOAD_PATH.unshift(File.expand_path(File.dirname(__FILE__) + '/../lib'))
diff --git a/doc/yardoc_handlers/identifier.rb b/doc/yardoc_handlers/identifier.rb
index 8991f9f..37a6ca3 100644
--- a/doc/yardoc_handlers/identifier.rb
+++ b/doc/yardoc_handlers/identifier.rb
@@ -1,5 +1,4 @@
 class NanocIdentifierHandler < ::YARD::Handlers::Ruby::AttributeHandler
-
   # e.g. identifier :foo, :bar
 
   handles method_call(:identifier), method_call(:identifiers)
@@ -9,11 +8,9 @@ class NanocIdentifierHandler < ::YARD::Handlers::Ruby::AttributeHandler
     identifiers = statement.parameters(false).map { |param| param.jump(:ident)[0] }
     namespace['nanoc_identifiers'] = identifiers
   end
-
 end
 
 class NanocRegisterFilterHandler < ::YARD::Handlers::Ruby::AttributeHandler
-
   # e.g. Nanoc::Filter.register '::Nanoc::Filters::AsciiDoc', :asciidoc
 
   handles method_call(:register)
@@ -21,7 +18,7 @@ class NanocRegisterFilterHandler < ::YARD::Handlers::Ruby::AttributeHandler
 
   def process
     target = statement.jump(:const_path_ref)
-    return if target != s(:const_path_ref, s(:var_ref, s(:const, "Nanoc")), s(:const, "Filter"))
+    return if target != s(:const_path_ref, s(:var_ref, s(:const, 'Nanoc')), s(:const, 'Filter'))
 
     class_name = statement.jump(:string_literal).jump(:tstring_content)[0]
     identifier = statement.jump(:symbol_literal).jump(:ident)[0]
@@ -30,5 +27,4 @@ class NanocRegisterFilterHandler < ::YARD::Handlers::Ruby::AttributeHandler
     obj['nanoc_identifiers'] ||= []
     obj['nanoc_identifiers'] << identifier
   end
-
 end
diff --git a/lib/nanoc.rb b/lib/nanoc.rb
index 07467f9..2df1f05 100644
--- a/lib/nanoc.rb
+++ b/lib/nanoc.rb
@@ -1,23 +1,21 @@
 # encoding: utf-8
 
 module Nanoc
-
   # @return [String] A string containing information about this nanoc version
   #   and its environment (Ruby engine and version, Rubygems version if any).
   def self.version_information
     gem_info = defined?(Gem) ? "with RubyGems #{Gem::VERSION}" : 'without RubyGems'
     engine   = defined?(RUBY_ENGINE) ? RUBY_ENGINE : 'ruby'
     res = ''
-    res << "nanoc #{Nanoc::VERSION} © 2007-2014 Denis Defreyne.\n"
+    res << "nanoc #{Nanoc::VERSION} © 2007-2015 Denis Defreyne.\n"
     res << "Running #{engine} #{RUBY_VERSION} (#{RUBY_RELEASE_DATE}) on #{RUBY_PLATFORM} #{gem_info}.\n"
     res
   end
 
   # @return [Boolean] True if the current platform is Windows,
   def self.on_windows?
-    !!(RUBY_PLATFORM =~ /windows|bccwin|cygwin|djgpp|mingw|mswin|wince/i)
+    RUBY_PLATFORM =~ /windows|bccwin|cygwin|djgpp|mingw|mswin|wince/i
   end
-
 end
 
 Nanoc3 = Nanoc
diff --git a/lib/nanoc/base.rb b/lib/nanoc/base.rb
index fb8079a..831306f 100644
--- a/lib/nanoc/base.rb
+++ b/lib/nanoc/base.rb
@@ -1,9 +1,7 @@
 # encoding: utf-8
 
 module Nanoc
-
   require 'nanoc/base/core_ext'
-  require 'nanoc/base/ordered_hash'
 
   # Load helper classes
   autoload 'Context',              'nanoc/base/context'
@@ -46,7 +44,6 @@ module Nanoc
   autoload 'RulesCollection',      'nanoc/base/compilation/rules_collection'
 
   # Deprecated; use PluginRepository instead
-  # TODO [in nanoc 4.0] remove me
+  # TODO: [in nanoc 4.0] remove me
   autoload 'Plugin',               'nanoc/base/plugin_registry'
-
 end
diff --git a/lib/nanoc/base/checksummer.rb b/lib/nanoc/base/checksummer.rb
index afd8037..df27016 100644
--- a/lib/nanoc/base/checksummer.rb
+++ b/lib/nanoc/base/checksummer.rb
@@ -1,15 +1,12 @@
 # encoding: utf-8
 
 module Nanoc
-
   # Creates checksums for given objects.
   #
   # A checksum is a string, such as “mL+TaqNsEeiPkWloPgCtAofT1yg=”, that is used
   # to determine whether a piece of data has changed.
   class Checksummer
-
     class << self
-
       # @param obj The object to create a checksum for
       #
       # @return [String] The digest
@@ -19,25 +16,30 @@ module Nanoc
         digest.base64digest
       end
 
-    private
+      private
 
-      def update(obj, digest)
+      def update(obj, digest, visited = Set.new)
         digest.update(obj.class.to_s)
 
+        if visited.include?(obj)
+          digest.update('recur')
+          return
+        end
+
         case obj
         when String
           digest.update(obj)
         when Array
           obj.each do |el|
             digest.update('elem')
-            update(el, digest)
+            update(el, digest, visited + [obj])
           end
         when Hash
           obj.each do |key, value|
             digest.update('key')
-            update(key, digest)
+            update(key, digest, visited + [obj])
             digest.update('value')
-            update(value, digest)
+            update(value, digest, visited + [obj])
           end
         when Pathname
           filename = obj.to_s
@@ -62,7 +64,7 @@ module Nanoc
           digest.update('attributes')
           attributes = obj.attributes.dup
           attributes.delete(:file)
-          update(attributes, digest)
+          update(attributes, digest, visited + [obj])
         else
           data = begin
             Marshal.dump(obj)
@@ -72,11 +74,7 @@ module Nanoc
 
           digest.update(data)
         end
-
       end
-
     end
-
   end
-
 end
diff --git a/lib/nanoc/base/compilation/checksum_store.rb b/lib/nanoc/base/compilation/checksum_store.rb
index 43232c5..d9b0963 100644
--- a/lib/nanoc/base/compilation/checksum_store.rb
+++ b/lib/nanoc/base/compilation/checksum_store.rb
@@ -1,13 +1,11 @@
 # encoding: utf-8
 
 module Nanoc
-
   # Stores checksums for objects in order to be able to detect whether a file
   # has changed since the last site compilation.
   #
   # @api private
   class ChecksumStore < ::Nanoc::Store
-
     # @option params [Nanoc::Site] site The site where this checksum store
     #   belongs to
     def initialize(params = {})
@@ -42,7 +40,7 @@ module Nanoc
       @checksums = {}
     end
 
-  protected
+    protected
 
     def data
       @checksums
@@ -51,7 +49,5 @@ module Nanoc
     def data=(new_data)
       @checksums = new_data
     end
-
   end
-
 end
diff --git a/lib/nanoc/base/compilation/compiled_content_cache.rb b/lib/nanoc/base/compilation/compiled_content_cache.rb
index d3a47d3..6b4f312 100644
--- a/lib/nanoc/base/compilation/compiled_content_cache.rb
+++ b/lib/nanoc/base/compilation/compiled_content_cache.rb
@@ -1,13 +1,11 @@
 # encoding: utf-8
 
 module Nanoc
-
   # Represents a cache than can be used to store already compiled content,
   # to prevent it from being needlessly recompiled.
   #
   # @api private
   class CompiledContentCache < ::Nanoc::Store
-
     def initialize
       super('tmp/compiled_content', 1)
 
@@ -47,7 +45,7 @@ module Nanoc
       @cache = {}
     end
 
-  protected
+    protected
 
     def data
       @cache
@@ -56,7 +54,5 @@ module Nanoc
     def data=(new_data)
       @cache = new_data
     end
-
   end
-
 end
diff --git a/lib/nanoc/base/compilation/compiler.rb b/lib/nanoc/base/compilation/compiler.rb
index 274fda6..4b0e750 100644
--- a/lib/nanoc/base/compilation/compiler.rb
+++ b/lib/nanoc/base/compilation/compiler.rb
@@ -1,7 +1,6 @@
 # encoding: utf-8
 
 module Nanoc
-
   # Responsible for compiling a site’s item representations.
   #
   # The compilation process makes use of notifications (see
@@ -39,7 +38,6 @@ module Nanoc
   # * `processing_ended` — indicates that the compiler has finished processing
   #   the specified object.
   class Compiler
-
     extend Nanoc::Memoization
 
     # @group Accessors
@@ -74,7 +72,7 @@ module Nanoc
     #
     # @overload run
     #   @return [void]
-    def run(*args)
+    def run(*_args)
       # Create output directory if necessary
       FileUtils.mkdir_p(@site.config[:output_dir])
 
@@ -125,7 +123,7 @@ module Nanoc
       route_reps
 
       # Load auxiliary stores
-      stores.each { |s| s.load }
+      stores.each(&:load)
 
       @loaded = true
     rescue => e
@@ -144,7 +142,7 @@ module Nanoc
       return if @unloading
       @unloading = true
 
-      stores.each { |s| s.unload }
+      stores.each(&:unload)
 
       @stack = []
 
@@ -175,7 +173,7 @@ module Nanoc
       end
 
       # Store
-      stores.each { |s| s.store }
+      stores.each(&:store)
     end
 
     # Returns the dependency tracker for this site, creating it first if it
@@ -206,7 +204,7 @@ module Nanoc
     # @api private
     def objects
       site.items + site.layouts + site.code_snippets +
-        [ site.config, rules_collection ]
+        [site.config, rules_collection]
     end
 
     # Creates the representations of all items as defined by the compilation
@@ -220,7 +218,7 @@ module Nanoc
         raise Nanoc::Errors::NoMatchingCompilationRuleFound.new(item) if matching_rules.empty?
 
         # Create reps
-        rep_names = matching_rules.map { |r| r.rep_name }.uniq
+        rep_names = matching_rules.map(&:rep_name).uniq
         rep_names.each do |rep_name|
           item.reps << ItemRep.new(item, rep_name)
         end
@@ -238,7 +236,7 @@ module Nanoc
 
         rules.each_pair do |snapshot, rule|
           # Get basic path by applying matching rule
-          basic_path = rule.apply_to(rep, :compiler => self)
+          basic_path = rule.apply_to(rep, compiler: self)
           next if basic_path.nil?
           if basic_path !~ %r{^/}
             raise "The path returned for the #{rep.inspect} item representation, “#{basic_path}”, does not start with a slash. Please ensure that all routing rules return a path that starts with a slash."
@@ -250,11 +248,12 @@ module Nanoc
           # Get normal path by stripping index filename
           rep.paths[snapshot] = basic_path
           @site.config[:index_filenames].each do |index_filename|
-            if rep.paths[snapshot][-index_filename.length..-1] == index_filename
-              # Strip and stop
-              rep.paths[snapshot] = rep.paths[snapshot][0..-index_filename.length - 1]
-              break
-            end
+            rep_path_ending = rep.paths[snapshot][-index_filename.length..-1]
+            next unless rep_path_ending == index_filename
+
+            # Strip and stop
+            rep.paths[snapshot] = rep.paths[snapshot][0..-index_filename.length - 1]
+            break
           end
         end
       end
@@ -269,32 +268,32 @@ module Nanoc
     # @api private
     def assigns_for(rep)
       if rep.binary?
-        content_or_filename_assigns = { :filename => rep.temporary_filenames[:last] }
+        content_or_filename_assigns = { filename: rep.temporary_filenames[:last] }
       else
-        content_or_filename_assigns = { :content => rep.content[:last] }
+        content_or_filename_assigns = { content: rep.content[:last] }
       end
 
       content_or_filename_assigns.merge({
-        :item       => rep.item,
-        :rep        => rep,
-        :item_rep   => rep,
-        :items      => site.items,
-        :layouts    => site.layouts,
-        :config     => site.config,
-        :site       => site
+        item: rep.item,
+        rep: rep,
+        item_rep: rep,
+        items: site.items,
+        layouts: site.layouts,
+        config: site.config,
+        site: site
       })
     end
 
     # @return [Nanoc::OutdatednessChecker] The outdatedness checker
     def outdatedness_checker
       Nanoc::OutdatednessChecker.new(
-        :site => @site,
-        :checksum_store => checksum_store,
-        :dependency_tracker => dependency_tracker)
+        site: @site,
+        checksum_store: checksum_store,
+        dependency_tracker: dependency_tracker)
     end
     memoize :outdatedness_checker
 
-  private
+    private
 
     # @return [Array<Nanoc::Item>] The site’s items
     def items
@@ -304,7 +303,7 @@ module Nanoc
 
     # @return [Array<Nanoc::ItemRep>] The site’s item representations
     def reps
-      items.map { |i| i.reps }.flatten
+      items.map(&:reps).flatten
     end
     memoize :reps
 
@@ -324,7 +323,7 @@ module Nanoc
 
       # Listen to processing start/stop
       Nanoc::NotificationCenter.on(:processing_started, self) { |obj| @stack.push(obj) }
-      Nanoc::NotificationCenter.on(:processing_ended,   self) { |obj| @stack.pop       }
+      Nanoc::NotificationCenter.on(:processing_ended,   self) { |_obj| @stack.pop       }
 
       # Assign snapshots
       reps.each do |rep|
@@ -350,7 +349,7 @@ module Nanoc
       end
 
       # Check whether everything was compiled
-      if !content_dependency_graph.vertices.empty?
+      unless content_dependency_graph.vertices.empty?
         raise Nanoc::Errors::RecursiveCompilation.new(content_dependency_graph.vertices)
       end
     ensure
@@ -382,8 +381,8 @@ module Nanoc
       else
         # Recalculate content
         rep.snapshot(:raw)
-        rep.snapshot(:pre, :final => false)
-        rules_collection.compilation_rule_for(rep).apply_to(rep, :compiler => self)
+        rep.snapshot(:pre, final: false)
+        rules_collection.compilation_rule_for(rep).apply_to(rep, compiler: self)
         rep.snapshot(:post) if rep.has_snapshot?(:post)
         rep.snapshot(:last)
       end
@@ -418,10 +417,10 @@ module Nanoc
     # Returns a preprocessor context, creating one if none exists yet.
     def preprocessor_context
       Nanoc::Context.new({
-        :site    => @site,
-        :config  => @site.config,
-        :items   => @site.items,
-        :layouts => @site.layouts
+        site: @site,
+        config: @site.config,
+        items: @site.items,
+        layouts: @site.layouts
       })
     end
     memoize :preprocessor_context
@@ -434,19 +433,19 @@ module Nanoc
 
     # @return [ChecksumStore] The checksum store
     def checksum_store
-      Nanoc::ChecksumStore.new(:site => @site)
+      Nanoc::ChecksumStore.new(site: @site)
     end
     memoize :checksum_store
 
     # @return [RuleMemoryStore] The rule memory store
     def rule_memory_store
-      Nanoc::RuleMemoryStore.new(:site => @site)
+      Nanoc::RuleMemoryStore.new(site: @site)
     end
     memoize :rule_memory_store
 
     # @return [RuleMemoryCalculator] The rule memory calculator
     def rule_memory_calculator
-      Nanoc::RuleMemoryCalculator.new(:rules_collection => rules_collection)
+      Nanoc::RuleMemoryCalculator.new(rules_collection: rules_collection)
     end
     memoize :rule_memory_calculator
 
@@ -460,7 +459,5 @@ module Nanoc
         rule_memory_store
       ]
     end
-
   end
-
 end
diff --git a/lib/nanoc/base/compilation/compiler_dsl.rb b/lib/nanoc/base/compilation/compiler_dsl.rb
index 2be2249..3a4c3b7 100644
--- a/lib/nanoc/base/compilation/compiler_dsl.rb
+++ b/lib/nanoc/base/compilation/compiler_dsl.rb
@@ -1,10 +1,8 @@
 # encoding: utf-8
 
 module Nanoc
-
   # Contains methods that will be executed by the site’s `Rules` file.
   class CompilerDSL
-
     # The current rules filename.
     #
     # @return [String] The current rules filename.
@@ -122,7 +120,7 @@ module Nanoc
       snapshot_name = params[:snapshot] || :last
 
       # Create rule
-      rule = Rule.new(identifier_to_regex(identifier), rep_name, block, :snapshot_name => snapshot_name)
+      rule = Rule.new(identifier_to_regex(identifier), rep_name, block, snapshot_name: snapshot_name)
       @rules_collection.add_item_routing_rule(rule)
     end
 
@@ -151,7 +149,7 @@ module Nanoc
     #
     #     layout '/custom/',  :haml, :format => :html5
     def layout(identifier, filter_name, params = {})
-      @rules_collection.layout_filter_mapping[identifier_to_regex(identifier)] = [ filter_name, params ]
+      @rules_collection.layout_filter_mapping[identifier_to_regex(identifier)] = [filter_name, params]
     end
 
     # Creates a pair of compilation and routing rules that indicate that the
@@ -199,7 +197,7 @@ module Nanoc
         # data source.
         item[:extension].nil? || (item[:content_filename].nil? && item.identifier =~ %r{#{item[:extension]}/$}) ? item.identifier.chop : item.identifier.chop + '.' + item[:extension]
       end
-      routing_rule = Rule.new(identifier_to_regex(identifier), rep_name, routing_block, :snapshot_name => :last)
+      routing_rule = Rule.new(identifier_to_regex(identifier), rep_name, routing_block, snapshot_name: :last)
       @rules_collection.add_item_routing_rule(routing_rule)
     end
 
@@ -230,7 +228,7 @@ module Nanoc
       compilation_rule = Rule.new(identifier_to_regex(identifier), rep_name, proc {})
       @rules_collection.add_item_compilation_rule(compilation_rule)
 
-      routing_rule = Rule.new(identifier_to_regex(identifier), rep_name, proc {}, :snapshot_name => :last)
+      routing_rule = Rule.new(identifier_to_regex(identifier), rep_name, proc {}, snapshot_name: :last)
       @rules_collection.add_item_routing_rule(routing_rule)
     end
 
@@ -247,13 +245,13 @@ module Nanoc
     #     include_rules 'rules/assets'
     #     include_rules 'rules/content'
     def include_rules(name)
-      filename = [ "#{name}", "#{name}.rb", "./#{name}", "./#{name}.rb" ].find { |f| File.file?(f) }
+      filename = ["#{name}", "#{name}.rb", "./#{name}", "./#{name}.rb"].find { |f| File.file?(f) }
       raise Nanoc::Errors::NoRulesFileFound.new if filename.nil?
 
       @rules_collection.parse(filename)
     end
 
-  private
+    private
 
     # Converts the given identifier, which can contain the '*' or '+'
     # wildcard characters, matching zero or more resp. one or more
@@ -264,14 +262,12 @@ module Nanoc
         # Add leading/trailing slashes if necessary
         new_identifier = identifier.dup
         new_identifier[/^/] = '/' if identifier[0, 1] != '/'
-        new_identifier[/$/] = '/' unless [ '*', '/' ].include?(identifier[-1, 1])
+        new_identifier[/$/] = '/' unless ['*', '/'].include?(identifier[-1, 1])
 
         /^#{new_identifier.gsub('*', '(.*?)').gsub('+', '(.+?)')}$/
       else
         identifier
       end
     end
-
   end
-
 end
diff --git a/lib/nanoc/base/compilation/dependency_tracker.rb b/lib/nanoc/base/compilation/dependency_tracker.rb
index fe93a07..a058634 100644
--- a/lib/nanoc/base/compilation/dependency_tracker.rb
+++ b/lib/nanoc/base/compilation/dependency_tracker.rb
@@ -1,7 +1,6 @@
 # encoding: utf-8
 
 module Nanoc
-
   # Responsible for remembering dependencies between items and layouts. It is
   # used to speed up compilation by only letting an item be recompiled when it
   # is outdated or any of its dependencies (or dependencies’ dependencies,
@@ -20,7 +19,6 @@ module Nanoc
   #
   # @api private
   class DependencyTracker < ::Nanoc::Store
-
     # @return [Array<Nanoc::Item, Nanoc::Layout>] The list of items and
     #   layouts that are being tracked by the dependency tracker
     attr_reader :objects
@@ -37,7 +35,7 @@ module Nanoc
       super('tmp/dependencies', 4)
 
       @objects = objects
-      @graph   = Nanoc::DirectedGraph.new([ nil ] + @objects)
+      @graph   = Nanoc::DirectedGraph.new([nil] + @objects)
       @stack   = []
     end
 
@@ -53,7 +51,7 @@ module Nanoc
 
       # Register start of visits
       Nanoc::NotificationCenter.on(:visit_started, self) do |obj|
-        if !@stack.empty?
+        unless @stack.empty?
           Nanoc::NotificationCenter.post(:dependency_created, @stack.last, obj)
           record_dependency(@stack.last, obj)
         end
@@ -61,7 +59,7 @@ module Nanoc
       end
 
       # Register end of visits
-      Nanoc::NotificationCenter.on(:visit_ended, self) do |obj|
+      Nanoc::NotificationCenter.on(:visit_ended, self) do |_obj|
         @stack.pop
       end
     end
@@ -71,7 +69,7 @@ module Nanoc
     # @return [void]
     def stop
       # Sanity check
-      if !@stack.empty?
+      unless @stack.empty?
         raise 'Internal inconsistency: dependency tracker stack not empty at end of compilation'
       end
 
@@ -169,21 +167,21 @@ module Nanoc
 
     # @see Nanoc::Store#unload
     def unload
-      @graph = Nanoc::DirectedGraph.new([ nil ] + @objects)
+      @graph = Nanoc::DirectedGraph.new([nil] + @objects)
     end
 
-  protected
+    protected
 
     def data
       {
-        :edges    => @graph.edges,
-        :vertices => @graph.vertices.map { |obj| obj && obj.reference }
+        edges: @graph.edges,
+        vertices: @graph.vertices.map { |obj| obj && obj.reference }
       }
     end
 
     def data=(new_data)
       # Create new graph
-      @graph = Nanoc::DirectedGraph.new([ nil ] + @objects)
+      @graph = Nanoc::DirectedGraph.new([nil] + @objects)
 
       # Load vertices
       previous_objects = new_data[:vertices].map do |reference|
@@ -194,7 +192,7 @@ module Nanoc
       new_data[:edges].each do |edge|
         from_index, to_index = *edge
         from = from_index && previous_objects[from_index]
-        to   = to_index   && previous_objects[to_index]
+        to   = to_index && previous_objects[to_index]
         @graph.add_edge(from, to)
       end
 
@@ -207,7 +205,5 @@ module Nanoc
         end
       end
     end
-
   end
-
 end
diff --git a/lib/nanoc/base/compilation/filter.rb b/lib/nanoc/base/compilation/filter.rb
index 26c1426..16103a7 100644
--- a/lib/nanoc/base/compilation/filter.rb
+++ b/lib/nanoc/base/compilation/filter.rb
@@ -1,7 +1,6 @@
 # encoding: utf-8
 
 module Nanoc
-
   # Nanoc::Filter is responsible for filtering items. It is the superclass
   # for all textual filters.
   #
@@ -27,7 +26,6 @@ module Nanoc
   #
   # @abstract Subclass and override {#run} to implement a custom filter.
   class Filter < Context
-
     TMP_BINARY_ITEMS_DIR = 'binary_items'
 
     # A hash containing variables that will be made available during
@@ -39,7 +37,6 @@ module Nanoc
     extend Nanoc::PluginRegistry::PluginMethods
 
     class << self
-
       # Sets the new type for the filter. The type can be `:binary` (default)
       # or `:text`. The given argument can either be a symbol indicating both
       # “from” and “to” types, or a hash where the only key is the “from” type
@@ -100,7 +97,6 @@ module Nanoc
           true
         end
       end
-
     end
 
     # Creates a new filter that has access to the given assigns.
@@ -135,7 +131,7 @@ module Nanoc
     # @return [String, void] If the filter output binary content, the return
     #   value is undefined; if the filter outputs textual content, the return
     #   value will be the filtered content.
-    def run(content_or_filename, params = {})
+    def run(content_or_filename, params = {}) # rubocop:disable Lint/UnusedMethodArgument
       raise NotImplementedError.new('Nanoc::Filter subclasses must implement #run')
     end
 
@@ -186,7 +182,5 @@ module Nanoc
         raise Nanoc::Errors::UnmetDependency.new(rep) if rep
       end
     end
-
   end
-
 end
diff --git a/lib/nanoc/base/compilation/item_rep_proxy.rb b/lib/nanoc/base/compilation/item_rep_proxy.rb
index f3aa6e3..c3dce00 100644
--- a/lib/nanoc/base/compilation/item_rep_proxy.rb
+++ b/lib/nanoc/base/compilation/item_rep_proxy.rb
@@ -1,7 +1,6 @@
 # encoding: utf-8
 
 module Nanoc
-
   # Represents an item representation, but provides an interface that is
   # easier to use when writing compilation and routing rules. It is also
   # responsible for fetching the necessary information from the compiler, such
@@ -10,11 +9,10 @@ module Nanoc
   # The API provided by item representation proxies allows layout identifiers
   # to be given as literals instead of as references to {Nanoc::Layout}.
   class ItemRepProxy
-
     extend Forwardable
 
     def_delegators :@item_rep, :item, :name, :binary, :binary?, :compiled_content, :has_snapshot?, :raw_path, :path
-    def_delegator  :@item_rep, :snapshot
+    def_delegator :@item_rep, :snapshot
 
     # @param [Nanoc::ItemRep] item_rep The item representation that this
     #   proxy should behave like
@@ -76,13 +74,14 @@ module Nanoc
     #
     # @return [true]
     #
-    # @see Nanoc::ItemRep#is_proxy?
-    # @see Nanoc::ItemRepRecorderProxy#is_proxy?
-    def is_proxy?
+    # @see Nanoc::ItemRep#proxy?
+    # @see Nanoc::ItemRepRecorderProxy#proxy?
+    def proxy?
       true
     end
+    alias_method :is_proxy?, :proxy?
 
-  private
+    private
 
     def set_assigns
       @item_rep.assigns = @compiler.assigns_for(@item_rep)
@@ -97,7 +96,5 @@ module Nanoc
       raise Nanoc::Errors::UnknownLayout.new(layout_identifier) if layout.nil?
       layout
     end
-
   end
-
 end
diff --git a/lib/nanoc/base/compilation/item_rep_recorder_proxy.rb b/lib/nanoc/base/compilation/item_rep_recorder_proxy.rb
index 462322f..92afd11 100644
--- a/lib/nanoc/base/compilation/item_rep_recorder_proxy.rb
+++ b/lib/nanoc/base/compilation/item_rep_recorder_proxy.rb
@@ -1,7 +1,6 @@
 # encoding: utf-8
 
 module Nanoc
-
   # Represents a fake iem representation that does not actually perform any
   # actual filtering, layouting or snapshotting, but instead keeps track of
   # what would happen if a real item representation would have been used
@@ -15,7 +14,6 @@ module Nanoc
   #
   # @api private
   class ItemRepRecorderProxy
-
     extend Forwardable
 
     def_delegators :@item_rep, :item, :name, :binary, :binary?, :compiled_content, :has_snapshot?, :raw_path, :path, :assigns, :assigns=
@@ -50,7 +48,7 @@ module Nanoc
     #
     # @see Nanoc::ItemRepProxy#filter, Nanoc::ItemRep#filter
     def filter(name, args = {})
-      @rule_memory << [ :filter, name, args ]
+      @rule_memory << [:filter, name, args]
     end
 
     # @return [void]
@@ -58,9 +56,9 @@ module Nanoc
     # @see Nanoc::ItemRepProxy#layout, Nanoc::ItemRep#layout
     def layout(layout_identifier, extra_filter_args = nil)
       if extra_filter_args
-        @rule_memory << [ :layout, layout_identifier, extra_filter_args ]
+        @rule_memory << [:layout, layout_identifier, extra_filter_args]
       else
-        @rule_memory << [ :layout, layout_identifier ]
+        @rule_memory << [:layout, layout_identifier]
       end
     end
 
@@ -68,11 +66,11 @@ module Nanoc
     #
     # @see Nanoc::ItemRep#snapshot
     def snapshot(snapshot_name, params = {})
-      @rule_memory << [ :snapshot, snapshot_name, params ]
+      @rule_memory << [:snapshot, snapshot_name, params]
 
       # Count
       existing = Set.new
-      names = @rule_memory.select { |r| r[0] == :snapshot }.map { |r| r[2] }
+      names = @rule_memory.select { |r| r[0] == :snapshot }.map { |r| r[1] }
       names.each do |n|
         if existing.include?(n)
           raise Nanoc::Errors::CannotCreateMultipleSnapshotsWithSameName.new(@item_rep, snapshot_name)
@@ -91,12 +89,11 @@ module Nanoc
     #
     # @return [true]
     #
-    # @see Nanoc::ItemRep#is_proxy?
-    # @see Nanoc::ItemRepProxy#is_proxy?
-    def is_proxy?
+    # @see Nanoc::ItemRep#proxy?
+    # @see Nanoc::ItemRepProxy#proxy?
+    def proxy?
       true
     end
-
+    alias_method :is_proxy?, :proxy?
   end
-
 end
diff --git a/lib/nanoc/base/compilation/outdatedness_checker.rb b/lib/nanoc/base/compilation/outdatedness_checker.rb
index a3a650c..ccfeeb6 100644
--- a/lib/nanoc/base/compilation/outdatedness_checker.rb
+++ b/lib/nanoc/base/compilation/outdatedness_checker.rb
@@ -1,12 +1,10 @@
 # encoding: utf-8
 
 module Nanoc
-
   # Responsible for determining whether an item or a layout is outdated.
   #
   # @api private
   class OutdatednessChecker
-
     extend Nanoc::Memoization
 
     # @option params [Nanoc::Site] :site (nil) The site this outdatedness
@@ -60,7 +58,7 @@ module Nanoc
     end
     memoize :outdatedness_reason_for
 
-  private
+    private
 
     # Checks whether the given object is outdated and therefore needs to be
     # recompiled. This method does not take dependencies into account; use
@@ -92,8 +90,8 @@ module Nanoc
           rule_memory_differs_for(obj)
 
         # Outdated if checksums are missing or different
-        return Nanoc::OutdatednessReasons::NotEnoughData if !checksums_available?(obj.item)
-        return Nanoc::OutdatednessReasons::SourceModified if !checksums_identical?(obj.item)
+        return Nanoc::OutdatednessReasons::NotEnoughData unless checksums_available?(obj.item)
+        return Nanoc::OutdatednessReasons::SourceModified unless checksums_identical?(obj.item)
 
         # Outdated if compiled file doesn't exist (yet)
         return Nanoc::OutdatednessReasons::NotWritten if obj.raw_path && !File.file?(obj.raw_path)
@@ -116,8 +114,8 @@ module Nanoc
           rule_memory_differs_for(obj)
 
         # Outdated if checksums are missing or different
-        return Nanoc::OutdatednessReasons::NotEnoughData if !checksums_available?(obj)
-        return Nanoc::OutdatednessReasons::SourceModified if !checksums_identical?(obj)
+        return Nanoc::OutdatednessReasons::NotEnoughData unless checksums_available?(obj)
+        return Nanoc::OutdatednessReasons::SourceModified unless checksums_identical?(obj)
 
         # Not outdated
         return nil
@@ -179,7 +177,7 @@ module Nanoc
     # @return [Boolean] false if either the new or the old checksum for the
     #   given object is not available, true if both checksums are available
     def checksums_available?(obj)
-      !!checksum_store[obj] && obj.checksum
+      checksum_store[obj] && obj.checksum
     end
     memoize :checksums_available?
 
@@ -220,7 +218,5 @@ module Nanoc
     def site
       @site
     end
-
   end
-
 end
diff --git a/lib/nanoc/base/compilation/outdatedness_reasons.rb b/lib/nanoc/base/compilation/outdatedness_reasons.rb
index ef02bbf..a4683a1 100644
--- a/lib/nanoc/base/compilation/outdatedness_reasons.rb
+++ b/lib/nanoc/base/compilation/outdatedness_reasons.rb
@@ -1,14 +1,11 @@
 # encoding: utf-8
 
 module Nanoc
-
   # Module that contains all outdatedness reasons.
   module OutdatednessReasons
-
     # A generic outdatedness reason. An outdatedness reason is basically a
     # descriptive message that explains why a given object is outdated.
     class Generic
-
       # @return [String] A descriptive message for this outdatedness reason
       attr_reader :message
 
@@ -17,7 +14,6 @@ module Nanoc
       def initialize(message)
         @message = message
       end
-
     end
 
     CodeSnippetsModified = Generic.new(
@@ -40,7 +36,5 @@ module Nanoc
 
     SourceModified = Generic.new(
       'The source file of this item has been modified since the last time the site was compiled.')
-
   end
-
 end
diff --git a/lib/nanoc/base/compilation/rule.rb b/lib/nanoc/base/compilation/rule.rb
index d0c723e..d663322 100644
--- a/lib/nanoc/base/compilation/rule.rb
+++ b/lib/nanoc/base/compilation/rule.rb
@@ -1,10 +1,8 @@
 # encoding: utf-8
 
 module Nanoc
-
   # Contains the processing information for a item.
   class Rule
-
     # @return [Regexp] The regex that determines which items this rule can be
     #   applied to. This rule can be applied to items with a identifier
     #   matching this regex.
@@ -66,10 +64,22 @@ module Nanoc
       compiler = params.fetch(:compiler) do
         raise ArgumentError, 'Required :compiler option is missing'
       end
-      rep = Nanoc::ItemRepProxy.new(rep, compiler) unless rep.is_proxy?
-      Nanoc::RuleContext.new(:rep => rep, :compiler => compiler).instance_eval(&@block)
+      rep = Nanoc::ItemRepProxy.new(rep, compiler) unless rep.proxy?
+      context = Nanoc::RuleContext.new(rep: rep, compiler: compiler)
+      context.instance_exec(matches(rep.item.identifier), &@block)
     end
 
-  end
+    protected
 
+    # Matches the rule regexp against items identifier and gives back group
+    # captures if any
+    #
+    # @param [String] identifier Identifier to capture groups for
+    #
+    # @return [nil, Array] Captured groups, if any
+    def matches(identifier)
+      matches = @identifier_regex.match(identifier)
+      matches && matches.captures
+    end
+  end
 end
diff --git a/lib/nanoc/base/compilation/rule_context.rb b/lib/nanoc/base/compilation/rule_context.rb
index 48fbe12..bf883f8 100644
--- a/lib/nanoc/base/compilation/rule_context.rb
+++ b/lib/nanoc/base/compilation/rule_context.rb
@@ -1,7 +1,6 @@
 # encoding: utf-8
 
 module Nanoc
-
   # Provides a context in which compilation and routing rules can be executed.
   # It provides access to the item representation that is being compiled or
   # routed.
@@ -17,7 +16,6 @@ module Nanoc
   #
   # @api private
   class RuleContext < Context
-
     # @option params [Nanoc::ItemRep] :rep The item representation that will
     #   be processed in this rule context
     #
@@ -35,13 +33,13 @@ module Nanoc
       end
 
       super({
-        :rep      => rep,
-        :item_rep => rep,
-        :item     => rep.item,
-        :site     => compiler.site,
-        :config   => compiler.site.config,
-        :items    => compiler.site.items,
-        :layouts  => compiler.site.layouts
+        rep: rep,
+        item_rep: rep,
+        item: rep.item,
+        site: compiler.site,
+        config: compiler.site.config,
+        items: compiler.site.items,
+        layouts: compiler.site.layouts
       })
     end
 
@@ -85,7 +83,5 @@ module Nanoc
     def snapshot(snapshot_name)
       rep.snapshot(snapshot_name)
     end
-
   end
-
 end
diff --git a/lib/nanoc/base/compilation/rule_memory_calculator.rb b/lib/nanoc/base/compilation/rule_memory_calculator.rb
index 0c0cbce..99b2f1e 100644
--- a/lib/nanoc/base/compilation/rule_memory_calculator.rb
+++ b/lib/nanoc/base/compilation/rule_memory_calculator.rb
@@ -1,13 +1,11 @@
 # encoding: utf-8
 
 module Nanoc
-
   # Calculates rule memories for objects that can be run through a rule (item
   # representations and layouts).
   #
   # @api private
   class RuleMemoryCalculator
-
     extend Nanoc::Memoization
 
     # @option params [Nanoc::RulesCollection] rules_collection The rules
@@ -35,7 +33,5 @@ module Nanoc
       result
     end
     memoize :[]
-
   end
-
 end
diff --git a/lib/nanoc/base/compilation/rule_memory_store.rb b/lib/nanoc/base/compilation/rule_memory_store.rb
index 4006bea..3c07370 100644
--- a/lib/nanoc/base/compilation/rule_memory_store.rb
+++ b/lib/nanoc/base/compilation/rule_memory_store.rb
@@ -1,13 +1,11 @@
 # encoding: utf-8
 
 module Nanoc
-
   # Stores rule memories for objects that can be run through a rule (item
   # representations and layouts).
   #
   # @api private
   class RuleMemoryStore < ::Nanoc::Store
-
     # @option params [Nanoc::Site] site The site where this rule memory store
     #   belongs to
     def initialize(params = {})
@@ -36,7 +34,7 @@ module Nanoc
       @rule_memories[obj.reference] = rule_memory
     end
 
-  protected
+    protected
 
     # @see Nanoc::Store#data
     def data
@@ -47,7 +45,5 @@ module Nanoc
     def data=(new_data)
       @rule_memories = new_data
     end
-
   end
-
 end
diff --git a/lib/nanoc/base/compilation/rules_collection.rb b/lib/nanoc/base/compilation/rules_collection.rb
index f1ee36b..3e95d50 100644
--- a/lib/nanoc/base/compilation/rules_collection.rb
+++ b/lib/nanoc/base/compilation/rules_collection.rb
@@ -1,12 +1,10 @@
 # encoding: utf-8
 
 module Nanoc
-
   # Keeps track of the rules in a site.
   #
   # @api private
   class RulesCollection
-
     # @return [String] the contents of the Rules file
     #
     # @api private
@@ -41,8 +39,8 @@ module Nanoc
 
       @item_compilation_rules = []
       @item_routing_rules     = []
-      @layout_filter_mapping  = OrderedHash.new
-      @preprocessors          = OrderedHash.new
+      @layout_filter_mapping  = {}
+      @preprocessors          = {}
     end
 
     # Add the given rule to the list of item compilation rules.
@@ -76,7 +74,7 @@ module Nanoc
     # @return [void]
     def load
       # Find rules file
-      rules_filenames = [ 'Rules', 'rules', 'Rules.rb', 'rules.rb' ]
+      rules_filenames = ['Rules', 'rules', 'Rules.rb', 'rules.rb']
       rules_filename = rules_filenames.find { |f| File.file?(f) }
       raise Nanoc::Errors::NoRulesFileFound.new if rules_filename.nil?
 
@@ -101,8 +99,8 @@ module Nanoc
     def unload
       @item_compilation_rules = []
       @item_routing_rules     = []
-      @layout_filter_mapping  = OrderedHash.new
-      @preprocessors          = OrderedHash.new
+      @layout_filter_mapping  = {}
+      @preprocessors          = {}
     end
 
     # Finds the first matching compilation rule for the given item
@@ -141,7 +139,7 @@ module Nanoc
     def routing_rules_for(rep)
       rules = {}
       @item_routing_rules.each do |rule|
-        next if !rule.applicable_to?(rep.item)
+        next unless rule.applicable_to?(rep.item)
         next if rule.rep_name != rep.name
         next if rules.key?(rule.snapshot_name)
 
@@ -192,8 +190,8 @@ module Nanoc
     # @return [Array] The rule memory for the given item representation
     def new_rule_memory_for_rep(rep)
       recording_proxy = rep.to_recording_proxy
-      compilation_rule_for(rep).apply_to(recording_proxy, :compiler => @compiler)
-      recording_proxy.rule_memory << [ :write, rep.path ]
+      compilation_rule_for(rep).apply_to(recording_proxy, compiler: @compiler)
+      recording_proxy.rule_memory << [:write, rep.path]
       make_rule_memory_serializable(recording_proxy.rule_memory)
     end
     memoize :new_rule_memory_for_rep
@@ -208,7 +206,7 @@ module Nanoc
     def make_rule_memory_serializable(rs)
       rs.map do |r|
         if r[0] == :filter
-          [ r[0], r[1], r[2].to_a.map { |a| Nanoc::Checksummer.calc(a) }  ]
+          [r[0], r[1], r[2].to_a.map { |a| Nanoc::Checksummer.calc(a) }]
         else
           r
         end
@@ -231,7 +229,7 @@ module Nanoc
     #   a Boolean indicating whether the snapshot is final or not
     def snapshots_for(rep)
       new_rule_memory_for_rep(rep).select { |e| e[0] == :snapshot }.map do |e|
-        [ e[1], e[2].fetch(:final) { true } ]
+        [e[1], e[2].fetch(:final) { true }]
       end
     end
 
@@ -253,7 +251,5 @@ module Nanoc
     def rule_memory_calculator
       @compiler.rule_memory_calculator
     end
-
   end
-
 end
diff --git a/lib/nanoc/base/context.rb b/lib/nanoc/base/context.rb
index c681ec4..7e251c4 100644
--- a/lib/nanoc/base/context.rb
+++ b/lib/nanoc/base/context.rb
@@ -1,11 +1,9 @@
 # encoding: utf-8
 
 module Nanoc
-
   # Provides a context and a binding for use in filters such as the ERB and
   # Haml ones.
   class Context
-
     # Creates a new context based off the contents of the hash.
     #
     # Each pair in the hash will be converted to an instance variable and an
@@ -31,7 +29,7 @@ module Nanoc
         instance_variable_set('@' + key.to_s, value)
 
         # Define method
-        metaclass = (class << self ; self ; end)
+        metaclass = (class << self; self; end)
         metaclass.send(:define_method, key) { value }
       end
     end
@@ -42,6 +40,5 @@ module Nanoc
     def get_binding
       binding
     end
-
   end
 end
diff --git a/lib/nanoc/base/core_ext.rb b/lib/nanoc/base/core_ext.rb
index 8ffc249..a3afb42 100644
--- a/lib/nanoc/base/core_ext.rb
+++ b/lib/nanoc/base/core_ext.rb
@@ -1,7 +1,6 @@
 # encoding: utf-8
 
 require 'nanoc/base/core_ext/array'
-require 'nanoc/base/core_ext/date'
 require 'nanoc/base/core_ext/hash'
 require 'nanoc/base/core_ext/pathname'
 require 'nanoc/base/core_ext/string'
diff --git a/lib/nanoc/base/core_ext/array.rb b/lib/nanoc/base/core_ext/array.rb
index f582564..5b78c95 100644
--- a/lib/nanoc/base/core_ext/array.rb
+++ b/lib/nanoc/base/core_ext/array.rb
@@ -1,7 +1,6 @@
 # encoding: utf-8
 
 module Nanoc::ArrayExtensions
-
   # Returns a new array where all items' keys are recursively converted to
   # symbols by calling {Nanoc::ArrayExtensions#symbolize_keys_recursively} or
   # {Nanoc::HashExtensions#symbolize_keys_recursively}.
@@ -27,7 +26,7 @@ module Nanoc::ArrayExtensions
   # @return [Array] The converted array
   def stringify_keys_recursively
     reduce([]) do |array, element|
-      array + [ element.respond_to?(:stringify_keys_recursively) ? element.stringify_keys_recursively : element ]
+      array + [element.respond_to?(:stringify_keys_recursively) ? element.stringify_keys_recursively : element]
     end
   end
 
@@ -66,7 +65,6 @@ module Nanoc::ArrayExtensions
   def checksum
     Nanoc::Checksummer.calc(self)
   end
-
 end
 
 class Array
diff --git a/lib/nanoc/base/core_ext/date.rb b/lib/nanoc/base/core_ext/date.rb
deleted file mode 100644
index 5e32c30..0000000
--- a/lib/nanoc/base/core_ext/date.rb
+++ /dev/null
@@ -1,29 +0,0 @@
-# encoding: utf-8
-
-require 'date'
-
-begin
-  d = ::Date.today
-  d.freeze
-  d.year
-  needs_patch = false
-rescue
-  needs_patch = true
-end
-
-if needs_patch
-
-  class ::Date
-
-    [ :amjd, :jd, :day_fraction, :mjd, :ld, :civil, :ordinal, :commercial, :weeknum0, :weeknum1, :time, :wday, :julian?, :gregorian?, :leap? ].each do |m|
-      module_eval <<EOS
-        alias_method :__orig_#{m}, :#{m}
-        def #{m}
-          self.frozen? ? self.dup.#{m} : self.send(:__orig_#{m})
-        end
-EOS
-    end
-
-  end
-
-end
diff --git a/lib/nanoc/base/core_ext/hash.rb b/lib/nanoc/base/core_ext/hash.rb
index d4476cf..ebb4932 100644
--- a/lib/nanoc/base/core_ext/hash.rb
+++ b/lib/nanoc/base/core_ext/hash.rb
@@ -1,7 +1,6 @@
 # encoding: utf-8
 
 module Nanoc::HashExtensions
-
   # Returns a new hash where all keys are recursively converted to symbols by
   # calling {Nanoc::ArrayExtensions#symbolize_keys_recursively} or
   # {Nanoc::HashExtensions#symbolize_keys_recursively}.
@@ -50,7 +49,7 @@ module Nanoc::HashExtensions
   def freeze_recursively
     return if self.frozen?
     freeze
-    each_pair do |key, value|
+    each_pair do |_key, value|
       if value.respond_to?(:freeze_recursively)
         value.freeze_recursively
       else
@@ -68,7 +67,6 @@ module Nanoc::HashExtensions
   def checksum
     Nanoc::Checksummer.calc(self)
   end
-
 end
 
 class Hash
diff --git a/lib/nanoc/base/core_ext/pathname.rb b/lib/nanoc/base/core_ext/pathname.rb
index f427fa8..f522513 100644
--- a/lib/nanoc/base/core_ext/pathname.rb
+++ b/lib/nanoc/base/core_ext/pathname.rb
@@ -1,7 +1,6 @@
 # encoding: utf-8
 
 module Nanoc::PathnameExtensions
-
   # Calculates the checksum for the file referenced to by this pathname. Any
   # change to the file contents will result in a different checksum.
   #
@@ -11,7 +10,6 @@ module Nanoc::PathnameExtensions
   def checksum
     Nanoc::Checksummer.calc(self)
   end
-
 end
 
 class Pathname
diff --git a/lib/nanoc/base/core_ext/string.rb b/lib/nanoc/base/core_ext/string.rb
index da4d3c5..c8bcbdb 100644
--- a/lib/nanoc/base/core_ext/string.rb
+++ b/lib/nanoc/base/core_ext/string.rb
@@ -1,7 +1,6 @@
 # encoding: utf-8
 
 module Nanoc::StringExtensions
-
   # Transforms string into an actual identifier
   #
   # @return [String] The identifier generated from the receiver
@@ -18,7 +17,6 @@ module Nanoc::StringExtensions
   def checksum
     Nanoc::Checksummer.calc(self)
   end
-
 end
 
 class String
diff --git a/lib/nanoc/base/directed_graph.rb b/lib/nanoc/base/directed_graph.rb
index 56067d2..ed1e0e6 100644
--- a/lib/nanoc/base/directed_graph.rb
+++ b/lib/nanoc/base/directed_graph.rb
@@ -1,7 +1,6 @@
 # encoding: utf-8
 
 module Nanoc
-
   # Represents a directed graph. It is used by the dependency tracker for
   # storing and querying dependencies between items.
   #
@@ -30,7 +29,6 @@ module Nanoc
   #   graph.predecessors_of('d').sort
   #     # => %w( b c )
   class DirectedGraph
-
     # @group Creating a graph
 
     # Creates a new directed graph with the given vertices.
@@ -211,7 +209,7 @@ module Nanoc
       result = []
       @vertices.each_pair do |v1, i1|
         direct_successors_of(v1).map { |v2| @vertices[v2] }.each do |i2|
-          result << [ i1, i2 ]
+          result << [i1, i2]
         end
       end
       result
@@ -233,7 +231,7 @@ module Nanoc
       delete_edge(from, to)
     end
 
-  private
+    private
 
     # Invalidates cached data. This method should be called when the internal
     # graph representation is changed.
@@ -249,7 +247,7 @@ module Nanoc
       all_vertices = Set.new
 
       processed_vertices   = Set.new
-      unprocessed_vertices = [ start ]
+      unprocessed_vertices = [start]
 
       until unprocessed_vertices.empty?
         # Get next unprocessed vertex
@@ -266,7 +264,5 @@ module Nanoc
 
       all_vertices.to_a
     end
-
   end
-
 end
diff --git a/lib/nanoc/base/errors.rb b/lib/nanoc/base/errors.rb
index 05ef43c..d46b368 100644
--- a/lib/nanoc/base/errors.rb
+++ b/lib/nanoc/base/errors.rb
@@ -1,10 +1,8 @@
 # encoding: utf-8
 
 module Nanoc
-
   # Module that contains all nanoc-specific errors.
   module Errors
-
     # Generic error. Superclass for all nanoc-specific errors.
     class Generic < ::StandardError
     end
@@ -17,103 +15,86 @@ module Nanoc
     # Error that is raised when a site is loaded that uses a data source with
     # an unknown identifier.
     class UnknownDataSource < Generic
-
       # @param [String] data_source_name The data source name for which no
       #   data source could be found
       def initialize(data_source_name)
         super("The data source specified in the site’s configuration file, “#{data_source_name}”, does not exist.")
       end
-
     end
 
     # Error that is raised during site compilation when an item uses a layout
     # that is not present in the site.
     class UnknownLayout < Generic
-
       # @param [String] layout_identifier The layout identifier for which no
       #   layout could be found
       def initialize(layout_identifier)
         super("The site does not have a layout with identifier “#{layout_identifier}”.")
       end
-
     end
 
     # Error that is raised during site compilation when an item uses a filter
     # that is not known.
     class UnknownFilter < Generic
-
       # @param [Symbol] filter_name The filter name for which no filter could
       #   be found
       def initialize(filter_name)
         super("The requested filter, “#{filter_name}”, does not exist.")
       end
-
     end
 
     # Error that is raised during site compilation when a layout is compiled
     # for which the filter cannot be determined. This is similar to the
     # {UnknownFilter} error, but specific for filters for layouts.
     class CannotDetermineFilter < Generic
-
       # @param [String] layout_identifier The identifier of the layout for
       #   which the filter could not be determined
       def initialize(layout_identifier)
         super("The filter to be used for the “#{layout_identifier}” layout could not be determined. Make sure the layout does have a filter.")
       end
-
     end
 
     # Error that is raised during site compilation when an item (directly or
     # indirectly) includes its own item content, leading to endless recursion.
     class RecursiveCompilation < Generic
-
       # @param [Array<Nanoc::ItemRep>] reps A list of item representations
       #   that mutually depend on each other
       def initialize(reps)
-        list = reps.map { |r| r.inspect }.join("\n")
+        list = reps.map(&:inspect).join("\n")
         super("The site cannot be compiled because the following items mutually depend on each other:\n#{list}.")
       end
-
     end
 
     # Error that is raised when no rules file can be found in the current
     # working directory.
     class NoRulesFileFound < Generic
-
       def initialize
         super('This site does not have a rules file, which is required for nanoc sites.')
       end
-
     end
 
     # Error that is raised when no compilation rule that can be applied to the
     # current item can be found.
     class NoMatchingCompilationRuleFound < Generic
-
       # @param [Nanoc::Item] item The item for which no compilation rule
       #   could be found
       def initialize(item)
         super("No compilation rules were found for the “#{item.identifier}” item.")
       end
-
     end
 
     # Error that is raised when no routing rule that can be applied to the
     # current item can be found.
     class NoMatchingRoutingRuleFound < Generic
-
       # @param [Nanoc::ItemRep] rep The item repiresentation for which no
       #   routing rule could be found
       def initialize(rep)
         super("No routing rules were found for the “#{rep.item.identifier}” item (rep “#{rep.name}”).")
       end
-
     end
 
     # Error that is raised when an rep cannot be compiled because it depends
     # on other representations.
     class UnmetDependency < Generic
-
       # @return [Nanoc::ItemRep] The item representation that cannot yet be
       #   compiled
       attr_reader :rep
@@ -124,24 +105,20 @@ module Nanoc
         @rep = rep
         super("The current item cannot be compiled yet because of an unmet dependency on the “#{rep.item.identifier}” item (rep “#{rep.name}”).")
       end
-
     end
 
     # Error that is raised when a binary item is attempted to be laid out.
     class CannotLayoutBinaryItem < Generic
-
       # @param [Nanoc::ItemRep] rep The item representation that was attempted
       #   to be laid out
       def initialize(rep)
         super("The “{rep.item.identifier}” item (rep “#{rep.name}”) cannot be laid out because it is a binary item. If you are getting this error for an item that should be textual instead of binary, make sure that its extension is included in the text_extensions array in the site configuration.")
       end
-
     end
 
     # Error that is raised when a textual filter is attempted to be applied to
     # a binary item representation.
     class CannotUseTextualFilter < Generic
-
       # @param [Nanoc::ItemRep] rep The item representation that was
       #   attempted to be filtered
       #
@@ -149,13 +126,11 @@ module Nanoc
       def initialize(rep, filter_class)
         super("The “#{filter_class.inspect}” filter cannot be used to filter the “#{rep.item.identifier}” item (rep “#{rep.name}”), because textual filters cannot be used on binary items.")
       end
-
     end
 
     # Error that is raised when a binary filter is attempted to be applied to
     # a textual item representation.
     class CannotUseBinaryFilter < Generic
-
       # @param [Nanoc::ItemRep] rep The item representation that was
       #   attempted to be filtered
       #
@@ -163,13 +138,11 @@ module Nanoc
       def initialize(rep, filter_class)
         super("The “#{filter_class.inspect}” filter cannot be used to filter the “#{rep.item.identifier}” item (rep “#{rep.name}”), because binary filters cannot be used on textual items. If you are getting this error for an item that should be textual instead of binary, make sure that its extension is included in the text_extensions array in the site configuration.")
       end
-
     end
 
     # Error that is raised when the compiled content at a non-existing snapshot
     # is requested.
     class NoSuchSnapshot < Generic
-
       # @return [Nanoc::ItemRep] The item rep from which the compiled content
       #   was requested
       attr_reader :item_rep
@@ -185,12 +158,10 @@ module Nanoc
         @item_rep, @snapshot = item_rep, snapshot
         super("The “#{item_rep.inspect}” item rep does not have a snapshot “#{snapshot.inspect}”")
       end
-
     end
 
     # Error that is raised when a snapshot with an existing name is made.
     class CannotCreateMultipleSnapshotsWithSameName < Generic
-
       # @param [Nanoc::ItemRep] rep The item representation for which a
       #   snapshot was attempted to be made
       #
@@ -199,37 +170,28 @@ module Nanoc
       def initialize(rep, snapshot)
         super("Attempted to create a snapshot with a duplicate name #{snapshot.inspect} for the item rep “#{rep.inspect}”")
       end
-
     end
 
     # Error that is raised when the compiled content of a binary item is attempted to be accessed.
     class CannotGetCompiledContentOfBinaryItem < Generic
-
       # @param [Nanoc::ItemRep] rep The binary item representation whose compiled content was attempted to be accessed
       def initialize(rep)
         super("You cannot access the compiled content of a binary item representation (but you can access the path). The offending item rep is #{rep.inspect}.")
       end
-
     end
 
     # @deprecated No longer necessary, but kept for backwards compatibility.
     class DataNotYetAvailable < Generic
-
       def initialize(type, plural)
         super("#{type} #{plural ? 'are' : 'is'} not available yet. You may be missing a Nanoc::Site#load_data call.")
       end
-
     end
 
     # Error that is raised when multiple items or layouts with the same identifier exist.
     class DuplicateIdentifier < Generic
-
       def initialize(identifier, type)
         super("There are multiple #{type}s with the #{identifier} identifier.")
       end
-
     end
-
   end
-
 end
diff --git a/lib/nanoc/base/memoization.rb b/lib/nanoc/base/memoization.rb
index d816b89..6d0040c 100644
--- a/lib/nanoc/base/memoization.rb
+++ b/lib/nanoc/base/memoization.rb
@@ -1,12 +1,10 @@
 # encoding: utf-8
 
 module Nanoc
-
   # Adds support for memoizing functions.
   #
   # @since 3.2.0
   module Memoization
-
     # Memoizes the method with the given name. The modified method will cache
     # the results of the original method, so that calling a method twice with
     # the same arguments will short-circuit and return the cached results
@@ -52,7 +50,7 @@ module Nanoc
         @__memoization_cache[method_name] ||= {}
 
         # Recalculate if necessary
-        if !@__memoization_cache[method_name].key?(args)
+        unless @__memoization_cache[method_name].key?(args)
           result = send(original_method_name, *args)
           @__memoization_cache[method_name][args] = result
         end
@@ -61,7 +59,5 @@ module Nanoc
         @__memoization_cache[method_name][args]
       end
     end
-
   end
-
 end
diff --git a/lib/nanoc/base/notification_center.rb b/lib/nanoc/base/notification_center.rb
index 3009ab5..843acda 100644
--- a/lib/nanoc/base/notification_center.rb
+++ b/lib/nanoc/base/notification_center.rb
@@ -1,7 +1,6 @@
 # encoding: utf-8
 
 module Nanoc
-
   # Provides a way to send notifications between objects. It allows blocks
   # associated with a certain notification name to be registered; these blocks
   # will be called when the notification with the given name is posted.
@@ -10,9 +9,7 @@ module Nanoc
   # table of subscribers is not stored in the observable object itself, but in
   # the notification center.
   class NotificationCenter
-
     class << self
-
       # Adds the given block to the list of blocks that should be called when
       # the notification with the given name is received.
       #
@@ -31,7 +28,7 @@ module Nanoc
         initialize_if_necessary(name)
 
         # Add observer
-        @notifications[name] << { :id => id, :block => block }
+        @notifications[name] << { id: id, block: block }
       end
 
       # Posts a notification with the given name and the given arguments.
@@ -70,15 +67,12 @@ module Nanoc
         @notifications[name].reject! { |i| i[:id] == id }
       end
 
-    private
+      private
 
       def initialize_if_necessary(name)
         @notifications ||= {}       # name => observers dictionary
         @notifications[name] ||= [] # list of observers
       end
-
     end
-
   end
-
 end
diff --git a/lib/nanoc/base/ordered_hash.rb b/lib/nanoc/base/ordered_hash.rb
deleted file mode 100644
index dafabb7..0000000
--- a/lib/nanoc/base/ordered_hash.rb
+++ /dev/null
@@ -1,228 +0,0 @@
-# encoding: utf-8
-
-# AUTHOR
-#    jan molic /mig/at/1984/dot/cz/
-#
-# DESCRIPTION
-#    Hash with preserved order and some array-like extensions
-#    Public domain.
-#
-# THANKS
-#    Andrew Johnson for his suggestions and fixes of Hash[],
-#    merge, to_a, inspect and shift
-class OrderedHash < ::Hash
-  attr_accessor :order
-
-  class << self
-    def [](*args)
-      hsh = OrderedHash.new
-      if args[0].is_a?(Hash)
-        hsh.replace args[0]
-      elsif args.size.odd?
-        raise ArgumentError, 'odd number of elements for Hash'
-      else
-        0.step(args.size - 1, 2) do |a|
-          b = a + 1
-          hsh[args[a]] = args[b]
-        end
-      end
-      hsh
-    end
-  end
-
-  def initialize(*a, &b)
-    super
-    @order = []
-  end
-
-  def store_only(a, b)
-    store a, b
-  end
-  alias_method :orig_store, :store
-
-  def store(a, b)
-    @order.push a unless key? a
-    super a, b
-  end
-  alias_method :[]=, :store
-
-  def ==(other)
-    return false if @order != other.order
-    super other
-  end
-
-  def clear
-    @order = []
-    super
-  end
-
-  def delete(key)
-    @order.delete key
-    super
-  end
-
-  def each_key
-    @order.each { |k| yield k }
-    self
-  end
-
-  def each_value
-    @order.each { |k| yield self[k] }
-    self
-  end
-
-  def each
-    @order.each { |k| yield k, self[k] }
-    self
-  end
-  alias_method :each_pair, :each
-
-  def delete_if
-    @order.clone.each do |k|
-      delete k if yield(k)
-    end
-    self
-  end
-
-  def values
-    ary = []
-    @order.each { |k| ary.push self[k] }
-    ary
-  end
-
-  def keys
-    @order
-  end
-
-  def first
-    { @order.first => self[@order.first] }
-  end
-
-  def last
-    { @order.last => self[@order.last] }
-  end
-
-  def invert
-    hsh2 = {}
-    @order.each { |k| hsh2[self[k]] = k }
-    hsh2
-  end
-
-  def reject(&block)
-    dup.delete_if(&block)
-  end
-
-  def reject!(&block)
-    hsh2 = reject(&block)
-    self == hsh2 ? nil : hsh2
-  end
-
-  def replace(hsh2)
-    @order = hsh2.keys
-    super hsh2
-  end
-
-  def shift
-    key = @order.first
-    key ? [key, delete(key)] : super
-  end
-
-  def unshift(k, v)
-    if self.include? k
-      false
-    else
-      @order.unshift k
-      orig_store(k, v)
-      true
-    end
-  end
-
-  def push(k, v)
-    if self.include? k
-      false
-    else
-      @order.push k
-      orig_store(k, v)
-      true
-    end
-  end
-
-  def pop
-    key = @order.last
-    key ? [key, delete(key)] : nil
-  end
-
-  def to_a
-    ary = []
-    each { |k, v| ary << [k, v] }
-    ary
-  end
-
-  def to_s
-    to_a.to_s
-  end
-
-  def inspect
-    ary = []
-    each { |k, v| ary << k.inspect + '=>' + v.inspect }
-    '{' + ary.join(', ') + '}'
-  end
-
-  def update(hsh2)
-    hsh2.each { |k, v| self[k] = v }
-    self
-  end
-  alias_method :merge!, :update
-
-  def merge(hsh2)
-    dup update(hsh2)
-  end
-
-  def select
-    ary = []
-    each { |k, v| ary << [k, v] if yield k, v }
-    ary
-  end
-
-  def class
-    Hash
-  end
-
-  def __class__
-    OrderedHash
-  end
-
-  attr_accessor 'to_yaml_style'
-  def yaml_inline=(bool)
-    if respond_to?('to_yaml_style')
-      self.to_yaml_style = :inline
-    else
-      unless defined? @__yaml_inline_meth
-        @__yaml_inline_meth =
-        lambda do |opts|
-          YAML.quick_emit(object_id, opts) do |emitter|
-            emitter << '{ ' << map { |kv| kv.join ': ' }.join(', ') << ' }'
-          end
-        end
-        class << self
-          def to_yaml(opts = {})
-            @__yaml_inline ? @__yaml_inline_meth[ opts ] : super
-          rescue
-            @to_yaml_style = :inline
-            super
-          end
-        end
-      end
-    end
-    @__yaml_inline = bool
-  end
-
-  def yaml_inline!
-    self.yaml_inline = true
-  end
-
-  def each_with_index
-    @order.each_with_index { |k, index| yield k, self[k], index }
-    self
-  end
-end # class OrderedHash
diff --git a/lib/nanoc/base/plugin_registry.rb b/lib/nanoc/base/plugin_registry.rb
index 1961d1b..5a09ca1 100644
--- a/lib/nanoc/base/plugin_registry.rb
+++ b/lib/nanoc/base/plugin_registry.rb
@@ -1,19 +1,16 @@
 # encoding: utf-8
 
 module Nanoc
-
   # The class responsible for keeping track of all loaded plugins, such as
   # filters ({Nanoc::Filter}), data sources ({Nanoc::DataSource}) and VCSes
   # ({Nanoc::Extra::VCS}).
   class PluginRegistry
-
     extend Nanoc::Memoization
 
     # A module that contains class methods for plugins. It provides functions
     # for setting identifiers, registering plugins and finding plugins. Plugin
     # classes should extend this module.
     module PluginMethods
-
       # @overload identifiers(*identifiers)
       #
       #   Sets the identifiers for this plugin.
@@ -86,7 +83,6 @@ module Nanoc
       def named(name)
         Nanoc::Plugin.find(self, name)
       end
-
     end
 
     # Returns the shared {PluginRegistry} instance, creating it if none exists
@@ -182,13 +178,13 @@ module Nanoc
           if existing_plugin
             # Add identifier to existing plugin
             existing_plugin[:identifiers] << identifier
-            existing_plugin[:identifiers] = existing_plugin[:identifiers].sort_by { |s| s.to_s }
+            existing_plugin[:identifiers] = existing_plugin[:identifiers].sort_by(&:to_s)
           else
             # Create new plugin
             plugins << {
-              :class       => klass,
-              :superclass  => superclass,
-              :identifiers => [ identifier ]
+              class: klass,
+              superclass: superclass,
+              identifiers: [identifier]
             }
           end
         end
@@ -202,9 +198,9 @@ module Nanoc
       find(self, name)
     end
 
-  protected
+    protected
 
-    def resolve(class_or_name, klass)
+    def resolve(class_or_name, _klass)
       if class_or_name.is_a?(String)
         class_or_name.scan(/\w+/).reduce(Kernel) do |memo, part|
           memo.const_get(part)
@@ -218,10 +214,8 @@ module Nanoc
     def name_for_class(klass)
       klass.to_s.sub(/^(::)?/, '::')
     end
-
   end
 
   # @deprecated Use {Nanoc::PluginRegistry.instance} instead
   Plugin = PluginRegistry.instance
-
 end
diff --git a/lib/nanoc/base/result_data/item_rep.rb b/lib/nanoc/base/result_data/item_rep.rb
index a7010f1..1049bf1 100644
--- a/lib/nanoc/base/result_data/item_rep.rb
+++ b/lib/nanoc/base/result_data/item_rep.rb
@@ -1,16 +1,13 @@
 # encoding: utf-8
 
 module Nanoc
-
   # A single representation (rep) of an item ({Nanoc::Item}). An item can
   # have multiple representations. A representation has its own output file.
   # A single item can therefore have multiple output files, each run through
   # a different set of filters with a different layout.
   class ItemRep
-
     # Contains all deprecated methods. Mixed into {Nanoc::ItemRep}.
     module Deprecated
-
       # @deprecated Modify the {#raw_paths} attribute instead
       def raw_path=(raw_path)
         raw_paths[:last] = raw_path
@@ -23,7 +20,7 @@ module Nanoc
 
       # @deprecated Use {Nanoc::ItemRep#compiled_content} instead.
       def content_at_snapshot(snapshot = :pre)
-        compiled_content(:snapshot => snapshot)
+        compiled_content(snapshot: snapshot)
       end
 
       # @deprecated
@@ -55,12 +52,10 @@ module Nanoc
       def written?
         raise NotImplementedError, 'Nanoc::ItemRep#written? is no longer implemented'
       end
-
     end
 
     # Contains all private methods. Mixed into {Nanoc::ItemRep}.
     module Private
-
       # @return [Hash] A hash containing the assigns that will be used in the
       #   next filter or layout operation. The keys (symbols) will be made
       #   available during the next operation.
@@ -120,7 +115,7 @@ module Nanoc
       # @return [void]
       def write(snapshot = :last)
         # Get raw path
-        raw_path = self.raw_path(:snapshot => snapshot)
+        raw_path = self.raw_path(snapshot: snapshot)
         return if raw_path.nil?
 
         # Create parent directory
@@ -175,21 +170,20 @@ module Nanoc
       def type
         :item_rep
       end
-
     end
 
     include Deprecated
     include Private
 
     # @return [Nanoc::Item] The item to which this rep belongs
-    attr_reader   :item
+    attr_reader :item
 
     # @return [Symbol] The representation's unique name
-    attr_reader   :name
+    attr_reader :name
 
     # @return [Boolean] true if this rep is currently binary; false otherwise
-    attr_reader   :binary
-    alias_method  :binary?, :binary
+    attr_reader :binary
+    alias_method :binary?, :binary
 
     # @return [Array] A list of snapshots, represented as arrays where the
     #   first element is the snapshot name (a Symbol) and the last element is
@@ -241,20 +235,29 @@ module Nanoc
       Nanoc::NotificationCenter.post(:visit_ended,   item)
 
       # Get name of last pre-layout snapshot
-      snapshot = params.fetch(:snapshot) { @content[:pre] ? :pre : :last }
-      is_moving = [ :pre, :post, :last ].include?(snapshot)
+      snapshot_name = params.fetch(:snapshot) { @content[:pre] ? :pre : :last }
+      is_moving = [:pre, :post, :last].include?(snapshot_name)
 
       # Check existance of snapshot
-      if !is_moving && snapshots.find { |s| s.first == snapshot && s.last == true }.nil?
-        raise Nanoc::Errors::NoSuchSnapshot.new(self, snapshot)
+      snapshot = snapshots.find { |s| s.first == snapshot_name }
+      if !is_moving && (snapshot.nil? || snapshot[-1] == false)
+        raise Nanoc::Errors::NoSuchSnapshot.new(self, snapshot_name)
       end
 
-      # Require compilation
-      if @content[snapshot].nil? || (!self.compiled? && is_moving)
+      # Verify snapshot is usable
+      is_still_moving =
+        case snapshot_name
+        when :post, :last
+          true
+        when :pre
+          snapshot.nil? || !snapshot[-1]
+        end
+      is_usable_snapshot = @content[snapshot_name] && (self.compiled? || !is_still_moving)
+      unless is_usable_snapshot
         raise Nanoc::Errors::UnmetDependency.new(self)
-      else
-        @content[snapshot]
       end
+
+      @content[snapshot_name]
     end
 
     # Checks whether content exists at a given snapshot.
@@ -263,9 +266,10 @@ module Nanoc
     #   given name, false otherwise
     #
     # @since 3.2.0
-    def has_snapshot?(snapshot_name)
+    def snapshot?(snapshot_name)
       !@content[snapshot_name].nil?
     end
+    alias_method :has_snapshot?, :snapshot?
 
     # Returns the item rep’s raw path. It includes the path to the output
     # directory and the full filename.
@@ -351,7 +355,7 @@ module Nanoc
         end
 
         # Create snapshot
-        snapshot(@content[:post] ? :post : :pre, :final => false) unless self.binary?
+        snapshot(@content[:post] ? :post : :pre, final: false) unless self.binary?
       ensure
         # Notify end
         Nanoc::NotificationCenter.post(:filtering_ended, self, filter_name)
@@ -382,13 +386,13 @@ module Nanoc
 
       # Create "pre" snapshot
       if @content[:post].nil?
-        snapshot(:pre, :final => true)
+        snapshot(:pre, final: true)
       end
 
       # Create filter
       klass = filter_named(filter_name)
       raise Nanoc::Errors::UnknownFilter.new(filter_name) if klass.nil?
-      filter = klass.new(assigns.merge({ :layout => layout }))
+      filter = klass.new(assigns.merge({ layout: layout }))
 
       # Visit
       Nanoc::NotificationCenter.post(:visit_started, layout)
@@ -403,7 +407,7 @@ module Nanoc
         @content[:last] = filter.setup_and_run(layout.raw_content, filter_args)
 
         # Create "post" snapshot
-        snapshot(:post, :final => false)
+        snapshot(:post, final: false)
       ensure
         # Notify end
         Nanoc::NotificationCenter.post(:filtering_ended,  self, filter_name)
@@ -421,8 +425,16 @@ module Nanoc
     #
     # @return [void]
     def snapshot(snapshot_name, params = {})
-      is_final = params.fetch(:final) { true }
-      @content[snapshot_name] = @content[:last] unless self.binary?
+      is_final = params.fetch(:final, true)
+
+      unless self.binary?
+        @content[snapshot_name] = @content[:last]
+      end
+
+      if snapshot_name == :pre && is_final
+        snapshots << [:pre, true]
+      end
+
       write(snapshot_name) if is_final
     end
 
@@ -444,11 +456,12 @@ module Nanoc
     #
     # @return [false]
     #
-    # @see Nanoc::ItemRepRecorderProxy#is_proxy?
-    # @see Nanoc::ItemRepProxy#is_proxy?
-    def is_proxy?
+    # @see Nanoc::ItemRepRecorderProxy#proxy?
+    # @see Nanoc::ItemRepProxy#proxy?
+    def proxy?
       false
     end
+    alias_method :is_proxy?, :proxy?
 
     # Returns an object that can be used for uniquely identifying objects.
     #
@@ -456,22 +469,22 @@ module Nanoc
     #
     # @return [Object] An unique reference to this object
     def reference
-      [ type, item.identifier, name ]
+      [type, item.identifier, name]
     end
 
     def inspect
       "<#{self.class} name=\"#{name}\" binary=#{self.binary?} raw_path=\"#{raw_path}\" item.identifier=\"#{item.identifier}\">"
     end
 
-  private
+    private
 
     def initialize_content
       # Initialize content and filenames
       if self.binary?
-        @temporary_filenames = { :last => @item.raw_filename }
+        @temporary_filenames = { last: @item.raw_filename }
         @content             = {}
       else
-        @content             = { :last => @item.raw_content }
+        @content             = { last: @item.raw_content }
         @content[:last].freeze
         @temporary_filenames = {}
       end
@@ -480,7 +493,5 @@ module Nanoc
     def filter_named(name)
       Nanoc::Filter.named(name)
     end
-
   end
-
 end
diff --git a/lib/nanoc/base/source_data/code_snippet.rb b/lib/nanoc/base/source_data/code_snippet.rb
index 8611e19..8b66070 100644
--- a/lib/nanoc/base/source_data/code_snippet.rb
+++ b/lib/nanoc/base/source_data/code_snippet.rb
@@ -1,10 +1,8 @@
 # encoding: utf-8
 
 module Nanoc
-
   # Nanoc::CodeSnippet represent a piece of custom code of a nanoc site.
   class CodeSnippet
-
     # A string containing the actual code in this code snippet.
     #
     # @return [String]
@@ -22,9 +20,9 @@ module Nanoc
     #
     # @param [String] filename The filename corresponding to this code snippet
     #
-    # @param [Time, Hash] params Extra parameters. Ignored by nanoc; it is
+    # @param [Time, Hash] _params Extra parameters. Ignored by nanoc; it is
     #   only included for backwards compatibility.
-    def initialize(data, filename, params = nil)
+    def initialize(data, filename, _params = nil)
       @data     = data
       @filename = filename
     end
@@ -40,7 +38,7 @@ module Nanoc
     #
     # @return [Object] An unique reference to this object
     def reference
-      [ :code_snippet, filename ]
+      [:code_snippet, filename]
     end
 
     def inspect
@@ -52,7 +50,5 @@ module Nanoc
     def checksum
       Nanoc::Checksummer.calc(self)
     end
-
   end
-
 end
diff --git a/lib/nanoc/base/source_data/configuration.rb b/lib/nanoc/base/source_data/configuration.rb
index ebdcfec..a289d5e 100644
--- a/lib/nanoc/base/source_data/configuration.rb
+++ b/lib/nanoc/base/source_data/configuration.rb
@@ -1,10 +1,8 @@
 # encoding: utf-8
 
 module Nanoc
-
   # Represents the site configuration.
   class Configuration < ::Hash
-
     # Creates a new configuration with the given hash.
     #
     # @param [Hash] hash The actual configuration hash
@@ -18,7 +16,5 @@ module Nanoc
     def reference
       :config
     end
-
   end
-
 end
diff --git a/lib/nanoc/base/source_data/data_source.rb b/lib/nanoc/base/source_data/data_source.rb
index 703b912..2f19fbd 100644
--- a/lib/nanoc/base/source_data/data_source.rb
+++ b/lib/nanoc/base/source_data/data_source.rb
@@ -1,7 +1,6 @@
 # encoding: utf-8
 
 module Nanoc
-
   # Responsible for loading site data. It is the (abstract) superclass for all
   # data sources. Subclasses must at least implement the data reading methods
   # ({#items} and {#layouts}); all other methods involving data manipulation
@@ -26,7 +25,6 @@ module Nanoc
   #   {#create_item} and {#create_layout} methods should be implemented as
   #   well.
   class DataSource
-
     # @return [String] The root path where items returned by this data source
     #   should be mounted.
     attr_reader :items_root
@@ -207,7 +205,7 @@ module Nanoc
     #   files that should be generated.
     #
     # @return [void]
-    def create_item(content, attributes, identifier, params = {})
+    def create_item(content, attributes, identifier, params = {}) # rubocop:disable Lint/UnusedMethodArgument
       not_implemented('create_item')
     end
 
@@ -230,17 +228,16 @@ module Nanoc
     #   files that should be generated.
     #
     # @return [void]
-    def create_layout(content, attributes, identifier, params = {})
+    def create_layout(content, attributes, identifier, params = {}) # rubocop:disable Lint/UnusedMethodArgument
       not_implemented('create_layout')
     end
 
-  private
+    private
 
     def not_implemented(name)
       raise NotImplementedError.new(
         "#{self.class} does not implement ##{name}"
       )
     end
-
   end
 end
diff --git a/lib/nanoc/base/source_data/item.rb b/lib/nanoc/base/source_data/item.rb
index 160a82a..25fa65a 100644
--- a/lib/nanoc/base/source_data/item.rb
+++ b/lib/nanoc/base/source_data/item.rb
@@ -1,12 +1,10 @@
 # encoding: utf-8
 
 module Nanoc
-
   # Represents a compileable item in a site. It has content and attributes, as
   # well as an identifier (which starts and ends with a slash). It can also
   # store the modification time to speed up compilation.
   class Item
-
     extend Nanoc::Memoization
 
     # @return [Hash] This item's attributes
@@ -26,15 +24,15 @@ module Nanoc
     attr_accessor :identifier
 
     # @return [Array<Nanoc::ItemRep>] This item’s list of item reps
-    attr_reader   :reps
+    attr_reader :reps
 
     # @return [String] This item's raw, uncompiled content of this item (only
     #   available for textual items)
-    attr_reader   :raw_content
+    attr_reader :raw_content
 
     # @return [String] The filename pointing to the file containing this
     #   item’s content
-    attr_reader   :raw_filename
+    attr_reader :raw_filename
 
     # @return [Nanoc::Site] The site this item belongs to
     attr_accessor :site
@@ -70,7 +68,7 @@ module Nanoc
     def initialize(raw_content_or_raw_filename, attributes, identifier, params = nil)
       # Parse params
       params ||= {}
-      params = { :mtime => params } if params.is_a?(Time)
+      params = { mtime: params } if params.is_a?(Time)
       params[:binary] = false unless params.key?(:binary)
 
       if raw_content_or_raw_filename.nil?
@@ -91,7 +89,7 @@ module Nanoc
       @identifier   = identifier.cleaned_identifier.freeze
 
       # Set mtime
-      @attributes.merge!(:mtime => params[:mtime]) if params[:mtime]
+      @attributes.merge!(mtime: params[:mtime]) if params[:mtime]
 
       @parent       = nil
       @children     = []
@@ -172,10 +170,10 @@ module Nanoc
       Nanoc::NotificationCenter.post(:visit_ended,   self)
 
       # Get captured content (hax)
-      # TODO [in nanoc 4.0] remove me
+      # TODO: [in nanoc 4.0] remove me
       if key.to_s =~ /^content_for_(.*)$/
         @@_content_for_warning_issued ||= false
-        @@_Nanoc_Helpers_Capturing_included ||= false
+        @@_capturing_helper_included ||= false
 
         # Warn
         unless @@_content_for_warning_issued
@@ -184,9 +182,9 @@ module Nanoc
         end
 
         # Include capturing helper if necessary
-        unless @@_Nanoc_Helpers_Capturing_included
+        unless @@_capturing_helper_included
           self.class.send(:include, ::Nanoc::Helpers::Capturing)
-          @@_Nanoc_Helpers_Capturing_included = true
+          @@_capturing_helper_included = true
         end
 
         # Get content
@@ -207,7 +205,7 @@ module Nanoc
 
     # @return [Boolean] True if the item is binary; false if it is not
     def binary?
-      !!@is_binary
+      @is_binary
     end
 
     # Returns the type of this object. Will always return `:item`, because
@@ -226,7 +224,7 @@ module Nanoc
     #
     # @return [Object] An unique reference to this object
     def reference
-      [ type, identifier ]
+      [type, identifier]
     end
 
     # Prevents all further modifications to its attributes.
@@ -295,7 +293,5 @@ module Nanoc
     def mtime
       self[:mtime]
     end
-
   end
-
 end
diff --git a/lib/nanoc/base/source_data/item_array.rb b/lib/nanoc/base/source_data/item_array.rb
index ca90378..6b3de09 100644
--- a/lib/nanoc/base/source_data/item_array.rb
+++ b/lib/nanoc/base/source_data/item_array.rb
@@ -1,10 +1,8 @@
 # encoding: utf-8
 
 module Nanoc
-
   # Acts as an array, but allows fetching items using identifiers, e.g. `@items['/blah/']`.
   class ItemArray
-
     include Enumerable
 
     extend Forwardable
@@ -18,7 +16,7 @@ module Nanoc
       :instance_eval, :instance_exec, :__send__, :__id__
     ]
 
-    DELEGATED_METHODS = (Array.instance_methods + Enumerable.instance_methods).map { |m| m.to_sym } - EXCLUDED_METHODS
+    DELEGATED_METHODS = (Array.instance_methods + Enumerable.instance_methods).map(&:to_sym) - EXCLUDED_METHODS
     def_delegators :@items, *DELEGATED_METHODS
 
     def initialize
@@ -34,6 +32,8 @@ module Nanoc
     def [](*args)
       if 1 == args.size && args.first.is_a?(String)
         item_with_identifier(args.first)
+      elsif 1 == args.size && args.first.is_a?(Regexp)
+        @items.select { |i| i.identifier =~ args.first }
       else
         @items[*args]
       end
@@ -48,7 +48,7 @@ module Nanoc
       end
     end
 
-  protected
+    protected
 
     def item_with_identifier(identifier)
       if self.frozen?
@@ -65,7 +65,5 @@ module Nanoc
       end
       @mapping.freeze
     end
-
   end
-
 end
diff --git a/lib/nanoc/base/source_data/layout.rb b/lib/nanoc/base/source_data/layout.rb
index 9bfe7bf..f9b6e9c 100644
--- a/lib/nanoc/base/source_data/layout.rb
+++ b/lib/nanoc/base/source_data/layout.rb
@@ -1,11 +1,9 @@
 # encoding: utf-8
 
 module Nanoc
-
   # Represents a layout in a nanoc site. It has content, attributes, an
   # identifier and a modification time (to speed up compilation).
   class Layout
-
     extend Nanoc::Memoization
 
     # @return [String] The raw content of this layout
@@ -40,8 +38,8 @@ module Nanoc
 
       # Set mtime
       params ||= {}
-      params = { :mtime => params } if params.is_a?(Time)
-      @attributes.merge(:mtime => params[:mtime]) if params[:mtime]
+      params = { mtime: params } if params.is_a?(Time)
+      @attributes.merge(mtime: params[:mtime]) if params[:mtime]
     end
 
     # Requests the attribute with the given key.
@@ -78,7 +76,7 @@ module Nanoc
     #
     # @return [Object] An unique reference to this object
     def reference
-      [ type, identifier ]
+      [type, identifier]
     end
 
     def inspect
@@ -122,7 +120,5 @@ module Nanoc
     def mtime
       self[:mtime]
     end
-
   end
-
 end
diff --git a/lib/nanoc/base/source_data/site.rb b/lib/nanoc/base/source_data/site.rb
index 9c4564a..f3cd8a0 100644
--- a/lib/nanoc/base/source_data/site.rb
+++ b/lib/nanoc/base/source_data/site.rb
@@ -1,7 +1,6 @@
 # encoding: utf-8
 
 module Nanoc
-
   # The in-memory representation of a nanoc site. It holds references to the
   # following site data:
   #
@@ -17,14 +16,13 @@ module Nanoc
   # that contains a configuration file, site data, a rakefile, a rules file,
   # etc. The way site data is stored depends on the data source.
   class Site
-
     # The default configuration for a data source. A data source's
     # configuration overrides these options.
     DEFAULT_DATA_SOURCE_CONFIG = {
-      :type         => 'filesystem_unified',
-      :items_root   => '/',
-      :layouts_root => '/',
-      :config       => {}
+      type: 'filesystem_unified',
+      items_root: '/',
+      layouts_root: '/',
+      config: {}
     }
 
     # The default configuration for a site. A site's configuration overrides
@@ -32,13 +30,14 @@ module Nanoc
     # that lacks some options, the default value will be taken from
     # `DEFAULT_CONFIG`.
     DEFAULT_CONFIG = {
-      :text_extensions    => %w( css erb haml htm html js less markdown md php rb sass scss txt xhtml xml coffee hb handlebars mustache ms slim ).sort,
-      :lib_dirs           => %w( lib ),
-      :output_dir         => 'output',
-      :data_sources       => [ {} ],
-      :index_filenames    => [ 'index.html' ],
-      :enable_output_diff => false,
-      :prune              => { :auto_prune => false, :exclude => [ '.git', '.hg', '.svn', 'CVS' ] }
+      text_extensions: %w( css erb haml htm html js less markdown md php rb sass scss txt xhtml xml coffee hb handlebars mustache ms slim ).sort,
+      lib_dirs: %w( lib ),
+      commands_dirs: %w( commands ),
+      output_dir: 'output',
+      data_sources: [{}],
+      index_filenames: ['index.html'],
+      enable_output_diff: false,
+      prune: { auto_prune: false, exclude: ['.git', '.hg', '.svn', 'CVS'] }
     }
 
     # Creates a site object for the site specified by the given
@@ -85,7 +84,7 @@ module Nanoc
           raise Nanoc::Errors::UnknownDataSource.new(data_source_hash[:type]) if data_source_class.nil?
 
           # Warn about deprecated data sources
-          # TODO [in nanoc 4.0] remove me
+          # TODO: [in nanoc 4.0] remove me
           case data_source_hash[:type]
           when 'filesystem'
             warn "Warning: the 'filesystem' data source has been renamed to 'filesystem_verbose'. Using 'filesystem' will work in nanoc 3.1.x, but it will likely not work anymore in a future release of nanoc. Please update your data source configuration and replace 'filesystem' with 'filesystem_verbose'."
@@ -189,14 +188,14 @@ module Nanoc
 
       @items.each do |item|
         parent_id_end = item.identifier.rindex('/', -2)
-        if parent_id_end
-          parent_id = item.identifier[0..parent_id_end]
-          parent = item_map[parent_id]
-          if parent
-            item.parent = parent
-            parent.children << item
-          end
-        end
+        next unless parent_id_end
+
+        parent_id = item.identifier[0..parent_id_end]
+        parent = item_map[parent_id]
+        next unless parent
+
+        item.parent = parent
+        parent.children << item
       end
     end
 
@@ -217,14 +216,14 @@ module Nanoc
     # @return [void]
     def freeze
       config.freeze_recursively
-      items.each         { |i|  i.freeze  }
-      layouts.each       { |l|  l.freeze  }
-      code_snippets.each { |cs| cs.freeze }
+      items.each(&:freeze)
+      layouts.each(&:freeze)
+      code_snippets.each(&:freeze)
     end
 
     # @deprecated It is no longer necessary to explicitly load site data. It
     #   is safe to remove all {#load_data} calls.
-    def load_data(force = false)
+    def load_data(_force = false)
       warn 'It is no longer necessary to call Nanoc::Site#load_data. This method no longer has any effect. All calls to this method can be safely removed.'
     end
 
@@ -240,10 +239,10 @@ module Nanoc
 
       # Load all data
       load_code_snippets
-      data_sources.each { |ds| ds.use }
-      load_items
-      load_layouts
-      data_sources.each { |ds| ds.unuse }
+      with_datasources do
+        load_items
+        load_layouts
+      end
       setup_child_parent_links
 
       # Ensure unique
@@ -251,7 +250,7 @@ module Nanoc
       ensure_identifier_uniqueness(@layouts, 'layout')
 
       # Load compiler too
-      # FIXME this should not be necessary
+      # FIXME: this should not be necessary
       compiler.load
 
       @loaded = true
@@ -297,7 +296,16 @@ module Nanoc
       filenames.find { |f| File.file?(f) }
     end
 
-  private
+    private
+
+    # Executes the given block, making sure that the datasources are
+    # available for the duration of the block
+    def with_datasources(&_block)
+      data_sources.each(&:use)
+      yield
+    ensure
+      data_sources.each(&:unuse)
+    end
 
     # Loads this site’s code and executes it.
     def load_code_snippets
@@ -318,7 +326,7 @@ module Nanoc
       end
 
       # Execute code snippets
-      @code_snippets.each { |cs| cs.load }
+      @code_snippets.each(&:load)
     end
 
     # Loads this site’s items, sets up item child-parent relationships and
@@ -365,7 +373,7 @@ module Nanoc
       if parent_config_file
         config.delete(:parent_config_file)
         config_path = File.absolute_path(parent_config_file, File.dirname(config_paths.last))
-        if !File.file?(config_path)
+        unless File.file?(config_path)
           raise Nanoc::Errors::GenericTrivial, "Could not find parent configuration file '#{parent_config_file}'"
         end
         if config_paths.include?(config_path)
@@ -422,5 +430,4 @@ module Nanoc
       @config = Nanoc::Configuration.new(@config)
     end
   end
-
 end
diff --git a/lib/nanoc/base/store.rb b/lib/nanoc/base/store.rb
index 900a9ea..f048757 100644
--- a/lib/nanoc/base/store.rb
+++ b/lib/nanoc/base/store.rb
@@ -1,7 +1,6 @@
 # encoding: utf-8
 
 module Nanoc
-
   # An abstract superclass for classes that need to store data to the
   # filesystem, such as checksums, cached compiled content and dependency
   # graphs.
@@ -15,7 +14,6 @@ module Nanoc
   #
   # @api private
   class Store
-
     # @return [String] The name of the file where data will be loaded from and
     #   stored to.
     attr_reader :filename
@@ -52,7 +50,7 @@ module Nanoc
     # @abstract This method must be implemented by the subclass.
     #
     # @return [void]
-    def data=(new_data)
+    def data=(new_data) # rubocop:disable Lint/UnusedMethodArgument
       raise NotImplementedError.new('Nanoc::Store subclasses must implement #data and #data=')
     end
 
@@ -67,7 +65,7 @@ module Nanoc
       end
 
       # Check file existance
-      if !File.file?(filename)
+      unless File.file?(filename)
         no_data_found
         @loaded = true
         return
@@ -129,12 +127,10 @@ module Nanoc
     def version_mismatch_detected
     end
 
-  private
+    private
 
     def pstore
       @pstore ||= PStore.new(filename)
     end
-
   end
-
 end
diff --git a/lib/nanoc/base/temp_filename_factory.rb b/lib/nanoc/base/temp_filename_factory.rb
index 9154709..89953ef 100644
--- a/lib/nanoc/base/temp_filename_factory.rb
+++ b/lib/nanoc/base/temp_filename_factory.rb
@@ -3,9 +3,7 @@
 require 'tmpdir'
 
 module Nanoc
-
   class TempFilenameFactory
-
     # @return [String] The root directory for all temporary filenames
     attr_reader :root_dir
 
@@ -42,16 +40,14 @@ module Nanoc
     def cleanup(prefix)
       path = File.join(@root_dir, prefix)
       if File.exist?(path)
-        FileUtils.remove_entry_secure(path)
+        FileUtils.rm_rf(path)
       end
 
       @counts.delete(prefix)
 
       if @counts.empty? && File.directory?(@root_dir)
-        FileUtils.remove_entry_secure(@root_dir)
+        FileUtils.rm_rf(@root_dir)
       end
     end
-
   end
-
 end
diff --git a/lib/nanoc/cli.rb b/lib/nanoc/cli.rb
index 6c7416f..e8147dd 100644
--- a/lib/nanoc/cli.rb
+++ b/lib/nanoc/cli.rb
@@ -16,7 +16,6 @@ if Nanoc.on_windows?
 end
 
 module Nanoc::CLI
-
   module Commands
   end
 
@@ -28,7 +27,7 @@ module Nanoc::CLI
   autoload 'ErrorHandler',        'nanoc/cli/error_handler'
 
   # Deprecated; use CommandRunner instead
-  # TODO [in nanoc 4.0] remove me
+  # TODO: [in nanoc 4.0] remove me
   autoload 'Command',             'nanoc/cli/command_runner'
 
   # @return [Boolean] true if debug output is enabled, false if not
@@ -48,9 +47,9 @@ module Nanoc::CLI
     @debug = boolean
   end
 
-  # Invokes the nanoc commandline tool with the given arguments.
+  # Invokes the nanoc command-line tool with the given arguments.
   #
-  # @param [Array<String>] args An array of commandline arguments
+  # @param [Array<String>] args An array of command-line arguments
   #
   # @return [void]
   def self.run(args)
@@ -60,7 +59,7 @@ module Nanoc::CLI
     end
   end
 
-  # @return [Cri::Command] The root command, i.e. the commandline tool itself
+  # @return [Cri::Command] The root command, i.e. the command-line tool itself
   def self.root_command
     @root_command
   end
@@ -78,20 +77,20 @@ module Nanoc::CLI
   #
   # @return [void]
   def self.after_setup(&block)
-    # TODO decide what should happen if the CLI is already set up
-    self.add_after_setup_proc(block)
+    # TODO: decide what should happen if the CLI is already set up
+    add_after_setup_proc(block)
   end
 
-protected
+  protected
 
-  # Makes the commandline interface ready for use.
+  # Makes the command-line interface ready for use.
   #
   # @return [void]
   def self.setup
     setup_cleaning_streams
     setup_commands
     load_custom_commands
-    after_setup_procs.each { |b| b.call }
+    after_setup_procs.each(&:call)
   end
 
   # Sets up the root command and base subcommands.
@@ -118,16 +117,25 @@ protected
     end
   end
 
-  # Loads site-specific commands in `commands/`.
+  # Loads site-specific commands.
   #
   # @return [void]
   def self.load_custom_commands
-    recursive_contents_of('commands').each do |filename|
+    if Nanoc::Site.cwd_is_nanoc_site?
+      site = Nanoc::Site.new('.')
+      site.config[:commands_dirs].each do |path|
+        load_commands_at(path)
+      end
+    end
+  end
+
+  def self.load_commands_at(path)
+    recursive_contents_of(path).each do |filename|
       # Create command
       command = Nanoc::CLI.load_command_at(filename)
 
       # Get supercommand
-      pieces = filename.gsub(/^commands\/|\.rb$/, '').split('/')
+      pieces = filename.gsub(/^#{path}\/|\.rb$/, '').split('/')
       pieces = pieces[0, pieces.size - 1] || []
       root = Nanoc::CLI.root_command
       supercommand = pieces.reduce(root) do |cmd, piece|
@@ -177,11 +185,11 @@ protected
   def self.wrap_in_cleaning_stream(io)
     cio = ::Nanoc::CLI::CleaningStream.new(io)
 
-    if !self.enable_utf8?(io)
+    unless self.enable_utf8?(io)
       cio.add_stream_cleaner(Nanoc::CLI::StreamCleaners::UTF8)
     end
 
-    if !self.enable_ansi_colors?(io)
+    unless self.enable_ansi_colors?(io)
       cio.add_stream_cleaner(Nanoc::CLI::StreamCleaners::ANSIColors)
     end
 
@@ -198,14 +206,14 @@ protected
 
   # @return [Boolean] true if UTF-8 support is present, false if not
   def self.enable_utf8?(io)
-    return true if !io.tty?
+    return true unless io.tty?
 
     %w( LC_ALL LC_CTYPE LANG ).any? { |e| ENV[e] =~ /UTF/i }
   end
 
   # @return [Boolean] true if color support is present, false if not
   def self.enable_ansi_colors?(io)
-    if !io.tty?
+    unless io.tty?
       return false
     end
 
@@ -224,5 +232,4 @@ protected
     @after_setup_procs ||= []
     @after_setup_procs << proc
   end
-
 end
diff --git a/lib/nanoc/cli/ansi_string_colorizer.rb b/lib/nanoc/cli/ansi_string_colorizer.rb
index 6cc8c46..d604a06 100644
--- a/lib/nanoc/cli/ansi_string_colorizer.rb
+++ b/lib/nanoc/cli/ansi_string_colorizer.rb
@@ -1,18 +1,16 @@
 # encoding: utf-8
 
 module Nanoc::CLI
-
   # A simple ANSI colorizer for strings. When given a string and a list of
   # attributes, it returns a colorized string.
   module ANSIStringColorizer
-
-    # TODO complete mapping
+    # TODO: complete mapping
     MAPPING = {
-      :bold   => "\e[1m",
-      :red    => "\e[31m",
-      :green  => "\e[32m",
-      :yellow => "\e[33m",
-      :blue   => "\e[34m"
+      bold: "\e[1m",
+      red: "\e[31m",
+      green: "\e[32m",
+      yellow: "\e[33m",
+      blue: "\e[34m"
     }
 
     # @param [String] s The string to colorize
@@ -24,7 +22,5 @@ module Nanoc::CLI
     def self.c(s, *as)
       as.map { |a| MAPPING[a] }.join('') + s + "\e[0m"
     end
-
   end
-
 end
diff --git a/lib/nanoc/cli/cleaning_stream.rb b/lib/nanoc/cli/cleaning_stream.rb
index bf18231..0c581ee 100644
--- a/lib/nanoc/cli/cleaning_stream.rb
+++ b/lib/nanoc/cli/cleaning_stream.rb
@@ -1,11 +1,9 @@
 # encoding: utf-8
 
 module Nanoc::CLI
-
   # An output stream that passes output through stream cleaners. This can be
   # used to strip ANSI color sequences, for instance.
   class CleaningStream
-
     # @param [IO, StringIO] stream The stream to wrap
     def initialize(stream)
       @stream = stream
@@ -20,7 +18,7 @@ module Nanoc::CLI
     #
     # @return [void]
     def add_stream_cleaner(klass)
-      unless @stream_cleaners.map { |c| c.class }.include?(klass)
+      unless @stream_cleaners.map(&:class).include?(klass)
         @stream_cleaners << klass.new
       end
     end
@@ -119,17 +117,15 @@ module Nanoc::CLI
       @stream.winsize = (arg)
     end
 
-  protected
+    protected
 
     def _nanoc_clean(s)
-      @stream_cleaners.reduce(s) { |m, c| c.clean(m) }
+      @stream_cleaners.reduce(s) { |a, e| e.clean(a) }
     end
 
     def _nanoc_swallow_broken_pipe_errors_while
       yield
     rescue Errno::EPIPE
     end
-
   end
-
 end
diff --git a/lib/nanoc/cli/command_runner.rb b/lib/nanoc/cli/command_runner.rb
index e735ce7..b04bc3f 100644
--- a/lib/nanoc/cli/command_runner.rb
+++ b/lib/nanoc/cli/command_runner.rb
@@ -1,16 +1,14 @@
 # encoding: utf-8
 
 module Nanoc::CLI
-
   # A command runner subclass for nanoc commands that adds nanoc-specific
   # convenience methods and error handling.
   class CommandRunner < ::Cri::CommandRunner
-
     # @see http://rubydoc.info/gems/cri/Cri/CommandRunner#call-instance_method
     #
     # @return [void]
     def call
-      Nanoc::CLI::ErrorHandler.handle_while(:command => self) do
+      Nanoc::CLI::ErrorHandler.handle_while(command: self) do
         run
       end
     end
@@ -38,9 +36,10 @@ module Nanoc::CLI
 
     # @return [Boolean] true if the current working directory is a nanoc site
     #   directory, false otherwise
-    def is_in_site_dir?
+    def in_site_dir?
       Nanoc::Site.cwd_is_nanoc_site?
     end
+    alias_method :is_in_site_dir?, :in_site_dir?
 
     # Asserts that the current working directory contains a site
     # ({Nanoc::Site} instance). If no site is present, prints an error
@@ -71,7 +70,7 @@ module Nanoc::CLI
       Nanoc::CLI.debug?
     end
 
-  protected
+    protected
 
     # Sets the data source's VCS to the VCS with the given name. Does nothing
     # when the site's data source does not support VCSes (i.e. does not
@@ -92,7 +91,7 @@ module Nanoc::CLI
 
       site.data_sources.each do |data_source|
         # Skip if not possible
-        next if !data_source.respond_to?(:vcs=)
+        next unless data_source.respond_to?(:vcs=)
 
         # Set VCS
         data_source.vcs = vcs_class.new
@@ -103,10 +102,8 @@ module Nanoc::CLI
     def stack
       (site && site.compiler.stack) || []
     end
-
   end
 
   # @deprecated Use {Nanoc::CLI::CommandRunner} instead
   Command = CommandRunner
-
 end
diff --git a/lib/nanoc/cli/commands/autocompile.rb b/lib/nanoc/cli/commands/autocompile.rb
index 9e763fe..eb7e9ff 100644
--- a/lib/nanoc/cli/commands/autocompile.rb
+++ b/lib/nanoc/cli/commands/autocompile.rb
@@ -1,11 +1,11 @@
 # encoding: utf-8
 
-usage       'autocompile [options]'
-summary     'start the autocompiler'
+usage 'autocompile [options]'
+summary 'start the autocompiler'
 be_hidden
-aliases     :aco
+aliases :aco
 description <<-EOS
-Start the autocompiler web server. Unless overridden with commandline options
+Start the autocompiler web server. Unless overridden with command-line options
 or configuration entries, the web server will run on port 3000 and listen on all
 IP addresses. Running the autocompiler requires the `mime/types` and `rack` gems.
 
@@ -24,9 +24,7 @@ required :o, :host,    'specify the host to listen on (default: 0.0.0.0)'
 required :p, :port,    'specify the port to listen on (default: 3000)'
 
 module Nanoc::CLI::Commands
-
   class AutoCompile < ::Nanoc::CLI::CommandRunner
-
     def run
       warn 'WARNING: The `autocompile` command is deprecated. Please consider using `guard-nanoc` instead (see https://github.com/nanoc/guard-nanoc).'
 
@@ -38,8 +36,8 @@ module Nanoc::CLI::Commands
 
       # Set options
       options_for_rack = {
-        :Port      => (options[:port] || autocompile_config[:port] || 3000).to_i,
-        :Host      => (options[:host] || autocompile_config[:host] || '0.0.0.0')
+        Port: (options[:port] || autocompile_config[:port] || 3000).to_i,
+        Host: (options[:host] || autocompile_config[:host] || '0.0.0.0')
       }
 
       # Guess which handler we should use
@@ -65,9 +63,7 @@ module Nanoc::CLI::Commands
       puts "Running on http://#{options_for_rack[:Host]}:#{options_for_rack[:Port]}/"
       handler.run(app, options_for_rack)
     end
-
   end
-
 end
 
 runner Nanoc::CLI::Commands::AutoCompile
diff --git a/lib/nanoc/cli/commands/check.rb b/lib/nanoc/cli/commands/check.rb
index dbcf348..b6d716d 100644
--- a/lib/nanoc/cli/commands/check.rb
+++ b/lib/nanoc/cli/commands/check.rb
@@ -1,7 +1,7 @@
 # encoding: utf-8
 
-usage       'check [options] [names]'
-summary     'run issue checks'
+usage 'check [options] [names]'
+summary 'run issue checks'
 description "
 Run issue checks on the current site. If the `--all` option is passed, all available issue checks will be run. If the `--deploy` option is passed, the issue checks marked for deployment will be run.
 "
@@ -11,9 +11,7 @@ flag :L, :list,   'list all checks'
 flag :d, :deploy, 'run checks for deployment'
 
 module Nanoc::CLI::Commands
-
   class Check < ::Nanoc::CLI::CommandRunner
-
     def run
       validate_options_and_arguments
       require_site
@@ -39,7 +37,7 @@ module Nanoc::CLI::Commands
       end
     end
 
-  protected
+    protected
 
     def validate_options_and_arguments
       if arguments.empty? && !options[:all] && !options[:deploy] && !options[:list]
@@ -47,9 +45,7 @@ module Nanoc::CLI::Commands
           'nothing to do (pass either --all, --deploy or --list or a list of checks)'
       end
     end
-
   end
-
 end
 
 runner Nanoc::CLI::Commands::Check
diff --git a/lib/nanoc/cli/commands/compile.rb b/lib/nanoc/cli/commands/compile.rb
index 67d2243..82e4ebb 100644
--- a/lib/nanoc/cli/commands/compile.rb
+++ b/lib/nanoc/cli/commands/compile.rb
@@ -1,7 +1,7 @@
 # encoding: utf-8
 
-usage       'compile [options]'
-summary     'compile items of this site'
+usage 'compile [options]'
+summary 'compile items of this site'
 description <<-EOS
 Compile all items of the current site.
 
@@ -21,9 +21,7 @@ option :a, :all,   '(ignored)'
 option :f, :force, '(ignored)'
 
 module Nanoc::CLI::Commands
-
   class Compile < ::Nanoc::CLI::CommandRunner
-
     extend Nanoc::Memoization
 
     # Listens to compilation events and reacts to them. This abstract class
@@ -32,8 +30,7 @@ module Nanoc::CLI::Commands
     #
     # @abstract Subclasses must override {#start} and may override {#stop}.
     class Listener
-
-      def initialize(params = {})
+      def initialize(_params = {})
       end
 
       # @param [Nanoc::CLI::CommandRunner] command_runner The command runner for this listener
@@ -41,7 +38,7 @@ module Nanoc::CLI::Commands
       # @return [Boolean] true if this listener should be enabled for the given command runner, false otherwise
       #
       # @abstract Returns `true` by default, but subclasses may override this.
-      def self.enable_for?(command_runner)
+      def self.enable_for?(command_runner) # rubocop:disable Lint/UnusedMethodArgument
         true
       end
 
@@ -59,12 +56,10 @@ module Nanoc::CLI::Commands
       # @return [void]
       def stop
       end
-
     end
 
     # Generates diffs for every output file written
     class DiffGenerator < Listener
-
       # @see Listener#enable_for?
       def self.enable_for?(command_runner)
         command_runner.site.config[:enable_output_diff]
@@ -76,11 +71,11 @@ module Nanoc::CLI::Commands
         setup_diffs
         old_contents = {}
         Nanoc::NotificationCenter.on(:will_write_rep) do |rep, snapshot|
-          path = rep.raw_path(:snapshot => snapshot)
+          path = rep.raw_path(snapshot: snapshot)
           old_contents[rep] = File.file?(path) ? File.read(path) : nil
         end
-        Nanoc::NotificationCenter.on(:rep_written) do |rep, path, is_created, is_modified|
-          if !rep.binary?
+        Nanoc::NotificationCenter.on(:rep_written) do |rep, path, _is_created, _is_modified|
+          unless rep.binary?
             new_contents = File.file?(path) ? File.read(path) : nil
             if old_contents[rep] && new_contents
               generate_diff_for(rep, old_contents[rep], new_contents)
@@ -96,7 +91,7 @@ module Nanoc::CLI::Commands
         teardown_diffs
       end
 
-    protected
+      protected
 
       def setup_diffs
         @diff_lock    = Mutex.new
@@ -105,7 +100,7 @@ module Nanoc::CLI::Commands
       end
 
       def teardown_diffs
-        @diff_threads.each { |t| t.join }
+        @diff_threads.each(&:join)
       end
 
       def generate_diff_for(rep, old_content, new_content)
@@ -137,8 +132,8 @@ module Nanoc::CLI::Commands
             new_file.flush
 
             # Diff
-            cmd = [ 'diff', '-u', old_file.path, new_file.path ]
-            Open3.popen3(*cmd) do |stdin, stdout, stderr|
+            cmd = ['diff', '-u', old_file.path, new_file.path]
+            Open3.popen3(*cmd) do |_stdin, stdout, _stderr|
               result = stdout.read
               return (result == '' ? nil : result)
             end
@@ -149,12 +144,10 @@ module Nanoc::CLI::Commands
              'content will be available.'
         nil
       end
-
     end
 
     # Records the time spent per filter and per item representation
     class TimingRecorder < Listener
-
       # @see Listener#enable_for?
       def self.enable_for?(command_runner)
         command_runner.options.fetch(:verbose, false)
@@ -169,11 +162,11 @@ module Nanoc::CLI::Commands
 
       # @see Listener#start
       def start
-        Nanoc::NotificationCenter.on(:filtering_started) do |rep, filter_name|
+        Nanoc::NotificationCenter.on(:filtering_started) do |_rep, filter_name|
           @times[filter_name] ||= []
-          @times[filter_name] << { :start => Time.now }
+          @times[filter_name] << { start: Time.now }
         end
-        Nanoc::NotificationCenter.on(:filtering_ended) do |rep, filter_name|
+        Nanoc::NotificationCenter.on(:filtering_ended) do |_rep, filter_name|
           @times[filter_name].last[:stop] = Time.now
         end
       end
@@ -184,7 +177,7 @@ module Nanoc::CLI::Commands
         super
       end
 
-    protected
+      protected
 
       def print_profiling_feedback
         # Get max filter length
@@ -194,7 +187,7 @@ module Nanoc::CLI::Commands
         # Print warning if necessary
         if @reps.any? { |r| !r.compiled? }
           $stderr.puts
-          $stderr.puts 'Warning: profiling information may not be accurate because ' +
+          $stderr.puts 'Warning: profiling information may not be accurate because ' \
                        'some items were not compiled.'
         end
 
@@ -215,7 +208,7 @@ module Nanoc::CLI::Commands
         # Calculate stats
         count = samples.size
         min   = samples.min
-        tot   = samples.reduce(0) { |memo, i| memo + i }
+        tot   = samples.reduce(0) { |a, e| a + e }
         avg   = tot / count
         max   = samples.max
 
@@ -253,24 +246,22 @@ module Nanoc::CLI::Commands
         end
         result
       end
-
     end
 
     # Controls garbage collection so that it only occurs once every 20 items
     class GCController < Listener
-
       # @see Listener#enable_for?
-      def self.enable_for?(command_runner)
+      def self.enable_for?(_command_runner)
         !ENV.key?('TRAVIS')
       end
 
-      def initialize(params = {})
+      def initialize(_params = {})
         @gc_count = 0
       end
 
       # @see Listener#start
       def start
-        Nanoc::NotificationCenter.on(:compilation_started) do |rep|
+        Nanoc::NotificationCenter.on(:compilation_started) do |_rep|
           if @gc_count % 20 == 0
             GC.enable
             GC.start
@@ -285,12 +276,10 @@ module Nanoc::CLI::Commands
         super
         GC.enable
       end
-
     end
 
     # Prints debug information (compilation started/ended, filtering started/ended, …)
     class DebugPrinter < Listener
-
       # @see Listener#enable_for?
       def self.enable_for?(command_runner)
         command_runner.debug?
@@ -327,12 +316,10 @@ module Nanoc::CLI::Commands
           puts "*** Dependency created from #{src.inspect} onto #{dst.inspect}"
         end
       end
-
     end
 
     # Prints file actions (created, updated, deleted, identical, skipped)
     class FileActionPrinter < Listener
-
       # @option params [Array<Nanoc::ItemRep>] :reps The list of item representations in the site
       def initialize(params = {})
         @start_times = {}
@@ -345,7 +332,7 @@ module Nanoc::CLI::Commands
         Nanoc::NotificationCenter.on(:compilation_started) do |rep|
           @start_times[rep.raw_path] = Time.now
         end
-        Nanoc::NotificationCenter.on(:rep_written) do |rep, path, is_created, is_modified|
+        Nanoc::NotificationCenter.on(:rep_written) do |_rep, path, is_created, is_modified|
           duration = path && @start_times[path] ? Time.now - @start_times[path] : nil
           action =
             case
@@ -367,18 +354,17 @@ module Nanoc::CLI::Commands
       def stop
         super
         @reps.select { |r| !r.compiled? }.each do |rep|
-          rep.raw_paths.each do |snapshot_name, raw_path|
+          rep.raw_paths.each do |_snapshot_name, raw_path|
             log(:low, :skip, raw_path, nil)
           end
         end
       end
 
-    private
+      private
 
       def log(level, action, path, duration)
         Nanoc::CLI::Logger.instance.file(level, action, path, duration)
       end
-
     end
 
     def initialize(options, arguments, command, params = {})
@@ -403,11 +389,11 @@ module Nanoc::CLI::Commands
       puts "Site compiled in #{format('%.2f', time_after - time_before)}s."
     end
 
-  protected
+    protected
 
     def prune
       if site.config[:prune][:auto_prune]
-        Nanoc::Extra::Pruner.new(site, :exclude => prune_config_exclude).run
+        Nanoc::Extra::Pruner.new(site, exclude: prune_config_exclude).run
       end
     end
 
@@ -422,11 +408,12 @@ module Nanoc::CLI::Commands
     end
 
     def setup_listeners
-      @listeners = @listener_classes.
-        select { |klass| klass.enable_for?(self) }.
-        map    { |klass| klass.new(:reps => reps) }
+      @listeners =
+        @listener_classes
+        .select { |klass| klass.enable_for?(self) }
+        .map    { |klass| klass.new(reps: reps) }
 
-      @listeners.each { |s| s.start }
+      @listeners.each(&:start)
     end
 
     def listeners
@@ -441,11 +428,11 @@ module Nanoc::CLI::Commands
     end
 
     def teardown_listeners
-      @listeners.each { |s| s.stop }
+      @listeners.each(&:stop)
     end
 
     def reps
-      site.items.map { |i| i.reps }.flatten
+      site.items.map(&:reps).flatten
     end
     memoize :reps
 
@@ -470,9 +457,7 @@ module Nanoc::CLI::Commands
     def prune_config_exclude
       prune_config[:exclude] || {}
     end
-
   end
-
 end
 
 runner Nanoc::CLI::Commands::Compile
diff --git a/lib/nanoc/cli/commands/create-item.rb b/lib/nanoc/cli/commands/create-item.rb
index cf85259..dc3576d 100644
--- a/lib/nanoc/cli/commands/create-item.rb
+++ b/lib/nanoc/cli/commands/create-item.rb
@@ -1,8 +1,8 @@
 # encoding: utf-8
 
-usage       'create-item [options] identifier'
-aliases     :create_item, :ci
-summary     'create an item'
+usage 'create-item [options] identifier'
+aliases :create_item, :ci
+summary 'create an item'
 description <<-EOS
 Create a new item in the current site. The first data source in the site
 configuration will be used.
@@ -11,9 +11,7 @@ EOS
 required :c, :vcs, 'specify the VCS to use'
 
 module Nanoc::CLI::Commands
-
   class CreateItem < ::Nanoc::CLI::CommandRunner
-
     def run
       # Check arguments
       if arguments.length != 1
@@ -30,9 +28,9 @@ module Nanoc::CLI::Commands
       set_vcs(options[:vcs])
 
       # Check whether item is unique
-      if !site.items.find { |i| i.identifier == identifier }.nil?
+      unless site.items.find { |i| i.identifier == identifier }.nil?
         raise Nanoc::Errors::GenericTrivial,
-          "An item already exists at #{identifier}. Please " +
+          "An item already exists at #{identifier}. Please " \
           'pick a unique name for the item you are creating.'
       end
 
@@ -45,15 +43,13 @@ module Nanoc::CLI::Commands
       data_source = site.data_sources[0]
       data_source.create_item(
         "Hi, I'm a new item!\n",
-        { :title => 'A New Item' },
+        { title: 'A New Item' },
         identifier
       )
 
       puts "An item has been created at #{identifier}."
     end
-
   end
-
 end
 
 runner Nanoc::CLI::Commands::CreateItem
diff --git a/lib/nanoc/cli/commands/create-layout.rb b/lib/nanoc/cli/commands/create-layout.rb
index e00b05d..2cd96ee 100644
--- a/lib/nanoc/cli/commands/create-layout.rb
+++ b/lib/nanoc/cli/commands/create-layout.rb
@@ -1,17 +1,15 @@
 # encoding: utf-8
 
-usage       'create-layout [options] identifier'
-aliases     :create_layout, :cl
-summary     'create a layout'
+usage 'create-layout [options] identifier'
+aliases :create_layout, :cl
+summary 'create a layout'
 description <<-EOS
 Create a new layout in the current site. The first data source in the site
 configuration will be used.
 EOS
 
 module Nanoc::CLI::Commands
-
   class CreateLayout < ::Nanoc::CLI::CommandRunner
-
     def run
       # Check arguments
       if arguments.length != 1
@@ -28,16 +26,16 @@ module Nanoc::CLI::Commands
       set_vcs(options[:vcs])
 
       # Check whether layout is unique
-      if !site.layouts.find { |l| l.identifier == identifier }.nil?
+      unless site.layouts.find { |l| l.identifier == identifier }.nil?
         raise Nanoc::Errors::GenericTrivial,
-          "A layout already exists at #{identifier}. Please " +
+          "A layout already exists at #{identifier}. Please " \
           'pick a unique name for the layout you are creating.'
       end
 
       # Check whether layout is not at /
       if identifier == '/'
         raise Nanoc::Errors::GenericTrivial,
-          "There cannot be a layout with the identifier '/'; " +
+          "There cannot be a layout with the identifier '/'; " \
           'please pick a different identifier for this layout.'
       end
 
@@ -49,14 +47,14 @@ module Nanoc::CLI::Commands
       # Create layout
       data_source = site.data_sources[0]
       data_source.create_layout(
-        "<html>\n" +
-        "  <head>\n" +
-        "    <title><%= @item[:title] %></title>\n" +
-        "  </head>\n" +
-        "  <body>\n" +
-        "    <p>Hi, I'm a new layout. Please customize me!</p>\n" +
-        "<%= yield %>\n" +
-        "  </body>\n" +
+        "<html>\n" \
+        "  <head>\n" \
+        "    <title><%= @item[:title] %></title>\n" \
+        "  </head>\n" \
+        "  <body>\n" \
+        "    <p>Hi, I'm a new layout. Please customize me!</p>\n" \
+        "<%= yield %>\n" \
+        "  </body>\n" \
         "</html>\n",
         {},
         identifier
@@ -64,9 +62,7 @@ module Nanoc::CLI::Commands
 
       puts "A layout has been created at #{identifier}."
     end
-
   end
-
 end
 
 runner Nanoc::CLI::Commands::CreateLayout
diff --git a/lib/nanoc/cli/commands/create-site.rb b/lib/nanoc/cli/commands/create-site.rb
index 67387df..100b82c 100644
--- a/lib/nanoc/cli/commands/create-site.rb
+++ b/lib/nanoc/cli/commands/create-site.rb
@@ -1,27 +1,23 @@
 # encoding: utf-8
 
-usage       'create-site [options] path'
-aliases     :create_site, :cs
-summary     'create a site'
+usage 'create-site [options] path'
+aliases :create_site, :cs
+summary 'create a site'
 description "
-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.
+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` command-line option.
 "
 
 required :d, :datasource, 'specify the data source for the new site'
 
 module Nanoc::CLI::Commands
-
   class CreateSite < ::Nanoc::CLI::CommandRunner
-
     class << self
-
-    protected
+      protected
 
       # Converts the given array to YAML format
       def array_to_yaml(array)
         '[ ' + array.map { |s| "'" + s + "'" }.join(', ') + ' ]'
       end
-
     end
 
     DEFAULT_CONFIG = <<EOS unless defined? DEFAULT_CONFIG
@@ -134,7 +130,7 @@ route '*' do
     item.identifier.chop + '.css'
   elsif item.binary?
     # Write item with identifier /foo/ to /foo.ext
-    item.identifier.chop + '.' + item[:extension]
+    item.identifier.chop + (item[:extension] ? '.' + item[:extension] : '')
   else
     # Write item with identifier /foo/ to /foo/index.html
     item.identifier + 'index.html'
@@ -329,11 +325,11 @@ EOS
       puts "Created a blank nanoc site at '#{path}'. Enjoy!"
     end
 
-  protected
+    protected
 
     # Creates a configuration file and a output directory for this site, as
     # well as a rakefile that loads the standard nanoc tasks.
-    def site_create_minimal(data_source)
+    def site_create_minimal(_data_source)
       # Create output
       FileUtils.mkdir_p('output')
 
@@ -370,7 +366,7 @@ EOS
       # Create home page
       data_source.create_item(
         DEFAULT_ITEM,
-        { :title => 'Home' },
+        { title: 'Home' },
         '/'
       )
 
@@ -379,7 +375,7 @@ EOS
         DEFAULT_STYLESHEET,
         {},
         '/stylesheet/',
-        :extension => '.css'
+        extension: '.css'
       )
 
       # Create layout
@@ -396,9 +392,7 @@ EOS
         io.write "\# before nanoc starts compiling.\n"
       end
     end
-
   end
-
 end
 
 runner Nanoc::CLI::Commands::CreateSite
diff --git a/lib/nanoc/cli/commands/deploy.rb b/lib/nanoc/cli/commands/deploy.rb
index c97c4a8..7e17512 100644
--- a/lib/nanoc/cli/commands/deploy.rb
+++ b/lib/nanoc/cli/commands/deploy.rb
@@ -1,28 +1,26 @@
 # encoding: utf-8
 
-usage       'deploy [options]'
-summary     'deploy the compiled site'
+usage 'deploy [options]'
+summary 'deploy the compiled site'
 description "
 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.
 "
 
-option :t, :target,           'specify the location to deploy to (default: `default`)', :argument => :required
-flag   :C, :'no-check',       'do not run the issue checks marked for deployment'
-flag   :L, :list,             'list available locations to deploy to'
-flag   :D, :'list-deployers', 'list available deployers'
+option :t, :target,           'specify the location to deploy to (default: `default`)', argument: :required
+flag :C, :'no-check',       'do not run the issue checks marked for deployment'
+flag :L, :list,             'list available locations to deploy to'
+flag :D, :'list-deployers', 'list available deployers'
 option :n, :'dry-run',        'show what would be deployed'
 
 module Nanoc::CLI::Commands
-
   class Deploy < ::Nanoc::CLI::CommandRunner
-
     def run
       load_site
 
       # List deployers
       if options[:'list-deployers']
         deployers      = Nanoc::PluginRegistry.instance.find_all(Nanoc::Extra::Deployer)
-        deployer_names = deployers.keys.sort_by { |k| k.to_s }
+        deployer_names = deployers.keys.sort_by(&:to_s)
         puts 'Available deployers:'
         deployer_names.each do |name|
           puts "  #{name}"
@@ -35,7 +33,7 @@ module Nanoc::CLI::Commands
 
       if options[:list]
         if deploy_configs.empty?
-          puts  'No deployment configurations.'
+          puts 'No deployment configurations.'
         else
           puts 'Available deployment configurations:'
           deploy_configs.keys.each do |name|
@@ -70,10 +68,10 @@ module Nanoc::CLI::Commands
       # Check
       unless options[:'no-check']
         runner = Nanoc::Extra::Checking::Runner.new(site)
-        if runner.has_dsl?
+        if runner.dsl_present?
           puts 'Running issue checks…'
           ok = runner.run_for_deploy
-          if !ok
+          unless ok
             puts 'Issues found, deploy aborted.'
             return
           end
@@ -85,12 +83,10 @@ module Nanoc::CLI::Commands
       deployer = deployer_class.new(
         site.config[:output_dir],
         config,
-        :dry_run => options[:'dry-run'])
+        dry_run: options[:'dry-run'])
       deployer.run
     end
-
   end
-
 end
 
 runner Nanoc::CLI::Commands::Deploy
diff --git a/lib/nanoc/cli/commands/nanoc.rb b/lib/nanoc/cli/commands/nanoc.rb
index dee1754..cc69378 100644
--- a/lib/nanoc/cli/commands/nanoc.rb
+++ b/lib/nanoc/cli/commands/nanoc.rb
@@ -1,6 +1,6 @@
 # encoding: utf-8
 
-usage   'nanoc command [options] [arguments]'
+usage 'nanoc command [options] [arguments]'
 summary 'nanoc, a static site compiler written in Ruby'
 
 opt :l, :color, 'enable color' do
@@ -12,7 +12,7 @@ opt :d, :debug, 'enable debugging' do
   Nanoc::CLI.debug = true
 end
 
-opt :h, :help, 'show the help message and quit' do |value, cmd|
+opt :h, :help, 'show the help message and quit' do |_value, cmd|
   puts cmd.help
   exit 0
 end
@@ -35,6 +35,6 @@ opt :w, :warn, 'enable warnings' do
   $-w = true
 end
 
-run do |opts, args, cmd|
+run do |_opts, _args, cmd|
   cmd.command_named('compile').run([])
 end
diff --git a/lib/nanoc/cli/commands/prune.rb b/lib/nanoc/cli/commands/prune.rb
index 23ef6ee..ddd0220 100644
--- a/lib/nanoc/cli/commands/prune.rb
+++ b/lib/nanoc/cli/commands/prune.rb
@@ -1,7 +1,7 @@
 # encoding: utf-8
 
-usage       'prune'
-summary     'remove files not managed by nanoc from the output directory'
+usage 'prune'
+summary 'remove files not managed by nanoc from the output directory'
 description <<-EOS
 Find all files in the output directory that do not correspond to an item
 managed by nanoc and remove them. Since this is a hazardous operation, an
@@ -15,16 +15,14 @@ flag :y, :yes,       'confirm deletion'
 flag :n, :'dry-run', 'print files to be deleted instead of actually deleting them'
 
 module Nanoc::CLI::Commands
-
   class Prune < ::Nanoc::CLI::CommandRunner
-
     def run
       load_site
 
       if options.key?(:yes)
-        Nanoc::Extra::Pruner.new(site, :exclude => prune_config_exclude).run
+        Nanoc::Extra::Pruner.new(site, exclude: prune_config_exclude).run
       elsif options.key?(:'dry-run')
-        Nanoc::Extra::Pruner.new(site, :exclude => prune_config_exclude, :dry_run => true).run
+        Nanoc::Extra::Pruner.new(site, exclude: prune_config_exclude, dry_run: true).run
       else
         $stderr.puts 'WARNING: Since the prune command is a destructive command, it requires an additional --yes flag in order to work.'
         $stderr.puts
@@ -33,7 +31,7 @@ module Nanoc::CLI::Commands
       end
     end
 
-  protected
+    protected
 
     def prune_config
       site.config[:prune] || {}
@@ -42,9 +40,7 @@ module Nanoc::CLI::Commands
     def prune_config_exclude
       prune_config[:exclude] || {}
     end
-
   end
-
 end
 
 runner Nanoc::CLI::Commands::Prune
diff --git a/lib/nanoc/cli/commands/shell.rb b/lib/nanoc/cli/commands/shell.rb
index 46ceea0..4169e49 100644
--- a/lib/nanoc/cli/commands/shell.rb
+++ b/lib/nanoc/cli/commands/shell.rb
@@ -1,16 +1,14 @@
 # encoding: utf-8
 
-usage       'shell'
-summary     'open a shell on the nanoc environment'
-aliases     'console'
+usage 'shell'
+summary 'open a shell on the nanoc environment'
+aliases 'console'
 description "
 Open an IRB shell on a context that contains @items, @layouts, @config and @site.
 "
 
 module Nanoc::CLI::Commands
-
   class Shell < ::Nanoc::CLI::CommandRunner
-
     def run
       require 'pry'
 
@@ -19,19 +17,17 @@ module Nanoc::CLI::Commands
       Nanoc::Context.new(env).pry
     end
 
-  protected
+    protected
 
     def env
       {
-        :site    => site,
-        :items   => site.items,
-        :layouts => site.layouts,
-        :config  => site.config
+        site: site,
+        items: site.items,
+        layouts: site.layouts,
+        config: site.config
       }
     end
-
   end
-
 end
 
 runner Nanoc::CLI::Commands::Shell
diff --git a/lib/nanoc/cli/commands/show-data.rb b/lib/nanoc/cli/commands/show-data.rb
index 1c2adb4..2a6faf4 100644
--- a/lib/nanoc/cli/commands/show-data.rb
+++ b/lib/nanoc/cli/commands/show-data.rb
@@ -1,17 +1,15 @@
 # encoding: utf-8
 
-usage       'show-data'
-aliases     :debug
-summary     'show data in this site'
+usage 'show-data'
+aliases :debug
+summary 'show data in this site'
 description <<-EOS
 Show information about all items, item representations and layouts in the
 current site, along with dependency information.
 EOS
 
 module Nanoc::CLI::Commands
-
   class ShowData < ::Nanoc::CLI::CommandRunner
-
     def run
       load_site
 
@@ -31,11 +29,11 @@ module Nanoc::CLI::Commands
       print_layouts(layouts, compiler)
     end
 
-  protected
+    protected
 
     def sorted_with_prev(objects)
       prev = nil
-      objects.sort_by { |o| o.identifier }.each do |object|
+      objects.sort_by(&:identifier).each do |object|
         yield(object, prev)
         prev = object
       end
@@ -43,7 +41,7 @@ module Nanoc::CLI::Commands
 
     def sorted_reps_with_prev(items)
       prev = nil
-      items.sort_by { |i| i.identifier }.each do |item|
+      items.sort_by(&:identifier).each do |item|
         item.reps.sort_by { |r| r.name.to_s }.each do |rep|
           yield(rep, prev)
           prev = rep
@@ -89,7 +87,7 @@ module Nanoc::CLI::Commands
         end
         length = rep.raw_paths.keys.map { |s| s.to_s.length }.max
         rep.raw_paths.each do |snapshot_name, raw_path|
-          puts "  [ %-#{length}s ] %s" % [ snapshot_name, raw_path ]
+          puts format("  [ %-#{length}s ] %s", snapshot_name, raw_path)
         end
       end
     end
@@ -124,9 +122,7 @@ module Nanoc::CLI::Commands
         puts
       end
     end
-
   end
-
 end
 
 runner Nanoc::CLI::Commands::ShowData
diff --git a/lib/nanoc/cli/commands/show-plugins.rb b/lib/nanoc/cli/commands/show-plugins.rb
index 2702763..c15139a 100644
--- a/lib/nanoc/cli/commands/show-plugins.rb
+++ b/lib/nanoc/cli/commands/show-plugins.rb
@@ -1,17 +1,15 @@
 # encoding: utf-8
 
-summary     'show all available plugins'
-aliases     :info
-usage       'show-plugins [options]'
+summary 'show all available plugins'
+aliases :info
+usage 'show-plugins [options]'
 description <<-EOS
 Show a list of available plugins, including filters, data sources and VCSes.
 If the current directory contains a nanoc web site, the plugins defined in this site will be shown as well.
 EOS
 
 module Nanoc::CLI::Commands
-
   class ShowPlugins < ::Nanoc::CLI::CommandRunner
-
     def run
       # Check arguments
       if arguments.size != 0
@@ -35,8 +33,8 @@ module Nanoc::CLI::Commands
 
       PLUGIN_CLASS_ORDER.each do |superclass|
         plugins_with_this_superclass = {
-          :builtin => plugins_builtin.select { |p| p[:superclass] == superclass },
-          :custom  => plugins_custom.select  { |p| p[:superclass] == superclass }
+          builtin: plugins_builtin.select { |p| p[:superclass] == superclass },
+          custom: plugins_custom.select  { |p| p[:superclass] == superclass }
         }
 
         # Print kind
@@ -45,7 +43,7 @@ module Nanoc::CLI::Commands
         puts
 
         # Print plugins organised by subtype
-        [ :builtin, :custom ].each do |type|
+        [:builtin, :custom].each do |type|
           # Find relevant plugins
           relevant_plugins = plugins_with_this_superclass[type]
 
@@ -59,7 +57,7 @@ module Nanoc::CLI::Commands
           # Print plugins
           relevant_plugins.sort_by { |k| k[:identifiers].join(', ') }.each do |plugin|
             # Display
-            puts sprintf(
+            puts format(
               "    %-#{max_identifiers_length}s (%s)",
               plugin[:identifiers].join(', '),
               plugin[:class].to_s.sub(/^::/, '')
@@ -71,7 +69,7 @@ module Nanoc::CLI::Commands
       end
     end
 
-  private
+    private
 
     PLUGIN_CLASS_ORDER = [
       Nanoc::Filter,
@@ -90,9 +88,7 @@ module Nanoc::CLI::Commands
     def name_for_plugin_class(klass)
       PLUGIN_CLASSES[klass]
     end
-
   end
-
 end
 
 runner Nanoc::CLI::Commands::ShowPlugins
diff --git a/lib/nanoc/cli/commands/show-rules.rb b/lib/nanoc/cli/commands/show-rules.rb
index dc44d52..ed72ce5 100644
--- a/lib/nanoc/cli/commands/show-rules.rb
+++ b/lib/nanoc/cli/commands/show-rules.rb
@@ -1,31 +1,29 @@
 # encoding: utf-8
 
-usage       'show-rules [thing]'
-aliases     :explain
-summary     'describe the rules for each item'
+usage 'show-rules [thing]'
+aliases :explain
+summary 'describe the rules for each item'
 description "
 Prints the rules used for all items and layouts in the current site.
 "
 
 module Nanoc::CLI::Commands
-
   class ShowRules < ::Nanoc::CLI::CommandRunner
-
     def run
       require_site
 
       @c    = Nanoc::CLI::ANSIStringColorizer
       @calc = site.compiler.rule_memory_calculator
 
-      # TODO explain /foo/
-      # TODO explain content/foo.html
-      # TODO explain output/foo/index.html
+      # TODO: explain /foo/
+      # TODO: explain content/foo.html
+      # TODO: explain output/foo/index.html
 
       site.items.each   { |i| explain_item(i)   }
       site.layouts.each { |l| explain_layout(l) }
     end
 
-  protected
+    protected
 
     def explain_item(item)
       puts "#{@c.c('Item ' + item.identifier, :bold, :yellow)}:"
@@ -36,16 +34,14 @@ module Nanoc::CLI::Commands
           puts '    (nothing)'
         else
           @calc[rep].each do |mem|
-            puts '    %s %s' % [
+            puts format('    %s %s',
               @c.c(format('%-10s', mem[0].to_s), :blue),
-              mem[1..-1].map { |m| m.inspect }.join(', ')
-            ]
+              mem[1..-1].map(&:inspect).join(', '))
           end
           if rep.raw_path
-            puts '    %s %s' % [
+            puts format('    %s %s',
               @c.c(format('%-10s', 'write to'), :blue),
-              rep.raw_path
-            ]
+              rep.raw_path)
           end
         end
       end
@@ -55,15 +51,12 @@ module Nanoc::CLI::Commands
     def explain_layout(layout)
       puts "#{@c.c('Layout ' + layout.identifier, :bold, :yellow)}:"
       puts "  (from #{layout[:filename]})" if layout[:filename]
-      puts '  %s %s' % [
+      puts format('  %s %s',
         @c.c(format('%-10s', 'filter'), :blue),
-        @calc[layout].map { |m| m.inspect }.join(', ')
-      ]
+        @calc[layout].map(&:inspect).join(', '))
       puts
     end
-
   end
-
 end
 
 runner Nanoc::CLI::Commands::ShowRules
diff --git a/lib/nanoc/cli/commands/sync.rb b/lib/nanoc/cli/commands/sync.rb
index 4df6a4e..d59af4c 100644
--- a/lib/nanoc/cli/commands/sync.rb
+++ b/lib/nanoc/cli/commands/sync.rb
@@ -1,6 +1,6 @@
 # encoding: utf-8
 
-usage   'sync'
+usage 'sync'
 summary 'sync data sources'
 description <<-EOS
 Sync data source data. This command is useful for updating local item caches
@@ -8,9 +8,7 @@ for data sources which rely on slow external APIs.
 EOS
 
 module Nanoc::CLI::Commands
-
   class Sync < ::Nanoc::CLI::CommandRunner
-
     def run
       # Check arguments
       if arguments.size != 0
@@ -28,9 +26,7 @@ module Nanoc::CLI::Commands
         end
       end
     end
-
   end
-
 end
 
 runner Nanoc::CLI::Commands::Sync
diff --git a/lib/nanoc/cli/commands/update.rb b/lib/nanoc/cli/commands/update.rb
index 2674b79..706e0a9 100644
--- a/lib/nanoc/cli/commands/update.rb
+++ b/lib/nanoc/cli/commands/update.rb
@@ -1,6 +1,6 @@
 # encoding: utf-8
 
-usage   'update [options]'
+usage 'update [options]'
 summary 'update the data stored by the data source to a newer version'
 description <<-EOS
 Update the data stored by the data source to a newer format. The format in
@@ -13,12 +13,10 @@ backup in case something goes wrong.
 EOS
 
 required :c, :vcs, 'select the VCS to use'
-flag     :y, :yes, 'update the data without warning'
+flag :y, :yes, 'update the data without warning'
 
 module Nanoc::CLI::Commands
-
   class Update < ::Nanoc::CLI::CommandRunner
-
     def run
       # Check arguments
       if arguments.size != 0
@@ -37,33 +35,29 @@ module Nanoc::CLI::Commands
         $stderr.puts '** WARNING **'
         $stderr.puts '*************'
         $stderr.puts
-        $stderr.puts 'Are you absolutely sure you want to update the ' +
-                     'content for this site? Updating the site content ' +
-                     'will change the structure of existing data. This ' +
-                     'operation is destructive and cannot be reverted. ' +
-                     'Please do not interrupt this operation; doing so can ' +
-                     'result in data loss. As always, consider making a ' +
+        $stderr.puts 'Are you absolutely sure you want to update the ' \
+                     'content for this site? Updating the site content ' \
+                     'will change the structure of existing data. This ' \
+                     'operation is destructive and cannot be reverted. ' \
+                     'Please do not interrupt this operation; doing so can ' \
+                     'result in data loss. As always, consider making a ' \
                      'backup copy.'
         $stderr.puts
-        $stderr.puts 'If this nanoc site is versioned using a VCS ' +
-                     'supported by nanoc, consider using the --vcs option ' +
-                     'to have nanoc perform add/delete/move operations ' +
-                     'using the specified VCS. To get a list of VCSes ' +
+        $stderr.puts 'If this nanoc site is versioned using a VCS ' \
+                     'supported by nanoc, consider using the --vcs option ' \
+                     'to have nanoc perform add/delete/move operations ' \
+                     'using the specified VCS. To get a list of VCSes ' \
                      'supported by nanoc, issue the "info" command.'
         $stderr.puts
-        $stderr.puts 'To continue, use the -y/--yes option, like "nanoc ' +
+        $stderr.puts 'To continue, use the -y/--yes option, like "nanoc ' \
                      'update -y".'
         exit 1
       end
 
       # Update
-      site.data_sources.each do |data_source|
-        data_source.update
-      end
+      site.data_sources.each(&:update)
     end
-
   end
-
 end
 
 runner Nanoc::CLI::Commands::Update
diff --git a/lib/nanoc/cli/commands/validate-css.rb b/lib/nanoc/cli/commands/validate-css.rb
index ae83212..5ed53a9 100644
--- a/lib/nanoc/cli/commands/validate-css.rb
+++ b/lib/nanoc/cli/commands/validate-css.rb
@@ -1,24 +1,20 @@
 # encoding: utf-8
 
-usage       'validate-css [options]'
-aliases     :validate_css, :vcss
-summary     'validate the site’s CSS'
+usage 'validate-css [options]'
+aliases :validate_css, :vcss
+summary 'validate the site’s CSS'
 be_hidden
 description "
 Validates the site’s CSS files.
 "
 
 module Nanoc::CLI::Commands
-
   class ValidateCSS < ::Nanoc::CLI::CommandRunner
-
     def run
       warn 'The `validate-css` command is deprecated. Please use the new `check` command instead.'
       Nanoc::CLI.run %w( check css )
     end
-
   end
-
 end
 
 runner Nanoc::CLI::Commands::ValidateCSS
diff --git a/lib/nanoc/cli/commands/validate-html.rb b/lib/nanoc/cli/commands/validate-html.rb
index f3bb7fb..2de4443 100644
--- a/lib/nanoc/cli/commands/validate-html.rb
+++ b/lib/nanoc/cli/commands/validate-html.rb
@@ -1,24 +1,20 @@
 # encoding: utf-8
 
-usage       'validate-html [options]'
-aliases     :validate_html, :vhtml
-summary     'validate the site’s HTML'
+usage 'validate-html [options]'
+aliases :validate_html, :vhtml
+summary 'validate the site’s HTML'
 be_hidden
 description "
 Validates the site’s HTML files.
 "
 
 module Nanoc::CLI::Commands
-
   class ValidateHTML < ::Nanoc::CLI::CommandRunner
-
     def run
       warn 'The `validate-html` command is deprecated. Please use the new `check` command instead.'
       Nanoc::CLI.run %w( check html )
     end
-
   end
-
 end
 
 runner Nanoc::CLI::Commands::ValidateHTML
diff --git a/lib/nanoc/cli/commands/validate-links.rb b/lib/nanoc/cli/commands/validate-links.rb
index 261c3e8..2f6622a 100644
--- a/lib/nanoc/cli/commands/validate-links.rb
+++ b/lib/nanoc/cli/commands/validate-links.rb
@@ -1,31 +1,27 @@
 # encoding: utf-8
 
-usage       'validate-links [options]'
-aliases     :validate_links, :vlink
-summary     'validate links in site'
+usage 'validate-links [options]'
+aliases :validate_links, :vlink
+summary 'validate links in site'
 be_hidden
 description "
 Validates the site’s links. By default, both internal and external links will be checked.
 "
 
-flag   :i, :internal, 'validate internal links only'
-flag   :e, :external, 'validate external links only'
+flag :i, :internal, 'validate internal links only'
+flag :e, :external, 'validate external links only'
 
 module Nanoc::CLI::Commands
-
   class ValidateLinks < ::Nanoc::CLI::CommandRunner
-
     def run
       warn 'The `validate-links` command is deprecated. Please use the new `check` command instead.'
 
       checks = []
       checks << 'ilinks' if options[:internal]
       checks << 'elinks' if options[:external]
-      Nanoc::CLI.run [ 'check', checks ].flatten
+      Nanoc::CLI.run ['check', checks].flatten
     end
-
   end
-
 end
 
 runner Nanoc::CLI::Commands::ValidateLinks
diff --git a/lib/nanoc/cli/commands/view.rb b/lib/nanoc/cli/commands/view.rb
index e94ac4d..c16b423 100644
--- a/lib/nanoc/cli/commands/view.rb
+++ b/lib/nanoc/cli/commands/view.rb
@@ -1,7 +1,7 @@
 # encoding: utf-8
 
-usage       'view [options]'
-summary     'start the web server that serves static files'
+usage 'view [options]'
+summary 'start the web server that serves static files'
 description <<-EOS
 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
@@ -13,9 +13,7 @@ required :o, :host,    'specify the host to listen on (default: 0.0.0.0)'
 required :p, :port,    'specify the port to listen on (default: 3000)'
 
 module Nanoc::CLI::Commands
-
   class View < ::Nanoc::CLI::CommandRunner
-
     DEFAULT_HANDLER_NAME = :thin
 
     def run
@@ -27,8 +25,8 @@ module Nanoc::CLI::Commands
 
       # Set options
       options_for_rack = {
-        :Port      => (options[:port] || 3000).to_i,
-        :Host      => (options[:host] || '0.0.0.0')
+        Port: (options[:port] || 3000).to_i,
+        Host: (options[:host] || '0.0.0.0')
       }
 
       # Get handler
@@ -49,7 +47,7 @@ module Nanoc::CLI::Commands
         use Rack::ShowExceptions
         use Rack::Lint
         use Rack::Head
-        use Adsf::Rack::IndexFileFinder, :root => site.config[:output_dir]
+        use Adsf::Rack::IndexFileFinder, root: site.config[:output_dir]
         run Rack::File.new(site.config[:output_dir])
       end.to_app
 
@@ -57,7 +55,7 @@ module Nanoc::CLI::Commands
       handler.run(app, options_for_rack)
     end
 
-  protected
+    protected
 
     def load_adsf
       # Load adsf
@@ -66,7 +64,7 @@ module Nanoc::CLI::Commands
         return
       rescue LoadError
         $stderr.puts "Could not find the required 'adsf' gem, " \
-          "which is necessary for the view command."
+          'which is necessary for the view command.'
       end
 
       # Check asdf
@@ -80,9 +78,7 @@ module Nanoc::CLI::Commands
       # Done
       exit 1
     end
-
   end
-
 end
 
 runner Nanoc::CLI::Commands::View
diff --git a/lib/nanoc/cli/commands/watch.rb b/lib/nanoc/cli/commands/watch.rb
index 1f9ed9e..d78b8b7 100644
--- a/lib/nanoc/cli/commands/watch.rb
+++ b/lib/nanoc/cli/commands/watch.rb
@@ -1,16 +1,14 @@
 # encoding: utf-8
 
-usage       'watch [options]'
-summary     'start the watcher'
+usage 'watch [options]'
+summary 'start the watcher'
 be_hidden
 description <<-EOS
 Start the watcher. When a change is detected, the site will be recompiled.
 EOS
 
 module Nanoc::CLI::Commands
-
   class Watch < ::Nanoc::CLI::CommandRunner
-
     def run
       warn 'WARNING: The `watch` command is deprecated. Please consider using `guard-nanoc` instead (see https://github.com/nanoc/guard-nanoc).'
 
@@ -44,7 +42,7 @@ module Nanoc::CLI::Commands
         begin
           site.compile
 
-          # TODO include icon (--image misc/success-icon.png)
+          # TODO: include icon (--image misc/success-icon.png)
           notify_on_compilation_success = watcher_config.fetch(:notify_on_compilation_success) { true }
           if notify_on_compilation_success
             @notifier.notify('Compilation complete')
@@ -53,7 +51,7 @@ module Nanoc::CLI::Commands
           time_spent = ((Time.now - start) * 1000.0).round
           puts "done in #{format '%is %ims', *(time_spent.divmod(1000))}"
         rescue Exception => e
-          # TODO include icon (--image misc/error-icon.png)
+          # TODO: include icon (--image misc/error-icon.png)
           notify_on_compilation_failure = watcher_config.fetch(:notify_on_compilation_failure) { true }
           if notify_on_compilation_failure
             @notifier.notify('Compilation failed')
@@ -69,8 +67,8 @@ module Nanoc::CLI::Commands
       rebuilder.call(nil)
 
       # Get directories to watch
-      dirs_to_watch  = watcher_config[:dirs_to_watch]  || %w( content layouts lib )
-      files_to_watch = watcher_config[:files_to_watch] || %w( nanoc.yaml config.yaml Rules rules Rules.rb rules.rb )
+      dirs_to_watch  = watcher_config.fetch(:dirs_to_watch, %w( content layouts lib ))
+      files_to_watch = watcher_config.fetch(:files_to_watch, %w( nanoc.yaml config.yaml Rules rules Rules.rb rules.rb ))
       files_to_watch = Regexp.new(files_to_watch.map { |name| Regexp.quote(name) + '$' }.join('|'))
       ignore_dir = Regexp.new(Dir.glob('*').map { |dir| dir if File.directory?(dir) }.compact.join('|'))
 
@@ -84,7 +82,7 @@ module Nanoc::CLI::Commands
       end
 
       listener = Listen::Listener.new(*dirs_to_watch).change(&callback)
-      listener_root = Listen::Listener.new('.', :filter => files_to_watch, :ignore => ignore_dir).change(&callback)
+      listener_root = Listen::Listener.new('.', filter: files_to_watch, ignore: ignore_dir).change(&callback)
 
       begin
         listener_root.start
@@ -97,8 +95,7 @@ module Nanoc::CLI::Commands
 
     # Allows sending user notifications in a cross-platform way.
     class Notifier
-
-      # A list of commandline tool names that can be used to send notifications
+      # A list of command-line tool names that can be used to send notifications
       TOOLS = %w( growlnotify notify-send ) unless defined? TOOLS
 
       # Send a notification. If no notifier is found, no notification will be
@@ -114,25 +111,25 @@ module Nanoc::CLI::Commands
         end
       end
 
-    protected
+      protected
 
-      def have_tool_nix?(tool)
+      def nix_tool_present?(tool)
         !`which #{tool}`.empty?
       rescue Errno::ENOENT
         false
       end
 
-      def have_tool_windows?(tool)
+      def windows_tool_present?(tool)
         !`where #{tool} 2> nul`.empty?
       rescue Errno::ENOENT
         false
       end
 
-      def have_tool?(tool)
+      def tool_present?(tool)
         if self.on_windows?
-          self.have_tool_windows?(tool)
+          self.windows_tool_present?(tool)
         else
-          self.have_tool_nix?(tool)
+          self.nix_tool_present?(tool)
         end
       end
 
@@ -141,16 +138,16 @@ module Nanoc::CLI::Commands
           require 'terminal-notifier'
           'terminal-notify'
         rescue LoadError
-          TOOLS.find { |t| have_tool?(t) }
+          TOOLS.find { |t| tool_present?(t) }
         end
       end
 
       def terminal_notify(message)
-        TerminalNotifier.notify(message, :title => 'nanoc')
+        TerminalNotifier.notify(message, title: 'nanoc')
       end
 
       def growlnotify_cmd_for(message)
-        [ 'growlnotify', '-m', message ]
+        ['growlnotify', '-m', message]
       end
 
       def growlnotify(message)
@@ -158,7 +155,7 @@ module Nanoc::CLI::Commands
       end
 
       def growlnotify_windows_cmd_for(message)
-        [ 'growlnotify', '/t:nanoc', message ]
+        ['growlnotify', '/t:nanoc', message]
       end
 
       def growlnotify_windows(message)
@@ -172,11 +169,8 @@ module Nanoc::CLI::Commands
       def on_windows?
         Nanoc.on_windows?
       end
-
     end
-
   end
-
 end
 
 runner Nanoc::CLI::Commands::Watch
diff --git a/lib/nanoc/cli/error_handler.rb b/lib/nanoc/cli/error_handler.rb
index f1d1db8..d877bd3 100644
--- a/lib/nanoc/cli/error_handler.rb
+++ b/lib/nanoc/cli/error_handler.rb
@@ -1,12 +1,10 @@
 # encoding: utf-8
 
 module Nanoc::CLI
-
   # Catches errors and prints nice diagnostic messages, then exits.
   #
   # @api private
   class ErrorHandler
-
     # @option params [Nanoc::CLI::Command, nil] command The command that is
     #   currently being executed, or nil if there is none
     def initialize(params = {})
@@ -50,7 +48,7 @@ module Nanoc::CLI
     # @return [void]
     #
     # @api private
-    def handle_while(&block)
+    def handle_while(&_block)
       # Set exit handler
       %w( INT TERM ).each do |signal|
         Signal.trap(signal) do
@@ -146,18 +144,18 @@ module Nanoc::CLI
       stream.puts "Crashlog created at #{Time.now}"
 
       # Sections
-      write_error_message(stream, error, :verbose => true)
-      write_compilation_stack(stream, error, :verbose => true)
-      write_stack_trace(stream, error, :verbose => true)
-      write_version_information(stream,        :verbose => true)
-      write_system_information(stream,        :verbose => true)
-      write_installed_gems(stream,        :verbose => true)
-      write_environment(stream,        :verbose => true)
-      write_gemfile_lock(stream,        :verbose => true)
-      write_load_paths(stream,        :verbose => true)
+      write_error_message(stream, error, verbose: true)
+      write_compilation_stack(stream, error, verbose: true)
+      write_stack_trace(stream, error, verbose: true)
+      write_version_information(stream,        verbose: true)
+      write_system_information(stream,        verbose: true)
+      write_installed_gems(stream,        verbose: true)
+      write_environment(stream,        verbose: true)
+      write_gemfile_lock(stream,        verbose: true)
+      write_load_paths(stream,        verbose: true)
     end
 
-  protected
+    protected
 
     # @return [Boolean] true if debug output is enabled, false if not
     #
@@ -184,7 +182,7 @@ module Nanoc::CLI
     # @return [Hash<String, Array>] A hash containing the gem names as keys and gem versions as value
     def gems_and_versions
       gems = {}
-      Gem::Specification.find_all.sort_by { |s| [ s.name, s.version ] }.each do |spec|
+      Gem::Specification.find_all.sort_by { |s| [s.name, s.version] }.each do |spec|
         gems[spec.name] ||= []
         gems[spec.name] << spec.version.to_s
       end
@@ -248,11 +246,11 @@ module Nanoc::CLI
         end
       when RuntimeError
         if error.message =~ /^can't modify frozen/
-          "You attempted to modify immutable data. Some data, such as " \
-          "item/layout attributes and raw item/layout content, can not " \
-          "be modified once compilation has started. (This was " \
-          "unintentionally possible in 3.1.x and before, but has been " \
-          "disabled in 3.2.x in order to allow compiler optimisations.)"
+          'You attempted to modify immutable data. Some data, such as ' \
+          'item/layout attributes and raw item/layout content, can not ' \
+          'be modified once compilation has started. (This was ' \
+          'unintentionally possible in 3.1.x and before, but has been ' \
+          'disabled in 3.2.x in order to allow compiler optimisations.)'
         end
       end
     end
@@ -279,13 +277,13 @@ module Nanoc::CLI
       stream.puts "#{resolution}" if resolution
     end
 
-    def write_compilation_stack(stream, error, params = {})
+    def write_compilation_stack(stream, _error, params = {})
       write_section_header(stream, 'Compilation stack', params)
 
       if stack.empty?
         stream.puts '  (empty)'
       else
-        stack.reverse.each do |obj|
+        stack.reverse_each do |obj|
           if obj.is_a?(Nanoc::ItemRep)
             stream.puts "  - [item]   #{obj.item.identifier} (rep #{obj.name})"
           else # layout
@@ -309,7 +307,7 @@ module Nanoc::CLI
       end
     end
 
-    def write_issue_link(stream, params = {})
+    def write_issue_link(stream, _params = {})
       stream.puts
       stream.puts 'If you believe this is a bug in nanoc, please do report it at'
       stream.puts '-> https://github.com/nanoc/nanoc/issues/new <-'
@@ -356,7 +354,5 @@ module Nanoc::CLI
         stream.puts "  #{index}. #{i}"
       end
     end
-
   end
-
 end
diff --git a/lib/nanoc/cli/logger.rb b/lib/nanoc/cli/logger.rb
index 596061b..c2b7355 100644
--- a/lib/nanoc/cli/logger.rb
+++ b/lib/nanoc/cli/logger.rb
@@ -3,19 +3,17 @@
 require 'singleton'
 
 module Nanoc::CLI
-
   # Nanoc::CLI::Logger is a singleton class responsible for generating
   # feedback in the terminal.
   class Logger
-
     # Maps actions (`:create`, `:update`, `:identical`, `:skip` and `:delete`)
     # onto their ANSI color codes.
     ACTION_COLORS = {
-      :create         => "\e[32m", # green
-      :update         => "\e[33m", # yellow
-      :identical      => '',       # (nothing)
-      :skip           => '',       # (nothing)
-      :delete         => "\e[31m"  # red
+      create: "\e[32m", # green
+      update: "\e[33m", # yellow
+      identical: '',    # (nothing)
+      skip: '',         # (nothing)
+      delete: "\e[31m"  # red
     }
 
     include Singleton
@@ -43,14 +41,12 @@ module Nanoc::CLI
     def file(level, action, name, duration = nil)
       log(
         level,
-        '%s%12s%s  %s%s' % [
+        format('%s%12s%s  %s%s',
           ACTION_COLORS[action.to_sym],
           action,
           "\e[0m",
-          duration.nil? ? '' : '[%2.2fs]  ' % [ duration ],
-          name
-        ]
-      )
+          duration.nil? ? '' : format('[%2.2fs]  ', duration),
+          name))
     end
 
     # Logs a message.
@@ -69,7 +65,5 @@ module Nanoc::CLI
       # Log when level permits it
       io.puts(message) if @level == :low || @level == level
     end
-
   end
-
 end
diff --git a/lib/nanoc/cli/stream_cleaners.rb b/lib/nanoc/cli/stream_cleaners.rb
index f879bd9..532410b 100644
--- a/lib/nanoc/cli/stream_cleaners.rb
+++ b/lib/nanoc/cli/stream_cleaners.rb
@@ -1,13 +1,9 @@
 # encoding: utf-8
 
 module Nanoc::CLI
-
   module StreamCleaners
-
     autoload 'Abstract',   'nanoc/cli/stream_cleaners/abstract'
     autoload 'ANSIColors', 'nanoc/cli/stream_cleaners/ansi_colors'
     autoload 'UTF8',       'nanoc/cli/stream_cleaners/utf8'
-
   end
-
 end
diff --git a/lib/nanoc/cli/stream_cleaners/abstract.rb b/lib/nanoc/cli/stream_cleaners/abstract.rb
index 6382533..37e4dca 100644
--- a/lib/nanoc/cli/stream_cleaners/abstract.rb
+++ b/lib/nanoc/cli/stream_cleaners/abstract.rb
@@ -1,23 +1,19 @@
 # encoding: utf-8
 
 module Nanoc::CLI::StreamCleaners
-
   # Superclass for all stream cleaners. Stream cleaners have a single method,
   # {#clean}, that takes a string and returns a cleaned string. Stream cleaners
   # can have state, so they can act as a FSM.
   #
   # @abstract Subclasses must implement {#clean}
   class Abstract
-
     # Returns a cleaned version of the given string.
     #
     # @param [String] s The string to clean
     #
     # @return [String] The cleaned string
-    def clean(s)
+    def clean(s) # rubocop:disable Lint/UnusedMethodArgument
       raise NotImplementedError, 'Subclasses of Nanoc::CLI::StreamCleaners::Abstract must implement #clean'
     end
-
   end
-
 end
diff --git a/lib/nanoc/cli/stream_cleaners/ansi_colors.rb b/lib/nanoc/cli/stream_cleaners/ansi_colors.rb
index 016a247..00aba5b 100644
--- a/lib/nanoc/cli/stream_cleaners/ansi_colors.rb
+++ b/lib/nanoc/cli/stream_cleaners/ansi_colors.rb
@@ -1,15 +1,11 @@
 # encoding: utf-8
 
 module Nanoc::CLI::StreamCleaners
-
   # Removes ANSI color escape sequences.
   class ANSIColors < Abstract
-
     # @see Nanoc::CLI::StreamCleaners::Abstract#clean
     def clean(s)
       s.gsub(/\e\[.+?m/, '')
     end
-
   end
-
 end
diff --git a/lib/nanoc/cli/stream_cleaners/utf8.rb b/lib/nanoc/cli/stream_cleaners/utf8.rb
index 7e488ff..f172760 100644
--- a/lib/nanoc/cli/stream_cleaners/utf8.rb
+++ b/lib/nanoc/cli/stream_cleaners/utf8.rb
@@ -1,16 +1,12 @@
 # encoding: utf-8
 
 module Nanoc::CLI::StreamCleaners
-
   # Simplifies output by replacing UTF-8 characters with their ASCII decompositions.
   class UTF8 < Abstract
-
     # @see Nanoc::CLI::StreamCleaners::Abstract#clean
     def clean(s)
-      # FIXME this decomposition is not generally usable
+      # FIXME: this decomposition is not generally usable
       s.gsub(/“|”/, '"').gsub(/‘|’/, '\'').gsub('…', '...').gsub('©', '(c)')
     end
-
   end
-
 end
diff --git a/lib/nanoc/data_sources.rb b/lib/nanoc/data_sources.rb
index 1a6a7b4..784fa98 100644
--- a/lib/nanoc/data_sources.rb
+++ b/lib/nanoc/data_sources.rb
@@ -1,7 +1,6 @@
 # encoding: utf-8
 
 module Nanoc::DataSources
-
   autoload 'Filesystem',         'nanoc/data_sources/filesystem'
   autoload 'FilesystemUnified',  'nanoc/data_sources/filesystem_unified'
   autoload 'FilesystemVerbose',  'nanoc/data_sources/filesystem_verbose'
@@ -12,7 +11,7 @@ module Nanoc::DataSources
   Nanoc::DataSource.register '::Nanoc::DataSources::Static',             :static
 
   # Deprecated; fetch data from online data sources manually instead
-  # TODO [in nanoc 4.0] remove me
+  # TODO: [in nanoc 4.0] remove me
   autoload 'Delicious', 'nanoc/data_sources/deprecated/delicious'
   autoload 'LastFM',    'nanoc/data_sources/deprecated/last_fm'
   autoload 'Twitter',   'nanoc/data_sources/deprecated/twitter'
@@ -21,11 +20,10 @@ module Nanoc::DataSources
   Nanoc::DataSource.register '::Nanoc::DataSources::Twitter',            :twitter
 
   # Deprecated; use `filesystem_verbose` or `filesystem_unified` instead
-  # TODO [in nanoc 4.0] remove me
+  # TODO: [in nanoc 4.0] remove me
   Nanoc::DataSource.register '::Nanoc::DataSources::FilesystemVerbose',  :filesystem
   Nanoc::DataSource.register '::Nanoc::DataSources::FilesystemUnified',  :filesystem_combined
   Nanoc::DataSource.register '::Nanoc::DataSources::FilesystemUnified',  :filesystem_compact
   FilesystemCombined = FilesystemUnified
   FilesystemCompact  = FilesystemUnified
-
 end
diff --git a/lib/nanoc/data_sources/deprecated/delicious.rb b/lib/nanoc/data_sources/deprecated/delicious.rb
index 5f956e7..f4ea0af 100644
--- a/lib/nanoc/data_sources/deprecated/delicious.rb
+++ b/lib/nanoc/data_sources/deprecated/delicious.rb
@@ -1,10 +1,8 @@
 # encoding: utf-8
 
 module Nanoc::DataSources
-
   # @deprecated Fetch data from online data sources manually instead
   class Delicious < Nanoc::DataSource
-
     def items
       @items ||= begin
         require 'json'
@@ -21,12 +19,12 @@ module Nanoc::DataSources
           # Get data
           content = raw_item['n']
           attributes = {
-            :url         => raw_item['u'],
-            :description => raw_item['d'],
-            :tags        => raw_item['t'],
-            :date        => Time.parse(raw_item['dt']),
-            :note        => raw_item['n'],
-            :author      => raw_item['a']
+            url: raw_item['u'],
+            description: raw_item['d'],
+            tags: raw_item['t'],
+            date: Time.parse(raw_item['dt']),
+            note: raw_item['n'],
+            author: raw_item['a']
           }
           identifier = "/#{i}/"
           mtime = nil
@@ -36,7 +34,5 @@ module Nanoc::DataSources
         end
       end
     end
-
   end
-
 end
diff --git a/lib/nanoc/data_sources/deprecated/last_fm.rb b/lib/nanoc/data_sources/deprecated/last_fm.rb
index c0c4323..5dbfa72 100644
--- a/lib/nanoc/data_sources/deprecated/last_fm.rb
+++ b/lib/nanoc/data_sources/deprecated/last_fm.rb
@@ -1,10 +1,8 @@
 # encoding: utf-8
 
 module Nanoc::DataSources
-
   # @deprecated Fetch data from online data sources manually instead
   class LastFM < Nanoc::DataSource
-
     def items
       @items ||= begin
         require 'json'
@@ -21,9 +19,9 @@ module Nanoc::DataSources
         # Get data
         @http_client ||= Nanoc::Extra::CHiCk::Client.new
         _status, _headers, data = *@http_client.get(
-          'http://ws.audioscrobbler.com/2.0/' +
-            '?method=user.getRecentTracks' +
-            '&format=json' +
+          'http://ws.audioscrobbler.com/2.0/' \
+            '?method=user.getRecentTracks' \
+            '&format=json' \
             '&user=' + URI.escape(config[:username]) +
             '&api_key=' + URI.escape(config[:api_key])
         )
@@ -36,8 +34,8 @@ module Nanoc::DataSources
         raw_items.enum_with_index.map do |raw_item, i|
           # Get artist data
           _artist_status, _artist_headers, artist_data = *@http_client.get(
-            'http://ws.audioscrobbler.com/2.0/' +
-              '?method=artist.getInfo' +
+            'http://ws.audioscrobbler.com/2.0/' \
+              '?method=artist.getInfo' \
               '&format=json' +
               (
                 if raw_item['artist']['mbid'].empty?
@@ -66,14 +64,14 @@ module Nanoc::DataSources
           end
 
           attributes = {
-            :name      => raw_item['name'],
-            :artist    => {
-              :name      => raw_artist_info['name'],
-              :url       => raw_artist_info['url']
+            name: raw_item['name'],
+            artist: {
+              name: raw_artist_info['name'],
+              url: raw_artist_info['url']
             },
-            :url       => raw_item['url'],
-            :played_at => track_played_at,
-            :now_playing => now_playing
+            url: raw_item['url'],
+            played_at: track_played_at,
+            now_playing: now_playing
           }
           identifier = "/#{i}/"
           mtime = nil
@@ -83,7 +81,5 @@ module Nanoc::DataSources
         end
       end
     end
-
   end
-
 end
diff --git a/lib/nanoc/data_sources/deprecated/twitter.rb b/lib/nanoc/data_sources/deprecated/twitter.rb
index a9181b7..6d742c8 100644
--- a/lib/nanoc/data_sources/deprecated/twitter.rb
+++ b/lib/nanoc/data_sources/deprecated/twitter.rb
@@ -1,10 +1,8 @@
 # encoding: utf-8
 
 module Nanoc::DataSources
-
   # @deprecated Fetch data from online data sources manually instead
   class Twitter < Nanoc::DataSource
-
     def items
       @item ||= begin
         require 'json'
@@ -17,12 +15,12 @@ module Nanoc::DataSources
         raw_items = JSON.parse(data)
 
         # Convert to items
-        raw_items.enum_with_index.map do |raw_item, i|
+        raw_items.enum_with_index.map do |raw_item, _i|
           # Get data
           content = raw_item['text']
           attributes = {
-            :created_at  => raw_item['created_at'],
-            :source      => raw_item['source']
+            created_at: raw_item['created_at'],
+            source: raw_item['source']
           }
           identifier = "/#{raw_item['id']}/"
           mtime = Time.parse(raw_item['created_at'])
@@ -32,7 +30,5 @@ module Nanoc::DataSources
         end
       end
     end
-
   end
-
 end
diff --git a/lib/nanoc/data_sources/filesystem.rb b/lib/nanoc/data_sources/filesystem.rb
index 366193b..dca8382 100644
--- a/lib/nanoc/data_sources/filesystem.rb
+++ b/lib/nanoc/data_sources/filesystem.rb
@@ -1,10 +1,8 @@
 # encoding: utf-8
 
 module Nanoc::DataSources
-
   # Provides functionality common across all filesystem data sources.
   module Filesystem
-
     # The VCS that will be called when adding, deleting and moving files. If
     # no VCS has been set, or if the VCS has been set to `nil`, a dummy VCS
     # will be returned.
@@ -34,7 +32,7 @@ module Nanoc::DataSources
     # See {Nanoc::DataSource#setup}.
     def setup
       # Create directories
-      [ content_dir_name, layouts_dir_name ].each do |dir|
+      [content_dir_name, layouts_dir_name].each do |dir|
         FileUtils.mkdir_p(dir)
         vcs.add(dir)
       end
@@ -60,12 +58,12 @@ module Nanoc::DataSources
       create_object(layouts_dir_name, content, attributes, identifier, params)
     end
 
-  protected
+    protected
 
     # Creates a new object (item or layout) on disk in dir_name according to
     # the given identifier. The file will have its attributes taken from the
     # attributes hash argument and its content from the content argument.
-    def create_object(dir_name, content, attributes, identifier, params = {})
+    def create_object(_dir_name, _content, _attributes, _identifier, _params = {})
       raise NotImplementedError.new(
         "#{self.class} does not implement ##{name}"
       )
@@ -89,7 +87,7 @@ module Nanoc::DataSources
         content_filename = filename_for(base_filename, content_ext)
 
         # Read content and metadata
-        is_binary = !!(content_filename && !@site.config[:text_extensions].include?(File.extname(content_filename)[1..-1]))
+        is_binary = content_filename && !@site.config[:text_extensions].include?(File.extname(content_filename)[1..-1])
         if is_binary && klass == Nanoc::Item
           meta                = (meta_filename && YAML.load_file(meta_filename)) || {}
           content_or_filename = content_filename
@@ -101,14 +99,14 @@ module Nanoc::DataSources
 
         # Get attributes
         attributes = {
-          :filename         => content_filename,
-          :content_filename => content_filename,
-          :meta_filename    => meta_filename,
-          :extension        => content_filename ? ext_of(content_filename)[1..-1] : nil,
+          filename: content_filename,
+          content_filename: content_filename,
+          meta_filename: meta_filename,
+          extension: content_filename ? ext_of(content_filename)[1..-1] : nil,
           # WARNING :file is deprecated; please create a File object manually
           # using the :content_filename or :meta_filename attributes.
-          # TODO [in nanoc 4.0] remove me
-          :file             => content_filename ? Nanoc::Extra::FileProxy.new(content_filename) : nil
+          # TODO: [in nanoc 4.0] remove me
+          file: content_filename ? Nanoc::Extra::FileProxy.new(content_filename) : nil
         }.merge(meta)
 
         # Get identifier
@@ -121,7 +119,7 @@ module Nanoc::DataSources
         end
 
         # Get modification times
-        meta_mtime    = meta_filename    ? File.stat(meta_filename).mtime    : nil
+        meta_mtime = meta_filename ? File.stat(meta_filename).mtime : nil
         content_mtime = content_filename ? File.stat(content_filename).mtime : nil
         if meta_mtime && content_mtime
           mtime = meta_mtime > content_mtime ? meta_mtime : content_mtime
@@ -136,7 +134,7 @@ module Nanoc::DataSources
         # Create layout object
         klass.new(
           content_or_filename, attributes, identifier,
-          :binary => is_binary, :mtime => mtime
+          binary: is_binary, mtime: mtime
         )
       end
     end
@@ -153,9 +151,10 @@ module Nanoc::DataSources
     #     'content/qux' => [ nil,    'html' ]
     #   }
     def all_split_files_in(dir_name)
-      grouped_filenames = all_files_in(dir_name).
-        reject   { |fn| fn =~ /(~|\.orig|\.rej|\.bak)$/ }.
-        group_by { |fn| basename_of(fn) }
+      grouped_filenames =
+        all_files_in(dir_name)
+        .reject   { |fn| fn =~ /(~|\.orig|\.rej|\.bak)$/ }
+        .group_by { |fn| basename_of(fn) }
 
       grouped_filenames.each_pair do |key, filenames|
         # Divide
@@ -163,15 +162,15 @@ module Nanoc::DataSources
         content_filenames = filenames.select { |fn| ext_of(fn) != '.yaml' }
 
         # Check number of files per type
-        if ![ 0, 1 ].include?(meta_filenames.size)
+        unless [0, 1].include?(meta_filenames.size)
           raise "Found #{meta_filenames.size} meta files for #{key}; expected 0 or 1"
         end
-        if ![ 0, 1 ].include?(content_filenames.size)
+        unless [0, 1].include?(content_filenames.size)
           raise "Found #{content_filenames.size} content files for #{key}; expected 0 or 1"
         end
 
         # Reorder elements and convert to extnames
-        filenames[0] = meta_filenames[0]    ? 'yaml'                                   : nil
+        filenames[0] = meta_filenames[0] ? 'yaml' : nil
         filenames[1] = content_filenames[0] ? ext_of(content_filenames[0])[1..-1] || '' : nil
       end
 
@@ -180,7 +179,7 @@ module Nanoc::DataSources
 
     # Returns all files in the given directory and directories below it.
     def all_files_in(dir_name)
-      Nanoc::Extra::FilesystemTools.all_files_in(dir_name)
+      Nanoc::Extra::FilesystemTools.all_files_in(dir_name, config[:extra_files])
     end
 
     # Returns the filename for the given base filename and the extension.
@@ -193,7 +192,7 @@ module Nanoc::DataSources
     # data sources may prefer to implement this differently (for example,
     # {Nanoc::DataSources::FilesystemVerbose} doubles the last part of the
     # basename before concatenating it with a period and the extension).
-    def filename_for(base_filename, ext)
+    def filename_for(_base_filename, _ext)
       raise NotImplementedError.new(
         "#{self.class} does not implement #filename_for"
       )
@@ -201,7 +200,7 @@ module Nanoc::DataSources
 
     # Returns the identifier that corresponds with the given filename, which
     # can be the content filename or the meta filename.
-    def identifier_for_filename(filename)
+    def identifier_for_filename(_filename)
       raise NotImplementedError.new(
         "#{self.class} does not implement #identifier_for_filename"
       )
@@ -235,7 +234,7 @@ module Nanoc::DataSources
     # Parses the file named `filename` and returns an array with its first
     # element a hash with the file's metadata, and with its second element the
     # file content itself.
-    def parse(content_filename, meta_filename, kind)
+    def parse(content_filename, meta_filename, _kind)
       # Read content and metadata from separate files
       if meta_filename
         content = content_filename ? read(content_filename) : ''
@@ -245,7 +244,7 @@ module Nanoc::DataSources
         rescue Exception => e
           raise "Could not parse YAML for #{meta_filename}: #{e.message}"
         end
-        return [ meta, content ]
+        return [meta, content]
       end
 
       # Read data
@@ -253,7 +252,7 @@ module Nanoc::DataSources
 
       # Check presence of metadata section
       if data !~ /\A-{3,5}\s*$/
-        return [ {}, data ]
+        return [{}, data]
       end
 
       # Split data
@@ -273,7 +272,7 @@ module Nanoc::DataSources
       content = pieces[4]
 
       # Done
-      [ meta, content ]
+      [meta, content]
     end
 
     # Reads the content of the file with the given name and returns a string
@@ -303,7 +302,7 @@ module Nanoc::DataSources
           raise_encoding_error(filename, original_encoding)
         end
 
-        if !data.valid_encoding?
+        unless data.valid_encoding?
           raise_encoding_error(filename, original_encoding)
         end
       end
@@ -318,7 +317,5 @@ module Nanoc::DataSources
     def raise_encoding_error(filename, encoding)
       raise RuntimeError.new("Could not read #{filename} because the file is not valid #{encoding}.")
     end
-
   end
-
 end
diff --git a/lib/nanoc/data_sources/filesystem_unified.rb b/lib/nanoc/data_sources/filesystem_unified.rb
index f10cc5e..36f7277 100644
--- a/lib/nanoc/data_sources/filesystem_unified.rb
+++ b/lib/nanoc/data_sources/filesystem_unified.rb
@@ -1,7 +1,6 @@
 # encoding: utf-8
 
 module Nanoc::DataSources
-
   # The filesystem_unified data source stores its items and layouts in nested
   # directories. Items and layouts are represented by one or two files; if it
   # is represented using one file, the metadata can be contained in this file.
@@ -68,10 +67,9 @@ module Nanoc::DataSources
   # understood by Ruby’s `Encoding`. If no encoding is set in the configuration,
   # one will be inferred from the environment.
   class FilesystemUnified < Nanoc::DataSource
-
     include Nanoc::DataSources::Filesystem
 
-  private
+    private
 
     # See {Nanoc::DataSources::Filesystem#create_object}.
     def create_object(dir_name, content, attributes, identifier, params = {})
@@ -121,7 +119,5 @@ module Nanoc::DataSources
       end
       filename.sub(regex, '').cleaned_identifier
     end
-
   end
-
 end
diff --git a/lib/nanoc/data_sources/filesystem_verbose.rb b/lib/nanoc/data_sources/filesystem_verbose.rb
index 6781dff..f2bea9a 100644
--- a/lib/nanoc/data_sources/filesystem_verbose.rb
+++ b/lib/nanoc/data_sources/filesystem_verbose.rb
@@ -1,7 +1,6 @@
 # encoding: utf-8
 
 module Nanoc::DataSources
-
   # The filesystem_verbose data source is the old data source for a new nanoc
   # site. It stores all data as files on the hard disk.
   #
@@ -40,10 +39,9 @@ module Nanoc::DataSources
   # understood by Ruby’s `Encoding`. If no encoding is set in the configuration,
   # one will be inferred from the environment.
   class FilesystemVerbose < Nanoc::DataSource
-
     include Nanoc::DataSources::Filesystem
 
-  private
+    private
 
     # See {Nanoc::DataSources::Filesystem#create_object}.
     def create_object(dir_name, content, attributes, identifier, params = {})
@@ -84,7 +82,5 @@ module Nanoc::DataSources
     def identifier_for_filename(filename)
       filename.sub(/[^\/]+\.yaml$/, '')
     end
-
   end
-
 end
diff --git a/lib/nanoc/data_sources/static.rb b/lib/nanoc/data_sources/static.rb
index 52a9e9d..83e0926 100644
--- a/lib/nanoc/data_sources/static.rb
+++ b/lib/nanoc/data_sources/static.rb
@@ -1,7 +1,6 @@
 # encoding: utf-8
 
 module Nanoc::DataSources
-
   # The static data source provides items from a single directory. Unlike the
   # filesystem data sources, static provides no additional item metadata. In
   # addition, all items are treated as 'binary', regardless of their extension
@@ -26,7 +25,6 @@ module Nanoc::DataSources
   # exclude them from the Blogging helper's atom feed generator, among other
   # things.
   class Static < Nanoc::DataSource
-
     identifier :static
 
     def items
@@ -36,8 +34,8 @@ module Nanoc::DataSources
       # Convert filenames to items
       all_files_in(prefix).map do |filename|
         attributes = {
-          :extension => File.extname(filename)[1..-1],
-          :filename  => filename,
+          extension: File.extname(filename)[1..-1],
+          filename: filename,
         }
         attributes[:is_hidden] = true unless config[:hide_items] == false
         identifier = filename[(prefix.length + 1)..-1] + '/'
@@ -48,17 +46,15 @@ module Nanoc::DataSources
           filename,
           attributes,
           identifier,
-          :binary => true, :mtime => mtime, :checksum => checksum
+          binary: true, mtime: mtime, checksum: checksum
         )
       end
     end
 
-  protected
+    protected
 
     def all_files_in(dir_name)
-      Nanoc::Extra::FilesystemTools.all_files_in(dir_name)
+      Nanoc::Extra::FilesystemTools.all_files_in(dir_name, config[:extra_files])
     end
-
   end
-
 end
diff --git a/lib/nanoc/extra.rb b/lib/nanoc/extra.rb
index 565f3ce..fc25718 100644
--- a/lib/nanoc/extra.rb
+++ b/lib/nanoc/extra.rb
@@ -1,7 +1,6 @@
 # encoding: utf-8
 
 module Nanoc::Extra
-
   autoload 'AutoCompiler',        'nanoc/extra/auto_compiler'
   autoload 'Checking',            'nanoc/extra/checking'
   autoload 'CHiCk',               'nanoc/extra/chick'
@@ -13,13 +12,12 @@ module Nanoc::Extra
   autoload 'JRubyNokogiriWarner', 'nanoc/extra/jruby_nokogiri_warner'
 
   # Deprecated; use {Nanoc::Context} instead
-  # TODO [in nanoc 4.0] remove me
+  # TODO: [in nanoc 4.0] remove me
   Context = ::Nanoc::Context
 
   # Deprecated
-  # TODO [in nanoc 4.0] remove me
+  # TODO: [in nanoc 4.0] remove me
   autoload 'FileProxy',         'nanoc/extra/file_proxy'
-
 end
 
 require 'nanoc/extra/core_ext'
diff --git a/lib/nanoc/extra/auto_compiler.rb b/lib/nanoc/extra/auto_compiler.rb
index ce2aa90..7190c65 100644
--- a/lib/nanoc/extra/auto_compiler.rb
+++ b/lib/nanoc/extra/auto_compiler.rb
@@ -1,11 +1,9 @@
 # encoding: utf-8
 
 module Nanoc::Extra
-
   # A web server that will automatically compile items as they are requested.
   # It also serves static files such as stylesheets and images.
   class AutoCompiler
-
     # @return [Nanoc::Site] The site this autocompiler belongs to
     attr_reader :site
 
@@ -38,10 +36,10 @@ module Nanoc::Extra
 
         # Find rep
         path = Rack::Utils.unescape(env['PATH_INFO'])
-        reps = site.items.map { |i| i.reps }.flatten
+        reps = site.items.map(&:reps).flatten
         rep = reps.find do |r|
           r.path == path ||
-            r.raw_path == site.config[:output_dir] + path
+          r.raw_path == site.config[:output_dir] + path
         end
 
         # Recompile
@@ -51,7 +49,7 @@ module Nanoc::Extra
         if path =~ /\/$/
           possible_paths = site.config[:index_filenames].map { |f| path + f }
         else
-          possible_paths = [ path ]
+          possible_paths = [path]
         end
 
         # Find matching file
@@ -67,7 +65,7 @@ module Nanoc::Extra
     rescue StandardError, ScriptError => e
       # Add compilation stack to env
       env['nanoc.stack'] = []
-      stack.reverse.each do |obj|
+      stack.reverse_each do |obj|
         if obj.is_a?(Nanoc::ItemRep) # item rep
           env['nanoc.stack'] << "[item] #{obj.item.identifier} (rep #{obj.name})"
         else # layout
@@ -79,7 +77,7 @@ module Nanoc::Extra
       raise e
     end
 
-  private
+    private
 
     def build_site
       @site = Nanoc::Site.new(@site_path)
@@ -97,7 +95,5 @@ module Nanoc::Extra
     def stack
       site.compiler.stack
     end
-
   end
-
 end
diff --git a/lib/nanoc/extra/checking.rb b/lib/nanoc/extra/checking.rb
index b411f9b..549e535 100644
--- a/lib/nanoc/extra/checking.rb
+++ b/lib/nanoc/extra/checking.rb
@@ -1,16 +1,12 @@
 # encoding: utf-8
 
 module Nanoc::Extra
-
   module Checking
-
     autoload 'Check',  'nanoc/extra/checking/check'
     autoload 'DSL',    'nanoc/extra/checking/dsl'
     autoload 'Runner', 'nanoc/extra/checking/runner.rb'
     autoload 'Issue',  'nanoc/extra/checking/issue'
-
   end
-
 end
 
 require 'nanoc/extra/checking/checks'
diff --git a/lib/nanoc/extra/checking/check.rb b/lib/nanoc/extra/checking/check.rb
index fc56b1c..8a0871f 100644
--- a/lib/nanoc/extra/checking/check.rb
+++ b/lib/nanoc/extra/checking/check.rb
@@ -1,9 +1,13 @@
 # encoding: utf-8
 
 module Nanoc::Extra::Checking
+  class OutputDirNotFoundError < Nanoc::Errors::Generic
+    def initialize(directory_path)
+      super("Unable to run check against output directory at “#{directory_path}”: directory does not exist.")
+    end
+  end
 
   class Check
-
     extend Nanoc::PluginRegistry::PluginMethods
 
     attr_reader :site
@@ -25,9 +29,11 @@ module Nanoc::Extra::Checking
     end
 
     def output_filenames
-      Dir[@site.config[:output_dir] + '/**/*'].select { |f| File.file?(f) }
+      output_dir = @site.config[:output_dir]
+      unless File.exist?(output_dir)
+        raise Nanoc::Extra::Checking::OutputDirNotFoundError.new(output_dir)
+      end
+      Dir[output_dir + '/**/*'].select { |f| File.file?(f) }
     end
-
   end
-
 end
diff --git a/lib/nanoc/extra/checking/checks.rb b/lib/nanoc/extra/checking/checks.rb
index 1886dd0..0dd2a40 100644
--- a/lib/nanoc/extra/checking/checks.rb
+++ b/lib/nanoc/extra/checking/checks.rb
@@ -1,12 +1,12 @@
 # encoding: utf-8
 
 module Nanoc::Extra::Checking::Checks
-
   autoload 'CSS',           'nanoc/extra/checking/checks/css'
   autoload 'ExternalLinks', 'nanoc/extra/checking/checks/external_links'
   autoload 'HTML',          'nanoc/extra/checking/checks/html'
   autoload 'InternalLinks', 'nanoc/extra/checking/checks/internal_links'
   autoload 'Stale',         'nanoc/extra/checking/checks/stale'
+  autoload 'MixedContent',  'nanoc/extra/checking/checks/mixed_content'
 
   Nanoc::Extra::Checking::Check.register '::Nanoc::Extra::Checking::Checks::CSS',           :css
   Nanoc::Extra::Checking::Check.register '::Nanoc::Extra::Checking::Checks::ExternalLinks', :external_links
@@ -15,5 +15,5 @@ module Nanoc::Extra::Checking::Checks
   Nanoc::Extra::Checking::Check.register '::Nanoc::Extra::Checking::Checks::InternalLinks', :internal_links
   Nanoc::Extra::Checking::Check.register '::Nanoc::Extra::Checking::Checks::InternalLinks', :ilinks
   Nanoc::Extra::Checking::Check.register '::Nanoc::Extra::Checking::Checks::Stale',         :stale
-
+  Nanoc::Extra::Checking::Check.register '::Nanoc::Extra::Checking::Checks::MixedContent',  :mixed_content
 end
diff --git a/lib/nanoc/extra/checking/checks/css.rb b/lib/nanoc/extra/checking/checks/css.rb
index 7d28c6d..a73bba0 100644
--- a/lib/nanoc/extra/checking/checks/css.rb
+++ b/lib/nanoc/extra/checking/checks/css.rb
@@ -1,9 +1,7 @@
 # encoding: utf-8
 
 module ::Nanoc::Extra::Checking::Checks
-
   class CSS < ::Nanoc::Extra::Checking::Check
-
     identifier :css
 
     def run
@@ -11,13 +9,15 @@ module ::Nanoc::Extra::Checking::Checks
 
       Dir[site.config[:output_dir] + '/**/*.css'].each do |filename|
         results = ::W3CValidators::CSSValidator.new.validate_file(filename)
+        lines = File.readlines(filename)
         results.errors.each do |e|
-          desc = e.message.gsub(%r{\s+}, ' ').strip
-          add_issue(desc, :subject => filename)
+          line_num = e.line.to_i - 1
+          line = lines[line_num]
+          message = e.message.gsub(%r{\s+}, ' ').strip.sub(/\s+:$/, '')
+          desc = "line #{line_num + 1}: #{message}: #{line}"
+          add_issue(desc, subject: filename)
         end
       end
     end
-
   end
-
 end
diff --git a/lib/nanoc/extra/checking/checks/external_links.rb b/lib/nanoc/extra/checking/checks/external_links.rb
index c4e8f20..a1a2e53 100644
--- a/lib/nanoc/extra/checking/checks/external_links.rb
+++ b/lib/nanoc/extra/checking/checks/external_links.rb
@@ -7,15 +7,13 @@ require 'timeout'
 require 'uri'
 
 module ::Nanoc::Extra::Checking::Checks
-
   # A validator that verifies that all external links point to a location that exists.
   class ExternalLinks < ::Nanoc::Extra::Checking::Check
-
     identifiers :external_links, :elinks
 
     def run
       # Find all broken external hrefs
-      # TODO de-duplicate this (duplicated in internal links check)
+      # TODO: de-duplicate this (duplicated in internal links check)
       filenames = output_filenames.select { |f| File.extname(f) == '.html' }
       hrefs_with_filenames = ::Nanoc::Extra::LinkCollector.new(filenames, :external).filenames_per_href
       results = select_invalid(hrefs_with_filenames.keys)
@@ -26,13 +24,12 @@ module ::Nanoc::Extra::Checking::Checks
         filenames.each do |filename|
           add_issue(
             "broken reference to #{res.href}: #{res.explanation}",
-            :subject => filename)
+            subject: filename)
         end
       end
     end
 
     class Result
-
       attr_reader :href
       attr_reader :explanation
 
@@ -40,11 +37,9 @@ module ::Nanoc::Extra::Checking::Checks
         @href        = href
         @explanation = explanation
       end
-
     end
 
     class ArrayEnumerator
-
       def initialize(array)
         @array = array
         @index = 0
@@ -57,7 +52,6 @@ module ::Nanoc::Extra::Checking::Checks
           return @array[@index - 1]
         end
       end
-
     end
 
     def select_invalid(hrefs)
@@ -71,16 +65,17 @@ module ::Nanoc::Extra::Checking::Checks
           loop do
             href = enum.next
             break if href.nil?
+
             res = validate(href)
-            if res
-              mutex.synchronize do
-                invalid << res
-              end
+            next unless res
+
+            mutex.synchronize do
+              invalid << res
             end
           end
         end
       end
-      threads.each { |t| t.join }
+      threads.each(&:join)
 
       invalid
     end
@@ -99,16 +94,19 @@ module ::Nanoc::Extra::Checking::Checks
 
       # Get status
       res = nil
+      last_err = nil
+      timeouts = [3, 5, 10, 30, 60]
       5.times do |i|
         begin
-          Timeout.timeout(10) do
+          Timeout.timeout(timeouts[i]) do
             res = request_url_once(url)
             if res.code == '405'
               res = request_url_once(url, Net::HTTP::Get)
             end
           end
         rescue => e
-          return Result.new(href, e.message)
+          last_err = e
+          next # can not allow
         end
 
         if res.code =~ /^3..$/
@@ -133,7 +131,11 @@ module ::Nanoc::Extra::Checking::Checks
           return Result.new(href, res.code)
         end
       end
-      raise 'should not have gotten here'
+      if last_err
+        return Result.new(href, last_err.message)
+      else
+        raise 'should not have gotten here'
+      end
     end
 
     def path_for_url(url)
@@ -159,7 +161,5 @@ module ::Nanoc::Extra::Checking::Checks
       end
       http.request(req)
     end
-
   end
-
 end
diff --git a/lib/nanoc/extra/checking/checks/html.rb b/lib/nanoc/extra/checking/checks/html.rb
index 43a2234..c932064 100644
--- a/lib/nanoc/extra/checking/checks/html.rb
+++ b/lib/nanoc/extra/checking/checks/html.rb
@@ -1,9 +1,7 @@
 # encoding: utf-8
 
 module ::Nanoc::Extra::Checking::Checks
-
   class HTML < ::Nanoc::Extra::Checking::Check
-
     identifier :html
 
     def run
@@ -11,13 +9,15 @@ module ::Nanoc::Extra::Checking::Checks
 
       Dir[site.config[:output_dir] + '/**/*.{htm,html}'].each do |filename|
         results = ::W3CValidators::MarkupValidator.new.validate_file(filename)
+        lines = File.readlines(filename)
         results.errors.each do |e|
-          desc = e.message.gsub(%r{\s+}, ' ').strip
-          add_issue(desc, :subject => filename)
+          line_num = e.line.to_i - 1
+          line = lines[line_num]
+          message = e.message.gsub(%r{\s+}, ' ').strip.sub(/\s+:$/, '')
+          desc = "line #{line_num + 1}: #{message}: #{line}"
+          add_issue(desc, subject: filename)
         end
       end
     end
-
   end
-
 end
diff --git a/lib/nanoc/extra/checking/checks/internal_links.rb b/lib/nanoc/extra/checking/checks/internal_links.rb
index fa9e856..4e5c177 100644
--- a/lib/nanoc/extra/checking/checks/internal_links.rb
+++ b/lib/nanoc/extra/checking/checks/internal_links.rb
@@ -3,10 +3,8 @@
 require 'uri'
 
 module Nanoc::Extra::Checking::Checks
-
   # A check that verifies that all internal links point to a location that exists.
   class InternalLinks < ::Nanoc::Extra::Checking::Check
-
     # Starts the validator. The results will be printed to stdout.
     #
     # Internal links that match a regexp pattern in `@config[:checks][:internal_links][:exclude]` will
@@ -14,25 +12,25 @@ module Nanoc::Extra::Checking::Checks
     #
     # @return [void]
     def run
-      # TODO de-duplicate this (duplicated in external links check)
+      # TODO: de-duplicate this (duplicated in external links check)
       filenames = output_filenames.select { |f| File.extname(f) == '.html' }
       hrefs_with_filenames = ::Nanoc::Extra::LinkCollector.new(filenames, :internal).filenames_per_href
       hrefs_with_filenames.each_pair do |href, fns|
         fns.each do |filename|
-          unless valid?(href, filename)
+          next if valid?(href, filename)
+
           add_issue(
             "broken reference to #{href}",
-            :subject  => filename)
-          end
+            subject: filename)
         end
       end
     end
 
-  protected
+    protected
 
     def valid?(href, origin)
       # Skip hrefs that point to self
-      # FIXME this is ugly and won’t always be correct
+      # FIXME: this is ugly and won’t always be correct
       return true if href == '.'
 
       # Skip hrefs that are specified in the exclude configuration
@@ -70,7 +68,5 @@ module Nanoc::Extra::Checking::Checks
       excludes =  @site.config.fetch(:checks, {}).fetch(:internal_links, {}).fetch(:exclude, [])
       excludes.any? { |pattern| Regexp.new(pattern).match(href) }
     end
-
   end
-
 end
diff --git a/lib/nanoc/extra/checking/checks/mixed_content.rb b/lib/nanoc/extra/checking/checks/mixed_content.rb
new file mode 100644
index 0000000..85a6e0a
--- /dev/null
+++ b/lib/nanoc/extra/checking/checks/mixed_content.rb
@@ -0,0 +1,31 @@
+# encoding: utf-8
+
+module Nanoc::Extra::Checking::Checks
+  # A check that verifies HTML files do not reference external resources with
+  # URLs that would trigger "mixed content" warnings.
+  class MixedContent < ::Nanoc::Extra::Checking::Check
+    PROTOCOL_PATTERN = /^(\w+):\/\//
+
+    def run
+      filenames = output_filenames.select { |f| File.extname(f) == '.html' }
+      resource_uris_with_filenames = ::Nanoc::Extra::LinkCollector.new(filenames).filenames_per_resource_uri
+
+      resource_uris_with_filenames.each_pair do |uri, fns|
+        next unless guaranteed_insecure?(uri)
+        fns.each do |filename|
+          add_issue(
+            "mixed content include: #{uri}",
+            subject: filename)
+        end
+      end
+    end
+
+    private
+
+    def guaranteed_insecure?(href)
+      protocol = PROTOCOL_PATTERN.match(href)
+
+      protocol && protocol[1].downcase == 'http'
+    end
+  end
+end
diff --git a/lib/nanoc/extra/checking/checks/stale.rb b/lib/nanoc/extra/checking/checks/stale.rb
index 72aad05..7df56da 100644
--- a/lib/nanoc/extra/checking/checks/stale.rb
+++ b/lib/nanoc/extra/checking/checks/stale.rb
@@ -1,31 +1,27 @@
 # encoding: utf-8
 
 module Nanoc::Extra::Checking::Checks
-
   class Stale < ::Nanoc::Extra::Checking::Check
-
     def run
       require 'set'
 
-      item_rep_paths = Set.new(@site.items.map { |i| i.reps }.flatten.map { |r| r.raw_path })
+      item_rep_paths = Set.new(@site.items.map(&:reps).flatten.map(&:raw_path))
 
       output_filenames.each do |f|
         next if pruner.filename_excluded?(f)
-        if !item_rep_paths.include?(f)
-          add_issue(
-            'file without matching item',
-            :subject  => f)
-        end
+        next if item_rep_paths.include?(f)
+
+        add_issue(
+          'file without matching item',
+          subject: f)
       end
     end
 
-  protected
+    protected
 
     def pruner
       exclude_config = @site.config.fetch(:prune, {}).fetch(:exclude, [])
-      @pruner ||= Nanoc::Extra::Pruner.new(@site, :exclude => exclude_config)
+      @pruner ||= Nanoc::Extra::Pruner.new(@site, exclude: exclude_config)
     end
-
   end
-
 end
diff --git a/lib/nanoc/extra/checking/dsl.rb b/lib/nanoc/extra/checking/dsl.rb
index 42243fd..97b5032 100644
--- a/lib/nanoc/extra/checking/dsl.rb
+++ b/lib/nanoc/extra/checking/dsl.rb
@@ -1,9 +1,7 @@
 # encoding: utf-8
 
 module Nanoc::Extra::Checking
-
   class DSL
-
     attr_reader :deploy_checks
 
     def self.from_file(filename)
@@ -25,7 +23,5 @@ module Nanoc::Extra::Checking
     def deploy_check(*identifiers)
       identifiers.each { |i| @deploy_checks << i }
     end
-
   end
-
 end
diff --git a/lib/nanoc/extra/checking/issue.rb b/lib/nanoc/extra/checking/issue.rb
index cde7646..b2f22c3 100644
--- a/lib/nanoc/extra/checking/issue.rb
+++ b/lib/nanoc/extra/checking/issue.rb
@@ -1,9 +1,7 @@
 # encoding: utf-8
 
 module Nanoc::Extra::Checking
-
   class Issue
-
     attr_reader :description
     attr_reader :subject
     attr_reader :check_class
@@ -13,7 +11,5 @@ module Nanoc::Extra::Checking
       @subject       = subject
       @check_class = check_class
     end
-
   end
-
 end
diff --git a/lib/nanoc/extra/checking/runner.rb b/lib/nanoc/extra/checking/runner.rb
index e2074a8..d5d1a4a 100644
--- a/lib/nanoc/extra/checking/runner.rb
+++ b/lib/nanoc/extra/checking/runner.rb
@@ -1,12 +1,10 @@
 # encoding: utf-8
 
 module Nanoc::Extra::Checking
-
   # Runner is reponsible for running issue checks.
   #
   # @api private
   class Runner
-
     CHECKS_FILENAMES = ['Checks', 'Checks.rb', 'checks', 'checks.rb']
 
     # @param [Nanoc::Site] site The nanoc site this runner is for
@@ -14,15 +12,16 @@ module Nanoc::Extra::Checking
       @site = site
     end
 
-    # @param [String] The name of the Checks file
+    # @return [String] The name of the Checks file
     def checks_filename
       @_checks_filename ||= CHECKS_FILENAMES.find { |f| File.file?(f) }
     end
 
     # @return [Boolean] true if a Checks file exists, false otherwise
-    def has_dsl?
+    def dsl_present?
       checks_filename && File.file?(checks_filename)
     end
+    alias_method :has_dsl?, :dsl_present?
 
     # Lists all available checks on stdout.
     #
@@ -65,12 +64,12 @@ module Nanoc::Extra::Checking
       run_check_classes(check_classes_named(check_class_names))
     end
 
-  protected
+    protected
 
     def load_dsl_if_available
       @dsl_loaded ||= false
-      if !@dsl_loaded
-        if self.has_dsl?
+      unless @dsl_loaded
+        if self.dsl_present?
           @dsl = Nanoc::Extra::Checking::DSL.from_file(checks_filename)
         else
           @dsl = nil
@@ -97,7 +96,7 @@ module Nanoc::Extra::Checking
     end
 
     def all_check_classes
-      Nanoc::Extra::Checking::Check.all.map { |p| p.last }.uniq
+      Nanoc::Extra::Checking::Check.all.map(&:last).uniq
     end
 
     def check_classes_named(n)
@@ -123,7 +122,7 @@ module Nanoc::Extra::Checking
         checks << check
         issues.merge(check.issues)
 
-        # TODO report progress
+        # TODO: report progress
 
         puts check.issues.empty? ? 'ok'.green : 'error'.red
       end
@@ -135,17 +134,16 @@ module Nanoc::Extra::Checking
 
       return if issues.empty?
       puts 'Issues found!'
-      issues.group_by { |i| i.subject }.to_a.sort_by { |p| p.first }.each do |pair|
+      issues.group_by(&:subject).to_a.sort_by(&:first).each do |pair|
         subject = pair.first
         issues  = pair.last
-        unless issues.empty?
-          puts "  #{subject}:"
-          issues.each do |i|
-            puts "    [ #{'ERROR'.red} ] #{i.check_class.identifier} - #{i.description}"
-          end
+        next if issues.empty?
+
+        puts "  #{subject}:"
+        issues.each do |i|
+          puts "    [ #{'ERROR'.red} ] #{i.check_class.identifier} - #{i.description}"
         end
       end
     end
   end
-
 end
diff --git a/lib/nanoc/extra/chick.rb b/lib/nanoc/extra/chick.rb
index e66540d..2853185 100644
--- a/lib/nanoc/extra/chick.rb
+++ b/lib/nanoc/extra/chick.rb
@@ -5,24 +5,21 @@ require 'rack'
 require 'rack/cache'
 
 module Nanoc::Extra
-
   # @deprecated Use a HTTP library such as
   #   [Net::HTTP](http://ruby-doc.org/stdlib/libdoc/net/http/rdoc/) or
   #   [Curb](https://github.com/taf2/curb) instead.
   module CHiCk
-
     # @deprecated Use a HTTP library such as
     #   [Net::HTTP](http://ruby-doc.org/stdlib/libdoc/net/http/rdoc/) or
     #   [Curb](https://github.com/taf2/curb) instead.
     class Client
-
       DEFAULT_OPTIONS = {
-        :cache => {
-          :metastore   => 'file:tmp/rack/cache.meta',
-          :entitystore => 'file:tmp/rack/cache.body'
+        cache: {
+          metastore: 'file:tmp/rack/cache.meta',
+          entitystore: 'file:tmp/rack/cache.body'
         },
-        :cache_controller => {
-          :max_age => 60
+        cache_controller: {
+          max_age: 60
         }
       }
 
@@ -37,13 +34,13 @@ module Nanoc::Extra
         # Build app
         options = @options
         @app ||= Rack::Builder.new do
-          use Rack::Cache, options[:cache].merge(:verbose => true)
+          use Rack::Cache, options[:cache].merge(verbose: true)
           use Nanoc::Extra::CHiCk::CacheController, options[:cache_controller]
           run Nanoc::Extra::CHiCk::RackClient
         end
 
         # Build environment for request
-        env = Rack::MockRequest.env_for(url, :method => 'GET')
+        env = Rack::MockRequest.env_for(url, method: 'GET')
 
         # Fetch
         puts "[CHiCk] Fetching #{url}..." if $DEBUG
@@ -55,16 +52,14 @@ module Nanoc::Extra
         body_parts.each { |part| body << part }
 
         # Done
-        [ status, headers, body ]
+        [status, headers, body]
       end
-
     end
 
     # @deprecated Use a HTTP library such as
     #   [Net::HTTP](http://ruby-doc.org/stdlib/libdoc/net/http/rdoc/) or
     #   [Curb](https://github.com/taf2/curb) instead.
     class CacheController
-
       def initialize(app, options = {})
         @app = app
         @options = options
@@ -77,14 +72,12 @@ module Nanoc::Extra
         end
         res
       end
-
     end
 
     # @deprecated Use a HTTP library such as
     #   [Net::HTTP](http://ruby-doc.org/stdlib/libdoc/net/http/rdoc/) or
     #   [Curb](https://github.com/taf2/curb) instead.
     class RackClient
-
       METHOD_TO_CLASS_MAPPING = {
         'DELETE'  => Net::HTTP::Delete,
         'GET'     => Net::HTTP::Get,
@@ -115,13 +108,10 @@ module Nanoc::Extra
           return [
             response.code.to_i,
             response.to_hash.reduce({}) { |m, (k, v)| m.merge(k => v[0]) },
-            [ response.body ]
+            [response.body]
           ]
         end
       end
-
     end
-
   end
-
 end
diff --git a/lib/nanoc/extra/core_ext.rb b/lib/nanoc/extra/core_ext.rb
index 1ee2300..4293a2d 100644
--- a/lib/nanoc/extra/core_ext.rb
+++ b/lib/nanoc/extra/core_ext.rb
@@ -1,5 +1,4 @@
 # encoding: utf-8
 
-require 'nanoc/extra/core_ext/enumerable'
 require 'nanoc/extra/core_ext/pathname'
 require 'nanoc/extra/core_ext/time'
diff --git a/lib/nanoc/extra/core_ext/enumerable.rb b/lib/nanoc/extra/core_ext/enumerable.rb
deleted file mode 100644
index a40e5f7..0000000
--- a/lib/nanoc/extra/core_ext/enumerable.rb
+++ /dev/null
@@ -1,33 +0,0 @@
-# encoding: utf-8
-
-module Enumerable
-
-  if !Enumerable.instance_methods.include?('group_by')
-
-    # Returns a hash, which keys are evaluated result from the block, and
-    # values are arrays of elements in enum corresponding to the key. This
-    # method is provided for backward compatibility with Ruby 1.8.6 and lower,
-    # since {#group_by} is only available in 1.8.7 and higher.
-    #
-    # @yieldparam [Object] obj The object to classify
-    #
-    # @return [Hash]
-    #
-    # @example Grouping integers by rest by division through 3
-    #
-    #   (1..6).group_by { |i| i % 3 }
-    #   # => { 0 => [3, 6], 1 => [1, 4], 2 => [2, 5] }
-    def group_by
-      groups = {}
-      each do |item|
-        key = yield(item)
-
-        groups[key] ||= []
-        groups[key] << item
-      end
-      groups
-    end
-
-  end
-
-end
diff --git a/lib/nanoc/extra/core_ext/pathname.rb b/lib/nanoc/extra/core_ext/pathname.rb
index 8dc502e..73933be 100644
--- a/lib/nanoc/extra/core_ext/pathname.rb
+++ b/lib/nanoc/extra/core_ext/pathname.rb
@@ -1,9 +1,7 @@
 # encoding: utf-8
 
 module Nanoc::Extra
-
   module PathnameExtensions
-
     def components
       components = []
       tmp = self
@@ -19,9 +17,7 @@ module Nanoc::Extra
     def include_component?(component)
       components.include?(component)
     end
-
   end
-
 end
 
 class ::Pathname
diff --git a/lib/nanoc/extra/core_ext/time.rb b/lib/nanoc/extra/core_ext/time.rb
index 64b0a35..125f50b 100644
--- a/lib/nanoc/extra/core_ext/time.rb
+++ b/lib/nanoc/extra/core_ext/time.rb
@@ -1,7 +1,6 @@
 # encoding: utf-8
 
 module Nanoc::Extra::TimeExtensions
-
   # @return [String] The time in an ISO-8601 date format.
   def to_iso8601_date
     strftime('%Y-%m-%d')
@@ -11,7 +10,6 @@ module Nanoc::Extra::TimeExtensions
   def to_iso8601_time
     getutc.strftime('%Y-%m-%dT%H:%M:%SZ')
   end
-
 end
 
 class Time
diff --git a/lib/nanoc/extra/deployer.rb b/lib/nanoc/extra/deployer.rb
index 4d32ba3..4d86366 100644
--- a/lib/nanoc/extra/deployer.rb
+++ b/lib/nanoc/extra/deployer.rb
@@ -1,13 +1,11 @@
 # encoding: utf-8
 
 module Nanoc::Extra
-
   # Represents a deployer, an object that allows uploading the compiled site
   # to a specific (remote) location.
   #
   # @abstract Subclass and override {#run} to implement a custom filter.
   class Deployer
-
     extend Nanoc::PluginRegistry::PluginMethods
 
     # @return [String] The path to the directory that contains the files to
@@ -41,7 +39,5 @@ module Nanoc::Extra
     def run
       raise NotImplementedError.new('Nanoc::Extra::Deployer subclasses must implement #run')
     end
-
   end
-
 end
diff --git a/lib/nanoc/extra/deployers.rb b/lib/nanoc/extra/deployers.rb
index 866dcf7..a1bc5ac 100644
--- a/lib/nanoc/extra/deployers.rb
+++ b/lib/nanoc/extra/deployers.rb
@@ -1,15 +1,11 @@
 # encoding: utf-8
 
 module Nanoc::Extra
-
   module Deployers
-
     autoload 'Fog',   'nanoc/extra/deployers/fog'
     autoload 'Rsync', 'nanoc/extra/deployers/rsync'
 
     Nanoc::Extra::Deployer.register '::Nanoc::Extra::Deployers::Fog',   :fog
     Nanoc::Extra::Deployer.register '::Nanoc::Extra::Deployers::Rsync', :rsync
-
   end
-
 end
diff --git a/lib/nanoc/extra/deployers/fog.rb b/lib/nanoc/extra/deployers/fog.rb
index b8b81fd..56748cd 100644
--- a/lib/nanoc/extra/deployers/fog.rb
+++ b/lib/nanoc/extra/deployers/fog.rb
@@ -1,7 +1,6 @@
 # encoding: utf-8
 
 module Nanoc::Extra::Deployers
-
   # A deployer that deploys a site using [fog](https://github.com/geemus/fog).
   #
   # @example A deployment configuration with public and staging configurations
@@ -9,6 +8,10 @@ module Nanoc::Extra::Deployers
   #   deploy:
   #     public:
   #       kind:       fog
+  #       bucket:     nanoc-site
+  #       cdn_id:     XXXXXX
+  #     preprod:
+  #       kind:       fog
   #       provider:   local
   #       local_root: ~/myCloud
   #       bucket:     nanoc-site
@@ -18,7 +21,6 @@ module Nanoc::Extra::Deployers
   #       local_root: ~/myCloud
   #       bucket:     nanoc-site-staging
   class Fog < ::Nanoc::Extra::Deployer
-
     # @see Nanoc::Extra::Deployer#run
     def run
       require 'fog'
@@ -27,6 +29,7 @@ module Nanoc::Extra::Deployers
       src      = File.expand_path(source_path)
       bucket   = config.delete(:bucket) || config.delete(:bucket_name)
       path     = config.delete(:path)
+      cdn_id   = config.delete(:cdn_id)
 
       config.delete(:kind)
 
@@ -35,6 +38,7 @@ module Nanoc::Extra::Deployers
 
       # Mock if necessary
       if self.dry_run?
+        puts 'Dry run - simulation'
         ::Fog.mock!
       end
 
@@ -45,7 +49,7 @@ module Nanoc::Extra::Deployers
       # Get bucket
       puts 'Getting bucket'
       begin
-        directory = connection.directories.get(bucket, :prefix => path)
+        directory = connection.directories.get(bucket, prefix: path)
       rescue ::Excon::Errors::NotFound
         should_create_bucket = true
       end
@@ -53,18 +57,20 @@ module Nanoc::Extra::Deployers
 
       # Create bucket if necessary
       if should_create_bucket
-        directory = connection.directories.create(:key => bucket, :prefix => path)
+        puts 'Creating bucket'
+        directory = connection.directories.create(key: bucket, prefix: path)
       end
 
       # Get list of remote files
       files = directory.files
       truncated = files.respond_to?(:is_truncated) && files.is_truncated
       while truncated
-        set = directory.files.all(:marker => files.last.key)
+        set = directory.files.all(marker: files.last.key)
         truncated = set.is_truncated
-        files = files + set
+        files += set
       end
-      keys_to_destroy = files.all.map { |file| file.key }
+      keys_to_destroy = files.all.map(&:key)
+      keys_to_invalidate = []
 
       # Upload all the files in the output folder to the clouds
       puts 'Uploading local files'
@@ -73,10 +79,11 @@ module Nanoc::Extra::Deployers
         files.each do |file_path|
           key = path ? File.join(path, file_path) : file_path
           directory.files.create(
-            :key => key,
-            :body => File.open(file_path),
-            :public => true)
+            key: key,
+            body: File.open(file_path),
+            public: true)
           keys_to_destroy.delete(key)
+          keys_to_invalidate.push(key)
         end
       end
 
@@ -86,16 +93,29 @@ module Nanoc::Extra::Deployers
         directory.files.get(key).destroy
       end
 
+      # invalidate CDN objects
+      if cdn_id
+        puts 'Invalidating CDN distribution'
+        keys_to_invalidate.concat(keys_to_destroy)
+        cdn = ::Fog::CDN.new(config)
+        # fog cannot mock CDN requests
+        unless self.dry_run?
+          distribution = cdn.get_distribution(cdn_id)
+          # usual limit per invalidation: 1000 objects
+          keys_to_invalidate.each_slice(1000) do |paths|
+            cdn.post_invalidation(distribution, paths)
+          end
+        end
+      end
+
       puts 'Done!'
     end
 
-  private
+    private
 
     # Prints the given message on stderr and exits.
     def error(msg)
       raise RuntimeError.new(msg)
     end
-
   end
-
 end
diff --git a/lib/nanoc/extra/deployers/rsync.rb b/lib/nanoc/extra/deployers/rsync.rb
index 09fd0c8..b768be9 100644
--- a/lib/nanoc/extra/deployers/rsync.rb
+++ b/lib/nanoc/extra/deployers/rsync.rb
@@ -1,7 +1,6 @@
 # encoding: utf-8
 
 module Nanoc::Extra::Deployers
-
   # A deployer that deploys a site using rsync.
   #
   # The configuration has should include a `:dst` value, a string containing
@@ -20,7 +19,6 @@ module Nanoc::Extra::Deployers
   #       dst: "ectype:sites/stoneship-staging/public"
   #       options: [ "-glpPrtvz" ]
   class Rsync < ::Nanoc::Extra::Deployer
-
     # Default rsync options
     DEFAULT_OPTIONS = [
       '--group',
@@ -51,19 +49,17 @@ module Nanoc::Extra::Deployers
       # Run
       if dry_run
         warn 'Performing a dry-run; no actions will actually be performed'
-        run_shell_cmd([ 'echo', 'rsync', options, src, dst ].flatten)
+        run_shell_cmd(['echo', 'rsync', options, src, dst].flatten)
       else
-        run_shell_cmd([ 'rsync', options, src, dst ].flatten)
+        run_shell_cmd(['rsync', options, src, dst].flatten)
       end
     end
 
-  private
+    private
 
     def run_shell_cmd(cmd)
-      piper = Nanoc::Extra::Piper.new(:stdout => $stdout, :stderr => $stderr)
+      piper = Nanoc::Extra::Piper.new(stdout: $stdout, stderr: $stderr)
       piper.run(cmd, nil)
     end
-
   end
-
 end
diff --git a/lib/nanoc/extra/file_proxy.rb b/lib/nanoc/extra/file_proxy.rb
index fb911c7..6456e16 100644
--- a/lib/nanoc/extra/file_proxy.rb
+++ b/lib/nanoc/extra/file_proxy.rb
@@ -1,10 +1,8 @@
 # encoding: utf-8
 
 module Nanoc::Extra
-
   # @deprecated Create a File instance directly and use that instead.
   class FileProxy
-
     instance_methods.each { |m| undef_method m unless m =~ /^__/ || m.to_s == 'object_id' }
 
     @@deprecation_warning_shown = false
@@ -16,12 +14,12 @@ module Nanoc::Extra
     def freeze
     end
 
-    def respond_to?(meth, include_all = false)
+    def respond_to?(meth, _include_all = false)
       file_instance_methods.include?(meth.to_sym)
     end
 
     def method_missing(sym, *args, &block)
-      if !@@deprecation_warning_shown
+      unless @@deprecation_warning_shown
         $stderr.puts 'WARNING: The :file attribute is deprecated and will be removed in a future version of nanoc. Instead of using this :file attribute, consider manually creating a File object when it’s needed, using the :content_filename, :meta_filename or :filename attributes.'
         @@deprecation_warning_shown = true
       end
@@ -29,12 +27,10 @@ module Nanoc::Extra
       File.open(@path, 'r') { |io| io.__send__(sym, *args, &block) }
     end
 
-  private
+    private
 
     def file_instance_methods
-      @@file_instance_methods ||= Set.new(File.instance_methods.map { |m| m.to_sym })
+      @@file_instance_methods ||= Set.new(File.instance_methods.map(&:to_sym))
     end
-
   end
-
 end
diff --git a/lib/nanoc/extra/filesystem_tools.rb b/lib/nanoc/extra/filesystem_tools.rb
index 175e819..59d4e04 100644
--- a/lib/nanoc/extra/filesystem_tools.rb
+++ b/lib/nanoc/extra/filesystem_tools.rb
@@ -1,17 +1,14 @@
 # encoding: utf-8
 
 module Nanoc::Extra
-
   # Contains useful functions for managing the filesystem.
   #
   # @api private
   module FilesystemTools
-
     # Error that is raised when too many symlink indirections are encountered.
     #
     # @api private
     class MaxSymlinkDepthExceededError < ::Nanoc::Errors::GenericTrivial
-
       # @return [String] The last filename that was attempted to be
       #   resolved before giving up
       attr_reader :filename
@@ -22,7 +19,6 @@ module Nanoc::Extra
         @filename = filename
         super("Too many indirections while resolving symlinks. I gave up after finding out #{filename} was yet another symlink. Sorry!")
       end
-
     end
 
     # Error that is raised when a file of an unknown type is encountered
@@ -30,7 +26,6 @@ module Nanoc::Extra
     #
     # @api private
     class UnsupportedFileTypeError < ::Nanoc::Errors::GenericTrivial
-
       # @return [String] The filename of the file whose type is not supported
       attr_reader :filename
 
@@ -40,7 +35,6 @@ module Nanoc::Extra
         @filename = filename
         super("The file at #{filename} is of an unsupported type (expected file, directory or link, but it is #{File.ftype(filename)}")
       end
-
     end
 
     # Returns all files in the given directory and directories below it,
@@ -49,18 +43,22 @@ module Nanoc::Extra
     # @param [String] dir_name The name of the directory whose contents to
     #   fetch
     #
+    # @param [String, Array, nil] extra_files The list of extra patterns
+    #   to extend the file search for files not found by default, example
+    #   "**/.{htaccess,htpasswd}"
+    #
     # @param [Integer] recursion_limit The maximum number of times to
     #   recurse into a symlink to a directory
     #
-    # @return [Array<String>] A list of filenames
+    # @return [Array<String>] A list of file names
     #
     # @raise [MaxSymlinkDepthExceededError] if too many indirections are
     #   encountered while resolving symlinks
     #
     # @raise [UnsupportedFileTypeError] if a file of an unsupported type is
     #   detected (something other than file, directory or link)
-    def all_files_in(dir_name, recursion_limit = 10)
-      Dir[dir_name + '/**/*'].map do |fn|
+    def all_files_in(dir_name, extra_files, recursion_limit = 10)
+      all_files_and_dirs_in(dir_name, extra_files).map do |fn|
         case File.ftype(fn)
         when 'link'
           if 0 == recursion_limit
@@ -70,7 +68,7 @@ module Nanoc::Extra
             if File.file?(absolute_target)
               fn
             else
-              all_files_in(absolute_target, recursion_limit - 1).map do |sfn|
+              all_files_in(absolute_target, extra_files, recursion_limit - 1).map do |sfn|
                 fn + sfn[absolute_target.size..-1]
               end
             end
@@ -86,6 +84,35 @@ module Nanoc::Extra
     end
     module_function :all_files_in
 
+    # Returns all files and directories in the given directory and
+    # directories below it.
+    #
+    # @param [String] dir_name The name of the directory whose contents to
+    #   fetch
+    #
+    # @param [String, Array, nil] extra_files The list of extra patterns
+    #   to extend the file search for files not found by default, example
+    #   "**/.{htaccess,htpasswd}"
+    #
+    # @return [Array<String>] A list of files and directories
+    #
+    # @raise [GenericTrivial] when pattern can not be handled
+    def all_files_and_dirs_in(dir_name, extra_files)
+      patterns = ["#{dir_name}/**/*"]
+      case extra_files
+      when nil
+      when String
+        patterns << "#{dir_name}/#{extra_files}"
+      when Array
+        patterns.concat(extra_files.map { |extra_file| "#{dir_name}/#{extra_file}" })
+      else
+        raise Nanoc::Errors::GenericTrivial,
+          "Do not know how to handle extra_files: #{extra_files.inspect}"
+      end
+      Dir.glob(patterns)
+    end
+    module_function :all_files_and_dirs_in
+
     # Resolves the given symlink into an absolute path.
     #
     # @param [String] filename The filename of the symlink to resolve
@@ -118,7 +145,5 @@ module Nanoc::Extra
       end
     end
     module_function :resolve_symlink
-
   end
-
 end
diff --git a/lib/nanoc/extra/jruby_nokogiri_warner.rb b/lib/nanoc/extra/jruby_nokogiri_warner.rb
index 31a09ec..0399aae 100644
--- a/lib/nanoc/extra/jruby_nokogiri_warner.rb
+++ b/lib/nanoc/extra/jruby_nokogiri_warner.rb
@@ -3,9 +3,7 @@
 require 'singleton'
 
 module Nanoc::Extra
-
   class JRubyNokogiriWarner
-
     include Singleton
 
     TEXT = <<EOS
@@ -35,14 +33,12 @@ EOS
     end
 
     def check_and_warn
-      return if !defined?(RUBY_ENGINE)
+      return unless defined?(RUBY_ENGINE)
       return if RUBY_ENGINE != 'jruby'
       return if @warned
 
       $stderr.puts TEXT
       @warned = true
     end
-
   end
-
 end
diff --git a/lib/nanoc/extra/link_collector.rb b/lib/nanoc/extra/link_collector.rb
index cb8367b..2a54383 100644
--- a/lib/nanoc/extra/link_collector.rb
+++ b/lib/nanoc/extra/link_collector.rb
@@ -3,8 +3,17 @@
 require 'set'
 
 module ::Nanoc::Extra
-
   class LinkCollector
+    URI_ATTRS = {
+      'a' => :href,
+      'audio' => :src,
+      'form' => :action,
+      'iframe' => :src,
+      'img' => :src,
+      'link' => :href,
+      'script' => :src,
+      'video' => :src
+    }
 
     def initialize(filenames, mode = nil)
       Nanoc::Extra::JRubyNokogiriWarner.check_and_warn
@@ -13,11 +22,11 @@ module ::Nanoc::Extra
       @filter =
         case mode
         when nil
-          lambda { |h| true }
+          ->(_h) { true }
         when :external
-          lambda { |h| external_href?(h) }
+          ->(h) { external_href?(h) }
         when :internal
-          lambda { |h| !external_href?(h) }
+          ->(h) { !external_href?(h) }
         else
           raise ArgumentError, 'Expected mode argument to be :internal, :external or nil'
         end
@@ -35,26 +44,46 @@ module ::Nanoc::Extra
       filenames_per_href
     end
 
+    def filenames_per_resource_uri
+      require 'nokogiri'
+      filenames_per_resource_uri = {}
+      @filenames.each do |filename|
+        resource_uris_in_file(filename).each do |resouce_uri|
+          filenames_per_resource_uri[resouce_uri] ||= Set.new
+          filenames_per_resource_uri[resouce_uri] << filename
+        end
+      end
+      filenames_per_resource_uri
+    end
+
     def external_href?(href)
-      !!(href =~ %r{^(\/\/|[a-z\-]+:)})
+      href =~ %r{^(\/\/|[a-z\-]+:)}
     end
 
     def hrefs_in_file(filename)
-      hrefs_in_file = Set.new
-      doc = Nokogiri::HTML(::File.read(filename))
-      doc.css('a').each { |e| hrefs_in_file << e[:href] unless e[:href].nil? }
-      doc.css('img').each { |e| hrefs_in_file << e[:src]  }
+      uris_in_file filename, %w(a img)
+    end
 
-      # Convert protocol-relative urls
-      # e.g. //example.com => http://example.com
-      hrefs_in_file.map! { |href| href.gsub /^\/\//, 'http://' }
+    def resource_uris_in_file(filename)
+      uris_in_file filename, %w(audio form img iframe link script video)
+    end
+
+    private
+
+    def uris_in_file(filename, tag_names)
+      uris = Set.new
+      doc = Nokogiri::HTML(::File.read(filename))
+      tag_names.each do |tag_name|
+        attr = URI_ATTRS[tag_name]
+        doc.css(tag_name).each do |e|
+          uris << e[attr] unless e[attr].nil?
+        end
+      end
 
       # Strip fragment
-      hrefs_in_file.map! { |href| href.gsub(/#.*$/, '') }
+      uris.map! { |href| href.gsub(/#.*$/, '') }
 
-      hrefs_in_file.select(&@filter)
+      uris.select(&@filter)
     end
-
   end
-
 end
diff --git a/lib/nanoc/extra/piper.rb b/lib/nanoc/extra/piper.rb
index 2d070f1..ae4bd27 100644
--- a/lib/nanoc/extra/piper.rb
+++ b/lib/nanoc/extra/piper.rb
@@ -3,11 +3,8 @@
 require 'open3'
 
 module Nanoc::Extra
-
   class Piper
-
     class Error < ::Nanoc::Errors::Generic
-
       def initialize(command, exit_code)
         @command   = command
         @exit_code = exit_code
@@ -16,12 +13,11 @@ module Nanoc::Extra
       def message
         "command exited with a nonzero status code #{@exit_code} (command: #{@command.join(' ')})"
       end
-
     end
 
     # @option [IO] :stdout ($stdout)
     # @option [IO] :stderr ($stderr)
-    def initialize(params={})
+    def initialize(params = {})
       @stdout = params.fetch(:stdout, $stdout)
       @stderr = params.fetch(:stderr, $stderr)
     end
@@ -43,12 +39,10 @@ module Nanoc::Extra
         stderr_thread.join
 
         exit_status = wait_thr.value
-        if !exit_status.success?
+        unless exit_status.success?
           raise Error.new(cmd, exit_status.to_i)
         end
       end
     end
-
   end
-
 end
diff --git a/lib/nanoc/extra/pruner.rb b/lib/nanoc/extra/pruner.rb
index c883137..9613c13 100644
--- a/lib/nanoc/extra/pruner.rb
+++ b/lib/nanoc/extra/pruner.rb
@@ -1,11 +1,9 @@
 # encoding: utf-8
 
 module Nanoc::Extra
-
   # Responsible for finding and deleting files in the site’s output directory
   # that are not managed by nanoc.
   class Pruner
-
     # @return [Nanoc::Site] The site this pruner belongs to
     attr_reader :site
 
@@ -27,18 +25,17 @@ module Nanoc::Extra
       require 'find'
 
       # Get compiled files
-      compiled_files = site.items.map do |item|
-        item.reps.map do |rep|
-          rep.raw_path
-        end
-      end.flatten.compact.select { |f| File.file?(f) }
+      all_raw_paths = site.items.map do |item|
+        item.reps.map(&:raw_path)
+      end
+      compiled_files = all_raw_paths.flatten.compact.select { |f| File.file?(f) }
 
       # Get present files and dirs
       present_files = []
       present_dirs = []
       Find.find(site.config[:output_dir] + '/') do |f|
         present_files << f if File.file?(f)
-        present_dirs  << f if File.directory?(f)
+        present_dirs << f if File.directory?(f)
       end
 
       # Remove stray files
@@ -64,7 +61,7 @@ module Nanoc::Extra
       @exclude.any? { |e| pathname.include_component?(e) }
     end
 
-  protected
+    protected
 
     def delete_file(file)
       if @dry_run
@@ -83,7 +80,5 @@ module Nanoc::Extra
         Dir.rmdir(dir)
       end
     end
-
   end
-
 end
diff --git a/lib/nanoc/extra/validators.rb b/lib/nanoc/extra/validators.rb
index f05ddfc..5fe6f09 100644
--- a/lib/nanoc/extra/validators.rb
+++ b/lib/nanoc/extra/validators.rb
@@ -1,12 +1,8 @@
 # encoding: utf-8
 
 module Nanoc::Extra
-
   module Validators
-
     autoload 'W3C',   'nanoc/extra/validators/w3c'
     autoload 'Links', 'nanoc/extra/validators/links'
-
   end
-
 end
diff --git a/lib/nanoc/extra/validators/links.rb b/lib/nanoc/extra/validators/links.rb
index 074a7be..0a2f537 100644
--- a/lib/nanoc/extra/validators/links.rb
+++ b/lib/nanoc/extra/validators/links.rb
@@ -1,11 +1,9 @@
 # encoding: utf-8
 
 module Nanoc::Extra::Validators
-
   # @deprecated Use the Checking API or the `check` command instead
   class Links
-
-    def initialize(dir, index_filenames, params = {})
+    def initialize(_dir, _index_filenames, params = {})
       @include_internal = params.key?(:internal) && params[:internal]
       @include_external = params.key?(:external) && params[:external]
     end
@@ -14,9 +12,7 @@ module Nanoc::Extra::Validators
       checks = []
       checks << 'ilinks' if options[:internal]
       checks << 'elinks' if options[:external]
-      Nanoc::CLI.run [ 'check', checks ].flatten
+      Nanoc::CLI.run ['check', checks].flatten
     end
-
   end
-
 end
diff --git a/lib/nanoc/extra/validators/w3c.rb b/lib/nanoc/extra/validators/w3c.rb
index a5cfab7..d98ca91 100644
--- a/lib/nanoc/extra/validators/w3c.rb
+++ b/lib/nanoc/extra/validators/w3c.rb
@@ -1,10 +1,8 @@
 # encoding: utf-8
 
 module Nanoc::Extra::Validators
-
   # @deprecated Use the Checking API or the `check` command instead
   class W3C
-
     def initialize(dir, types)
       @dir   = dir
       @types = types
@@ -19,9 +17,7 @@ module Nanoc::Extra::Validators
         raise Nanoc::Errors::GenericTrivial, "unknown type(s) specified: #{types.join(', ')}"
       end
 
-      Nanoc::CLI.run([ 'check', args ].flatten)
+      Nanoc::CLI.run(['check', args].flatten)
     end
-
   end
-
 end
diff --git a/lib/nanoc/extra/vcs.rb b/lib/nanoc/extra/vcs.rb
index f1459d1..037d756 100644
--- a/lib/nanoc/extra/vcs.rb
+++ b/lib/nanoc/extra/vcs.rb
@@ -1,7 +1,6 @@
 # encoding: utf-8
 
 module Nanoc::Extra
-
   # A very simple representation of a version control system (VCS) that
   # abstracts the add, remove and move operations. It does not commit. This
   # class is primarily used by data sources that store data as flat files on
@@ -10,7 +9,6 @@ module Nanoc::Extra
   # @abstract Subclass and override {#add}, {#remove} and {#move} to implement
   #   a custom VCS.
   class VCS
-
     extend Nanoc::PluginRegistry::PluginMethods
 
     # Adds the file with the given filename to the working copy.
@@ -20,7 +18,7 @@ module Nanoc::Extra
     # @return [void]
     #
     # @abstract
-    def add(filename)
+    def add(filename) # rubocop:disable Lint/UnusedMethodArgument
       not_implemented('add')
     end
 
@@ -33,7 +31,7 @@ module Nanoc::Extra
     # @return [void]
     #
     # @abstract
-    def remove(filename)
+    def remove(filename) # rubocop:disable Lint/UnusedMethodArgument
       not_implemented('remove')
     end
 
@@ -48,19 +46,17 @@ module Nanoc::Extra
     # @return [void]
     #
     # @abstract
-    def move(src, dst)
+    def move(src, dst) # rubocop:disable Lint/UnusedMethodArgument
       not_implemented('move')
     end
 
-  private
+    private
 
     def not_implemented(name)
       raise NotImplementedError.new(
-        "#{self.class} does not override ##{name}, which is required for " +
+        "#{self.class} does not override ##{name}, which is required for " \
         'this data source to be used.'
       )
     end
-
   end
-
 end
diff --git a/lib/nanoc/extra/vcses.rb b/lib/nanoc/extra/vcses.rb
index fa52c03..26f00a3 100644
--- a/lib/nanoc/extra/vcses.rb
+++ b/lib/nanoc/extra/vcses.rb
@@ -1,7 +1,6 @@
 # encoding: utf-8
 
 module Nanoc::Extra::VCSes
-
   autoload 'Bazaar',     'nanoc/extra/vcses/bazaar'
   autoload 'Dummy',      'nanoc/extra/vcses/dummy'
   autoload 'Git',        'nanoc/extra/vcses/git'
@@ -13,5 +12,4 @@ module Nanoc::Extra::VCSes
   Nanoc::Extra::VCS.register '::Nanoc::Extra::VCSes::Git',        :git
   Nanoc::Extra::VCS.register '::Nanoc::Extra::VCSes::Mercurial',  :mercurial, :hg
   Nanoc::Extra::VCS.register '::Nanoc::Extra::VCSes::Subversion', :subversion, :svn
-
 end
diff --git a/lib/nanoc/extra/vcses/bazaar.rb b/lib/nanoc/extra/vcses/bazaar.rb
index 9a2060d..d46b50c 100644
--- a/lib/nanoc/extra/vcses/bazaar.rb
+++ b/lib/nanoc/extra/vcses/bazaar.rb
@@ -1,10 +1,8 @@
 # encoding: utf-8
 
 module Nanoc::Extra::VCSes
-
   # @see Nanoc::Extra::VCS
   class Bazaar < Nanoc::Extra::VCS
-
     # @see Nanoc::Extra::VCS#add
     def add(filename)
       system('bzr', 'add', filename)
@@ -19,7 +17,5 @@ module Nanoc::Extra::VCSes
     def move(src, dst)
       system('bzr', 'mv', src, dst)
     end
-
   end
-
 end
diff --git a/lib/nanoc/extra/vcses/dummy.rb b/lib/nanoc/extra/vcses/dummy.rb
index 0d1f8a6..7b5ab86 100644
--- a/lib/nanoc/extra/vcses/dummy.rb
+++ b/lib/nanoc/extra/vcses/dummy.rb
@@ -1,12 +1,10 @@
 # encoding: utf-8
 
 module Nanoc::Extra::VCSes
-
   # @see Nanoc::Extra::VCS
   class Dummy < Nanoc::Extra::VCS
-
     # @see Nanoc::Extra::VCS#add
-    def add(filename)
+    def add(_filename)
     end
 
     # @see Nanoc::Extra::VCS#remove
@@ -18,7 +16,5 @@ module Nanoc::Extra::VCSes
     def move(src, dst)
       FileUtils.move(src, dst)
     end
-
   end
-
 end
diff --git a/lib/nanoc/extra/vcses/git.rb b/lib/nanoc/extra/vcses/git.rb
index 4caf615..e8eb6cc 100644
--- a/lib/nanoc/extra/vcses/git.rb
+++ b/lib/nanoc/extra/vcses/git.rb
@@ -1,10 +1,8 @@
 # encoding: utf-8
 
 module Nanoc::Extra::VCSes
-
   # @see Nanoc::Extra::VCS
   class Git < Nanoc::Extra::VCS
-
     # @see Nanoc::Extra::VCS#add
     def add(filename)
       system('git', 'add', filename)
@@ -19,7 +17,5 @@ module Nanoc::Extra::VCSes
     def move(src, dst)
       system('git', 'mv', src, dst)
     end
-
   end
-
 end
diff --git a/lib/nanoc/extra/vcses/mercurial.rb b/lib/nanoc/extra/vcses/mercurial.rb
index 27bce2e..05a7516 100644
--- a/lib/nanoc/extra/vcses/mercurial.rb
+++ b/lib/nanoc/extra/vcses/mercurial.rb
@@ -1,10 +1,8 @@
 # encoding: utf-8
 
 module Nanoc::Extra::VCSes
-
   # @see Nanoc::Extra::VCS
   class Mercurial < Nanoc::Extra::VCS
-
     # @see Nanoc::Extra::VCS#add
     def add(filename)
       system('hg', 'add', filename)
@@ -19,7 +17,5 @@ module Nanoc::Extra::VCSes
     def move(src, dst)
       system('hg', 'mv', src, dst)
     end
-
   end
-
 end
diff --git a/lib/nanoc/extra/vcses/subversion.rb b/lib/nanoc/extra/vcses/subversion.rb
index bf8e564..89d44bf 100644
--- a/lib/nanoc/extra/vcses/subversion.rb
+++ b/lib/nanoc/extra/vcses/subversion.rb
@@ -1,10 +1,8 @@
 # encoding: utf-8
 
 module Nanoc::Extra::VCSes
-
   # @see Nanoc::Extra::VCS
   class Subversion < Nanoc::Extra::VCS
-
     # @see Nanoc::Extra::VCS#add
     def add(filename)
       system('svn', 'add', filename)
@@ -19,7 +17,5 @@ module Nanoc::Extra::VCSes
     def move(src, dst)
       system('svn', 'mv', src, dst)
     end
-
   end
-
 end
diff --git a/lib/nanoc/filters.rb b/lib/nanoc/filters.rb
index ec9f82c..d82b999 100644
--- a/lib/nanoc/filters.rb
+++ b/lib/nanoc/filters.rb
@@ -1,7 +1,6 @@
 # encoding: utf-8
 
 module Nanoc::Filters
-
   autoload 'AsciiDoc',        'nanoc/filters/asciidoc'
   autoload 'BlueCloth',       'nanoc/filters/bluecloth'
   autoload 'CodeRay',         'nanoc/filters/coderay'
@@ -59,5 +58,4 @@ module Nanoc::Filters
   Nanoc::Filter.register '::Nanoc::Filters::UglifyJS',        :uglify_js
   Nanoc::Filter.register '::Nanoc::Filters::XSL',             :xsl
   Nanoc::Filter.register '::Nanoc::Filters::YUICompressor',   :yui_compressor
-
 end
diff --git a/lib/nanoc/filters/asciidoc.rb b/lib/nanoc/filters/asciidoc.rb
index a8c2082..fa852d5 100644
--- a/lib/nanoc/filters/asciidoc.rb
+++ b/lib/nanoc/filters/asciidoc.rb
@@ -1,24 +1,20 @@
 # encoding: utf-8
 
 module Nanoc::Filters
-
   # @since 3.2.0
   class AsciiDoc < Nanoc::Filter
-
     # Runs the content through [AsciiDoc](http://www.methods.co.nz/asciidoc/).
     # This method takes no options.
     #
     # @param [String] content The content to filter
     #
     # @return [String] The filtered content
-    def run(content, params = {})
+    def run(content, _params = {})
       stdout = StringIO.new
       stderr = $stderr
-      piper = Nanoc::Extra::Piper.new(:stdout => stdout, :stderr => stderr)
+      piper = Nanoc::Extra::Piper.new(stdout: stdout, stderr: stderr)
       piper.run(%w( asciidoc -o - - ), content)
       stdout.string
     end
-
   end
-
 end
diff --git a/lib/nanoc/filters/bluecloth.rb b/lib/nanoc/filters/bluecloth.rb
index f8ba7e5..25f9790 100644
--- a/lib/nanoc/filters/bluecloth.rb
+++ b/lib/nanoc/filters/bluecloth.rb
@@ -2,7 +2,6 @@
 
 module Nanoc::Filters
   class BlueCloth < Nanoc::Filter
-
     requires 'bluecloth'
 
     # Runs the content through [BlueCloth](http://deveiate.org/projects/BlueCloth).
@@ -11,9 +10,8 @@ module Nanoc::Filters
     # @param [String] content The content to filter
     #
     # @return [String] The filtered content
-    def run(content, params = {})
+    def run(content, _params = {})
       ::BlueCloth.new(content).to_html
     end
-
   end
 end
diff --git a/lib/nanoc/filters/coderay.rb b/lib/nanoc/filters/coderay.rb
index d0c82d2..8f4d3e7 100644
--- a/lib/nanoc/filters/coderay.rb
+++ b/lib/nanoc/filters/coderay.rb
@@ -2,7 +2,6 @@
 
 module Nanoc::Filters
   class CodeRay < Nanoc::Filter
-
     requires 'coderay'
 
     # @deprecated Use the `:colorize_syntax` filter instead.
@@ -16,6 +15,5 @@ module Nanoc::Filters
       # Get result
       ::CodeRay.scan(content, params[:language]).html
     end
-
   end
 end
diff --git a/lib/nanoc/filters/coffeescript.rb b/lib/nanoc/filters/coffeescript.rb
index 1c1d174..720d361 100644
--- a/lib/nanoc/filters/coffeescript.rb
+++ b/lib/nanoc/filters/coffeescript.rb
@@ -1,10 +1,8 @@
 # encoding: utf-8
 
 module Nanoc::Filters
-
   # @since 3.3.0
   class CoffeeScript < Nanoc::Filter
-
     requires 'coffee-script'
 
     # Runs the content through [CoffeeScript](http://coffeescript.org/).
@@ -13,10 +11,8 @@ module Nanoc::Filters
     # @param [String] content The CoffeeScript content to turn into JavaScript
     #
     # @return [String] The resulting JavaScript
-    def run(content, params = {})
+    def run(content, _params = {})
       ::CoffeeScript.compile(content)
     end
-
   end
-
 end
diff --git a/lib/nanoc/filters/colorize_syntax.rb b/lib/nanoc/filters/colorize_syntax.rb
index 2254804..14c9975 100644
--- a/lib/nanoc/filters/colorize_syntax.rb
+++ b/lib/nanoc/filters/colorize_syntax.rb
@@ -2,7 +2,6 @@
 
 module Nanoc::Filters
   class ColorizeSyntax < Nanoc::Filter
-
     requires 'nokogiri', 'stringio', 'open3'
 
     # The default colorizer to use for a language if the colorizer for that
@@ -30,7 +29,7 @@ module Nanoc::Filters
     #
     # * `:coderay` for [Coderay](http://coderay.rubychan.de/)
     # * `:pygmentize` for [pygmentize](http://pygments.org/docs/cmdline/), the
-    #   commandline frontend for [Pygments](http://pygments.org/)
+    #   command-line frontend for [Pygments](http://pygments.org/)
     # * `:pygmentsrb` for [pygments.rb](https://github.com/tmm1/pygments.rb),
     #   a Ruby interface for [Pygments](http://pygments.org/)
     # * `:simon_highlight` for [Highlight](http://www.andre-simon.de/doku/highlight/en/highlight.html)
@@ -142,7 +141,7 @@ module Nanoc::Filters
       end
 
       method = "to_#{syntax}".to_sym
-      doc.send(method, :encoding => 'UTF-8')
+      doc.send(method, encoding: 'UTF-8')
     end
 
     # Parses the given content using the given class. This method also handles
@@ -159,18 +158,16 @@ module Nanoc::Filters
     #
     # @api private
     def parse(content, klass, is_fullpage)
-      begin
-        if is_fullpage
-          klass.parse(content, nil, 'UTF-8')
-        else
-          klass.fragment(content)
-        end
-      rescue => e
-        if e.message =~ /can't modify frozen string/
-          parse(content.dup, klass, is_fullpage)
-        else
-          raise e
-        end
+      if is_fullpage
+        klass.parse(content, nil, 'UTF-8')
+      else
+        klass.fragment(content)
+      end
+    rescue => e
+      if e.message =~ /can't modify frozen string/
+        parse(content.dup, klass, is_fullpage)
+      else
+        raise e
       end
     end
 
@@ -199,12 +196,12 @@ module Nanoc::Filters
     #
     # @return [String] The colorized output, which is identical to the input
     #   in this case
-    def dummy(code, language, params = {})
+    def dummy(code, language, params = {}) # rubocop:disable Lint/UnusedMethodArgument
       code
     end
 
     # Runs the content through [pygmentize](http://pygments.org/docs/cmdline/),
-    # the commandline frontend for [Pygments](http://pygments.org/).
+    # the command-line frontend for [Pygments](http://pygments.org/).
     #
     # @api private
     #
@@ -219,14 +216,14 @@ module Nanoc::Filters
       check_availability('pygmentize', '-V')
 
       params[:encoding] ||= 'utf-8'
-      params[:nowrap]   ||= 'True'
+      params[:nowrap] ||= 'True'
 
-      cmd = [ 'pygmentize', '-l', language, '-f', 'html' ]
+      cmd = ['pygmentize', '-l', language, '-f', 'html']
       cmd << '-O' << params.map { |k, v| "#{k}=#{v}" }.join(',') unless params.empty?
 
       stdout = StringIO.new
       stderr = $stderr
-      piper = Nanoc::Extra::Piper.new(:stdout => stdout, :stderr => stderr)
+      piper = Nanoc::Extra::Piper.new(stdout: stdout, stderr: stderr)
       piper.run(cmd, code)
 
       stdout.string
@@ -249,15 +246,15 @@ module Nanoc::Filters
       args[:lexer] ||= language
       args[:options] ||= {}
       args[:options][:encoding] ||= 'utf-8'
-      args[:options][:nowrap]   ||= 'True'
+      args[:options][:nowrap] ||= 'True'
 
       Pygments.highlight(code, args)
     end
 
     SIMON_HIGHLIGHT_OPT_MAP = {
-        :wrap => '-W',
-        :include_style => '-I',
-        :line_numbers  => '-l',
+        wrap: '-W',
+        include_style: '-I',
+        line_numbers: '-l',
     }
 
     # Runs the content through [Highlight](http://www.andre-simon.de/doku/highlight/en/highlight.html).
@@ -276,12 +273,12 @@ module Nanoc::Filters
     def simon_highlight(code, language, params = {})
       check_availability('highlight', '--version')
 
-      cmd = [ 'highlight', '--syntax', language, '--fragment' ]
-      params.each do |key, value|
+      cmd = ['highlight', '--syntax', language, '--fragment']
+      params.each do |key, _value|
         if SIMON_HIGHLIGHT_OPT_MAP[key]
           cmd << SIMON_HIGHLIGHT_OPT_MAP[key]
         else
-          # TODO allow passing other options
+          # TODO: allow passing other options
           case key
           when :style
             cmd << '--style' << params[:style]
@@ -291,14 +288,14 @@ module Nanoc::Filters
 
       stdout = StringIO.new
       stderr = $stderr
-      piper = Nanoc::Extra::Piper.new(:stdout => stdout, :stderr => stderr)
+      piper = Nanoc::Extra::Piper.new(stdout: stdout, stderr: stderr)
       piper.run(cmd, code)
 
       stdout.string
     end
 
     # Wraps the element in <div class="CodeRay"><div class="code">
-    def coderay_postprocess(language, element)
+    def coderay_postprocess(_language, element)
       # Skip if we're a free <code>
       return if element.parent.nil?
 
@@ -329,7 +326,7 @@ module Nanoc::Filters
       require 'rouge'
 
       formatter_options = {
-        :css_class => params.fetch(:css_class, 'highlight'),
+        css_class: params.fetch(:css_class, 'highlight'),
       }
       formatter = Rouge::Formatters::HTML.new(formatter_options)
       lexer = Rouge::Lexer.find_fancy(language, code) || Rouge::Lexers::PlainText
@@ -340,26 +337,30 @@ module Nanoc::Filters
     #
     # Before:
     #
-    #   <pre><code class="language-ruby"><pre><code class="highlight">
+    #   <pre><code class="language-ruby"><pre class="highlight"><code>
     #
     # After:
     #
     #   <pre><code class="language-ruby highlight">
-    def rouge_postprocess(language, element)
+    def rouge_postprocess(_language, element)
       return if element.name != 'pre'
 
       code1 = element.xpath('code').first
       return if code1.nil?
-      code2 = code1.xpath('pre/code').first
+
+      pre = code1.xpath('pre').first
+      return if pre.nil?
+
+      code2 = pre.xpath('code').first
       return if code2.nil?
 
       code1.inner_html = code2.inner_html
-      code1['class'] = [ code1['class'], code2['class'] ].compact.join(' ')
+      code1['class'] = [code1['class'], pre['class']].compact.join(' ')
     end
 
-  protected
+    protected
 
-    KNOWN_COLORIZERS = [ :coderay, :dummy, :pygmentize, :pygmentsrb, :simon_highlight, :rouge ]
+    KNOWN_COLORIZERS = [:coderay, :dummy, :pygmentize, :pygmentsrb, :simon_highlight, :rouge]
 
     # Removes the first blank lines and any whitespace at the end.
     def strip(s)
@@ -388,9 +389,8 @@ module Nanoc::Filters
     end
 
     def check_availability(*cmd)
-      piper = Nanoc::Extra::Piper.new(:stdout => StringIO.new, :stderr => StringIO.new)
+      piper = Nanoc::Extra::Piper.new(stdout: StringIO.new, stderr: StringIO.new)
       piper.run(cmd, nil)
     end
-
   end
 end
diff --git a/lib/nanoc/filters/erb.rb b/lib/nanoc/filters/erb.rb
index b1286f7..f36aa93 100644
--- a/lib/nanoc/filters/erb.rb
+++ b/lib/nanoc/filters/erb.rb
@@ -2,7 +2,6 @@
 
 module Nanoc::Filters
   class ERB < Nanoc::Filter
-
     requires 'erb'
 
     # Runs the content through [ERB](http://ruby-doc.org/stdlib/libdoc/erb/rdoc/classes/ERB.html).
@@ -23,7 +22,7 @@ module Nanoc::Filters
       context = ::Nanoc::Context.new(assigns)
 
       # Get binding
-      proc = assigns[:content] ? lambda { assigns[:content] } : nil
+      proc = assigns[:content] ? -> { assigns[:content] } : nil
       assigns_binding = context.get_binding(&proc)
 
       # Get result
@@ -33,6 +32,5 @@ module Nanoc::Filters
       erb.filename = filename
       erb.result(assigns_binding)
     end
-
   end
 end
diff --git a/lib/nanoc/filters/erubis.rb b/lib/nanoc/filters/erubis.rb
index c60cb7b..0286be2 100644
--- a/lib/nanoc/filters/erubis.rb
+++ b/lib/nanoc/filters/erubis.rb
@@ -2,7 +2,6 @@
 
 module Nanoc::Filters
   class Erubis < Nanoc::Filter
-
     requires 'erubis'
 
     # The same as `::Erubis::Eruby` but adds `_erbout` as an alias for the
@@ -18,17 +17,16 @@ module Nanoc::Filters
     # @param [String] content The content to filter
     #
     # @return [String] The filtered content
-    def run(content, params = {})
+    def run(content, _params = {})
       # Create context
       context = ::Nanoc::Context.new(assigns)
 
       # Get binding
-      proc = assigns[:content] ? lambda { assigns[:content] } : nil
+      proc = assigns[:content] ? -> { assigns[:content] } : nil
       assigns_binding = context.get_binding(&proc)
 
       # Get result
-      ErubisWithErbout.new(content, :filename => filename).result(assigns_binding)
+      ErubisWithErbout.new(content, filename: filename).result(assigns_binding)
     end
-
   end
 end
diff --git a/lib/nanoc/filters/haml.rb b/lib/nanoc/filters/haml.rb
index adbdacf..54c827f 100644
--- a/lib/nanoc/filters/haml.rb
+++ b/lib/nanoc/filters/haml.rb
@@ -2,7 +2,6 @@
 
 module Nanoc::Filters
   class Haml < Nanoc::Filter
-
     requires 'haml'
 
     # Runs the content through [Haml](http://haml-lang.com/).
@@ -13,15 +12,14 @@ module Nanoc::Filters
     # @return [String] The filtered content
     def run(content, params = {})
       # Get options
-      options = params.merge(:filename => filename)
+      options = params.merge(filename: filename)
 
       # Create context
       context = ::Nanoc::Context.new(assigns)
 
       # Get result
-      proc = assigns[:content] ? lambda { assigns[:content] } : nil
+      proc = assigns[:content] ? -> { assigns[:content] } : nil
       ::Haml::Engine.new(content, options).render(context, assigns, &proc)
     end
-
   end
 end
diff --git a/lib/nanoc/filters/handlebars.rb b/lib/nanoc/filters/handlebars.rb
index d9bfdd3..02f1792 100644
--- a/lib/nanoc/filters/handlebars.rb
+++ b/lib/nanoc/filters/handlebars.rb
@@ -1,10 +1,8 @@
 # encoding: utf-8
 
 module Nanoc::Filters
-
   # @since 3.4.0
   class Handlebars < Nanoc::Filter
-
     requires 'handlebars'
 
     # Runs the content through
@@ -15,7 +13,7 @@ module Nanoc::Filters
     # @param [String] content The content to filter
     #
     # @return [String] The filtered content
-    def run(content, params = {})
+    def run(content, _params = {})
       context = item.attributes.dup
       context[:item]   = assigns[:item].attributes
       context[:config] = assigns[:config]
@@ -28,7 +26,5 @@ module Nanoc::Filters
       template = handlebars.compile(content)
       template.call(context)
     end
-
   end
-
 end
diff --git a/lib/nanoc/filters/kramdown.rb b/lib/nanoc/filters/kramdown.rb
index 4d5dd8f..0bbde48 100644
--- a/lib/nanoc/filters/kramdown.rb
+++ b/lib/nanoc/filters/kramdown.rb
@@ -2,7 +2,6 @@
 
 module Nanoc::Filters
   class Kramdown < Nanoc::Filter
-
     requires 'kramdown'
 
     # Runs the content through [Kramdown](http://kramdown.gettalong.org/).
@@ -12,9 +11,13 @@ module Nanoc::Filters
     #
     # @return [String] The filtered content
     def run(content, params = {})
-      # Get result
-      ::Kramdown::Document.new(content, params).to_html
-    end
+      document = ::Kramdown::Document.new(content, params)
 
+      document.warnings.each do |warning|
+        $stderr.puts "kramdown warning: #{warning}"
+      end
+
+      document.to_html
+    end
   end
 end
diff --git a/lib/nanoc/filters/less.rb b/lib/nanoc/filters/less.rb
index 69f037d..06dab3e 100644
--- a/lib/nanoc/filters/less.rb
+++ b/lib/nanoc/filters/less.rb
@@ -2,7 +2,6 @@
 
 module Nanoc::Filters
   class Less < Nanoc::Filter
-
     requires 'less'
 
     # Runs the content through [LESS](http://lesscss.org/).
@@ -30,7 +29,7 @@ module Nanoc::Filters
         if imported_pathname.relative?
           imported_pathname = current_dir_pathname + imported_pathname
         end
-        next if !imported_pathname.exist?
+        next unless imported_pathname.exist?
         imported_filename = imported_pathname.realpath
 
         # Find matching item
@@ -44,10 +43,9 @@ module Nanoc::Filters
       depend_on(imported_items)
 
       # Add filename to load path
-      paths = [ File.dirname(@item[:content_filename]) ]
-      parser = ::Less::Parser.new(:paths => paths)
+      paths = [File.dirname(@item[:content_filename])]
+      parser = ::Less::Parser.new(paths: paths)
       parser.parse(content).to_css params
     end
-
   end
 end
diff --git a/lib/nanoc/filters/markaby.rb b/lib/nanoc/filters/markaby.rb
index 1eaddd6..62a2f3d 100644
--- a/lib/nanoc/filters/markaby.rb
+++ b/lib/nanoc/filters/markaby.rb
@@ -2,7 +2,6 @@
 
 module Nanoc::Filters
   class Markaby < Nanoc::Filter
-
     requires 'markaby'
 
     # Runs the content through [Markaby](http://markaby.github.io/).
@@ -11,10 +10,9 @@ module Nanoc::Filters
     # @param [String] content The content to filter
     #
     # @return [String] The filtered content
-    def run(content, params = {})
+    def run(content, _params = {})
       # Get result
       ::Markaby::Builder.new(assigns).instance_eval(content).to_s
     end
-
   end
 end
diff --git a/lib/nanoc/filters/maruku.rb b/lib/nanoc/filters/maruku.rb
index 70e3929..615a4cb 100644
--- a/lib/nanoc/filters/maruku.rb
+++ b/lib/nanoc/filters/maruku.rb
@@ -2,7 +2,6 @@
 
 module Nanoc::Filters
   class Maruku < Nanoc::Filter
-
     requires 'maruku'
 
     # Runs the content through [Maruku](https://github.com/bhollis/maruku/).
@@ -15,6 +14,5 @@ module Nanoc::Filters
       # Get result
       ::Maruku.new(content, params).to_html
     end
-
   end
 end
diff --git a/lib/nanoc/filters/mustache.rb b/lib/nanoc/filters/mustache.rb
index 94743dd..06e5a35 100644
--- a/lib/nanoc/filters/mustache.rb
+++ b/lib/nanoc/filters/mustache.rb
@@ -1,10 +1,8 @@
 # encoding: utf-8
 
 module Nanoc::Filters
-
   # @since 3.2.0
   class Mustache < Nanoc::Filter
-
     requires 'mustache'
 
     # Runs the content through
@@ -14,11 +12,9 @@ module Nanoc::Filters
     # @param [String] content The content to filter
     #
     # @return [String] The filtered content
-    def run(content, params = {})
-      context = item.attributes.merge({ :yield => assigns[:content] })
+    def run(content, _params = {})
+      context = item.attributes.merge({ yield: assigns[:content] })
       ::Mustache.render(content, context)
     end
-
   end
-
 end
diff --git a/lib/nanoc/filters/pandoc.rb b/lib/nanoc/filters/pandoc.rb
index 8799256..cce1a0d 100644
--- a/lib/nanoc/filters/pandoc.rb
+++ b/lib/nanoc/filters/pandoc.rb
@@ -2,19 +2,36 @@
 
 module Nanoc::Filters
   class Pandoc < Nanoc::Filter
-
     requires 'pandoc-ruby'
 
     # Runs the content through [Pandoc](http://johnmacfarlane.net/pandoc/)
-    # using [PandocRuby](https://github.com/alphabetum/pandoc-ruby). Options
-    # are passed on to PandocRuby.
+    # using [PandocRuby](https://github.com/alphabetum/pandoc-ruby).
+    #
+    # Arguments can be passed to PandocRuby in two ways:
+    #
+    # * Use the `:args` option. This approach is more flexible, since it
+    #   allows passing an array instead of a hash.
+    #
+    # * Pass the arguments directly to the filter. With this approach, only
+    #   hashes can be passed, which is more limiting than the `:args` approach.
+    #
+    # The `:args` approach is recommended.
+    #
+    # @example Passing arguments using `:arg`
+    #
+    #     filter :pandoc, args: [:s, {:f => :markdown, :to => :html}, 'no-wrap', :toc]
+    #
+    # @example Passing arguments not using `:arg`
+    #
+    #     filter :pandoc, :f => :markdown, :to => :html
     #
     # @param [String] content The content to filter
     #
     # @return [String] The filtered content
-    def run(content, *params)
-      PandocRuby.convert(content, *params)
-    end
+    def run(content, params = {})
+      args = params.key?(:args) ? params[:args] : params
 
+      PandocRuby.convert(content, *args)
+    end
   end
 end
diff --git a/lib/nanoc/filters/rainpress.rb b/lib/nanoc/filters/rainpress.rb
index 3e332ab..3fd6c84 100644
--- a/lib/nanoc/filters/rainpress.rb
+++ b/lib/nanoc/filters/rainpress.rb
@@ -2,7 +2,6 @@
 
 module Nanoc::Filters
   class Rainpress < Nanoc::Filter
-
     requires 'rainpress'
 
     # Runs the content through [Rainpress](http://code.google.com/p/rainpress/).
@@ -14,6 +13,5 @@ module Nanoc::Filters
     def run(content, params = {})
       ::Rainpress.compress(content, params)
     end
-
   end
 end
diff --git a/lib/nanoc/filters/rdiscount.rb b/lib/nanoc/filters/rdiscount.rb
index e1eafe0..ecf5b52 100644
--- a/lib/nanoc/filters/rdiscount.rb
+++ b/lib/nanoc/filters/rdiscount.rb
@@ -2,7 +2,6 @@
 
 module Nanoc::Filters
   class RDiscount < Nanoc::Filter
-
     requires 'rdiscount'
 
     # Runs the content through [RDiscount](http://github.com/rtomayko/rdiscount).
@@ -17,6 +16,5 @@ module Nanoc::Filters
 
       ::RDiscount.new(content, *extensions).to_html
     end
-
   end
 end
diff --git a/lib/nanoc/filters/rdoc.rb b/lib/nanoc/filters/rdoc.rb
index df063a3..12b0f6a 100644
--- a/lib/nanoc/filters/rdoc.rb
+++ b/lib/nanoc/filters/rdoc.rb
@@ -2,7 +2,6 @@
 
 module Nanoc::Filters
   class RDoc < Nanoc::Filter
-
     requires 'rdoc'
 
     def self.setup
@@ -16,11 +15,10 @@ module Nanoc::Filters
     # @param [String] content The content to filter
     #
     # @return [String] The filtered content
-    def run(content, params = {})
+    def run(content, _params = {})
       options = ::RDoc::Options.new
       to_html = ::RDoc::Markup::ToHtml.new(options)
       ::RDoc::Markup.new.convert(content, to_html)
     end
-
   end
 end
diff --git a/lib/nanoc/filters/redcarpet.rb b/lib/nanoc/filters/redcarpet.rb
index 1c0f6da..5a53dc2 100644
--- a/lib/nanoc/filters/redcarpet.rb
+++ b/lib/nanoc/filters/redcarpet.rb
@@ -1,10 +1,8 @@
 # encoding: utf-8
 
 module Nanoc::Filters
-
   # @since 3.2.0
   class Redcarpet < Nanoc::Filter
-
     requires 'redcarpet'
 
     # Runs the content through [Redcarpet](https://github.com/vmg/redcarpet).
@@ -81,7 +79,5 @@ module Nanoc::Filters
         end
       end
     end
-
   end
-
 end
diff --git a/lib/nanoc/filters/redcloth.rb b/lib/nanoc/filters/redcloth.rb
index 0cb6fd8..c50c522 100644
--- a/lib/nanoc/filters/redcloth.rb
+++ b/lib/nanoc/filters/redcloth.rb
@@ -2,7 +2,6 @@
 
 module Nanoc::Filters
   class RedCloth < Nanoc::Filter
-
     requires 'redcloth'
 
     # Runs the content through [RedCloth](http://redcloth.org/). This method
@@ -42,6 +41,5 @@ module Nanoc::Filters
       # Get result
       r.to_html
     end
-
   end
 end
diff --git a/lib/nanoc/filters/relativize_paths.rb b/lib/nanoc/filters/relativize_paths.rb
index fb711b2..707c484 100644
--- a/lib/nanoc/filters/relativize_paths.rb
+++ b/lib/nanoc/filters/relativize_paths.rb
@@ -2,11 +2,10 @@
 
 module Nanoc::Filters
   class RelativizePaths < Nanoc::Filter
-
     require 'nanoc/helpers/link_to'
     include Nanoc::Helpers::LinkTo
 
-    SELECTORS = [ '*/@href', '*/@src', 'object/@data', 'param[@name="movie"]/@content', 'comment()' ]
+    SELECTORS = ['*/@href', '*/@src', 'object/@data', 'param[@name="movie"]/@content', 'comment()']
 
     # Relativizes all paths in the given content, which can be HTML, XHTML, XML
     # or CSS. This filter is quite useful if a site needs to be hosted in a
@@ -37,7 +36,7 @@ module Nanoc::Filters
       # Filter
       case params[:type]
       when :css
-        # FIXME parse CSS the proper way using csspool or something
+        # FIXME: parse CSS the proper way using csspool or something
         content.gsub(/url\((['"]?)(\/(?:[^\/].*?)?)\1\)/) do
           'url(' + $1 + relative_path_to($2) + $1 + ')'
         end
@@ -53,7 +52,7 @@ module Nanoc::Filters
           klass = ::Nokogiri::XML
         when :xhtml
           klass = ::Nokogiri::XML
-          # FIXME cleanup because it is ugly
+          # FIXME: cleanup because it is ugly
           # this cleans the XHTML namespace to process fragments and full
           # documents in the same way. At least, Nokogiri adds this namespace
           # if detects the `html` element.
@@ -63,13 +62,13 @@ module Nanoc::Filters
         nokogiri_process(content, selectors, namespaces, klass, params[:type])
       else
         raise RuntimeError.new(
-          'The relativize_paths needs to know the type of content to ' +
-          'process. Pass a :type to the filter call (:html for HTML, ' +
+          'The relativize_paths needs to know the type of content to ' \
+          'process. Pass a :type to the filter call (:html for HTML, ' \
           ':xhtml for XHTML, :xml for XML, or :css for CSS).')
       end
     end
 
-  protected
+    protected
 
     def nokogiri_process(content, selectors, namespaces, klass, type)
       # Ensure that all prefixes are strings
@@ -80,7 +79,7 @@ module Nanoc::Filters
         doc.xpath(selector, namespaces).each do |node|
           if node.name == 'comment'
             content = node.content.dup
-            content = content.sub(%r{^(\s*\[.+?\]>\s*)(.+?)(\s*<!\[endif\])}m) do |m|
+            content = content.sub(%r{^(\s*\[.+?\]>\s*)(.+?)(\s*<!\[endif\])}m) do |_m|
               fragment = nokogiri_process($2, selectors, namespaces, klass, type)
               $1 + fragment + $3
             end
@@ -98,6 +97,5 @@ module Nanoc::Filters
     def path_is_relativizable?(s)
       s[0, 1] == '/'
     end
-
   end
 end
diff --git a/lib/nanoc/filters/rubypants.rb b/lib/nanoc/filters/rubypants.rb
index e088ace..321a8a0 100644
--- a/lib/nanoc/filters/rubypants.rb
+++ b/lib/nanoc/filters/rubypants.rb
@@ -2,7 +2,6 @@
 
 module Nanoc::Filters
   class RubyPants < Nanoc::Filter
-
     requires 'rubypants'
 
     # Runs the content through [RubyPants](http://rubydoc.info/gems/rubypants/).
@@ -11,10 +10,9 @@ module Nanoc::Filters
     # @param [String] content The content to filter
     #
     # @return [String] The filtered content
-    def run(content, params = {})
+    def run(content, _params = {})
       # Get result
       ::RubyPants.new(content).to_html
     end
-
   end
 end
diff --git a/lib/nanoc/filters/sass.rb b/lib/nanoc/filters/sass.rb
index cd500a1..02f9d1e 100644
--- a/lib/nanoc/filters/sass.rb
+++ b/lib/nanoc/filters/sass.rb
@@ -2,7 +2,6 @@
 
 module Nanoc::Filters
   class Sass < Nanoc::Filter
-
     requires 'sass', 'nanoc/filters/sass/sass_filesystem_importer'
 
     # Runs the content through [Sass](http://sass-lang.com/).
@@ -13,8 +12,8 @@ module Nanoc::Filters
     # @return [String] The filtered content
     def run(content, params = {})
       options = params.merge({
-        :nanoc_current_filter => self,
-        :filename => @item && @item.raw_filename,
+        nanoc_current_filter: self,
+        filename: @item && @item.raw_filename,
       })
       engine = ::Sass::Engine.new(content, options)
       engine.render
@@ -26,6 +25,5 @@ module Nanoc::Filters
           Pathname.new(i.raw_filename).realpath == Pathname.new(filename).realpath
       end
     end
-
   end
 end
diff --git a/lib/nanoc/filters/sass/sass_filesystem_importer.rb b/lib/nanoc/filters/sass/sass_filesystem_importer.rb
index 3aaa51e..2f95ffc 100644
--- a/lib/nanoc/filters/sass/sass_filesystem_importer.rb
+++ b/lib/nanoc/filters/sass/sass_filesystem_importer.rb
@@ -1,7 +1,6 @@
 # encoding: utf-8
 
 class ::Sass::Importers::Filesystem
-
   alias_method :_orig_find, :_find
 
   def _find(dir, name, options)
@@ -11,11 +10,12 @@ class ::Sass::Importers::Filesystem
 
     # Create dependency
     filter = options[:nanoc_current_filter]
-    item = filter.imported_filename_to_item(full_filename)
-    filter.depend_on([ item ]) unless item.nil?
+    if filter
+      item = filter.imported_filename_to_item(full_filename)
+      filter.depend_on([item]) unless item.nil?
+    end
 
     # Call original _find
     _orig_find(dir, name, options)
   end
-
 end
diff --git a/lib/nanoc/filters/slim.rb b/lib/nanoc/filters/slim.rb
index 2280766..e15a0fa 100644
--- a/lib/nanoc/filters/slim.rb
+++ b/lib/nanoc/filters/slim.rb
@@ -1,10 +1,8 @@
 # encoding: utf-8
 
 module Nanoc::Filters
-
   # @since 3.2.0
   class Slim < Nanoc::Filter
-
     requires 'slim'
 
     # Runs the content through [Slim](http://slim-lang.com/).
@@ -15,8 +13,8 @@ module Nanoc::Filters
     # @return [String] The filtered content
     def run(content, params = {})
       params = {
-        :disable_capture => true,      # Capture managed by nanoc
-        :buffer          => '_erbout', # Force slim to output to the buffer used by nanoc
+        disable_capture: true,      # Capture managed by nanoc
+        buffer: '_erbout', # Force slim to output to the buffer used by nanoc
       }.merge params
 
       # Create context
@@ -24,7 +22,5 @@ module Nanoc::Filters
 
       ::Slim::Template.new(params) { content }.render(context) { assigns[:content] }
     end
-
   end
-
 end
diff --git a/lib/nanoc/filters/typogruby.rb b/lib/nanoc/filters/typogruby.rb
index 781b513..47de9b1 100644
--- a/lib/nanoc/filters/typogruby.rb
+++ b/lib/nanoc/filters/typogruby.rb
@@ -1,10 +1,8 @@
 # encoding: utf-8
 
 module Nanoc::Filters
-
   # @since 3.2.0
   class Typogruby < Nanoc::Filter
-
     requires 'typogruby'
 
     # Runs the content through [Typogruby](http://avdgaag.github.com/typogruby/).
@@ -13,11 +11,9 @@ module Nanoc::Filters
     # @param [String] content The content to filter
     #
     # @return [String] The filtered content
-    def run(content, params = {})
+    def run(content, _params = {})
       # Get result
       ::Typogruby.improve(content)
     end
-
   end
-
 end
diff --git a/lib/nanoc/filters/uglify_js.rb b/lib/nanoc/filters/uglify_js.rb
index 71aaee3..4024acc 100644
--- a/lib/nanoc/filters/uglify_js.rb
+++ b/lib/nanoc/filters/uglify_js.rb
@@ -2,7 +2,6 @@
 
 module Nanoc::Filters
   class UglifyJS < Nanoc::Filter
-
     requires 'uglifier'
 
     # Runs the content through [UglifyJS](https://github.com/mishoo/UglifyJS2/).
@@ -17,6 +16,5 @@ module Nanoc::Filters
       # Add filename to load path
       Uglifier.new(params).compile(content)
     end
-
   end
 end
diff --git a/lib/nanoc/filters/xsl.rb b/lib/nanoc/filters/xsl.rb
index 9962476..5dede2d 100644
--- a/lib/nanoc/filters/xsl.rb
+++ b/lib/nanoc/filters/xsl.rb
@@ -1,10 +1,8 @@
 # encoding: utf-8
 
 module Nanoc::Filters
-
   # @since 3.3.0
   class XSL < Nanoc::Filter
-
     requires 'nokogiri'
 
     # Runs the item content through an [XSLT](http://www.w3.org/TR/xslt)
@@ -25,7 +23,7 @@ module Nanoc::Filters
     #
     #     layout 'xsl-report', :xsl, :awesome => 'definitely'
     #
-    # @param [String] content Ignored. As the filter can be run only as a
+    # @param [String] _content Ignored. As the filter can be run only as a
     #   layout, the value of the `:content` parameter passed to the class at
     #   initialization is used as the content to transform.
     #
@@ -33,7 +31,7 @@ module Nanoc::Filters
     #   `xsl:param` elements.
     #
     # @return [String] The transformed content
-    def run(content, params = {})
+    def run(_content, params = {})
       Nanoc::Extra::JRubyNokogiriWarner.check_and_warn
 
       if assigns[:layout].nil?
@@ -45,7 +43,5 @@ module Nanoc::Filters
 
       xsl.apply_to(xml, ::Nokogiri::XSLT.quote_params(params))
     end
-
   end
-
 end
diff --git a/lib/nanoc/filters/yui_compressor.rb b/lib/nanoc/filters/yui_compressor.rb
index 343732f..5b3438e 100644
--- a/lib/nanoc/filters/yui_compressor.rb
+++ b/lib/nanoc/filters/yui_compressor.rb
@@ -1,10 +1,8 @@
 # encoding: utf-8
 
 module Nanoc::Filters
-
   # @since 3.3.0
   class YUICompressor < Nanoc::Filter
-
     requires 'yuicompressor'
 
     # Compress Javascript or CSS using [YUICompressor](http://rubydoc.info/gems/yuicompressor).
@@ -19,7 +17,5 @@ module Nanoc::Filters
     def run(content, params = {})
       ::YUICompressor.compress(content, params)
     end
-
   end
-
 end
diff --git a/lib/nanoc/helpers.rb b/lib/nanoc/helpers.rb
index 7143e22..8f42178 100644
--- a/lib/nanoc/helpers.rb
+++ b/lib/nanoc/helpers.rb
@@ -1,7 +1,6 @@
 # encoding: utf-8
 
 module Nanoc::Helpers
-
   autoload 'Blogging',    'nanoc/helpers/blogging'
   autoload 'Breadcrumbs', 'nanoc/helpers/breadcrumbs'
   autoload 'Capturing',   'nanoc/helpers/capturing'
@@ -12,5 +11,4 @@ module Nanoc::Helpers
   autoload 'Tagging',     'nanoc/helpers/tagging'
   autoload 'Text',        'nanoc/helpers/text'
   autoload 'XMLSitemap',  'nanoc/helpers/xml_sitemap'
-
 end
diff --git a/lib/nanoc/helpers/blogging.rb b/lib/nanoc/helpers/blogging.rb
index f03f9c8..3273b8e 100644
--- a/lib/nanoc/helpers/blogging.rb
+++ b/lib/nanoc/helpers/blogging.rb
@@ -1,7 +1,6 @@
 # encoding: utf-8
 
 module Nanoc::Helpers
-
   # Provides functionality for building blogs, such as finding articles and
   # constructing feeds.
   #
@@ -22,13 +21,12 @@ module Nanoc::Helpers
   #
   # The two main functions are {#sorted_articles} and {#atom_feed}.
   module Blogging
-
     # Returns an unsorted list of articles, i.e. items where the `kind`
     # attribute is set to `"article"`.
     #
     # @return [Array] An array containing all articles
     def articles
-      blk = lambda { @items.select { |item| item[:kind] == 'article' } }
+      blk = -> { @items.select { |item| item[:kind] == 'article' } }
       if @items.frozen?
         @article_items ||= blk.call
       else
@@ -42,9 +40,7 @@ module Nanoc::Helpers
     #
     # @return [Array] A sorted array containing all articles
     def sorted_articles
-      blk = lambda do
-        articles.sort_by { |a| attribute_to_time(a[:created_at]) }.reverse
-      end
+      blk = -> { articles.sort_by { |a| attribute_to_time(a[:created_at]) }.reverse }
 
       if @items.frozen?
         @sorted_article_items ||= blk.call
@@ -54,13 +50,13 @@ module Nanoc::Helpers
     end
 
     class AtomFeedBuilder
-
       include Nanoc::Helpers::Blogging
 
       attr_accessor :site
 
       attr_accessor :limit
       attr_accessor :relevant_articles
+      attr_accessor :preserve_order
       attr_accessor :content_proc
       attr_accessor :excerpt_proc
       attr_accessor :title
@@ -82,17 +78,21 @@ module Nanoc::Helpers
 
       def build
         buffer = ''
-        xml = Builder::XmlMarkup.new(:target => buffer, :indent => 2)
+        xml = Builder::XmlMarkup.new(target: buffer, indent: 2)
         build_for_feed(xml)
         buffer
       end
 
-    protected
+      protected
 
       def sorted_relevant_articles
-        relevant_articles.sort_by do |a|
-          attribute_to_time(a[:created_at])
-        end.reverse.first(limit)
+        all = relevant_articles
+
+        unless @preserve_order
+          all = all.sort_by { |a| attribute_to_time(a[:created_at]) }
+        end
+
+        all.reverse.first(limit)
       end
 
       def last_article
@@ -128,24 +128,24 @@ module Nanoc::Helpers
 
       def build_for_feed(xml)
         xml.instruct!
-        xml.feed(:xmlns => 'http://www.w3.org/2005/Atom') do
+        xml.feed(xmlns: 'http://www.w3.org/2005/Atom') do
           root_url = @site.config[:base_url] + '/'
 
           # Add primary attributes
-          xml.id      root_url
-          xml.title   title
+          xml.id root_url
+          xml.title title
 
           # Add date
           xml.updated(attribute_to_time(last_article[:created_at]).to_iso8601_time)
 
           # Add links
-          xml.link(:rel => 'alternate', :href => root_url)
-          xml.link(:rel => 'self',      :href => feed_url)
+          xml.link(rel: 'alternate', href: root_url)
+          xml.link(rel: 'self',      href: feed_url)
 
           # Add author information
           xml.author do
-            xml.name  author_name
-            xml.uri   author_uri
+            xml.name author_name
+            xml.uri author_uri
           end
 
           # Add icon and logo
@@ -166,31 +166,30 @@ module Nanoc::Helpers
 
         xml.entry do
           # Add primary attributes
-          xml.id        atom_tag_for(a)
-          xml.title     a[:title], :type => 'html'
+          xml.id atom_tag_for(a)
+          xml.title a[:title], type: 'html'
 
           # Add dates
           xml.published attribute_to_time(a[:created_at]).to_iso8601_time
-          xml.updated   attribute_to_time(a[:updated_at] || a[:created_at]).to_iso8601_time
+          xml.updated attribute_to_time(a[:updated_at] || a[:created_at]).to_iso8601_time
 
           # Add specific author information
           if a[:author_name] || a[:author_uri]
             xml.author do
-              xml.name  a[:author_name] || author_name
-              xml.uri   a[:author_uri]  || author_uri
+              xml.name a[:author_name] || author_name
+              xml.uri a[:author_uri] || author_uri
             end
           end
 
           # Add link
-          xml.link(:rel => 'alternate', :href => url)
+          xml.link(rel: 'alternate', href: url)
 
           # Add content
           summary = excerpt_proc.call(a)
-          xml.content   content_proc.call(a), :type => 'html'
-          xml.summary   summary, :type => 'html' unless summary.nil?
+          xml.content content_proc.call(a), type: 'html'
+          xml.summary summary, type: 'html' unless summary.nil?
         end
       end
-
     end
 
     # Returns a string representing the atom feed containing recent articles,
@@ -274,8 +273,13 @@ module Nanoc::Helpers
     # @option params [Number] :limit (5) The maximum number of articles to
     #   show
     #
-    # @option params [Array] :articles (sorted_articles) A list of articles to
-    #   include in the feed
+    # @option params [Array] :articles (articles) A list of articles to include
+    #   in the feed
+    #
+    # @option params [Boolean] :preserve_order (false) Whether or not the
+    #   ordering of the list of articles should be preserved. If false, the
+    #   articles will be sorted by `created_at`. If true, the list of articles
+    #   will be used as-is, and should have the most recent articles last.
     #
     # @option params [Proc] :content_proc (->{ |article|
     #   article.compiled_content(:snapshot => :pre) }) A proc that returns the
@@ -309,8 +313,9 @@ module Nanoc::Helpers
       # Fill builder
       builder.limit             = params[:limit] || 5
       builder.relevant_articles = params[:articles] || articles || []
-      builder.content_proc      = params[:content_proc] || lambda { |a| a.compiled_content(:snapshot => :pre) }
-      builder.excerpt_proc      = params[:excerpt_proc] || lambda { |a| a[:excerpt] }
+      builder.preserve_order    = params.fetch(:preserve_order, false)
+      builder.content_proc      = params[:content_proc] || ->(a) { a.compiled_content(snapshot: :pre) }
+      builder.excerpt_proc      = params[:excerpt_proc] || ->(a) { a[:excerpt] }
       builder.title             = params[:title] || @item[:title] || @site.config[:title]
       builder.author_name       = params[:author_name] || @item[:author_name] || @site.config[:author_name]
       builder.author_uri        = params[:author_uri] || @item[:author_uri] || @site.config[:author_uri]
@@ -386,7 +391,5 @@ module Nanoc::Helpers
       time = Time.parse(time) if time.is_a?(String)
       time
     end
-
   end
-
 end
diff --git a/lib/nanoc/helpers/breadcrumbs.rb b/lib/nanoc/helpers/breadcrumbs.rb
index d72e89a..1510eb0 100644
--- a/lib/nanoc/helpers/breadcrumbs.rb
+++ b/lib/nanoc/helpers/breadcrumbs.rb
@@ -1,11 +1,9 @@
 # encoding: utf-8
 
 module Nanoc::Helpers
-
   # Provides support for breadcrumbs, which allow the user to go up in the
   # page hierarchy.
   module Breadcrumbs
-
     # Creates a breadcrumb trail leading from the current item to its parent,
     # to its parent’s parent, etc, until the root item is reached. This
     # function does not require that each intermediate item exist; for
@@ -29,7 +27,5 @@ module Nanoc::Helpers
 
       trail
     end
-
   end
-
 end
diff --git a/lib/nanoc/helpers/capturing.rb b/lib/nanoc/helpers/capturing.rb
index 82282fd..553ab07 100644
--- a/lib/nanoc/helpers/capturing.rb
+++ b/lib/nanoc/helpers/capturing.rb
@@ -1,7 +1,6 @@
 # encoding: utf-8
 
 module Nanoc::Helpers
-
   # Provides functionality for “capturing” content in one place and reusing
   # this content elsewhere.
   #
@@ -35,10 +34,8 @@ module Nanoc::Helpers
   #     <%= @item[:content_for_summary] || '(no summary)' %>
   #   </div>
   module Capturing
-
     # @api private
     class CapturesStore
-
       def initialize
         @store = {}
       end
@@ -52,11 +49,9 @@ module Nanoc::Helpers
         @store[item.identifier] ||= {}
         @store[item.identifier][name]
       end
-
     end
 
     class ::Nanoc::Site
-
       # @api private
       def captures_store
         @captures_store ||= CapturesStore.new
@@ -67,7 +62,6 @@ module Nanoc::Helpers
         require 'set'
         @captures_store_compiled_items ||= Set.new
       end
-
     end
 
     # @overload content_for(name, &block)
@@ -101,7 +95,7 @@ module Nanoc::Helpers
       if block_given? # Set content
         # Get args
         if args.size != 1
-          raise ArgumentError, 'expected 1 argument (the name ' +
+          raise ArgumentError, 'expected 1 argument (the name ' \
             "of the capture) but got #{args.size} instead"
         end
         name = args[0]
@@ -112,7 +106,7 @@ module Nanoc::Helpers
       else # Get content
         # Get args
         if args.size != 2
-          raise ArgumentError, 'expected 2 arguments (the item ' +
+          raise ArgumentError, 'expected 2 arguments (the item ' \
             "and the name of the capture) but got #{args.size} instead"
         end
         item = args[0]
@@ -127,13 +121,13 @@ module Nanoc::Helpers
           # This is an extremely ugly hack to get the compiler to recompile the
           # item from which we use content. For this, we need to manually edit
           # the content attribute to reset it. :(
-          # FIXME clean this up
-          if !@site.captures_store_compiled_items.include? item
+          # FIXME: clean this up
+          unless @site.captures_store_compiled_items.include? item
             @site.captures_store_compiled_items << item
             item.forced_outdated = true
             item.reps.each do |r|
               raw_content = item.raw_content
-              r.content = { :raw => raw_content, :last => raw_content }
+              r.content = { raw: raw_content, last: raw_content }
               raise Nanoc::Errors::UnmetDependency.new(r)
             end
           end
@@ -169,7 +163,5 @@ module Nanoc::Helpers
       # Done.
       erbout_addition
     end
-
   end
-
 end
diff --git a/lib/nanoc/helpers/filtering.rb b/lib/nanoc/helpers/filtering.rb
index 2504bf0..bee02ff 100644
--- a/lib/nanoc/helpers/filtering.rb
+++ b/lib/nanoc/helpers/filtering.rb
@@ -1,10 +1,8 @@
 # encoding: utf-8
 
 module Nanoc::Helpers
-
   # Provides functionality for filtering parts of an item or a layout.
   module Filtering
-
     require 'nanoc/helpers/capturing'
     include Nanoc::Helpers::Capturing
 
@@ -46,7 +44,5 @@ module Nanoc::Helpers
       buffer = eval('_erbout', block.binding)
       buffer << filtered_data
     end
-
   end
-
 end
diff --git a/lib/nanoc/helpers/html_escape.rb b/lib/nanoc/helpers/html_escape.rb
index 3c41ee9..1e2f253 100644
--- a/lib/nanoc/helpers/html_escape.rb
+++ b/lib/nanoc/helpers/html_escape.rb
@@ -1,10 +1,8 @@
 # encoding: utf-8
 
 module Nanoc::Helpers
-
   # Contains functionality for HTML-escaping strings.
   module HTMLEscape
-
     require 'nanoc/helpers/capturing'
     include Nanoc::Helpers::Capturing
 
@@ -38,18 +36,16 @@ module Nanoc::Helpers
         buffer = eval('_erbout', block.binding)
         buffer << escaped_data
       elsif string
-        string.gsub('&', '&').
-               gsub('<', '<').
-               gsub('>', '>').
-               gsub('"', '"')
+        string.gsub('&', '&')
+          .gsub('<', '<')
+          .gsub('>', '>')
+          .gsub('"', '"')
       else
-        raise "The #html_escape or #h function needs either a " \
-          "string or a block to HTML-escape, but neither a string nor a block was given"
+        raise 'The #html_escape or #h function needs either a ' \
+          'string or a block to HTML-escape, but neither a string nor a block was given'
       end
     end
 
     alias_method :h, :html_escape
-
   end
-
 end
diff --git a/lib/nanoc/helpers/link_to.rb b/lib/nanoc/helpers/link_to.rb
index c95e35b..5688b43 100644
--- a/lib/nanoc/helpers/link_to.rb
+++ b/lib/nanoc/helpers/link_to.rb
@@ -1,10 +1,8 @@
 # encoding: utf-8
 
 module Nanoc::Helpers
-
   # Contains functions for linking to items and item representations.
   module LinkTo
-
     require 'nanoc/helpers/html_escape'
     include Nanoc::Helpers::HTMLEscape
 
@@ -154,7 +152,5 @@ module Nanoc::Helpers
       # Done
       relative_path
     end
-
   end
-
 end
diff --git a/lib/nanoc/helpers/rendering.rb b/lib/nanoc/helpers/rendering.rb
index de6475d..b5ccdd0 100644
--- a/lib/nanoc/helpers/rendering.rb
+++ b/lib/nanoc/helpers/rendering.rb
@@ -1,10 +1,8 @@
 # encoding: utf-8
 
 module Nanoc::Helpers
-
   # Provides functionality for rendering layouts as partials.
   module Rendering
-
     include Nanoc::Helpers::Capturing
 
     # Renders the given layout. The given layout will be run through the first
@@ -91,14 +89,14 @@ module Nanoc::Helpers
 
       # Get assigns
       assigns = {
-        :content    => captured_content,
-        :item       => @item,
-        :item_rep   => @item_rep,
-        :items      => @items,
-        :layout     => layout,
-        :layouts    => @layouts,
-        :config     => @config,
-        :site       => @site
+        content: captured_content,
+        item: @item,
+        item_rep: @item_rep,
+        items: @items,
+        layout: layout,
+        layouts: @layouts,
+        config: @config,
+        site: @site
       }.merge(other_assigns)
 
       # Get filter name
@@ -134,7 +132,5 @@ module Nanoc::Helpers
         Nanoc::NotificationCenter.post(:processing_ended, layout)
       end
     end
-
   end
-
 end
diff --git a/lib/nanoc/helpers/tagging.rb b/lib/nanoc/helpers/tagging.rb
index 6750f7f..0eb6c90 100644
--- a/lib/nanoc/helpers/tagging.rb
+++ b/lib/nanoc/helpers/tagging.rb
@@ -1,7 +1,6 @@
 # encoding: utf-8
 
 module Nanoc::Helpers
-
   # Provides support for managing tags added to items.
   #
   # To add tags to items, set the `tags` attribute to an array of tags that
@@ -11,7 +10,6 @@ module Nanoc::Helpers
   #
   #   tags: [ 'foo', 'bar', 'baz' ]
   module Tagging
-
     require 'nanoc/helpers/html_escape'
     include Nanoc::Helpers::HTMLEscape
 
@@ -31,7 +29,7 @@ module Nanoc::Helpers
     #
     # @return [String] A hyperlinked list of tags for the given item
     def tags_for(item, params = {})
-      base_url  = params[:base_url]  || 'http://technorati.com/tag/'
+      base_url  = params[:base_url] || 'http://technorati.com/tag/'
       none_text = params[:none_text] || '(none)'
       separator = params[:separator] || ', '
 
@@ -63,9 +61,7 @@ module Nanoc::Helpers
     #
     # @return [String] A link for the given tag and the given base URL
     def link_for_tag(tag, base_url)
-      %[<a href="#{h base_url}#{h tag}" rel="tag">#{h tag}</a>]
+      %(<a href="#{h base_url}#{h tag}" rel="tag">#{h tag}</a>)
     end
-
   end
-
 end
diff --git a/lib/nanoc/helpers/text.rb b/lib/nanoc/helpers/text.rb
index 575ba4e..d37d438 100644
--- a/lib/nanoc/helpers/text.rb
+++ b/lib/nanoc/helpers/text.rb
@@ -1,10 +1,8 @@
 # encoding: utf-8
 
 module Nanoc::Helpers
-
   # Contains several useful text-related helper functions.
   module Text
-
     # Returns an excerpt for the given string. HTML tags are ignored, so if
     # you don't want them to turn up, they should be stripped from the string
     # before passing it to the excerpt function.
@@ -20,7 +18,7 @@ module Nanoc::Helpers
     # @return [String] The excerpt of the given string
     def excerptize(string, params = {})
       # Initialize params
-      params[:length]   ||= 25
+      params[:length] ||= 25
       params[:omission] ||= '...'
 
       # Get excerpt
@@ -35,10 +33,8 @@ module Nanoc::Helpers
     #
     # @return [String] The given string with all HTML stripped
     def strip_html(string)
-      # FIXME will need something more sophisticated than this, because it sucks
+      # FIXME: will need something more sophisticated than this, because it sucks
       string.gsub(/<[^>]*(>+|\s*\z)/m, '').strip
     end
-
   end
-
 end
diff --git a/lib/nanoc/helpers/xml_sitemap.rb b/lib/nanoc/helpers/xml_sitemap.rb
index 333d76c..37880d4 100644
--- a/lib/nanoc/helpers/xml_sitemap.rb
+++ b/lib/nanoc/helpers/xml_sitemap.rb
@@ -1,12 +1,10 @@
 # encoding: utf-8
 
 module Nanoc::Helpers
-
   # Contains functionality for building XML sitemaps that will be crawled by
   # search engines. See the [Sitemaps protocol site](http://www.sitemaps.org)
   # for details.
   module XMLSitemap
-
     # Builds an XML sitemap and returns it.
     #
     # The following attributes can optionally be set on items to change the
@@ -48,7 +46,7 @@ module Nanoc::Helpers
 
       # Create builder
       buffer = ''
-      xml = Builder::XmlMarkup.new(:target => buffer, :indent => 2)
+      xml = Builder::XmlMarkup.new(target: buffer, indent: 2)
 
       # Check for required attributes
       if @site.config[:base_url].nil?
@@ -57,17 +55,17 @@ module Nanoc::Helpers
 
       # Build sitemap
       xml.instruct!
-      xml.urlset(:xmlns => 'http://www.sitemaps.org/schemas/sitemap/0.9') do
+      xml.urlset(xmlns: 'http://www.sitemaps.org/schemas/sitemap/0.9') do
         # Add item
-        items.sort_by { |i| i.identifier }.each do |item|
+        items.sort_by(&:identifier).each do |item|
           reps = item.reps.reject { |r| r.raw_path.nil? }
           reps.reject! { |r| !select_proc[r] } if select_proc
           reps.sort_by { |r| r.name.to_s }.each do |rep|
             xml.url do
-              xml.loc         @site.config[:base_url] + rep.path
-              xml.lastmod     item[:mtime].to_iso8601_date unless item[:mtime].nil?
-              xml.changefreq  item[:changefreq] unless item[:changefreq].nil?
-              xml.priority    item[:priority] unless item[:priority].nil?
+              xml.loc @site.config[:base_url] + rep.path
+              xml.lastmod item[:mtime].to_iso8601_date unless item[:mtime].nil?
+              xml.changefreq item[:changefreq] unless item[:changefreq].nil?
+              xml.priority item[:priority] unless item[:priority].nil?
             end
           end
         end
@@ -76,7 +74,5 @@ module Nanoc::Helpers
       # Return sitemap
       buffer
     end
-
   end
-
 end
diff --git a/lib/nanoc/tasks/clean.rake b/lib/nanoc/tasks/clean.rake
index a7c18fb..60b0a08 100644
--- a/lib/nanoc/tasks/clean.rake
+++ b/lib/nanoc/tasks/clean.rake
@@ -5,7 +5,7 @@ task :clean do
   # Load site
   site = Nanoc::Site.new('.')
   if site.nil?
-    $stderr.puts 'The current working directory does not seem to be a ' +
+    $stderr.puts 'The current working directory does not seem to be a ' \
                  'valid/complete nanoc site directory; aborting.'
     exit 1
   end
diff --git a/lib/nanoc/tasks/clean.rb b/lib/nanoc/tasks/clean.rb
index 5912520..f6a80eb 100644
--- a/lib/nanoc/tasks/clean.rb
+++ b/lib/nanoc/tasks/clean.rb
@@ -1,9 +1,7 @@
 # encoding: utf-8
 
 module Nanoc::Tasks
-
   class Clean
-
     def initialize(site)
       @site = site
     end
@@ -14,16 +12,12 @@ module Nanoc::Tasks
       end
     end
 
-  private
+    private
 
     def filenames
       @site.items.map do |item|
-        item.reps.map do |rep|
-          rep.raw_path
-        end
+        item.reps.map(&:raw_path)
       end.flatten
     end
-
   end
-
 end
diff --git a/lib/nanoc/tasks/deploy/rsync.rake b/lib/nanoc/tasks/deploy/rsync.rake
index e1b7fbd..e541fc4 100644
--- a/lib/nanoc/tasks/deploy/rsync.rake
+++ b/lib/nanoc/tasks/deploy/rsync.rake
@@ -3,16 +3,14 @@
 require 'nanoc/cli'
 
 namespace :deploy do
-
   desc 'Upload the compiled site using rsync'
   task :rsync do
-    dry_run     = !!ENV['dry_run']
+    dry_run     = ENV['dry_run']
     config_name = ENV['config'] || :default
 
-    cmd = [ 'deploy', '-t', config_name ]
+    cmd = ['deploy', '-t', config_name]
     cmd << '-n' if dry_run
 
     Nanoc::CLI.run cmd
   end
-
 end
diff --git a/lib/nanoc/tasks/validate.rake b/lib/nanoc/tasks/validate.rake
index 7dc543b..9f3a315 100644
--- a/lib/nanoc/tasks/validate.rake
+++ b/lib/nanoc/tasks/validate.rake
@@ -1,7 +1,6 @@
 # encoding: utf-8
 
 namespace :validate do
-
   desc 'Validate the site’s HTML files'
   task :html do
     Nanoc::CLI.run %w( validate_html )
@@ -13,7 +12,6 @@ namespace :validate do
   end
 
   namespace :links do
-
     desc 'Validate the site’s internal links'
     task :internal do
       Nanoc::CLI.run %w( validate_links --internal )
@@ -23,12 +21,10 @@ namespace :validate do
     task :external do
       Nanoc::CLI.run %w( validate_links --external )
     end
-
   end
 
   desc 'Validate the site’s internal and external links'
   task :links do
     Nanoc::CLI.run %w( validate_links )
   end
-
 end
diff --git a/lib/nanoc/version.rb b/lib/nanoc/version.rb
index fc3b319..ebba8cf 100644
--- a/lib/nanoc/version.rb
+++ b/lib/nanoc/version.rb
@@ -1,8 +1,6 @@
 # encoding: utf-8
 
 module Nanoc
-
   # The current nanoc version.
-  VERSION = '3.7.3'
-
+  VERSION = '3.8.0'
 end
diff --git a/metadata.yml b/metadata.yml
index 67bbb61..5f4f8df 100644
--- a/metadata.yml
+++ b/metadata.yml
@@ -1,14 +1,14 @@
 --- !ruby/object:Gem::Specification
 name: nanoc
 version: !ruby/object:Gem::Version
-  version: 3.7.3
+  version: 3.8.0
 platform: ruby
 authors:
 - Denis Defreyne
 autorequire: 
 bindir: bin
 cert_chain: []
-date: 2014-08-31 00:00:00.000000000 Z
+date: 2015-05-04 00:00:00.000000000 Z
 dependencies:
 - !ruby/object:Gem::Dependency
   name: cri
@@ -28,16 +28,22 @@ dependencies:
   name: bundler
   requirement: !ruby/object:Gem::Requirement
     requirements:
-    - - "~>"
+    - - ">="
+      - !ruby/object:Gem::Version
+        version: 1.7.10
+    - - "<"
       - !ruby/object:Gem::Version
-        version: '1.5'
+        version: '2.0'
   type: :development
   prerelease: false
   version_requirements: !ruby/object:Gem::Requirement
     requirements:
-    - - "~>"
+    - - ">="
       - !ruby/object:Gem::Version
-        version: '1.5'
+        version: 1.7.10
+    - - "<"
+      - !ruby/object:Gem::Version
+        version: '2.0'
 description: nanoc is a simple but very flexible static site generator written in
   Ruby. It operates on local files, and therefore does not run on the server. nanoc
   “compiles” the local source files into HTML (usually), by evaluating eRuby, Markdown,
@@ -55,7 +61,6 @@ files:
 - CONTRIBUTING.md
 - ChangeLog
 - Gemfile
-- Gemfile.lock
 - LICENSE
 - NEWS.md
 - README.md
@@ -84,7 +89,6 @@ files:
 - lib/nanoc/base/context.rb
 - lib/nanoc/base/core_ext.rb
 - lib/nanoc/base/core_ext/array.rb
-- lib/nanoc/base/core_ext/date.rb
 - lib/nanoc/base/core_ext/hash.rb
 - lib/nanoc/base/core_ext/pathname.rb
 - lib/nanoc/base/core_ext/string.rb
@@ -92,7 +96,6 @@ files:
 - lib/nanoc/base/errors.rb
 - lib/nanoc/base/memoization.rb
 - lib/nanoc/base/notification_center.rb
-- lib/nanoc/base/ordered_hash.rb
 - lib/nanoc/base/plugin_registry.rb
 - lib/nanoc/base/result_data/item_rep.rb
 - lib/nanoc/base/source_data/code_snippet.rb
@@ -151,13 +154,13 @@ files:
 - lib/nanoc/extra/checking/checks/external_links.rb
 - lib/nanoc/extra/checking/checks/html.rb
 - lib/nanoc/extra/checking/checks/internal_links.rb
+- lib/nanoc/extra/checking/checks/mixed_content.rb
 - lib/nanoc/extra/checking/checks/stale.rb
 - lib/nanoc/extra/checking/dsl.rb
 - lib/nanoc/extra/checking/issue.rb
 - lib/nanoc/extra/checking/runner.rb
 - lib/nanoc/extra/chick.rb
 - lib/nanoc/extra/core_ext.rb
-- lib/nanoc/extra/core_ext/enumerable.rb
 - lib/nanoc/extra/core_ext/pathname.rb
 - lib/nanoc/extra/core_ext/time.rb
 - lib/nanoc/extra/deployer.rb
@@ -236,7 +239,6 @@ files:
 - tasks/test.rake
 - test/base/checksummer_spec.rb
 - test/base/core_ext/array_spec.rb
-- test/base/core_ext/date_spec.rb
 - test/base/core_ext/hash_spec.rb
 - test/base/core_ext/pathname_spec.rb
 - test/base/core_ext/string_spec.rb
@@ -253,6 +255,7 @@ files:
 - test/base/test_item.rb
 - test/base/test_item_array.rb
 - test/base/test_item_rep.rb
+- test/base/test_item_rep_recorder_proxy.rb
 - test/base/test_layout.rb
 - test/base/test_memoization.rb
 - test/base/test_notification_center.rb
@@ -286,11 +289,11 @@ files:
 - test/extra/checking/checks/test_external_links.rb
 - test/extra/checking/checks/test_html.rb
 - test/extra/checking/checks/test_internal_links.rb
+- test/extra/checking/checks/test_mixed_content.rb
 - test/extra/checking/checks/test_stale.rb
 - test/extra/checking/test_check.rb
 - test/extra/checking/test_dsl.rb
 - test/extra/checking/test_runner.rb
-- test/extra/core_ext/test_enumerable.rb
 - test/extra/core_ext/test_pathname.rb
 - test/extra/core_ext/test_time.rb
 - test/extra/deployers/test_fog.rb
@@ -333,6 +336,7 @@ files:
 - test/filters/test_yui_compressor.rb
 - test/fixtures/vcr_cassettes/css_run_error.yml
 - test/fixtures/vcr_cassettes/css_run_ok.yml
+- test/fixtures/vcr_cassettes/css_run_parse_error.yml
 - test/fixtures/vcr_cassettes/html_run_error.yml
 - test/fixtures/vcr_cassettes/html_run_ok.yml
 - test/gem_loader.rb
@@ -363,7 +367,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
   requirements:
   - - ">="
     - !ruby/object:Gem::Version
-      version: '0'
+      version: 1.9.3
 required_rubygems_version: !ruby/object:Gem::Requirement
   requirements:
   - - ">="
@@ -371,7 +375,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
       version: '0'
 requirements: []
 rubyforge_project: 
-rubygems_version: 2.2.2
+rubygems_version: 2.4.6
 signing_key: 
 specification_version: 4
 summary: a web publishing system written in Ruby for building small to medium-sized
diff --git a/nanoc.gemspec b/nanoc.gemspec
index 6e180a9..6dc12a6 100644
--- a/nanoc.gemspec
+++ b/nanoc.gemspec
@@ -14,17 +14,20 @@ Gem::Specification.new do |s|
   s.email   = 'denis.defreyne at stoneship.org'
   s.license = 'MIT'
 
-  s.files              = Dir['[A-Z]*'] +
-                         Dir['doc/yardoc_{templates,handlers}/**/*'] +
-                         Dir['{bin,lib,tasks,test}/**/*'] +
-                         [ 'nanoc.gemspec' ]
-  s.executables        = [ 'nanoc' ]
-  s.require_paths      = [ 'lib' ]
+  s.files =
+    Dir['[A-Z]*'] +
+      Dir['doc/yardoc_{templates,handlers}/**/*'] +
+      Dir['{bin,lib,tasks,test}/**/*'] +
+      ['nanoc.gemspec']
+  s.executables        = ['nanoc']
+  s.require_paths      = ['lib']
 
-  s.rdoc_options     = [ '--main', 'README.md' ]
-  s.extra_rdoc_files = [ 'ChangeLog', 'LICENSE', 'README.md', 'NEWS.md' ]
+  s.rdoc_options     = ['--main', 'README.md']
+  s.extra_rdoc_files = ['ChangeLog', 'LICENSE', 'README.md', 'NEWS.md']
+
+  s.required_ruby_version = '>= 1.9.3'
 
   s.add_runtime_dependency('cri', '~> 2.3')
 
-  s.add_development_dependency('bundler', '~> 1.5')
+  s.add_development_dependency('bundler', '>= 1.7.10', '< 2.0')
 end
diff --git a/tasks/rubocop.rake b/tasks/rubocop.rake
index 81feb01..7d0e85a 100644
--- a/tasks/rubocop.rake
+++ b/tasks/rubocop.rake
@@ -2,8 +2,9 @@ begin
   require 'rubocop/rake_task'
 
   RuboCop::RakeTask.new(:rubocop) do |task|
+    task.options  = %w( --display-cop-names --format simple )
     task.patterns = ['lib/**/*.rb']
   end
 rescue LoadError
-  warn "Could not load RuboCop. RuboCop rake tasks will be unavailable."
+  warn 'Could not load RuboCop. RuboCop rake tasks will be unavailable.'
 end
diff --git a/tasks/test.rake b/tasks/test.rake
index 14837d2..eead35f 100644
--- a/tasks/test.rake
+++ b/tasks/test.rake
@@ -12,26 +12,25 @@ def run_tests(dir_glob)
   test_files = Dir["#{dir_glob}*_spec.rb"] + Dir["#{dir_glob}test_*.rb"]
   test_files.each { |f| require f }
 
-  exit MiniTest::Unit.new.run(ENV['ARGS'].split)
+  res = Minitest.run(ENV['ARGS'].split)
+  exit(res) if res != 0
 end
 
 namespace :test do
-
   # test:all
   desc 'Run all tests'
   task :all do
-    run_tests "test/**/"
+    run_tests 'test/**/'
   end
 
   # test:...
   %w( base cli data_sources extra filters helpers tasks ).each do |dir|
     desc "Run all #{dir} tests"
-    task dir.to_sym do |task|
+    task dir.to_sym do |_task|
       run_tests "test/#{dir}/**/"
     end
   end
-
 end
 
-desc 'Alias for test:all'
-task :test => [ :'test:all' ]
+desc 'Alias for test:all + rubocop'
+task test: [:'test:all', :rubocop]
diff --git a/test/base/checksummer_spec.rb b/test/base/checksummer_spec.rb
index e63ed22..13708bb 100644
--- a/test/base/checksummer_spec.rb
+++ b/test/base/checksummer_spec.rb
@@ -3,21 +3,17 @@
 require 'tempfile'
 
 describe Nanoc::Checksummer do
-
   subject { Nanoc::Checksummer }
 
   CHECKSUM_REGEX = /\A[0-9a-zA-Z\/+]+=*\Z/
 
   describe 'for String' do
-
     it 'should checksum strings' do
       subject.calc('foo').must_equal('+5k/BWvkYc6T1qhGaSyf3861CyE=')
     end
-
   end
 
   describe 'for Array' do
-
     it 'should checksum arrays' do
       subject.calc([1, 'a', :a]).must_equal 'YtWOEFUAMQritkY38KXHFZM/n2E='
     end
@@ -30,10 +26,14 @@ describe Nanoc::Checksummer do
       subject.calc([-> {}]).must_match(CHECKSUM_REGEX)
     end
 
+    it 'should checksum recursive arrays' do
+      array = [:a]
+      array << array
+      subject.calc(array).must_equal('mR3c98xA5ecazxx+M3h0Ss4/J20=')
+    end
   end
 
   describe 'for Hash' do
-
     it 'should checksum hashes' do
       subject.calc({ a: 1, b: 2 }).must_equal 'qY8fW6gWK7F1XQ9MLrx3Gru/RTY='
     end
@@ -43,18 +43,28 @@ describe Nanoc::Checksummer do
     end
 
     it 'should checksum non-serializable hashes' do
-      subject.calc({ a: ->{} }).must_match(CHECKSUM_REGEX)
+      subject.calc({ a: -> {} }).must_match(CHECKSUM_REGEX)
     end
 
+    it 'should checksum recursive hash keys' do
+      hash = {}
+      hash[hash] = 123
+      subject.calc(hash).must_equal('mKucWMhRtR/FHWNqR/EErF4qgTk=')
+    end
+
+    it 'should checksum recursive hash values' do
+      hash = {}
+      hash[123] = hash
+      subject.calc(hash).must_equal('PBiDX0nWnV+DAYB+w+xM0Kf21ZM=')
+    end
   end
 
   describe 'for Pathname' do
-
     let(:file)            { Tempfile.new('foo') }
     let(:filename)        { file.path }
     let(:pathname)        { Pathname.new(filename) }
-    let(:atime)           { 1234567890 }
-    let(:mtime)           { 1234567890 }
+    let(:atime)           { 1_234_567_890 }
+    let(:mtime)           { 1_234_567_890 }
     let(:data)            { 'stuffs' }
     let(:normal_checksum) { 'THy7Y28oroov/KvPxT6wcMnXr/s=' }
 
@@ -69,14 +79,12 @@ describe Nanoc::Checksummer do
     end
 
     describe 'does not exist' do
-
       let(:non_existing_filename) { 'askldjfklaslasdfkjsajdf' }
       let(:pathname)              { Pathname.new(filename) }
 
       it 'should still checksum' do
         subject.calc(pathname).must_equal(normal_checksum)
       end
-
     end
 
     it 'should get the mtime right' do
@@ -94,43 +102,35 @@ describe Nanoc::Checksummer do
     end
 
     describe 'if the mtime changes' do
-
-      let(:mtime) { 1333333333 }
+      let(:mtime) { 1_333_333_333 }
 
       it 'should have a different checksum' do
         subject.calc(pathname).must_match(CHECKSUM_REGEX)
         subject.calc(pathname).wont_equal(normal_checksum)
       end
-
     end
 
     describe 'if the content changes, but not the file size' do
-
       let(:data) { 'STUFF!' }
 
       it 'should have the same checksum' do
         subject.calc(pathname).must_equal(normal_checksum)
       end
-
     end
 
     describe 'if the file size changes' do
-
       let(:data) { 'stuff and stuff and stuff!!!' }
 
       it 'should have a different checksum' do
         subject.calc(pathname).must_match(CHECKSUM_REGEX)
         subject.calc(pathname).wont_equal(normal_checksum)
       end
-
     end
-
   end
 
   it 'should not have the same checksum for same content but different class'
 
   describe 'for Nanoc::RulesCollection' do
-
     let(:data)            { 'STUFF!' }
     let(:normal_checksum) { 'r4SwDpCp5saBPeZk2gQOJcipTZU=' }
 
@@ -145,20 +145,16 @@ describe Nanoc::Checksummer do
     end
 
     describe 'if the content changes' do
-
       let(:data) { 'Other stuff!' }
 
       it 'should have a different checksum' do
         subject.calc(rules_collection).must_match(CHECKSUM_REGEX)
         subject.calc(rules_collection).wont_equal(normal_checksum)
       end
-
     end
-
   end
 
   describe 'for Nanoc::CodeSnippet' do
-
     let(:data)            { 'asdf' }
     let(:filename)        { File.expand_path('bob.txt') }
     let(:code_snippet)    { Nanoc::CodeSnippet.new(data, filename) }
@@ -169,30 +165,24 @@ describe Nanoc::Checksummer do
     end
 
     describe 'if the filename changes' do
-
       let(:filename) { File.expand_path('george.txt') }
 
       it 'should have the same checksum' do
         subject.calc(code_snippet).must_equal(normal_checksum)
       end
-
     end
 
     describe 'if the content changes' do
-
       let(:data) { 'Other stuff!' }
 
       it 'should have a different checksum' do
         subject.calc(code_snippet).must_match(CHECKSUM_REGEX)
         subject.calc(code_snippet).wont_equal(normal_checksum)
       end
-
     end
-
   end
 
   describe 'for Nanoc::Configuration' do
-
     let(:wrapped)         { { a: 1, b: 2 } }
     let(:configuration)   { Nanoc::Configuration.new(wrapped) }
     let(:normal_checksum) { 'eYYQ74x29njbtXMtuKZX/ogD8JA=' }
@@ -202,20 +192,16 @@ describe Nanoc::Checksummer do
     end
 
     describe 'if the content changes' do
-
       let(:wrapped) { { a: 666, b: 2 } }
 
       it 'should have a different checksum' do
         subject.calc(configuration).must_match(CHECKSUM_REGEX)
         subject.calc(configuration).wont_equal(normal_checksum)
       end
-
     end
-
   end
 
   describe 'for Nanoc::Item' do
-
     let(:content)         { 'asdf' }
     let(:filename)        { File.expand_path('bob.txt') }
     let(:attributes)      { { a: 1, b: 2 } }
@@ -227,48 +213,46 @@ describe Nanoc::Checksummer do
       subject.calc(item).must_equal(normal_checksum)
     end
 
-    describe 'with changed attributes' do
+    describe 'with recursive attributes' do
+      it 'should checksum' do
+        item.attributes[:a] = item
+        subject.calc(item).must_match(CHECKSUM_REGEX)
+        subject.calc(item).wont_equal(normal_checksum)
+      end
+    end
 
+    describe 'with changed attributes' do
       let(:attributes) { { x: 4, y: 5 } }
 
       it 'should have a different checksum' do
         subject.calc(item).must_match(CHECKSUM_REGEX)
         subject.calc(item).wont_equal(normal_checksum)
       end
-
     end
 
     describe 'with changed content' do
-
       let(:content) { 'something drastically different' }
 
       it 'should have a different checksum' do
         subject.calc(item).must_match(CHECKSUM_REGEX)
         subject.calc(item).wont_equal(normal_checksum)
       end
-
     end
-
   end
 
   describe 'for other marshal-able classes' do
-
     let(:obj) { :foobar }
 
     it 'should checksum' do
       subject.calc(obj).must_match(CHECKSUM_REGEX)
     end
-
   end
 
   describe 'for other non-marshal-able classes' do
-
     let(:obj) { proc {} }
 
     it 'should checksum' do
       subject.calc(obj).must_match(CHECKSUM_REGEX)
     end
-
   end
-
 end
diff --git a/test/base/core_ext/array_spec.rb b/test/base/core_ext/array_spec.rb
index 5c6f73a..e0eef3b 100644
--- a/test/base/core_ext/array_spec.rb
+++ b/test/base/core_ext/array_spec.rb
@@ -1,31 +1,26 @@
 # encoding: utf-8
 
 describe 'Array#symbolize_keys_recursively' do
-
   it 'should convert keys to symbols' do
-    array_old = [ :abc, 'xyz', { 'foo' => 'bar', :baz => :qux } ]
-    array_new = [ :abc, 'xyz', { :foo  => 'bar', :baz => :qux } ]
+    array_old = [:abc, 'xyz', { 'foo' => 'bar', :baz => :qux }]
+    array_new = [:abc, 'xyz', { foo: 'bar', baz: :qux }]
     array_old.symbolize_keys_recursively.must_equal array_new
   end
-
 end
 
 describe 'Array#stringify_keys_recursively' do
-
   it 'should convert keys to strings' do
-    array_old = [ :abc, 'xyz', { :foo  => 'bar', 'baz' => :qux } ]
-    array_new = [ :abc, 'xyz', { 'foo' => 'bar', 'baz' => :qux } ]
+    array_old = [:abc, 'xyz', { :foo  => 'bar', 'baz' => :qux }]
+    array_new = [:abc, 'xyz', { 'foo' => 'bar', 'baz' => :qux }]
     array_old.stringify_keys_recursively.must_equal array_new
   end
-
 end
 
 describe 'Array#freeze_recursively' do
-
   include Nanoc::TestHelpers
 
   it 'should prevent first-level elements from being modified' do
-    array = [ :a, [ :b, :c ], :d ]
+    array = [:a, [:b, :c], :d]
     array.freeze_recursively
 
     assert_raises_frozen_error do
@@ -34,7 +29,7 @@ describe 'Array#freeze_recursively' do
   end
 
   it 'should prevent second-level elements from being modified' do
-    array = [ :a, [ :b, :c ], :d ]
+    array = [:a, [:b, :c], :d]
     array.freeze_recursively
 
     assert_raises_frozen_error do
@@ -52,14 +47,11 @@ describe 'Array#freeze_recursively' do
     assert a[0].frozen?
     assert_equal a, a[0]
   end
-
 end
 
 describe 'Array#checksum' do
-
   it 'should work' do
     expectation = 'CEUlNvu/3DUmlbtpFRiLHU8oHA0='
-    [ [ :foo, 123 ] ].checksum.must_equal expectation
+    [[:foo, 123]].checksum.must_equal expectation
   end
-
 end
diff --git a/test/base/core_ext/date_spec.rb b/test/base/core_ext/date_spec.rb
deleted file mode 100644
index 42f5c37..0000000
--- a/test/base/core_ext/date_spec.rb
+++ /dev/null
@@ -1,15 +0,0 @@
-# encoding: utf-8
-
-describe 'Date' do
-
-  it 'should not crash when requesting frozen attributes' do
-    # This test will pass without patch on MRI 1.9.x, but on MRI 1.8.x it
-    # crashes. (Untested on other Ruby implementations such as Rubinius and
-    # JRuby).
-    d = Date.today
-    d.freeze
-    d.year
-  end
-
-end
-
diff --git a/test/base/core_ext/hash_spec.rb b/test/base/core_ext/hash_spec.rb
index 3c0134e..7b1ad82 100644
--- a/test/base/core_ext/hash_spec.rb
+++ b/test/base/core_ext/hash_spec.rb
@@ -1,10 +1,9 @@
 # encoding: utf-8
 
 describe 'Hash#symbolize_keys_recursively' do
-
   it 'should convert keys to symbols' do
     hash_old = { 'foo' => 'bar' }
-    hash_new = { :foo  => 'bar' }
+    hash_new = { foo: 'bar' }
     hash_old.symbolize_keys_recursively.must_equal hash_new
   end
 
@@ -13,11 +12,9 @@ describe 'Hash#symbolize_keys_recursively' do
     hash_new = hash_old
     hash_old.symbolize_keys_recursively.must_equal hash_new
   end
-
 end
 
 describe 'Hash#stringify_keys_recursively' do
-
   it 'should leave strings as strings' do
     hash_old = { 'foo' => 'bar' }
     hash_new = { 'foo' => 'bar' }
@@ -25,7 +22,7 @@ describe 'Hash#stringify_keys_recursively' do
   end
 
   it 'should convert symbols to strings' do
-    hash_old = { :foo  => 'bar' }
+    hash_old = { foo: 'bar' }
     hash_new = { 'foo' => 'bar' }
     hash_old.stringify_keys_recursively.must_equal hash_new
   end
@@ -41,15 +38,13 @@ describe 'Hash#stringify_keys_recursively' do
     hash_new = { ''  => 'bar' }
     hash_old.stringify_keys_recursively.must_equal hash_new
   end
-
 end
 
 describe 'Hash#freeze_recursively' do
-
   include Nanoc::TestHelpers
 
   it 'should prevent first-level elements from being modified' do
-    hash = { :a => { :b => :c } }
+    hash = { a: { b: :c } }
     hash.freeze_recursively
 
     assert_raises_frozen_error do
@@ -58,7 +53,7 @@ describe 'Hash#freeze_recursively' do
   end
 
   it 'should prevent second-level elements from being modified' do
-    hash = { :a => { :b => :c } }
+    hash = { a: { b: :c } }
     hash.freeze_recursively
 
     assert_raises_frozen_error do
@@ -76,24 +71,17 @@ describe 'Hash#freeze_recursively' do
     assert a[:x].frozen?
     assert_equal a, a[:x]
   end
-
 end
 
 describe 'Hash#checksum' do
-
   it 'should work' do
     expectation = 'wy7gHokc700tqJ/BmJ+EK6/F0bc='
-    { :foo => 123 }.checksum.must_equal expectation
+    { foo: 123 }.checksum.must_equal expectation
   end
 
   it 'should not sort keys' do
-    if RUBY_VERSION =~ /\A1\.8./
-      skip "Ruby 1.8.x does not have ordered hashes"
-    end
-
-    a = { :a => 1, :c => 2, :b => 3 }.checksum
-    b = { :a => 1, :b => 3, :c => 2 }.checksum
+    a = { a: 1, c: 2, b: 3 }.checksum
+    b = { a: 1, b: 3, c: 2 }.checksum
     a.wont_equal b
   end
-
 end
diff --git a/test/base/core_ext/pathname_spec.rb b/test/base/core_ext/pathname_spec.rb
index f35b4c5..4afb8a0 100644
--- a/test/base/core_ext/pathname_spec.rb
+++ b/test/base/core_ext/pathname_spec.rb
@@ -1,13 +1,12 @@
 # encoding: utf-8
 
 describe 'Pathname#checksum' do
-
   it 'should work on empty files' do
     begin
       # Create file
       FileUtils.mkdir_p('tmp')
       File.open('tmp/myfile', 'w') { |io| io.write('') }
-      timestamp = Time.at(1234569)
+      timestamp = Time.at(1_234_569)
       File.utime(timestamp, timestamp, 'tmp/myfile')
 
       # Create checksum
@@ -23,7 +22,7 @@ describe 'Pathname#checksum' do
       # Create file
       FileUtils.mkdir_p('tmp')
       File.open('tmp/myfile', 'w') { |io| io.write('abc') }
-      timestamp = Time.at(1234569)
+      timestamp = Time.at(1_234_569)
       File.utime(timestamp, timestamp, 'tmp/myfile')
 
       # Create checksum
@@ -33,5 +32,4 @@ describe 'Pathname#checksum' do
       FileUtils.rm_rf('tmp')
     end
   end
-
 end
diff --git a/test/base/core_ext/string_spec.rb b/test/base/core_ext/string_spec.rb
index 579ef59..8cdfc2b 100644
--- a/test/base/core_ext/string_spec.rb
+++ b/test/base/core_ext/string_spec.rb
@@ -1,7 +1,6 @@
 # encoding: utf-8
 
 describe 'String#cleaned_identifier' do
-
   it 'should not convert already clean paths' do
     '/foo/bar/'.cleaned_identifier.must_equal '/foo/bar/'
   end
@@ -21,11 +20,9 @@ describe 'String#cleaned_identifier' do
   it 'should remove double slashes at end' do
     '/foo/bar//'.cleaned_identifier.must_equal '/foo/bar/'
   end
-
 end
 
 describe 'String#checksum' do
-
   it 'should work on empty strings' do
     ''.checksum.must_equal 'PfY7essFItpoXa1f6EuB/deyUmQ='
   end
@@ -33,5 +30,4 @@ describe 'String#checksum' do
   it 'should work on all strings' do
     'abc'.checksum.must_equal 'NkkYRO+25f6psNSeCYykXKCg3C0='
   end
-
 end
diff --git a/test/base/temp_filename_factory_spec.rb b/test/base/temp_filename_factory_spec.rb
index 98682bc..65281b9 100644
--- a/test/base/temp_filename_factory_spec.rb
+++ b/test/base/temp_filename_factory_spec.rb
@@ -1,7 +1,6 @@
 # encoding: utf-8
 
 describe Nanoc::TempFilenameFactory do
-
   subject do
     Nanoc::TempFilenameFactory.instance
   end
@@ -13,7 +12,6 @@ describe Nanoc::TempFilenameFactory do
   end
 
   describe '#create' do
-
     it 'should create unique paths' do
       path_a = subject.create(prefix)
       path_b = subject.create(prefix)
@@ -37,11 +35,9 @@ describe Nanoc::TempFilenameFactory do
       path_b = subject.create(prefix)
       path_a.must_equal(path_b)
     end
-
   end
 
   describe '#cleanup' do
-
     it 'should remove generated files' do
       path_a = subject.create(prefix)
       File.file?(path_a).wont_equal(true) # not yet used
@@ -60,11 +56,9 @@ describe Nanoc::TempFilenameFactory do
       subject.cleanup(prefix)
       File.directory?(subject.root_dir).wont_equal(true)
     end
-
   end
 
   describe 'other instance' do
-
     let(:other_instance) do
       Nanoc::TempFilenameFactory.new
     end
@@ -74,7 +68,5 @@ describe Nanoc::TempFilenameFactory do
       path_b = other_instance.create(prefix)
       path_a.wont_equal(path_b)
     end
-
   end
-
 end
diff --git a/test/base/test_checksum_store.rb b/test/base/test_checksum_store.rb
index 7c6e20b..6706387 100644
--- a/test/base/test_checksum_store.rb
+++ b/test/base/test_checksum_store.rb
@@ -1,7 +1,6 @@
 # encoding: utf-8
 
 class Nanoc::ChecksumStoreTest < Nanoc::TestCase
-
   def test_get_with_existing_object
     require 'pstore'
 
@@ -9,7 +8,7 @@ class Nanoc::ChecksumStoreTest < Nanoc::TestCase
     FileUtils.mkdir_p('tmp')
     pstore = PStore.new('tmp/checksums')
     pstore.transaction do
-      pstore[:data] = { [ :item, '/moo/' ] => 'zomg' }
+      pstore[:data] = { [:item, '/moo/'] => 'zomg' }
       pstore[:version] = 1
     end
 
@@ -28,5 +27,4 @@ class Nanoc::ChecksumStoreTest < Nanoc::TestCase
     obj = Nanoc::Item.new('Moo?', {}, '/animals/cow/')
     assert_equal nil, store[obj]
   end
-
 end
diff --git a/test/base/test_code_snippet.rb b/test/base/test_code_snippet.rb
index 67615cc..46b7be4 100644
--- a/test/base/test_code_snippet.rb
+++ b/test/base/test_code_snippet.rb
@@ -1,7 +1,6 @@
 # encoding: utf-8
 
 class Nanoc::CodeSnippetTest < Nanoc::TestCase
-
   def test_load
     # Initialize
     $complete_insane_parrot = 'meow'
@@ -25,5 +24,4 @@ class Nanoc::CodeSnippetTest < Nanoc::TestCase
     # Ensure binding is correct
     assert_equal('meow', @foo)
   end
-
 end
diff --git a/test/base/test_compiler.rb b/test/base/test_compiler.rb
index 3a9d33e..4f53761 100644
--- a/test/base/test_compiler.rb
+++ b/test/base/test_compiler.rb
@@ -1,10 +1,9 @@
 # encoding: utf-8
 
 class Nanoc::CompilerTest < Nanoc::TestCase
-
   def test_compilation_rule_for
     # Mock rules
-    rules = [ mock, mock, mock ]
+    rules = [mock, mock, mock]
     rules[0].expects(:applicable_to?).returns(false)
     rules[1].expects(:applicable_to?).returns(true)
     rules[1].expects(:rep_name).returns('wrong')
@@ -27,7 +26,7 @@ class Nanoc::CompilerTest < Nanoc::TestCase
 
   def test_routing_rule_for
     # Mock rules
-    rules = [ mock, mock, mock ]
+    rules = [mock, mock, mock]
     rules[0].expects(:applicable_to?).returns(false)
     rules[1].expects(:applicable_to?).returns(true)
     rules[1].expects(:rep_name).returns('wrong')
@@ -54,14 +53,14 @@ class Nanoc::CompilerTest < Nanoc::TestCase
 
     # Create compiler
     compiler = Nanoc::Compiler.new(site)
-    compiler.rules_collection.layout_filter_mapping[/.*/] = [ :erb, { :foo => 'bar' } ]
+    compiler.rules_collection.layout_filter_mapping[/.*/] = [:erb, { foo: 'bar' }]
 
     # Mock layout
     layout = MiniTest::Mock.new
     layout.expect(:identifier, '/some_layout/')
 
     # Check
-    assert_equal([ :erb, { :foo => 'bar' } ], compiler.rules_collection.filter_for_layout(layout))
+    assert_equal([:erb, { foo: 'bar' }], compiler.rules_collection.filter_for_layout(layout))
   end
 
   def test_filter_for_layout_with_existant_layout_and_unknown_filter
@@ -70,14 +69,14 @@ class Nanoc::CompilerTest < Nanoc::TestCase
 
     # Create compiler
     compiler = Nanoc::Compiler.new(site)
-    compiler.rules_collection.layout_filter_mapping[/.*/] = [ :some_unknown_filter, { :foo => 'bar' } ]
+    compiler.rules_collection.layout_filter_mapping[/.*/] = [:some_unknown_filter, { foo: 'bar' }]
 
     # Mock layout
     layout = MiniTest::Mock.new
     layout.expect(:identifier, '/some_layout/')
 
     # Check
-    assert_equal([ :some_unknown_filter, { :foo => 'bar' } ], compiler.rules_collection.filter_for_layout(layout))
+    assert_equal([:some_unknown_filter, { foo: 'bar' }], compiler.rules_collection.filter_for_layout(layout))
   end
 
   def test_filter_for_layout_with_nonexistant_layout
@@ -86,7 +85,7 @@ class Nanoc::CompilerTest < Nanoc::TestCase
 
     # Create compiler
     compiler = Nanoc::Compiler.new(site)
-    compiler.rules_collection.layout_filter_mapping[%r{^/foo/$}] = [ :erb, { :foo => 'bar' } ]
+    compiler.rules_collection.layout_filter_mapping[%r{^/foo/$}] = [:erb, { foo: 'bar' }]
 
     # Mock layout
     layout = MiniTest::Mock.new
@@ -102,13 +101,13 @@ class Nanoc::CompilerTest < Nanoc::TestCase
 
     # Create compiler
     compiler = Nanoc::Compiler.new(site)
-    compiler.rules_collection.layout_filter_mapping[%r{^/a/b/c/.*/$}] = [ :erb, { :char => 'd' } ]
-    compiler.rules_collection.layout_filter_mapping[%r{^/a/.*/$}]     = [ :erb, { :char => 'b' } ]
-    compiler.rules_collection.layout_filter_mapping[%r{^/a/b/.*/$}]   = [ :erb, { :char => 'c' } ] # never used!
-    compiler.rules_collection.layout_filter_mapping[%r{^/.*/$}]       = [ :erb, { :char => 'a' } ]
+    compiler.rules_collection.layout_filter_mapping[%r{^/a/b/c/.*/$}] = [:erb, { char: 'd' }]
+    compiler.rules_collection.layout_filter_mapping[%r{^/a/.*/$}]     = [:erb, { char: 'b' }]
+    compiler.rules_collection.layout_filter_mapping[%r{^/a/b/.*/$}]   = [:erb, { char: 'c' }] # never used!
+    compiler.rules_collection.layout_filter_mapping[%r{^/.*/$}]       = [:erb, { char: 'a' }]
 
     # Mock layout
-    layouts = [ mock, mock, mock, mock ]
+    layouts = [mock, mock, mock, mock]
     layouts[0].stubs(:identifier).returns('/a/b/c/d/')
     layouts[1].stubs(:identifier).returns('/a/b/c/')
     layouts[2].stubs(:identifier).returns('/a/b/')
@@ -137,10 +136,10 @@ class Nanoc::CompilerTest < Nanoc::TestCase
 
     # Set snapshot filenames
     rep.raw_paths = {
-      :raw  => 'raw.txt',
-      :pre  => 'pre.txt',
-      :post => 'post.txt',
-      :last => 'last.txt'
+      raw: 'raw.txt',
+      pre: 'pre.txt',
+      post: 'post.txt',
+      last: 'last.txt'
     }
 
     # Create rule
@@ -159,12 +158,12 @@ class Nanoc::CompilerTest < Nanoc::TestCase
     site = mock
     site.stubs(:config).returns({})
     site.stubs(:items).returns([])
-    site.stubs(:layouts).returns([ layout ])
+    site.stubs(:layouts).returns([layout])
 
     # Create compiler
     compiler = Nanoc::Compiler.new(site)
     compiler.rules_collection.expects(:compilation_rule_for).times(2).with(rep).returns(rule)
-    compiler.rules_collection.layout_filter_mapping[%r{^/blah/$}] = [ :erb, {} ]
+    compiler.rules_collection.layout_filter_mapping[%r{^/blah/$}] = [:erb, {}]
     site.stubs(:compiler).returns(compiler)
 
     # Compile
@@ -217,7 +216,7 @@ class Nanoc::CompilerTest < Nanoc::TestCase
   end
 
   def test_compile_with_two_dependent_reps
-    with_site(:compilation_rule_content => 'filter :erb') do |site|
+    with_site(compilation_rule_content: 'filter :erb') do |site|
       File.open('content/foo.html', 'w') do |io|
         io.write('<%= @items.find { |i| i.identifier == "/bar/" }.compiled_content %>!!!')
       end
@@ -236,7 +235,7 @@ class Nanoc::CompilerTest < Nanoc::TestCase
   end
 
   def test_compile_with_two_mutually_dependent_reps
-    with_site(:compilation_rule_content => 'filter :erb') do |site|
+    with_site(compilation_rule_content: 'filter :erb') do |site|
       File.open('content/foo.html', 'w') do |io|
         io.write('<%= @items.find { |i| i.identifier == "/bar/" }.compiled_content %>')
       end
@@ -589,5 +588,4 @@ class Nanoc::CompilerTest < Nanoc::TestCase
       assert_empty stack
     end
   end
-
 end
diff --git a/test/base/test_compiler_dsl.rb b/test/base/test_compiler_dsl.rb
index f6dfd2e..7111b71 100644
--- a/test/base/test_compiler_dsl.rb
+++ b/test/base/test_compiler_dsl.rb
@@ -1,17 +1,16 @@
 # encoding: utf-8
 
 class Nanoc::CompilerDSLTest < Nanoc::TestCase
-
   def test_compile
-    # TODO implement
+    # TODO: implement
   end
 
   def test_route
-    # TODO implement
+    # TODO: implement
   end
 
   def test_layout
-    # TODO implement
+    # TODO: implement
   end
 
   def test_preprocess_twice
@@ -38,7 +37,7 @@ class Nanoc::CompilerDSLTest < Nanoc::TestCase
     Nanoc::CLI.run %w( create_site per-rules-file-preprocessor )
     FileUtils.cd('per-rules-file-preprocessor') do
       # Create rep
-      item = Nanoc::Item.new('foo', { :extension => 'bar' }, '/foo/')
+      item = Nanoc::Item.new('foo', { extension: 'bar' }, '/foo/')
 
       # Create a bonus rules file
       File.open('more_rules.rb', 'w') { |io| io.write "preprocess { @items['/foo/'][:preprocessed] = true }" }
@@ -59,7 +58,7 @@ class Nanoc::CompilerDSLTest < Nanoc::TestCase
       # Check that the two preprocess blocks have been added
       assert_equal 2, site.compiler.rules_collection.preprocessors.size
       refute_nil site.compiler.rules_collection.preprocessors.first
-      refute_nil site.compiler.rules_collection.preprocessors.last
+      refute_nil site.compiler.rules_collection.preprocessors.to_a.last
 
       # Apply preprocess blocks
       site.compiler.preprocess
@@ -72,7 +71,7 @@ class Nanoc::CompilerDSLTest < Nanoc::TestCase
     Nanoc::CLI.run %w( create_site with_bonus_rules )
     FileUtils.cd('with_bonus_rules') do
       # Create rep
-      item = Nanoc::Item.new('foo', { :extension => 'bar' }, '/foo/')
+      item = Nanoc::Item.new('foo', { extension: 'bar' }, '/foo/')
       rep  = Nanoc::ItemRep.new(item, :default)
 
       # Create a bonus rules file
@@ -106,7 +105,7 @@ EOS
       # Create items
       assert Dir['content/*'].empty?
       File.open('content/robots.txt', 'w') do |io|
-        io.write "Hello I am robots"
+        io.write 'Hello I am robots'
       end
 
       # Compile
@@ -114,7 +113,7 @@ EOS
       site.compile
 
       # Check paths
-      assert_equal [ 'output/robots.txt' ], Dir['output/*']
+      assert_equal ['output/robots.txt'], Dir['output/*']
     end
   end
 
@@ -130,7 +129,7 @@ EOS
       # Create items
       assert Dir['content/*'].empty?
       File.open('content/foo', 'w') do |io|
-        io.write "Hello I am foo"
+        io.write 'Hello I am foo'
       end
 
       # Compile
@@ -138,7 +137,7 @@ EOS
       site.compile
 
       # Check paths
-      assert_equal [ 'output/foo' ], Dir['output/*']
+      assert_equal ['output/foo'], Dir['output/*']
     end
   end
 
@@ -160,7 +159,7 @@ EOS
       # Create items
       FileUtils.mkdir_p('static')
       File.open('static/foo.txt', 'w') do |io|
-        io.write "Hello I am foo"
+        io.write 'Hello I am foo'
       end
 
       # Compile
@@ -168,7 +167,7 @@ EOS
       site.compile
 
       # Check paths
-      assert_equal [ 'output/foo.txt' ], Dir['output/*']
+      assert_equal ['output/foo.txt'], Dir['output/*']
     end
   end
 
@@ -200,8 +199,8 @@ EOS
       site.compile
 
       # Check paths
-      assert_equal [ 'output/foo' ],            Dir['output/*']
-      assert_equal [ 'output/foo/index.html' ], Dir['output/foo/*']
+      assert_equal ['output/foo'],            Dir['output/*']
+      assert_equal ['output/foo/index.html'], Dir['output/foo/*']
     end
   end
 
@@ -219,11 +218,11 @@ EOS
       # Create items
       assert Dir['content/*'].empty?
       File.open('content/lame.txt', 'w') do |io|
-        io.write "Hello I am lame"
+        io.write 'Hello I am lame'
       end
 
       File.open('content/notlame.txt', 'w') do |io|
-        io.write "Hello I am not lame"
+        io.write 'Hello I am not lame'
       end
 
       # Compile
@@ -231,7 +230,7 @@ EOS
       site.compile
 
       # Check paths
-      assert_equal [ 'output/notlame.txt'], Dir['output/*']
+      assert_equal ['output/notlame.txt'], Dir['output/*']
     end
   end
 
@@ -263,8 +262,8 @@ EOS
       site.compile
 
       # Check paths
-      assert_equal [ 'output/foo' ],            Dir['output/*']
-      assert_equal [ 'output/foo/index.html' ], Dir['output/foo/*']
+      assert_equal ['output/foo'],            Dir['output/*']
+      assert_equal ['output/foo/index.html'], Dir['output/foo/*']
     end
   end
 
@@ -377,9 +376,8 @@ EOS
 
   def test_config
     $venetian = 'unsnares'
-    compiler_dsl = Nanoc::CompilerDSL.new(nil, { :venetian => 'snares' })
+    compiler_dsl = Nanoc::CompilerDSL.new(nil, { venetian: 'snares' })
     compiler_dsl.instance_eval { $venetian = @config[:venetian] }
     assert_equal 'snares', $venetian
   end
-
 end
diff --git a/test/base/test_context.rb b/test/base/test_context.rb
index 45c9145..ca84ad7 100644
--- a/test/base/test_context.rb
+++ b/test/base/test_context.rb
@@ -1,21 +1,20 @@
 # encoding: utf-8
 
 class Nanoc::ContextTest < Nanoc::TestCase
-
   def test_context_with_instance_variable
     # Create context
-    context = Nanoc::Context.new({ :foo => 'bar', :baz => 'quux' })
+    context = Nanoc::Context.new({ foo: 'bar', baz: 'quux' })
 
     # Ensure correct evaluation
-    assert_equal('bar', eval("@foo", context.get_binding))
+    assert_equal('bar', eval('@foo', context.get_binding))
   end
 
   def test_context_with_instance_method
     # Create context
-    context = Nanoc::Context.new({ :foo => 'bar', :baz => 'quux' })
+    context = Nanoc::Context.new({ foo: 'bar', baz: 'quux' })
 
     # Ensure correct evaluation
-    assert_equal('bar', eval("foo", context.get_binding))
+    assert_equal('bar', eval('foo', context.get_binding))
   end
 
   def test_example
@@ -25,5 +24,4 @@ class Nanoc::ContextTest < Nanoc::TestCase
     # Run
     assert_examples_correct 'Nanoc::Context#initialize'
   end
-
 end
diff --git a/test/base/test_data_source.rb b/test/base/test_data_source.rb
index af1e650..34b0d2d 100644
--- a/test/base/test_data_source.rb
+++ b/test/base/test_data_source.rb
@@ -1,7 +1,6 @@
 # encoding: utf-8
 
 class Nanoc::DataSourceTest < Nanoc::TestCase
-
   def test_loading
     # Create data source
     data_source = Nanoc::DataSource.new(nil, nil, nil, nil)
@@ -40,5 +39,4 @@ class Nanoc::DataSourceTest < Nanoc::TestCase
     assert_raises(NotImplementedError) { data_source.create_item(nil, nil, nil) }
     assert_raises(NotImplementedError) { data_source.create_layout(nil, nil, nil) }
   end
-
 end
diff --git a/test/base/test_dependency_tracker.rb b/test/base/test_dependency_tracker.rb
index 3902f0a..9598d9c 100644
--- a/test/base/test_dependency_tracker.rb
+++ b/test/base/test_dependency_tracker.rb
@@ -1,10 +1,9 @@
 # encoding: utf-8
 
 class Nanoc::DependencyTrackerTest < Nanoc::TestCase
-
   def test_initialize
     # Mock items
-    items = [ mock, mock ]
+    items = [mock, mock]
 
     # Create
     tracker = Nanoc::DependencyTracker.new(items)
@@ -16,7 +15,7 @@ class Nanoc::DependencyTrackerTest < Nanoc::TestCase
 
   def test_record_dependency
     # Mock items
-    items = [ mock, mock ]
+    items = [mock, mock]
 
     # Create
     tracker = Nanoc::DependencyTracker.new(items)
@@ -25,12 +24,12 @@ class Nanoc::DependencyTrackerTest < Nanoc::TestCase
     tracker.record_dependency(items[0], items[1])
 
     # Verify dependencies
-    assert_contains_exactly [ items[1] ], tracker.objects_causing_outdatedness_of(items[0])
+    assert_contains_exactly [items[1]], tracker.objects_causing_outdatedness_of(items[0])
   end
 
   def test_record_dependency_no_self
     # Mock items
-    items = [ mock, mock ]
+    items = [mock, mock]
 
     # Create
     tracker = Nanoc::DependencyTracker.new(items)
@@ -40,12 +39,12 @@ class Nanoc::DependencyTrackerTest < Nanoc::TestCase
     tracker.record_dependency(items[0], items[1])
 
     # Verify dependencies
-    assert_contains_exactly [ items[1] ], tracker.objects_causing_outdatedness_of(items[0])
+    assert_contains_exactly [items[1]], tracker.objects_causing_outdatedness_of(items[0])
   end
 
   def test_record_dependency_no_doubles
     # Mock items
-    items = [ mock, mock ]
+    items = [mock, mock]
 
     # Create
     tracker = Nanoc::DependencyTracker.new(items)
@@ -56,12 +55,12 @@ class Nanoc::DependencyTrackerTest < Nanoc::TestCase
     tracker.record_dependency(items[0], items[1])
 
     # Verify dependencies
-    assert_contains_exactly [ items[1] ], tracker.objects_causing_outdatedness_of(items[0])
+    assert_contains_exactly [items[1]], tracker.objects_causing_outdatedness_of(items[0])
   end
 
   def test_objects_causing_outdatedness_of
     # Mock items
-    items = [ mock, mock, mock ]
+    items = [mock, mock, mock]
 
     # Create
     tracker = Nanoc::DependencyTracker.new(items)
@@ -71,12 +70,12 @@ class Nanoc::DependencyTrackerTest < Nanoc::TestCase
     tracker.record_dependency(items[1], items[2])
 
     # Verify dependencies
-    assert_contains_exactly [ items[1] ], tracker.objects_causing_outdatedness_of(items[0])
+    assert_contains_exactly [items[1]], tracker.objects_causing_outdatedness_of(items[0])
   end
 
   def test_objects_outdated_due_to
     # Mock items
-    items = [ mock, mock, mock ]
+    items = [mock, mock, mock]
 
     # Create
     tracker = Nanoc::DependencyTracker.new(items)
@@ -86,12 +85,12 @@ class Nanoc::DependencyTrackerTest < Nanoc::TestCase
     tracker.record_dependency(items[1], items[2])
 
     # Verify dependencies
-    assert_contains_exactly [ items[0] ], tracker.objects_outdated_due_to(items[1])
+    assert_contains_exactly [items[0]], tracker.objects_outdated_due_to(items[1])
   end
 
   def test_start_and_stop
     # Mock items
-    items = [ mock, mock ]
+    items = [mock, mock]
 
     # Create
     tracker = Nanoc::DependencyTracker.new(items)
@@ -105,18 +104,18 @@ class Nanoc::DependencyTrackerTest < Nanoc::TestCase
     tracker.stop
 
     # Verify dependencies
-    assert_contains_exactly [ items[1] ], tracker.objects_causing_outdatedness_of(items[0])
+    assert_contains_exactly [items[1]], tracker.objects_causing_outdatedness_of(items[0])
     assert_empty tracker.objects_causing_outdatedness_of(items[1])
   end
 
   def test_store_graph_and_load_graph_simple
     # Mock items
-    items = [ mock('0'), mock('1'), mock('2'), mock('3') ]
+    items = [mock('0'), mock('1'), mock('2'), mock('3')]
     items.each { |i| i.stubs(:type).returns(:item) }
-    items[0].stubs(:reference).returns([ :item, '/aaa/' ])
-    items[1].stubs(:reference).returns([ :item, '/bbb/' ])
-    items[2].stubs(:reference).returns([ :item, '/ccc/' ])
-    items[3].stubs(:reference).returns([ :item, '/ddd/' ])
+    items[0].stubs(:reference).returns([:item, '/aaa/'])
+    items[1].stubs(:reference).returns([:item, '/bbb/'])
+    items[2].stubs(:reference).returns([:item, '/ccc/'])
+    items[3].stubs(:reference).returns([:item, '/ddd/'])
 
     # Create
     tracker = Nanoc::DependencyTracker.new(items)
@@ -137,24 +136,24 @@ class Nanoc::DependencyTrackerTest < Nanoc::TestCase
     tracker.load_graph
 
     # Check loaded graph
-    assert_contains_exactly [ items[1] ],           tracker.objects_causing_outdatedness_of(items[0])
-    assert_contains_exactly [ items[2], items[3] ], tracker.objects_causing_outdatedness_of(items[1])
+    assert_contains_exactly [items[1]],           tracker.objects_causing_outdatedness_of(items[0])
+    assert_contains_exactly [items[2], items[3]], tracker.objects_causing_outdatedness_of(items[1])
     assert_empty tracker.objects_causing_outdatedness_of(items[2])
     assert_empty tracker.objects_causing_outdatedness_of(items[3])
   end
 
   def test_store_graph_and_load_graph_with_removed_items
     # Mock items
-    items = [ mock('0'), mock('1'), mock('2'), mock('3') ]
+    items = [mock('0'), mock('1'), mock('2'), mock('3')]
     items.each { |i| i.stubs(:type).returns(:item) }
-    items[0].stubs(:reference).returns([ :item, '/aaa/' ])
-    items[1].stubs(:reference).returns([ :item, '/bbb/' ])
-    items[2].stubs(:reference).returns([ :item, '/ccc/' ])
-    items[3].stubs(:reference).returns([ :item, '/ddd/' ])
+    items[0].stubs(:reference).returns([:item, '/aaa/'])
+    items[1].stubs(:reference).returns([:item, '/bbb/'])
+    items[2].stubs(:reference).returns([:item, '/ccc/'])
+    items[3].stubs(:reference).returns([:item, '/ddd/'])
 
     # Create new and old lists
-    old_items = [ items[0], items[1], items[2], items[3] ]
-    new_items = [ items[0], items[1], items[2]           ]
+    old_items = [items[0], items[1], items[2], items[3]]
+    new_items = [items[0], items[1], items[2]]
 
     # Create
     tracker = Nanoc::DependencyTracker.new(old_items)
@@ -175,18 +174,18 @@ class Nanoc::DependencyTrackerTest < Nanoc::TestCase
     tracker.load_graph
 
     # Check loaded graph
-    assert_contains_exactly [ items[1] ],       tracker.objects_causing_outdatedness_of(items[0])
-    assert_contains_exactly [ items[2], nil ],  tracker.objects_causing_outdatedness_of(items[1])
+    assert_contains_exactly [items[1]],       tracker.objects_causing_outdatedness_of(items[0])
+    assert_contains_exactly [items[2], nil],  tracker.objects_causing_outdatedness_of(items[1])
     assert_empty tracker.objects_causing_outdatedness_of(items[2])
   end
 
   def test_store_graph_with_nils_in_dst
     # Mock items
-    items = [ mock('0'), mock('1'), mock('2') ]
+    items = [mock('0'), mock('1'), mock('2')]
     items.each { |i| i.stubs(:type).returns(:item) }
-    items[0].stubs(:reference).returns([ :item, '/aaa/' ])
-    items[1].stubs(:reference).returns([ :item, '/bbb/' ])
-    items[2].stubs(:reference).returns([ :item, '/ccc/' ])
+    items[0].stubs(:reference).returns([:item, '/aaa/'])
+    items[1].stubs(:reference).returns([:item, '/bbb/'])
+    items[2].stubs(:reference).returns([:item, '/ccc/'])
 
     # Create
     tracker = Nanoc::DependencyTracker.new(items)
@@ -206,17 +205,17 @@ class Nanoc::DependencyTrackerTest < Nanoc::TestCase
     tracker.load_graph
 
     # Check loaded graph
-    assert_contains_exactly [ items[1] ], tracker.objects_causing_outdatedness_of(items[0])
-    assert_contains_exactly [ nil ],      tracker.objects_causing_outdatedness_of(items[1])
+    assert_contains_exactly [items[1]], tracker.objects_causing_outdatedness_of(items[0])
+    assert_contains_exactly [nil],      tracker.objects_causing_outdatedness_of(items[1])
   end
 
   def test_store_graph_with_nils_in_src
     # Mock items
-    items = [ mock('0'), mock('1'), mock('2') ]
+    items = [mock('0'), mock('1'), mock('2')]
     items.each { |i| i.stubs(:type).returns(:item) }
-    items[0].stubs(:reference).returns([ :item, '/aaa/' ])
-    items[1].stubs(:reference).returns([ :item, '/bbb/' ])
-    items[2].stubs(:reference).returns([ :item, '/ccc/' ])
+    items[0].stubs(:reference).returns([:item, '/aaa/'])
+    items[1].stubs(:reference).returns([:item, '/bbb/'])
+    items[2].stubs(:reference).returns([:item, '/ccc/'])
 
     # Create
     tracker = Nanoc::DependencyTracker.new(items)
@@ -236,13 +235,13 @@ class Nanoc::DependencyTrackerTest < Nanoc::TestCase
     tracker.load_graph
 
     # Check loaded graph
-    assert_contains_exactly [ items[1] ], tracker.objects_causing_outdatedness_of(items[0])
+    assert_contains_exactly [items[1]], tracker.objects_causing_outdatedness_of(items[0])
     assert_empty tracker.objects_causing_outdatedness_of(items[1])
   end
 
   def test_forget_dependencies_for
     # Mock items
-    items = [ mock, mock, mock ]
+    items = [mock, mock, mock]
 
     # Create
     tracker = Nanoc::DependencyTracker.new(items)
@@ -250,11 +249,10 @@ class Nanoc::DependencyTrackerTest < Nanoc::TestCase
     # Record some dependencies
     tracker.record_dependency(items[0], items[1])
     tracker.record_dependency(items[1], items[2])
-    assert_contains_exactly [ items[1] ], tracker.objects_causing_outdatedness_of(items[0])
+    assert_contains_exactly [items[1]], tracker.objects_causing_outdatedness_of(items[0])
 
     # Forget dependencies
     tracker.forget_dependencies_for(items[0])
     assert_empty tracker.objects_causing_outdatedness_of(items[0])
   end
-
 end
diff --git a/test/base/test_directed_graph.rb b/test/base/test_directed_graph.rb
index d3dab3a..7c68742 100644
--- a/test/base/test_directed_graph.rb
+++ b/test/base/test_directed_graph.rb
@@ -1,80 +1,79 @@
 # encoding: utf-8
 
 class Nanoc::DirectedGraphTest < Nanoc::TestCase
-
   def test_direct_predecessors
-    graph = Nanoc::DirectedGraph.new([ 1, 2, 3 ])
+    graph = Nanoc::DirectedGraph.new([1, 2, 3])
     graph.add_edge(1, 2)
     graph.add_edge(2, 3)
 
     assert_equal [],    graph.direct_predecessors_of(1)
-    assert_equal [ 1 ], graph.direct_predecessors_of(2)
-    assert_equal [ 2 ], graph.direct_predecessors_of(3)
+    assert_equal [1], graph.direct_predecessors_of(2)
+    assert_equal [2], graph.direct_predecessors_of(3)
   end
 
   def test_predecessors
-    graph = Nanoc::DirectedGraph.new([ 1, 2, 3 ])
+    graph = Nanoc::DirectedGraph.new([1, 2, 3])
     graph.add_edge(1, 2)
     graph.add_edge(2, 3)
 
     assert_equal [],       graph.predecessors_of(1).sort
-    assert_equal [ 1 ],    graph.predecessors_of(2).sort
-    assert_equal [ 1, 2 ], graph.predecessors_of(3).sort
+    assert_equal [1],    graph.predecessors_of(2).sort
+    assert_equal [1, 2], graph.predecessors_of(3).sort
   end
 
   def test_direct_successors
-    graph = Nanoc::DirectedGraph.new([ 1, 2, 3 ])
+    graph = Nanoc::DirectedGraph.new([1, 2, 3])
     graph.add_edge(1, 2)
     graph.add_edge(2, 3)
 
-    assert_equal [ 2 ], graph.direct_successors_of(1)
-    assert_equal [ 3 ], graph.direct_successors_of(2)
+    assert_equal [2], graph.direct_successors_of(1)
+    assert_equal [3], graph.direct_successors_of(2)
     assert_equal [],    graph.direct_successors_of(3)
   end
 
   def test_successors
-    graph = Nanoc::DirectedGraph.new([ 1, 2, 3 ])
+    graph = Nanoc::DirectedGraph.new([1, 2, 3])
     graph.add_edge(1, 2)
     graph.add_edge(2, 3)
 
-    assert_equal [ 2, 3 ], graph.successors_of(1).sort
-    assert_equal [ 3 ],    graph.successors_of(2).sort
+    assert_equal [2, 3], graph.successors_of(1).sort
+    assert_equal [3],    graph.successors_of(2).sort
     assert_equal [],       graph.successors_of(3).sort
   end
 
   def test_edges
-    graph = Nanoc::DirectedGraph.new([ 1, 2, 3 ])
+    graph = Nanoc::DirectedGraph.new([1, 2, 3])
     graph.add_edge(1, 2)
     graph.add_edge(2, 3)
 
-    assert_equal [ [ 0, 1 ], [ 1, 2 ] ], graph.edges.sort
+    assert_equal [[0, 1], [1, 2]], graph.edges.sort
   end
 
   def test_edges_with_new_vertices
-    graph = Nanoc::DirectedGraph.new([ 1 ])
-    assert_equal [ 1 ], graph.vertices
+    graph = Nanoc::DirectedGraph.new([1])
+    assert_equal [1], graph.vertices
     graph.add_edge(1, 2)
-    assert_equal [ 1, 2 ], graph.vertices
+    assert_equal [1, 2], graph.vertices
     graph.add_edge(3, 2)
-    assert_equal [ 1, 2, 3 ], graph.vertices
+    assert_equal [1, 2, 3], graph.vertices
 
-    assert_equal [ [ 0, 1 ], [ 2, 1 ] ], graph.edges.sort
+    assert_equal [[0, 1], [2, 1]], graph.edges.sort
   end
 
   def test_add_edge
-    graph = Nanoc::DirectedGraph.new([ 1, 2, 3 ])
-    
+    graph = Nanoc::DirectedGraph.new([1, 2, 3])
+
     assert_equal [], graph.successors_of(1)
     assert_equal [], graph.predecessors_of(2)
 
     graph.add_edge(1, 2)
 
-    assert_equal [ 2 ], graph.successors_of(1)
-    assert_equal [ 1 ], graph.predecessors_of(2)
+    assert_equal [2], graph.successors_of(1)
+    assert_equal [1], graph.predecessors_of(2)
   end
 
   def test_add_edge_with_new_vertices
-    graph = Nanoc::DirectedGraph.new([ 1 ])
+    graph = Nanoc::DirectedGraph.new([1])
     graph.add_edge(1, 2)
     graph.add_edge(3, 2)
 
@@ -83,11 +82,11 @@ class Nanoc::DirectedGraphTest < Nanoc::TestCase
   end
 
   def test_delete_edge
-    graph = Nanoc::DirectedGraph.new([ 1, 2, 3 ])
-    graph.add_edge(1,2)
+    graph = Nanoc::DirectedGraph.new([1, 2, 3])
+    graph.add_edge(1, 2)
 
-    assert_equal [ 2 ], graph.successors_of(1)
-    assert_equal [ 1 ], graph.predecessors_of(2)
+    assert_equal [2], graph.successors_of(1)
+    assert_equal [1], graph.predecessors_of(2)
 
     graph.delete_edge(1, 2)
 
@@ -96,7 +95,7 @@ class Nanoc::DirectedGraphTest < Nanoc::TestCase
   end
 
   def test_delete_edges_from
-    graph = Nanoc::DirectedGraph.new([ 1, 2, 3 ])
+    graph = Nanoc::DirectedGraph.new([1, 2, 3])
 
     graph.add_edge(1, 2)
     graph.add_edge(2, 1)
@@ -105,37 +104,37 @@ class Nanoc::DirectedGraphTest < Nanoc::TestCase
     graph.add_edge(1, 3)
     graph.add_edge(3, 1)
 
-    assert_equal [ 2, 3 ], graph.direct_predecessors_of(1).sort
-    assert_equal [ 2, 3 ], graph.direct_successors_of(1).sort
-    assert_equal [ 1, 3 ], graph.direct_predecessors_of(2).sort
-    assert_equal [ 1, 3 ], graph.direct_successors_of(2).sort
-    assert_equal [ 1, 2 ], graph.direct_predecessors_of(3).sort
-    assert_equal [ 1, 2 ], graph.direct_successors_of(3).sort
+    assert_equal [2, 3], graph.direct_predecessors_of(1).sort
+    assert_equal [2, 3], graph.direct_successors_of(1).sort
+    assert_equal [1, 3], graph.direct_predecessors_of(2).sort
+    assert_equal [1, 3], graph.direct_successors_of(2).sort
+    assert_equal [1, 2], graph.direct_predecessors_of(3).sort
+    assert_equal [1, 2], graph.direct_successors_of(3).sort
     assert_equal Set.new([]), graph.roots
 
     graph.delete_edges_from(1)
 
-    assert_equal [ 2, 3 ], graph.direct_predecessors_of(1).sort
-    assert_equal [      ], graph.direct_successors_of(1).sort
-    assert_equal [ 3    ], graph.direct_predecessors_of(2).sort
-    assert_equal [ 1, 3 ], graph.direct_successors_of(2).sort
-    assert_equal [ 2    ], graph.direct_predecessors_of(3).sort
-    assert_equal [ 1, 2 ], graph.direct_successors_of(3).sort
+    assert_equal [2, 3], graph.direct_predecessors_of(1).sort
+    assert_equal [], graph.direct_successors_of(1).sort
+    assert_equal [3], graph.direct_predecessors_of(2).sort
+    assert_equal [1, 3], graph.direct_successors_of(2).sort
+    assert_equal [2], graph.direct_predecessors_of(3).sort
+    assert_equal [1, 2], graph.direct_successors_of(3).sort
     assert_equal Set.new([]), graph.roots
 
     graph.delete_edges_from(2)
 
-    assert_equal [ 3    ], graph.direct_predecessors_of(1).sort
-    assert_equal [      ], graph.direct_successors_of(1).sort
-    assert_equal [ 3    ], graph.direct_predecessors_of(2).sort
-    assert_equal [      ], graph.direct_successors_of(2).sort
-    assert_equal [      ], graph.direct_predecessors_of(3).sort
-    assert_equal [ 1, 2 ], graph.direct_successors_of(3).sort
-    assert_equal Set.new([ 3 ]), graph.roots
+    assert_equal [3], graph.direct_predecessors_of(1).sort
+    assert_equal [], graph.direct_successors_of(1).sort
+    assert_equal [3], graph.direct_predecessors_of(2).sort
+    assert_equal [], graph.direct_successors_of(2).sort
+    assert_equal [], graph.direct_predecessors_of(3).sort
+    assert_equal [1, 2], graph.direct_successors_of(3).sort
+    assert_equal Set.new([3]), graph.roots
   end
 
   def test_delete_edges_to
-    graph = Nanoc::DirectedGraph.new([ 1, 2, 3 ])
+    graph = Nanoc::DirectedGraph.new([1, 2, 3])
 
     graph.add_edge(1, 2)
     graph.add_edge(2, 1)
@@ -144,37 +143,37 @@ class Nanoc::DirectedGraphTest < Nanoc::TestCase
     graph.add_edge(1, 3)
     graph.add_edge(3, 1)
 
-    assert_equal [ 2, 3 ], graph.direct_predecessors_of(1).sort
-    assert_equal [ 2, 3 ], graph.direct_successors_of(1).sort
-    assert_equal [ 1, 3 ], graph.direct_predecessors_of(2).sort
-    assert_equal [ 1, 3 ], graph.direct_successors_of(2).sort
-    assert_equal [ 1, 2 ], graph.direct_predecessors_of(3).sort
-    assert_equal [ 1, 2 ], graph.direct_successors_of(3).sort
+    assert_equal [2, 3], graph.direct_predecessors_of(1).sort
+    assert_equal [2, 3], graph.direct_successors_of(1).sort
+    assert_equal [1, 3], graph.direct_predecessors_of(2).sort
+    assert_equal [1, 3], graph.direct_successors_of(2).sort
+    assert_equal [1, 2], graph.direct_predecessors_of(3).sort
+    assert_equal [1, 2], graph.direct_successors_of(3).sort
     assert_equal Set.new([]), graph.roots
 
     graph.delete_edges_to(1)
 
-    assert_equal [      ], graph.direct_predecessors_of(1).sort
-    assert_equal [ 2, 3 ], graph.direct_successors_of(1).sort
-    assert_equal [ 1, 3 ], graph.direct_predecessors_of(2).sort
-    assert_equal [ 3    ], graph.direct_successors_of(2).sort
-    assert_equal [ 1, 2 ], graph.direct_predecessors_of(3).sort
-    assert_equal [ 2    ], graph.direct_successors_of(3).sort
-    assert_equal Set.new([ 1 ]), graph.roots
+    assert_equal [], graph.direct_predecessors_of(1).sort
+    assert_equal [2, 3], graph.direct_successors_of(1).sort
+    assert_equal [1, 3], graph.direct_predecessors_of(2).sort
+    assert_equal [3], graph.direct_successors_of(2).sort
+    assert_equal [1, 2], graph.direct_predecessors_of(3).sort
+    assert_equal [2], graph.direct_successors_of(3).sort
+    assert_equal Set.new([1]), graph.roots
 
     graph.delete_edges_to(2)
 
-    assert_equal [      ], graph.direct_predecessors_of(1).sort
-    assert_equal [ 3    ], graph.direct_successors_of(1).sort
-    assert_equal [      ], graph.direct_predecessors_of(2).sort
-    assert_equal [ 3    ], graph.direct_successors_of(2).sort
-    assert_equal [ 1, 2 ], graph.direct_predecessors_of(3).sort
-    assert_equal [      ], graph.direct_successors_of(3).sort
-    assert_equal Set.new([ 1, 2 ]), graph.roots
+    assert_equal [], graph.direct_predecessors_of(1).sort
+    assert_equal [3], graph.direct_successors_of(1).sort
+    assert_equal [], graph.direct_predecessors_of(2).sort
+    assert_equal [3], graph.direct_successors_of(2).sort
+    assert_equal [1, 2], graph.direct_predecessors_of(3).sort
+    assert_equal [], graph.direct_successors_of(3).sort
+    assert_equal Set.new([1, 2]), graph.roots
   end
 
   def test_delete_vertex
-    graph = Nanoc::DirectedGraph.new([ 1, 2, 3 ])
+    graph = Nanoc::DirectedGraph.new([1, 2, 3])
 
     graph.add_edge(1, 2)
     graph.add_edge(2, 1)
@@ -185,27 +184,27 @@ class Nanoc::DirectedGraphTest < Nanoc::TestCase
 
     graph.delete_vertex(2)
 
-    assert_equal [ 3 ], graph.direct_predecessors_of(1).sort
-    assert_equal [ 3 ], graph.direct_successors_of(1).sort
-    assert_equal [ 1 ], graph.direct_predecessors_of(3).sort
-    assert_equal [ 1 ], graph.direct_successors_of(3).sort
+    assert_equal [3], graph.direct_predecessors_of(1).sort
+    assert_equal [3], graph.direct_successors_of(1).sort
+    assert_equal [1], graph.direct_predecessors_of(3).sort
+    assert_equal [1], graph.direct_successors_of(3).sort
     assert_equal Set.new([]), graph.roots
   end
 
   def test_delete_vertex_resulting_roots
-    graph = Nanoc::DirectedGraph.new([ 1, 2, 3 ])
-    assert_equal Set.new([ 1, 2, 3 ]), graph.roots
+    graph = Nanoc::DirectedGraph.new([1, 2, 3])
+    assert_equal Set.new([1, 2, 3]), graph.roots
 
     graph.add_edge(1, 2)
     graph.add_edge(2, 3)
-    assert_equal Set.new([ 1 ]), graph.roots
+    assert_equal Set.new([1]), graph.roots
 
     graph.delete_vertex(2)
-    assert_equal Set.new([ 1, 3 ]), graph.roots
+    assert_equal Set.new([1, 3]), graph.roots
   end
 
   def test_should_return_empty_array_for_nonexistant_vertices
-    graph = Nanoc::DirectedGraph.new([ 1, 2, 3 ])
+    graph = Nanoc::DirectedGraph.new([1, 2, 3])
 
     assert_equal [], graph.direct_predecessors_of(4)
     assert_equal [], graph.predecessors_of(4)
@@ -214,30 +213,30 @@ class Nanoc::DirectedGraphTest < Nanoc::TestCase
   end
 
   def test_roots_after_init
-    graph = Nanoc::DirectedGraph.new([ 1, 2, 3 ])
+    graph = Nanoc::DirectedGraph.new([1, 2, 3])
 
-    assert_equal Set.new([ 1, 2, 3 ]), graph.roots
+    assert_equal Set.new([1, 2, 3]), graph.roots
   end
 
   def test_roots_after_adding_edge
-    graph = Nanoc::DirectedGraph.new([ 1, 2, 3 ])
+    graph = Nanoc::DirectedGraph.new([1, 2, 3])
     graph.add_edge(1, 2)
-    assert_equal Set.new([ 1, 3 ]), graph.roots
+    assert_equal Set.new([1, 3]), graph.roots
 
-    graph = Nanoc::DirectedGraph.new([ 1, 2, 3 ])
+    graph = Nanoc::DirectedGraph.new([1, 2, 3])
     graph.add_edge(1, 3)
-    assert_equal Set.new([ 1, 2 ]), graph.roots
+    assert_equal Set.new([1, 2]), graph.roots
 
-    graph = Nanoc::DirectedGraph.new([ 1, 2, 3 ])
+    graph = Nanoc::DirectedGraph.new([1, 2, 3])
     graph.add_edge(2, 1)
-    assert_equal Set.new([ 2, 3 ]), graph.roots
+    assert_equal Set.new([2, 3]), graph.roots
 
-    graph = Nanoc::DirectedGraph.new([ 1, 2, 3 ])
+    graph = Nanoc::DirectedGraph.new([1, 2, 3])
     graph.add_edge(1, 2)
     graph.add_edge(2, 3)
-    assert_equal Set.new([ 1 ]), graph.roots
+    assert_equal Set.new([1]), graph.roots
 
-    graph = Nanoc::DirectedGraph.new([ 1, 2, 3 ])
+    graph = Nanoc::DirectedGraph.new([1, 2, 3])
     graph.add_edge(1, 2)
     graph.add_edge(2, 3)
     graph.add_edge(3, 1)
@@ -245,45 +244,44 @@ class Nanoc::DirectedGraphTest < Nanoc::TestCase
   end
 
   def test_roots_after_removing_edge
-    graph = Nanoc::DirectedGraph.new([ 1, 2, 3 ])
+    graph = Nanoc::DirectedGraph.new([1, 2, 3])
     graph.add_edge(1, 2)
     graph.delete_edge(1, 2)
-    assert_equal Set.new([ 1, 2, 3 ]), graph.roots
+    assert_equal Set.new([1, 2, 3]), graph.roots
 
-    graph = Nanoc::DirectedGraph.new([ 1, 2, 3 ])
+    graph = Nanoc::DirectedGraph.new([1, 2, 3])
     graph.add_edge(1, 3)
-    assert_equal Set.new([ 1, 2 ]), graph.roots
+    assert_equal Set.new([1, 2]), graph.roots
     graph.delete_edge(1, 2) # no such edge
-    assert_equal Set.new([ 1, 2 ]), graph.roots
+    assert_equal Set.new([1, 2]), graph.roots
 
-    graph = Nanoc::DirectedGraph.new([ 1, 2, 3 ])
+    graph = Nanoc::DirectedGraph.new([1, 2, 3])
     graph.add_edge(2, 1)
     graph.delete_edge(2, 1)
-    assert_equal Set.new([ 1, 2, 3 ]), graph.roots
+    assert_equal Set.new([1, 2, 3]), graph.roots
 
-    graph = Nanoc::DirectedGraph.new([ 1, 2, 3 ])
+    graph = Nanoc::DirectedGraph.new([1, 2, 3])
     graph.add_edge(1, 2)
     graph.add_edge(2, 3)
     graph.delete_edge(1, 2)
-    assert_equal Set.new([ 1, 2 ]), graph.roots
+    assert_equal Set.new([1, 2]), graph.roots
     graph.delete_edge(2, 3)
-    assert_equal Set.new([ 1, 2, 3 ]), graph.roots
+    assert_equal Set.new([1, 2, 3]), graph.roots
 
-    graph = Nanoc::DirectedGraph.new([ 1, 2, 3 ])
+    graph = Nanoc::DirectedGraph.new([1, 2, 3])
     graph.add_edge(1, 2)
     graph.add_edge(2, 3)
     graph.add_edge(3, 1)
     graph.delete_edge(1, 2)
-    assert_equal Set.new([ 2 ]), graph.roots
+    assert_equal Set.new([2]), graph.roots
     graph.delete_edge(2, 3)
-    assert_equal Set.new([ 2, 3 ]), graph.roots
+    assert_equal Set.new([2, 3]), graph.roots
     graph.delete_edge(3, 1)
-    assert_equal Set.new([ 1, 2, 3 ]), graph.roots
+    assert_equal Set.new([1, 2, 3]), graph.roots
   end
 
   def test_example
     YARD.parse(LIB_DIR + '/nanoc/base/directed_graph.rb')
     assert_examples_correct 'Nanoc::DirectedGraph'
   end
-
 end
diff --git a/test/base/test_filter.rb b/test/base/test_filter.rb
index 8095200..38080d6 100644
--- a/test/base/test_filter.rb
+++ b/test/base/test_filter.rb
@@ -1,7 +1,6 @@
 # encoding: utf-8
 
 class Nanoc::FilterTest < Nanoc::TestCase
-
   def test_initialize
     # Create filter
     filter = Nanoc::Filter.new
@@ -12,7 +11,7 @@ class Nanoc::FilterTest < Nanoc::TestCase
 
   def test_assigns
     # Create filter
-    filter = Nanoc::Filter.new({ :foo => 'bar' })
+    filter = Nanoc::Filter.new({ foo: 'bar' })
 
     # Check assigns
     assert_equal('bar', filter.assigns[:foo])
@@ -20,7 +19,7 @@ class Nanoc::FilterTest < Nanoc::TestCase
 
   def test_assigns_with_instance_variables
     # Create filter
-    filter = Nanoc::Filter.new({ :foo => 'bar' })
+    filter = Nanoc::Filter.new({ foo: 'bar' })
 
     # Check assigns
     assert_equal('bar', filter.instance_eval { @foo })
@@ -28,7 +27,7 @@ class Nanoc::FilterTest < Nanoc::TestCase
 
   def test_assigns_with_instance_methods
     # Create filter
-    filter = Nanoc::Filter.new({ :foo => 'bar' })
+    filter = Nanoc::Filter.new({ foo: 'bar' })
 
     # Check assigns
     assert_equal('bar', filter.instance_eval { foo })
@@ -52,7 +51,7 @@ class Nanoc::FilterTest < Nanoc::TestCase
     item_rep.expects(:name).returns(:quux)
 
     # Create filter
-    filter = Nanoc::Filter.new({ :item => item, :item_rep => item_rep })
+    filter = Nanoc::Filter.new({ item: item, item_rep: item_rep })
 
     # Check filename
     assert_equal('item /foo/bar/baz/ (rep quux)', filter.filename)
@@ -64,7 +63,7 @@ class Nanoc::FilterTest < Nanoc::TestCase
     layout.expects(:identifier).returns('/wohba/')
 
     # Create filter
-    filter = Nanoc::Filter.new({ :item => mock, :item_rep => mock, :layout => layout })
+    filter = Nanoc::Filter.new({ item: mock, item_rep: mock, layout: layout })
 
     # Check filename
     assert_equal('layout /wohba/', filter.filename)
@@ -77,5 +76,4 @@ class Nanoc::FilterTest < Nanoc::TestCase
     # Check filename
     assert_equal('?', filter.filename)
   end
-
 end
diff --git a/test/base/test_item.rb b/test/base/test_item.rb
index 8c046c7..a6b2ead 100644
--- a/test/base/test_item.rb
+++ b/test/base/test_item.rb
@@ -1,22 +1,21 @@
 # encoding: utf-8
 
 class Nanoc::ItemTest < Nanoc::TestCase
-
   def test_initialize_with_attributes_with_string_keys
-    item = Nanoc::Item.new("foo", { 'abc' => 'xyz' }, '/foo/')
+    item = Nanoc::Item.new('foo', { 'abc' => 'xyz' }, '/foo/')
 
     assert_equal nil,   item.attributes['abc']
     assert_equal 'xyz', item.attributes[:abc]
   end
 
   def test_initialize_with_unclean_identifier
-    item = Nanoc::Item.new("foo", {}, '/foo')
+    item = Nanoc::Item.new('foo', {}, '/foo')
 
     assert_equal '/foo/', item.identifier
   end
 
   def test_frozen_identifier
-    item = Nanoc::Item.new("foo", {}, '/foo')
+    item = Nanoc::Item.new('foo', {}, '/foo')
 
     assert_raises_frozen_error do
       item.identifier.chop!
@@ -26,8 +25,8 @@ class Nanoc::ItemTest < Nanoc::TestCase
   def test_lookup
     # Create item
     item = Nanoc::Item.new(
-      "content",
-      { :one => 'one in item' },
+      'content',
+      { one: 'one in item' },
       '/path/'
     )
 
@@ -39,7 +38,7 @@ class Nanoc::ItemTest < Nanoc::TestCase
   end
 
   def test_set_attribute
-    item = Nanoc::Item.new("foo", {}, '/foo')
+    item = Nanoc::Item.new('foo', {}, '/foo')
     assert_equal nil, item[:motto]
 
     item[:motto] = 'More human than human'
@@ -49,14 +48,16 @@ class Nanoc::ItemTest < Nanoc::TestCase
   def test_compiled_content_with_default_rep_and_default_snapshot
     # Mock rep
     rep = Object.new
-    def rep.name ; :default ; end
+    def rep.name
+      :default
+    end
     def rep.compiled_content(params)
       "content at #{params[:snapshot].inspect}"
     end
 
     # Mock item
-    item = Nanoc::Item.new("foo", {}, '/foo')
-    item.expects(:reps).returns([ rep ])
+    item = Nanoc::Item.new('foo', {}, '/foo')
+    item.expects(:reps).returns([rep])
 
     # Check
     assert_equal 'content at nil', item.compiled_content
@@ -65,43 +66,47 @@ class Nanoc::ItemTest < Nanoc::TestCase
   def test_compiled_content_with_custom_rep_and_default_snapshot
     # Mock reps
     rep = Object.new
-    def rep.name ; :foo ; end
+    def rep.name
+      :foo
+    end
     def rep.compiled_content(params)
       "content at #{params[:snapshot].inspect}"
     end
 
     # Mock item
-    item = Nanoc::Item.new("foo", {}, '/foo')
-    item.expects(:reps).returns([ rep ])
+    item = Nanoc::Item.new('foo', {}, '/foo')
+    item.expects(:reps).returns([rep])
 
     # Check
-    assert_equal 'content at nil', item.compiled_content(:rep => :foo)
+    assert_equal 'content at nil', item.compiled_content(rep: :foo)
   end
 
   def test_compiled_content_with_default_rep_and_custom_snapshot
     # Mock reps
     rep = Object.new
-    def rep.name ; :default ; end
+    def rep.name
+      :default
+    end
     def rep.compiled_content(params)
       "content at #{params[:snapshot].inspect}"
     end
 
     # Mock item
-    item = Nanoc::Item.new("foo", {}, '/foo')
-    item.expects(:reps).returns([ rep ])
+    item = Nanoc::Item.new('foo', {}, '/foo')
+    item.expects(:reps).returns([rep])
 
     # Check
-    assert_equal 'content at :blah', item.compiled_content(:snapshot => :blah)
+    assert_equal 'content at :blah', item.compiled_content(snapshot: :blah)
   end
 
   def test_compiled_content_with_custom_nonexistant_rep
     # Mock item
-    item = Nanoc::Item.new("foo", {}, '/foo')
+    item = Nanoc::Item.new('foo', {}, '/foo')
     item.expects(:reps).returns([])
 
     # Check
     assert_raises(Nanoc::Errors::Generic) do
-      item.compiled_content(:rep => :lkasdhflahgwfe)
+      item.compiled_content(rep: :lkasdhflahgwfe)
     end
   end
 
@@ -112,8 +117,8 @@ class Nanoc::ItemTest < Nanoc::TestCase
     rep.expects(:path).returns('the correct path')
 
     # Mock item
-    item = Nanoc::Item.new("foo", {}, '/foo')
-    item.expects(:reps).returns([ rep ])
+    item = Nanoc::Item.new('foo', {}, '/foo')
+    item.expects(:reps).returns([rep])
 
     # Check
     assert_equal 'the correct path', item.path
@@ -126,15 +131,15 @@ class Nanoc::ItemTest < Nanoc::TestCase
     rep.expects(:path).returns('the correct path')
 
     # Mock item
-    item = Nanoc::Item.new("foo", {}, '/foo')
-    item.expects(:reps).returns([ rep ])
+    item = Nanoc::Item.new('foo', {}, '/foo')
+    item.expects(:reps).returns([rep])
 
     # Check
-    assert_equal 'the correct path', item.path(:rep => :moo)
+    assert_equal 'the correct path', item.path(rep: :moo)
   end
 
   def test_freeze_should_disallow_changes
-    item = Nanoc::Item.new("foo", { :a => { :b => 123 }}, '/foo/')
+    item = Nanoc::Item.new('foo', { a: { b: 123 } }, '/foo/')
     item.freeze
 
     assert_raises_frozen_error do
@@ -148,15 +153,14 @@ class Nanoc::ItemTest < Nanoc::TestCase
 
   def test_dump_and_load
     item = Nanoc::Item.new(
-      "foobar",
-      { :a => { :b => 123 }},
+      'foobar',
+      { a: { b: 123 } },
       '/foo/')
 
     item = Marshal.load(Marshal.dump(item))
 
     assert_equal '/foo/', item.identifier
     assert_equal 'foobar', item.raw_content
-    assert_equal({ :a => { :b => 123 }}, item.attributes)
+    assert_equal({ a: { b: 123 } }, item.attributes)
   end
-
 end
diff --git a/test/base/test_item_array.rb b/test/base/test_item_array.rb
index a2613ac..24ce389 100644
--- a/test/base/test_item_array.rb
+++ b/test/base/test_item_array.rb
@@ -1,7 +1,6 @@
 # encoding: utf-8
 
 class Nanoc::ItemArrayTest < Nanoc::TestCase
-
   def setup
     super
 
@@ -46,11 +45,11 @@ class Nanoc::ItemArrayTest < Nanoc::TestCase
   end
 
   def test_brackets_and_slice_with_range
-    assert_equal [ @one, @two ], @items[0..1]
-    assert_equal [ @one, @two ], @items[0, 2]
+    assert_equal [@one, @two], @items[0..1]
+    assert_equal [@one, @two], @items[0, 2]
 
-    assert_equal [ @one, @two ], @items.slice(0..1)
-    assert_equal [ @one, @two ], @items.slice(0, 2)
+    assert_equal [@one, @two], @items.slice(0..1)
+    assert_equal [@one, @two], @items.slice(0, 2)
   end
 
   def test_brackets_and_slice_and_at_with_identifier
@@ -97,6 +96,14 @@ class Nanoc::ItemArrayTest < Nanoc::TestCase
     assert_nil @items.at('/tenthousand/')
   end
 
+  def test_regex
+    foo = Nanoc::Item.new('Item Foo', {}, '/foo/')
+    @items << foo
+
+    assert_equal [@one], @items[/n/]
+    assert_equal [@two, foo], @items[%r{o/}]
+  end
+
   def test_less_than_less_than
     assert_nil @items[2]
     assert_nil @items['/foo/']
@@ -150,11 +157,11 @@ class Nanoc::ItemArrayTest < Nanoc::TestCase
     assert_nil @items['/one/']
     assert_nil @items['/two/']
 
-    assert_equal "New Item One", @items[0].raw_content
-    assert_equal "New Item One", @items['/new/one/'].raw_content
+    assert_equal 'New Item One', @items[0].raw_content
+    assert_equal 'New Item One', @items['/new/one/'].raw_content
 
-    assert_equal "New Item Two", @items[1].raw_content
-    assert_equal "New Item Two", @items['/new/two/'].raw_content
+    assert_equal 'New Item Two', @items[1].raw_content
+    assert_equal 'New Item Two', @items['/new/two/'].raw_content
   end
 
   def test_collect_bang_frozen
@@ -169,7 +176,7 @@ class Nanoc::ItemArrayTest < Nanoc::TestCase
 
   def test_concat
     new_item = Nanoc::Item.new('New item', {}, '/new/')
-    @items.concat([ new_item ])
+    @items.concat([new_item])
 
     assert_equal new_item, @items[2]
     assert_equal new_item, @items['/new/']
@@ -211,10 +218,10 @@ class Nanoc::ItemArrayTest < Nanoc::TestCase
     assert_nil @items['/one/']
     assert_nil @items['/two/']
 
-    assert_equal "Item 0", @items[0].raw_content
-    assert_equal "Item 0", @items['/new/0/'].raw_content
-    assert_equal "Item 1", @items[1].raw_content
-    assert_equal "Item 1", @items['/new/1/'].raw_content
+    assert_equal 'Item 0', @items[0].raw_content
+    assert_equal 'Item 0', @items['/new/0/'].raw_content
+    assert_equal 'Item 1', @items[1].raw_content
+    assert_equal 'Item 1', @items['/new/1/'].raw_content
   end
 
   def test_fill_range
@@ -225,11 +232,11 @@ class Nanoc::ItemArrayTest < Nanoc::TestCase
 
     assert_equal @one, @items[0]
     assert_equal @one, @items['/one/']
-    assert_equal "Item 1", @items[1].raw_content
-    assert_equal "Item 1", @items['/new/1/'].raw_content
+    assert_equal 'Item 1', @items[1].raw_content
+    assert_equal 'Item 1', @items['/new/1/'].raw_content
   end
 
-  if Array.new.respond_to?(:keep_if)
+  if [].respond_to?(:keep_if)
     def test_keep_if
       assert_equal @two, @items[1]
       assert_equal @two, @items['/two/']
@@ -253,7 +260,7 @@ class Nanoc::ItemArrayTest < Nanoc::TestCase
   end
 
   def test_push
-    pushy = Nanoc::Item.new("Pushy", {}, '/pushy/')
+    pushy = Nanoc::Item.new('Pushy', {}, '/pushy/')
     @items.push(pushy)
 
     assert_equal @one, @items[0]
@@ -275,10 +282,10 @@ class Nanoc::ItemArrayTest < Nanoc::TestCase
   end
 
   def test_replace
-    max  = Nanoc::Item.new("Max", {}, '/max/')
+    max  = Nanoc::Item.new('Max', {}, '/max/')
     mona = Nanoc::Item.new('Mona', {}, '/mona/')
 
-    @items.replace([ max, mona ])
+    @items.replace([max, mona])
 
     assert_nil @items['/one/']
     assert_nil @items['/two/']
@@ -289,7 +296,7 @@ class Nanoc::ItemArrayTest < Nanoc::TestCase
     assert_equal mona, @items['/mona/']
   end
 
-  if Array.new.respond_to?(:select!)
+  if [].respond_to?(:select!)
     def test_select_bang
       assert_equal @two, @items[1]
       assert_equal @two, @items['/two/']
@@ -320,7 +327,7 @@ class Nanoc::ItemArrayTest < Nanoc::TestCase
   end
 
   def test_unshift
-    unshifty = Nanoc::Item.new("Unshifty", {}, '/unshifty/')
+    unshifty = Nanoc::Item.new('Unshifty', {}, '/unshifty/')
     @items.unshift(unshifty)
 
     assert_equal unshifty, @items[0]
@@ -330,5 +337,4 @@ class Nanoc::ItemArrayTest < Nanoc::TestCase
     assert_equal @two, @items[2]
     assert_equal @two, @items['/two/']
   end
-
 end
diff --git a/test/base/test_item_rep.rb b/test/base/test_item_rep.rb
index dfd5932..41c05e7 100644
--- a/test/base/test_item_rep.rb
+++ b/test/base/test_item_rep.rb
@@ -1,19 +1,18 @@
 # encoding: utf-8
 
 class Nanoc::ItemRepTest < Nanoc::TestCase
-
   def test_created_modified_compiled
-    # TODO implement
+    # TODO: implement
   end
 
   def test_compiled_content_with_only_last_available
     # Create rep
     item = Nanoc::Item.new(
       'blah blah blah', {}, '/',
-      :binary => false, :mtime => Time.now-500
+      binary: false, mtime: Time.now - 500
     )
     rep = Nanoc::ItemRep.new(item, nil)
-    rep.instance_eval { @content = { :last => 'last content' } }
+    rep.instance_eval { @content = { last: 'last content' } }
     rep.expects(:compiled?).returns(true)
 
     # Check
@@ -24,10 +23,10 @@ class Nanoc::ItemRepTest < Nanoc::TestCase
     # Create rep
     item = Nanoc::Item.new(
       'blah blah blah', {}, '/',
-      :binary => false, :mtime => Time.now-500
+      binary: false, mtime: Time.now - 500
     )
     rep = Nanoc::ItemRep.new(item, nil)
-    rep.instance_eval { @content = { :pre => 'pre content', :last => 'last content' } }
+    rep.instance_eval { @content = { pre: 'pre content', last: 'last content' } }
     rep.expects(:compiled?).returns(true)
 
     # Check
@@ -38,36 +37,36 @@ class Nanoc::ItemRepTest < Nanoc::TestCase
     # Create rep
     item = Nanoc::Item.new(
       'blah blah blah', {}, '/',
-      :binary => false, :mtime => Time.now-500
+      binary: false, mtime: Time.now - 500
     )
     rep = Nanoc::ItemRep.new(item, nil)
-    rep.instance_eval { @content = { :pre => 'pre content', :last => 'last content' } }
+    rep.instance_eval { @content = { pre: 'pre content', last: 'last content' } }
     rep.expects(:compiled?).returns(true)
 
     # Check
-    assert_equal 'last content', rep.compiled_content(:snapshot => :last)
+    assert_equal 'last content', rep.compiled_content(snapshot: :last)
   end
 
   def test_compiled_content_with_invalid_snapshot
     # Create rep
     item = Nanoc::Item.new(
       'blah blah blah', {}, '/',
-      :binary => false, :mtime => Time.now-500
+      binary: false, mtime: Time.now - 500
     )
     rep = Nanoc::ItemRep.new(item, nil)
-    rep.instance_eval { @content = { :pre => 'pre content', :last => 'last content' } }
+    rep.instance_eval { @content = { pre: 'pre content', last: 'last content' } }
 
     # Check
     assert_raises Nanoc::Errors::NoSuchSnapshot do
-      rep.compiled_content(:snapshot => :klsjflkasdfl)
+      rep.compiled_content(snapshot: :klsjflkasdfl)
     end
   end
 
   def test_compiled_content_with_uncompiled_content
     # Create rep
     item = Nanoc::Item.new(
-      "blah blah", {}, '/',
-      :binary => false
+      'blah blah', {}, '/',
+      binary: false
     )
     rep = Nanoc::ItemRep.new(item, nil)
     rep.expects(:compiled?).returns(false)
@@ -78,6 +77,57 @@ class Nanoc::ItemRepTest < Nanoc::TestCase
     end
   end
 
+  def test_compiled_content_with_moving_pre_snapshot
+    # Create rep
+    item = Nanoc::Item.new(
+      'blah blah', {}, '/',
+      binary: false
+    )
+    rep = Nanoc::ItemRep.new(item, nil)
+    rep.expects(:compiled?).returns(false)
+    rep.instance_eval { @content = { pre: 'pre!', last: 'last!' } }
+
+    # Check
+    assert_raises(Nanoc::Errors::UnmetDependency) do
+      rep.compiled_content(snapshot: :pre)
+    end
+  end
+
+  def test_compiled_content_with_non_moving_pre_snapshot
+    # Create rep
+    item = Nanoc::Item.new(
+      'blah blah', {}, '/',
+      binary: false
+    )
+    rep = Nanoc::ItemRep.new(item, nil)
+    rep.expects(:compiled?).returns(false)
+    rep.snapshots = [[:pre, true]]
+    rep.instance_eval { @content = { pre: 'pre!', post: 'post!', last: 'last!' } }
+
+    # Check
+    assert_equal 'pre!', rep.compiled_content(snapshot: :pre)
+  end
+
+  def test_compiled_content_with_final_pre_snapshot_in_layout
+    # Mock layout
+    layout = Nanoc::Layout.new(
+      %(BEFORE <%= @item_rep.compiled_content(snapshot: :pre) %> AFTER),
+      {},
+      '/somelayout/')
+
+    # Create item and item rep
+    item = Nanoc::Item.new(
+      'blah blah', {}, '/',
+      binary: false
+    )
+    rep = create_rep_for(item, :foo)
+    rep.assigns = { item_rep: rep }
+
+    # Run and check
+    rep.layout(layout, :erb, {})
+    assert_equal('BEFORE blah blah AFTER', rep.instance_eval { @content[:last] })
+  end
+
   def test_filter
     # Mock site
     site = MiniTest::Mock.new
@@ -87,8 +137,8 @@ class Nanoc::ItemRepTest < Nanoc::TestCase
 
     # Mock item
     item = Nanoc::Item.new(
-      %[<%= '<%= "blah" %' + '>' %>], {}, '/',
-      :binary => false
+      %(<%= '<%= "blah" %' + '>' %>), {}, '/',
+      binary: false
     )
 
     # Create item rep
@@ -101,22 +151,22 @@ class Nanoc::ItemRepTest < Nanoc::TestCase
     # Filter once
     item_rep.assigns = {}
     item_rep.filter(:erb)
-    assert_equal(%[<%= "blah" %>], item_rep.instance_eval { @content[:last] })
+    assert_equal(%(<%= "blah" %>), item_rep.instance_eval { @content[:last] })
 
     # Filter twice
     item_rep.assigns = {}
     item_rep.filter(:erb)
-    assert_equal(%[blah], item_rep.instance_eval { @content[:last] })
+    assert_equal(%(blah), item_rep.instance_eval { @content[:last] })
   end
 
   def test_layout
     # Mock layout
-    layout = Nanoc::Layout.new(%[<%= "blah" %>], {}, '/somelayout/')
+    layout = Nanoc::Layout.new(%(<%= "blah" %>), {}, '/somelayout/')
 
     # Mock item
     item = Nanoc::Item.new(
-      "blah blah", {}, '/',
-      :binary => false
+      'blah blah', {}, '/',
+      binary: false
     )
 
     # Create item rep
@@ -129,7 +179,7 @@ class Nanoc::ItemRepTest < Nanoc::TestCase
     # Layout
     item_rep.assigns = {}
     item_rep.layout(layout, :erb, {})
-    assert_equal(%[blah], item_rep.instance_eval { @content[:last] })
+    assert_equal(%(blah), item_rep.instance_eval { @content[:last] })
   end
 
   def test_snapshot
@@ -141,8 +191,8 @@ class Nanoc::ItemRepTest < Nanoc::TestCase
 
     # Mock item
     item = Nanoc::Item.new(
-      %[<%= '<%= "blah" %' + '>' %>], {}, '/foobar/',
-      :binary => false
+      %(<%= '<%= "blah" %' + '>' %>), {}, '/foobar/',
+      binary: false
     )
 
     # Create item rep
@@ -161,56 +211,56 @@ class Nanoc::ItemRepTest < Nanoc::TestCase
     item_rep.snapshot(:qux)
 
     # Check snapshots
-    assert_equal(%[<%= '<%= "blah" %' + '>' %>], item_rep.instance_eval { @content[:foo] })
-    assert_equal(%[<%= "blah" %>],               item_rep.instance_eval { @content[:bar] })
-    assert_equal(%[blah],                        item_rep.instance_eval { @content[:qux] })
+    assert_equal(%(<%= '<%= "blah" %' + '>' %>), item_rep.instance_eval { @content[:foo] })
+    assert_equal(%(<%= "blah" %>),               item_rep.instance_eval { @content[:bar] })
+    assert_equal(%(blah),                        item_rep.instance_eval { @content[:qux] })
   end
 
   def test_snapshot_should_be_written
     # Mock item
     item = Nanoc::Item.new(
-      "blah blah", {}, '/',
-      :binary => false
+      'blah blah', {}, '/',
+      binary: false
     )
 
     # Create rep
     item_rep = Nanoc::ItemRep.new(item, :foo)
     item_rep.instance_eval { @content[:last] = 'Lorem ipsum, etc.' }
-    item_rep.raw_paths = { :moo => 'foo-moo.txt' }
+    item_rep.raw_paths = { moo: 'foo-moo.txt' }
 
     # Test non-final
-    refute File.file?(item_rep.raw_path(:snapshot => :moo))
-    item_rep.snapshot(:moo, :final => false)
-    refute File.file?(item_rep.raw_path(:snapshot => :moo))
+    refute File.file?(item_rep.raw_path(snapshot: :moo))
+    item_rep.snapshot(:moo, final: false)
+    refute File.file?(item_rep.raw_path(snapshot: :moo))
 
     # Test final 1
-    item_rep.snapshot(:moo, :final => true)
-    assert File.file?(item_rep.raw_path(:snapshot => :moo))
-    assert_equal 'Lorem ipsum, etc.', File.read(item_rep.raw_path(:snapshot => :moo))
-    FileUtils.rm_f(item_rep.raw_path(:snapshot => :moo))
+    item_rep.snapshot(:moo, final: true)
+    assert File.file?(item_rep.raw_path(snapshot: :moo))
+    assert_equal 'Lorem ipsum, etc.', File.read(item_rep.raw_path(snapshot: :moo))
+    FileUtils.rm_f(item_rep.raw_path(snapshot: :moo))
 
     # Test final 2
     item_rep.snapshot(:moo)
-    assert File.file?(item_rep.raw_path(:snapshot => :moo))
-    assert_equal 'Lorem ipsum, etc.', File.read(item_rep.raw_path(:snapshot => :moo))
+    assert File.file?(item_rep.raw_path(snapshot: :moo))
+    assert_equal 'Lorem ipsum, etc.', File.read(item_rep.raw_path(snapshot: :moo))
   end
 
   def test_write_should_not_touch_identical_textual_files
     # Mock item
     item = Nanoc::Item.new(
-      "blah blah", {}, '/',
-      :binary => false
+      'blah blah', {}, '/',
+      binary: false
     )
 
     # Create rep
     item_rep = Nanoc::ItemRep.new(item, :foo)
-    def item_rep.generate_diff ; end
+    def item_rep.generate_diff; end
     item_rep.instance_eval { @content[:last] = 'Lorem ipsum, etc.' }
     item_rep.raw_path = 'foo/bar/baz/quux.txt'
 
     # Write once
     item_rep.write
-    a_long_time_ago = Time.now-1_000_000
+    a_long_time_ago = Time.now - 1_000_000
     File.utime(a_long_time_ago, a_long_time_ago, item_rep.raw_path)
 
     # Write again
@@ -222,8 +272,8 @@ class Nanoc::ItemRepTest < Nanoc::TestCase
   def test_write
     # Mock item
     item = Nanoc::Item.new(
-      "blah blah", {}, '/',
-      :binary => false
+      'blah blah', {}, '/',
+      binary: false
     )
 
     # Create rep
@@ -242,19 +292,21 @@ class Nanoc::ItemRepTest < Nanoc::TestCase
   def test_filter_text_to_binary
     # Mock item
     item = Nanoc::Item.new(
-      "blah blah", {}, '/',
-      :binary => false
+      'blah blah', {}, '/',
+      binary: false
     )
 
     # Create rep
     rep = Nanoc::ItemRep.new(item, :foo)
-    def rep.assigns ; {} ; end
+    def rep.assigns
+      {}
+    end
 
     # Create fake filter
-    def rep.filter_named(name)
+    def rep.filter_named(_name)
       @filter ||= Class.new(::Nanoc::Filter) do
-        type :text => :binary
-        def run(content, params={})
+        type text: :binary
+        def run(content, _params = {})
           File.open(output_filename, 'w') { |io| io.write(content) }
         end
       end
@@ -270,19 +322,21 @@ class Nanoc::ItemRepTest < Nanoc::TestCase
   def test_filter_with_textual_rep_and_binary_filter
     # Mock item
     item = Nanoc::Item.new(
-      "blah blah", {}, '/',
-      :binary => false
+      'blah blah', {}, '/',
+      binary: false
     )
 
     # Create rep
     rep = Nanoc::ItemRep.new(item, :foo)
-    def rep.assigns ; {} ; end
+    def rep.assigns
+      {}
+    end
 
     # Create fake filter
-    def rep.filter_named(name)
+    def rep.filter_named(_name)
       @filter ||= Class.new(::Nanoc::Filter) do
         type :binary
-        def run(content, params={})
+        def run(content, _params = {})
           File.open(output_filename, 'w') { |io| io.write(content) }
         end
       end
@@ -296,9 +350,9 @@ class Nanoc::ItemRepTest < Nanoc::TestCase
 
   def test_using_textual_filters_on_binary_reps_raises
     item = create_binary_item
-    site = mock_and_stub(:items => [item],
-      :layouts => [],
-      :config  => []
+    site = mock_and_stub(items: [item],
+      layouts: [],
+      config: []
     )
     item.stubs(:site).returns(site)
     rep = create_rep_for(item, :foo)
@@ -329,18 +383,18 @@ class Nanoc::ItemRepTest < Nanoc::TestCase
 
   def test_converted_binary_rep_can_be_layed_out
     # Mock layout
-    layout = Nanoc::Layout.new(%[<%= "blah" %> <%= yield %>], {}, '/somelayout/')
+    layout = Nanoc::Layout.new(%(<%= "blah" %> <%= yield %>), {}, '/somelayout/')
 
     # Create item and item rep
     item = create_binary_item
     rep = create_rep_for(item, :foo)
-    rep.assigns = { :content => 'meh' }
+    rep.assigns = { content: 'meh' }
 
     # Create filter
     Class.new(::Nanoc::Filter) do
-      type       :binary => :text
+      type binary: :text
       identifier :binary_to_text
-      def run(content, params={})
+      def run(content, _params = {})
         content + ' textified'
       end
     end
@@ -353,9 +407,9 @@ class Nanoc::ItemRepTest < Nanoc::TestCase
 
   def test_converted_binary_rep_can_be_filtered_with_textual_filters
     item = create_binary_item
-    site = mock_and_stub(:items => [item],
-      :layouts => [],
-      :config  => []
+    site = mock_and_stub(items: [item],
+      layouts: [],
+      config: []
     )
     item.stubs(:site).returns(site)
     rep = create_rep_for(item, :foo)
@@ -364,22 +418,22 @@ class Nanoc::ItemRepTest < Nanoc::TestCase
 
     assert rep.binary?
 
-    def rep.filter_named(name)
+    def rep.filter_named(_name)
       Class.new(::Nanoc::Filter) do
-        type :binary => :text
-        def run(content, params={})
-          "Some textual content"
+        type binary: :text
+        def run(_content, _params = {})
+          'Some textual content'
         end
       end
     end
     rep.filter(:binary_to_text)
     assert !rep.binary?
 
-    def rep.filter_named(name)
+    def rep.filter_named(_name)
       Class.new(::Nanoc::Filter) do
         type :text
-        def run(content, params={})
-          "Some textual content"
+        def run(_content, _params = {})
+          'Some textual content'
         end
       end
     end
@@ -390,9 +444,9 @@ class Nanoc::ItemRepTest < Nanoc::TestCase
   def test_converted_binary_rep_cannot_be_filtered_with_binary_filters
     item = create_binary_item
     site = mock_and_stub(
-      :items   => [item],
-      :layouts => [],
-      :config  => []
+      items: [item],
+      layouts: [],
+      config: []
     )
     item.stubs(:site).returns(site)
     rep = create_rep_for(item, :foo)
@@ -400,11 +454,11 @@ class Nanoc::ItemRepTest < Nanoc::TestCase
     create_binary_filter
 
     assert rep.binary?
-    def rep.filter_named(name)
+    def rep.filter_named(_name)
       @filter ||= Class.new(::Nanoc::Filter) do
-        type :binary => :text
-        def run(content, params={})
-          "Some textual content"
+        type binary: :text
+        def run(_content, _params = {})
+          'Some textual content'
         end
       end
     end
@@ -415,16 +469,18 @@ class Nanoc::ItemRepTest < Nanoc::TestCase
 
   def test_new_content_should_be_frozen
     filter_class = Class.new(::Nanoc::Filter) do
-      def run(content, params={})
+      def run(content, _params = {})
         content.gsub!('foo', 'moo')
         content
       end
     end
 
-    item = Nanoc::Item.new("foo bar", {}, '/foo/')
+    item = Nanoc::Item.new('foo bar', {}, '/foo/')
     rep = Nanoc::ItemRep.new(item, :default)
     rep.instance_eval { @filter_class = filter_class }
-    def rep.filter_named(name) ; @filter_class ; end
+    def rep.filter_named(_name)
+      @filter_class
+    end
 
     assert_raises_frozen_error do
       rep.filter(:whatever)
@@ -433,16 +489,18 @@ class Nanoc::ItemRepTest < Nanoc::TestCase
 
   def test_filter_should_freeze_content
     filter_class = Class.new(::Nanoc::Filter) do
-      def run(content, params={})
+      def run(content, _params = {})
         content.gsub!('foo', 'moo')
         content
       end
     end
 
-    item = Nanoc::Item.new("foo bar", {}, '/foo/')
+    item = Nanoc::Item.new('foo bar', {}, '/foo/')
     rep = Nanoc::ItemRep.new(item, :default)
     rep.instance_eval { @filter_class = filter_class }
-    def rep.filter_named(name) ; @filter_class ; end
+    def rep.filter_named(_name)
+      @filter_class
+    end
 
     assert_raises_frozen_error do
       rep.filter(:erb)
@@ -452,8 +510,8 @@ class Nanoc::ItemRepTest < Nanoc::TestCase
 
   def test_raw_path_should_generate_dependency
     items = [
-      Nanoc3::Item.new("foo", {}, '/foo/'),
-      Nanoc3::Item.new("bar", {}, '/bar/')
+      Nanoc3::Item.new('foo', {}, '/foo/'),
+      Nanoc3::Item.new('bar', {}, '/bar/')
     ]
     item_reps = [
       Nanoc3::ItemRep.new(items[0], :default),
@@ -467,13 +525,13 @@ class Nanoc::ItemRepTest < Nanoc::TestCase
     Nanoc3::NotificationCenter.post(:visit_ended,   items[0])
     dt.stop
 
-    assert_equal [ items[1] ], dt.objects_causing_outdatedness_of(items[0])
+    assert_equal [items[1]], dt.objects_causing_outdatedness_of(items[0])
   end
 
   def test_path_should_generate_dependency
     items = [
-      Nanoc3::Item.new("foo", {}, '/foo/'),
-      Nanoc3::Item.new("bar", {}, '/bar/')
+      Nanoc3::Item.new('foo', {}, '/foo/'),
+      Nanoc3::Item.new('bar', {}, '/bar/')
     ]
     item_reps = [
       Nanoc3::ItemRep.new(items[0], :default),
@@ -487,11 +545,11 @@ class Nanoc::ItemRepTest < Nanoc::TestCase
     Nanoc3::NotificationCenter.post(:visit_ended,   items[0])
     dt.stop
 
-    assert_equal [ items[1] ], dt.objects_causing_outdatedness_of(items[0])
+    assert_equal [items[1]], dt.objects_causing_outdatedness_of(items[0])
   end
 
   def test_access_compiled_content_of_binary_item
-    item = Nanoc::Item.new("content/somefile.dat", {}, '/somefile/', :binary => true)
+    item = Nanoc::Item.new('content/somefile.dat', {}, '/somefile/', binary: true)
     item_rep = Nanoc::ItemRep.new(item, :foo)
     assert_raises(Nanoc::Errors::CannotGetCompiledContentOfBinaryItem) do
       item_rep.compiled_content
@@ -503,8 +561,8 @@ class Nanoc::ItemRepTest < Nanoc::TestCase
     FileUtils.mkdir_p('content')
     File.open('content/meow.dat', 'w') { |io| io.write('asdf') }
     item = Nanoc::Item.new(
-      "content/meow.dat", {}, '/',
-      :binary => true
+      'content/meow.dat', {}, '/',
+      binary: true
     )
 
     # Create rep
@@ -516,7 +574,7 @@ class Nanoc::ItemRepTest < Nanoc::TestCase
 
     # Write
     notified = false
-    Nanoc::NotificationCenter.on(:rep_written, self) do |rep, raw_path, is_created, is_modified|
+    Nanoc::NotificationCenter.on(:rep_written, self) do |_rep, _raw_path, is_created, is_modified|
       notified = true
       assert is_created
       assert is_modified
@@ -531,8 +589,8 @@ class Nanoc::ItemRepTest < Nanoc::TestCase
     FileUtils.mkdir_p('content')
     File.open('content/meow.dat', 'w') { |io| io.write('asdf') }
     item = Nanoc::Item.new(
-      "content/meow.dat", {}, '/',
-      :binary => true
+      'content/meow.dat', {}, '/',
+      binary: true
     )
 
     # Create rep
@@ -546,7 +604,7 @@ class Nanoc::ItemRepTest < Nanoc::TestCase
 
     # Write
     notified = false
-    Nanoc::NotificationCenter.on(:rep_written, self) do |rep, raw_path, is_created, is_modified|
+    Nanoc::NotificationCenter.on(:rep_written, self) do |_rep, _raw_path, is_created, is_modified|
       notified = true
       refute is_created
       assert is_modified
@@ -556,19 +614,19 @@ class Nanoc::ItemRepTest < Nanoc::TestCase
     Nanoc::NotificationCenter.remove(:rep_written, self)
   end
 
-private
+  private
 
   def create_binary_item
     Nanoc::Item.new(
-      "/a/file/name.dat", {}, '/',
-      :binary => true
+      '/a/file/name.dat', {}, '/',
+      binary: true
     )
   end
 
   def mock_and_stub(params)
     m = mock
     params.each do |method, return_value|
-      m.stubs(method.to_sym).returns( return_value )
+      m.stubs(method.to_sym).returns(return_value)
     end
     m
   end
@@ -580,8 +638,8 @@ private
   def create_textual_filter
     f = create_filter(:text)
     f.class_eval do
-      def run(content, params={})
-        ""
+      def run(_content, _params = {})
+        ''
       end
     end
     f
@@ -590,7 +648,7 @@ private
   def create_binary_filter
     f = create_filter(:binary)
     f.class_eval do
-      def run(content, params={})
+      def run(content, _params = {})
         File.open(output_filename, 'w') { |io| io.write(content) }
       end
     end
@@ -603,5 +661,4 @@ private
     Nanoc::Filter.register filter_klass, "#{type}_filter".to_sym
     filter_klass
   end
-
 end
diff --git a/test/base/test_item_rep_recorder_proxy.rb b/test/base/test_item_rep_recorder_proxy.rb
new file mode 100644
index 0000000..92d0669
--- /dev/null
+++ b/test/base/test_item_rep_recorder_proxy.rb
@@ -0,0 +1,19 @@
+# encoding: utf-8
+
+class Nanoc::ItemRepRecorderProxyTest < Nanoc::TestCase
+  def test_double_names
+    proxy = Nanoc::ItemRepRecorderProxy.new(mock)
+
+    proxy.snapshot(:foo, stuff: :giraffe)
+    assert_raises(Nanoc::Errors::CannotCreateMultipleSnapshotsWithSameName) do
+      proxy.snapshot(:foo, stuff: :donkey)
+    end
+  end
+
+  def test_double_params
+    proxy = Nanoc::ItemRepRecorderProxy.new(mock)
+
+    proxy.snapshot(:foo)
+    proxy.snapshot(:bar)
+  end
+end
diff --git a/test/base/test_layout.rb b/test/base/test_layout.rb
index 6f82eb2..dc39334 100644
--- a/test/base/test_layout.rb
+++ b/test/base/test_layout.rb
@@ -1,19 +1,18 @@
 # encoding: utf-8
 
 class Nanoc::LayoutTest < Nanoc::TestCase
-
   def test_initialize
     # Make sure attributes are cleaned
-    layout = Nanoc::Layout.new("content", { 'foo' => 'bar' }, '/foo/')
-    assert_equal({ :foo => 'bar' }, layout.attributes)
+    layout = Nanoc::Layout.new('content', { 'foo' => 'bar' }, '/foo/')
+    assert_equal({ foo: 'bar' }, layout.attributes)
 
     # Make sure identifier is cleaned
-    layout = Nanoc::Layout.new("content", { 'foo' => 'bar' }, 'foo')
+    layout = Nanoc::Layout.new('content', { 'foo' => 'bar' }, 'foo')
     assert_equal('/foo/', layout.identifier)
   end
 
   def test_frozen_identifier
-    layout = Nanoc::Layout.new("foo", {}, '/foo')
+    layout = Nanoc::Layout.new('foo', {}, '/foo')
 
     assert_raises_frozen_error do
       layout.identifier.chop!
@@ -22,7 +21,7 @@ class Nanoc::LayoutTest < Nanoc::TestCase
 
   def test_lookup_with_known_attribute
     # Create layout
-    layout = Nanoc::Layout.new("content", { 'foo' => 'bar' }, '/foo/')
+    layout = Nanoc::Layout.new('content', { 'foo' => 'bar' }, '/foo/')
 
     # Check attributes
     assert_equal('bar', layout[:foo])
@@ -30,7 +29,7 @@ class Nanoc::LayoutTest < Nanoc::TestCase
 
   def test_lookup_with_unknown_attribute
     # Create layout
-    layout = Nanoc::Layout.new("content", { 'foo' => 'bar' }, '/foo/')
+    layout = Nanoc::Layout.new('content', { 'foo' => 'bar' }, '/foo/')
 
     # Check attributes
     assert_equal(nil, layout[:filter])
@@ -38,15 +37,14 @@ class Nanoc::LayoutTest < Nanoc::TestCase
 
   def test_dump_and_load
     layout = Nanoc::Layout.new(
-      "foobar",
-      { :a => { :b => 123 }},
+      'foobar',
+      { a: { b: 123 } },
       '/foo/')
 
     layout = Marshal.load(Marshal.dump(layout))
 
     assert_equal '/foo/', layout.identifier
     assert_equal 'foobar', layout.raw_content
-    assert_equal({ :a => { :b => 123 }}, layout.attributes)
+    assert_equal({ a: { b: 123 } }, layout.attributes)
   end
-
 end
diff --git a/test/base/test_memoization.rb b/test/base/test_memoization.rb
index 68da84c..f6eff9c 100644
--- a/test/base/test_memoization.rb
+++ b/test/base/test_memoization.rb
@@ -1,9 +1,7 @@
 # encoding: utf-8
 
 class Nanoc::MemoizationTest < Nanoc::TestCase
-
   class Sample1
-
     extend Nanoc::Memoization
 
     def initialize(value)
@@ -11,14 +9,12 @@ class Nanoc::MemoizationTest < Nanoc::TestCase
     end
 
     def run(n)
-      @value*10 + n
+      @value * 10 + n
     end
     memoize :run
-
   end
 
   class Sample2
-
     extend Nanoc::Memoization
 
     def initialize(value)
@@ -26,14 +22,12 @@ class Nanoc::MemoizationTest < Nanoc::TestCase
     end
 
     def run(n)
-      @value*100 + n
+      @value * 100 + n
     end
     memoize :run
-
   end
 
   class EqualSample
-
     extend Nanoc::Memoization
 
     def initialize(value)
@@ -44,19 +38,18 @@ class Nanoc::MemoizationTest < Nanoc::TestCase
       4
     end
 
-    def eql?(other)
+    def eql?(_other)
       true
     end
 
-    def ==(other)
+    def ==(_other)
       true
     end
 
     def run(n)
-      @value*10 + n
+      @value * 10 + n
     end
     memoize :run
-
   end
 
   def test
@@ -66,10 +59,10 @@ class Nanoc::MemoizationTest < Nanoc::TestCase
     sample2b = Sample2.new(25)
 
     3.times do
-      assert_equal 10*10+5,  sample1a.run(5)
-      assert_equal 10*15+7,  sample1b.run(7)
-      assert_equal 100*20+5, sample2a.run(5)
-      assert_equal 100*25+7, sample2b.run(7)
+      assert_equal 10 * 10 + 5,  sample1a.run(5)
+      assert_equal 10 * 15 + 7,  sample1b.run(7)
+      assert_equal 100 * 20 + 5, sample2a.run(5)
+      assert_equal 100 * 25 + 7, sample2b.run(7)
     end
   end
 
@@ -78,11 +71,10 @@ class Nanoc::MemoizationTest < Nanoc::TestCase
     sample2 = EqualSample.new(3)
 
     3.times do
-      assert_equal 2*10+5, sample1.run(5)
-      assert_equal 2*10+3, sample1.run(3)
-      assert_equal 3*10+5, sample2.run(5)
-      assert_equal 3*10+3, sample2.run(3)
+      assert_equal 2 * 10 + 5, sample1.run(5)
+      assert_equal 2 * 10 + 3, sample1.run(3)
+      assert_equal 3 * 10 + 5, sample2.run(5)
+      assert_equal 3 * 10 + 3, sample2.run(3)
     end
   end
-
 end
diff --git a/test/base/test_notification_center.rb b/test/base/test_notification_center.rb
index ab5bb8a..5f52d15 100644
--- a/test/base/test_notification_center.rb
+++ b/test/base/test_notification_center.rb
@@ -1,7 +1,6 @@
 # encoding: utf-8
 
 class Nanoc::NotificationCenterTest < Nanoc::TestCase
-
   def test_post
     # Set up notification
     Nanoc::NotificationCenter.on :ping_received, :test do
@@ -28,5 +27,4 @@ class Nanoc::NotificationCenterTest < Nanoc::TestCase
     Nanoc::NotificationCenter.post :ping_received
     assert(!@ping_received)
   end
-
 end
diff --git a/test/base/test_outdatedness_checker.rb b/test/base/test_outdatedness_checker.rb
index e28a3c3..0d48252 100644
--- a/test/base/test_outdatedness_checker.rb
+++ b/test/base/test_outdatedness_checker.rb
@@ -1,10 +1,9 @@
 # encoding: utf-8
 
 class Nanoc::OutdatednessCheckerTest < Nanoc::TestCase
-
   def test_not_outdated
     # Compile once
-    with_site(:name => 'foo') do |site|
+    with_site(name: 'foo') do |site|
       File.open('content/index.html', 'w') { |io| io.write('o hello') }
       File.open('lib/stuff.rb', 'w') { |io| io.write('$foo = 123') }
 
@@ -12,7 +11,7 @@ class Nanoc::OutdatednessCheckerTest < Nanoc::TestCase
     end
 
     # Check
-    with_site(:name => 'foo') do |site|
+    with_site(name: 'foo') do |site|
       outdatedness_checker = site.compiler.send :outdatedness_checker
       rep = site.items[0].reps[0]
       assert_nil outdatedness_checker.outdatedness_reason_for(rep)
@@ -21,7 +20,7 @@ class Nanoc::OutdatednessCheckerTest < Nanoc::TestCase
 
   def test_outdated_if_item_checksum_nil
     # Compile once
-    with_site(:name => 'foo') do |site|
+    with_site(name: 'foo') do |site|
       File.open('content/index.html', 'w') { |io| io.write('o hello') }
       File.open('lib/stuff.rb', 'w') { |io| io.write('$foo = 123') }
 
@@ -29,12 +28,12 @@ class Nanoc::OutdatednessCheckerTest < Nanoc::TestCase
     end
 
     # Delete checksums
-    with_site(:name => 'foo') do |site|
+    with_site(name: 'foo') do |_site|
       FileUtils.rm('tmp/checksums')
     end
 
     # Check
-    with_site(:name => 'foo') do |site|
+    with_site(name: 'foo') do |site|
       outdatedness_checker = site.compiler.send :outdatedness_checker
       rep = site.items.find { |i| i.identifier == '/' }.reps[0]
       assert_equal ::Nanoc::OutdatednessReasons::NotEnoughData,
@@ -44,7 +43,7 @@ class Nanoc::OutdatednessCheckerTest < Nanoc::TestCase
 
   def test_outdated_if_compiled_file_doesnt_exist
     # Compile once
-    with_site(:name => 'foo') do |site|
+    with_site(name: 'foo') do |site|
       File.open('content/index.html', 'w') { |io| io.write('o hello') }
       File.open('lib/stuff.rb', 'w') { |io| io.write('$foo = 123') }
 
@@ -57,7 +56,7 @@ class Nanoc::OutdatednessCheckerTest < Nanoc::TestCase
     end
 
     # Check
-    with_site(:name => 'foo') do |site|
+    with_site(name: 'foo') do |site|
       outdatedness_checker = site.compiler.send :outdatedness_checker
       rep = site.items.find { |i| i.identifier == '/' }.reps[0]
       assert_equal ::Nanoc::OutdatednessReasons::NotWritten,
@@ -67,7 +66,7 @@ class Nanoc::OutdatednessCheckerTest < Nanoc::TestCase
 
   def test_outdated_if_item_checksum_is_different
     # Compile once
-    with_site(:name => 'foo') do |site|
+    with_site(name: 'foo') do |site|
       File.open('content/index.html', 'w') { |io| io.write('o hello') }
       File.open('content/new.html', 'w') { |io| io.write('o hello too') }
       File.open('lib/stuff.rb', 'w') { |io| io.write('$foo = 123') }
@@ -81,7 +80,7 @@ class Nanoc::OutdatednessCheckerTest < Nanoc::TestCase
     end
 
     # Check
-    with_site(:name => 'foo') do |site|
+    with_site(name: 'foo') do |site|
       outdatedness_checker = site.compiler.send :outdatedness_checker
       rep = site.items.find { |i| i.identifier == '/new/' }.reps[0]
       assert_equal ::Nanoc::OutdatednessReasons::SourceModified,
@@ -91,7 +90,7 @@ class Nanoc::OutdatednessCheckerTest < Nanoc::TestCase
 
   def test_outdated_if_dependent_layout_outdated
     # Compile once
-    with_site(:name => 'foo', :compilation_rule_content => 'layout "/default/"', :has_layout => true) do |site|
+    with_site(name: 'foo', compilation_rule_content: 'layout "/default/"', has_layout: true) do |site|
       File.open('content/index.html', 'w') { |io| io.write('o hello') }
       File.open('layouts/default.html', 'w') { |io| io.write('!!! <%= yield %> !!!') }
 
@@ -104,8 +103,8 @@ class Nanoc::OutdatednessCheckerTest < Nanoc::TestCase
     end
 
     # Check
-    with_site(:name => 'foo') do |site|
-      # FIXME ugly fugly hack
+    with_site(name: 'foo') do |site|
+      # FIXME: ugly fugly hack
       site.compiler.load
 
       outdatedness_checker = site.compiler.send :outdatedness_checker
@@ -117,7 +116,7 @@ class Nanoc::OutdatednessCheckerTest < Nanoc::TestCase
 
   def test_outdated_if_dependent_item_outdated
     # Compile once
-    with_site(:name => 'foo', :compilation_rule_content => 'filter :erb') do |site|
+    with_site(name: 'foo', compilation_rule_content: 'filter :erb') do |site|
       File.open('content/a.html', 'w') do |io|
         io.write('<%= @items.find { |i| i.identifier == "/b/" }.compiled_content %>')
       end
@@ -136,8 +135,8 @@ class Nanoc::OutdatednessCheckerTest < Nanoc::TestCase
     end
 
     # Check
-    with_site(:name => 'foo') do |site|
-      # FIXME ugly fugly hack
+    with_site(name: 'foo') do |site|
+      # FIXME: ugly fugly hack
       site.compiler.load
 
       outdatedness_checker = site.compiler.send :outdatedness_checker
@@ -149,7 +148,7 @@ class Nanoc::OutdatednessCheckerTest < Nanoc::TestCase
 
   def test_outdated_if_dependent_item_outdated_chained
     # Compile once
-    with_site(:name => 'foo', :compilation_rule_content => 'filter :erb') do |site|
+    with_site(name: 'foo', compilation_rule_content: 'filter :erb') do |site|
       File.open('content/a.html', 'w') do |io|
         io.write('<%= @items.find { |i| i.identifier == "/b/" }.compiled_content %> aaa')
       end
@@ -171,8 +170,8 @@ class Nanoc::OutdatednessCheckerTest < Nanoc::TestCase
     end
 
     # Check
-    with_site(:name => 'foo') do |site|
-      # FIXME ugly fugly hack
+    with_site(name: 'foo') do |site|
+      # FIXME: ugly fugly hack
       site.compiler.load
 
       outdatedness_checker = site.compiler.send :outdatedness_checker
@@ -184,7 +183,7 @@ class Nanoc::OutdatednessCheckerTest < Nanoc::TestCase
 
   def test_outdated_if_dependent_item_removed
     # Compile once
-    with_site(:name => 'foo', :compilation_rule_content => 'filter :erb') do |site|
+    with_site(name: 'foo', compilation_rule_content: 'filter :erb') do |site|
       File.open('content/a.html', 'w') do |io|
         io.write('<% @items.select { |i| i.identifier != @item.identifier }.each do |i| %>')
         io.write('  <%= i.compiled_content %>')
@@ -203,8 +202,8 @@ class Nanoc::OutdatednessCheckerTest < Nanoc::TestCase
     end
 
     # Check
-    with_site(:name => 'foo') do |site|
-      # FIXME ugly fugly hack
+    with_site(name: 'foo') do |site|
+      # FIXME: ugly fugly hack
       site.compiler.load
 
       outdatedness_checker = site.compiler.send :outdatedness_checker
@@ -216,7 +215,7 @@ class Nanoc::OutdatednessCheckerTest < Nanoc::TestCase
 
   def test_outdated_if_dependent_item_added
     # Compile once
-    with_site(:name => 'foo', :compilation_rule_content => 'filter :erb') do |site|
+    with_site(name: 'foo', compilation_rule_content: 'filter :erb') do |site|
       File.open('content/a.html', 'w') do |io|
         io.write('<% @items.select { |i| i.identifier != @item.identifier }.each do |i| %>')
         io.write('  <%= i.compiled_content %>')
@@ -237,8 +236,8 @@ class Nanoc::OutdatednessCheckerTest < Nanoc::TestCase
     end
 
     # Check
-    with_site(:name => 'foo') do |site|
-      # FIXME ugly fugly hack
+    with_site(name: 'foo') do |site|
+      # FIXME: ugly fugly hack
       site.compiler.load
 
       outdatedness_checker = site.compiler.send :outdatedness_checker
@@ -248,11 +247,11 @@ class Nanoc::OutdatednessCheckerTest < Nanoc::TestCase
     end
   end
 
-  # TODO make sure outdatedness of non-outdated items is correct
+  # TODO: make sure outdatedness of non-outdated items is correct
 
   def test_outdated_if_code_snippets_outdated
     # Compile once
-    with_site(:name => 'foo') do |site|
+    with_site(name: 'foo') do |site|
       File.open('content/index.html', 'w') { |io| io.write('o hello') }
 
       site.compile
@@ -264,7 +263,7 @@ class Nanoc::OutdatednessCheckerTest < Nanoc::TestCase
     end
 
     # Check
-    with_site(:name => 'foo') do |site|
+    with_site(name: 'foo') do |site|
       outdatedness_checker = site.compiler.send :outdatedness_checker
       rep = site.items.find { |i| i.identifier == '/' }.reps[0]
       assert_equal ::Nanoc::OutdatednessReasons::CodeSnippetsModified,
@@ -274,7 +273,7 @@ class Nanoc::OutdatednessCheckerTest < Nanoc::TestCase
 
   def test_outdated_if_config_outdated
     # Compile once
-    with_site(:name => 'foo') do |site|
+    with_site(name: 'foo') do |site|
       File.open('content/index.html', 'w') { |io| io.write('o hello') }
 
       site.compile
@@ -286,7 +285,7 @@ class Nanoc::OutdatednessCheckerTest < Nanoc::TestCase
     end
 
     # Check
-    with_site(:name => 'foo') do |site|
+    with_site(name: 'foo') do |site|
       outdatedness_checker = site.compiler.send :outdatedness_checker
       rep = site.items.find { |i| i.identifier == '/' }.reps[0]
       assert_equal ::Nanoc::OutdatednessReasons::ConfigurationModified,
@@ -296,7 +295,7 @@ class Nanoc::OutdatednessCheckerTest < Nanoc::TestCase
 
   def test_not_outdated_if_irrelevant_rule_modified
     # Compile once
-    with_site(:name => 'foo') do |site|
+    with_site(name: 'foo') do |site|
       File.open('content/index.html', 'w') { |io| io.write('o hello') }
 
       site.compile
@@ -308,7 +307,7 @@ class Nanoc::OutdatednessCheckerTest < Nanoc::TestCase
     end
 
     # Check
-    with_site(:name => 'foo') do |site|
+    with_site(name: 'foo') do |site|
       outdatedness_checker = site.compiler.send :outdatedness_checker
       rep = site.items.find { |i| i.identifier == '/' }.reps[0]
       assert_nil outdatedness_checker.outdatedness_reason_for(rep)
@@ -317,7 +316,7 @@ class Nanoc::OutdatednessCheckerTest < Nanoc::TestCase
 
   def test_outdated_if_relevant_rule_modified
     # Create site
-    with_site(:name => 'foo') do |site|
+    with_site(name: 'foo') do |_site|
       File.open('content/index.html', 'w') { |io| io.write('o hello') }
       File.open('Rules', 'w') do |io|
         io.write("compile '/' do\n")
@@ -360,7 +359,7 @@ class Nanoc::OutdatednessCheckerTest < Nanoc::TestCase
 
   def test_items_in_rules_should_not_cause_outdatedness
     # Create site
-    with_site(:name => 'foo') do |site|
+    with_site(name: 'foo') do |_site|
       File.open('content/index.html', 'w') { |io| io.write('o hello') }
       File.open('Rules', 'w') do |io|
         io.write("compile '/' do\n")
@@ -384,14 +383,14 @@ class Nanoc::OutdatednessCheckerTest < Nanoc::TestCase
       site = Nanoc::Site.new('.')
       outdatedness_checker = site.compiler.outdatedness_checker
       site.items.each do |item|
-        refute outdatedness_checker.outdated?(item), "item should not be outdated"
+        refute outdatedness_checker.outdated?(item), 'item should not be outdated'
       end
     end
   end
 
   def test_non_serializable_parameters_in_rules_should_be_allowed
     # Create site
-    with_site(:name => 'foo') do |site|
+    with_site(name: 'foo') do |_site|
       File.open('content/index.html', 'w') { |io| io.write('o hello') }
       File.open('Rules', 'w') do |io|
         io.write("compile '/' do\n")
@@ -417,9 +416,8 @@ class Nanoc::OutdatednessCheckerTest < Nanoc::TestCase
       site = Nanoc::Site.new('.')
       outdatedness_checker = site.compiler.outdatedness_checker
       site.items.each do |item|
-        refute outdatedness_checker.outdated?(item), "item should not be outdated"
+        refute outdatedness_checker.outdated?(item), 'item should not be outdated'
       end
     end
   end
-
 end
diff --git a/test/base/test_plugin.rb b/test/base/test_plugin.rb
index bc94db0..bb6b5fb 100644
--- a/test/base/test_plugin.rb
+++ b/test/base/test_plugin.rb
@@ -1,7 +1,6 @@
 # encoding: utf-8
 
 class Nanoc::PluginTest < Nanoc::TestCase
-
   class SampleFilter < Nanoc::Filter
     identifier :_plugin_test_sample_filter
   end
@@ -24,5 +23,4 @@ class Nanoc::PluginTest < Nanoc::TestCase
 
     refute_nil filter
   end
-
 end
diff --git a/test/base/test_rule.rb b/test/base/test_rule.rb
index 1c30301..ac47ab4 100644
--- a/test/base/test_rule.rb
+++ b/test/base/test_rule.rb
@@ -1,17 +1,25 @@
 # encoding: utf-8
 
 class Nanoc::RuleTest < Nanoc::TestCase
-
   def test_initialize
-    # TODO implement
+    # TODO: implement
   end
 
   def test_applicable_to
-    # TODO implement
+    # TODO: implement
   end
 
   def test_apply_to
-    # TODO implement
+    # TODO: implement
   end
 
+  def test_matches
+    regexp     = %r</(.*)/(.*)/>
+    identifier = '/anything/else/'
+    expected   = ['anything', 'else']
+
+    rule = Nanoc::Rule.new(regexp, :string, Proc.new {})
+
+    assert_equal expected, rule.send(:matches, identifier)
+  end
 end
diff --git a/test/base/test_rule_context.rb b/test/base/test_rule_context.rb
index c7a8720..d03c175 100644
--- a/test/base/test_rule_context.rb
+++ b/test/base/test_rule_context.rb
@@ -1,7 +1,6 @@
 # encoding: utf-8
 
 class Nanoc::RuleContextTest < Nanoc::TestCase
-
   def test_objects
     # Mock everything
     config = mock
@@ -18,7 +17,7 @@ class Nanoc::RuleContextTest < Nanoc::TestCase
     compiler = Nanoc::Compiler.new(site)
 
     # Create context
-    @rule_context = Nanoc::RuleContext.new(:rep => rep, :compiler => compiler)
+    @rule_context = Nanoc::RuleContext.new(rep: rep, compiler: compiler)
 
     # Check
     assert_equal rep,     @rule_context.rep
@@ -44,7 +43,7 @@ class Nanoc::RuleContextTest < Nanoc::TestCase
     # Mock rep
     rep = mock
     rep.stubs(:item).returns(item)
-    rep.expects(:filter).with(:foo, { :bar => 'baz' })
+    rep.expects(:filter).with(:foo, { bar: 'baz' })
     rep.expects(:layout).with('foo')
     rep.expects(:snapshot).with('awesome')
 
@@ -52,12 +51,11 @@ class Nanoc::RuleContextTest < Nanoc::TestCase
     compiler = Nanoc::Compiler.new(site)
 
     # Create context
-    @rule_context = Nanoc::RuleContext.new(:rep => rep, :compiler => compiler)
+    @rule_context = Nanoc::RuleContext.new(rep: rep, compiler: compiler)
 
     # Check
-    rep.filter   :foo, :bar => 'baz'
-    rep.layout   'foo'
+    rep.filter :foo, bar: 'baz'
+    rep.layout 'foo'
     rep.snapshot 'awesome'
   end
-
 end
diff --git a/test/base/test_site.rb b/test/base/test_site.rb
index 3ffa8da..12aa18e 100644
--- a/test/base/test_site.rb
+++ b/test/base/test_site.rb
@@ -1,7 +1,6 @@
 # encoding: utf-8
 
 class Nanoc::SiteTest < Nanoc::TestCase
-
   def test_initialize_with_dir_without_config_yaml
     assert_raises(Nanoc::Errors::GenericTrivial) do
       Nanoc::Site.new('.')
@@ -21,12 +20,12 @@ class Nanoc::SiteTest < Nanoc::TestCase
   end
 
   def test_initialize_with_config_hash
-    site = Nanoc::Site.new(:foo => 'bar')
+    site = Nanoc::Site.new(foo: 'bar')
     assert_equal 'bar', site.config[:foo]
   end
 
   def test_initialize_with_incomplete_data_source_config
-    site = Nanoc::Site.new(:data_sources => [ { :type => 'foo', :items_root => '/bar/' } ])
+    site = Nanoc::Site.new(data_sources: [{ type: 'foo', items_root: '/bar/' }])
     assert_equal('foo',   site.config[:data_sources][0][:type])
     assert_equal('/bar/', site.config[:data_sources][0][:items_root])
     assert_equal('/',     site.config[:data_sources][0][:layouts_root])
@@ -74,7 +73,7 @@ EOF
     end
 
     error = assert_raises(Nanoc::Errors::GenericTrivial) do
-      site = Nanoc::Site.new('.')
+      Nanoc::Site.new('.')
     end
     assert_equal(
       "Could not find parent configuration file 'foo/foo.yaml'",
@@ -98,7 +97,7 @@ EOF
     end
 
     error = assert_raises(Nanoc::Errors::GenericTrivial) do
-      site = Nanoc::Site.new('.')
+      Nanoc::Site.new('.')
     end
     assert_equal(
       "Cycle detected. Could not use parent configuration file '../nanoc.yaml'",
@@ -146,7 +145,7 @@ EOF
       # Update configuration
       File.open('nanoc.yaml', 'w') do |io|
         io.write "data_sources:\n"
-        io.write "  - type: site_test_foo"
+        io.write '  - type: site_test_foo'
       end
 
       # Create site
@@ -176,9 +175,9 @@ EOF
       bar    = site.items.find { |i| i.identifier == '/parent/bar/' }
       qux    = site.items.find { |i| i.identifier == '/parent/bar/qux/' }
 
-      assert_equal Set.new([ parent, style ]), Set.new(root.children)
-      assert_equal Set.new([ foo, bar ]),      Set.new(parent.children)
-      assert_equal Set.new([ qux ]),           Set.new(bar.children)
+      assert_equal Set.new([parent, style]), Set.new(root.children)
+      assert_equal Set.new([foo, bar]),      Set.new(parent.children)
+      assert_equal Set.new([qux]),           Set.new(bar.children)
 
       assert_equal nil,    root.parent
       assert_equal root,   parent.parent
@@ -213,15 +212,13 @@ EOF
       end
     end
   end
-
 end
 
 describe 'Nanoc::Site#initialize' do
-
   include Nanoc::TestHelpers
 
   it 'should merge default config' do
-    site = Nanoc::Site.new(:foo => 'bar')
+    site = Nanoc::Site.new(foo: 'bar')
     site.config[:foo].must_equal 'bar'
     site.config[:output_dir].must_equal 'output'
   end
@@ -231,30 +228,26 @@ describe 'Nanoc::Site#initialize' do
   end
 
   it 'should not raise for non-existant output directory' do
-    Nanoc::Site.new(:output_dir => 'fklsdhailfdjalghlkasdflhagjskajdf')
+    Nanoc::Site.new(output_dir: 'fklsdhailfdjalghlkasdflhagjskajdf')
   end
 
   it 'should not raise for unknown data sources' do
     proc do
-      Nanoc::Site.new(:data_source => 'fklsdhailfdjalghlkasdflhagjskajdf')
+      Nanoc::Site.new(data_source: 'fklsdhailfdjalghlkasdflhagjskajdf')
     end
   end
-
 end
 
 describe 'Nanoc::Site#compiler' do
-
   include Nanoc::TestHelpers
 
   it 'should not raise under normal circumstances' do
     site = Nanoc::Site.new({})
     site.compiler
   end
-
 end
 
 describe 'Nanoc::Site#data_sources' do
-
   include Nanoc::TestHelpers
 
   it 'should not raise for known data sources' do
@@ -265,8 +258,8 @@ describe 'Nanoc::Site#data_sources' do
   it 'should raise for unknown data sources' do
     proc do
       site = Nanoc::Site.new(
-        :data_sources => [
-          { :type => 'fklsdhailfdjalghlkasdflhagjskajdf' }
+        data_sources: [
+          { type: 'fklsdhailfdjalghlkasdflhagjskajdf' }
         ]
       )
       site.data_sources
@@ -291,5 +284,4 @@ describe 'Nanoc::Site#data_sources' do
       assert data_sources.first.config[:bbb] = 'two'
     end
   end
-
 end
diff --git a/test/base/test_store.rb b/test/base/test_store.rb
index c071a2f..f9c05cf 100644
--- a/test/base/test_store.rb
+++ b/test/base/test_store.rb
@@ -1,9 +1,7 @@
 # encoding: utf-8
 
 class Nanoc::StoreTest < Nanoc::TestCase
-
   class TestStore < Nanoc::Store
-
     def data
       @data
     end
@@ -11,7 +9,6 @@ class Nanoc::StoreTest < Nanoc::TestCase
     def data=(new_data)
       @data = new_data
     end
-
   end
 
   def test_delete_and_reload_on_error
@@ -19,13 +16,13 @@ class Nanoc::StoreTest < Nanoc::TestCase
 
     # Create
     store.load
-    store.data = { :fun => 'sure' }
+    store.data = { fun: 'sure' }
     store.store
 
     # Test stored values
     store = TestStore.new('test.db', 1)
     store.load
-    assert_equal({ :fun => 'sure' }, store.data)
+    assert_equal({ fun: 'sure' }, store.data)
 
     # Mess up
     File.open('test.db', 'w') do |io|
@@ -37,5 +34,4 @@ class Nanoc::StoreTest < Nanoc::TestCase
     store.load
     assert_equal(nil, store.data)
   end
-
 end
diff --git a/test/cli/commands/test_check.rb b/test/cli/commands/test_check.rb
index 3ca65b2..b953f69 100644
--- a/test/cli/commands/test_check.rb
+++ b/test/cli/commands/test_check.rb
@@ -1,9 +1,8 @@
 # encoding: utf-8
 
 class Nanoc::CLI::Commands::CheckTest < Nanoc::TestCase
-
   def test_check_stale
-    with_site do |site|
+    with_site do |_site|
       FileUtils.mkdir_p('output')
 
       # Should not raise now
@@ -16,5 +15,4 @@ class Nanoc::CLI::Commands::CheckTest < Nanoc::TestCase
       end
     end
   end
-
 end
diff --git a/test/cli/commands/test_compile.rb b/test/cli/commands/test_compile.rb
index 8945c18..797927e 100644
--- a/test/cli/commands/test_compile.rb
+++ b/test/cli/commands/test_compile.rb
@@ -1,9 +1,8 @@
 # encoding: utf-8
 
 class Nanoc::CLI::Commands::CompileTest < Nanoc::TestCase
-
   def test_profiling_information
-    with_site do |site|
+    with_site do |_site|
       Nanoc::CLI.run %w( create_item foo )
       Nanoc::CLI.run %w( create_item bar )
       Nanoc::CLI.run %w( create_item baz )
@@ -29,7 +28,7 @@ class Nanoc::CLI::Commands::CompileTest < Nanoc::TestCase
   end
 
   def test_auto_prune
-    with_site do |site|
+    with_site do |_site|
       Nanoc::CLI.run %w( create_item foo )
       Nanoc::CLI.run %w( create_item bar )
       Nanoc::CLI.run %w( create_item baz )
@@ -70,7 +69,7 @@ class Nanoc::CLI::Commands::CompileTest < Nanoc::TestCase
   end
 
   def test_auto_prune_with_exclude
-    with_site do |site|
+    with_site do |_site|
       Nanoc::CLI.run %w( create_item foo )
       Nanoc::CLI.run %w( create_item bar )
       Nanoc::CLI.run %w( create_item baz )
@@ -118,18 +117,29 @@ class Nanoc::CLI::Commands::CompileTest < Nanoc::TestCase
   def test_setup_and_teardown_listeners
     with_site do
       test_listener_class = Class.new(::Nanoc::CLI::Commands::Compile::Listener) do
-        def start ; @started = true ; end
-        def stop  ; @stopped = true ; end
-        def started? ; @started ; end
-        def stopped? ; @stopped ; end
+        def start
+          @started = true
+        end
+
+        def stop
+          @stopped = true
+        end
+
+        def started?
+          @started
+        end
+
+        def stopped?
+          @stopped
+        end
       end
 
       options = {}
       arguments = []
       cmd = nil
-      listener_classes = [ test_listener_class ]
+      listener_classes = [test_listener_class]
       cmd_runner = Nanoc::CLI::Commands::Compile.new(
-        options, arguments, cmd, :listener_classes => listener_classes)
+        options, arguments, cmd, listener_classes: listener_classes)
 
       cmd_runner.run
 
@@ -148,7 +158,7 @@ class Nanoc::CLI::Commands::CompileTest < Nanoc::TestCase
     rep.compiled = true
 
     # Listen
-    listener = new_file_action_printer([ rep ])
+    listener = new_file_action_printer([rep])
     listener.start
     Nanoc::NotificationCenter.post(:compilation_started, rep)
     Nanoc::NotificationCenter.post(:rep_written, rep, rep.raw_path, false, true)
@@ -169,7 +179,7 @@ class Nanoc::CLI::Commands::CompileTest < Nanoc::TestCase
     rep.raw_path = 'output/foo.txt'
 
     # Listen
-    listener = new_file_action_printer([ rep ])
+    listener = new_file_action_printer([rep])
     listener.start
     Nanoc::NotificationCenter.post(:compilation_started, rep)
     listener.stop
@@ -183,15 +193,15 @@ class Nanoc::CLI::Commands::CompileTest < Nanoc::TestCase
   end
 
   def new_file_action_printer(reps)
-    listener = Nanoc::CLI::Commands::Compile::FileActionPrinter.new(:reps => reps)
+    listener = Nanoc::CLI::Commands::Compile::FileActionPrinter.new(reps: reps)
 
     def listener.log(level, action, path, duration)
       @events ||= []
       @events << {
-        :level    => level,
-        :action   => action,
-        :path     => path,
-        :duration => duration
+        level: level,
+        action: action,
+        path: path,
+        duration: duration
       }
     end
 
@@ -201,5 +211,4 @@ class Nanoc::CLI::Commands::CompileTest < Nanoc::TestCase
 
     listener
   end
-
 end
diff --git a/test/cli/commands/test_create_item.rb b/test/cli/commands/test_create_item.rb
index 8b61dcc..c6ee947 100644
--- a/test/cli/commands/test_create_item.rb
+++ b/test/cli/commands/test_create_item.rb
@@ -1,12 +1,10 @@
 # encoding: utf-8
 
 class Nanoc::CLI::Commands::CreateItemTest < Nanoc::TestCase
-
   def test_run
-    with_site do |site|
+    with_site do |_site|
       Nanoc::CLI.run %w( create_item /blah/ )
       assert File.file?('content/blah.html')
     end
   end
-
 end
diff --git a/test/cli/commands/test_create_layout.rb b/test/cli/commands/test_create_layout.rb
index f924202..440b2ac 100644
--- a/test/cli/commands/test_create_layout.rb
+++ b/test/cli/commands/test_create_layout.rb
@@ -1,7 +1,6 @@
 # encoding: utf-8
 
 class Nanoc::CLI::Commands::CreateLayoutTest < Nanoc::TestCase
-
   def test_can_compile_new_layout
     require 'nanoc/cli'
 
@@ -22,5 +21,4 @@ class Nanoc::CLI::Commands::CreateLayoutTest < Nanoc::TestCase
       site.compile
     end
   end
-
 end
diff --git a/test/cli/commands/test_create_site.rb b/test/cli/commands/test_create_site.rb
index eb2942c..d4b40c5 100644
--- a/test/cli/commands/test_create_site.rb
+++ b/test/cli/commands/test_create_site.rb
@@ -1,7 +1,6 @@
 # encoding: utf-8
 
 class Nanoc::CLI::Commands::CreateSiteTest < Nanoc::TestCase
-
   def test_create_site_with_existing_name
     Nanoc::CLI.run %w( create_site foo )
     assert_raises(::Nanoc::Errors::GenericTrivial) do
@@ -19,8 +18,21 @@ class Nanoc::CLI::Commands::CreateSiteTest < Nanoc::TestCase
     end
   end
 
+  def test_can_compile_new_site_with_binary_items
+    Nanoc::CLI.run %w( create_site foo )
+
+    FileUtils.cd('foo') do
+      File.open('content/blah', 'w') { |io| io << 'asdf' }
+      site = Nanoc::Site.new('.')
+      site.load_data
+      site.compile
+
+      assert File.file?('output/blah')
+    end
+  end
+
   def test_default_encoding
-    if !defined?(Encoding)
+    unless defined?(Encoding)
       skip 'No Encoding class'
       return
     end
@@ -31,14 +43,13 @@ class Nanoc::CLI::Commands::CreateSiteTest < Nanoc::TestCase
     Nanoc::CLI.run %w( create_site foo )
 
     FileUtils.cd('foo') do
-
       # Try with encoding = default encoding = utf-8
       File.open('content/index.html', 'w') { |io| io.write("Hello <\xD6>!\n") }
       site = Nanoc::Site.new('.')
       exception = assert_raises(RuntimeError) do
         site.compile
       end
-      assert_equal "Could not read content/index.html because the file is not valid UTF-8.", exception.message
+      assert_equal 'Could not read content/index.html because the file is not valid UTF-8.', exception.message
 
       # Try with encoding = specific
       File.open('nanoc.yaml', 'w') { |io| io.write("meh: true\n") }
@@ -59,5 +70,4 @@ class Nanoc::CLI::Commands::CreateSiteTest < Nanoc::TestCase
       assert_match(/\/stylesheet.css/, File.read('output/index.html'))
     end
   end
-
 end
diff --git a/test/cli/commands/test_deploy.rb b/test/cli/commands/test_deploy.rb
index 3cf20b3..b976d30 100644
--- a/test/cli/commands/test_deploy.rb
+++ b/test/cli/commands/test_deploy.rb
@@ -1,15 +1,14 @@
 # encoding: utf-8
 
 class Nanoc::CLI::Commands::DeployTest < Nanoc::TestCase
-
   def test_deploy
-    skip_unless_have_command "rsync"
-    with_site do |site|
+    skip_unless_have_command 'rsync'
+    with_site do |_site|
       File.open('nanoc.yaml', 'w') do |io|
         io.write "deploy:\n"
         io.write "  public:\n"
         io.write "    kind: rsync\n"
-        io.write "    dst: mydestination"
+        io.write '    dst: mydestination'
       end
 
       FileUtils.mkdir_p('output')
@@ -23,12 +22,12 @@ class Nanoc::CLI::Commands::DeployTest < Nanoc::TestCase
   end
 
   def test_deploy_with_dry_run
-    with_site do |site|
+    with_site do |_site|
       File.open('nanoc.yaml', 'w') do |io|
         io.write "deploy:\n"
         io.write "  public:\n"
         io.write "    kind: rsync\n"
-        io.write "    dst: mydestination"
+        io.write '    dst: mydestination'
       end
 
       FileUtils.mkdir_p('output')
@@ -42,7 +41,7 @@ class Nanoc::CLI::Commands::DeployTest < Nanoc::TestCase
   end
 
   def test_deploy_with_list_without_config
-    with_site do |site|
+    with_site do |_site|
       FileUtils.mkdir_p('output')
       File.open('output/blah.html', 'w') { |io| io.write 'moo' }
 
@@ -58,12 +57,12 @@ class Nanoc::CLI::Commands::DeployTest < Nanoc::TestCase
   end
 
   def test_deploy_with_list
-    with_site do |site|
+    with_site do |_site|
       File.open('nanoc.yaml', 'w') do |io|
         io.write "deploy:\n"
         io.write "  public:\n"
         io.write "    kind: rsync\n"
-        io.write "    dst: mydestination"
+        io.write '    dst: mydestination'
       end
 
       FileUtils.mkdir_p('output')
@@ -81,12 +80,12 @@ class Nanoc::CLI::Commands::DeployTest < Nanoc::TestCase
   end
 
   def test_deploy_with_list_deployers
-    with_site do |site|
+    with_site do |_site|
       File.open('nanoc.yaml', 'w') do |io|
         io.write "deploy:\n"
         io.write "  public:\n"
         io.write "    kind: rsync\n"
-        io.write "    dst: mydestination"
+        io.write '    dst: mydestination'
       end
 
       FileUtils.mkdir_p('output')
@@ -104,12 +103,12 @@ class Nanoc::CLI::Commands::DeployTest < Nanoc::TestCase
   end
 
   def test_deploy_without_kind
-    skip_unless_have_command "rsync"
-    with_site do |site|
+    skip_unless_have_command 'rsync'
+    with_site do |_site|
       File.open('nanoc.yaml', 'w') do |io|
         io.write "deploy:\n"
         io.write "  public:\n"
-        io.write "    dst: mydestination"
+        io.write '    dst: mydestination'
       end
 
       FileUtils.mkdir_p('output')
@@ -127,17 +126,17 @@ class Nanoc::CLI::Commands::DeployTest < Nanoc::TestCase
   end
 
   def test_deploy_without_target_without_default
-    with_site do |site|
+    with_site do |_site|
       File.open('nanoc.yaml', 'w') do |io|
         io.write "deploy:\n"
         io.write "  public:\n"
-        io.write "    dst: mydestination"
+        io.write '    dst: mydestination'
       end
 
       FileUtils.mkdir_p('output')
       File.open('output/blah.html', 'w') { |io| io.write 'moo' }
 
-       capturing_stdio do
+      capturing_stdio do
         err = assert_raises Nanoc::Errors::GenericTrivial do
           Nanoc::CLI.run %w( deploy )
         end
@@ -147,12 +146,12 @@ class Nanoc::CLI::Commands::DeployTest < Nanoc::TestCase
   end
 
   def test_deploy_without_target_with_default
-    skip_unless_have_command "rsync"
-    with_site do |site|
+    skip_unless_have_command 'rsync'
+    with_site do |_site|
       File.open('nanoc.yaml', 'w') do |io|
         io.write "deploy:\n"
         io.write "  default:\n"
-        io.write "    dst: mydestination"
+        io.write '    dst: mydestination'
       end
 
       FileUtils.mkdir_p('output')
@@ -166,5 +165,4 @@ class Nanoc::CLI::Commands::DeployTest < Nanoc::TestCase
       assert File.file?('mydestination/blah.html')
     end
   end
-
 end
diff --git a/test/cli/commands/test_help.rb b/test/cli/commands/test_help.rb
index 73a65e5..f3f029e 100644
--- a/test/cli/commands/test_help.rb
+++ b/test/cli/commands/test_help.rb
@@ -1,10 +1,8 @@
 # encoding: utf-8
 
 class Nanoc::CLI::Commands::HelpTest < Nanoc::TestCase
-
   def test_run
     Nanoc::CLI.run %w( help )
     Nanoc::CLI.run %w( help co )
   end
-
 end
diff --git a/test/cli/commands/test_info.rb b/test/cli/commands/test_info.rb
index 76be666..cd6b457 100644
--- a/test/cli/commands/test_info.rb
+++ b/test/cli/commands/test_info.rb
@@ -1,9 +1,7 @@
 # encoding: utf-8
 
 class Nanoc::CLI::Commands::InfoTest < Nanoc::TestCase
-
   def test_run
     Nanoc::CLI.run %w( info )
   end
-
 end
diff --git a/test/cli/commands/test_prune.rb b/test/cli/commands/test_prune.rb
index ef91bc4..04b1e2e 100644
--- a/test/cli/commands/test_prune.rb
+++ b/test/cli/commands/test_prune.rb
@@ -1,9 +1,8 @@
 # encoding: utf-8
 
 class Nanoc::CLI::Commands::PruneTest < Nanoc::TestCase
-
   def test_run_without_yes
-    with_site do |site|
+    with_site do |_site|
       # Set output dir
       File.open('nanoc.yaml', 'w') { |io| io.write 'output_dir: output2' }
       FileUtils.mkdir_p('output2')
@@ -25,7 +24,7 @@ class Nanoc::CLI::Commands::PruneTest < Nanoc::TestCase
   end
 
   def test_run_with_yes
-    with_site do |site|
+    with_site do |_site|
       # Set output dir
       File.open('nanoc.yaml', 'w') { |io| io.write 'output_dir: output2' }
       FileUtils.mkdir_p('output2')
@@ -45,7 +44,7 @@ class Nanoc::CLI::Commands::PruneTest < Nanoc::TestCase
   end
 
   def test_run_with_dry_run
-    with_site do |site|
+    with_site do |_site|
       # Set output dir
       File.open('nanoc.yaml', 'w') { |io| io.write 'output_dir: output2' }
       FileUtils.mkdir_p('output2')
@@ -65,7 +64,7 @@ class Nanoc::CLI::Commands::PruneTest < Nanoc::TestCase
   end
 
   def test_run_with_exclude
-     with_site do |site|
+    with_site do |_site|
       # Set output dir
       File.open('nanoc.yaml', 'w') { |io| io.write "prune:\n  exclude: [ 'good-dir', 'good-file.html' ]" }
       FileUtils.mkdir_p('output')
@@ -93,12 +92,12 @@ class Nanoc::CLI::Commands::PruneTest < Nanoc::TestCase
   end
 
   def test_run_with_symlink_to_output_dir
-    skip_unless_have_symlink
+    skip_unless_symlinks_supported
     if defined?(JRUBY_VERSION) && JRUBY_VERSION == '1.7.11'
-      skip "JRuby 1.7.11 has buggy File.find behavior (see https://github.com/jruby/jruby/issues/1647)"
+      skip 'JRuby 1.7.11 has buggy File.find behavior (see https://github.com/jruby/jruby/issues/1647)'
     end
 
-    with_site do |site|
+    with_site do |_site|
       # Set output dir
       FileUtils.rm_rf('output')
       FileUtils.mkdir_p('output-real')
@@ -121,7 +120,7 @@ class Nanoc::CLI::Commands::PruneTest < Nanoc::TestCase
   end
 
   def test_run_with_nested_empty_dirs
-    with_site do |site|
+    with_site do |_site|
       # Set output dir
       File.open('nanoc.yaml', 'w') { |io| io.write 'output_dir: output' }
       FileUtils.mkdir_p('output')
@@ -138,5 +137,4 @@ class Nanoc::CLI::Commands::PruneTest < Nanoc::TestCase
       assert !File.directory?('output/a')
     end
   end
-
 end
diff --git a/test/cli/commands/test_sync.rb b/test/cli/commands/test_sync.rb
index 0f60de3..b1590d5 100644
--- a/test/cli/commands/test_sync.rb
+++ b/test/cli/commands/test_sync.rb
@@ -1,5 +1,4 @@
 class Nanoc::CLI::Commands::SyncTest < Nanoc::TestCase
-
   def test_run
     with_site do
       File.open('lib/foo_data_source.rb', 'w') do |io|
@@ -16,7 +15,7 @@ class Nanoc::CLI::Commands::SyncTest < Nanoc::TestCase
       File.open('nanoc.yaml', 'w') do |io|
         io.write "data_sources:\n"
         io.write "  - type: sync_test_foo\n"
-        io.write "    items_root: /"
+        io.write '    items_root: /'
       end
 
       Nanoc::CLI.run %w( sync )
@@ -25,5 +24,4 @@ class Nanoc::CLI::Commands::SyncTest < Nanoc::TestCase
       assert_equal File.read('foo_source_data.yaml'), 'sync: true'
     end
   end
-
 end
diff --git a/test/cli/commands/test_update.rb b/test/cli/commands/test_update.rb
index c8d39da..e8408a7 100644
--- a/test/cli/commands/test_update.rb
+++ b/test/cli/commands/test_update.rb
@@ -1,8 +1,6 @@
 # encoding: utf-8
 
 class Nanoc::CLI::Commands::UpdateTest < Nanoc::TestCase
-
   def test_stub
   end
-
 end
diff --git a/test/cli/commands/test_watch.rb b/test/cli/commands/test_watch.rb
index 3f96e18..c51f79f 100644
--- a/test/cli/commands/test_watch.rb
+++ b/test/cli/commands/test_watch.rb
@@ -1,31 +1,30 @@
 # encoding: utf-8
 
 class Nanoc::CLI::Commands::WatchTest < Nanoc::TestCase
-
   def test_run
-    with_site do |s|
+    with_site do |_s|
       watch_thread = Thread.new do
         Nanoc::CLI.run %w( watch )
       end
 
       File.open('content/index.html', 'w') { |io| io.write('Hello there!') }
-      self.wait_until_content_equals('content/index.html', 'Hello there!')
+      wait_until_content_equals('content/index.html', 'Hello there!')
 
       File.open('content/index.html', 'w') { |io| io.write('Hello there again!') }
-      self.wait_until_content_equals('content/index.html', 'Hello there again!')
+      wait_until_content_equals('content/index.html', 'Hello there again!')
 
       watch_thread.kill
     end
   end
 
   def test_notify
-    with_site do |s|
+    with_site do |_s|
       watch_thread = Thread.new do
         Nanoc::CLI.run %w( watch )
       end
 
       File.open('content/index.html', 'w') { |io| io.write('Hello there!') }
-      self.wait_until_exists('output/index.html')
+      wait_until_exists('output/index.html')
       assert_equal 'Hello there!', File.read('output/index.html')
 
       watch_thread.kill
@@ -35,13 +34,13 @@ class Nanoc::CLI::Commands::WatchTest < Nanoc::TestCase
   def test_growlnotify_cmd
     Nanoc::CLI.setup
     notifier = Nanoc::CLI::Commands::Watch::Notifier.new
-    assert_equal [ 'growlnotify', '-m', 'foo' ], notifier.send(:growlnotify_cmd_for, 'foo')
+    assert_equal ['growlnotify', '-m', 'foo'], notifier.send(:growlnotify_cmd_for, 'foo')
   end
 
   def test_growlnotify_windows_cmd
     Nanoc::CLI.setup
     notifier = Nanoc::CLI::Commands::Watch::Notifier.new
-    assert_equal [ 'growlnotify', '/t:nanoc', 'foo' ], notifier.send(:growlnotify_windows_cmd_for, 'foo')
+    assert_equal ['growlnotify', '/t:nanoc', 'foo'], notifier.send(:growlnotify_windows_cmd_for, 'foo')
   end
 
   def wait_until_exists(filename)
@@ -49,13 +48,13 @@ class Nanoc::CLI::Commands::WatchTest < Nanoc::TestCase
       break if File.file?(filename)
       sleep 0.5
     end
-    if !File.file?(filename)
-      raise RuntimeError, "Expected #{filename} to appear but it didn't :("
+    unless File.file?(filename)
+      raise "Expected #{filename} to appear but it didn't :("
     end
   end
 
   def wait_until_content_equals(filename, expected_content)
-    self.wait_until_exists(filename)
+    wait_until_exists(filename)
 
     20.times do
       break if File.read(filename) == expected_content
@@ -64,7 +63,7 @@ class Nanoc::CLI::Commands::WatchTest < Nanoc::TestCase
 
     actual_content = File.read(filename)
     if actual_content != expected_content
-      raise RuntimeError, "Expected #{filename} to have " \
+      raise "Expected #{filename} to have " \
         "content #{expected_content.inspect} but it had " \
         "content #{actual_content.inspect} instead :("
     end
@@ -72,5 +71,4 @@ class Nanoc::CLI::Commands::WatchTest < Nanoc::TestCase
     # Ugly, but seems to be necessary or changes are not picked up. :(
     sleep 0.5
   end
-
 end
diff --git a/test/cli/test_cleaning_stream.rb b/test/cli/test_cleaning_stream.rb
index 67a7fc5..a98c142 100644
--- a/test/cli/test_cleaning_stream.rb
+++ b/test/cli/test_cleaning_stream.rb
@@ -1,23 +1,20 @@
 # encoding: utf-8
 
 class Nanoc::CLI::CleaningStreamTest < Nanoc::TestCase
-
   class Stream
-
     attr_accessor :called_methods
 
     def initialize
       @called_methods = Set.new
     end
 
-    def method_missing(symbol, *args)
+    def method_missing(symbol, *_args)
       @called_methods << symbol
     end
-
   end
 
   def test_forward
-    methods = [ :write, :<<, :tty?, :flush, :tell, :print, :puts, :string, :reopen, :exist?, :exists?, :close ]
+    methods = [:write, :<<, :tty?, :flush, :tell, :print, :puts, :string, :reopen, :exist?, :exists?, :close]
 
     s = Stream.new
     cs = Nanoc::CLI::CleaningStream.new(s)
@@ -45,16 +42,17 @@ class Nanoc::CLI::CleaningStreamTest < Nanoc::TestCase
     stream = StringIO.new
     cleaning_stream = Nanoc::CLI::CleaningStream.new(stream)
     logger = Logger.new(cleaning_stream)
-    logger.info("Some info")
-    logger.warn("Something could start going wrong!")
+    logger.info('Some info')
+    logger.warn('Something could start going wrong!')
   end
 
   def test_broken_pipe
     stream = StringIO.new
-    def stream.write(s) ; raise Errno::EPIPE.new ; end
+    def stream.write(_s)
+      raise Errno::EPIPE.new
+    end
 
     cleaning_stream = Nanoc::CLI::CleaningStream.new(stream)
     cleaning_stream.write('lol')
   end
-
 end
diff --git a/test/cli/test_cli.rb b/test/cli/test_cli.rb
index 8381292..cd10f35 100644
--- a/test/cli/test_cli.rb
+++ b/test/cli/test_cli.rb
@@ -1,7 +1,6 @@
 # encoding: utf-8
 
 class Nanoc::CLITest < Nanoc::TestCase
-
   COMMAND_CODE = <<EOS
 # encoding: utf-8
 
@@ -75,6 +74,36 @@ EOS
     end
   end
 
+  def test_load_custom_commands_non_default_commands_dirs
+    Nanoc::CLI.run %w( create_site foo )
+    FileUtils.cd('foo') do
+      File.open('nanoc.yaml', 'w') { |io| io.write('commands_dirs: [commands, commands_alt]') }
+
+      # Create command
+      FileUtils.mkdir_p('commands_alt')
+      File.open('commands_alt/_test.rb', 'w') do |io|
+        io.write(COMMAND_CODE)
+      end
+
+      # Create subcommand
+      FileUtils.mkdir_p('commands_alt/_test')
+      File.open('commands_alt/_test/_sub.rb', 'w') do |io|
+        io.write(SUBCOMMAND_CODE)
+      end
+
+      # Run command
+      begin
+        Nanoc::CLI.run %w( _test _sub )
+      rescue SystemExit
+        assert false, 'Running _test sub should not cause system exit'
+      end
+
+      # Check
+      assert File.file?('_test_sub.out')
+      assert_equal 'It works sub!', File.read('_test_sub.out')
+    end
+  end
+
   def test_load_custom_commands_broken
     Nanoc::CLI.run %w( create_site foo )
 
@@ -119,18 +148,24 @@ EOS
     }
     with_env_vars(new_env_diff) do
       io = StringIO.new
-      def io.tty? ; true ; end
+      def io.tty?
+        true
+      end
       refute Nanoc::CLI.enable_utf8?(io)
 
       io = StringIO.new
-      def io.tty? ; false ; end
+      def io.tty?
+        false
+      end
       assert Nanoc::CLI.enable_utf8?(io)
     end
   end
 
   def test_enable_utf8
     io = StringIO.new
-    def io.tty? ; true ; end
+    def io.tty?
+      true
+    end
 
     new_env_diff = {
       'LC_ALL'   => 'en_US.ISO-8859-1',
@@ -153,5 +188,4 @@ EOS
       with_env_vars({ 'LANG'     => 'en_US.utf8'  }) { assert Nanoc::CLI.enable_utf8?(io) }
     end
   end
-
 end
diff --git a/test/cli/test_error_handler.rb b/test/cli/test_error_handler.rb
index ab0db9e..ea50d8f 100644
--- a/test/cli/test_error_handler.rb
+++ b/test/cli/test_error_handler.rb
@@ -1,7 +1,6 @@
 # encoding: utf-8
 
 class Nanoc::CLI::ErrorHandlerTest < Nanoc::TestCase
-
   def setup
     super
     @handler = Nanoc::CLI::ErrorHandler.new
@@ -13,13 +12,17 @@ class Nanoc::CLI::ErrorHandlerTest < Nanoc::TestCase
   end
 
   def test_resolution_for_with_known_gem_without_bundler
-    def @handler.using_bundler? ; false ; end
+    def @handler.using_bundler?
+      false
+    end
     error = LoadError.new('no such file to load -- kramdown')
     assert_match(/^Install the 'kramdown' gem using `gem install kramdown`./, @handler.send(:resolution_for, error))
   end
 
   def test_resolution_for_with_known_gem_with_bundler
-    def @handler.using_bundler? ; true ; end
+    def @handler.using_bundler?
+      true
+    end
     error = LoadError.new('no such file to load -- kramdown')
     assert_match(/^Make sure the gem is added to Gemfile/, @handler.send(:resolution_for, error))
   end
@@ -33,22 +36,22 @@ class Nanoc::CLI::ErrorHandlerTest < Nanoc::TestCase
     error = new_error(20)
 
     stream = StringIO.new
-    @handler.send(:write_stack_trace, stream, error, :verbose => false)
+    @handler.send(:write_stack_trace, stream, error, verbose: false)
     assert_match(/See full crash log for details./, stream.string)
 
     stream = StringIO.new
-    @handler.send(:write_stack_trace, stream, error, :verbose => false)
+    @handler.send(:write_stack_trace, stream, error, verbose: false)
     assert_match(/See full crash log for details./, stream.string)
 
     stream = StringIO.new
-    @handler.send(:write_stack_trace, stream, error, :verbose => true)
+    @handler.send(:write_stack_trace, stream, error, verbose: true)
     refute_match(/See full crash log for details./, stream.string)
   end
 
   def new_error(amount_factor)
     backtrace_generator = lambda do |af|
       if af == 0
-        raise "finally!"
+        raise 'finally!'
       else
         backtrace_generator.call(af - 1)
       end
@@ -60,5 +63,4 @@ class Nanoc::CLI::ErrorHandlerTest < Nanoc::TestCase
       return e
     end
   end
-
 end
diff --git a/test/cli/test_logger.rb b/test/cli/test_logger.rb
index 9f6324f..4c5cd12 100644
--- a/test/cli/test_logger.rb
+++ b/test/cli/test_logger.rb
@@ -1,8 +1,6 @@
 # encoding: utf-8
 
 class Nanoc::CLI::LoggerTest < Nanoc::TestCase
-
   def test_stub
   end
-
 end
diff --git a/test/data_sources/test_filesystem.rb b/test/data_sources/test_filesystem.rb
index 1731a77..b218db4 100644
--- a/test/data_sources/test_filesystem.rb
+++ b/test/data_sources/test_filesystem.rb
@@ -1,7 +1,6 @@
 # encoding: utf-8
 
 class Nanoc::DataSources::FilesystemTest < Nanoc::TestCase
-
   class SampleFilesystemDataSource < Nanoc::DataSource
     include Nanoc::DataSources::Filesystem
   end
@@ -71,7 +70,7 @@ class Nanoc::DataSources::FilesystemTest < Nanoc::TestCase
 
   def test_all_split_files_in_allowing_periods_in_identifiers
     # Create data source
-    data_source = Nanoc::DataSources::FilesystemCompact.new(nil, nil, nil, { :allow_periods_in_identifiers => true })
+    data_source = Nanoc::DataSources::FilesystemCompact.new(nil, nil, nil, { allow_periods_in_identifiers: true })
 
     # Write sample files
     FileUtils.mkdir_p('foo')
@@ -86,9 +85,9 @@ class Nanoc::DataSources::FilesystemTest < Nanoc::TestCase
 
     # Get all files
     output_expected = {
-      './foo'       => [ 'yaml', 'html' ],
-      './bar.entry' => [ nil,    'html' ],
-      './foo/qux'   => [ 'yaml', nil    ]
+      './foo'       => %w(yaml html),
+      './bar.entry' => [nil,    'html'],
+      './foo/qux'   => ['yaml', nil]
     }
     output_actual = data_source.send :all_split_files_in, '.'
 
@@ -113,9 +112,9 @@ class Nanoc::DataSources::FilesystemTest < Nanoc::TestCase
 
     # Get all files
     output_expected = {
-      './foo'       => [ 'yaml', 'html'     ],
-      './bar'       => [ nil,    'html.erb' ],
-      './foo/qux'   => [ 'yaml', nil        ]
+      './foo'       => %w(yaml html),
+      './bar'       => [nil,    'html.erb'],
+      './foo/qux'   => ['yaml', nil]
     }
     output_actual = data_source.send :all_split_files_in, '.'
 
@@ -135,9 +134,9 @@ class Nanoc::DataSources::FilesystemTest < Nanoc::TestCase
 
     # Check
     expected = {
-      './aaa/foo' => [ nil, 'html' ],
-      './bbb/foo' => [ nil, 'html' ],
-      './ccc/foo' => [ nil, 'html' ]
+      './aaa/foo' => [nil, 'html'],
+      './bbb/foo' => [nil, 'html'],
+      './ccc/foo' => [nil, 'html']
     }
     assert_equal expected, data_source.send(:all_split_files_in, '.')
   end
@@ -159,7 +158,7 @@ class Nanoc::DataSources::FilesystemTest < Nanoc::TestCase
 
   def test_basename_of_allowing_periods_in_identifiers
     # Create data source
-    data_source = Nanoc::DataSources::FilesystemCompact.new(nil, nil, nil, { :allow_periods_in_identifiers => true })
+    data_source = Nanoc::DataSources::FilesystemCompact.new(nil, nil, nil, { allow_periods_in_identifiers: true })
 
     # Get input and expected output
     expected = {
@@ -219,7 +218,7 @@ class Nanoc::DataSources::FilesystemTest < Nanoc::TestCase
 
   def test_ext_of_allowing_periods_in_identifiers
     # Create data source
-    data_source = Nanoc::DataSources::FilesystemCompact.new(nil, nil, nil, { :allow_periods_in_identifiers => true })
+    data_source = Nanoc::DataSources::FilesystemCompact.new(nil, nil, nil, { allow_periods_in_identifiers: true })
 
     # Get input and expected output
     expected = {
@@ -291,7 +290,7 @@ class Nanoc::DataSources::FilesystemTest < Nanoc::TestCase
     # Parse it
     result = data_source.instance_eval { parse('test.html', nil, 'foobar') }
     assert_equal({ 'foo' => 'bar' }, result[0])
-    assert_equal("", result[1])
+    assert_equal('', result[1])
   end
 
   def test_parse_embedded_meta_only_2
@@ -308,7 +307,7 @@ class Nanoc::DataSources::FilesystemTest < Nanoc::TestCase
     # Parse it
     result = data_source.instance_eval { parse('test.html', nil, 'foobar') }
     assert_equal({ 'foo' => 'bar' }, result[0])
-    assert_equal("", result[1])
+    assert_equal('', result[1])
   end
 
   def test_parse_embedded_meta_only_3
@@ -316,7 +315,7 @@ class Nanoc::DataSources::FilesystemTest < Nanoc::TestCase
     File.open('test.html', 'w') do |io|
       io.write "-----\r\n"
       io.write "foo: bar\n"
-      io.write "-----"
+      io.write '-----'
     end
 
     # Create data source
@@ -325,7 +324,7 @@ class Nanoc::DataSources::FilesystemTest < Nanoc::TestCase
     # Parse it
     result = data_source.instance_eval { parse('test.html', nil, 'foobar') }
     assert_equal({ 'foo' => 'bar' }, result[0])
-    assert_equal("", result[1])
+    assert_equal('', result[1])
   end
 
   def test_parse_embedded_invalid_2
@@ -403,7 +402,7 @@ class Nanoc::DataSources::FilesystemTest < Nanoc::TestCase
       io.write "-----\n"
       io.write "-----\n"
       io.write "\nblah blah\n"
-      io.write "-----"
+      io.write '-----'
     end
 
     # Create data source
@@ -417,14 +416,14 @@ class Nanoc::DataSources::FilesystemTest < Nanoc::TestCase
 
   def test_parse_utf8_bom
     File.open('test.html', 'w') do |io|
-      io.write [ 0xEF, 0xBB, 0xBF ].map { |i| i.chr }.join
+      io.write [0xEF, 0xBB, 0xBF].map(&:chr).join
       io.write "-----\n"
       io.write "utf8bomawareness: high\n"
       io.write "-----\n"
       io.write "content goes here\n"
     end
 
-    data_source = Nanoc::DataSources::FilesystemCombined.new(nil, nil, nil, :encoding => 'utf-8')
+    data_source = Nanoc::DataSources::FilesystemCombined.new(nil, nil, nil, encoding: 'utf-8')
 
     result = data_source.instance_eval { parse('test.html', nil, 'foobar') }
     assert_equal({ 'utf8bomawareness' => 'high' }, result[0])
@@ -468,16 +467,15 @@ class Nanoc::DataSources::FilesystemTest < Nanoc::TestCase
 
   def test_parse_external
     # Create a file
-    File.open('test.html', 'w') { |io| io.write("blah blah") }
-    File.open('test.yaml', 'w') { |io| io.write("foo: bar") }
+    File.open('test.html', 'w') { |io| io.write('blah blah') }
+    File.open('test.yaml', 'w') { |io| io.write('foo: bar') }
 
     # Create data source
     data_source = Nanoc::DataSources::FilesystemCombined.new(nil, nil, nil, nil)
 
     # Parse it
     result = data_source.instance_eval { parse('test.html', 'test.yaml', 'foobar') }
-    assert_equal({ "foo" => "bar"}, result[0])
-    assert_equal("blah blah",       result[1])
+    assert_equal({ 'foo' => 'bar' }, result[0])
+    assert_equal('blah blah',       result[1])
   end
-
 end
diff --git a/test/data_sources/test_filesystem_unified.rb b/test/data_sources/test_filesystem_unified.rb
index 21c499a..400b407 100644
--- a/test/data_sources/test_filesystem_unified.rb
+++ b/test/data_sources/test_filesystem_unified.rb
@@ -1,8 +1,7 @@
 # encoding: utf-8
 
 class Nanoc::DataSources::FilesystemUnifiedTest < Nanoc::TestCase
-
-  def new_data_source(params=nil)
+  def new_data_source(params = nil)
     # Mock site
     site = Nanoc::Site.new({})
 
@@ -16,7 +15,7 @@ class Nanoc::DataSources::FilesystemUnifiedTest < Nanoc::TestCase
   def test_create_object_not_at_root
     # Create item
     data_source = new_data_source
-    data_source.send(:create_object, 'foobar', 'content here', { :foo => 'bar' }, '/asdf/')
+    data_source.send(:create_object, 'foobar', 'content here', { foo: 'bar' }, '/asdf/')
 
     # Check file existance
     assert File.directory?('foobar')
@@ -32,7 +31,7 @@ class Nanoc::DataSources::FilesystemUnifiedTest < Nanoc::TestCase
   def test_create_object_at_root
     # Create item
     data_source = new_data_source
-    data_source.send(:create_object, 'foobar', 'content here', { :foo => 'bar' }, '/')
+    data_source.send(:create_object, 'foobar', 'content here', { foo: 'bar' }, '/')
 
     # Check file existance
     assert File.directory?('foobar')
@@ -55,6 +54,7 @@ class Nanoc::DataSources::FilesystemUnifiedTest < Nanoc::TestCase
       def initialize(*stuff)
         @stuff = stuff
       end
+
       def ==(other)
         @stuff == other.stuff
       end
@@ -77,32 +77,32 @@ class Nanoc::DataSources::FilesystemUnifiedTest < Nanoc::TestCase
         'test 1',
         { 'num' => 1, :filename => 'foo/bar.html',   :extension => 'html', :file => File.open('foo/bar.html') },
         '/bar/',
-        :binary => false, :mtime => File.mtime('foo/bar.html')
+        binary: false, mtime: File.mtime('foo/bar.html')
       ),
       klass.new(
         'test 2',
         { 'num' => 2, :filename => 'foo/b.c.html',   :extension => 'c.html', :file => File.open('foo/b.c.html') },
         '/b/',
-        :binary => false, :mtime => File.mtime('foo/b.c.html')
+        binary: false, mtime: File.mtime('foo/b.c.html')
       ),
       klass.new(
         'test 3',
         { 'num' => 3, :filename => 'foo/a/b/c.html', :extension => 'html', :file => File.open('foo/a/b/c.html') },
         '/a/b/c/',
-        :binary => false, :mtime => File.mtime('foo/a/b/c.html')
+        binary: false, mtime: File.mtime('foo/a/b/c.html')
       )
     ]
     actual_out = data_source.send(:load_objects, 'foo', 'The Foo', klass).sort_by { |i| i.stuff[0] }
 
     # Check
-    (0..expected_out.size-1).each do |i|
+    (0..expected_out.size - 1).each do |i|
       assert_equal expected_out[i].stuff[0], actual_out[i].stuff[0], 'content must match'
       assert_equal expected_out[i].stuff[2], actual_out[i].stuff[2], 'identifier must match'
       assert_equal expected_out[i].stuff[3][:mtime], actual_out[i].stuff[3][:mtime], 'mtime must match'
       assert_equal expected_out[i].stuff[1][:file].path, actual_out[i].stuff[1][:file].path, 'file paths must match'
-      expected_out[i].stuff[1][:file].close;
+      expected_out[i].stuff[1][:file].close
       actual_out[i].stuff[1][:file].close
-      [ 'num', :filename, :extension ].each do |key|
+      ['num', :filename, :extension].each do |key|
         assert_equal expected_out[i].stuff[1][key], actual_out[i].stuff[1][key], "attribute key #{key} must match"
       end
     end
@@ -114,7 +114,7 @@ class Nanoc::DataSources::FilesystemUnifiedTest < Nanoc::TestCase
 
     # Create sample files
     FileUtils.mkdir_p('foo')
-    File.open('foo/stuff.dat', 'w') { |io| io.write("random binary data") }
+    File.open('foo/stuff.dat', 'w') { |io| io.write('random binary data') }
 
     # Load
     items = data_source.send(:load_objects, 'foo', 'item', Nanoc::Item)
@@ -132,7 +132,7 @@ class Nanoc::DataSources::FilesystemUnifiedTest < Nanoc::TestCase
 
     # Create sample files
     FileUtils.mkdir_p('foo')
-    File.open('foo/stuff.dat', 'w') { |io| io.write("random binary data") }
+    File.open('foo/stuff.dat', 'w') { |io| io.write('random binary data') }
 
     # Load
     assert_raises(RuntimeError) do
@@ -142,7 +142,7 @@ class Nanoc::DataSources::FilesystemUnifiedTest < Nanoc::TestCase
 
   def test_identifier_for_filename_allowing_periods_in_identifiers
     # Create data source
-    data_source = new_data_source(:allow_periods_in_identifiers => true)
+    data_source = new_data_source(allow_periods_in_identifiers: true)
 
     # Get input and expected output
     expected = {
@@ -198,10 +198,10 @@ class Nanoc::DataSources::FilesystemUnifiedTest < Nanoc::TestCase
       'foo/blah_index.yaml'  => '/foo/blah_index/'
     }
 
-    data_source = new_data_source(:allow_periods_in_identifiers => true)
+    data_source = new_data_source(allow_periods_in_identifiers: true)
     expectations.each_pair do |meta_filename, expected_identifier|
       content_filename = meta_filename.sub(/yaml$/, 'html')
-      [ meta_filename, content_filename ].each do |filename|
+      [meta_filename, content_filename].each do |filename|
         assert_equal(
           expected_identifier,
           data_source.instance_eval { identifier_for_filename(filename) }
@@ -227,7 +227,7 @@ class Nanoc::DataSources::FilesystemUnifiedTest < Nanoc::TestCase
     data_source = new_data_source
     expectations.each_pair do |meta_filename, expected_identifier|
       content_filename = meta_filename.sub(/yaml$/, 'html')
-      [ meta_filename, content_filename ].each do |filename|
+      [meta_filename, content_filename].each do |filename|
         assert_equal(
           expected_identifier,
           data_source.instance_eval { identifier_for_filename(filename) }
@@ -246,7 +246,7 @@ class Nanoc::DataSources::FilesystemUnifiedTest < Nanoc::TestCase
       '/foo/index'            => '/foo/',
     }
 
-    data_source = new_data_source(:allow_periods_in_identifiers => true)
+    data_source = new_data_source(allow_periods_in_identifiers: true)
     expected.each_pair do |input, expected_output|
       actual_output = data_source.send(:identifier_for_filename, input)
       assert_equal(
@@ -278,7 +278,7 @@ class Nanoc::DataSources::FilesystemUnifiedTest < Nanoc::TestCase
 
   def test_load_objects_allowing_periods_in_identifiers
     # Create data source
-    data_source = new_data_source(:allow_periods_in_identifiers => true)
+    data_source = new_data_source(allow_periods_in_identifiers: true)
 
     # Create a fake class
     klass = Class.new do
@@ -286,6 +286,7 @@ class Nanoc::DataSources::FilesystemUnifiedTest < Nanoc::TestCase
       def initialize(*stuff)
         @stuff = stuff
       end
+
       def ==(other)
         @stuff == other.stuff
       end
@@ -296,13 +297,13 @@ class Nanoc::DataSources::FilesystemUnifiedTest < Nanoc::TestCase
     FileUtils.mkdir_p('foo/a/b')
     File.open('foo/a/b/c.yaml',     'w') { |io| io.write("---\nnum: 1\n") }
     File.open('foo/b.c.yaml',       'w') { |io| io.write("---\nnum: 2\n") }
-    File.open('foo/b.c.html',       'w') { |io| io.write("test 2")        }
-    File.open('foo/car.html',       'w') { |io| io.write("test 3")        }
-    File.open('foo/ugly.yaml~',     'w') { |io| io.write("blah")          }
-    File.open('foo/ugly.html~',     'w') { |io| io.write("blah")          }
-    File.open('foo/ugly.html.orig', 'w') { |io| io.write("blah")          }
-    File.open('foo/ugly.html.rej',  'w') { |io| io.write("blah")          }
-    File.open('foo/ugly.html.bak',  'w') { |io| io.write("blah")          }
+    File.open('foo/b.c.html',       'w') { |io| io.write('test 2')        }
+    File.open('foo/car.html',       'w') { |io| io.write('test 3')        }
+    File.open('foo/ugly.yaml~',     'w') { |io| io.write('blah')          }
+    File.open('foo/ugly.html~',     'w') { |io| io.write('blah')          }
+    File.open('foo/ugly.html.orig', 'w') { |io| io.write('blah')          }
+    File.open('foo/ugly.html.rej',  'w') { |io| io.write('blah')          }
+    File.open('foo/ugly.html.bak',  'w') { |io| io.write('blah')          }
 
     # Get expected output
     expected_out = [
@@ -316,7 +317,7 @@ class Nanoc::DataSources::FilesystemUnifiedTest < Nanoc::TestCase
           :file             => nil
         },
         '/a/b/c/',
-        :binary => false, :mtime => File.mtime('foo/a/b/c.yaml')
+        binary: false, mtime: File.mtime('foo/a/b/c.yaml')
       ),
       klass.new(
         'test 2',
@@ -328,18 +329,18 @@ class Nanoc::DataSources::FilesystemUnifiedTest < Nanoc::TestCase
           :file             => File.open('foo/b.c.html')
         },
         '/b.c/',
-        :binary => false, :mtime => File.mtime('foo/b.c.html') > File.mtime('foo/b.c.yaml') ? File.mtime('foo/b.c.html') : File.mtime('foo/b.c.yaml')
+        binary: false, mtime: File.mtime('foo/b.c.html') > File.mtime('foo/b.c.yaml') ? File.mtime('foo/b.c.html') : File.mtime('foo/b.c.yaml')
       ),
       klass.new(
         'test 3',
         {
-          :content_filename => 'foo/car.html',
-          :meta_filename    => nil,
-          :extension        => 'html',
-          :file             => File.open('foo/car.html')
+          content_filename: 'foo/car.html',
+          meta_filename: nil,
+          extension: 'html',
+          file: File.open('foo/car.html')
         },
         '/car/',
-        :binary => false, :mtime => File.mtime('foo/car.html')
+        binary: false, mtime: File.mtime('foo/car.html')
       )
     ]
 
@@ -347,7 +348,7 @@ class Nanoc::DataSources::FilesystemUnifiedTest < Nanoc::TestCase
     actual_out = data_source.send(:load_objects, 'foo', 'The Foo', klass).sort_by { |i| i.stuff[2] }
 
     # Check
-    (0..expected_out.size-1).each do |i|
+    (0..expected_out.size - 1).each do |i|
       assert_equal expected_out[i].stuff[0], actual_out[i].stuff[0], 'content must match'
       assert_equal expected_out[i].stuff[2], actual_out[i].stuff[2], 'identifier must match'
       assert_equal expected_out[i].stuff[3][:mtime], actual_out[i].stuff[3][:mtime], 'mtime must match'
@@ -358,7 +359,7 @@ class Nanoc::DataSources::FilesystemUnifiedTest < Nanoc::TestCase
       actual_file.close unless actual_file.nil?
       expected_file.close unless expected_file.nil?
 
-      [ 'num', :content_filename, :meta_filename, :extension ].each do |key|
+      ['num', :content_filename, :meta_filename, :extension].each do |key|
         assert_equal expected_out[i].stuff[1][key], actual_out[i].stuff[1][key], "attribute key #{key} must match"
       end
     end
@@ -374,6 +375,7 @@ class Nanoc::DataSources::FilesystemUnifiedTest < Nanoc::TestCase
       def initialize(*stuff)
         @stuff = stuff
       end
+
       def ==(other)
         @stuff == other.stuff
       end
@@ -384,13 +386,13 @@ class Nanoc::DataSources::FilesystemUnifiedTest < Nanoc::TestCase
     FileUtils.mkdir_p('foo/a/b')
     File.open('foo/a/b/c.yaml',     'w') { |io| io.write("---\nnum: 1\n") }
     File.open('foo/b.yaml',         'w') { |io| io.write("---\nnum: 2\n") }
-    File.open('foo/b.html.erb',     'w') { |io| io.write("test 2")        }
-    File.open('foo/car.html',       'w') { |io| io.write("test 3")        }
-    File.open('foo/ugly.yaml~',     'w') { |io| io.write("blah")          }
-    File.open('foo/ugly.html~',     'w') { |io| io.write("blah")          }
-    File.open('foo/ugly.html.orig', 'w') { |io| io.write("blah")          }
-    File.open('foo/ugly.html.rej',  'w') { |io| io.write("blah")          }
-    File.open('foo/ugly.html.bak',  'w') { |io| io.write("blah")          }
+    File.open('foo/b.html.erb',     'w') { |io| io.write('test 2')        }
+    File.open('foo/car.html',       'w') { |io| io.write('test 3')        }
+    File.open('foo/ugly.yaml~',     'w') { |io| io.write('blah')          }
+    File.open('foo/ugly.html~',     'w') { |io| io.write('blah')          }
+    File.open('foo/ugly.html.orig', 'w') { |io| io.write('blah')          }
+    File.open('foo/ugly.html.rej',  'w') { |io| io.write('blah')          }
+    File.open('foo/ugly.html.bak',  'w') { |io| io.write('blah')          }
 
     # Get expected output
     expected_out = [
@@ -404,7 +406,7 @@ class Nanoc::DataSources::FilesystemUnifiedTest < Nanoc::TestCase
           :file             => nil
         },
         '/a/b/c/',
-        :binary => false, :mtime => File.mtime('foo/a/b/c.yaml')
+        binary: false, mtime: File.mtime('foo/a/b/c.yaml')
       ),
       klass.new(
         'test 2',
@@ -416,18 +418,18 @@ class Nanoc::DataSources::FilesystemUnifiedTest < Nanoc::TestCase
           :file             => File.open('foo/b.html.erb')
         },
         '/b/',
-        :binary => false, :mtime => File.mtime('foo/b.html.erb') > File.mtime('foo/b.yaml') ? File.mtime('foo/b.html.erb') : File.mtime('foo/b.yaml')
+        binary: false, mtime: File.mtime('foo/b.html.erb') > File.mtime('foo/b.yaml') ? File.mtime('foo/b.html.erb') : File.mtime('foo/b.yaml')
       ),
       klass.new(
         'test 3',
         {
-          :content_filename => 'foo/car.html',
-          :meta_filename    => nil,
-          :extension        => 'html',
-          :file             => File.open('foo/car.html')
+          content_filename: 'foo/car.html',
+          meta_filename: nil,
+          extension: 'html',
+          file: File.open('foo/car.html')
         },
         '/car/',
-        :binary => false, :mtime => File.mtime('foo/car.html')
+        binary: false, mtime: File.mtime('foo/car.html')
       )
     ]
 
@@ -435,7 +437,7 @@ class Nanoc::DataSources::FilesystemUnifiedTest < Nanoc::TestCase
     actual_out = data_source.send(:load_objects, 'foo', 'The Foo', klass).sort_by { |i| i.stuff[2] }
 
     # Check
-    (0..expected_out.size-1).each do |i|
+    (0..expected_out.size - 1).each do |i|
       assert_equal expected_out[i].stuff[0], actual_out[i].stuff[0], 'content must match'
       assert_equal expected_out[i].stuff[2], actual_out[i].stuff[2], 'identifier must match'
       assert_equal expected_out[i].stuff[3][:mtime], actual_out[i].stuff[3][:mtime], 'mtime must match'
@@ -446,7 +448,7 @@ class Nanoc::DataSources::FilesystemUnifiedTest < Nanoc::TestCase
       actual_file.close unless actual_file.nil?
       expected_file.close unless expected_file.nil?
 
-      [ 'num', :content_filename, :meta_filename, :extension ].each do |key|
+      ['num', :content_filename, :meta_filename, :extension].each do |key|
         assert_equal expected_out[i].stuff[1][key], actual_out[i].stuff[1][key], "attribute key #{key} must match"
       end
     end
@@ -454,17 +456,17 @@ class Nanoc::DataSources::FilesystemUnifiedTest < Nanoc::TestCase
 
   def test_create_object_allowing_periods_in_identifiers
     # Create data source
-    data_source = new_data_source(:allow_periods_in_identifiers => true)
+    data_source = new_data_source(allow_periods_in_identifiers: true)
 
     # Create object without period
-    data_source.send(:create_object, 'foo', 'some content', { :some => 'attributes' }, '/asdf/')
+    data_source.send(:create_object, 'foo', 'some content', { some: 'attributes' }, '/asdf/')
     assert File.file?('foo/asdf.html')
     data = data_source.send(:parse, 'foo/asdf.html', nil, 'moo')
     assert_equal({ 'some' => 'attributes' }, data[0])
     assert_equal('some content',             data[1])
 
     # Create object with period
-    data_source.send(:create_object, 'foo', 'some content', { :some => 'attributes' }, '/as.df/')
+    data_source.send(:create_object, 'foo', 'some content', { some: 'attributes' }, '/as.df/')
     assert File.file?('foo/as.df.html')
     data = data_source.send(:parse, 'foo/as.df.html', nil, 'moo')
     assert_equal({ 'some' => 'attributes' }, data[0])
@@ -476,7 +478,7 @@ class Nanoc::DataSources::FilesystemUnifiedTest < Nanoc::TestCase
     data_source = new_data_source
 
     # Create object without period
-    data_source.send(:create_object, 'foo', 'some content', { :some => 'attributes' }, '/asdf/')
+    data_source.send(:create_object, 'foo', 'some content', { some: 'attributes' }, '/asdf/')
     assert File.file?('foo/asdf.html')
     data = data_source.send(:parse, 'foo/asdf.html', nil, 'moo')
     assert_equal({ 'some' => 'attributes' }, data[0])
@@ -484,7 +486,7 @@ class Nanoc::DataSources::FilesystemUnifiedTest < Nanoc::TestCase
 
     # Create object with period
     assert_raises(RuntimeError) do
-      data_source.send(:create_object, 'foo', 'some content', { :some => 'attributes' }, '/as.df/')
+      data_source.send(:create_object, 'foo', 'some content', { some: 'attributes' }, '/as.df/')
     end
   end
 
@@ -517,8 +519,8 @@ class Nanoc::DataSources::FilesystemUnifiedTest < Nanoc::TestCase
 
   def test_compile_iso_8859_1_site
     # Check encoding
-    if !''.respond_to?(:encode)
-      skip "Test only works on 1.9.x"
+    unless ''.respond_to?(:encode)
+      skip 'Test only works on 1.9.x'
       return
     end
 
@@ -526,7 +528,7 @@ class Nanoc::DataSources::FilesystemUnifiedTest < Nanoc::TestCase
     data_source = new_data_source
 
     # Create item
-    data_source.create_item("Hëllö", {}, '/foo/')
+    data_source.create_item('Hëllö', {}, '/foo/')
 
     # Parse
     begin
@@ -536,7 +538,7 @@ class Nanoc::DataSources::FilesystemUnifiedTest < Nanoc::TestCase
       items = data_source.items
 
       assert_equal 1, items.size
-      assert_equal Encoding.find("UTF-8"), items[0].raw_content.encoding
+      assert_equal Encoding.find('UTF-8'), items[0].raw_content.encoding
     ensure
       Encoding.default_external = original_default_external_encoding
     end
@@ -544,8 +546,8 @@ class Nanoc::DataSources::FilesystemUnifiedTest < Nanoc::TestCase
 
   def test_compile_iso_8859_1_site_with_explicit_encoding
     # Check encoding
-    if !''.respond_to?(:encode)
-      skip "Test only works on 1.9.x"
+    unless ''.respond_to?(:encode)
+      skip 'Test only works on 1.9.x'
       return
     end
 
@@ -558,7 +560,7 @@ class Nanoc::DataSources::FilesystemUnifiedTest < Nanoc::TestCase
       original_default_external_encoding = Encoding.default_external
       Encoding.default_external = 'ISO-8859-1'
 
-      data_source.create_item("Hëllö", {}, '/foo/')
+      data_source.create_item('Hëllö', {}, '/foo/')
     ensure
       Encoding.default_external = original_default_external_encoding
     end
@@ -566,7 +568,6 @@ class Nanoc::DataSources::FilesystemUnifiedTest < Nanoc::TestCase
     # Parse
     items = data_source.items
     assert_equal 1, items.size
-    assert_equal Encoding.find("UTF-8"), items[0].raw_content.encoding
+    assert_equal Encoding.find('UTF-8'), items[0].raw_content.encoding
   end
-
 end
diff --git a/test/data_sources/test_filesystem_verbose.rb b/test/data_sources/test_filesystem_verbose.rb
index df6acfe..5300874 100644
--- a/test/data_sources/test_filesystem_verbose.rb
+++ b/test/data_sources/test_filesystem_verbose.rb
@@ -1,8 +1,7 @@
 # encoding: utf-8
 
 class Nanoc::DataSources::FilesystemVerboseTest < Nanoc::TestCase
-
-  def new_data_source(params=nil)
+  def new_data_source(params = nil)
     # Mock site
     site = Nanoc::Site.new({})
 
@@ -34,7 +33,7 @@ class Nanoc::DataSources::FilesystemVerboseTest < Nanoc::TestCase
       io.write("title: Bar\n")
     end
     File.open('content/bar/bar.xml', 'w') do |io|
-      io.write("Lorem ipsum dolor sit amet...")
+      io.write('Lorem ipsum dolor sit amet...')
     end
 
     # Load items
@@ -42,22 +41,22 @@ class Nanoc::DataSources::FilesystemVerboseTest < Nanoc::TestCase
 
     # Check items
     assert_equal(2, items.size)
-    assert(items.any? { |a|
+    assert(items.any? do |a|
       a[:title]            == 'Foo' &&
       a[:extension]        == 'html' &&
       a[:content_filename] == 'content/foo/foo.html' &&
       a[:meta_filename]    == 'content/foo/foo.yaml'
-    })
-    assert(items.any? { |a|
+    end)
+    assert(items.any? do |a|
       a[:title]            == 'Bar' &&
       a[:extension]        == 'xml' &&
       a[:content_filename] == 'content/bar/bar.xml' &&
       a[:meta_filename]    == 'content/bar/bar.yaml'
-    })
+    end)
   end
 
   def test_items_with_period_in_name
-    data_source = new_data_source(:allow_periods_in_identifiers => true)
+    data_source = new_data_source(allow_periods_in_identifiers: true)
 
     # Create foo.css
     FileUtils.mkdir_p('content/foo')
@@ -67,7 +66,7 @@ class Nanoc::DataSources::FilesystemVerboseTest < Nanoc::TestCase
     File.open('content/foo/foo.css', 'w') do |io|
       io.write('body.foo {}')
     end
-    
+
     # Create foo.bar.css
     FileUtils.mkdir_p('content/foo.bar')
     File.open('content/foo.bar/foo.bar.yaml', 'w') do |io|
@@ -76,10 +75,10 @@ class Nanoc::DataSources::FilesystemVerboseTest < Nanoc::TestCase
     File.open('content/foo.bar/foo.bar.css', 'w') do |io|
       io.write('body.foobar {}')
     end
-    
+
     # Load
     items = data_source.items.sort_by { |i| i[:title] }
-    
+
     # Check
     assert_equal 2, items.size
     assert_equal '/foo/',                        items[0].identifier
@@ -114,18 +113,18 @@ class Nanoc::DataSources::FilesystemVerboseTest < Nanoc::TestCase
 
     # Check items
     assert_equal(2, items.size)
-    assert(items.any? { |a|
-      a[:title]            == nil &&
-      a[:extension]        == 'html' &&
+    assert(items.any? do |a|
+      a[:title].nil? &&
+      a[:extension] == 'html' &&
       a[:content_filename] == 'content/foo/foo.html' &&
-      a[:meta_filename]    == nil
-    })
-    assert(items.any? { |a|
-      a[:title]            == 'Bar' &&
-      a[:extension]        == nil &&
-      a[:content_filename] == nil &&
-      a[:meta_filename]    == 'content/bar/bar.yaml'
-    })
+      a[:meta_filename].nil?
+    end)
+    assert(items.any? do |a|
+      a[:title] == 'Bar' &&
+      a[:extension].nil? &&
+      a[:content_filename].nil? &&
+      a[:meta_filename] == 'content/bar/bar.yaml'
+    end)
   end
 
   def test_layouts
@@ -164,7 +163,7 @@ class Nanoc::DataSources::FilesystemVerboseTest < Nanoc::TestCase
     File.open('layouts/foo/foo.html', 'w') do |io|
       io.write('body.foo {}')
     end
-    
+
     # Create bar.html.erb
     FileUtils.mkdir_p('layouts/bar')
     File.open('layouts/bar/bar.yaml', 'w') do |io|
@@ -173,10 +172,10 @@ class Nanoc::DataSources::FilesystemVerboseTest < Nanoc::TestCase
     File.open('layouts/bar/bar.html.erb', 'w') do |io|
       io.write('body.foobar {}')
     end
-    
+
     # Load
-    layouts = data_source.layouts.sort_by { |i| i.identifier }
-    
+    layouts = data_source.layouts.sort_by(&:identifier)
+
     # Check
     assert_equal 2, layouts.size
     assert_equal '/bar/', layouts[0].identifier
@@ -186,7 +185,7 @@ class Nanoc::DataSources::FilesystemVerboseTest < Nanoc::TestCase
   end
 
   def test_layouts_with_period_in_name_allowing_periods_in_identifiers
-    data_source = new_data_source(:allow_periods_in_identifiers => true)
+    data_source = new_data_source(allow_periods_in_identifiers: true)
 
     # Create foo.html
     FileUtils.mkdir_p('layouts/foo')
@@ -196,7 +195,7 @@ class Nanoc::DataSources::FilesystemVerboseTest < Nanoc::TestCase
     File.open('layouts/foo/foo.html', 'w') do |io|
       io.write('body.foo {}')
     end
-    
+
     # Create bar.html.erb
     FileUtils.mkdir_p('layouts/bar.xyz')
     File.open('layouts/bar.xyz/bar.xyz.yaml', 'w') do |io|
@@ -205,10 +204,10 @@ class Nanoc::DataSources::FilesystemVerboseTest < Nanoc::TestCase
     File.open('layouts/bar.xyz/bar.xyz.html', 'w') do |io|
       io.write('body.foobar {}')
     end
-    
+
     # Load
-    layouts = data_source.layouts.sort_by { |i| i.identifier }
-    
+    layouts = data_source.layouts.sort_by(&:identifier)
+
     # Check
     assert_equal 2, layouts.size
     assert_equal '/bar.xyz/', layouts[0].identifier
@@ -220,7 +219,7 @@ class Nanoc::DataSources::FilesystemVerboseTest < Nanoc::TestCase
   def test_create_item_at_root
     # Create item
     data_source = new_data_source
-    data_source.create_item('content here', { :foo => 'bar' }, '/')
+    data_source.create_item('content here', { foo: 'bar' }, '/')
 
     # Check file existance
     assert File.directory?('content')
@@ -235,7 +234,7 @@ class Nanoc::DataSources::FilesystemVerboseTest < Nanoc::TestCase
   def test_create_item_not_at_root
     # Create item
     data_source = new_data_source
-    data_source.create_item('content here', { :foo => 'bar' }, '/moo/')
+    data_source.create_item('content here', { foo: 'bar' }, '/moo/')
 
     # Check file existance
     assert File.directory?('content/moo')
@@ -250,7 +249,7 @@ class Nanoc::DataSources::FilesystemVerboseTest < Nanoc::TestCase
   def test_create_layout
     # Create layout
     data_source = new_data_source
-    data_source.create_layout('content here', { :foo => 'bar' }, '/moo/')
+    data_source.create_layout('content here', { foo: 'bar' }, '/moo/')
 
     # Check file existance
     assert File.directory?('layouts/moo')
@@ -268,7 +267,7 @@ class Nanoc::DataSources::FilesystemVerboseTest < Nanoc::TestCase
 
     # Create sample files
     FileUtils.mkdir_p('foo')
-    File.open('foo/stuff.dat', 'w') { |io| io.write("random binary data") }
+    File.open('foo/stuff.dat', 'w') { |io| io.write('random binary data') }
 
     # Load
     items = data_source.send(:load_objects, 'foo', 'item', Nanoc::Item)
@@ -351,5 +350,4 @@ class Nanoc::DataSources::FilesystemVerboseTest < Nanoc::TestCase
       data_source.items
     end
   end
-
 end
diff --git a/test/data_sources/test_static.rb b/test/data_sources/test_static.rb
index da03c73..57e0143 100644
--- a/test/data_sources/test_static.rb
+++ b/test/data_sources/test_static.rb
@@ -1,8 +1,7 @@
 # encoding: utf-8
 
 class Nanoc::DataSources::StaticTest < Nanoc::TestCase
-
-  def new_data_source(params=nil)
+  def new_data_source(params = nil)
     # Mock site
     site = Nanoc::Site.new({})
 
@@ -14,77 +13,77 @@ class Nanoc::DataSources::StaticTest < Nanoc::TestCase
   end
 
   def test_items_with_symlinks
-    skip_unless_have_symlink
+    skip_unless_symlinks_supported
 
     # Create data source
-    data_source = new_data_source(:prefix => 'foo')
+    data_source = new_data_source(prefix: 'foo')
 
     # Create sample files
     FileUtils.mkdir_p('foo')
     FileUtils.mkdir_p('foo-outside-1')
     FileUtils.mkdir_p('foo-outside-2')
-    File.open('foo/a.png',           'w') { |io| io.write("random binary data") }
-    File.open('foo-outside-1/b.png', 'w') { |io| io.write("more binary data") }
-    File.open('foo-outside-2/c.png', 'w') { |io| io.write("yet more binary data") }
+    File.open('foo/a.png',           'w') { |io| io.write('random binary data') }
+    File.open('foo-outside-1/b.png', 'w') { |io| io.write('more binary data') }
+    File.open('foo-outside-2/c.png', 'w') { |io| io.write('yet more binary data') }
 
     # Create symlinks
     File.symlink('../foo-outside-1', 'foo/1')
     File.symlink('../foo-outside-2/c.png', 'foo/c.png')
 
     # Check all files
-    expected_filenames = [ 'foo/a.png', 'foo/1/b.png', 'foo/c.png' ].sort
-    actual_filenames   = Nanoc::Extra::FilesystemTools.all_files_in('foo').sort
+    expected_filenames = ['foo/a.png', 'foo/1/b.png', 'foo/c.png'].sort
+    actual_filenames   = Nanoc::Extra::FilesystemTools.all_files_in('foo', nil).sort
     assert_equal expected_filenames, actual_filenames
 
     # Check items
-    items = data_source.send(:items).sort_by { |i| i.identifier }
-    actual_item_identifiers   = items.map { |i| i.identifier }.sort
+    items = data_source.send(:items).sort_by(&:identifier)
+    actual_item_identifiers   = items.map(&:identifier).sort
     expected_item_identifiers = %w( /a.png/ /1/b.png/ /c.png/ ).sort
     assert_equal expected_item_identifiers, actual_item_identifiers
   end
 
   def test_items
     # Create data source
-    data_source = new_data_source(:prefix => 'foo')
+    data_source = new_data_source(prefix: 'foo')
 
     # Create sample files
     FileUtils.mkdir_p('foo')
     FileUtils.mkdir_p('foo/a/b')
-    File.open('foo/bar.png',   'w') { |io| io.write("random binary data") }
-    File.open('foo/b.c.css',   'w') { |io| io.write("more binary data") }
-    File.open('foo/a/b/c.gif', 'w') { |io| io.write("yet more binary data") }
+    File.open('foo/bar.png',   'w') { |io| io.write('random binary data') }
+    File.open('foo/b.c.css',   'w') { |io| io.write('more binary data') }
+    File.open('foo/a/b/c.gif', 'w') { |io| io.write('yet more binary data') }
 
     # Get expected and actual output
     expected_out = [
       Nanoc::Item.new(
         'foo/bar.png',
-        { :extension => 'png', :filename => 'foo/bar.png' },
+        { extension: 'png', filename: 'foo/bar.png' },
         '/bar.png/',
-        :binary => true,
-        :mtime => File.mtime('foo/bar.png'),
-        :checksum => Pathname.new('foo/bar.png').checksum
+        binary: true,
+        mtime: File.mtime('foo/bar.png'),
+        checksum: Pathname.new('foo/bar.png').checksum
       ),
       Nanoc::Item.new(
         'foo/b.c.css',
-        { :extension => 'css', :filename => 'foo/b.c.css' },
+        { extension: 'css', filename: 'foo/b.c.css' },
         '/b.c.css/',
-        :binary => true,
-        :mtime => File.mtime('foo/b.c.css'),
-        :checksum => Pathname.new('foo/b.c.css').checksum
+        binary: true,
+        mtime: File.mtime('foo/b.c.css'),
+        checksum: Pathname.new('foo/b.c.css').checksum
       ),
       Nanoc::Item.new(
         'foo/a/b/c.gif',
-        { :extension => 'gif', :filename => 'foo/a/b/c.gif' },
+        { extension: 'gif', filename: 'foo/a/b/c.gif' },
         '/a/b/c.gif/',
-        :binary => true,
-        :mtime => File.mtime('foo/a/b/c.gif'),
-        :checksum => Pathname.new('foo/a/b/c.gif').checksum
+        binary: true,
+        mtime: File.mtime('foo/a/b/c.gif'),
+        checksum: Pathname.new('foo/a/b/c.gif').checksum
       )
-    ].sort_by { |i| i.identifier }
+    ].sort_by(&:identifier)
 
-    actual_out = data_source.send(:items).sort_by { |i| i.identifier }
+    actual_out = data_source.send(:items).sort_by(&:identifier)
 
-    (0..expected_out.size-1).each do |i|
+    (0..expected_out.size - 1).each do |i|
       assert_equal expected_out[i].raw_content, actual_out[i].raw_content, 'content must match'
       assert_equal expected_out[i].identifier, actual_out[i].identifier, 'identifier must match'
       assert_equal expected_out[i].mtime, actual_out[i].mtime, 'mtime must match'
diff --git a/test/extra/checking/checks/test_css.rb b/test/extra/checking/checks/test_css.rb
index d182071..abd60f8 100644
--- a/test/extra/checking/checks/test_css.rb
+++ b/test/extra/checking/checks/test_css.rb
@@ -1,7 +1,6 @@
 # encoding: utf-8
 
 class Nanoc::Extra::Checking::Checks::CSSTest < Nanoc::TestCase
-
   def test_run_ok
     VCR.use_cassette('css_run_ok') do
       with_site do |site|
@@ -34,9 +33,31 @@ class Nanoc::Extra::Checking::Checks::CSSTest < Nanoc::TestCase
 
         # Check
         refute check.issues.empty?
+        assert_equal 1, check.issues.size
+        assert_equal 'line 1: Property coxlor doesn\'t exist: h1 { coxlor: rxed; }',
+          check.issues.to_a[0].description
       end
     end
   end
 
-end
+  def test_run_parse_error
+    VCR.use_cassette('css_run_parse_error') do
+      with_site do |site|
+        # Create files
+        FileUtils.mkdir_p('output')
+        File.open('output/blah.html', 'w') { |io| io.write('<h1>Hi!</h1>') }
+        File.open('output/style.css', 'w') { |io| io.write('h1 { ; {') }
+
+        # Run check
+        check = Nanoc::Extra::Checking::Checks::CSS.new(site)
+        check.run
 
+        # Check
+        refute check.issues.empty?
+        assert_equal 1, check.issues.size
+        assert_equal 'line 1: Parse Error: h1 { ; {',
+          check.issues.to_a[0].description
+      end
+    end
+  end
+end
diff --git a/test/extra/checking/checks/test_external_links.rb b/test/extra/checking/checks/test_external_links.rb
index 7e81a7d..5378272 100644
--- a/test/extra/checking/checks/test_external_links.rb
+++ b/test/extra/checking/checks/test_external_links.rb
@@ -1,7 +1,6 @@
 # encoding: utf-8
 
 class Nanoc::Extra::Checking::Checks::ExternalLinksTest < Nanoc::TestCase
-
   def test_run
     with_site do |site|
       # Create files
@@ -52,13 +51,13 @@ class Nanoc::Extra::Checking::Checks::ExternalLinksTest < Nanoc::TestCase
 
   def test_fallback_to_get_when_head_is_not_allowed
     with_site do |site|
-      #Create check
+      # Create check
       check = Nanoc::Extra::Checking::Checks::ExternalLinks.new(site)
       def check.request_url_once(url, req_method = Net::HTTP::Head)
         Net::HTTPResponse.new('1.1', (req_method == Net::HTTP::Head || url.path == '/405') ? '405' : '200', 'okay')
       end
 
-      #Test
+      # Test
       assert_nil check.validate('http://127.0.0.1:9204')
       refute_nil check.validate('http://127.0.0.1:9204/405')
     end
@@ -75,5 +74,4 @@ class Nanoc::Extra::Checking::Checks::ExternalLinksTest < Nanoc::TestCase
       assert_equal '/meow?foo=bar', check.send(:path_for_url, URI.parse('http://example.com/meow?foo=bar'))
     end
   end
-
 end
diff --git a/test/extra/checking/checks/test_html.rb b/test/extra/checking/checks/test_html.rb
index 285c8be..9037023 100644
--- a/test/extra/checking/checks/test_html.rb
+++ b/test/extra/checking/checks/test_html.rb
@@ -1,7 +1,6 @@
 # encoding: utf-8
 
 class Nanoc::Extra::Checking::Checks::HTMLTest < Nanoc::TestCase
-
   def test_run_ok
     VCR.use_cassette('html_run_ok') do
       with_site do |site|
@@ -34,9 +33,11 @@ class Nanoc::Extra::Checking::Checks::HTMLTest < Nanoc::TestCase
 
         # Check
         refute check.issues.empty?
+        assert_equal 2, check.issues.size
+        assert_equal 'line 1: no document type declaration; will parse without validation: <h2>Hi!</h1>', check.issues.to_a[0].description
+        assert_equal 'line 1: end tag for element "H1" which is not open: <h2>Hi!</h1>', check.issues.to_a[1].description
+          check.issues.to_a[0].description
       end
     end
   end
-
 end
-
diff --git a/test/extra/checking/checks/test_internal_links.rb b/test/extra/checking/checks/test_internal_links.rb
index f87a851..662a5c1 100644
--- a/test/extra/checking/checks/test_internal_links.rb
+++ b/test/extra/checking/checks/test_internal_links.rb
@@ -1,7 +1,6 @@
 # encoding: utf-8
 
 class Nanoc::Extra::Checking::Checks::InternalLinksTest < Nanoc::TestCase
-
   def test_run
     with_site do |site|
       # Create files
@@ -57,7 +56,7 @@ class Nanoc::Extra::Checking::Checks::InternalLinksTest < Nanoc::TestCase
     with_site do |site|
       # Create check
       check = Nanoc::Extra::Checking::Checks::InternalLinks.new(site)
-      site.config.update({ :checks => { :internal_links => { :exclude => ['^/excluded\d+'] } } })
+      site.config.update({ checks: { internal_links: { exclude: ['^/excluded\d+'] } } })
 
       # Test
       assert check.send(:valid?, '/excluded1', 'output/origin')
@@ -77,5 +76,4 @@ class Nanoc::Extra::Checking::Checks::InternalLinksTest < Nanoc::TestCase
       refute check.send(:valid?, 'stuff/wrong%20foo', 'output/origin')
     end
   end
-
 end
diff --git a/test/extra/checking/checks/test_mixed_content.rb b/test/extra/checking/checks/test_mixed_content.rb
new file mode 100644
index 0000000..c174678
--- /dev/null
+++ b/test/extra/checking/checks/test_mixed_content.rb
@@ -0,0 +1,188 @@
+# encoding: utf-8
+
+class Nanoc::Extra::Checking::Checks::MixedContentTest < Nanoc::TestCase
+  def create_output_file(name, lines)
+    FileUtils.mkdir_p('output')
+    File.open('output/' + name, 'w') { |io|
+      io.write(lines.join('\n'))
+    }
+  end
+
+  def assert_include(haystack, needle)
+    assert haystack.include?(needle), "Expected to find '#{needle}' in #{haystack}"
+  end
+
+  def test_https_content
+    with_site do |site|
+      create_output_file('foo.html', [
+        '<img src="https://nanoc.ws/logo.png" />',
+        '<img src="HTTPS://nanoc.ws/logo.png" />',
+        '<link href="https://nanoc.ws/style.css" />',
+        '<script src="https://nanoc.ws/app.js"></script>',
+        '<form action="https://nanoc.ws/process.cgi"></form>',
+        '<iframe src="https://nanoc.ws/preview.html"></iframe>',
+        '<audio src="https://nanoc.ws/theme-song.flac"></audio>',
+        '<video src="https://nanoc.ws/screen-cast.mkv"></video>'
+      ])
+      check = Nanoc::Extra::Checking::Checks::MixedContent.new(site)
+      check.run
+
+      assert check.issues.empty?
+    end
+  end
+
+  def test_root_relative_content
+    with_site do |site|
+      create_output_file('foo.html', [
+        '<img src="/logo.png" />',
+        '<link href="/style.css" />',
+        '<script src="/app.js"></script>',
+        '<form action="/process.cgi"></form>',
+        '<iframe src="/preview.html"></iframe>',
+        '<audio src="/theme-song.flac"></audio>',
+        '<video src="/screen-cast.mkv"></video>'
+      ])
+      check = Nanoc::Extra::Checking::Checks::MixedContent.new(site)
+      check.run
+
+      assert check.issues.empty?
+    end
+  end
+
+  def test_protocol_relative_content
+    with_site do |site|
+      create_output_file('foo.html', [
+        '<img src="//nanoc.ws/logo.png" />',
+        '<link href="//nanoc.ws/style.css" />',
+        '<script src="//nanoc.ws/app.js"></script>',
+        '<form action="//nanoc.ws/process.cgi"></form>',
+        '<iframe src="//nanoc.ws/preview.html"></iframe>',
+        '<audio src="//nanoc.ws/theme-song.flac"></audio>',
+        '<video src="//nanoc.ws/screen-cast.mkv"></video>'
+      ])
+      check = Nanoc::Extra::Checking::Checks::MixedContent.new(site)
+      check.run
+
+      assert check.issues.empty?
+    end
+  end
+
+  def test_document_relative_content
+    with_site do |site|
+      create_output_file('foo.html', [
+        '<img src="logo.png" />',
+        '<link href="style.css" />',
+        '<script src="app.js"></script>',
+        '<form action="process.cgi"></form>',
+        '<iframe src="preview.html"></iframe>',
+        '<audio src="theme-song.flac"></audio>',
+        '<video src="screen-cast.mkv"></video>'
+      ])
+      check = Nanoc::Extra::Checking::Checks::MixedContent.new(site)
+      check.run
+
+      assert check.issues.empty?
+    end
+  end
+
+  def test_query_relative_content
+    with_site do |site|
+      create_output_file('foo.html', [
+        '<img src="?query-string" />',
+        '<link href="?query-string" />',
+        '<script src="?query-string"></script>',
+        '<form action="?query-string"></form>',
+        '<iframe src="?query-string"></iframe>',
+        '<audio src="?query-string"></audio>',
+        '<video src="?query-string"></video>'
+      ])
+      check = Nanoc::Extra::Checking::Checks::MixedContent.new(site)
+      check.run
+
+      assert check.issues.empty?
+    end
+  end
+
+  def test_fragment_relative_content
+    with_site do |site|
+      create_output_file('foo.html', [
+        '<img src="#fragment" />',
+        '<link href="#fragment" />',
+        '<script src="#fragment"></script>',
+        '<form action="#fragment"></form>',
+        '<iframe src="#fragment"></iframe>',
+        '<audio src="#fragment"></audio>',
+        '<video src="#fragment"></video>'
+      ])
+      check = Nanoc::Extra::Checking::Checks::MixedContent.new(site)
+      check.run
+
+      assert check.issues.empty?
+    end
+  end
+
+  def test_http_content
+    with_site do |site|
+      create_output_file('foo.html', [
+        '<img src="http://nanoc.ws/logo.png" />',
+        '<img src="HTTP://nanoc.ws/logo.png" />',
+        '<link href="http://nanoc.ws/style.css" />',
+        '<script src="http://nanoc.ws/app.js"></script>',
+        '<form action="http://nanoc.ws/process.cgi"></form>',
+        '<iframe src="http://nanoc.ws/preview.html"></iframe>',
+        '<audio src="http://nanoc.ws/theme-song.flac"></audio>',
+        '<video src="http://nanoc.ws/screencast.mkv"></video>'
+      ])
+      check = Nanoc::Extra::Checking::Checks::MixedContent.new(site)
+      check.run
+
+      issues = check.issues.to_a
+      assert_equal 8, issues.count
+
+      descriptions = issues.map { |issue| issue.description }
+      issues.each do |issue|
+        assert_equal 'output/foo.html', issue.subject
+      end
+
+      # The order of the reported issues is not important, so use this class's
+      # `assert_include` helper to avoid asserting those details
+      assert_include descriptions, 'mixed content include: http://nanoc.ws/logo.png'
+
+      assert_include descriptions, 'mixed content include: HTTP://nanoc.ws/logo.png'
+
+      assert_include descriptions, 'mixed content include: http://nanoc.ws/style.css'
+
+      assert_include descriptions, 'mixed content include: http://nanoc.ws/app.js'
+
+      assert_include descriptions, 'mixed content include: http://nanoc.ws/process.cgi'
+
+      assert_include descriptions, 'mixed content include: http://nanoc.ws/preview.html'
+
+      assert_include descriptions, 'mixed content include: http://nanoc.ws/theme-song.flac'
+
+      assert_include descriptions, 'mixed content include: http://nanoc.ws/screencast.mkv'
+    end
+  end
+
+  def test_inert_content
+    with_site do |site|
+      create_output_file('foo.html', [
+        '<a href="http://nanoc.ws">The homepage</a>',
+        '<a name="Not a link">Content</a>',
+        '<script>// inline JavaScript</script>',
+        '<img href="http://nanoc.ws/logo.png" />',
+        '<link src="http://nanoc.ws/style.css" />',
+        '<script href="http://nanoc.ws/app.js"></script>',
+        '<form src="http://nanoc.ws/process.cgi"></form>',
+        '<iframe href="http://nanoc.ws/preview.html"></iframe>',
+        '<audio href="http://nanoc.ws/theme-song.flac"></audio>',
+        '<video target="http://nanoc.ws/screen-cast.mkv"></video>',
+        '<p>http://nanoc.ws/harmless-text</p>'
+      ])
+      check = Nanoc::Extra::Checking::Checks::MixedContent.new(site)
+      check.run
+
+      assert check.issues.empty?
+    end
+  end
+end
diff --git a/test/extra/checking/checks/test_stale.rb b/test/extra/checking/checks/test_stale.rb
index 5e4ccdb..e38ee9a 100644
--- a/test/extra/checking/checks/test_stale.rb
+++ b/test/extra/checking/checks/test_stale.rb
@@ -1,7 +1,6 @@
 # encoding: utf-8
 
 class Nanoc::Extra::Checking::Checks::StaleTest < Nanoc::TestCase
-
   def check_class
     Nanoc::Extra::Checking::Checks::Stale
   end
@@ -14,57 +13,56 @@ class Nanoc::Extra::Checking::Checks::StaleTest < Nanoc::TestCase
   end
 
   def test_run_ok
-    with_site do |site|
+    with_site do |_site|
       assert Dir['content/*'].empty?
       assert Dir['output/*'].empty?
 
       # Empty
       FileUtils.mkdir_p('output')
-      assert self.calc_issues.empty?
+      assert calc_issues.empty?
 
       # One OK file
       File.open('content/index.html', 'w') { |io| io.write('stuff') }
       File.open('output/index.html', 'w') { |io| io.write('stuff') }
-      assert self.calc_issues.empty?
+      assert calc_issues.empty?
     end
   end
 
   def test_run_error
-    with_site do |site|
+    with_site do |_site|
       assert Dir['content/*'].empty?
       assert Dir['output/*'].empty?
 
       File.open('content/index.html', 'w') { |io| io.write('stuff') }
       File.open('output/WRONG.html', 'w') { |io| io.write('stuff') }
-      assert_equal 1, self.calc_issues.count
-      issue = self.calc_issues.to_a[0]
-      assert_equal "file without matching item", issue.description
-      assert_equal "output/WRONG.html", issue.subject
+      assert_equal 1, calc_issues.count
+      issue = calc_issues.to_a[0]
+      assert_equal 'file without matching item', issue.description
+      assert_equal 'output/WRONG.html', issue.subject
     end
   end
 
   def test_run_excluded
-    with_site do |site|
+    with_site do |_site|
       assert Dir['content/*'].empty?
       assert Dir['output/*'].empty?
 
       File.open('nanoc.yaml', 'w') { |io| io.write "prune:\n  exclude: [ 'excluded.html' ]" }
       File.open('content/index.html', 'w') { |io| io.write('stuff') }
       File.open('output/excluded.html', 'w') { |io| io.write('stuff') }
-      assert self.calc_issues.empty?
+      assert calc_issues.empty?
     end
   end
 
   def test_run_excluded_with_broken_config
-    with_site do |site|
+    with_site do |_site|
       assert Dir['content/*'].empty?
       assert Dir['output/*'].empty?
 
       File.open('nanoc.yaml', 'w') { |io| io.write "prune:\n  blah: meh" }
       File.open('content/index.html', 'w') { |io| io.write('stuff') }
       File.open('output/excluded.html', 'w') { |io| io.write('stuff') }
-      refute self.calc_issues.empty?
+      refute calc_issues.empty?
     end
   end
-
 end
diff --git a/test/extra/checking/test_check.rb b/test/extra/checking/test_check.rb
index 54623f3..ee5d1f6 100644
--- a/test/extra/checking/test_check.rb
+++ b/test/extra/checking/test_check.rb
@@ -1,14 +1,22 @@
 # encoding: utf-8
 
 class Nanoc::Extra::Checking::CheckTest < Nanoc::TestCase
-
   def test_output_filenames
     with_site do |site|
       check = Nanoc::Extra::Checking::Check.new(site)
       assert check.output_filenames.empty?
       File.open('output/foo.html', 'w') { |io| io.write 'hello' }
-      assert_equal [ 'output/foo.html' ], check.output_filenames
+      assert_equal ['output/foo.html'], check.output_filenames
     end
   end
 
+  def test_no_output_dir
+    with_site do |site|
+      site.config[:output_dir] = 'non-existent'
+      check = Nanoc::Extra::Checking::Check.new(site)
+      assert_raises Nanoc::Extra::Checking::OutputDirNotFoundError do
+        check.output_filenames
+      end
+    end
+  end
 end
diff --git a/test/extra/checking/test_dsl.rb b/test/extra/checking/test_dsl.rb
index ea6e341..b54ff5c 100644
--- a/test/extra/checking/test_dsl.rb
+++ b/test/extra/checking/test_dsl.rb
@@ -1,9 +1,8 @@
 # encoding: utf-8
 
 class Nanoc::Extra::Checking::DSLTest < Nanoc::TestCase
-
   def test_from_file
-    with_site do |site|
+    with_site do |_site|
       File.open('Checks', 'w') { |io| io.write("check :foo do\n\nend\ndeploy_check :bar\n") }
       dsl = Nanoc::Extra::Checking::DSL.from_file('Checks')
 
@@ -11,8 +10,7 @@ class Nanoc::Extra::Checking::DSLTest < Nanoc::TestCase
       refute Nanoc::Extra::Checking::Check.named(:foo).nil?
 
       # One check marked for deployment
-      assert_equal [ :bar ], dsl.deploy_checks
+      assert_equal [:bar], dsl.deploy_checks
     end
   end
-
 end
diff --git a/test/extra/checking/test_runner.rb b/test/extra/checking/test_runner.rb
index e532bc6..67a2937 100644
--- a/test/extra/checking/test_runner.rb
+++ b/test/extra/checking/test_runner.rb
@@ -1,7 +1,6 @@
 # encoding: utf-8
 
 class Nanoc::Extra::Checking::RunnerTest < Nanoc::TestCase
-
   def test_run_specific
     with_site do |site|
       File.open('output/blah', 'w') { |io| io.write('I am stale! Haha!') }
@@ -41,5 +40,4 @@ class Nanoc::Extra::Checking::RunnerTest < Nanoc::TestCase
       assert ios[:stderr].empty?
     end
   end
-
 end
diff --git a/test/extra/core_ext/test_enumerable.rb b/test/extra/core_ext/test_enumerable.rb
deleted file mode 100644
index 4922937..0000000
--- a/test/extra/core_ext/test_enumerable.rb
+++ /dev/null
@@ -1,28 +0,0 @@
-# encoding: utf-8
-
-class Nanoc::ExtraCoreExtEnumerableTest < Nanoc::TestCase
-
-  class MyCollection
-
-    include Enumerable
-
-    def initialize(array)
-      @array = array
-    end
-
-    def each(&block)
-      @array.each { |i| block.call(i) }
-    end
-
-  end
-
-  def test_group_by
-    input = MyCollection.new([ 'foo', 'bar', 'baz' ])
-
-    output_expected = { ?f => [ 'foo' ], ?b => [ 'bar', 'baz' ] }
-    output_actual   = input.group_by { |i| i[0] }
-
-    assert_equal output_expected, output_actual
-  end
-
-end
diff --git a/test/extra/core_ext/test_pathname.rb b/test/extra/core_ext/test_pathname.rb
index f0341ea..db97e7d 100644
--- a/test/extra/core_ext/test_pathname.rb
+++ b/test/extra/core_ext/test_pathname.rb
@@ -1,7 +1,6 @@
 # encoding: utf-8
 
 class Nanoc::Extra::CoreExtPathnameTest < Nanoc::TestCase
-
   def test_components
     assert_equal %w( / a bb ccc dd e ), Pathname.new('/a/bb/ccc/dd/e').components
   end
@@ -10,6 +9,4 @@ class Nanoc::Extra::CoreExtPathnameTest < Nanoc::TestCase
     assert Pathname.new('/home/ddfreyne/').include_component?('ddfreyne')
     refute Pathname.new('/home/ddfreyne/').include_component?('acid')
   end
-
 end
-
diff --git a/test/extra/core_ext/test_time.rb b/test/extra/core_ext/test_time.rb
index 933a50d..c3e359f 100644
--- a/test/extra/core_ext/test_time.rb
+++ b/test/extra/core_ext/test_time.rb
@@ -1,7 +1,6 @@
 # encoding: utf-8
 
 class Nanoc::ExtraCoreExtTimeTest < Nanoc::TestCase
-
   def test_to_iso8601_date
     assert_equal('2008-05-19', Time.utc(2008, 5, 19, 14, 20, 0, 0).to_iso8601_date)
   end
@@ -9,5 +8,4 @@ class Nanoc::ExtraCoreExtTimeTest < Nanoc::TestCase
   def test_to_iso8601_time
     assert_equal('2008-05-19T14:20:00Z', Time.utc(2008, 5, 19, 14, 20, 0, 0).to_iso8601_time)
   end
-
 end
diff --git a/test/extra/deployers/test_fog.rb b/test/extra/deployers/test_fog.rb
index 1fe599e..d3d0992 100644
--- a/test/extra/deployers/test_fog.rb
+++ b/test/extra/deployers/test_fog.rb
@@ -1,21 +1,20 @@
 # encoding: utf-8
 
 class Nanoc::Extra::Deployers::FogTest < Nanoc::TestCase
-
   def test_run
     if_have 'fog' do
       # Create deployer
       fog = Nanoc::Extra::Deployers::Fog.new(
         'output/',
         {
-          :bucket     => 'mybucket',
-          :provider   => 'local',
-          :local_root => 'mylocalcloud'})
+          bucket: 'mybucket',
+          provider: 'local',
+          local_root: 'mylocalcloud' })
 
       # Create site
       FileUtils.mkdir_p('output')
-      File.open('output/meow', 'w') { |io| io.write "I am a cat!" }
-      File.open('output/bark', 'w') { |io| io.write "I am a dog!" }
+      File.open('output/meow', 'w') { |io| io.write 'I am a cat!' }
+      File.open('output/bark', 'w') { |io| io.write 'I am a dog!' }
 
       # Create local cloud (but not bucket)
       FileUtils.mkdir_p('mylocalcloud')
@@ -26,8 +25,8 @@ class Nanoc::Extra::Deployers::FogTest < Nanoc::TestCase
       # Check
       assert File.file?('mylocalcloud/mybucket/meow')
       assert File.file?('mylocalcloud/mybucket/bark')
-      assert_equal "I am a cat!", File.read('mylocalcloud/mybucket/meow')
-      assert_equal "I am a dog!", File.read('mylocalcloud/mybucket/bark')
+      assert_equal 'I am a cat!', File.read('mylocalcloud/mybucket/meow')
+      assert_equal 'I am a dog!', File.read('mylocalcloud/mybucket/bark')
     end
   end
 
@@ -38,17 +37,17 @@ class Nanoc::Extra::Deployers::FogTest < Nanoc::TestCase
         fog = Nanoc::Extra::Deployers::Fog.new(
           'output/',
           {
-            :provider              => 'aws',
-            # FIXME bucket is necessary for deployer but fog doesn't like it
-            :bucket_name           => 'doesntmatter',
-            :aws_access_key_id     => 'meh',
-            :aws_secret_access_key => 'dontcare'},
-          :dry_run => true)
+            provider: 'aws',
+            # FIXME: bucket is necessary for deployer but fog doesn't like it
+            bucket_name: 'doesntmatter',
+            aws_access_key_id: 'meh',
+            aws_secret_access_key: 'dontcare' },
+          dry_run: true)
 
         # Create site
         FileUtils.mkdir_p('output')
-        File.open('output/meow', 'w') { |io| io.write "I am a cat!" }
-        File.open('output/bark', 'w') { |io| io.write "I am a dog!" }
+        File.open('output/meow', 'w') { |io| io.write 'I am a cat!' }
+        File.open('output/bark', 'w') { |io| io.write 'I am a dog!' }
 
         # Create local cloud (but not bucket)
         FileUtils.mkdir_p('mylocalcloud')
@@ -56,21 +55,53 @@ class Nanoc::Extra::Deployers::FogTest < Nanoc::TestCase
         # Run
         fog.run
       ensure
-        # Hack :(
+        # FIXME: ugly hack
         ::Fog.instance_eval { @mocking = false }
       end
     end
   end
 
+  def test_run_cdn_with_dry_run
+    if_have 'fog' do
+      begin
+	# Create deployer
+	fog = Nanoc::Extra::Deployers::Fog.new(
+	  'output/',
+	  {
+	    :provider              => 'aws',
+            :cdn_id                => 'id-cdn',
+	    # FIXME bucket is necessary for deployer but fog doesn't like it
+	    :bucket_name           => 'doesntmatter',
+	    :aws_access_key_id     => 'meh',
+	    :aws_secret_access_key => 'dontcare'},
+	  :dry_run => true)
+
+	# Create site
+	FileUtils.mkdir_p('output')
+	File.open('output/meow', 'w') { |io| io.write "I am a cat!" }
+	File.open('output/bark', 'w') { |io| io.write "I am a dog!" }
+
+	# Create local cloud (but not bucket)
+	FileUtils.mkdir_p('mylocalcloud')
+
+	# Run
+	fog.run
+      ensure
+	# Hack :(
+	::Fog.instance_eval { @mocking = false }
+      end
+    end
+  end
+
   def test_run_delete_stray
     if_have 'fog' do
       # Create deployer
       fog = Nanoc::Extra::Deployers::Fog.new(
         'output/',
         {
-          :bucket     => 'mybucket',
-          :provider   => 'local',
-          :local_root => 'mylocalcloud'})
+          bucket: 'mybucket',
+          provider: 'local',
+          local_root: 'mylocalcloud' })
 
       # Setup fake local cloud
       FileUtils.mkdir_p('mylocalcloud/mybucket')
@@ -80,8 +111,8 @@ class Nanoc::Extra::Deployers::FogTest < Nanoc::TestCase
 
       # Create site
       FileUtils.mkdir_p('output')
-      File.open('output/meow', 'w') { |io| io.write "I am a cat!" }
-      File.open('output/bark', 'w') { |io| io.write "I am a dog!" }
+      File.open('output/meow', 'w') { |io| io.write 'I am a cat!' }
+      File.open('output/bark', 'w') { |io| io.write 'I am a dog!' }
 
       # Create local cloud (but not bucket)
       FileUtils.mkdir_p('mylocalcloud')
@@ -93,9 +124,8 @@ class Nanoc::Extra::Deployers::FogTest < Nanoc::TestCase
       refute File.file?('mylocalcloud/mybucket/etc')
       assert File.file?('mylocalcloud/mybucket/meow')
       assert File.file?('mylocalcloud/mybucket/bark')
-      assert_equal "I am a cat!", File.read('mylocalcloud/mybucket/meow')
-      assert_equal "I am a dog!", File.read('mylocalcloud/mybucket/bark')
+      assert_equal 'I am a cat!', File.read('mylocalcloud/mybucket/meow')
+      assert_equal 'I am a dog!', File.read('mylocalcloud/mybucket/bark')
     end
   end
-
 end
diff --git a/test/extra/deployers/test_rsync.rb b/test/extra/deployers/test_rsync.rb
index 2beff4d..3152d0a 100644
--- a/test/extra/deployers/test_rsync.rb
+++ b/test/extra/deployers/test_rsync.rb
@@ -1,7 +1,6 @@
 # encoding: utf-8
 
 class Nanoc::Extra::Deployers::RsyncTest < Nanoc::TestCase
-
   def test_run_without_dst
     # Create deployer
     rsync = Nanoc::Extra::Deployers::Rsync.new(
@@ -26,7 +25,7 @@ class Nanoc::Extra::Deployers::RsyncTest < Nanoc::TestCase
     # Create deployer
     rsync = Nanoc::Extra::Deployers::Rsync.new(
       'output/',
-      { :dst => 'asdf/' })
+      { dst: 'asdf/' })
 
     # Mock run_shell_cmd
     def rsync.run_shell_cmd(args)
@@ -46,7 +45,7 @@ class Nanoc::Extra::Deployers::RsyncTest < Nanoc::TestCase
     # Create deployer
     rsync = Nanoc::Extra::Deployers::Rsync.new(
       'output',
-      { :dst => 'asdf' })
+      { dst: 'asdf' })
 
     # Mock run_shell_cmd
     def rsync.run_shell_cmd(args)
@@ -59,7 +58,7 @@ class Nanoc::Extra::Deployers::RsyncTest < Nanoc::TestCase
     # Check args
     opts = Nanoc::Extra::Deployers::Rsync::DEFAULT_OPTIONS
     assert_equal(
-      [ 'rsync', opts, 'output/', 'asdf' ].flatten,
+      ['rsync', opts, 'output/', 'asdf'].flatten,
       rsync.instance_eval { @shell_cms_args }
     )
   end
@@ -68,8 +67,8 @@ class Nanoc::Extra::Deployers::RsyncTest < Nanoc::TestCase
     # Create deployer
     rsync = Nanoc::Extra::Deployers::Rsync.new(
       'output',
-      { :dst => 'asdf' },
-      :dry_run => true)
+      { dst: 'asdf' },
+      dry_run: true)
 
     # Mock run_shell_cmd
     def rsync.run_shell_cmd(args)
@@ -82,9 +81,8 @@ class Nanoc::Extra::Deployers::RsyncTest < Nanoc::TestCase
     # Check args
     opts = Nanoc::Extra::Deployers::Rsync::DEFAULT_OPTIONS
     assert_equal(
-      [ 'echo', 'rsync', opts, 'output/', 'asdf' ].flatten,
+      ['echo', 'rsync', opts, 'output/', 'asdf'].flatten,
       rsync.instance_eval { @shell_cms_args }
     )
   end
-
 end
diff --git a/test/extra/test_auto_compiler.rb b/test/extra/test_auto_compiler.rb
index d92570b..046f46d 100644
--- a/test/extra/test_auto_compiler.rb
+++ b/test/extra/test_auto_compiler.rb
@@ -1,7 +1,6 @@
 # encoding: utf-8
 
 class Nanoc::Extra::AutoCompilerTest < Nanoc::TestCase
-
   def test_handle_request_with_item_rep_with_index_filename
     if_have 'mime/types', 'rack' do
       # Create site
@@ -11,13 +10,13 @@ class Nanoc::Extra::AutoCompilerTest < Nanoc::TestCase
         # Create item
         FileUtils.mkdir_p('content/foo')
         File.open('content/foo/index.html', 'w') do |io|
-          io.write "Moo!"
+          io.write 'Moo!'
         end
 
         # Create output file
         FileUtils.mkdir_p('output/foo')
         File.open('output/foo/index.html', 'w') do |io|
-          io.write "Compiled moo!"
+          io.write 'Compiled moo!'
         end
 
         # Create site
@@ -36,7 +35,7 @@ class Nanoc::Extra::AutoCompilerTest < Nanoc::TestCase
         assert_equal(200, status)
         assert_equal('text/html', headers['Content-Type'])
         body.each do |b|
-          assert_equal "Compiled moo!", b
+          assert_equal 'Compiled moo!', b
         end
       end
     end
@@ -77,13 +76,17 @@ class Nanoc::Extra::AutoCompilerTest < Nanoc::TestCase
         @expected_path_info = 'somefile.txt'
         @actual_path_info   = env['PATH_INFO']
       end
-      def file_server.expected_path_info ; @expected_path_info ; end
-      def file_server.actual_path_info   ; @actual_path_info   ; end
+      def file_server.expected_path_info
+        @expected_path_info
+      end
+      def file_server.actual_path_info
+        @actual_path_info
+      end
 
       # Create site
       site = mock
       site.expects(:items).returns([])
-      site.expects(:config).returns({ :output_dir => 'out', :index_filenames => [ 'index.html' ] })
+      site.expects(:config).returns({ output_dir: 'out', index_filenames: ['index.html'] })
 
       # Create autocompiler
       autocompiler = Nanoc::Extra::AutoCompiler.new('.')
@@ -111,13 +114,17 @@ class Nanoc::Extra::AutoCompilerTest < Nanoc::TestCase
         @expected_path_info = '/foo/bar/index.html'
         @actual_path_info   = env['PATH_INFO']
       end
-      def file_server.expected_path_info ; @expected_path_info ; end
-      def file_server.actual_path_info   ; @actual_path_info   ; end
+      def file_server.expected_path_info
+        @expected_path_info
+      end
+      def file_server.actual_path_info
+        @actual_path_info
+      end
 
       # Create site
       site = mock
       site.expects(:items).returns([])
-      site.expects(:config).at_least_once.returns({ :output_dir => 'out', :index_filenames => [ 'index.html' ] })
+      site.expects(:config).at_least_once.returns({ output_dir: 'out', index_filenames: ['index.html'] })
 
       # Create autocompiler
       autocompiler = Nanoc::Extra::AutoCompiler.new('.')
@@ -145,13 +152,17 @@ class Nanoc::Extra::AutoCompilerTest < Nanoc::TestCase
         @expected_path_info = 'foo/bar/'
         @actual_path_info   = env['PATH_INFO']
       end
-      def file_server.expected_path_info ; @expected_path_info ; end
-      def file_server.actual_path_info   ; @actual_path_info   ; end
+      def file_server.expected_path_info
+        @expected_path_info
+      end
+      def file_server.actual_path_info
+        @actual_path_info
+      end
 
       # Create site
       site = mock
       site.expects(:items).returns([])
-      site.expects(:config).at_least_once.returns({ :output_dir => 'out', :index_filenames => [ 'index.html' ] })
+      site.expects(:config).at_least_once.returns({ output_dir: 'out', index_filenames: ['index.html'] })
 
       # Create autocompiler
       autocompiler = Nanoc::Extra::AutoCompiler.new('.')
@@ -179,13 +190,17 @@ class Nanoc::Extra::AutoCompilerTest < Nanoc::TestCase
         @expected_path_info = 'foo/bar'
         @actual_path_info   = env['PATH_INFO']
       end
-      def file_server.expected_path_info ; @expected_path_info ; end
-      def file_server.actual_path_info   ; @actual_path_info   ; end
+      def file_server.expected_path_info
+        @expected_path_info
+      end
+      def file_server.actual_path_info
+        @actual_path_info
+      end
 
       # Create site
       site = mock
       site.expects(:items).returns([])
-      site.expects(:config).at_least_once.returns({ :output_dir => 'out', :index_filenames => [ 'index.html' ] })
+      site.expects(:config).at_least_once.returns({ output_dir: 'out', index_filenames: ['index.html'] })
 
       # Create autocompiler
       autocompiler = Nanoc::Extra::AutoCompiler.new('.')
@@ -213,13 +228,17 @@ class Nanoc::Extra::AutoCompilerTest < Nanoc::TestCase
         @expected_path_info = 'foo/bar'
         @actual_path_info   = env['PATH_INFO']
       end
-      def file_server.expected_path_info ; @expected_path_info ; end
-      def file_server.actual_path_info   ; @actual_path_info   ; end
+      def file_server.expected_path_info
+        @expected_path_info
+      end
+      def file_server.actual_path_info
+        @actual_path_info
+      end
 
       # Create site
       site = mock
       site.expects(:items).returns([])
-      site.expects(:config).at_least_once.returns({ :output_dir => 'out', :index_filenames => [ 'index.html' ] })
+      site.expects(:config).at_least_once.returns({ output_dir: 'out', index_filenames: ['index.html'] })
 
       # Create autocompiler
       autocompiler = Nanoc::Extra::AutoCompiler.new('.')
@@ -243,13 +262,17 @@ class Nanoc::Extra::AutoCompilerTest < Nanoc::TestCase
         @expected_path_info = 'four-oh-four.txt'
         @actual_path_info   = env['PATH_INFO']
       end
-      def file_server.expected_path_info ; @expected_path_info ; end
-      def file_server.actual_path_info   ; @actual_path_info   ; end
+      def file_server.expected_path_info
+        @expected_path_info
+      end
+      def file_server.actual_path_info
+        @actual_path_info
+      end
 
       # Create site
       site = mock
       site.expects(:items).returns([])
-      site.expects(:config).at_least_once.returns({ :output_dir => 'out', :index_filenames => [ 'index.html' ] })
+      site.expects(:config).at_least_once.returns({ output_dir: 'out', index_filenames: ['index.html'] })
 
       # Create autocompiler
       autocompiler = Nanoc::Extra::AutoCompiler.new('.')
@@ -294,12 +317,12 @@ class Nanoc::Extra::AutoCompilerTest < Nanoc::TestCase
       FileUtils.cd('bar') do
         # Create item
         File.open('content/index.html', 'w') do |io|
-          io.write "Moo!"
+          io.write 'Moo!'
         end
 
         # Create output file
         File.open('output/index.html', 'w') do |io|
-          io.write "Compiled moo!"
+          io.write 'Compiled moo!'
         end
 
         # Create site
@@ -318,7 +341,7 @@ class Nanoc::Extra::AutoCompilerTest < Nanoc::TestCase
         assert_equal(200, status)
         assert_equal('text/html', headers['Content-Type'])
         body.each do |b|
-          assert_equal "Compiled moo!", b
+          assert_equal 'Compiled moo!', b
         end
       end
     end
@@ -332,7 +355,7 @@ class Nanoc::Extra::AutoCompilerTest < Nanoc::TestCase
       FileUtils.cd('bar') do
         # Create item
         File.open('content/whatever.html', 'w') do |io|
-          io.write "Whatever!"
+          io.write 'Whatever!'
         end
 
         # Create site
@@ -360,7 +383,7 @@ class Nanoc::Extra::AutoCompilerTest < Nanoc::TestCase
       FileUtils.cd('foo') do
         # Create item that outputs config elements
         File.open('content/index.html', 'w') do |io|
-          io.write "The Grand Value of Configuration is <%= @config[:value] %>!"
+          io.write 'The Grand Value of Configuration is <%= @config[:value] %>!'
         end
 
         # Create autocompiler
@@ -368,24 +391,24 @@ class Nanoc::Extra::AutoCompilerTest < Nanoc::TestCase
 
         # Set config to 1st value
         File.open('nanoc.yaml', 'w') do |io|
-          io.write "value: Foo"
+          io.write 'value: Foo'
         end
-        File.utime(Time.now+5, Time.now+5, 'nanoc.yaml')
+        File.utime(Time.now + 5, Time.now + 5, 'nanoc.yaml')
 
         # Check
-        status, headers, body = autocompiler.call('REQUEST_METHOD' => 'GET', 'PATH_INFO' => '/')
+        _status, _headers, body = autocompiler.call('REQUEST_METHOD' => 'GET', 'PATH_INFO' => '/')
         body.each do |b|
           assert_match(/The Grand Value of Configuration is Foo!/, b)
         end
 
         # Set config to 2nd value
         File.open('nanoc.yaml', 'w') do |io|
-          io.write "value: Bar"
+          io.write 'value: Bar'
         end
-        File.utime(Time.now+5, Time.now+5, 'nanoc.yaml')
+        File.utime(Time.now + 5, Time.now + 5, 'nanoc.yaml')
 
         # Check
-        status, headers, body = autocompiler.call('REQUEST_METHOD' => 'GET', 'PATH_INFO' => '/')
+        _status, _headers, body = autocompiler.call('REQUEST_METHOD' => 'GET', 'PATH_INFO' => '/')
         body.each do |b|
           assert_match(/The Grand Value of Configuration is Bar!/, b)
         end
@@ -400,7 +423,7 @@ class Nanoc::Extra::AutoCompilerTest < Nanoc::TestCase
 
       # Mock dependencies
       site = mock
-      site.stubs(:config).returns({ :output_dir => 'output/' })
+      site.stubs(:config).returns({ output_dir: 'output/' })
       site.stubs(:items).returns([])
       autocompiler.stubs(:build_site)
       autocompiler.stubs(:site).returns(site)
@@ -411,5 +434,4 @@ class Nanoc::Extra::AutoCompilerTest < Nanoc::TestCase
       assert_match("File not found: /software\n", result[2][0])
     end
   end
-
 end
diff --git a/test/extra/test_file_proxy.rb b/test/extra/test_file_proxy.rb
index d2792fc..a21e957 100644
--- a/test/extra/test_file_proxy.rb
+++ b/test/extra/test_file_proxy.rb
@@ -1,11 +1,10 @@
 # encoding: utf-8
 
 class Nanoc::Extra::FileProxyTest < Nanoc::TestCase
-
   def test_create_many
     if_implemented do
       # Create test file
-      File.open('test.txt', 'w') { |io| }
+      File.open('test.txt', 'w') { |_io| }
 
       # Create lots of file proxies
       count = Process.getrlimit(Process::RLIMIT_NOFILE)[0] + 5
@@ -13,5 +12,4 @@ class Nanoc::Extra::FileProxyTest < Nanoc::TestCase
       count.times { file_proxies << Nanoc::Extra::FileProxy.new('test.txt') }
     end
   end
-
 end
diff --git a/test/extra/test_filesystem_tools.rb b/test/extra/test_filesystem_tools.rb
index 354e81a..af8b527 100644
--- a/test/extra/test_filesystem_tools.rb
+++ b/test/extra/test_filesystem_tools.rb
@@ -3,7 +3,7 @@
 class Nanoc::Extra::FilesystemToolsTest < Nanoc::TestCase
   def setup
     super
-    skip_unless_have_symlink
+    skip_unless_symlinks_supported
   end
 
   def test_all_files_in_follows_symlinks_to_dirs
@@ -13,7 +13,7 @@ class Nanoc::Extra::FilesystemToolsTest < Nanoc::TestCase
       File.open("dir#{i}/foo.md", 'w') { |io| io.write('o hai') }
     end
     (1..10).each do |i|
-      File.symlink("../dir#{i}", "dir#{i-1}/sub")
+      File.symlink("../dir#{i}", "dir#{i - 1}/sub")
     end
 
     # Check
@@ -32,7 +32,7 @@ class Nanoc::Extra::FilesystemToolsTest < Nanoc::TestCase
       'dir0/sub/sub/sub/sub/sub/sub/sub/sub/sub/foo.md',
       'dir0/sub/sub/sub/sub/sub/sub/sub/sub/sub/sub/foo.md'
     ]
-    actual_files = Nanoc::Extra::FilesystemTools.all_files_in('dir0').sort
+    actual_files = Nanoc::Extra::FilesystemTools.all_files_in('dir0', nil).sort
     assert_equal expected_files, actual_files
   end
 
@@ -43,11 +43,11 @@ class Nanoc::Extra::FilesystemToolsTest < Nanoc::TestCase
       File.open("dir#{i}/foo.md", 'w') { |io| io.write('o hai') }
     end
     (1..15).each do |i|
-      File.symlink("../dir#{i}", "dir#{i-1}/sub")
+      File.symlink("../dir#{i}", "dir#{i - 1}/sub")
     end
 
     assert_raises Nanoc::Extra::FilesystemTools::MaxSymlinkDepthExceededError do
-      Nanoc::Extra::FilesystemTools.all_files_in('dir0')
+      Nanoc::Extra::FilesystemTools.all_files_in('dir0', nil)
     end
   end
 
@@ -60,8 +60,8 @@ class Nanoc::Extra::FilesystemToolsTest < Nanoc::TestCase
 
     File.symlink('../bar', 'foo/barlink')
 
-    expected_files = [ 'foo/barlink/y.md', 'foo/x.md' ]
-    actual_files   = Nanoc::Extra::FilesystemTools.all_files_in('foo').sort
+    expected_files = ['foo/barlink/y.md', 'foo/x.md']
+    actual_files   = Nanoc::Extra::FilesystemTools.all_files_in('foo', nil).sort
     assert_equal expected_files, actual_files
   end
 
@@ -73,8 +73,8 @@ class Nanoc::Extra::FilesystemToolsTest < Nanoc::TestCase
     File.symlink('../bar', 'dir/bar-link')
 
     # Check
-    expected_files = [ 'dir/bar-link', 'dir/foo' ]
-    actual_files   = Nanoc::Extra::FilesystemTools.all_files_in('dir').sort
+    expected_files = ['dir/bar-link', 'dir/foo']
+    actual_files   = Nanoc::Extra::FilesystemTools.all_files_in('dir', nil).sort
     assert_equal expected_files, actual_files
   end
 
@@ -93,7 +93,7 @@ class Nanoc::Extra::FilesystemToolsTest < Nanoc::TestCase
     File.open('foo', 'w') { |io| io.write('o hai') }
     File.symlink('foo', 'symlin-0')
     (1..7).each do |i|
-      File.symlink("symlink-#{i-1}", "symlink-#{i}")
+      File.symlink("symlink-#{i - 1}", "symlink-#{i}")
     end
 
     assert_raises Nanoc::Extra::FilesystemTools::MaxSymlinkDepthExceededError do
@@ -101,4 +101,44 @@ class Nanoc::Extra::FilesystemToolsTest < Nanoc::TestCase
     end
   end
 
+  def test_unwanted_dotfiles_not_found
+    # Write sample files
+    FileUtils.mkdir_p('dir')
+    File.open('dir/.DS_Store', 'w') { |io| io.write('o hai') }
+    File.open('dir/.htaccess', 'w') { |io| io.write('o hai') }
+
+    actual_files = Nanoc::Extra::FilesystemTools.all_files_in('dir', nil).sort
+    assert_equal [], actual_files
+  end
+
+  def test_user_dotfiles_are_valid_items
+    # Write sample files
+    FileUtils.mkdir_p('dir')
+    File.open('dir/.other', 'w') { |io| io.write('o hai') }
+
+    actual_files = Nanoc::Extra::FilesystemTools.all_files_in('dir', '**/.other').sort
+    assert_equal ['dir/.other'], actual_files
+  end
+
+  def test_multiple_user_dotfiles_are_valid_items
+    # Write sample files
+    FileUtils.mkdir_p('dir')
+    File.open('dir/.other', 'w') { |io| io.write('o hai') }
+    File.open('dir/.DS_Store', 'w') { |io| io.write('o hai') }
+
+    actual_files = Nanoc::Extra::FilesystemTools.all_files_in('dir', ['**/.other', '**/.DS_Store']).sort
+    assert_equal ['dir/.other', 'dir/.DS_Store'].sort, actual_files.sort
+  end
+
+  def test_unknown_pattern
+    # Write sample files
+    FileUtils.mkdir_p('dir')
+    File.open('dir/.other', 'w') { |io| io.write('o hai') }
+
+    pattern = { dotfiles: '**/.other' }
+
+    assert_raises Nanoc::Errors::GenericTrivial, "Do not know how to handle extra_files: #{pattern.inspect}" do
+      Nanoc::Extra::FilesystemTools.all_files_in('dir0', pattern)
+    end
+  end
 end
diff --git a/test/extra/test_link_collector.rb b/test/extra/test_link_collector.rb
index 78ae6e8..10ea95c 100644
--- a/test/extra/test_link_collector.rb
+++ b/test/extra/test_link_collector.rb
@@ -1,20 +1,26 @@
 # encoding: utf-8
 
 class Nanoc::Extra::LinkCollectorTest < Nanoc::TestCase
-
   def test_all
     # Create dummy data
     File.open('file-a.html', 'w') do |io|
-      io << %[<a href="http://example.com/">A 1</a>\n]
-      io << %[<a href="https://example.com/">A 2</a>\n]
-      io << %[<a href="stuff/"A 3></a>\n]
-      io << %[<a name="href-less-anchor">A 4</a>]
-      io << %[<a href="https://example.com/with-fragment#moo">A 5</a>\n]
+      io << %(<a href="http://example.com/">A 1</a>
+)
+      io << %(<a href="https://example.com/">A 2</a>
+)
+      io << %(<a href="stuff/"A 3></a>
+)
+      io << %(<a name="href-less-anchor">A 4</a>)
+      io << %(<a href="https://example.com/with-fragment#moo">A 5</a>
+)
     end
     File.open('file-b.html', 'w') do |io|
-      io << %[<a href="mailto:bob at example.com">B 1</a>\n]
-      io << %[<a href="../stuff">B 2</a>\n]
-      io << %[<a href="/stuff">B 3</a>\n]
+      io << %(<a href="mailto:bob at example.com">B 1</a>
+)
+      io << %(<a href="../stuff">B 2</a>
+)
+      io << %(<a href="/stuff">B 3</a>
+)
     end
 
     # Create validator
@@ -37,14 +43,20 @@ class Nanoc::Extra::LinkCollectorTest < Nanoc::TestCase
   def test_external
     # Create dummy data
     File.open('file-a.html', 'w') do |io|
-      io << %[<a href="http://example.com/">A 1</a>\n]
-      io << %[<a href="https://example.com/">A 2</a>\n]
-      io << %[<a href="stuff/"A 3></a>\n]
+      io << %(<a href="http://example.com/">A 1</a>
+)
+      io << %(<a href="https://example.com/">A 2</a>
+)
+      io << %(<a href="stuff/"A 3></a>
+)
     end
     File.open('file-b.html', 'w') do |io|
-      io << %[<a href="mailto:bob at example.com">B 1</a>\n]
-      io << %[<a href="../stuff">B 2</a>\n]
-      io << %[<a href="/stuff">B 3</a>\n]
+      io << %(<a href="mailto:bob at example.com">B 1</a>
+)
+      io << %(<a href="../stuff">B 2</a>
+)
+      io << %(<a href="/stuff">B 3</a>
+)
     end
 
     # Create validator
@@ -64,14 +76,20 @@ class Nanoc::Extra::LinkCollectorTest < Nanoc::TestCase
   def test_internal
     # Create dummy data
     File.open('file-a.html', 'w') do |io|
-      io << %[<a href="http://example.com/">A 1</a>\n]
-      io << %[<a href="https://example.com/">A 2</a>\n]
-      io << %[<a href="stuff/"A 3></a>\n]
+      io << %(<a href="http://example.com/">A 1</a>
+)
+      io << %(<a href="https://example.com/">A 2</a>
+)
+      io << %(<a href="stuff/"A 3></a>
+)
     end
     File.open('file-b.html', 'w') do |io|
-      io << %[<a href="mailto:bob at example.com">B 1</a>\n]
-      io << %[<a href="../stuff">B 2</a>\n]
-      io << %[<a href="/stuff">B 3</a>\n]
+      io << %(<a href="mailto:bob at example.com">B 1</a>
+)
+      io << %(<a href="../stuff">B 2</a>
+)
+      io << %(<a href="/stuff">B 3</a>
+)
     end
 
     # Create validator
@@ -87,5 +105,4 @@ class Nanoc::Extra::LinkCollectorTest < Nanoc::TestCase
     assert_includes hrefs, '../stuff'
     assert_includes hrefs, '/stuff'
   end
-
 end
diff --git a/test/extra/test_piper.rb b/test/extra/test_piper.rb
index 15c8c40..fb994cd 100644
--- a/test/extra/test_piper.rb
+++ b/test/extra/test_piper.rb
@@ -1,7 +1,6 @@
 # encoding: utf-8
 
 class Nanoc::Extra::PiperTest < Nanoc::TestCase
-
   def test_basic
     stdout = StringIO.new
     stderr = StringIO.new
@@ -11,7 +10,7 @@ class Nanoc::Extra::PiperTest < Nanoc::TestCase
     File.open('foo.txt', 'w') { |io| io.write('hi') }
     File.open('bar.txt', 'w') { |io| io.write('ho') }
 
-    piper = Nanoc::Extra::Piper.new(:stdout => stdout, :stderr => stderr)
+    piper = Nanoc::Extra::Piper.new(stdout: stdout, stderr: stderr)
     piper.run(cmd, nil)
 
     assert_match(/foo\.txt/, stdout.string)
@@ -26,7 +25,7 @@ class Nanoc::Extra::PiperTest < Nanoc::TestCase
     input = 'Hello World!'
     cmd = %w( cat )
 
-    piper = Nanoc::Extra::Piper.new(:stdout => stdout, :stderr => stderr)
+    piper = Nanoc::Extra::Piper.new(stdout: stdout, stderr: stderr)
     piper.run(cmd, input)
 
     assert_equal(input, stdout.string)
@@ -39,10 +38,9 @@ class Nanoc::Extra::PiperTest < Nanoc::TestCase
 
     cmd = %w( cat kafhawilgoiwaejagoualjdsfilofiewaguihaifeowuiga )
 
-    piper = Nanoc::Extra::Piper.new(:stdout => stdout, :stderr => stderr)
+    piper = Nanoc::Extra::Piper.new(stdout: stdout, stderr: stderr)
     assert_raises(Nanoc::Extra::Piper::Error) do
       piper.run(cmd, nil)
     end
   end
-
 end
diff --git a/test/extra/test_vcs.rb b/test/extra/test_vcs.rb
index bb29558..6288588 100644
--- a/test/extra/test_vcs.rb
+++ b/test/extra/test_vcs.rb
@@ -1,7 +1,6 @@
 # encoding: utf-8
 
 class Nanoc::Extra::VCSTest < Nanoc::TestCase
-
   def test_named
     assert_nil(Nanoc::Extra::VCS.named(:lkasjdlkfjlkasdfkj))
 
@@ -16,5 +15,4 @@ class Nanoc::Extra::VCSTest < Nanoc::TestCase
     assert_raises(NotImplementedError) { vcs.remove('x')    }
     assert_raises(NotImplementedError) { vcs.move('x', 'y') }
   end
-
 end
diff --git a/test/extra/validators/test_links.rb b/test/extra/validators/test_links.rb
index f722bb4..3a19aa6 100644
--- a/test/extra/validators/test_links.rb
+++ b/test/extra/validators/test_links.rb
@@ -1,5 +1,4 @@
 # encoding: utf-8
 
 class Nanoc::Extra::Validators::LinksTest < Nanoc::TestCase
-
 end
diff --git a/test/extra/validators/test_w3c.rb b/test/extra/validators/test_w3c.rb
index 0ea0451..63574d5 100644
--- a/test/extra/validators/test_w3c.rb
+++ b/test/extra/validators/test_w3c.rb
@@ -1,19 +1,18 @@
 # encoding: utf-8
 
 class Nanoc::Extra::Validators::W3CTest < Nanoc::TestCase
-
   def test_simple
     if_have 'w3c_validators' do
-      with_site do |site|
+      with_site do |_site|
         # Create some sample files
-        %w{ foo bar baz }.each do |filename|
-          %w{ xxx yyy }.each do |extension|
-            File.open("output/#{filename}.#{extension}", 'w') { |io| io.write("hello") }
+        %w(foo bar baz).each do |filename|
+          %w(xxx yyy).each do |extension|
+            File.open("output/#{filename}.#{extension}", 'w') { |io| io.write('hello') }
           end
         end
 
         # Create validator
-        w3c = Nanoc::Extra::Validators::W3C.new('.', [ :html ])
+        w3c = Nanoc::Extra::Validators::W3C.new('.', [:html])
 
         # Run
         w3c.run
@@ -23,9 +22,9 @@ class Nanoc::Extra::Validators::W3CTest < Nanoc::TestCase
 
   def test_with_unknown_types
     if_have 'w3c_validators' do
-      with_site do |site|
+      with_site do |_site|
         # Create validator
-        w3c = Nanoc::Extra::Validators::W3C.new('.', [ :foo ])
+        w3c = Nanoc::Extra::Validators::W3C.new('.', [:foo])
 
         # Test
         exception = assert_raises Nanoc::Errors::GenericTrivial do
@@ -35,5 +34,4 @@ class Nanoc::Extra::Validators::W3CTest < Nanoc::TestCase
       end
     end
   end
-
 end
diff --git a/test/filters/test_asciidoc.rb b/test/filters/test_asciidoc.rb
index a288cae..04fc69b 100644
--- a/test/filters/test_asciidoc.rb
+++ b/test/filters/test_asciidoc.rb
@@ -1,16 +1,14 @@
 # encoding: utf-8
 
 class Nanoc::Filters::AsciiDocTest < Nanoc::TestCase
-
   def test_filter
-    skip_unless_have_command "asciidoc"
+    skip_unless_have_command 'asciidoc'
 
     # Create filter
     filter = ::Nanoc::Filters::AsciiDoc.new
 
     # Run filter
-    result = filter.setup_and_run("== Blah blah")
+    result = filter.setup_and_run('== Blah blah')
     assert_match %r{<h2 id="_blah_blah">Blah blah</h2>}, result
   end
-
 end
diff --git a/test/filters/test_bluecloth.rb b/test/filters/test_bluecloth.rb
index 19fcebe..081ec8d 100644
--- a/test/filters/test_bluecloth.rb
+++ b/test/filters/test_bluecloth.rb
@@ -1,16 +1,14 @@
 # encoding: utf-8
 
 class Nanoc::Filters::BlueClothTest < Nanoc::TestCase
-
   def test_filter
     if_have 'bluecloth' do
       # Create filter
       filter = ::Nanoc::Filters::BlueCloth.new
 
       # Run filter
-      result = filter.setup_and_run("> Quote")
+      result = filter.setup_and_run('> Quote')
       assert_match %r{<blockquote>\s*<p>Quote</p>\s*</blockquote>}, result
     end
   end
-
 end
diff --git a/test/filters/test_coderay.rb b/test/filters/test_coderay.rb
index 1f748ab..35c4f65 100644
--- a/test/filters/test_coderay.rb
+++ b/test/filters/test_coderay.rb
@@ -1,7 +1,6 @@
 # encoding: utf-8
 
 class Nanoc::Filters::CodeRayTest < Nanoc::TestCase
-
   def test_filter_without_language
     if_have 'coderay' do
       # Get filter
@@ -22,7 +21,7 @@ class Nanoc::Filters::CodeRayTest < Nanoc::TestCase
 
       # Run filter
       code = "def some_function ; x = blah.foo ; x.bar 'xyzzy' ; end"
-      result = filter.setup_and_run(code, :language => 'ruby')
+      result = filter.setup_and_run(code, language: 'ruby')
       assert_match %r{^<span class="keyword">def</span> <span class="function">some_function</span>}, result
     end
   end
@@ -34,9 +33,8 @@ class Nanoc::Filters::CodeRayTest < Nanoc::TestCase
 
       # Run filter
       code = "def some_function ; x = blah.foo ; x.bar 'xyzzy' ; end"
-      result = filter.setup_and_run(code, :language => 'skldfhjsdhfjszfnocmluhfixfmersumulh')
+      result = filter.setup_and_run(code, language: 'skldfhjsdhfjszfnocmluhfixfmersumulh')
       assert_equal code, result
     end
   end
-
 end
diff --git a/test/filters/test_coffeescript.rb b/test/filters/test_coffeescript.rb
index 772ffc5..742ea96 100644
--- a/test/filters/test_coffeescript.rb
+++ b/test/filters/test_coffeescript.rb
@@ -1,7 +1,6 @@
 # encoding: utf-8
 
 class Nanoc::Filters::CoffeeScriptTest < Nanoc::TestCase
-
   def test_filter
     if_have 'coffee-script' do
       # Create filter
@@ -9,8 +8,7 @@ class Nanoc::Filters::CoffeeScriptTest < Nanoc::TestCase
 
       # Run filter (no assigns)
       result = filter.setup_and_run('alert 42')
-      assert_equal("(function() { alert(42); }).call(this); ", result.gsub(/\s+/, ' '))
+      assert_equal('(function() { alert(42); }).call(this); ', result.gsub(/\s+/, ' '))
     end
   end
-
 end
diff --git a/test/filters/test_colorize_syntax.rb b/test/filters/test_colorize_syntax.rb
index 642474a..9c93132 100644
--- a/test/filters/test_colorize_syntax.rb
+++ b/test/filters/test_colorize_syntax.rb
@@ -1,7 +1,6 @@
 # encoding: utf-8
 
 class Nanoc::Filters::ColorizeSyntaxTest < Nanoc::TestCase
-
   CODERAY_PRE  = '<div class="CodeRay"><div class="code">'
   CODERAY_POST = '</div></div>'
 
@@ -30,7 +29,7 @@ class Nanoc::Filters::ColorizeSyntaxTest < Nanoc::TestCase
       expected_output = input # because we are using a dummy
 
       # Run filter
-      actual_output = filter.setup_and_run(input, :default_colorizer => :dummy)
+      actual_output = filter.setup_and_run(input, default_colorizer: :dummy)
       assert_equal(expected_output, actual_output)
     end
   end
@@ -41,7 +40,7 @@ class Nanoc::Filters::ColorizeSyntaxTest < Nanoc::TestCase
       input.freeze
 
       filter = ::Nanoc::Filters::ColorizeSyntax.new
-      filter.setup_and_run(input, :default_colorizer => :dummy)
+      filter.setup_and_run(input, default_colorizer: :dummy)
     end
   end
 
@@ -62,10 +61,10 @@ class Nanoc::Filters::ColorizeSyntaxTest < Nanoc::TestCase
   </body>
 </html>
 EOS
-      expected_output_regex = %r[^<!DOCTYPE html>\s*<html>\s*<head>\s*<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\">\s*<title>Foo</title>\s*</head>\s*<body>\s*<pre title="moo"><code class="language-ruby"># comment</code></pre>\s*</body>\s*</html>]
+      expected_output_regex = %r{^<!DOCTYPE html>\s*<html>\s*<head>\s*<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\">\s*<title>Foo</title>\s*</head>\s*<body>\s*<pre title="moo"><code class="language-ruby"># comment</code></pre>\s*</body>\s*</html>}
 
       # Run filter
-      actual_output = filter.setup_and_run(input, :default_colorizer => :dummy, :is_fullpage => true)
+      actual_output = filter.setup_and_run(input, default_colorizer: :dummy, is_fullpage: true)
       assert_match expected_output_regex, actual_output
     end
   end
@@ -76,7 +75,8 @@ EOS
       filter = ::Nanoc::Filters::ColorizeSyntax.new
 
       # Get input and expected output
-      input = %[<pre title="moo"><code>#!ruby\n# comment</code></pre>]
+      input = %(<pre title="moo"><code>#!ruby
+# comment</code></pre>)
       expected_output = CODERAY_PRE + '<pre title="moo"><code class="language-ruby"><span class="comment"># comment</span></code></pre>' + CODERAY_POST
 
       # Run filter
@@ -91,7 +91,9 @@ EOS
       filter = ::Nanoc::Filters::ColorizeSyntax.new
 
       # Get input and expected output
-      input = %[<pre title="moo"><code>def moo ; end\n#!ruby\n# comment</code></pre>]
+      input = %(<pre title="moo"><code>def moo ; end
+#!ruby
+# comment</code></pre>)
       expected_output = "<pre title=\"moo\"><code>def moo ; end\n#!ruby\n# comment</code></pre>"
 
       # Run filter
@@ -106,8 +108,10 @@ EOS
       filter = ::Nanoc::Filters::ColorizeSyntax.new
 
       # Get input and expected output
-      input = %[<pre title="moo"><code class="language-ruby">#!ruby\n# comment</code></pre>]
-      expected_output = CODERAY_PRE + %[<pre title="moo"><code class="language-ruby"><span class="doctype">#!ruby</span>\n<span class="comment"># comment</span></code></pre>] + CODERAY_POST
+      input = %(<pre title="moo"><code class="language-ruby">#!ruby
+# comment</code></pre>)
+      expected_output = CODERAY_PRE + %(<pre title="moo"><code class="language-ruby"><span class="doctype">#!ruby</span>
+<span class="comment"># comment</span></code></pre>) + CODERAY_POST
 
       # Run filter
       actual_output = filter.setup_and_run(input)
@@ -132,7 +136,7 @@ EOS
 
   def test_pygmentize
     if_have 'nokogiri' do
-      skip_unless_have_command "pygmentize"
+      skip_unless_have_command 'pygmentize'
 
       # Create filter
       filter = ::Nanoc::Filters::ColorizeSyntax.new
@@ -142,13 +146,13 @@ EOS
       expected_output = '<pre title="moo"><code class="language-ruby"><span class="c1"># comment</span></code></pre>'
 
       # Run filter
-      actual_output = filter.setup_and_run(input, :colorizers => { :ruby => :pygmentize })
+      actual_output = filter.setup_and_run(input, colorizers: { ruby: :pygmentize })
       assert_equal(expected_output, actual_output)
     end
   end
 
   def test_pygmentsrb
-    skip "pygments.rb does not support Windows" if on_windows?
+    skip 'pygments.rb does not support Windows' if on_windows?
     if_have 'pygments', 'nokogiri' do
       # Create filter
       filter = ::Nanoc::Filters::ColorizeSyntax.new
@@ -158,24 +162,26 @@ EOS
       expected_output = '<pre title="moo"><code class="language-ruby"><span class="c1"># comment…</span></code></pre>'
 
       # Run filter
-      actual_output = filter.setup_and_run(input, :colorizers => { :ruby => :pygmentsrb })
+      actual_output = filter.setup_and_run(input, colorizers: { ruby: :pygmentsrb })
       assert_equal(expected_output, actual_output)
     end
   end
 
   def test_simon_highlight
     if_have 'nokogiri' do
-      skip_unless_have_command "highlight"
+      skip_unless_have_command 'highlight'
 
       # Create filter
       filter = ::Nanoc::Filters::ColorizeSyntax.new
 
       # Get input and expected output
-      input = %Q[<pre title="moo"><code class="language-ruby">\n# comment\n</code></pre>]
+      input = %(<pre title="moo"><code class="language-ruby">
+# comment
+</code></pre>)
       expected_output = '<pre title="moo"><code class="language-ruby"><span class="hl slc"># comment</span></code></pre>'
 
       # Run filter
-      actual_output = filter.setup_and_run(input, :default_colorizer => :simon_highlight)
+      actual_output = filter.setup_and_run(input, default_colorizer: :simon_highlight)
       assert_equal(expected_output, actual_output)
     end
   end
@@ -187,7 +193,7 @@ EOS
 
       # Run filter
       assert_raises RuntimeError do
-        filter.setup_and_run('<p>whatever</p>', :syntax => :kasflwafhaweoineurl)
+        filter.setup_and_run('<p>whatever</p>', syntax: :kasflwafhaweoineurl)
       end
     end
   end
@@ -202,7 +208,7 @@ EOS
       expected_output = '<p>foo<br/>bar</p>'
 
       # Run filter
-      actual_output = filter.setup_and_run(input, :syntax => :xml)
+      actual_output = filter.setup_and_run(input, syntax: :xml)
       assert_equal(expected_output, actual_output)
     end
   end
@@ -217,13 +223,13 @@ EOS
       expected_output = '<p>foo<br />bar</p>'
 
       # Run filter
-      actual_output = filter.setup_and_run(input, :syntax => :xhtml)
+      actual_output = filter.setup_and_run(input, syntax: :xhtml)
       assert_equal(expected_output, actual_output)
     end
   end
 
   def test_colorize_syntax_with_default_colorizer
-    skip_unless_have_command "pygmentize"
+    skip_unless_have_command 'pygmentize'
 
     if_have 'nokogiri' do
       # Create filter
@@ -234,7 +240,7 @@ EOS
       expected_output = '<pre><code class="language-ruby"><span class="nb">puts</span> <span class="s2">"foo"</span></code></pre>'
 
       # Run filter
-      actual_output = filter.setup_and_run(input, :default_colorizer => :pygmentize)
+      actual_output = filter.setup_and_run(input, default_colorizer: :pygmentize)
       assert_equal(expected_output, actual_output)
     end
   end
@@ -252,13 +258,13 @@ EOS
         input = '<pre><code class="language-ruby">puts "foo"</code></pre>'
 
         # Run filter
-        [ :albino, :pygmentize, :simon_highlight ].each do |colorizer|
+        [:albino, :pygmentize, :simon_highlight].each do |colorizer|
           begin
             input = '<pre><code class="language-ruby">puts "foo"</code></pre>'
             filter.setup_and_run(
               input,
-              :colorizers => { :ruby => colorizer })
-            flunk "expected colorizer to raise if no executable is available"
+              colorizers: { ruby: colorizer })
+            flunk 'expected colorizer to raise if no executable is available'
           rescue
           end
         end
@@ -335,7 +341,7 @@ EOS
       expected_output = '<code class="language-ruby"># comment</code>'
 
       # Run filter
-      actual_output = filter.setup_and_run(input, :outside_pre => false)
+      actual_output = filter.setup_and_run(input, outside_pre: false)
       assert_equal(expected_output, actual_output)
     end
   end
@@ -350,7 +356,7 @@ EOS
       expected_output = '<code class="language-ruby"><span class="comment"># comment</span></code>'
 
       # Run filter
-      actual_output = filter.setup_and_run(input, :outside_pre => true)
+      actual_output = filter.setup_and_run(input, outside_pre: true)
       assert_equal(expected_output, actual_output)
     end
   end
@@ -361,7 +367,7 @@ EOS
       filter = ::Nanoc::Filters::ColorizeSyntax.new
 
       # Simple test
-      assert_equal "  bar", filter.send(:strip, "\n  bar")
+      assert_equal '  bar', filter.send(:strip, "\n  bar")
 
       # Get input and expected output
       input = <<EOS
@@ -407,7 +413,7 @@ after
 EOS
 
       # Run filter
-      actual_output = filter.setup_and_run(input, :default_colorizer => :rouge)
+      actual_output = filter.setup_and_run(input, default_colorizer: :rouge)
       assert_equal(expected_output, actual_output)
     end
   end
@@ -434,9 +440,8 @@ after
 EOS
 
       # Run filter
-      actual_output = filter.setup_and_run(input, :default_colorizer => :rouge, :rouge => { :css_class => 'my-class' })
+      actual_output = filter.setup_and_run(input, default_colorizer: :rouge, rouge: { css_class: 'my-class' })
       assert_equal(expected_output, actual_output)
     end
   end
-
 end
diff --git a/test/filters/test_erb.rb b/test/filters/test_erb.rb
index cb37343..1006edf 100644
--- a/test/filters/test_erb.rb
+++ b/test/filters/test_erb.rb
@@ -1,10 +1,9 @@
 # encoding: utf-8
 
 class Nanoc::Filters::ERBTest < Nanoc::TestCase
-
   def test_filter_with_instance_variable
     # Create filter
-    filter = ::Nanoc::Filters::ERB.new({ :location => 'a cheap motel' })
+    filter = ::Nanoc::Filters::ERB.new({ location: 'a cheap motel' })
 
     # Run filter
     result = filter.setup_and_run('<%= "I was hiding in #{@location}." %>')
@@ -13,7 +12,7 @@ class Nanoc::Filters::ERBTest < Nanoc::TestCase
 
   def test_filter_with_instance_method
     # Create filter
-    filter = ::Nanoc::Filters::ERB.new({ :location => 'a cheap motel' })
+    filter = ::Nanoc::Filters::ERB.new({ location: 'a cheap motel' })
 
     # Run filter
     result = filter.setup_and_run('<%= "I was hiding in #{location}." %>')
@@ -29,9 +28,9 @@ class Nanoc::Filters::ERBTest < Nanoc::TestCase
 
     # Create filter
     filter = ::Nanoc::Filters::ERB.new({
-      :item     => item,
-      :item_rep => item_rep,
-      :location => 'a cheap motel'
+      item: item,
+      item_rep: item_rep,
+      location: 'a cheap motel'
     })
 
     # Run filter
@@ -48,7 +47,7 @@ class Nanoc::Filters::ERBTest < Nanoc::TestCase
 
   def test_filter_with_yield
     # Create filter
-    filter = ::Nanoc::Filters::ERB.new({ :content => 'a cheap motel' })
+    filter = ::Nanoc::Filters::ERB.new({ content: 'a cheap motel' })
 
     # Run filter
     result = filter.setup_and_run('<%= "I was hiding in #{yield}." %>')
@@ -57,7 +56,7 @@ class Nanoc::Filters::ERBTest < Nanoc::TestCase
 
   def test_filter_with_yield_without_content
     # Create filter
-    filter = ::Nanoc::Filters::ERB.new({ :location => 'a cheap motel' })
+    filter = ::Nanoc::Filters::ERB.new({ location: 'a cheap motel' })
 
     # Run filter
     assert_raises LocalJumpError do
@@ -72,21 +71,21 @@ class Nanoc::Filters::ERBTest < Nanoc::TestCase
 
     # Set up
     filter = ::Nanoc::Filters::ERB.new
-    File.open('moo', 'w') { |io| io.write("one miiillion dollars") }
+    File.open('moo', 'w') { |io| io.write('one miiillion dollars') }
 
     # Without
-    res = filter.setup_and_run('<%= File.read("moo") %>', :safe_level => nil)
+    res = filter.setup_and_run('<%= File.read("moo") %>', safe_level: nil)
     assert_equal 'one miiillion dollars', res
 
     # With
     assert_raises(SecurityError) do
-      res = filter.setup_and_run('<%= File.read("moo") %>', :safe_level => 3)
+      res = filter.setup_and_run('<%= File.read("moo") %>', safe_level: 3)
     end
   end
 
   def test_trim_mode
     # Set up
-    filter = ::Nanoc::Filters::ERB.new({ :location => 'a cheap motel' })
+    filter = ::Nanoc::Filters::ERB.new({ location: 'a cheap motel' })
     $trim_mode_works = false
 
     # Without
@@ -94,14 +93,13 @@ class Nanoc::Filters::ERBTest < Nanoc::TestCase
     refute $trim_mode_works
 
     # With
-    filter.setup_and_run('% $trim_mode_works = true', :trim_mode => '%')
+    filter.setup_and_run('% $trim_mode_works = true', trim_mode: '%')
     assert $trim_mode_works
   end
 
   def test_locals
     filter = ::Nanoc::Filters::ERB.new
-    result = filter.setup_and_run('<%= @local %>', :locals => { :local => 123 })
+    result = filter.setup_and_run('<%= @local %>', locals: { local: 123 })
     assert_equal '123', result
   end
-
 end
diff --git a/test/filters/test_erubis.rb b/test/filters/test_erubis.rb
index 690e9bb..18054b9 100644
--- a/test/filters/test_erubis.rb
+++ b/test/filters/test_erubis.rb
@@ -1,11 +1,10 @@
 # encoding: utf-8
 
 class Nanoc::Filters::ErubisTest < Nanoc::TestCase
-
   def test_filter_with_instance_variable
     if_have 'erubis' do
       # Create filter
-      filter = ::Nanoc::Filters::Erubis.new({ :location => 'a cheap motel' })
+      filter = ::Nanoc::Filters::Erubis.new({ location: 'a cheap motel' })
 
       # Run filter
       result = filter.setup_and_run('<%= "I was hiding in #{@location}." %>')
@@ -16,7 +15,7 @@ class Nanoc::Filters::ErubisTest < Nanoc::TestCase
   def test_filter_with_instance_method
     if_have 'erubis' do
       # Create filter
-      filter = ::Nanoc::Filters::Erubis.new({ :location => 'a cheap motel' })
+      filter = ::Nanoc::Filters::Erubis.new({ location: 'a cheap motel' })
 
       # Run filter
       result = filter.setup_and_run('<%= "I was hiding in #{location}." %>')
@@ -45,7 +44,7 @@ class Nanoc::Filters::ErubisTest < Nanoc::TestCase
   def test_filter_with_yield
     if_have 'erubis' do
       # Create filter
-      filter = ::Nanoc::Filters::Erubis.new({ :content => 'a cheap motel' })
+      filter = ::Nanoc::Filters::Erubis.new({ content: 'a cheap motel' })
 
       # Run filter
       result = filter.setup_and_run('<%= "I was hiding in #{yield}." %>')
@@ -56,7 +55,7 @@ class Nanoc::Filters::ErubisTest < Nanoc::TestCase
   def test_filter_with_yield_without_content
     if_have 'erubis' do
       # Create filter
-      filter = ::Nanoc::Filters::Erubis.new({ :location => 'a cheap motel' })
+      filter = ::Nanoc::Filters::Erubis.new({ location: 'a cheap motel' })
 
       # Run filter
       assert_raises LocalJumpError do
@@ -72,5 +71,4 @@ class Nanoc::Filters::ErubisTest < Nanoc::TestCase
       assert_equal 'stuffstuff', result
     end
   end
-
 end
diff --git a/test/filters/test_haml.rb b/test/filters/test_haml.rb
index 78e5f17..c91af74 100644
--- a/test/filters/test_haml.rb
+++ b/test/filters/test_haml.rb
@@ -1,11 +1,10 @@
 # encoding: utf-8
 
 class Nanoc::Filters::HamlTest < Nanoc::TestCase
-
   def test_filter
     if_have 'haml' do
       # Create filter
-      filter = ::Nanoc::Filters::Haml.new({ :question => 'Is this the Payne residence?' })
+      filter = ::Nanoc::Filters::Haml.new({ question: 'Is this the Payne residence?' })
 
       # Run filter (no assigns)
       result = filter.setup_and_run('%html')
@@ -24,14 +23,14 @@ class Nanoc::Filters::HamlTest < Nanoc::TestCase
   def test_filter_with_params
     if_have 'haml' do
       # Create filter
-      filter = ::Nanoc::Filters::Haml.new({ :foo => 'bar' })
+      filter = ::Nanoc::Filters::Haml.new({ foo: 'bar' })
 
       # Check with HTML5
-      result = filter.setup_and_run('%img', :format => :html5)
+      result = filter.setup_and_run('%img', format: :html5)
       assert_match(/<img>/, result)
 
       # Check with XHTML
-      result = filter.setup_and_run('%img', :format => :xhtml)
+      result = filter.setup_and_run('%img', format: :xhtml)
       assert_match(/<img\s*\/>/, result)
     end
   end
@@ -39,7 +38,7 @@ class Nanoc::Filters::HamlTest < Nanoc::TestCase
   def test_filter_error
     if_have 'haml' do
       # Create filter
-      filter = ::Nanoc::Filters::Haml.new({ :foo => 'bar' })
+      filter = ::Nanoc::Filters::Haml.new({ foo: 'bar' })
 
       # Run filter
       raised = false
@@ -57,7 +56,7 @@ class Nanoc::Filters::HamlTest < Nanoc::TestCase
   def test_filter_with_yield
     if_have 'haml' do
       # Create filter
-      filter = ::Nanoc::Filters::Haml.new({ :content => 'Is this the Payne residence?' })
+      filter = ::Nanoc::Filters::Haml.new({ content: 'Is this the Payne residence?' })
 
       # Run filter
       result = filter.setup_and_run('%p= yield')
@@ -68,7 +67,7 @@ class Nanoc::Filters::HamlTest < Nanoc::TestCase
   def test_filter_with_yield_without_content
     if_have 'haml' do
       # Create filter
-      filter = ::Nanoc::Filters::Haml.new({ :location => 'Is this the Payne residence?' })
+      filter = ::Nanoc::Filters::Haml.new({ location: 'Is this the Payne residence?' })
 
       # Run filter
       assert_raises LocalJumpError do
@@ -90,5 +89,4 @@ class Nanoc::Filters::HamlTest < Nanoc::TestCase
       assert_match(/Max Payne&#x000A;Mona Sax/, result)
     end
   end
-
 end
diff --git a/test/filters/test_handlebars.rb b/test/filters/test_handlebars.rb
index b1148b3..1f0732e 100644
--- a/test/filters/test_handlebars.rb
+++ b/test/filters/test_handlebars.rb
@@ -1,26 +1,25 @@
 # encoding: utf-8
 
 class Nanoc::Filters::HandlebarsTest < Nanoc::TestCase
-
   def test_filter
     if_have 'handlebars' do
       # Create data
       item = Nanoc::Item.new(
         'content',
-        { :title => 'Max Payne', :protagonist => 'Max Payne', :location => 'here' },
+        { title: 'Max Payne', protagonist: 'Max Payne', location: 'here' },
         '/games/max-payne/')
       layout = Nanoc::Layout.new(
         'layout content',
-        { :name => 'Max Payne' },
+        { name: 'Max Payne' },
         '/default/')
-      config = { :animals => 'cats and dogs' }
+      config = { animals: 'cats and dogs' }
 
       # Create filter
       assigns = {
-        :item    => item,
-        :layout  => layout,
-        :config  => config,
-        :content => 'No Payne No Gayne'
+        item: item,
+        layout: layout,
+        config: config,
+        content: 'No Payne No Gayne'
       }
       filter = ::Nanoc::Filters::Handlebars.new(assigns)
 
@@ -39,13 +38,13 @@ class Nanoc::Filters::HandlebarsTest < Nanoc::TestCase
       # Create data
       item = Nanoc::Item.new(
         'content',
-        { :title => 'Max Payne', :protagonist => 'Max Payne', :location => 'here' },
+        { title: 'Max Payne', protagonist: 'Max Payne', location: 'here' },
         '/games/max-payne/')
 
       # Create filter
       assigns = {
-        :item    => item,
-        :content => 'No Payne No Gayne'
+        item: item,
+        content: 'No Payne No Gayne'
       }
       filter = ::Nanoc::Filters::Handlebars.new(assigns)
 
@@ -54,5 +53,4 @@ class Nanoc::Filters::HandlebarsTest < Nanoc::TestCase
       assert_equal('Max Payne says: No Payne No Gayne.', result)
     end
   end
-
 end
diff --git a/test/filters/test_kramdown.rb b/test/filters/test_kramdown.rb
index fc9c4ab..23d26e8 100644
--- a/test/filters/test_kramdown.rb
+++ b/test/filters/test_kramdown.rb
@@ -1,16 +1,28 @@
 # encoding: utf-8
 
 class Nanoc::Filters::KramdownTest < Nanoc::TestCase
-
   def test_filter
     if_have 'kramdown' do
       # Create filter
       filter = ::Nanoc::Filters::Kramdown.new
 
       # Run filter
-      result = filter.setup_and_run("This is _so_ **cool**!")
+      result = filter.setup_and_run('This is _so_ **cool**!')
       assert_equal("<p>This is <em>so</em> <strong>cool</strong>!</p>\n", result)
     end
   end
 
+  def test_warnings
+    if_have 'kramdown' do
+      # Create filter
+      filter = ::Nanoc::Filters::Kramdown.new
+
+      # Run filter
+      io = capturing_stdio do
+        filter.setup_and_run('{:foo}this is bogus')
+      end
+      assert_empty io[:stdout]
+      assert_equal "kramdown warning: Found span IAL after text - ignoring it\n", io[:stderr]
+    end
+  end
 end
diff --git a/test/filters/test_less.rb b/test/filters/test_less.rb
index 22f4a3b..e5b9ebe 100644
--- a/test/filters/test_less.rb
+++ b/test/filters/test_less.rb
@@ -1,14 +1,13 @@
 # encoding: utf-8
 
 class Nanoc::Filters::LessTest < Nanoc::TestCase
-
   def test_filter
     if_have 'less' do
       # Create item
-      @item = Nanoc::Item.new("blah", { :content_filename => 'content/foo/bar.txt' }, '/foo/bar/')
+      @item = Nanoc::Item.new('blah', { content_filename: 'content/foo/bar.txt' }, '/foo/bar/')
 
       # Create filter
-      filter = ::Nanoc::Filters::Less.new(:item => @item, :items => [ @item ])
+      filter = ::Nanoc::Filters::Less.new(item: @item, items: [@item])
 
       # Run filter
       result = filter.setup_and_run('.foo { bar: 1 + 1 }')
@@ -23,10 +22,10 @@ class Nanoc::Filters::LessTest < Nanoc::TestCase
       File.open('content/foo/bar/imported_file.less', 'w') { |io| io.write('p { color: red; }') }
 
       # Create item
-      @item = Nanoc::Item.new("blah", { :content_filename => 'content/foo/bar.txt' }, '/foo/bar/')
+      @item = Nanoc::Item.new('blah', { content_filename: 'content/foo/bar.txt' }, '/foo/bar/')
 
       # Create filter
-      filter = ::Nanoc::Filters::Less.new(:item => @item, :items => [ @item ])
+      filter = ::Nanoc::Filters::Less.new(item: @item, items: [@item])
 
       # Run filter
       result = filter.setup_and_run('@import "content/foo/bar/imported_file.less";')
@@ -42,10 +41,10 @@ class Nanoc::Filters::LessTest < Nanoc::TestCase
 
       # Create item
       File.open('content/foo/bar.txt', 'w') { |io| io.write('meh') }
-      @item = Nanoc::Item.new("blah", { :content_filename => 'content/foo/bar.txt' }, '/foo/bar/')
+      @item = Nanoc::Item.new('blah', { content_filename: 'content/foo/bar.txt' }, '/foo/bar/')
 
       # Create filter
-      filter = ::Nanoc::Filters::Less.new(:item => @item, :items => [ @item ])
+      filter = ::Nanoc::Filters::Less.new(item: @item, items: [@item])
 
       # Run filter
       result = filter.setup_and_run('@import "bar/imported_file.less";')
@@ -62,7 +61,7 @@ class Nanoc::Filters::LessTest < Nanoc::TestCase
           io.write('@import "b.less";')
         end
         File.open('content/b.less', 'w') do |io|
-          io.write("p { color: red; }")
+          io.write('p { color: red; }')
         end
 
         # Update rules
@@ -92,7 +91,7 @@ class Nanoc::Filters::LessTest < Nanoc::TestCase
 
         # Update included file
         File.open('content/b.less', 'w') do |io|
-          io.write("p { color: blue; }")
+          io.write('p { color: blue; }')
         end
 
         # Recompile
@@ -111,15 +110,14 @@ class Nanoc::Filters::LessTest < Nanoc::TestCase
   def test_compression
     if_have 'less' do
       # Create item
-      @item = Nanoc::Item.new("blah", { :content_filename => 'content/foo/bar.txt' }, '/foo/bar/')
+      @item = Nanoc::Item.new('blah', { content_filename: 'content/foo/bar.txt' }, '/foo/bar/')
 
       # Create filter
-      filter = ::Nanoc::Filters::Less.new(:item => @item, :items => [ @item ])
+      filter = ::Nanoc::Filters::Less.new(item: @item, items: [@item])
 
       # Run filter with compress option
-      result = filter.setup_and_run('.foo { bar: a; } .bar { foo: b; }', :compress => true)
+      result = filter.setup_and_run('.foo { bar: a; } .bar { foo: b; }', compress: true)
       assert_match(/^\.foo\{bar:a\}\n?\.bar\{foo:b\}/, result)
     end
   end
-
 end
diff --git a/test/filters/test_markaby.rb b/test/filters/test_markaby.rb
index 27fca5c..459716c 100644
--- a/test/filters/test_markaby.rb
+++ b/test/filters/test_markaby.rb
@@ -1,11 +1,10 @@
 # encoding: utf-8
 
 class Nanoc::Filters::MarkabyTest < Nanoc::TestCase
-
   def test_filter
     # Don’t run this test on 1.9.x, because it breaks and it annoys me
     if RUBY_VERSION >= '1.9'
-      skip "Markaby is not compatible with 1.9.x"
+      skip 'Markaby is not compatible with 1.9.x'
       return
     end
 
@@ -15,8 +14,7 @@ class Nanoc::Filters::MarkabyTest < Nanoc::TestCase
 
       # Run filter
       result = filter.setup_and_run("html do\nend")
-      assert_equal("<html></html>", result)
+      assert_equal('<html></html>', result)
     end
   end
-
 end
diff --git a/test/filters/test_maruku.rb b/test/filters/test_maruku.rb
index b361905..cb1ffa1 100644
--- a/test/filters/test_maruku.rb
+++ b/test/filters/test_maruku.rb
@@ -1,16 +1,14 @@
 # encoding: utf-8
 
 class Nanoc::Filters::MarukuTest < Nanoc::TestCase
-
   def test_filter
     if_have 'maruku' do
       # Create filter
       filter = ::Nanoc::Filters::Maruku.new
 
       # Run filter
-      result = filter.setup_and_run("This is _so_ *cool*!")
-      assert_equal("<p>This is <em>so</em> <em>cool</em>!</p>", result.strip)
+      result = filter.setup_and_run('This is _so_ *cool*!')
+      assert_equal('<p>This is <em>so</em> <em>cool</em>!</p>', result.strip)
     end
   end
-
 end
diff --git a/test/filters/test_mustache.rb b/test/filters/test_mustache.rb
index eeb0e76..47ebebf 100644
--- a/test/filters/test_mustache.rb
+++ b/test/filters/test_mustache.rb
@@ -1,18 +1,17 @@
 # encoding: utf-8
 
 class Nanoc::Filters::MustacheTest < Nanoc::TestCase
-
   def test_filter
     if_have 'mustache' do
       # Create item
       item = Nanoc::Item.new(
         'content',
-        { :title => 'Max Payne', :protagonist => 'Max Payne' },
+        { title: 'Max Payne', protagonist: 'Max Payne' },
         '/games/max-payne/'
       )
 
       # Create filter
-      filter = ::Nanoc::Filters::Mustache.new({ :item => item })
+      filter = ::Nanoc::Filters::Mustache.new({ item: item })
 
       # Run filter
       result = filter.setup_and_run('The protagonist of {{title}} is {{protagonist}}.')
@@ -25,18 +24,17 @@ class Nanoc::Filters::MustacheTest < Nanoc::TestCase
       # Create item
       item = Nanoc::Item.new(
         'content',
-        { :title => 'Max Payne', :protagonist => 'Max Payne' },
+        { title: 'Max Payne', protagonist: 'Max Payne' },
         '/games/max-payne/'
       )
 
       # Create filter
       filter = ::Nanoc::Filters::Mustache.new(
-        { :content => 'No Payne No Gayne', :item => item })
+        { content: 'No Payne No Gayne', item: item })
 
       # Run filter
       result = filter.setup_and_run('Max says: {{yield}}.')
       assert_equal('Max says: No Payne No Gayne.', result)
     end
   end
-
 end
diff --git a/test/filters/test_pandoc.rb b/test/filters/test_pandoc.rb
index e0d933a..694de66 100644
--- a/test/filters/test_pandoc.rb
+++ b/test/filters/test_pandoc.rb
@@ -1,10 +1,9 @@
 # encoding: utf-8
 
 class Nanoc::Filters::PandocTest < Nanoc::TestCase
-
   def test_filter
     if_have 'pandoc-ruby' do
-      skip_unless_have_command "pandoc"
+      skip_unless_have_command 'pandoc'
 
       # Create filter
       filter = ::Nanoc::Filters::Pandoc.new
@@ -15,19 +14,32 @@ class Nanoc::Filters::PandocTest < Nanoc::TestCase
     end
   end
 
-  def test_params
+  def test_params_old
     if_have 'pandoc-ruby' do
-      skip_unless_have_command "pandoc"
+      skip_unless_have_command 'pandoc'
 
       # Create filter
       filter = ::Nanoc::Filters::Pandoc.new
 
       # Run filter
-      opts = [ :s, {:f => :markdown, :to => :html}, 'no-wrap', :toc ]
-      result = filter.setup_and_run("# Heading\n", *opts)
-      assert_match '<div id="TOC">', result
+      args = { f: :markdown, to: :html }
+      result = filter.setup_and_run("# Heading\n", args)
       assert_match(%r{<h1 id=\"heading\">Heading</h1>\s*}, result)
     end
   end
 
+  def test_params_new
+    if_have 'pandoc-ruby' do
+      skip_unless_have_command 'pandoc'
+
+      # Create filter
+      filter = ::Nanoc::Filters::Pandoc.new
+
+      # Run filter
+      args = [:s, { f: :markdown, to: :html }, 'no-wrap', :toc]
+      result = filter.setup_and_run("# Heading\n", args: args)
+      assert_match '<div id="TOC">', result
+      assert_match(%r{<h1 id=\"heading\">Heading</h1>\s*}, result)
+    end
+  end
 end
diff --git a/test/filters/test_rainpress.rb b/test/filters/test_rainpress.rb
index 3ef6d4c..e8a410c 100644
--- a/test/filters/test_rainpress.rb
+++ b/test/filters/test_rainpress.rb
@@ -1,15 +1,14 @@
 # encoding: utf-8
 
 class Nanoc::Filters::RainpressTest < Nanoc::TestCase
-
   def test_filter
     if_have 'rainpress' do
       # Create filter
       filter = ::Nanoc::Filters::Rainpress.new
 
       # Run filter
-      result = filter.setup_and_run("body { color: black; }")
-      assert_equal("body{color:#000}", result)
+      result = filter.setup_and_run('body { color: black; }')
+      assert_equal('body{color:#000}', result)
     end
   end
 
@@ -19,9 +18,8 @@ class Nanoc::Filters::RainpressTest < Nanoc::TestCase
       filter = ::Nanoc::Filters::Rainpress.new
 
       # Run filter
-      result = filter.setup_and_run("body { color: #aabbcc; }", :colors => false)
-      assert_equal("body{color:#aabbcc}", result)
+      result = filter.setup_and_run('body { color: #aabbcc; }', colors: false)
+      assert_equal('body{color:#aabbcc}', result)
     end
   end
-
 end
diff --git a/test/filters/test_rdiscount.rb b/test/filters/test_rdiscount.rb
index c161cac..01242e4 100644
--- a/test/filters/test_rdiscount.rb
+++ b/test/filters/test_rdiscount.rb
@@ -1,14 +1,13 @@
 # encoding: utf-8
 
 class Nanoc::Filters::RDiscountTest < Nanoc::TestCase
-
   def test_filter
     if_have 'rdiscount' do
       # Create filter
       filter = ::Nanoc::Filters::RDiscount.new
 
       # Run filter
-      result = filter.setup_and_run("> Quote")
+      result = filter.setup_and_run('> Quote')
       assert_match(/<blockquote>\s*<p>Quote<\/p>\s*<\/blockquote>/, result)
     end
   end
@@ -21,9 +20,8 @@ class Nanoc::Filters::RDiscountTest < Nanoc::TestCase
       # Run filter
       input           = "The quotation 'marks' sure make this look sarcastic!"
       output_expected = /The quotation ‘marks’ sure make this look sarcastic!/
-      output_actual   = filter.setup_and_run(input, :extensions => [ :smart ])
+      output_actual   = filter.setup_and_run(input, extensions: [:smart])
       assert_match(output_expected, output_actual)
     end
   end
-
 end
diff --git a/test/filters/test_rdoc.rb b/test/filters/test_rdoc.rb
index d0fcc1e..b8c8174 100644
--- a/test/filters/test_rdoc.rb
+++ b/test/filters/test_rdoc.rb
@@ -1,14 +1,12 @@
 # encoding: utf-8
 
 class Nanoc::Filters::RDocTest < Nanoc::TestCase
-
   def test_filter
     # Get filter
     filter = ::Nanoc::Filters::RDoc.new
 
     # Run filter
-    result = filter.setup_and_run("= Foo")
+    result = filter.setup_and_run('= Foo')
     assert_match(%r{\A\s*<h1( id="label-Foo")?>Foo(<span>.*</span>)?</h1>\s*\Z}, result)
   end
-
 end
diff --git a/test/filters/test_redcarpet.rb b/test/filters/test_redcarpet.rb
index 9e9c299..fec3c3e 100644
--- a/test/filters/test_redcarpet.rb
+++ b/test/filters/test_redcarpet.rb
@@ -1,7 +1,6 @@
 # encoding: utf-8
 
 class Nanoc::Filters::RedcarpetTest < Nanoc::TestCase
-
   def test_find
     if_have 'redcarpet' do
       refute Nanoc::Filter.named(:redcarpet).nil?
@@ -14,7 +13,7 @@ class Nanoc::Filters::RedcarpetTest < Nanoc::TestCase
       filter = ::Nanoc::Filters::Redcarpet.new
 
       # Run filter
-      result = filter.setup_and_run("> Quote")
+      result = filter.setup_and_run('> Quote')
       assert_match(/<blockquote>\s*<p>Quote<\/p>\s*<\/blockquote>/, result)
     end
   end
@@ -26,13 +25,13 @@ class Nanoc::Filters::RedcarpetTest < Nanoc::TestCase
 
       # Run filter
       if ::Redcarpet::VERSION > '2'
-        input           = "this is ~~good~~ bad"
+        input           = 'this is ~~good~~ bad'
         output_expected = /this is <del>good<\/del> bad/
-        output_actual   = filter.setup_and_run(input, :options => { :strikethrough => true })
+        output_actual   = filter.setup_and_run(input, options: { strikethrough: true })
       else
         input           = "The quotation 'marks' sure make this look sarcastic!"
         output_expected = /The quotation ‘marks’ sure make this look sarcastic!/
-        output_actual   = filter.setup_and_run(input, :options => [ :smart ])
+        output_actual   = filter.setup_and_run(input, options: [:smart])
       end
       assert_match(output_expected, output_actual)
     end
@@ -60,9 +59,9 @@ class Nanoc::Filters::RedcarpetTest < Nanoc::TestCase
       input           = "![Alt](/path/to/img 'Title')"
       output_expected = %r{<img src="/path/to/img" alt="Alt" title="Title"/>}
       if ::Redcarpet::VERSION > '2'
-        output_actual   = filter.setup_and_run(input, :renderer_options => { :xhtml => true })
+        output_actual   = filter.setup_and_run(input, renderer_options: { xhtml: true })
       else
-        output_actual   = filter.setup_and_run(input, :options => [ :xhtml ])
+        output_actual   = filter.setup_and_run(input, options: [:xhtml])
       end
       assert_match(output_expected, output_actual)
     end
@@ -71,7 +70,7 @@ class Nanoc::Filters::RedcarpetTest < Nanoc::TestCase
   def test_html_toc
     if_have 'redcarpet' do
       unless ::Redcarpet::VERSION > '2'
-        skip "Requires Redcarpet >= 2"
+        skip 'Requires Redcarpet >= 2'
       end
 
       # Create filter
@@ -79,7 +78,7 @@ class Nanoc::Filters::RedcarpetTest < Nanoc::TestCase
 
       # Run filter
       input = "# Heading 1\n## Heading 2\n"
-      output_actual = filter.run(input, :renderer => Redcarpet::Render::HTML_TOC)
+      output_actual = filter.run(input, renderer: Redcarpet::Render::HTML_TOC)
 
       # Test
       output_expected = %r{<ul>\n<li>\n<a href=\"#heading-1\">Heading 1</a>\n<ul>\n<li>\n<a href=\"#heading-2\">Heading 2</a>\n</li>\n</ul>\n</li>\n</ul>}
@@ -96,7 +95,7 @@ class Nanoc::Filters::RedcarpetTest < Nanoc::TestCase
       input = "A Title\n======"
       if ::Redcarpet::VERSION > '2'
         output_expected = %r{<ul>\n<li>\n<a href="#a-title">A Title</a>\n</li>\n</ul>\n<h1 id="a-title">A Title</h1>\n}
-        output_actual   = filter.setup_and_run(input, :with_toc => true)
+        output_actual   = filter.setup_and_run(input, with_toc: true)
       else
         output_expected = %r{<h1>A Title</h1>\n}
         output_actual   = filter.setup_and_run(input)
@@ -106,5 +105,4 @@ class Nanoc::Filters::RedcarpetTest < Nanoc::TestCase
       assert_match(output_expected, output_actual)
     end
   end
-
 end
diff --git a/test/filters/test_redcloth.rb b/test/filters/test_redcloth.rb
index f21042b..198c188 100644
--- a/test/filters/test_redcloth.rb
+++ b/test/filters/test_redcloth.rb
@@ -1,15 +1,14 @@
 # encoding: utf-8
 
 class Nanoc::Filters::RedClothTest < Nanoc::TestCase
-
   def test_filter
     if_have 'redcloth' do
       # Get filter
       filter = ::Nanoc::Filters::RedCloth.new
 
       # Run filter
-      result = filter.setup_and_run("h1. Foo")
-      assert_equal("<h1>Foo</h1>", result)
+      result = filter.setup_and_run('h1. Foo')
+      assert_equal('<h1>Foo</h1>', result)
     end
   end
 
@@ -19,13 +18,12 @@ class Nanoc::Filters::RedClothTest < Nanoc::TestCase
       filter = ::Nanoc::Filters::RedCloth.new
 
       # Run filter without options
-      result = filter.setup_and_run("I am a member of SPECTRE.")
+      result = filter.setup_and_run('I am a member of SPECTRE.')
       assert_equal("<p>I am a member of <span class=\"caps\">SPECTRE</span>.</p>", result)
 
       # Run filter with options
-      result = filter.setup_and_run("I am a member of SPECTRE.", :no_span_caps => true)
-      assert_equal("<p>I am a member of SPECTRE.</p>", result)
+      result = filter.setup_and_run('I am a member of SPECTRE.', no_span_caps: true)
+      assert_equal('<p>I am a member of SPECTRE.</p>', result)
     end
   end
-
 end
diff --git a/test/filters/test_relativize_paths.rb b/test/filters/test_relativize_paths.rb
index f97ed49..ced036a 100644
--- a/test/filters/test_relativize_paths.rb
+++ b/test/filters/test_relativize_paths.rb
@@ -1,8 +1,6 @@
 # encoding: utf-8
 
 class Nanoc::Filters::RelativizePathsTest < Nanoc::TestCase
-
-
   def test_filter_html_with_double_quotes
     # Create filter with mock item
     filter = Nanoc::Filters::RelativizePaths.new
@@ -19,11 +17,11 @@ class Nanoc::Filters::RelativizePathsTest < Nanoc::TestCase
     end
 
     # Set content
-    raw_content      = %[<a href="/foo">foo</a>]
-    expected_content = %[<a href="../..">foo</a>]
+    raw_content      = %(<a href="/foo">foo</a>)
+    expected_content = %(<a href="../..">foo</a>)
 
     # Test
-    actual_content = filter.setup_and_run(raw_content, :type => :html)
+    actual_content = filter.setup_and_run(raw_content, type: :html)
     assert_equal(expected_content, actual_content)
   end
 
@@ -43,11 +41,11 @@ class Nanoc::Filters::RelativizePathsTest < Nanoc::TestCase
     end
 
     # Set content
-    raw_content      = %[<a href='/foo'>foo</a>]
-    expected_content = %[<a href="../..">foo</a>]
+    raw_content      = %(<a href='/foo'>foo</a>)
+    expected_content = %(<a href="../..">foo</a>)
 
     # Test
-    actual_content = filter.setup_and_run(raw_content, :type => :html)
+    actual_content = filter.setup_and_run(raw_content, type: :html)
     assert_equal(expected_content, actual_content)
   end
 
@@ -67,11 +65,11 @@ class Nanoc::Filters::RelativizePathsTest < Nanoc::TestCase
     end
 
     # Set content
-    raw_content      = %[<a href=/foo>foo</a>]
-    expected_content = %[<a href="../..">foo</a>]
+    raw_content      = %(<a href=/foo>foo</a>)
+    expected_content = %(<a href="../..">foo</a>)
 
     # Test
-    actual_content = filter.setup_and_run(raw_content, :type => :html)
+    actual_content = filter.setup_and_run(raw_content, type: :html)
     assert_equal(expected_content, actual_content)
   end
 
@@ -106,7 +104,7 @@ EOS
     expected_match_1 = %r{\A\s*<!DOCTYPE html\s*>\s*<html>\s*<head>(.|\s)*<title>Hello</title>\s*</head>\s*<body>\s*<a href="../..">foo</a>\s*</body>\s*</html>\s*\Z}m
 
     # Test
-    actual_content = filter.setup_and_run(raw_content, :type => :html)
+    actual_content = filter.setup_and_run(raw_content, type: :html)
     assert_match(expected_match_0, actual_content)
     assert_match(expected_match_1, actual_content)
   end
@@ -127,11 +125,11 @@ EOS
     end
 
     # Set content
-    raw_content      = %[<a href="/foo">foo</a> <a href="/bar">bar</a>]
-    expected_content = %[<a href="../..">foo</a> <a href="../../../bar">bar</a>]
+    raw_content      = %(<a href="/foo">foo</a> <a href="/bar">bar</a>)
+    expected_content = %(<a href="../..">foo</a> <a href="../../../bar">bar</a>)
 
     # Test
-    actual_content = filter.setup_and_run(raw_content, :type => :html)
+    actual_content = filter.setup_and_run(raw_content, type: :html)
     assert_equal(expected_content, actual_content)
   end
 
@@ -151,11 +149,11 @@ EOS
     end
 
     # Set content
-    raw_content      = %[<a href="/"><img src="/bar.png" /></a>]
-    expected_content = %[<a href="../../../"><img src="../../../bar.png"></a>]
+    raw_content      = %(<a href="/"><img src="/bar.png" /></a>)
+    expected_content = %(<a href="../../../"><img src="../../../bar.png"></a>)
 
     # Test
-    actual_content = filter.setup_and_run(raw_content, :type => :html)
+    actual_content = filter.setup_and_run(raw_content, type: :html)
     assert_equal(expected_content, actual_content)
   end
 
@@ -175,11 +173,11 @@ EOS
     end
 
     # Set content
-    raw_content      = %[stuff href="/foo" more stuff]
-    expected_content = %[stuff href="/foo" more stuff]
+    raw_content      = %(stuff href="/foo" more stuff)
+    expected_content = %(stuff href="/foo" more stuff)
 
     # Test
-    actual_content = filter.setup_and_run(raw_content, :type => :html)
+    actual_content = filter.setup_and_run(raw_content, type: :html)
     assert_equal(expected_content, actual_content)
   end
 
@@ -199,11 +197,11 @@ EOS
     end
 
     # Set content
-    raw_content      = %[<a href="/">foo</a>]
-    expected_content = %[<a href="../../">foo</a>]
+    raw_content      = %(<a href="/">foo</a>)
+    expected_content = %(<a href="../../">foo</a>)
 
     # Test
-    actual_content = filter.setup_and_run(raw_content, :type => :html)
+    actual_content = filter.setup_and_run(raw_content, type: :html)
     assert_equal(expected_content, actual_content)
   end
 
@@ -223,11 +221,11 @@ EOS
     end
 
     # Set content
-    raw_content      = %[<a href="//example.com/">example.com</a>]
-    expected_content = %[<a href="//example.com/">example.com</a>]
+    raw_content      = %(<a href="//example.com/">example.com</a>)
+    expected_content = %(<a href="//example.com/">example.com</a>)
 
     # Test
-    actual_content = filter.setup_and_run(raw_content, :type => :html)
+    actual_content = filter.setup_and_run(raw_content, type: :html)
     assert_equal(expected_content, actual_content)
   end
 
@@ -247,11 +245,11 @@ EOS
     end
 
     # Set content
-    raw_content      = %[<a href="#max-payne">Max Payne</a>]
-    expected_content = %[<a href="#max-payne">Max Payne</a>]
+    raw_content      = %(<a href="#max-payne">Max Payne</a>)
+    expected_content = %(<a href="#max-payne">Max Payne</a>)
 
     # Test
-    actual_content = filter.setup_and_run(raw_content, :type => :html)
+    actual_content = filter.setup_and_run(raw_content, type: :html)
     assert_equal(expected_content, actual_content)
   end
 
@@ -271,11 +269,11 @@ EOS
     end
 
     # Set content
-    raw_content      = %[<a href="http://example.com/">Example</a>]
-    expected_content = %[<a href="http://example.com/">Example</a>]
+    raw_content      = %(<a href="http://example.com/">Example</a>)
+    expected_content = %(<a href="http://example.com/">Example</a>)
 
     # Test
-    actual_content = filter.setup_and_run(raw_content, :type => :html)
+    actual_content = filter.setup_and_run(raw_content, type: :html)
     assert_equal(expected_content, actual_content)
   end
 
@@ -295,11 +293,11 @@ EOS
     end
 
     # Set content
-    raw_content      = %[<a href="example">Example</a>]
-    expected_content = %[<a href="example">Example</a>]
+    raw_content      = %(<a href="example">Example</a>)
+    expected_content = %(<a href="example">Example</a>)
 
     # Test
-    actual_content = filter.setup_and_run(raw_content, :type => :html)
+    actual_content = filter.setup_and_run(raw_content, type: :html)
     assert_equal(expected_content, actual_content)
   end
 
@@ -318,21 +316,20 @@ EOS
       @item_rep.path = '/woof/meow/'
     end
 
-    raw_content    = %[<object data="/example"><param name="movie" content="/example"></object>]
-    actual_content = filter.setup_and_run(raw_content, :type => :html)
+    raw_content    = %(<object data="/example"><param name="movie" content="/example"></object>)
+    actual_content = filter.setup_and_run(raw_content, type: :html)
 
     assert_match(/<object data="..\/..\/example">/, actual_content)
     assert_match(/<param (name="movie" )?content="..\/..\/example"/, actual_content)
   end
 
-
   def test_filter_implicit
     # Create filter with mock item
     filter = Nanoc::Filters::RelativizePaths.new
 
     # Test
     assert_raises(RuntimeError) do
-      filter.setup_and_run("moo")
+      filter.setup_and_run('moo')
     end
   end
 
@@ -356,7 +353,7 @@ EOS
     expected_content = %[background: url("../background.png");]
 
     # Test
-    actual_content = filter.setup_and_run(raw_content, :type => :css)
+    actual_content = filter.setup_and_run(raw_content, type: :css)
     assert_equal(expected_content, actual_content)
   end
 
@@ -380,7 +377,7 @@ EOS
     expected_content = %[background: url('../background.png');]
 
     # Test
-    actual_content = filter.setup_and_run(raw_content, :type => :css)
+    actual_content = filter.setup_and_run(raw_content, type: :css)
     assert_equal(expected_content, actual_content)
   end
 
@@ -404,7 +401,7 @@ EOS
     expected_content = %[background: url(../background.png);]
 
     # Test
-    actual_content = filter.setup_and_run(raw_content, :type => :css)
+    actual_content = filter.setup_and_run(raw_content, type: :css)
     assert_equal(expected_content, actual_content)
   end
 
@@ -428,7 +425,7 @@ EOS
     expected_content = %[background: url(../a.png) url(../b.png);]
 
     # Test
-    actual_content = filter.setup_and_run(raw_content, :type => :css)
+    actual_content = filter.setup_and_run(raw_content, type: :css)
     assert_equal(expected_content, actual_content)
   end
 
@@ -455,7 +452,7 @@ EOS
     expected_content = %[background: url(../../);]
 
     # Test
-    actual_content = filter.setup_and_run(raw_content, :type => :css)
+    actual_content = filter.setup_and_run(raw_content, type: :css)
     assert_equal(expected_content, actual_content)
   end
 
@@ -479,7 +476,7 @@ EOS
     expected_content = %[background: url(//example.com);]
 
     # Test
-    actual_content = filter.setup_and_run(raw_content, :type => :css)
+    actual_content = filter.setup_and_run(raw_content, type: :css)
     assert_equal(expected_content, actual_content)
   end
 
@@ -508,7 +505,7 @@ EOS
 </foo>
 XML
 
-      actual_content = filter.setup_and_run(raw_content, :type => :xml, :select => ['*/@boo'])
+      actual_content = filter.setup_and_run(raw_content, type: :xml, select: ['*/@boo'])
 
       assert_match(expected, actual_content)
     end
@@ -537,7 +534,7 @@ XML
 </foo>
 XML
 
-      actual_content = filter.setup_and_run(raw_content, :type => :xml, :select => ['far/@href'])
+      actual_content = filter.setup_and_run(raw_content, type: :xml, select: ['far/@href'])
 
       assert_match(/<foo>/, actual_content)
       assert_match(/<bar><far href="..\/..">baz<\/far><\/bar>/, actual_content)
@@ -568,9 +565,9 @@ XML
 XML
 
       actual_content = filter.setup_and_run(raw_content, {
-        :type => :xml,
-        :namespaces => {:ex => 'http://example.org'},
-        :select => ['ex:a/@href']
+        type: :xml,
+        namespaces: { ex: 'http://example.org' },
+        select: ['ex:a/@href']
       })
 
       assert_match(/<foo xmlns="http:\/\/example.org">/,    actual_content)
@@ -609,7 +606,7 @@ XML
 </html>
 XML
 
-      actual_content = filter.setup_and_run(raw_content, :type => :xhtml)
+      actual_content = filter.setup_and_run(raw_content, type: :xhtml)
 
       assert_match(/<link[^>]*href="..\/..\/..\/css"[^>]*\/>/, actual_content)
       assert_match(/<script src="..\/..\/..\/js">/,            actual_content)
@@ -646,7 +643,7 @@ XML
         %r{\A\s*<a href="../..">bar</a>\s*<p>\s*<img src="../../../img" />\s*</p>\s*\Z}m
 
       # Test
-      actual_content = filter.setup_and_run(raw_content.freeze, :type => :xhtml)
+      actual_content = filter.setup_and_run(raw_content.freeze, type: :xhtml)
       assert_match(expected_content, actual_content)
     end
   end
@@ -668,21 +665,21 @@ XML
       end
 
       # Set content
-      raw_content = %[
+      raw_content = %(
 <link rel="stylesheet" href="/foo.css" />
 <!--[if lt IE 9]>
     <script src="/js/lib/html5shiv.js"></script>
 <![endif]-->
-]
 
-      actual_content = filter.setup_and_run(raw_content.freeze, :type => :xhtml)
+)
+
+      actual_content = filter.setup_and_run(raw_content.freeze, type: :xhtml)
 
       assert_match(/<link (rel="stylesheet" )?href="..\/..\/foo.css" /,      actual_content)
       assert_match(/<script src="..\/..\/js\/lib\/html5shiv.js"><\/script>/, actual_content)
     end
   end
 
-
   def test_filter_fragment_html_with_comments
     if_have 'nokogiri' do
       # Create filter with mock item
@@ -700,15 +697,16 @@ XML
       end
 
       # Set content
-      raw_content = %[
+      raw_content = %(
 <!--[if lt IE 9]>
     <script src="/js/lib/html5shiv.js"></script>
 <![endif]-->
-]
+
+)
 
       # Test
-      actual_content = filter.setup_and_run(raw_content.freeze, :type => :html)
-      assert actual_content.include? %[<script src="../../js/lib/html5shiv.js">]
+      actual_content = filter.setup_and_run(raw_content.freeze, type: :html)
+      assert actual_content.include? %(<script src="../../js/lib/html5shiv.js">)
     end
   end
 
@@ -728,12 +726,11 @@ XML
     end
 
     # Set content
-    raw_content      = %[<!DOCTYPE html>]
-    expected_content = %[<!DOCTYPE html>]
+    raw_content      = %(<!DOCTYPE html>)
+    expected_content = %(<!DOCTYPE html>)
 
     # Test
-    actual_content = filter.setup_and_run(raw_content, :type => :html)
+    actual_content = filter.setup_and_run(raw_content, type: :html)
     assert_equal(expected_content, actual_content)
   end
-
 end
diff --git a/test/filters/test_rubypants.rb b/test/filters/test_rubypants.rb
index 1b89e54..bd177ce 100644
--- a/test/filters/test_rubypants.rb
+++ b/test/filters/test_rubypants.rb
@@ -1,16 +1,14 @@
 # encoding: utf-8
 
 class Nanoc::Filters::RubyPantsTest < Nanoc::TestCase
-
   def test_filter
     if_have 'rubypants' do
       # Get filter
       filter = ::Nanoc::Filters::RubyPants.new
 
       # Run filter
-      result = filter.setup_and_run("Wait---what?")
-      assert_equal("Wait—what?", result)
+      result = filter.setup_and_run('Wait---what?')
+      assert_equal('Wait—what?', result)
     end
   end
-
 end
diff --git a/test/filters/test_sass.rb b/test/filters/test_sass.rb
index c816373..62fd884 100644
--- a/test/filters/test_sass.rb
+++ b/test/filters/test_sass.rb
@@ -1,11 +1,10 @@
 # encoding: utf-8
 
 class Nanoc::Filters::SassTest < Nanoc::TestCase
-
   def test_filter
     if_have 'sass' do
       # Get filter
-      filter = create_filter({ :foo => 'bar' })
+      filter = create_filter({ foo: 'bar' })
 
       # Run filter
       result = filter.setup_and_run(".foo #bar\n  color: #f00")
@@ -16,14 +15,14 @@ class Nanoc::Filters::SassTest < Nanoc::TestCase
   def test_filter_with_params
     if_have 'sass' do
       # Create filter
-      filter = create_filter({ :foo => 'bar' })
+      filter = create_filter({ foo: 'bar' })
 
       # Check with compact
-      result = filter.setup_and_run(".foo #bar\n  color: #f00", :style => 'compact')
+      result = filter.setup_and_run(".foo #bar\n  color: #f00", style: 'compact')
       assert_match(/^\.foo #bar[\s]*\{[\s]*color:\s*(red|#f00);?[\s]*\}/m, result)
 
       # Check with compressed
-      result = filter.setup_and_run(".foo #bar\n  color: #f00", :style => 'compressed')
+      result = filter.setup_and_run(".foo #bar\n  color: #f00", style: 'compressed')
       assert_match(/^\.foo #bar[\s]*\{[\s]*color:\s*(red|#f00);?[\s]*\}/m, result)
     end
   end
@@ -64,8 +63,8 @@ class Nanoc::Filters::SassTest < Nanoc::TestCase
       filter = create_filter
 
       # Create sample file
-      File.open('moo.sass', 'w') { |io| io.write %Q{@import subdir/relative} }
-      FileUtils.mkdir_p("subdir")
+      File.open('moo.sass', 'w') { |io| io.write %(@import subdir/relative) }
+      FileUtils.mkdir_p('subdir')
       File.open('subdir/relative.sass', 'w') { |io| io.write "body\n  color: red" }
 
       # Run filter
@@ -251,7 +250,7 @@ class Nanoc::Filters::SassTest < Nanoc::TestCase
 
         # Check
         output_files = Dir['output/**/*'].select { |f| File.file?(f) }
-        assert_equal [ 'output/style/super/main.css' ], output_files
+        assert_equal ['output/style/super/main.css'], output_files
         assert_match(/^p\s*\{\s*color:\s*red;?\s*\}/, File.read('output/style/super/main.css'))
 
         # Update included file
@@ -265,24 +264,34 @@ class Nanoc::Filters::SassTest < Nanoc::TestCase
 
         # Recheck
         output_files = Dir['output/**/*'].select { |f| File.file?(f) }
-        assert_equal [ 'output/style/super/main.css' ], output_files
+        assert_equal ['output/style/super/main.css'], output_files
         assert_match(/^p\s*\{\s*color:\s*blue;?\s*\}/, File.read('output/style/super/main.css'))
       end
     end
   end
 
-private
+  def test_sass_without_filter
+    if_have 'sass' do
+      File.open('_morestuff.sass', 'w') do |io|
+        io.write("p\n  color: blue")
+      end
+
+      options = { :filename => File.join(Dir.getwd, 'test.sass') }
+      ::Sass::Engine.new('@import "morestuff"', options).render
+    end
+  end
+
+  private
 
-  def create_filter(params={})
+  def create_filter(params = {})
     FileUtils.mkdir_p('content')
-    File.open('content/xyzzy.sass', 'w') { |io| io.write('p\n  color: green')}
+    File.open('content/xyzzy.sass', 'w') { |io| io.write('p\n  color: green') }
 
-    items = [ Nanoc::Item.new(
+    items = [Nanoc::Item.new(
       'blah',
-      { :content_filename => 'content/xyzzy.sass' },
-      '/blah/') ]
-    params = { :item => items[0], :items => items }.merge(params)
+      { content_filename: 'content/xyzzy.sass' },
+      '/blah/')]
+    params = { item: items[0], items: items }.merge(params)
     ::Nanoc::Filters::Sass.new(params)
   end
-
 end
diff --git a/test/filters/test_slim.rb b/test/filters/test_slim.rb
index 109b711..f8c50ef 100644
--- a/test/filters/test_slim.rb
+++ b/test/filters/test_slim.rb
@@ -1,11 +1,10 @@
 # encoding: utf-8
 
 class Nanoc::Filters::SlimTest < Nanoc::TestCase
-
   def test_filter
     if_have 'slim' do
       # Create filter
-      filter = ::Nanoc::Filters::Slim.new({ :rabbit => 'The rabbit is on the branch.' })
+      filter = ::Nanoc::Filters::Slim.new({ rabbit: 'The rabbit is on the branch.' })
 
       # Run filter (no assigns)
       result = filter.setup_and_run('html')
@@ -13,21 +12,20 @@ class Nanoc::Filters::SlimTest < Nanoc::TestCase
 
       # Run filter (assigns without @)
       result = filter.setup_and_run('p = rabbit')
-      assert_equal("<p>The rabbit is on the branch.</p>", result)
+      assert_equal('<p>The rabbit is on the branch.</p>', result)
 
       # Run filter (assigns with @)
       result = filter.setup_and_run('p = @rabbit')
-      assert_equal("<p>The rabbit is on the branch.</p>", result)
+      assert_equal('<p>The rabbit is on the branch.</p>', result)
     end
   end
 
   def test_filter_with_yield
     if_have 'slim' do
-      filter = ::Nanoc::Filters::Slim.new({ :content => 'The rabbit is on the branch.' })
+      filter = ::Nanoc::Filters::Slim.new({ content: 'The rabbit is on the branch.' })
 
       result = filter.setup_and_run('p = yield')
-      assert_equal("<p>The rabbit is on the branch.</p>", result)
+      assert_equal('<p>The rabbit is on the branch.</p>', result)
     end
   end
-
 end
diff --git a/test/filters/test_typogruby.rb b/test/filters/test_typogruby.rb
index 8b77bfa..b048ea9 100644
--- a/test/filters/test_typogruby.rb
+++ b/test/filters/test_typogruby.rb
@@ -1,7 +1,6 @@
 # encoding: utf-8
 
 class Nanoc::Filters::TypogrubyTest < Nanoc::TestCase
-
   def test_filter
     if_have 'typogruby' do
       # Get filter
@@ -14,6 +13,4 @@ class Nanoc::Filters::TypogrubyTest < Nanoc::TestCase
       assert_equal(b, result)
     end
   end
-
 end
-
diff --git a/test/filters/test_uglify_js.rb b/test/filters/test_uglify_js.rb
index 40d3432..9f66431 100644
--- a/test/filters/test_uglify_js.rb
+++ b/test/filters/test_uglify_js.rb
@@ -1,14 +1,13 @@
 # encoding: utf-8
 
 class Nanoc::Filters::UglifyJSTest < Nanoc::TestCase
-
   def test_filter
     if_have 'uglifier' do
       # Create filter
       filter = ::Nanoc::Filters::UglifyJS.new
 
       # Run filter
-      input = "foo = 1; (function(bar) { if (true) alert(bar); })(foo)"
+      input = 'foo = 1; (function(bar) { if (true) alert(bar); })(foo)'
       result = filter.setup_and_run(input)
       assert_match(/foo=1,function\((.)\)\{alert\(\1\)\}\(foo\);/, result)
     end
@@ -19,12 +18,11 @@ class Nanoc::Filters::UglifyJSTest < Nanoc::TestCase
       filter = ::Nanoc::Filters::UglifyJS.new
       input = "if(donkey) alert('It is a donkey!');"
 
-      result = filter.setup_and_run(input, :output => { :beautify => false })
+      result = filter.setup_and_run(input, output: { beautify: false })
       assert_equal 'donkey&&alert("It is a donkey!");', result
 
-      result = filter.setup_and_run(input, :output => { :beautify => true })
+      result = filter.setup_and_run(input, output: { beautify: true })
       assert_equal 'donkey && alert("It is a donkey!");', result
     end
   end
-
 end
diff --git a/test/filters/test_xsl.rb b/test/filters/test_xsl.rb
index abb300b..f54a3d1 100644
--- a/test/filters/test_xsl.rb
+++ b/test/filters/test_xsl.rb
@@ -3,7 +3,6 @@
 require 'tempfile'
 
 class Nanoc::Filters::XSLTest < Nanoc::TestCase
-
   SAMPLE_XSL = <<-EOS
 <?xml version="1.0" encoding="utf-8"?>
 <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
@@ -88,17 +87,17 @@ EOS
     if_have 'nokogiri' do
       # Create our data objects
       item = Nanoc::Item.new(SAMPLE_XML_IN,
-                             { },
+                             {},
                              '/content/')
       layout = Nanoc::Layout.new(SAMPLE_XSL,
-                                 { },
+                                 {},
                                  '/layout/')
 
       # Create an instance of the filter
       assigns = {
-        :item => item,
-        :layout => layout,
-        :content => item.raw_content
+        item: item,
+        layout: layout,
+        content: item.raw_content
       }
       filter = ::Nanoc::Filters::XSL.new(assigns)
 
@@ -112,23 +111,23 @@ EOS
     if_have 'nokogiri' do
       # Create our data objects
       item = Nanoc::Item.new(SAMPLE_XML_IN_WITH_PARAMS,
-                             { },
+                             {},
                              '/content/')
       layout = Nanoc::Layout.new(SAMPLE_XSL_WITH_PARAMS,
-                                 { },
+                                 {},
                                  '/layout/')
 
       # Create an instance of the filter
       assigns = {
-        :item => item,
-        :layout => layout,
-        :content => item.raw_content
+        item: item,
+        layout: layout,
+        content: item.raw_content
       }
       filter = ::Nanoc::Filters::XSL.new(assigns)
 
       # Run the filter and validate the results
       result = filter.setup_and_run(layout.raw_content,
-                                    :foo => 'bar')
+                                    foo: 'bar')
       assert_match SAMPLE_XML_OUT_WITH_PARAMS, result
     end
   end
@@ -137,17 +136,17 @@ EOS
     if_have 'nokogiri' do
       # Create our data objects
       item = Nanoc::Item.new(SAMPLE_XML_IN_WITH_OMIT_XML_DECL,
-                             { },
+                             {},
                              '/content/')
       layout = Nanoc::Layout.new(SAMPLE_XSL_WITH_OMIT_XML_DECL,
-                                 { },
+                                 {},
                                  '/layout/')
 
       # Create an instance of the filter
       assigns = {
-        :item => item,
-        :layout => layout,
-        :content => item.raw_content
+        item: item,
+        layout: layout,
+        content: item.raw_content
       }
       filter = ::Nanoc::Filters::XSL.new(assigns)
 
@@ -156,5 +155,4 @@ EOS
       assert_match SAMPLE_XML_OUT_WITH_OMIT_XML_DECL, result
     end
   end
-
 end
diff --git a/test/filters/test_yui_compressor.rb b/test/filters/test_yui_compressor.rb
index db94595..b1d18a4 100644
--- a/test/filters/test_yui_compressor.rb
+++ b/test/filters/test_yui_compressor.rb
@@ -1,7 +1,6 @@
 # encoding: utf-8
 
 class Nanoc::Filters::YUICompressorTest < Nanoc::TestCase
-
   def test_filter_javascript
     if_have 'yuicompressor' do
       filter = ::Nanoc::Filters::YUICompressor.new
@@ -16,11 +15,11 @@ class Nanoc::Filters::YUICompressorTest < Nanoc::TestCase
         }
       JAVASCRIPT
 
-      result = filter.setup_and_run(sample_js, { :type => 'js', :munge => true })
-      assert_match "function factorial(c){var a=1;for(var b=2;b<=c;b++){a*=b}return a};", result
+      result = filter.setup_and_run(sample_js, { type: 'js', munge: true })
+      assert_match 'function factorial(c){var a=1;for(var b=2;b<=c;b++){a*=b}return a};', result
 
-      result = filter.setup_and_run(sample_js, { :type => 'js', :munge => false })
-      assert_match "function factorial(n){var result=1;for(var i=2;i<=n;i++){result*=i}return result};", result
+      result = filter.setup_and_run(sample_js, { type: 'js', munge: false })
+      assert_match 'function factorial(n){var result=1;for(var i=2;i<=n;i++){result*=i}return result};', result
     end
   end
 
@@ -34,9 +33,8 @@ class Nanoc::Filters::YUICompressorTest < Nanoc::TestCase
         }
       CSS
 
-      result = filter.setup_and_run(sample_css, { :type => 'css' })
-      assert_match "*{margin:0}", result
+      result = filter.setup_and_run(sample_css, { type: 'css' })
+      assert_match '*{margin:0}', result
     end
   end
-
 end
diff --git a/test/fixtures/vcr_cassettes/css_run_parse_error.yml b/test/fixtures/vcr_cassettes/css_run_parse_error.yml
new file mode 100644
index 0000000..83d6134
--- /dev/null
+++ b/test/fixtures/vcr_cassettes/css_run_parse_error.yml
@@ -0,0 +1,65 @@
+---
+http_interactions:
+- request:
+    method: get
+    uri: http://jigsaw.w3.org/css-validator/validator?output=soap12&profile=css3&text=h1%20%7B%20%3B%20%7B
+    body:
+      encoding: US-ASCII
+      string: ''
+    headers:
+      Accept-Encoding:
+      - gzip;q=1.0,deflate;q=0.6,identity;q=0.3
+      Accept:
+      - "*/*"
+      User-Agent:
+      - Ruby
+  response:
+    status:
+      code: 200
+      message: OK
+    headers:
+      Cache-Control:
+      - no-cache
+      Date:
+      - Sat, 06 Dec 2014 11:12:00 GMT
+      Pragma:
+      - no-cache
+      Transfer-Encoding:
+      - chunked
+      Content-Language:
+      - en
+      Content-Type:
+      - application/soap+xml;charset=utf-8
+      Server:
+      - Jigsaw/2.3.0-beta2
+      Vary:
+      - Accept-Language
+      X-W3c-Validator-Errors:
+      - '1'
+      X-W3c-Validator-Status:
+      - Invalid
+    body:
+      encoding: UTF-8
+      string: "<?xml version='1.0' encoding=\"utf-8\"?>\n<env:Envelope xmlns:env=\"http://www.w3.org/2003/05/soap-envelope\">\n
+        \   <env:Body>\n        <m:cssvalidationresponse\n            env:encodingStyle=\"http://www.w3.org/2003/05/soap-encoding\"\n
+        \           xmlns:m=\"http://www.w3.org/2005/07/css-validator\">\n            <m:uri>file://localhost/TextArea</m:uri>\n
+        \           <m:checkedby>http://jigsaw.w3.org/css-validator/</m:checkedby>\n
+        \           <m:csslevel>css3</m:csslevel>\n            <m:date>2014-12-06T11:12:00Z</m:date>\n
+        \           <m:validity>false</m:validity>\n            <m:result>\n                <m:errors
+        xml:lang=\"en\">\n                    <m:errorcount>1</m:errorcount>\n                                                                    \n
+        \               <m:errorlist>\n                    <m:uri>file://localhost/TextArea</m:uri>\n
+        \                           \n                        <m:error>\n                            <m:line>1</m:line>\n
+        \                           <m:errortype>parse-error</m:errortype>\n                            <m:context>h1</m:context>
+        \       \n                            <m:errorsubtype>\n                                unrecognized\n
+        \                           </m:errorsubtype>\n                            <m:skippedstring>\n
+        \                               ; {\n                            </m:skippedstring>\n
+        \                           <m:type>generator.unrecognize</m:type>\n        \n
+        \                           <m:message>\n        \n                                Parse
+        Error\n                            </m:message>\n                        </m:error>\n
+        \           \n                    </m:errorlist>\n        \n                </m:errors>\n
+        \               <m:warnings xml:lang=\"en\">\n                    <m:warningcount>0</m:warningcount>\n
+        \               </m:warnings>\n            </m:result>\n        </m:cssvalidationresponse>\n
+        \   </env:Body>\n</env:Envelope>\n\n"
+    http_version: 
+  recorded_at: Sat, 06 Dec 2014 11:12:00 GMT
+recorded_with: VCR 2.9.3
diff --git a/test/gem_loader.rb b/test/gem_loader.rb
index 24cf2aa..0c9ad48 100644
--- a/test/gem_loader.rb
+++ b/test/gem_loader.rb
@@ -3,9 +3,9 @@
 begin
   require 'rubygems'
 
-  gemspec = File.expand_path("nanoc.gemspec", Dir.pwd)
+  gemspec = File.expand_path('nanoc.gemspec', Dir.pwd)
   Gem::Specification.load(gemspec).dependencies.each do |dep|
     gem dep.name, *dep.requirement.as_list
   end
-rescue LoadError => e
+rescue LoadError
 end
diff --git a/test/helper.rb b/test/helper.rb
index f38fb6c..37196d9 100644
--- a/test/helper.rb
+++ b/test/helper.rb
@@ -4,7 +4,7 @@
 require File.dirname(__FILE__) + '/gem_loader.rb'
 
 # Load unit testing stuff
-require 'minitest/unit'
+require 'minitest/test'
 require 'minitest/spec'
 require 'minitest/mock'
 require 'mocha/setup'
@@ -30,7 +30,6 @@ VCR.configure do |c|
 end
 
 module Nanoc::TestHelpers
-
   LIB_DIR = File.expand_path(File.dirname(__FILE__) + '/../lib')
 
   def disable_nokogiri?
@@ -40,7 +39,7 @@ module Nanoc::TestHelpers
   def if_have(*libs)
     libs.each do |lib|
       if defined?(RUBY_ENGINE) && RUBY_ENGINE == 'jruby' && lib == 'nokogiri' && disable_nokogiri?
-        skip "Pure Java Nokogiri has issues that cause problems with nanoc (see https://github.com/nanoc/nanoc/pull/422) -- run without DISABLE_NOKOGIRI to enable Nokogiri tests"
+        skip 'Pure Java Nokogiri has issues that cause problems with nanoc (see https://github.com/nanoc/nanoc/pull/422) -- run without DISABLE_NOKOGIRI to enable Nokogiri tests'
         return
       end
 
@@ -56,15 +55,13 @@ module Nanoc::TestHelpers
   end
 
   def if_implemented
-    begin
-      yield
-    rescue NotImplementedError, NameError
-      skip $!
-      return
-    end
+    yield
+  rescue NotImplementedError, NameError
+    skip $ERROR_INFO
+    return
   end
 
-  def with_site(params={})
+  def with_site(params = {})
     # Build site name
     site_name = params[:name]
     if site_name.nil?
@@ -161,7 +158,7 @@ EOS
     end
   end
 
-  def capturing_stdio(&block)
+  def capturing_stdio(&_block)
     # Store
     orig_stdout = $stdout
     orig_stderr = $stderr
@@ -170,7 +167,7 @@ EOS
     $stdout = StringIO.new
     $stderr = StringIO.new
     yield
-    { :stdout => $stdout.string, :stderr => $stderr.string }
+    { stdout: $stdout.string, stderr: $stderr.string }
   ensure
     # Restore
     $stdout = orig_stdout
@@ -182,7 +179,7 @@ EOS
     P(object).tags(:example).each do |example|
       # Classify
       lines = example.text.lines.map do |line|
-        [ line =~ /^\s*# ?=>/ ? :result : :code, line ]
+        [line =~ /^\s*# ?=>/ ? :result : :code, line]
       end
 
       # Join
@@ -194,7 +191,7 @@ EOS
           pieces << line
         end
       end
-      lines = pieces.map { |p| p.last }
+      lines = pieces.map(&:last)
 
       # Test
       b = binding
@@ -210,14 +207,14 @@ EOS
 
   def assert_contains_exactly(expected, actual)
     assert_equal expected.size, actual.size,
-      'Expected %s to be of same size as %s' % [actual.inspect, expected.inspect]
+      format('Expected %s to be of same size as %s', actual.inspect, expected.inspect)
     remaining = actual.dup.to_a
     expected.each do |e|
       index = remaining.index(e)
       remaining.delete_at(index) if index
     end
     assert remaining.empty?,
-      'Expected %s to contain all the elements of %s' % [actual.inspect, expected.inspect]
+      format('Expected %s to contain all the elements of %s', actual.inspect, expected.inspect)
   end
 
   def assert_raises_frozen_error
@@ -225,24 +222,24 @@ EOS
     assert_match(/(^can't modify frozen |^unable to modify frozen object$)/, error.message)
   end
 
-  def with_env_vars(hash, &block)
+  def with_env_vars(hash, &_block)
     orig_env_hash = ENV.to_hash
-    hash.each_pair { |k,v| ENV[k] = v }
+    hash.each_pair { |k, v| ENV[k] = v }
     yield
   ensure
-    orig_env_hash.each_pair { |k,v| ENV[k] = v }
+    orig_env_hash.each_pair { |k, v| ENV[k] = v }
   end
 
   def on_windows?
     Nanoc.on_windows?
   end
 
-  def have_command?(cmd)
-    which, null = on_windows? ? ["where", "NUL"] : ["which", "/dev/null"]
+  def command?(cmd)
+    which, null = on_windows? ? %w(where NUL) : ['which', '/dev/null']
     system("#{which} #{cmd} > #{null} 2>&1")
   end
 
-  def have_symlink?
+  def symlinks_supported?
     File.symlink nil, nil
   rescue NotImplementedError
     return false
@@ -251,28 +248,25 @@ EOS
   end
 
   def skip_unless_have_command(cmd)
-    skip "Could not find external command \"#{cmd}\"" unless have_command?(cmd)
+    skip "Could not find external command \"#{cmd}\"" unless command?(cmd)
   end
 
-  def skip_unless_have_symlink
-    skip "Symlinks are not supported by Ruby on Windows" unless have_symlink?
+  def skip_unless_symlinks_supported
+    skip 'Symlinks are not supported by Ruby on Windows' unless symlinks_supported?
   end
-
 end
 
-class Nanoc::TestCase < MiniTest::Unit::TestCase
-
+class Nanoc::TestCase < Minitest::Test
   include Nanoc::TestHelpers
-
 end
 
 # Unexpected system exit is unexpected
-::MiniTest::Unit::TestCase::PASSTHROUGH_EXCEPTIONS.delete(SystemExit)
+::Minitest::Test::PASSTHROUGH_EXCEPTIONS.delete(SystemExit)
 
 # A more precise inspect method for Time improves assert failure messages.
 #
 class Time
   def inspect
-    strftime("%a %b %d %H:%M:%S.#{"%06d" % usec} %Z %Y")
+    strftime("%a %b %d %H:%M:%S.#{format('%06d', usec)} %Z %Y")
   end
 end
diff --git a/test/helpers/test_blogging.rb b/test/helpers/test_blogging.rb
index c64664e..11d74ed 100644
--- a/test/helpers/test_blogging.rb
+++ b/test/helpers/test_blogging.rb
@@ -1,7 +1,6 @@
 # encoding: utf-8
 
 class Nanoc::Helpers::BloggingTest < Nanoc::TestCase
-
   include Nanoc::Helpers::Blogging
   include Nanoc::Helpers::Text
 
@@ -14,7 +13,7 @@ class Nanoc::Helpers::BloggingTest < Nanoc::TestCase
     item.stubs(:[]).with(:custom_path_in_feed).returns(nil)
     item.stubs(:[]).with(:custom_url_in_feed).returns(nil)
     item.stubs(:[]).with(:excerpt).returns(nil)
-    item.stubs(:path).returns("/item/")
+    item.stubs(:path).returns('/item/')
     item.stubs(:[]).with(:author_name).returns(nil)
     item.stubs(:[]).with(:author_uri).returns(nil)
     item.stubs(:compiled_content).returns('item content')
@@ -32,17 +31,17 @@ class Nanoc::Helpers::BloggingTest < Nanoc::TestCase
     @items = [
       Nanoc::Item.new(
         'blah',
-        { :kind => 'item' },
+        { kind: 'item' },
         '/0/'
       ),
       Nanoc::Item.new(
         'blah blah',
-        { :kind => 'article' },
+        { kind: 'article' },
         '/1/'
       ),
       Nanoc::Item.new(
         'blah blah blah',
-        { :kind => 'article' },
+        { kind: 'article' },
         '/2/'
       )
     ]
@@ -61,17 +60,17 @@ class Nanoc::Helpers::BloggingTest < Nanoc::TestCase
     @items = [
       Nanoc::Item.new(
         'blah',
-        { :kind => 'item' },
+        { kind: 'item' },
         '/0/'
       ),
       Nanoc::Item.new(
         'blah',
-        { :kind => 'article', :created_at => (Date.today - 1).to_s }, 
+        { kind: 'article', created_at: (Date.today - 1).to_s },
         '/1/'
       ),
       Nanoc::Item.new(
         'blah',
-        { :kind => 'article', :created_at => (Time.now - 500).to_s },
+        { kind: 'article', created_at: (Time.now - 500).to_s },
         '/2/'
       )
     ]
@@ -88,7 +87,7 @@ class Nanoc::Helpers::BloggingTest < Nanoc::TestCase
   def test_atom_feed
     if_have 'builder' do
       # Create items
-      @items = [ mock, mock_article, mock_article ]
+      @items = [mock, mock_article, mock_article]
 
       # Create item 0
       @items[0].stubs(:[]).with(:kind).returns('item')
@@ -101,15 +100,15 @@ class Nanoc::Helpers::BloggingTest < Nanoc::TestCase
       @items[1].stubs(:[]).with(:custom_path_in_feed).returns(nil)
       @items[1].stubs(:[]).with(:custom_url_in_feed).returns(nil)
       @items[1].stubs(:[]).with(:excerpt).returns(nil)
-      @items[1].stubs(:path).returns("/item1/")
-      @items[1].expects(:compiled_content).with(:snapshot => :pre).returns('item 1 content')
+      @items[1].stubs(:path).returns('/item1/')
+      @items[1].expects(:compiled_content).with(snapshot: :pre).returns('item 1 content')
 
       # Create item 2
-      @items[2].expects(:compiled_content).with(:snapshot => :pre).returns('item 2 content')
+      @items[2].expects(:compiled_content).with(snapshot: :pre).returns('item 2 content')
 
       # Mock site
       @site = mock
-      @site.stubs(:config).returns({ :base_url => 'http://example.com' })
+      @site.stubs(:config).returns({ base_url: 'http://example.com' })
 
       # Create feed item
       @item = mock
@@ -117,7 +116,7 @@ class Nanoc::Helpers::BloggingTest < Nanoc::TestCase
       @item.stubs(:[]).with(:author_name).returns('Denis Defreyne')
       @item.stubs(:[]).with(:author_uri).returns('http://stoneship.org/')
       @item.stubs(:[]).with(:feed_url).returns(nil)
-      @item.stubs(:path).returns("/journal/feed/")
+      @item.stubs(:path).returns('/journal/feed/')
 
       # Check
       atom_feed
@@ -127,7 +126,7 @@ class Nanoc::Helpers::BloggingTest < Nanoc::TestCase
   def test_atom_feed_with_times
     if_have 'builder' do
       # Create items
-      @items = [ mock_item, mock_article, mock_article ]
+      @items = [mock_item, mock_article, mock_article]
 
       # Create item 1
       @items[1].stubs(:[]).with(:updated_at).returns(Time.now - 500)
@@ -141,7 +140,7 @@ class Nanoc::Helpers::BloggingTest < Nanoc::TestCase
 
       # Mock site
       @site = mock
-      @site.stubs(:config).returns({ :base_url => 'http://example.com' })
+      @site.stubs(:config).returns({ base_url: 'http://example.com' })
 
       # Create feed item
       @item = mock
@@ -149,7 +148,7 @@ class Nanoc::Helpers::BloggingTest < Nanoc::TestCase
       @item.stubs(:[]).with(:author_name).returns('Denis Defreyne')
       @item.stubs(:[]).with(:author_uri).returns('http://stoneship.org/')
       @item.stubs(:[]).with(:feed_url).returns(nil)
-      @item.stubs(:path).returns("/journal/feed/")
+      @item.stubs(:path).returns('/journal/feed/')
 
       # Check
       atom_feed
@@ -159,11 +158,11 @@ class Nanoc::Helpers::BloggingTest < Nanoc::TestCase
   def test_atom_feed_without_articles
     if_have 'builder' do
       # Mock items
-      @items = [ mock_item, mock_item ]
+      @items = [mock_item, mock_item]
 
       # Mock site
       @site = mock
-      @site.stubs(:config).returns({ :base_url => 'http://example.com' })
+      @site.stubs(:config).returns({ base_url: 'http://example.com' })
 
       # Create feed item
       @item = mock
@@ -185,11 +184,11 @@ class Nanoc::Helpers::BloggingTest < Nanoc::TestCase
   def test_atom_feed_without_base_url
     if_have 'builder' do
       # Create items
-      @items = [ mock_item, mock_article ]
+      @items = [mock_item, mock_article]
 
       # Mock site
       @site = mock
-      @site.stubs(:config).returns({:base_url => nil})
+      @site.stubs(:config).returns({ base_url: nil })
 
       # Create feed item
       @item = mock
@@ -211,11 +210,11 @@ class Nanoc::Helpers::BloggingTest < Nanoc::TestCase
   def test_atom_feed_without_title
     if_have 'builder' do
       # Create items
-      @items = [ mock_item, mock_article ]
+      @items = [mock_item, mock_article]
 
       # Mock site
       @site = mock
-      @site.stubs(:config).returns({ :base_url => 'http://example.com' })
+      @site.stubs(:config).returns({ base_url: 'http://example.com' })
 
       # Create feed item
       @item = mock
@@ -237,11 +236,11 @@ class Nanoc::Helpers::BloggingTest < Nanoc::TestCase
   def test_atom_feed_without_author_name
     if_have 'builder' do
       # Create items
-      @items = [ mock_item, mock_article ]
+      @items = [mock_item, mock_article]
 
       # Mock site
       @site = mock
-      @site.stubs(:config).returns({ :base_url => 'http://example.com' })
+      @site.stubs(:config).returns({ base_url: 'http://example.com' })
 
       # Create feed item
       @item = mock
@@ -263,16 +262,16 @@ class Nanoc::Helpers::BloggingTest < Nanoc::TestCase
   def test_atom_feed_with_author_name_and_uri_from_content_item
     if_have 'builder' do
       # Create items
-      @items = [ mock_article ]
+      @items = [mock_article]
 
       # Create item 1
-      @items[0].stubs(:[]).with(:author_name).returns("Don Alias")
-      @items[0].stubs(:[]).with(:author_uri).returns("http://don.example.com/")
+      @items[0].stubs(:[]).with(:author_name).returns('Don Alias')
+      @items[0].stubs(:[]).with(:author_uri).returns('http://don.example.com/')
       @items[0].expects(:compiled_content).returns('item 1 content')
 
       # Mock site
       @site = mock
-      @site.stubs(:config).returns({ :base_url => 'http://example.com/' })
+      @site.stubs(:config).returns({ base_url: 'http://example.com/' })
 
       # Create feed item
       @item = mock
@@ -281,7 +280,7 @@ class Nanoc::Helpers::BloggingTest < Nanoc::TestCase
       @item.stubs(:[]).with(:author_name).returns('Denis Defreyne')
       @item.stubs(:[]).with(:author_uri).returns('http://stoneship.org/')
       @item.stubs(:[]).with(:feed_url).returns(nil)
-      @item.stubs(:path).returns("/journal/feed/")
+      @item.stubs(:path).returns('/journal/feed/')
 
       # Check
       # TODO: Use xpath matchers for more specific test
@@ -311,11 +310,11 @@ class Nanoc::Helpers::BloggingTest < Nanoc::TestCase
   def test_atom_feed_without_author_uri
     if_have 'builder' do
       # Create items
-      @items = [ mock_item, mock_article ]
+      @items = [mock_item, mock_article]
 
       # Mock site
       @site = mock
-      @site.stubs(:config).returns({ :base_url => 'http://example.com' })
+      @site.stubs(:config).returns({ base_url: 'http://example.com' })
 
       # Create feed item
       @item = mock
@@ -337,13 +336,13 @@ class Nanoc::Helpers::BloggingTest < Nanoc::TestCase
   def test_atom_feed_without_articles_created_at
     if_have 'builder' do
       # Create items
-      @items = [ mock_item, mock_article, mock_article ]
+      @items = [mock_item, mock_article, mock_article]
       @items[1].stubs(:[]).with(:created_at).returns(Time.now.to_s)
       @items[2].stubs(:[]).with(:created_at).returns(nil)
 
       # Mock site
       @site = mock
-      @site.stubs(:config).returns({ :base_url => 'http://example.com' })
+      @site.stubs(:config).returns({ base_url: 'http://example.com' })
 
       # Create feed item
       @item = mock
@@ -365,12 +364,12 @@ class Nanoc::Helpers::BloggingTest < Nanoc::TestCase
   def test_atom_feed_with_title_author_name_and_uri_as_params
     if_have 'builder' do
       # Create items
-      @items = [ mock_item, mock_article ]
-      @items[1].expects(:compiled_content).with(:snapshot => :pre).returns('asdf')
+      @items = [mock_item, mock_article]
+      @items[1].expects(:compiled_content).with(snapshot: :pre).returns('asdf')
 
       # Mock site
       @site = mock
-      @site.stubs(:config).returns({ :base_url => 'http://example.com' })
+      @site.stubs(:config).returns({ base_url: 'http://example.com' })
 
       # Create feed item
       @item = mock
@@ -381,9 +380,9 @@ class Nanoc::Helpers::BloggingTest < Nanoc::TestCase
 
       # Check
       atom_feed(
-        :author_name => 'Bob',
-        :author_uri  => 'http://example.com/~bob/',
-        :title       => 'My Blog Or Something'
+        author_name: 'Bob',
+        author_uri: 'http://example.com/~bob/',
+        title: 'My Blog Or Something'
       )
     end
   end
@@ -391,15 +390,15 @@ class Nanoc::Helpers::BloggingTest < Nanoc::TestCase
   def test_atom_feed_with_title_author_name_and_uri_from_config
     if_have 'builder' do
       # Create items
-      @items = [ mock_item, mock_article ]
-      @items[1].expects(:compiled_content).with(:snapshot => :pre).returns('asdf')
+      @items = [mock_item, mock_article]
+      @items[1].expects(:compiled_content).with(snapshot: :pre).returns('asdf')
 
       # Mock site
       @config = {
-        :author_name => 'Bob',
-        :author_uri  => 'http://example.com/~bob/',
-        :title       => 'My Blog Or Something',
-        :base_url    => 'http://example.com'
+        author_name: 'Bob',
+        author_uri: 'http://example.com/~bob/',
+        title: 'My Blog Or Something',
+        base_url: 'http://example.com'
       }
       @site = mock
       @site.stubs(:config).returns(@config)
@@ -419,15 +418,15 @@ class Nanoc::Helpers::BloggingTest < Nanoc::TestCase
   def test_atom_feed_with_articles_param
     if_have 'builder' do
       # Mock items
-      @items = [ mock_article, mock_article ]
+      @items = [mock_article, mock_article]
 
       @items[0].expects(:compiled_content).never
       @items[1].stubs(:[]).with(:title).returns('Item One')
-      @items[1].expects(:compiled_content).with(:snapshot => :pre).returns('asdf')
+      @items[1].expects(:compiled_content).with(snapshot: :pre).returns('asdf')
 
       # Mock site
       @site = mock
-      @site.stubs(:config).returns({ :base_url => 'http://example.com' })
+      @site.stubs(:config).returns({ base_url: 'http://example.com' })
 
       # Create feed item
       @item = mock
@@ -437,14 +436,14 @@ class Nanoc::Helpers::BloggingTest < Nanoc::TestCase
       @item.stubs(:[]).with(:[]).with(:feed_url).returns('http://example.com/feed')
 
       # Check
-      atom_feed :articles => [ @items[1] ]
+      atom_feed articles: [@items[1]]
     end
   end
 
   def test_atom_feed_with_limit_param
     if_have 'builder' do
       # Mock articles
-      @items = [ mock_article, mock_article ]
+      @items = [mock_article, mock_article]
       @items.each_with_index do |article, i|
         article.stubs(:[]).with(:title).returns("Article #{i}")
         article.stubs(:[]).with(:created_at).returns(Time.now - i)
@@ -452,7 +451,7 @@ class Nanoc::Helpers::BloggingTest < Nanoc::TestCase
 
       # Mock site
       @site = mock
-      @site.stubs(:config).returns({ :base_url => 'http://example.com' })
+      @site.stubs(:config).returns({ base_url: 'http://example.com' })
 
       # Create feed item
       @item = mock
@@ -462,7 +461,7 @@ class Nanoc::Helpers::BloggingTest < Nanoc::TestCase
       @item.stubs(:[]).with(:feed_url).returns('http://example.com/feed')
 
       # Check
-      result = atom_feed :limit => 1, :articles => @items
+      result = atom_feed limit: 1, articles: @items
       assert_match(
         Regexp.new('Article 0', Regexp::MULTILINE),
         result
@@ -477,7 +476,7 @@ class Nanoc::Helpers::BloggingTest < Nanoc::TestCase
   def test_atom_feed_sorting
     if_have 'builder' do
       # Mock articles
-      @items = [ mock_article, mock_article ]
+      @items = [mock_article, mock_article]
       @items.each_with_index do |article, i|
         article.stubs(:[]).with(:title).returns("Article #{i}")
       end
@@ -486,7 +485,7 @@ class Nanoc::Helpers::BloggingTest < Nanoc::TestCase
 
       # Mock site
       @site = mock
-      @site.stubs(:config).returns({ :base_url => 'http://example.com' })
+      @site.stubs(:config).returns({ base_url: 'http://example.com' })
 
       # Create feed item
       @item = mock
@@ -504,14 +503,44 @@ class Nanoc::Helpers::BloggingTest < Nanoc::TestCase
     end
   end
 
+  def test_atom_feed_preserve_order
+    if_have 'builder' do
+      # Mock articles
+      @items = [mock_article, mock_article]
+      @items.each_with_index do |article, i|
+        article.stubs(:[]).with(:title).returns("Article #{i}")
+      end
+      @items[0].stubs(:[]).with(:created_at).returns('01-01-2015')
+      @items[1].stubs(:[]).with(:created_at).returns('01-01-2014')
+
+      # Mock site
+      @site = mock
+      @site.stubs(:config).returns({ base_url: 'http://example.com' })
+
+      # Create feed item
+      @item = mock
+      @item.stubs(:[]).with(:title).returns('My Blog Or Something')
+      @item.stubs(:[]).with(:author_name).returns('J. Doe')
+      @item.stubs(:[]).with(:author_uri).returns('http://example.com/~jdoe')
+      @item.stubs(:[]).with(:feed_url).returns('http://example.com/feed')
+
+      # Check
+      result = atom_feed(preserve_order: true)
+      assert_match(
+        Regexp.new('Article 1.*Article 0', Regexp::MULTILINE),
+        result
+      )
+    end
+  end
+
   def test_atom_feed_with_content_proc_param
     if_have 'builder' do
       # Mock article
-      @items = [ mock_article ]
+      @items = [mock_article]
 
       # Mock site
       @site = mock
-      @site.stubs(:config).returns({ :base_url => 'http://example.com' })
+      @site.stubs(:config).returns({ base_url: 'http://example.com' })
 
       # Create feed item
       @item = mock
@@ -521,7 +550,7 @@ class Nanoc::Helpers::BloggingTest < Nanoc::TestCase
       @item.stubs(:[]).with(:feed_url).returns('http://example.com/feed')
 
       # Check
-      result = atom_feed :content_proc => lambda { |a| 'foobar!' }
+      result = atom_feed content_proc: ->(_a) { 'foobar!' }
       assert_match 'foobar!</content>', result
     end
   end
@@ -529,11 +558,11 @@ class Nanoc::Helpers::BloggingTest < Nanoc::TestCase
   def test_atom_feed_with_excerpt_proc_param
     if_have 'builder' do
       # Mock article
-      @items = [ mock_article ]
+      @items = [mock_article]
 
       # Mock site
       @site = mock
-      @site.stubs(:config).returns({ :base_url => 'http://example.com' })
+      @site.stubs(:config).returns({ base_url: 'http://example.com' })
 
       # Create feed item
       @item = mock
@@ -543,7 +572,7 @@ class Nanoc::Helpers::BloggingTest < Nanoc::TestCase
       @item.stubs(:[]).with(:[]).with(:feed_url).returns('http://example.com/feed')
 
       # Check
-      result = atom_feed :excerpt_proc => lambda { |a| 'foobar!' }
+      result = atom_feed excerpt_proc: ->(_a) { 'foobar!' }
       assert_match 'foobar!</summary>', result
     end
   end
@@ -551,11 +580,11 @@ class Nanoc::Helpers::BloggingTest < Nanoc::TestCase
   def test_atom_feed_with_icon_param
     if_have 'builder' do
       # Mock article
-      @items = [ mock_article ]
+      @items = [mock_article]
 
       # Mock site
       @site = mock
-      @site.stubs(:config).returns({ :base_url => 'http://example.com' })
+      @site.stubs(:config).returns({ base_url: 'http://example.com' })
 
       # Create feed item
       @item = mock
@@ -565,7 +594,7 @@ class Nanoc::Helpers::BloggingTest < Nanoc::TestCase
       @item.stubs(:[]).with(:feed_url).returns('http://example.com/feed')
 
       # Check
-      result = atom_feed :icon => 'http://example.com/icon.png'
+      result = atom_feed icon: 'http://example.com/icon.png'
       assert_match '<icon>http://example.com/icon.png</icon>', result
     end
   end
@@ -573,11 +602,11 @@ class Nanoc::Helpers::BloggingTest < Nanoc::TestCase
   def test_atom_feed_with_logo_param
     if_have 'builder' do
       # Mock article
-      @items = [ mock_article ]
+      @items = [mock_article]
 
       # Mock site
       @site = mock
-      @site.stubs(:config).returns({ :base_url => 'http://example.com' })
+      @site.stubs(:config).returns({ base_url: 'http://example.com' })
 
       # Create feed item
       @item = mock
@@ -587,7 +616,7 @@ class Nanoc::Helpers::BloggingTest < Nanoc::TestCase
       @item.stubs(:[]).with(:feed_url).returns('http://example.com/feed')
 
       # Check
-      result = atom_feed :logo => 'http://example.com/logo.png'
+      result = atom_feed logo: 'http://example.com/logo.png'
       assert_match '<logo>http://example.com/logo.png</logo>', result
     end
   end
@@ -595,12 +624,12 @@ class Nanoc::Helpers::BloggingTest < Nanoc::TestCase
   def test_atom_feed_with_item_without_path
     if_have 'builder' do
       # Create items
-      @items = [ mock_article ]
+      @items = [mock_article]
       @items[0].stubs(:path).returns(nil)
 
       # Mock site
       @site = mock
-      @site.stubs(:config).returns({ :base_url => 'http://example.com' })
+      @site.stubs(:config).returns({ base_url: 'http://example.com' })
 
       # Create feed item
       @item = mock
@@ -609,7 +638,7 @@ class Nanoc::Helpers::BloggingTest < Nanoc::TestCase
       @item.stubs(:[]).with(:author_name).returns('Denis Defreyne')
       @item.stubs(:[]).with(:author_uri).returns('http://stoneship.org/')
       @item.stubs(:[]).with(:feed_url).returns(nil)
-      @item.stubs(:path).returns("/journal/feed/")
+      @item.stubs(:path).returns('/journal/feed/')
 
       # Check
       atom_feed
@@ -618,7 +647,7 @@ class Nanoc::Helpers::BloggingTest < Nanoc::TestCase
 
   def test_url_for_without_custom_path_in_feed
     # Create site
-    @site = Nanoc::Site.new({ :base_url => 'http://example.com' })
+    @site = Nanoc::Site.new({ base_url: 'http://example.com' })
 
     # Create article
     item = Nanoc::Item.new('content', {}, '/foo/')
@@ -634,11 +663,11 @@ class Nanoc::Helpers::BloggingTest < Nanoc::TestCase
 
   def test_url_for_with_custom_path_in_feed
     # Create site
-    @site = Nanoc::Site.new({ :base_url => 'http://example.com' })
+    @site = Nanoc::Site.new({ base_url: 'http://example.com' })
 
     # Create article
     item = Nanoc::Item.new(
-      'content', { :custom_path_in_feed => '/meow/woof/' }, '/foo/')
+      'content', { custom_path_in_feed: '/meow/woof/' }, '/foo/')
     item.reps << Nanoc::ItemRep.new(item, :default)
 
     # Check
@@ -650,11 +679,11 @@ class Nanoc::Helpers::BloggingTest < Nanoc::TestCase
 
   def test_url_for_with_custom_url_in_feed
     # Create site
-    @site = Nanoc::Site.new({ :base_url => 'http://example.com' })
+    @site = Nanoc::Site.new({ base_url: 'http://example.com' })
 
     # Create article
     item = Nanoc::Item.new(
-      'content', { :custom_url_in_feed => 'http://example.org/x' }, '/foo/')
+      'content', { custom_url_in_feed: 'http://example.org/x' }, '/foo/')
     item.reps << Nanoc::ItemRep.new(item, :default)
 
     # Check
@@ -676,7 +705,7 @@ class Nanoc::Helpers::BloggingTest < Nanoc::TestCase
 
   def test_url_for_without_path
     # Create site
-    @site = Nanoc::Site.new({ :base_url => 'http://example.com' })
+    @site = Nanoc::Site.new({ base_url: 'http://example.com' })
 
     # Create article
     item = Nanoc::Item.new('content', {}, '/foo/')
@@ -689,7 +718,7 @@ class Nanoc::Helpers::BloggingTest < Nanoc::TestCase
 
   def test_feed_url_without_custom_feed_url
     # Create site
-    @site = Nanoc::Site.new({ :base_url => 'http://example.com' })
+    @site = Nanoc::Site.new({ base_url: 'http://example.com' })
 
     # Create article
     @item = Nanoc::Item.new('content', {}, '/foo/')
@@ -705,10 +734,10 @@ class Nanoc::Helpers::BloggingTest < Nanoc::TestCase
 
   def test_feed_url_with_custom_feed_url
     # Create site
-    @site = Nanoc::Site.new({ :base_url => 'http://example.com' })
+    @site = Nanoc::Site.new({ base_url: 'http://example.com' })
 
     # Create feed item
-    @item = Nanoc::Item.new('content', { :feed_url => 'http://example.com/feed/' }, '/foo/')
+    @item = Nanoc::Item.new('content', { feed_url: 'http://example.com/feed/' }, '/foo/')
     @item.reps << Nanoc::ItemRep.new(@item, :default)
     @item.reps[0].path = '/foo/bar/'
 
@@ -731,10 +760,10 @@ class Nanoc::Helpers::BloggingTest < Nanoc::TestCase
 
   def test_atom_tag_for_with_path
     # Create site
-    @site = Nanoc::Site.new({ :base_url => 'http://example.com' })
+    @site = Nanoc::Site.new({ base_url: 'http://example.com' })
 
     # Create article
-    item = Nanoc::Item.new('content', { :created_at => '2008-05-19' }, '/foo/')
+    item = Nanoc::Item.new('content', { created_at: '2008-05-19' }, '/foo/')
     item.reps << Nanoc::ItemRep.new(item, :default)
     item.reps[0].path = '/foo/bar/'
 
@@ -744,10 +773,10 @@ class Nanoc::Helpers::BloggingTest < Nanoc::TestCase
 
   def test_atom_tag_for_without_path
     # Create site
-    @site = Nanoc::Site.new({ :base_url => 'http://example.com' })
+    @site = Nanoc::Site.new({ base_url: 'http://example.com' })
 
     # Create article
-    item = Nanoc::Item.new('content', { :created_at => '2008-05-19' }, '/baz/qux/')
+    item = Nanoc::Item.new('content', { created_at: '2008-05-19' }, '/baz/qux/')
     item.reps << Nanoc::ItemRep.new(item, :default)
 
     # Check
@@ -756,10 +785,10 @@ class Nanoc::Helpers::BloggingTest < Nanoc::TestCase
 
   def test_atom_tag_for_with_base_url_in_dir
     # Create site
-    @site = Nanoc::Site.new({ :base_url => 'http://example.com/somedir' })
+    @site = Nanoc::Site.new({ base_url: 'http://example.com/somedir' })
 
     # Create article
-    item = Nanoc::Item.new('content', { :created_at => '2008-05-19' }, '/foo/')
+    item = Nanoc::Item.new('content', { created_at: '2008-05-19' }, '/foo/')
     item.reps << Nanoc::ItemRep.new(item, :default)
     item.reps[0].path = '/foo/bar/'
 
@@ -769,10 +798,10 @@ class Nanoc::Helpers::BloggingTest < Nanoc::TestCase
 
   def test_atom_tag_for_with_time
     # Create site
-    @site = Nanoc::Site.new({ :base_url => 'http://example.com' })
+    @site = Nanoc::Site.new({ base_url: 'http://example.com' })
 
     # Create article
-    item = Nanoc::Item.new('content', { :created_at => Time.parse('2008-05-19') }, '/foo/')
+    item = Nanoc::Item.new('content', { created_at: Time.parse('2008-05-19') }, '/foo/')
     item.reps << Nanoc::ItemRep.new(item, :default)
     item.reps[0].path = '/foo/bar/'
 
@@ -782,15 +811,14 @@ class Nanoc::Helpers::BloggingTest < Nanoc::TestCase
 
   def test_atom_tag_for_with_date
     # Create site
-    @site = Nanoc::Site.new({ :base_url => 'http://example.com' })
+    @site = Nanoc::Site.new({ base_url: 'http://example.com' })
 
     # Create article
-    item = Nanoc::Item.new('content', { :created_at => Date.parse('2008-05-19') }, '/foo/')
+    item = Nanoc::Item.new('content', { created_at: Date.parse('2008-05-19') }, '/foo/')
     item.reps << Nanoc::ItemRep.new(item, :default)
     item.reps[0].path = '/foo/bar/'
 
     # Check
     assert_equal('tag:example.com,2008-05-19:/foo/bar/', atom_tag_for(item))
   end
-
 end
diff --git a/test/helpers/test_breadcrumbs.rb b/test/helpers/test_breadcrumbs.rb
index b94092b..da92a4f 100644
--- a/test/helpers/test_breadcrumbs.rb
+++ b/test/helpers/test_breadcrumbs.rb
@@ -1,43 +1,41 @@
 # encoding: utf-8
 
 class Nanoc::Helpers::BreadcrumbsTest < Nanoc::TestCase
-
   include Nanoc::Helpers::Breadcrumbs
 
   def test_breadcrumbs_trail_at_root
     @items = Nanoc::ItemArray.new
-    @items << Nanoc::Item.new("root", {}, '/')
+    @items << Nanoc::Item.new('root', {}, '/')
     @item = @items.last
 
-    assert_equal [ @items[0] ], breadcrumbs_trail
+    assert_equal [@items[0]], breadcrumbs_trail
   end
 
   def test_breadcrumbs_trail_with_1_parent
     @items = Nanoc::ItemArray.new
-    @items << Nanoc::Item.new("parent", {}, '/')
-    @items << Nanoc::Item.new("child",  {}, '/foo/')
+    @items << Nanoc::Item.new('parent', {}, '/')
+    @items << Nanoc::Item.new('child',  {}, '/foo/')
     @item = @items.last
 
-    assert_equal [ @items[0], @items[1] ], breadcrumbs_trail
+    assert_equal [@items[0], @items[1]], breadcrumbs_trail
   end
 
   def test_breadcrumbs_trail_with_many_parents
     @items = Nanoc::ItemArray.new
-    @items << Nanoc::Item.new("grandparent", {}, '/')
-    @items << Nanoc::Item.new("parent",      {}, '/foo/')
-    @items << Nanoc::Item.new("child",       {}, '/foo/bar/')
+    @items << Nanoc::Item.new('grandparent', {}, '/')
+    @items << Nanoc::Item.new('parent',      {}, '/foo/')
+    @items << Nanoc::Item.new('child',       {}, '/foo/bar/')
     @item = @items.last
 
-    assert_equal [ @items[0], @items[1], @items[2] ], breadcrumbs_trail
+    assert_equal [@items[0], @items[1], @items[2]], breadcrumbs_trail
   end
 
   def test_breadcrumbs_trail_with_nils
     @items = Nanoc::ItemArray.new
-    @items << Nanoc::Item.new("grandparent", {}, '/')
-    @items << Nanoc::Item.new("child",       {}, '/foo/bar/')
+    @items << Nanoc::Item.new('grandparent', {}, '/')
+    @items << Nanoc::Item.new('child',       {}, '/foo/bar/')
     @item = @items.last
 
-    assert_equal [ @items[0], nil, @items[1] ], breadcrumbs_trail
+    assert_equal [@items[0], nil, @items[1]], breadcrumbs_trail
   end
-
 end
diff --git a/test/helpers/test_capturing.rb b/test/helpers/test_capturing.rb
index a77e6d0..cf7c31f 100644
--- a/test/helpers/test_capturing.rb
+++ b/test/helpers/test_capturing.rb
@@ -1,7 +1,6 @@
 # encoding: utf-8
 
 class Nanoc::Helpers::CapturingTest < Nanoc::TestCase
-
   include Nanoc::Helpers::Capturing
 
   def test_content_for
@@ -13,9 +12,9 @@ class Nanoc::Helpers::CapturingTest < Nanoc::TestCase
     end
 
     # Build content to be evaluated
-    content = "head <% content_for :sidebar do %>\n" +
-              "  <%= 1+2 %>\n" +
-              "<% end %> foot"
+    content = "head <% content_for :sidebar do %>\n" \
+              "  <%= 1+2 %>\n" \
+              '<% end %> foot'
 
     # Build site
     @site = Nanoc3::Site.new({})
@@ -89,7 +88,7 @@ EOS
 
     @site = Nanoc3::Site.new({})
     @item = Nanoc::Item.new('content', {}, '/')
-    content = "<% content_for :a do %>Content One<% end %>"
+    content = '<% content_for :a do %>Content One<% end %>'
     ::ERB.new(content).result(binding)
 
     assert_equal 'Content One', content_for(@item, :a)
@@ -97,7 +96,7 @@ EOS
 
     @site = Nanoc3::Site.new({})
     @item = Nanoc::Item.new('content', {}, '/')
-    content = "<% content_for :b do %>Content Two<% end %>"
+    content = '<% content_for :b do %>Content Two<% end %>'
     ::ERB.new(content).result(binding)
 
     assert_equal nil,           content_for(@item, :a)
@@ -105,7 +104,7 @@ EOS
   end
 
   def test_dependencies
-    with_site do |site|
+    with_site do |_site|
       # Prepare
       File.open('lib/helpers.rb', 'w') do |io|
         io.write 'include Nanoc::Helpers::Capturing'
@@ -135,7 +134,7 @@ EOS
   end
 
   def test_dependency_without_item_variable
-    with_site do |site|
+    with_site do |_site|
       # Prepare
       File.open('lib/helpers.rb', 'w') do |io|
         io.write "include Nanoc::Helpers::Capturing\n"
@@ -170,14 +169,14 @@ EOS
   end
 
   def test_self
-    with_site do |site|
+    with_site do |_site|
       File.open('lib/helpers.rb', 'w') do |io|
         io.write 'include Nanoc::Helpers::Capturing'
       end
 
       File.open('content/self.erb', 'w') do |io|
-        io.write "<% content_for :foo do %>Foo!<% end %>"
-        io.write "<%= content_for(@item, :foo) %>"
+        io.write '<% content_for :foo do %>Foo!<% end %>'
+        io.write '<%= content_for(@item, :foo) %>'
       end
 
       File.open('Rules', 'w') do |io|
@@ -191,7 +190,7 @@ EOS
   end
 
   def test_recompile_dependency
-    with_site do |site|
+    with_site do |_site|
       # Prepare
       File.open('lib/helpers.rb', 'w') do |io|
         io.write 'include Nanoc::Helpers::Capturing'
@@ -221,5 +220,4 @@ EOS
       assert_equal 'New-Content', File.read('output/includer/index.html')
     end
   end
-
 end
diff --git a/test/helpers/test_filtering.rb b/test/helpers/test_filtering.rb
index 74e856c..698fc7f 100644
--- a/test/helpers/test_filtering.rb
+++ b/test/helpers/test_filtering.rb
@@ -1,15 +1,14 @@
 # encoding: utf-8
 
 class Nanoc::Helpers::FilteringTest < Nanoc::TestCase
-
   include Nanoc::Helpers::Filtering
 
   def test_filter_simple
     if_have 'rubypants' do
       # Build content to be evaluated
-      content = "<p>Foo...</p>\n" +
-                "<% filter :rubypants do %>\n" +
-                " <p>Bar...</p>\n" +
+      content = "<p>Foo...</p>\n" \
+                "<% filter :rubypants do %>\n" \
+                " <p>Bar...</p>\n" \
                 "<% end %>\n"
 
       # Mock item and rep
@@ -28,9 +27,9 @@ class Nanoc::Helpers::FilteringTest < Nanoc::TestCase
   def test_filter_with_assigns
     if_have 'rubypants' do
       # Build content to be evaluated
-      content = "<p>Foo...</p>\n" +
-                "<% filter :erb do %>\n" +
-                " <p><%%= @item[:title] %></p>\n" +
+      content = "<p>Foo...</p>\n" \
+                "<% filter :erb do %>\n" \
+                " <p><%%= @item[:title] %></p>\n" \
                 "<% end %>\n"
 
       # Mock item and rep
@@ -40,8 +39,8 @@ class Nanoc::Helpers::FilteringTest < Nanoc::TestCase
       @item_rep = mock
       @item_rep.expects(:name).returns('default')
       @item_rep.expects(:assigns).returns({
-        :item     => @item,
-        :item_rep => @item_rep
+        item: @item,
+        item_rep: @item_rep
       })
 
       # Evaluate content
@@ -55,9 +54,9 @@ class Nanoc::Helpers::FilteringTest < Nanoc::TestCase
 
   def test_filter_with_unknown_filter_name
     # Build content to be evaluated
-    content = "<p>Foo...</p>\n" +
-              "<% filter :askjdflkawgjlkwaheflnvz do %>\n" +
-              " <p>Blah blah blah.</p>\n" +
+    content = "<p>Foo...</p>\n" \
+              "<% filter :askjdflkawgjlkwaheflnvz do %>\n" \
+              " <p>Blah blah blah.</p>\n" \
               "<% end %>\n"
 
     # Evaluate content
@@ -69,8 +68,8 @@ class Nanoc::Helpers::FilteringTest < Nanoc::TestCase
   def test_filter_with_arguments
     if_have 'coderay' do
       # Build content to be evaluated
-      content = "<% filter :coderay, :language => 'ruby' do %>\n" +
-                "   def some_function ; x = blah.foo ; x.bar 'xyzzy' ; end\n" +
+      content = "<% filter :coderay, :language => 'ruby' do %>\n" \
+                "   def some_function ; x = blah.foo ; x.bar 'xyzzy' ; end\n" \
                 "<% end %>\n"
 
       # Mock item and rep
@@ -86,9 +85,9 @@ class Nanoc::Helpers::FilteringTest < Nanoc::TestCase
   def test_with_haml
     if_have 'haml' do
       # Build content to be evaluated
-      content = "%p Foo.\n" +
-                "- filter(:erb) do\n" +
-                "  <%= 'abc' + 'xyz' %>\n" +
+      content = "%p Foo.\n" \
+                "- filter(:erb) do\n" \
+                "  <%= 'abc' + 'xyz' %>\n" \
                 "%p Bar.\n"
 
       # Mock item and rep
@@ -107,8 +106,8 @@ class Nanoc::Helpers::FilteringTest < Nanoc::TestCase
     Nanoc::NotificationCenter.on(:filtering_ended)   { notifications << :filtering_ended   }
 
     # Build content to be evaluated
-    content = "<% filter :erb do %>\n" +
-              "   ... stuff ...\n" +
+    content = "<% filter :erb do %>\n" \
+              "   ... stuff ...\n" \
               "<% end %>\n"
 
     # Mock item and rep
@@ -120,5 +119,4 @@ class Nanoc::Helpers::FilteringTest < Nanoc::TestCase
     assert notifications.include?(:filtering_started)
     assert notifications.include?(:filtering_ended)
   end
-
 end
diff --git a/test/helpers/test_html_escape.rb b/test/helpers/test_html_escape.rb
index c3229e2..483b600 100644
--- a/test/helpers/test_html_escape.rb
+++ b/test/helpers/test_html_escape.rb
@@ -1,7 +1,6 @@
 # encoding: utf-8
 
 class Nanoc::Helpers::HTMLEscapeTest < Nanoc::TestCase
-
   include Nanoc::Helpers::HTMLEscape
 
   def test_html_escape_with_string
@@ -26,5 +25,4 @@ class Nanoc::Helpers::HTMLEscapeTest < Nanoc::TestCase
       h
     end
   end
-
 end
diff --git a/test/helpers/test_link_to.rb b/test/helpers/test_link_to.rb
index bdc6c5f..08a9f7c 100644
--- a/test/helpers/test_link_to.rb
+++ b/test/helpers/test_link_to.rb
@@ -1,7 +1,6 @@
 # encoding: utf-8
 
 class Nanoc::Helpers::LinkToTest < Nanoc::TestCase
-
   include Nanoc::Helpers::LinkTo
 
   def test_link_to_with_path
@@ -40,7 +39,7 @@ class Nanoc::Helpers::LinkToTest < Nanoc::TestCase
     # Check
     assert_equal(
       '<a title="Dis mai foo!" href="/foo/">Foo</a>',
-      link_to('Foo', '/foo/', :title => 'Dis mai foo!')
+      link_to('Foo', '/foo/', title: 'Dis mai foo!')
     )
   end
 
@@ -48,16 +47,18 @@ class Nanoc::Helpers::LinkToTest < Nanoc::TestCase
     # Check
     assert_equal(
       '<a title="Foo & Bar" href="/foo&bar/">Foo & Bar</a>',
-      link_to('Foo & Bar', '/foo&bar/', :title => 'Foo & Bar')
+      link_to('Foo & Bar', '/foo&bar/', title: 'Foo & Bar')
     )
   end
 
   def test_link_to_to_nil_item_or_item_rep
     obj = Object.new
-    def obj.path ; nil ; end
+    def obj.path
+      nil
+    end
 
     assert_raises RuntimeError do
-      link_to("Some Text", obj)
+      link_to('Some Text', obj)
     end
   end
 
@@ -151,7 +152,6 @@ class Nanoc::Helpers::LinkToTest < Nanoc::TestCase
     )
   end
 
-
   def test_relative_path_to_item
     # Mock self
     @item_rep = mock
@@ -210,7 +210,7 @@ class Nanoc::Helpers::LinkToTest < Nanoc::TestCase
     YARD.parse(LIB_DIR + '/nanoc/helpers/link_to.rb')
 
     # Mock
-    @items = [ mock, mock, mock ]
+    @items = [mock, mock, mock]
     @items[0].stubs(:identifier).returns('/about/')
     @items[0].stubs(:path).returns('/about.html')
     @items[1].stubs(:identifier).returns('/software/')
@@ -250,5 +250,4 @@ class Nanoc::Helpers::LinkToTest < Nanoc::TestCase
     # Run
     assert_examples_correct 'Nanoc::Helpers::LinkTo#relative_path_to'
   end
-
 end
diff --git a/test/helpers/test_rendering.rb b/test/helpers/test_rendering.rb
index b1424ba..e84f78f 100644
--- a/test/helpers/test_rendering.rb
+++ b/test/helpers/test_rendering.rb
@@ -1,7 +1,6 @@
 # encoding: utf-8
 
 class Nanoc::Helpers::RenderingTest < Nanoc::TestCase
-
   include Nanoc::Helpers::Rendering
 
   def test_render
@@ -38,7 +37,7 @@ class Nanoc::Helpers::RenderingTest < Nanoc::TestCase
         io.write("layout '/foo/', nil\n")
       end
 
-      File.open('layouts/foo.erb', 'w').close()
+      File.open('layouts/foo.erb', 'w').close
 
       assert_raises(Nanoc::Errors::CannotDetermineFilter) do
         render '/foo/'
@@ -54,7 +53,7 @@ class Nanoc::Helpers::RenderingTest < Nanoc::TestCase
         io.write("layout '/foo/', :asdf\n")
       end
 
-      File.open('layouts/foo.erb', 'w').close()
+      File.open('layouts/foo.erb', 'w').close
 
       assert_raises(Nanoc::Errors::UnknownFilter) do
         render '/foo/'
@@ -76,12 +75,11 @@ class Nanoc::Helpers::RenderingTest < Nanoc::TestCase
 
       _erbout = '[erbout-before]'
       result = render '/foo/' do
-        _erbout << "This is some extra content"
+        _erbout << 'This is some extra content'
       end
 
       assert_equal('[erbout-before][partial-before]This is some extra content[partial-after]', _erbout)
       assert_equal '', result
     end
   end
-
 end
diff --git a/test/helpers/test_tagging.rb b/test/helpers/test_tagging.rb
index 16ea940..a197a23 100644
--- a/test/helpers/test_tagging.rb
+++ b/test/helpers/test_tagging.rb
@@ -1,7 +1,6 @@
 # encoding: utf-8
 
 class Nanoc::Helpers::TaggingTest < Nanoc::TestCase
-
   include Nanoc::Helpers::Tagging
 
   def test_tags_for_without_tags
@@ -17,45 +16,45 @@ class Nanoc::Helpers::TaggingTest < Nanoc::TestCase
 
   def test_tags_for_with_custom_base_url
     # Create item
-    item = Nanoc::Item.new('content', { :tags => [ 'foo', 'bar' ]}, '/path/')
+    item = Nanoc::Item.new('content', { tags: %w(foo bar) }, '/path/')
 
     # Check
     assert_equal(
-      "#{link_for_tag('foo', 'http://stoneship.org/tag/')}, " +
+      "#{link_for_tag('foo', 'http://stoneship.org/tag/')}, " \
       "#{link_for_tag('bar', 'http://stoneship.org/tag/')}",
-      tags_for(item, :base_url => 'http://stoneship.org/tag/')
+      tags_for(item, base_url: 'http://stoneship.org/tag/')
     )
   end
 
   def test_tags_for_with_custom_none_text
     # Create item
-    item = Nanoc::Item.new('content', { :tags => [ ]}, '/path/')
+    item = Nanoc::Item.new('content', { tags: [] }, '/path/')
 
     # Check
     assert_equal(
       'no tags for you, fool',
-      tags_for(item, :none_text => 'no tags for you, fool')
+      tags_for(item, none_text: 'no tags for you, fool')
     )
   end
 
   def test_tags_for_with_custom_separator
     # Create item
-    item = Nanoc::Item.new('content', { :tags => [ 'foo', 'bar' ]}, '/path/')
+    item = Nanoc::Item.new('content', { tags: %w(foo bar) }, '/path/')
 
     # Check
     assert_equal(
-      "#{link_for_tag('foo', 'http://technorati.com/tag/')} ++ " +
+      "#{link_for_tag('foo', 'http://technorati.com/tag/')} ++ " \
       "#{link_for_tag('bar', 'http://technorati.com/tag/')}",
-      tags_for(item, :separator => ' ++ ')
+      tags_for(item, separator: ' ++ ')
     )
   end
 
   def test_items_with_tag
     # Create items
     @items = [
-      Nanoc::Item.new('item 1', { :tags => [ :foo ]       }, '/item1/'),
-      Nanoc::Item.new('item 2', { :tags => [ :bar ]       }, '/item2/'),
-      Nanoc::Item.new('item 3', { :tags => [ :foo, :bar ] }, '/item3/')
+      Nanoc::Item.new('item 1', { tags: [:foo]       }, '/item1/'),
+      Nanoc::Item.new('item 2', { tags: [:bar]       }, '/item2/'),
+      Nanoc::Item.new('item 3', { tags: [:foo, :bar] }, '/item3/')
     ]
 
     # Find items
@@ -63,23 +62,22 @@ class Nanoc::Helpers::TaggingTest < Nanoc::TestCase
 
     # Check
     assert_equal(
-      [ @items[0], @items[2] ],
+      [@items[0], @items[2]],
       items_with_foo_tag
     )
   end
 
   def test_link_for_tag
     assert_equal(
-      %[<a href="http://stoneship.org/tags/foobar" rel="tag">foobar</a>],
+      %(<a href="http://stoneship.org/tags/foobar" rel="tag">foobar</a>),
       link_for_tag('foobar', 'http://stoneship.org/tags/')
     )
   end
 
   def test_link_for_tag_escape
     assert_equal(
-      %[<a href="http://stoneship.org/tags&stuff/foo&bar" rel="tag">foo&bar</a>],
+      %(<a href="http://stoneship.org/tags&stuff/foo&bar" rel="tag">foo&bar</a>),
       link_for_tag('foo&bar', 'http://stoneship.org/tags&stuff/')
     )
   end
-
 end
diff --git a/test/helpers/test_text.rb b/test/helpers/test_text.rb
index 1933622..5b082ca 100644
--- a/test/helpers/test_text.rb
+++ b/test/helpers/test_text.rb
@@ -1,22 +1,20 @@
 # encoding: utf-8
 
 class Nanoc::Helpers::TextTest < Nanoc::TestCase
-
   include Nanoc::Helpers::Text
 
   def test_excerpt_length
-    assert_equal('...',                         excerptize('Foo bar baz quux meow woof', :length => 3))
-    assert_equal('Foo ...',                     excerptize('Foo bar baz quux meow woof', :length => 7))
-    assert_equal('Foo bar baz quux meow woof',  excerptize('Foo bar baz quux meow woof', :length => 26))
-    assert_equal('Foo bar baz quux meow woof',  excerptize('Foo bar baz quux meow woof', :length => 8623785))
+    assert_equal('...',                         excerptize('Foo bar baz quux meow woof', length: 3))
+    assert_equal('Foo ...',                     excerptize('Foo bar baz quux meow woof', length: 7))
+    assert_equal('Foo bar baz quux meow woof',  excerptize('Foo bar baz quux meow woof', length: 26))
+    assert_equal('Foo bar baz quux meow woof',  excerptize('Foo bar baz quux meow woof', length: 8_623_785))
   end
 
   def test_excerpt_omission
-    assert_equal('Foo [continued]',             excerptize('Foo bar baz quux meow woof', :length => 15, :omission => '[continued]'))
+    assert_equal('Foo [continued]',             excerptize('Foo bar baz quux meow woof', length: 15, omission: '[continued]'))
   end
 
   def test_strip_html
-    # TODO implement
+    # TODO: implement
   end
-
 end
diff --git a/test/helpers/test_xml_sitemap.rb b/test/helpers/test_xml_sitemap.rb
index e435ebb..b17b406 100644
--- a/test/helpers/test_xml_sitemap.rb
+++ b/test/helpers/test_xml_sitemap.rb
@@ -1,7 +1,6 @@
 # encoding: utf-8
 
 class Nanoc::Helpers::XMLSitemapTest < Nanoc::TestCase
-
   include Nanoc::Helpers::XMLSitemap
 
   def teardown
@@ -19,27 +18,27 @@ class Nanoc::Helpers::XMLSitemapTest < Nanoc::TestCase
 
       # Create item 1
       @items << Nanoc::Item.new('some content 1', {}, '/item-one/')
-      self.create_item_rep(@items.last, :one_a, '/item-one/a/')
-      self.create_item_rep(@items.last, :one_b, '/item-one/b/')
+      create_item_rep(@items.last, :one_a, '/item-one/a/')
+      create_item_rep(@items.last, :one_b, '/item-one/b/')
 
       # Create item 2
-      @items << Nanoc::Item.new('some content 2', { :is_hidden => true }, '/item-two/')
+      @items << Nanoc::Item.new('some content 2', { is_hidden: true }, '/item-two/')
 
       # Create item 3
-      attrs = { :mtime => Time.parse('2004-07-12'), :changefreq => 'daily', :priority => 0.5 }
+      attrs = { mtime: Time.parse('2004-07-12'), changefreq: 'daily', priority: 0.5 }
       @items << Nanoc::Item.new('some content 3', attrs, '/item-three/')
-      self.create_item_rep(@items.last, :three_a, '/item-three/a/')
-      self.create_item_rep(@items.last, :three_b, '/item-three/b/')
+      create_item_rep(@items.last, :three_a, '/item-three/a/')
+      create_item_rep(@items.last, :three_b, '/item-three/b/')
 
       # Create item 4
       @items << Nanoc::Item.new('some content 4', {}, '/item-four/')
-      self.create_item_rep(@items.last, :four_a, nil)
+      create_item_rep(@items.last, :four_a, nil)
 
       # Create sitemap item
       @item = Nanoc::Item.new('sitemap content', {}, '/sitemap/')
 
       # Create site
-      @site = Nanoc::Site.new({ :base_url => 'http://example.com' })
+      @site = Nanoc::Site.new({ base_url: 'http://example.com' })
 
       # Build sitemap
       res = xml_sitemap
@@ -75,18 +74,18 @@ class Nanoc::Helpers::XMLSitemapTest < Nanoc::TestCase
       @items = []
       @items << nil
       @items << Nanoc::Item.new('some content 1', {}, '/item-one/')
-      self.create_item_rep(@items.last, :one_a, '/item-one/a/')
-      self.create_item_rep(@items.last, :one_b, '/item-one/b/')
+      create_item_rep(@items.last, :one_a, '/item-one/a/')
+      create_item_rep(@items.last, :one_b, '/item-one/b/')
       @items << nil
 
       # Create sitemap item
       @item = Nanoc::Item.new('sitemap content', {}, '/sitemap/')
 
       # Create site
-      @site = Nanoc::Site.new({ :base_url => 'http://example.com' })
+      @site = Nanoc::Site.new({ base_url: 'http://example.com' })
 
       # Build sitemap
-      res = xml_sitemap(:items => [ @items[1] ])
+      res = xml_sitemap(items: [@items[1]])
 
       # Check
       doc = Nokogiri::XML(res)
@@ -108,18 +107,18 @@ class Nanoc::Helpers::XMLSitemapTest < Nanoc::TestCase
   def test_filter
     if_have 'builder', 'nokogiri' do
       # Create items
-      @items = [ Nanoc::Item.new('some content 1', {}, '/item-one/') ]
-      self.create_item_rep(@items.last, :one_a, '/item-one/a/')
-      self.create_item_rep(@items.last, :one_b, '/item-one/b/')
+      @items = [Nanoc::Item.new('some content 1', {}, '/item-one/')]
+      create_item_rep(@items.last, :one_a, '/item-one/a/')
+      create_item_rep(@items.last, :one_b, '/item-one/b/')
 
       # Create sitemap item
       @item = Nanoc::Item.new('sitemap content', {}, '/sitemap/')
 
       # Create site
-      @site = Nanoc::Site.new({ :base_url => 'http://example.com' })
+      @site = Nanoc::Site.new({ base_url: 'http://example.com' })
 
       # Build sitemap
-      res = xml_sitemap(:rep_select => lambda { |rep| rep.name == :one_a } )
+      res = xml_sitemap(rep_select: ->(rep) { rep.name == :one_a })
 
       # Check
       doc = Nokogiri::XML(res)
@@ -139,23 +138,23 @@ class Nanoc::Helpers::XMLSitemapTest < Nanoc::TestCase
       # Create items
       @items = []
       @items << Nanoc::Item.new('some content 1', {}, '/george/')
-      self.create_item_rep(@items.last, :a_alice,   '/george/alice/')
-      self.create_item_rep(@items.last, :b_zoey,    '/george/zoey/')
+      create_item_rep(@items.last, :a_alice,   '/george/alice/')
+      create_item_rep(@items.last, :b_zoey,    '/george/zoey/')
       @items << Nanoc::Item.new('some content 1', {}, '/walton/')
-      self.create_item_rep(@items.last, :a_eve,     '/walton/eve/')
-      self.create_item_rep(@items.last, :b_bob,     '/walton/bob/')
+      create_item_rep(@items.last, :a_eve,     '/walton/eve/')
+      create_item_rep(@items.last, :b_bob,     '/walton/bob/')
       @items << Nanoc::Item.new('some content 1', {}, '/lucas/')
-      self.create_item_rep(@items.last, :a_trudy,   '/lucas/trudy/')
-      self.create_item_rep(@items.last, :b_mallory, '/lucas/mallory/')
+      create_item_rep(@items.last, :a_trudy,   '/lucas/trudy/')
+      create_item_rep(@items.last, :b_mallory, '/lucas/mallory/')
 
       # Create sitemap item
       @item = Nanoc::Item.new('sitemap content', {}, '/sitemap/')
 
       # Create site
-      @site = Nanoc::Site.new({ :base_url => 'http://example.com' })
+      @site = Nanoc::Site.new({ base_url: 'http://example.com' })
 
       # Build sitemap
-      res = xml_sitemap(:items => @items)
+      res = xml_sitemap(items: @items)
 
       # Check
       doc = Nokogiri::XML(res)
@@ -172,14 +171,13 @@ class Nanoc::Helpers::XMLSitemapTest < Nanoc::TestCase
     end
   end
 
-protected
+  protected
 
   def create_item_rep(item, name, path)
     rep = Nanoc::ItemRep.new(item, name)
-    rep.paths     = { :last => path }
-    rep.raw_paths = { :last => path }
+    rep.paths     = { last: path }
+    rep.raw_paths = { last: path }
     item.reps << rep
     rep
   end
-
 end
diff --git a/test/tasks/test_clean.rb b/test/tasks/test_clean.rb
index 337be81..6d440f2 100644
--- a/test/tasks/test_clean.rb
+++ b/test/tasks/test_clean.rb
@@ -1,18 +1,17 @@
 # encoding: utf-8
 
 class Nanoc::Tasks::CleanTest < Nanoc::TestCase
-
   def test_simple
     if_have 'w3c_validators' do
       # Stub items
-      items = [ mock, mock ]
-      reps  = [ [ mock, mock ], [ mock, mock ] ]
+      items = [mock, mock]
+      reps  = [[mock, mock], [mock, mock]]
       items[0].expects(:reps).returns(reps[0])
       items[1].expects(:reps).returns(reps[1])
 
       # Create sample files
-      [ 0, 1 ].each do |item_id|
-        [ 0, 1 ].each do |rep_id|
+      [0, 1].each do |item_id|
+        [0, 1].each do |rep_id|
           filename = "item-#{item_id}-rep-#{rep_id}.txt"
           reps[item_id][rep_id].expects(:raw_path).returns(filename)
           File.open(filename, 'w') { |io| io.write('hello') }
@@ -31,8 +30,8 @@ class Nanoc::Tasks::CleanTest < Nanoc::TestCase
       clean.run
 
       # Check
-      [ 0, 1 ].each do |item_id|
-        [ 0, 1 ].each do |rep_id|
+      [0, 1].each do |item_id|
+        [0, 1].each do |rep_id|
           filename = "item-#{item_id}-rep-#{rep_id}.txt"
           assert !File.file?(filename)
         end
@@ -45,14 +44,14 @@ class Nanoc::Tasks::CleanTest < Nanoc::TestCase
       # Stub items
       item = mock
       rep = mock
-      item.expects(:reps).returns([ rep ])
+      item.expects(:reps).returns([rep])
 
       # Create sample file
       rep.expects(:raw_path).returns(nil)
 
       # Stub site
       site = mock
-      site.expects(:items).returns([ item ])
+      site.expects(:items).returns([item])
 
       # Create clean task
       clean = ::Nanoc::Tasks::Clean.new(site)
@@ -61,5 +60,4 @@ class Nanoc::Tasks::CleanTest < Nanoc::TestCase
       clean.run
     end
   end
-
 end
diff --git a/test/test_gem.rb b/test/test_gem.rb
index 7356097..8ce4601 100644
--- a/test/test_gem.rb
+++ b/test/test_gem.rb
@@ -1,7 +1,6 @@
 # encoding: utf-8
 
 class Nanoc::GemTest < Nanoc::TestCase
-
   def setup
     super
     FileUtils.cd(@orig_wd)
@@ -15,7 +14,7 @@ class Nanoc::GemTest < Nanoc::TestCase
     files_before = Set.new Dir['**/*']
     stdout = StringIO.new
     stderr = StringIO.new
-    piper = Nanoc::Extra::Piper.new(:stdout => stdout, :stderr => stderr)
+    piper = Nanoc::Extra::Piper.new(stdout: stdout, stderr: stderr)
     piper.run(%w( gem build nanoc.gemspec ), nil)
     files_after = Set.new Dir['**/*']
 
@@ -30,5 +29,4 @@ class Nanoc::GemTest < Nanoc::TestCase
   ensure
     Dir['nanoc-*.gem'].each { |f| FileUtils.rm(f) }
   end
-
 end

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-ruby-extras/nanoc.git



More information about the Pkg-ruby-extras-commits mailing list