nanoc 4.11.13 → 4.11.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/NEWS.md +790 -744
  3. data/lib/nanoc.rb +4 -2
  4. data/lib/nanoc/data_sources/filesystem.rb +12 -6
  5. data/lib/nanoc/extra.rb +1 -0
  6. data/lib/nanoc/extra/core_ext.rb +0 -1
  7. data/lib/nanoc/extra/link_collector.rb +1 -1
  8. data/lib/nanoc/extra/srcset_parser.rb +79 -0
  9. data/lib/nanoc/filters/colorize_syntax/colorizers.rb +1 -1
  10. data/lib/nanoc/filters/erb.rb +1 -5
  11. data/lib/nanoc/filters/relativize_paths.rb +62 -10
  12. data/lib/nanoc/filters/sass/functions.rb +1 -1
  13. data/lib/nanoc/helpers/rendering.rb +5 -4
  14. data/lib/nanoc/orig_cli.rb +0 -5
  15. data/lib/nanoc/rule_dsl.rb +1 -0
  16. data/lib/nanoc/rule_dsl/action_provider.rb +1 -1
  17. data/lib/nanoc/rule_dsl/compiler_dsl.rb +1 -1
  18. data/lib/nanoc/rule_dsl/errors.rb +25 -0
  19. data/lib/nanoc/rule_dsl/rules_loader.rb +1 -1
  20. data/lib/nanoc/version.rb +1 -1
  21. metadata +37 -33
  22. data/lib/nanoc/base.rb +0 -13
  23. data/lib/nanoc/base/changes_stream.rb +0 -53
  24. data/lib/nanoc/base/errors.rb +0 -65
  25. data/lib/nanoc/checking.rb +0 -14
  26. data/lib/nanoc/checking/check.rb +0 -93
  27. data/lib/nanoc/checking/checks.rb +0 -14
  28. data/lib/nanoc/checking/checks/css.rb +0 -16
  29. data/lib/nanoc/checking/checks/external_links.rb +0 -151
  30. data/lib/nanoc/checking/checks/html.rb +0 -16
  31. data/lib/nanoc/checking/checks/internal_links.rb +0 -95
  32. data/lib/nanoc/checking/checks/mixed_content.rb +0 -37
  33. data/lib/nanoc/checking/checks/stale.rb +0 -41
  34. data/lib/nanoc/checking/checks/w3c_validator.rb +0 -31
  35. data/lib/nanoc/checking/dsl.rb +0 -27
  36. data/lib/nanoc/checking/issue.rb +0 -16
  37. data/lib/nanoc/checking/loader.rb +0 -50
  38. data/lib/nanoc/checking/runner.rb +0 -136
  39. data/lib/nanoc/deploying.rb +0 -10
  40. data/lib/nanoc/deploying/deployer.rb +0 -45
  41. data/lib/nanoc/deploying/deployers.rb +0 -11
  42. data/lib/nanoc/deploying/deployers/fog.rb +0 -220
  43. data/lib/nanoc/deploying/deployers/git.rb +0 -112
  44. data/lib/nanoc/deploying/deployers/rsync.rb +0 -68
  45. data/lib/nanoc/extra/core_ext/pathname.rb +0 -27
  46. data/lib/nanoc/orig_cli/commands/check.rb +0 -43
  47. data/lib/nanoc/orig_cli/commands/deploy.rb +0 -126
@@ -1,37 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Nanoc::Checking::Checks
4
- # A check that verifies HTML files do not reference external resources with
5
- # URLs that would trigger "mixed content" warnings.
6
- #
7
- # @api private
8
- class MixedContent < ::Nanoc::Checking::Check
9
- identifier :mixed_content
10
-
11
- PROTOCOL_PATTERN = /^(\w+):\/\//.freeze
12
-
13
- def run
14
- filenames = output_html_filenames
15
- resource_uris_with_filenames = ::Nanoc::Extra::LinkCollector.new(filenames).filenames_per_resource_uri
16
-
17
- resource_uris_with_filenames.each_pair do |uri, fns|
18
- next unless guaranteed_insecure?(uri)
19
-
20
- fns.each do |filename|
21
- add_issue(
22
- "mixed content include: #{uri}",
23
- subject: filename,
24
- )
25
- end
26
- end
27
- end
28
-
29
- private
30
-
31
- def guaranteed_insecure?(href)
32
- protocol = PROTOCOL_PATTERN.match(href)
33
-
34
- protocol && protocol[1].casecmp('http').zero?
35
- end
36
- end
37
- end
@@ -1,41 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Nanoc::Checking::Checks
4
- # @api private
5
- class Stale < ::Nanoc::Checking::Check
6
- identifier :stale
7
-
8
- def run
9
- output_filenames.each do |f|
10
- next if pruner.filename_excluded?(f)
11
- next if item_rep_paths.include?(f)
12
-
13
- add_issue(
14
- 'file without matching item',
15
- subject: f,
16
- )
17
- end
18
- end
19
-
20
- protected
21
-
22
- def item_rep_paths
23
- @item_rep_paths ||=
24
- Set.new(
25
- @items
26
- .flat_map(&:reps)
27
- .map(&:_unwrap)
28
- .flat_map(&:raw_paths)
29
- .flat_map(&:values)
30
- .flatten,
31
- )
32
- end
33
-
34
- def pruner
35
- exclude_config = @config.fetch(:prune, {}).fetch(:exclude, [])
36
- # FIXME: specifying reps this way is icky
37
- reps = Nanoc::Core::ItemRepRepo.new
38
- @pruner ||= Nanoc::Core::Pruner.new(@config._unwrap, reps, exclude: exclude_config)
39
- end
40
- end
41
- end
@@ -1,31 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module ::Nanoc::Checking::Checks
4
- # @api private
5
- class W3CValidator < ::Nanoc::Checking::Check
6
- def run
7
- require 'w3c_validators'
8
- require 'resolv-replace'
9
-
10
- Dir[@config.output_dir + '/**/*.' + extension].each do |filename|
11
- results = validator_class.new.validate_file(filename)
12
- lines = File.readlines(filename)
13
- results.errors.each do |e|
14
- line_num = e.line.to_i - 1
15
- line = lines[line_num]
16
- message = e.message.gsub(%r{\s+}, ' ').strip.sub(/\s+:$/, '')
17
- desc = "line #{line_num + 1}: #{message}: #{line}"
18
- add_issue(desc, subject: filename)
19
- end
20
- end
21
- end
22
-
23
- def extension
24
- raise NotImplementedError
25
- end
26
-
27
- def validator_class
28
- raise NotImplementedError
29
- end
30
- end
31
- end
@@ -1,27 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Nanoc::Checking
4
- # @api private
5
- class DSL
6
- def self.from_file(filename, enabled_checks:)
7
- dsl = new(enabled_checks: enabled_checks)
8
- absolute_filename = File.expand_path(filename)
9
- dsl.instance_eval(File.read(filename), absolute_filename)
10
- dsl
11
- end
12
-
13
- def initialize(enabled_checks:)
14
- @enabled_checks = enabled_checks
15
- end
16
-
17
- def check(identifier, &block)
18
- klass = Class.new(::Nanoc::Checking::Check)
19
- klass.send(:define_method, :run, &block)
20
- klass.send(:identifier, identifier)
21
- end
22
-
23
- def deploy_check(*identifiers)
24
- identifiers.each { |i| @enabled_checks << i }
25
- end
26
- end
27
- end
@@ -1,16 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Nanoc::Checking
4
- # @api private
5
- class Issue
6
- attr_reader :description
7
- attr_reader :subject
8
- attr_reader :check_class
9
-
10
- def initialize(desc, subject, check_class)
11
- @description = desc
12
- @subject = subject
13
- @check_class = check_class
14
- end
15
- end
16
- end
@@ -1,50 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Nanoc::Checking
4
- # @api private
5
- class Loader
6
- CHECKS_FILENAMES = ['Checks', 'Checks.rb', 'checks', 'checks.rb'].freeze
7
-
8
- def initialize(config:)
9
- @config = config
10
- end
11
-
12
- def run
13
- dsl
14
- end
15
-
16
- def enabled_checks
17
- (enabled_checks_from_dsl + enabled_checks_from_config).uniq
18
- end
19
-
20
- private
21
-
22
- def dsl_present?
23
- checks_filename && File.file?(checks_filename)
24
- end
25
-
26
- def enabled_checks_from_dsl
27
- dsl
28
- @enabled_checks_from_dsl
29
- end
30
-
31
- def enabled_checks_from_config
32
- @config.fetch(:checking, {}).fetch(:enabled_checks, []).map(&:to_sym)
33
- end
34
-
35
- def dsl
36
- @enabled_checks_from_dsl ||= []
37
-
38
- @dsl ||=
39
- if dsl_present?
40
- Nanoc::Checking::DSL.from_file(checks_filename, enabled_checks: @enabled_checks_from_dsl)
41
- else
42
- Nanoc::Checking::DSL.new(enabled_checks: @enabled_checks_from_dsl)
43
- end
44
- end
45
-
46
- def checks_filename
47
- @_checks_filename ||= CHECKS_FILENAMES.find { |f| File.file?(f) }
48
- end
49
- end
50
- end
@@ -1,136 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Nanoc::Checking
4
- # Runner is reponsible for running issue checks.
5
- #
6
- # @api private
7
- class Runner
8
- # @param [Nanoc::Core::Site] site The Nanoc site this runner is for
9
- def initialize(site)
10
- @site = site
11
- end
12
-
13
- def any_enabled_checks?
14
- enabled_checks.any?
15
- end
16
-
17
- # Lists all available checks on stdout.
18
- #
19
- # @return [void]
20
- def list_checks
21
- load_all
22
-
23
- puts 'Available checks:'
24
- puts
25
- puts all_check_classes.map { |i| ' ' + i.identifier.to_s }.sort.join("\n")
26
- end
27
-
28
- # Runs all checks.
29
- #
30
- # @return [Boolean] true if successful, false otherwise
31
- def run_all
32
- load_all
33
- run_check_classes(all_check_classes)
34
- end
35
-
36
- # Runs the checks marked for deployment.
37
- #
38
- # @return [Boolean] true if successful, false otherwise
39
- def run_for_deploy
40
- # TODO: rename to #run_enabled
41
- load_all
42
- run_check_classes(check_classes_named(enabled_checks))
43
- end
44
-
45
- # Runs the checks with the given names.
46
- #
47
- # @param [Array<Symbol>] check_class_names The names of the checks
48
- #
49
- # @return [Boolean] true if successful, false otherwise
50
- def run_specific(check_class_names)
51
- load_all
52
- run_check_classes(check_classes_named(check_class_names))
53
- end
54
-
55
- private
56
-
57
- def loader
58
- @loader ||= Nanoc::Checking::Loader.new(config: @site.config)
59
- end
60
-
61
- def load_all
62
- loader.run
63
- end
64
-
65
- def enabled_checks
66
- loader.enabled_checks
67
- end
68
-
69
- def run_check_classes(classes)
70
- issues = run_checks(classes)
71
- print_issues(issues)
72
- issues.empty? ? true : false
73
- end
74
-
75
- def all_check_classes
76
- Nanoc::Checking::Check.all
77
- end
78
-
79
- def check_classes_named(names)
80
- names.map do |name|
81
- name = name.to_s.tr('-', '_').to_sym
82
- klass = Nanoc::Checking::Check.named(name)
83
- raise Nanoc::Core::TrivialError, "Unknown check: #{name}" if klass.nil?
84
-
85
- klass
86
- end
87
- end
88
-
89
- def run_checks(classes)
90
- return [] if classes.empty?
91
-
92
- # TODO: remove me
93
- Nanoc::Core::Compiler.new_for(@site).run_until_reps_built
94
-
95
- checks = []
96
- issues = Set.new
97
- length = classes.map { |c| c.identifier.to_s.length }.max + 18
98
- classes.each do |klass|
99
- print format(" %-#{length}s", "Running check #{klass.identifier}… ")
100
-
101
- check = klass.create(@site)
102
- check.run
103
-
104
- checks << check
105
- issues.merge(check.issues)
106
-
107
- # TODO: report progress
108
-
109
- puts check.issues.empty? ? 'ok'.green : 'error'.red
110
- end
111
- issues
112
- end
113
-
114
- def subject_to_s(str)
115
- str || '(global)'
116
- end
117
-
118
- def print_issues(issues)
119
- require 'colored'
120
-
121
- return if issues.empty?
122
-
123
- puts 'Issues found!'
124
- issues.group_by(&:subject).to_a.sort_by { |s| subject_to_s(s.first) }.each do |pair|
125
- subject = pair.first
126
- issues = pair.last
127
- next if issues.empty?
128
-
129
- puts " #{subject_to_s(subject)}:"
130
- issues.each do |i|
131
- puts " [ #{'ERROR'.red} ] #{i.check_class.identifier} - #{i.description}"
132
- end
133
- end
134
- end
135
- end
136
- end
@@ -1,10 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Nanoc
4
- # @api private
5
- module Deploying
6
- end
7
- end
8
-
9
- require 'nanoc/deploying/deployer'
10
- require 'nanoc/deploying/deployers'
@@ -1,45 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Nanoc::Deploying
4
- # Represents a deployer, an object that allows uploading the compiled site
5
- # to a specific (remote) location.
6
- #
7
- # @abstract Subclass and override {#run} to implement a custom filter.
8
- #
9
- # @api private
10
- class Deployer
11
- extend DDPlugin::Plugin
12
-
13
- # @return [String] The path to the directory that contains the files to
14
- # upload. It should not have a trailing slash.
15
- attr_reader :source_path
16
-
17
- # @return [Hash] The deployer configuration
18
- attr_reader :config
19
-
20
- # @return [Boolean] true if the deployer should only show what would be
21
- # deployed instead of doing the actual deployment
22
- attr_reader :dry_run
23
- alias dry_run? dry_run
24
-
25
- # @param [String] source_path The path to the directory that contains the
26
- # files to upload. It should not have a trailing slash.
27
- #
28
- # @return [Hash] config The deployer configuration
29
- #
30
- # @param [Boolean] dry_run true if the deployer should
31
- # only show what would be deployed instead actually deploying
32
- def initialize(source_path, config, dry_run: false)
33
- @source_path = source_path
34
- @config = config
35
- @dry_run = dry_run
36
- end
37
-
38
- # Performs the actual deployment.
39
- #
40
- # @abstract
41
- def run
42
- raise NotImplementedError.new('Nanoc::Deploying::Deployer subclasses must implement #run')
43
- end
44
- end
45
- end
@@ -1,11 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'tty-command'
4
-
5
- # @api private
6
- module Nanoc::Deploying::Deployers
7
- end
8
-
9
- require_relative 'deployers/fog'
10
- require_relative 'deployers/git'
11
- require_relative 'deployers/rsync'
@@ -1,220 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Nanoc::Deploying::Deployers
4
- # A deployer that deploys a site using [fog](https://github.com/fog/fog).
5
- #
6
- # @example A deployment configuration with public and staging configurations
7
- #
8
- # deploy:
9
- # public:
10
- # kind: fog
11
- # bucket: nanoc-site
12
- # cdn_id: XXXXXX
13
- # preprod:
14
- # kind: fog
15
- # provider: local
16
- # local_root: ~/myCloud
17
- # bucket: nanoc-site
18
- # staging:
19
- # kind: fog
20
- # provider: local
21
- # local_root: ~/myCloud
22
- # bucket: nanoc-site-staging
23
- #
24
- # @api private
25
- class Fog < ::Nanoc::Deploying::Deployer
26
- identifier :fog
27
-
28
- class FogWrapper
29
- def initialize(directory, is_dry_run)
30
- @directory = directory
31
- @is_dry_run = is_dry_run
32
- end
33
-
34
- def upload(source_filename, destination_key)
35
- log_effectful("uploading #{source_filename} -> #{destination_key}")
36
-
37
- unless dry_run?
38
- File.open(source_filename) do |io|
39
- @directory.files.create(
40
- key: destination_key,
41
- body: io,
42
- public: true,
43
- )
44
- end
45
- end
46
- end
47
-
48
- def remove(keys)
49
- keys.each do |key|
50
- log_effectful("removing #{key}")
51
-
52
- unless dry_run?
53
- @directory.files.get(key).destroy
54
- end
55
- end
56
- end
57
-
58
- def invalidate(keys, cdn, distribution)
59
- keys.each_slice(1000) do |keys_slice|
60
- keys_slice.each do |key|
61
- log_effectful("invalidating #{key}")
62
- end
63
-
64
- unless dry_run?
65
- cdn.post_invalidation(distribution, keys_slice)
66
- end
67
- end
68
- end
69
-
70
- def dry_run?
71
- @is_dry_run
72
- end
73
-
74
- def log_effectful(str)
75
- if @is_dry_run
76
- puts "[dry run] #{str}"
77
- else
78
- puts str
79
- end
80
- end
81
- end
82
-
83
- # @see Nanoc::Deploying::Deployer#run
84
- def run
85
- require 'fog/core'
86
-
87
- src = File.expand_path(source_path)
88
- bucket = config[:bucket] || config[:bucket_name]
89
- path = config[:path]
90
- cdn_id = config[:cdn_id]
91
-
92
- if path&.end_with?('/')
93
- raise "The path `#{path}` is not supposed to have a trailing slash"
94
- end
95
-
96
- connection = connect
97
- directory = get_or_create_bucket(connection, bucket, path)
98
- wrapper = FogWrapper.new(directory, dry_run?)
99
-
100
- remote_files = list_remote_files(directory)
101
- etags = read_etags(remote_files)
102
-
103
- modified_keys, retained_keys = upload_all(src, path, etags, wrapper)
104
-
105
- removed_keys = remote_files.map(&:key) - retained_keys - modified_keys
106
- wrapper.remove(removed_keys)
107
-
108
- if cdn_id
109
- cdn = ::Fog::CDN.new(config_for_fog)
110
- distribution = cdn.get_distribution(cdn_id)
111
- wrapper.invalidate(modified_keys + removed_keys, cdn, distribution)
112
- end
113
- end
114
-
115
- private
116
-
117
- def config_for_fog
118
- config.dup.tap do |c|
119
- c.delete(:bucket)
120
- c.delete(:bucket_name)
121
- c.delete(:path)
122
- c.delete(:cdn_id)
123
- c.delete(:kind)
124
- end
125
- end
126
-
127
- def connect
128
- ::Fog::Storage.new(config_for_fog)
129
- rescue ArgumentError
130
- require "fog/#{config[:provider]}"
131
- ::Fog::Storage.new(config_for_fog)
132
- end
133
-
134
- def get_or_create_bucket(connection, bucket, path)
135
- directory =
136
- begin
137
- connection.directories.get(bucket, prefix: path)
138
- rescue ::Excon::Errors::NotFound
139
- nil
140
- end
141
-
142
- if directory
143
- directory
144
- elsif dry_run?
145
- puts '[dry run] creating bucket'
146
- else
147
- puts 'creating bucket'
148
- connection.directories.create(key: bucket, prefix: path)
149
- end
150
- end
151
-
152
- def remote_key_for_local_filename(local_filename, src, path)
153
- relative_local_filename = local_filename.sub(/\A#{src}\//, '')
154
-
155
- if path
156
- File.join(path, relative_local_filename)
157
- else
158
- relative_local_filename
159
- end
160
- end
161
-
162
- def list_remote_files(directory)
163
- if directory
164
- [].tap do |files|
165
- directory.files.each { |file| files << file }
166
- end
167
- else
168
- []
169
- end
170
- end
171
-
172
- def list_local_files(src)
173
- Dir[src + '/**/*'].select { |f| File.file?(f) }
174
- end
175
-
176
- def upload_all(src, path, etags, wrapper)
177
- modified_keys = []
178
- retained_keys = []
179
-
180
- local_files = list_local_files(src)
181
- local_files.each do |file_path|
182
- key = remote_key_for_local_filename(file_path, src, path)
183
- if needs_upload?(key, file_path, etags)
184
- wrapper.upload(file_path, key)
185
- modified_keys.push(key)
186
- else
187
- retained_keys.push(key)
188
- end
189
- end
190
-
191
- [modified_keys, retained_keys]
192
- end
193
-
194
- def needs_upload?(key, file_path, etags)
195
- remote_etag = etags[key]
196
- return true if remote_etag.nil?
197
-
198
- local_etag = calc_local_etag(file_path)
199
- remote_etag != local_etag
200
- end
201
-
202
- def read_etags(files)
203
- case config[:provider]
204
- when 'aws'
205
- files.each_with_object({}) do |file, etags|
206
- etags[file.key] = file.etag
207
- end
208
- else
209
- {}
210
- end
211
- end
212
-
213
- def calc_local_etag(file_path)
214
- case config[:provider]
215
- when 'aws'
216
- Digest::MD5.file(file_path).hexdigest
217
- end
218
- end
219
- end
220
- end