nanoc 4.8.19 → 4.9.0

Sign up to get free protection for your applications and to get access to all the features.
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?