nanoc 4.8.19 → 4.9.0

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 (44) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE +1 -1
  3. data/NEWS.md +21 -0
  4. data/lib/nanoc.rb +5 -4
  5. data/lib/nanoc/base/entities/directed_graph.rb +4 -4
  6. data/lib/nanoc/base/entities/identifier.rb +5 -0
  7. data/lib/nanoc/base/feature.rb +1 -2
  8. data/lib/nanoc/base/repos/dependency_store.rb +4 -4
  9. data/lib/nanoc/base/services/compiler/phases/abstract.rb +8 -0
  10. data/lib/nanoc/base/services/compiler/phases/write.rb +58 -1
  11. data/lib/nanoc/base/services/compiler/stages/compile_reps.rb +39 -29
  12. data/lib/nanoc/base/services/compiler/stages/determine_outdatedness.rb +2 -2
  13. data/lib/nanoc/base/services/instrumentor.rb +1 -1
  14. data/lib/nanoc/base/services/item_rep_writer.rb +2 -2
  15. data/lib/nanoc/base/views/compilation_item_rep_view.rb +7 -0
  16. data/lib/nanoc/checking.rb +4 -1
  17. data/lib/nanoc/checking/check.rb +7 -0
  18. data/lib/nanoc/checking/checks/external_links.rb +21 -12
  19. data/lib/nanoc/checking/dsl.rb +5 -7
  20. data/lib/nanoc/checking/loader.rb +50 -0
  21. data/lib/nanoc/checking/runner.rb +18 -40
  22. data/lib/nanoc/cli.rb +1 -1
  23. data/lib/nanoc/cli/ansi_string_colorizer.rb +4 -4
  24. data/lib/nanoc/cli/cleaning_stream.rb +12 -12
  25. data/lib/nanoc/cli/commands/check.rb +5 -15
  26. data/lib/nanoc/cli/commands/compile_listeners/diff_generator.rb +7 -7
  27. data/lib/nanoc/cli/commands/compile_listeners/file_action_printer.rb +12 -2
  28. data/lib/nanoc/cli/commands/compile_listeners/timing_recorder.rb +27 -19
  29. data/lib/nanoc/cli/commands/deploy.rb +1 -1
  30. data/lib/nanoc/cli/commands/show-rules.rb +2 -2
  31. data/lib/nanoc/cli/error_handler.rb +1 -4
  32. data/lib/nanoc/cli/stream_cleaners/abstract.rb +2 -2
  33. data/lib/nanoc/cli/stream_cleaners/ansi_colors.rb +2 -2
  34. data/lib/nanoc/cli/stream_cleaners/utf8.rb +2 -2
  35. data/lib/nanoc/data_sources/filesystem/parser.rb +1 -1
  36. data/lib/nanoc/deploying/deployers/fog.rb +3 -3
  37. data/lib/nanoc/extra.rb +1 -2
  38. data/lib/nanoc/filters/colorize_syntax.rb +2 -2
  39. data/lib/nanoc/filters/relativize_paths.rb +2 -2
  40. data/lib/nanoc/helpers/blogging.rb +11 -11
  41. data/lib/nanoc/rule_dsl/compilation_rule_context.rb +1 -1
  42. data/lib/nanoc/version.rb +1 -1
  43. metadata +27 -13
  44. data/lib/nanoc/extra/parallel_collection.rb +0 -57
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 77b1cd3570e8e94e5d17d18bf9954f0ac8d8a45e48f4e3d2f7d39cbdc8a2e3ff
4
- data.tar.gz: a5a95e6ab7be8df7c016fdd659bac76734965ed9aed677110647ae7d0cfa19b8
3
+ metadata.gz: ed74af636c6e396ec74288c853dca19336227108e99d4db697f83a655c50d3fc
4
+ data.tar.gz: 9e38014cee26d2f3f88e4e77ea1d9f2c493c78f5613e6e7ea6867d3d0828ce94
5
5
  SHA512:
6
- metadata.gz: 5860e580a17a5ed72590cb7c81eb6b6e1649e5c2973a322ed1ca544af0faa4a37de23343151a6dbbe730cd022b9d58f54feae6546c7f243e7c359bc2e3f1ea2a
7
- data.tar.gz: e8642774bd3d54418ae6c78392dedca7eecbc80b2d06d7e83aa9a579ab35074a06de192420476b1ca7dfb48e29f2c7fc3b809925faedb23cb5522b89f170b9f0
6
+ metadata.gz: 44254d1810d3d2bfbbf12b9155035c968d641cdf1880fef342baee260cc4e0fc2fd17731df76b7108292917becb1b2c48902aa87feee47422b33b3941ada97ea
7
+ data.tar.gz: 271d3c575afbf515409eb5d635eaf53b0a387e97bea37eaf18a8a424d522a961a2138d36d85cb49b988cfdaae782ab5145b2bc596744b3ef5c3b24853a191f59
data/LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2007-2017 Denis Defreyne and contributors
1
+ Copyright (c) 2007-2018 Denis Defreyne and contributors
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining a copy
4
4
  of this software and associated documentation files (the "Software"), to deal
data/NEWS.md CHANGED
@@ -1,5 +1,26 @@
1
1
  # Nanoc news
2
2
 
3
+ ## 4.9.0 (2018-01-31)
4
+
5
+ Features:
6
+
7
+ * Added `Identifier#match?` (#1305)
8
+ * Added `Nanoc::Check.define`
9
+ * Made Nanoc read checks configuration from `nanoc.yaml` (#1296)
10
+
11
+ Enhancements:
12
+
13
+ * Made Nanoc write out files asynchronously (#1288)
14
+ * Made the filesystem data source ignore an initial blank line after the frontmatter (#1292, #1293)
15
+ * Made the `check` command run deploy checks by default (#1304)
16
+ * Made Nanoc honor the `NO_COLOR` environment variable (#1306)
17
+
18
+ Bugs:
19
+
20
+ * Fixed a potential deadlock in the `external_links` check (#1294, #1295)
21
+ * Fixed the `external_links` check’s handling of redirects without a `Location` header (#1297, #1302)
22
+ * Fixed the `external_links` check’s handling of URLs with invalid components (#1303)
23
+
3
24
  ## 4.8.19 (2018-01-01)
4
25
 
5
26
  Enhancements:
data/lib/nanoc.rb CHANGED
@@ -2,14 +2,15 @@
2
2
 
3
3
  # Load external dependencies
4
4
  require 'addressable'
5
+ require 'ddmemoize'
6
+ require 'ddmetrics'
5
7
  require 'ddplugin'
6
8
  require 'hamster'
9
+ require 'parallel'
7
10
  require 'ref'
8
11
  require 'slow_enumerator_tools'
9
- require 'ddmemoize'
10
- require 'ddtelemetry'
11
12
 
12
- DDMemoize.enable_telemetry
13
+ DDMemoize.enable_metrics
13
14
 
14
15
  module Nanoc
15
16
  # @return [String] A string containing information about this Nanoc version
@@ -17,7 +18,7 @@ module Nanoc
17
18
  #
18
19
  # @api private
19
20
  def self.version_information
20
- "Nanoc #{Nanoc::VERSION} © 2007-2017 Denis Defreyne.\n" \
21
+ "Nanoc #{Nanoc::VERSION} © 2007-2018 Denis Defreyne.\n" \
21
22
  "Running #{RUBY_ENGINE} #{RUBY_VERSION} (#{RUBY_RELEASE_DATE}) on #{RUBY_PLATFORM} with RubyGems #{Gem::VERSION}.\n"
22
23
  end
23
24
 
@@ -88,13 +88,13 @@ module Nanoc::Int
88
88
 
89
89
  # Adds the given vertex to the graph.
90
90
  #
91
- # @param v The vertex to add to the graph
91
+ # @param vertex The vertex to add to the graph
92
92
  #
93
93
  # @return [void]
94
- def add_vertex(v)
95
- return if @vertices.key?(v)
94
+ def add_vertex(vertex)
95
+ return if @vertices.key?(vertex)
96
96
 
97
- @vertices[v] = @next_vertex_idx.tap { @next_vertex_idx += 1 }
97
+ @vertices[vertex] = @next_vertex_idx.tap { @next_vertex_idx += 1 }
98
98
  end
99
99
 
100
100
  # Deletes all edges going to the given vertex.
@@ -101,6 +101,11 @@ module Nanoc
101
101
  Nanoc::Int::Pattern.from(other).match?(to_s) ? 0 : nil
102
102
  end
103
103
 
104
+ contract C::Any => C::Bool
105
+ def match?(other)
106
+ Nanoc::Int::Pattern.from(other).match?(to_s)
107
+ end
108
+
104
109
  contract C::Any => C::Num
105
110
  def <=>(other)
106
111
  to_s <=> other.to_s
@@ -91,5 +91,4 @@ module Nanoc
91
91
  end
92
92
  end
93
93
 
94
- Nanoc::Feature.define('live_cmd', version: '4.8')
95
- Nanoc::Feature.define('sensible_stack_traces', version: '4.8')
94
+ Nanoc::Feature.define('live_cmd', version: '4.9')
@@ -120,8 +120,8 @@ module Nanoc::Int
120
120
  @graph.add_edge(dst_ref, src_ref, props: props.to_h)
121
121
  end
122
122
 
123
- def add_vertex_for(o)
124
- @refs2objs[obj2ref(o)] = o
123
+ def add_vertex_for(obj)
124
+ @refs2objs[obj2ref(obj)] = obj
125
125
  end
126
126
 
127
127
  # Empties the list of dependencies for the given object. This is necessary
@@ -159,8 +159,8 @@ module Nanoc::Int
159
159
  refs.map { |r| ref2obj(r) }
160
160
  end
161
161
 
162
- def props_for(a, b)
163
- props = @graph.props_for(obj2ref(a), obj2ref(b)) || {}
162
+ def props_for(from, to)
163
+ props = @graph.props_for(obj2ref(from), obj2ref(to)) || {}
164
164
 
165
165
  if props.values.any? { |v| v }
166
166
  props
@@ -8,6 +8,14 @@ module Nanoc::Int::Compiler::Phases
8
8
  @wrapped = wrapped
9
9
  end
10
10
 
11
+ def start
12
+ @wrapped&.start
13
+ end
14
+
15
+ def stop
16
+ @wrapped&.stop
17
+ end
18
+
11
19
  def call(rep, is_outdated:)
12
20
  notify(:phase_started, rep)
13
21
  run(rep, is_outdated: is_outdated) do
@@ -4,17 +4,74 @@ module Nanoc::Int::Compiler::Phases
4
4
  class Write < Abstract
5
5
  include Nanoc::Int::ContractsSupport
6
6
 
7
+ class Worker
8
+ def initialize(queue:, snapshot_repo:)
9
+ @queue = queue
10
+ @snapshot_repo = snapshot_repo
11
+ end
12
+
13
+ def start
14
+ @thread = Thread.new do
15
+ Thread.current.abort_on_exception = true
16
+ Thread.current.priority = -1 # schedule I/O work ASAP
17
+
18
+ writer = Nanoc::Int::ItemRepWriter.new
19
+
20
+ while rep = @queue.pop # rubocop:disable Lint/AssignmentInCondition
21
+ writer.write_all(rep, @snapshot_repo)
22
+ end
23
+ end
24
+ end
25
+
26
+ def join
27
+ @thread.join
28
+ end
29
+ end
30
+
31
+ class WorkerPool
32
+ def initialize(queue:, size:, snapshot_repo:)
33
+ @workers = Array.new(size) { Worker.new(queue: queue, snapshot_repo: snapshot_repo) }
34
+ end
35
+
36
+ def start
37
+ @workers.each(&:start)
38
+ end
39
+
40
+ def join
41
+ @workers.each(&:join)
42
+ end
43
+ end
44
+
45
+ QUEUE_SIZE = 40
46
+ WORKER_POOL_SIZE = 5
47
+
7
48
  def initialize(snapshot_repo:, wrapped:)
8
49
  super(wrapped: wrapped)
9
50
 
10
51
  @snapshot_repo = snapshot_repo
52
+
53
+ @queue = SizedQueue.new(QUEUE_SIZE)
54
+ @worker_pool = WorkerPool.new(queue: @queue, size: WORKER_POOL_SIZE, snapshot_repo: @snapshot_repo)
55
+ end
56
+
57
+ def start
58
+ super
59
+ @worker_pool.start
60
+ end
61
+
62
+ def stop
63
+ super
64
+ @queue.close
65
+ @worker_pool.join
11
66
  end
12
67
 
13
68
  contract Nanoc::Int::ItemRep, C::KeywordArgs[is_outdated: C::Bool], C::Func[C::None => C::Any] => C::Any
14
69
  def run(rep, is_outdated:) # rubocop:disable Lint/UnusedMethodArgument
15
70
  yield
16
71
 
17
- Nanoc::Int::ItemRepWriter.new.write_all(rep, @snapshot_repo)
72
+ @queue << rep
73
+
74
+ Nanoc::Int::NotificationCenter.post(:rep_write_enqueued, rep)
18
75
  end
19
76
  end
20
77
  end
@@ -14,8 +14,12 @@ module Nanoc::Int::Compiler::Stages
14
14
  def run
15
15
  outdated_reps = @reps.select { |r| @outdatedness_store.include?(r) }
16
16
  selector = Nanoc::Int::ItemRepSelector.new(outdated_reps)
17
- selector.each do |rep|
18
- handle_errors_while(rep) { compile_rep(rep, is_outdated: @outdatedness_store.include?(rep)) }
17
+ run_phase_stack do |phase_stack|
18
+ selector.each do |rep|
19
+ handle_errors_while(rep) do
20
+ compile_rep(rep, phase_stack: phase_stack, is_outdated: @outdatedness_store.include?(rep))
21
+ end
22
+ end
19
23
  end
20
24
  ensure
21
25
  @outdatedness_store.store
@@ -30,40 +34,46 @@ module Nanoc::Int::Compiler::Stages
30
34
  raise Nanoc::Int::Errors::CompilationError.new(e, item_rep)
31
35
  end
32
36
 
33
- def compile_rep(rep, is_outdated:)
34
- item_rep_compiler.call(rep, is_outdated: is_outdated)
37
+ def compile_rep(rep, phase_stack:, is_outdated:)
38
+ phase_stack.call(rep, is_outdated: is_outdated)
35
39
  end
36
40
 
37
- def item_rep_compiler
38
- @_item_rep_compiler ||= begin
39
- recalculate_phase = Nanoc::Int::Compiler::Phases::Recalculate.new(
40
- action_sequences: @action_sequences,
41
- dependency_store: @dependency_store,
42
- compilation_context: @compilation_context,
43
- )
41
+ def run_phase_stack
42
+ phase_stack = build_phase_stack
43
+ phase_stack.start
44
+ yield(phase_stack)
45
+ ensure
46
+ phase_stack.stop
47
+ end
44
48
 
45
- cache_phase = Nanoc::Int::Compiler::Phases::Cache.new(
46
- compiled_content_cache: @compiled_content_cache,
47
- snapshot_repo: @compilation_context.snapshot_repo,
48
- wrapped: recalculate_phase,
49
- )
49
+ def build_phase_stack
50
+ recalculate_phase = Nanoc::Int::Compiler::Phases::Recalculate.new(
51
+ action_sequences: @action_sequences,
52
+ dependency_store: @dependency_store,
53
+ compilation_context: @compilation_context,
54
+ )
50
55
 
51
- resume_phase = Nanoc::Int::Compiler::Phases::Resume.new(
52
- wrapped: cache_phase,
53
- )
56
+ cache_phase = Nanoc::Int::Compiler::Phases::Cache.new(
57
+ compiled_content_cache: @compiled_content_cache,
58
+ snapshot_repo: @compilation_context.snapshot_repo,
59
+ wrapped: recalculate_phase,
60
+ )
54
61
 
55
- write_phase = Nanoc::Int::Compiler::Phases::Write.new(
56
- snapshot_repo: @compilation_context.snapshot_repo,
57
- wrapped: resume_phase,
58
- )
62
+ resume_phase = Nanoc::Int::Compiler::Phases::Resume.new(
63
+ wrapped: cache_phase,
64
+ )
59
65
 
60
- mark_done_phase = Nanoc::Int::Compiler::Phases::MarkDone.new(
61
- wrapped: write_phase,
62
- outdatedness_store: @outdatedness_store,
63
- )
66
+ write_phase = Nanoc::Int::Compiler::Phases::Write.new(
67
+ snapshot_repo: @compilation_context.snapshot_repo,
68
+ wrapped: resume_phase,
69
+ )
64
70
 
65
- mark_done_phase
66
- end
71
+ mark_done_phase = Nanoc::Int::Compiler::Phases::MarkDone.new(
72
+ wrapped: write_phase,
73
+ outdatedness_store: @outdatedness_store,
74
+ )
75
+
76
+ mark_done_phase
67
77
  end
68
78
  end
69
79
  end
@@ -38,8 +38,8 @@ module Nanoc::Int::Compiler::Stages
38
38
  Set.new(items.flat_map { |i| @reps[i] })
39
39
  end
40
40
 
41
- def outdated?(r)
42
- @outdatedness_store.include?(r) || @outdatedness_checker.outdated?(r)
41
+ def outdated?(rep)
42
+ @outdatedness_store.include?(rep) || @outdatedness_checker.outdated?(rep)
43
43
  end
44
44
  end
45
45
  end
@@ -4,7 +4,7 @@ module Nanoc::Int
4
4
  # @api private
5
5
  class Instrumentor
6
6
  def self.call(key, *args)
7
- stopwatch = DDTelemetry::Stopwatch.new
7
+ stopwatch = DDMetrics::Stopwatch.new
8
8
  stopwatch.start
9
9
  yield
10
10
  ensure
@@ -33,7 +33,7 @@ module Nanoc::Int
33
33
 
34
34
  # Notify
35
35
  Nanoc::Int::NotificationCenter.post(
36
- :will_write_rep, item_rep, raw_path
36
+ :rep_write_started, item_rep, raw_path
37
37
  )
38
38
 
39
39
  content = snapshot_repo.get(item_rep, snapshot_name)
@@ -54,7 +54,7 @@ module Nanoc::Int
54
54
 
55
55
  # Notify
56
56
  Nanoc::Int::NotificationCenter.post(
57
- :rep_written, item_rep, content.binary?, raw_path, is_created, is_modified
57
+ :rep_write_ended, item_rep, content.binary?, raw_path, is_created, is_modified
58
58
  )
59
59
  end
60
60
 
@@ -23,6 +23,13 @@ module Nanoc
23
23
  Fiber.yield(Nanoc::Int::Errors::UnmetDependency.new(@item_rep))
24
24
  end
25
25
 
26
+ # Wait for file to exist
27
+ if res
28
+ start = Time.now
29
+ sleep 0.05 until File.file?(res) || Time.now - start > 1.0
30
+ raise Nanoc::Int::Errors::InternalInconsistency, "File did not apear in time: #{res}" unless File.file?(res)
31
+ end
32
+
26
33
  res
27
34
  end
28
35
 
@@ -7,5 +7,8 @@ end
7
7
  require_relative 'checking/check'
8
8
  require_relative 'checking/checks'
9
9
  require_relative 'checking/dsl'
10
- require_relative 'checking/runner.rb'
10
+ require_relative 'checking/runner'
11
+ require_relative 'checking/loader'
11
12
  require_relative 'checking/issue'
13
+
14
+ Nanoc::Check = Nanoc::Checking::Check
@@ -14,6 +14,13 @@ module Nanoc::Checking
14
14
 
15
15
  attr_reader :issues
16
16
 
17
+ def self.define(ident, &block)
18
+ klass = Class.new(::Nanoc::Checking::Check) { identifier(ident) }
19
+ klass.send(:define_method, :run) do
20
+ instance_exec(&block)
21
+ end
22
+ end
23
+
17
24
  def self.create(site)
18
25
  output_dir = site.config[:output_dir]
19
26
  unless File.exist?(output_dir)
@@ -37,8 +37,7 @@ module ::Nanoc::Checking::Checks
37
37
  end
38
38
 
39
39
  def select_invalid(hrefs)
40
- col = Nanoc::Extra::ParallelCollection.new(hrefs, parallelism: 10)
41
- col.map { |href| validate(href) }.compact
40
+ ::Parallel.map(hrefs, in_threads: 10) { |href| validate(href) }.compact
42
41
  end
43
42
 
44
43
  def validate(href)
@@ -46,7 +45,7 @@ module ::Nanoc::Checking::Checks
46
45
  url = nil
47
46
  begin
48
47
  url = URI.parse(href)
49
- rescue URI::InvalidURIError
48
+ rescue URI::Error
50
49
  return Result.new(href, 'invalid URI')
51
50
  end
52
51
 
@@ -75,15 +74,8 @@ module ::Nanoc::Checking::Checks
75
74
  return Result.new(href, 'too many redirects')
76
75
  end
77
76
 
78
- # Find proper location
79
- location = res['Location']
80
- if location !~ /^https?:\/\//
81
- base_url = url.dup
82
- base_url.path = (location =~ /^\// ? '' : '/')
83
- base_url.query = nil
84
- base_url.fragment = nil
85
- location = base_url.to_s + location
86
- end
77
+ location = extract_location(res, url)
78
+ return Result.new(href, 'redirection without a target location') if location.nil?
87
79
 
88
80
  url = URI.parse(location)
89
81
  elsif res.code == '200'
@@ -99,6 +91,23 @@ module ::Nanoc::Checking::Checks
99
91
  end
100
92
  end
101
93
 
94
+ def extract_location(res, url)
95
+ location = res['Location']
96
+
97
+ case location
98
+ when nil
99
+ nil
100
+ when /^https?:\/\//
101
+ location
102
+ else
103
+ base_url = url.dup
104
+ base_url.path = (location =~ /^\// ? '' : '/')
105
+ base_url.query = nil
106
+ base_url.fragment = nil
107
+ base_url.to_s + location
108
+ end
109
+ end
110
+
102
111
  def path_for_url(url)
103
112
  path =
104
113
  if url.path.nil? || url.path.empty?