nanoc 4.1.6 → 4.2.0b1

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 (70) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +1 -0
  3. data/Gemfile.lock +2 -1
  4. data/NEWS.md +11 -4
  5. data/lib/nanoc/base/checksummer.rb +135 -46
  6. data/lib/nanoc/base/compilation/compiler.rb +18 -28
  7. data/lib/nanoc/base/compilation/dependency_tracker.rb +22 -32
  8. data/lib/nanoc/base/compilation/filter.rb +2 -4
  9. data/lib/nanoc/base/entities.rb +1 -0
  10. data/lib/nanoc/base/entities/content.rb +14 -3
  11. data/lib/nanoc/base/entities/document.rb +14 -6
  12. data/lib/nanoc/base/entities/item.rb +0 -31
  13. data/lib/nanoc/base/entities/item_rep.rb +1 -1
  14. data/lib/nanoc/base/entities/lazy_value.rb +36 -0
  15. data/lib/nanoc/base/entities/pattern.rb +3 -2
  16. data/lib/nanoc/base/entities/site.rb +2 -0
  17. data/lib/nanoc/base/memoization.rb +17 -10
  18. data/lib/nanoc/base/repos/compiled_content_cache.rb +1 -1
  19. data/lib/nanoc/base/repos/data_source.rb +10 -6
  20. data/lib/nanoc/base/services/executor.rb +22 -22
  21. data/lib/nanoc/base/services/item_rep_router.rb +4 -5
  22. data/lib/nanoc/base/views.rb +0 -1
  23. data/lib/nanoc/base/views/item_rep_view.rb +3 -9
  24. data/lib/nanoc/base/views/mixins/document_view_mixin.rb +4 -11
  25. data/lib/nanoc/base/views/view.rb +1 -0
  26. data/lib/nanoc/base/views/view_context.rb +5 -1
  27. data/lib/nanoc/cli/commands/compile.rb +0 -6
  28. data/lib/nanoc/data_sources.rb +5 -5
  29. data/lib/nanoc/data_sources/filesystem.rb +219 -90
  30. data/lib/nanoc/extra/checking/check.rb +1 -2
  31. data/lib/nanoc/extra/checking/checks.rb +2 -0
  32. data/lib/nanoc/extra/checking/checks/css.rb +6 -14
  33. data/lib/nanoc/extra/checking/checks/html.rb +6 -14
  34. data/lib/nanoc/extra/checking/checks/internal_links.rb +14 -3
  35. data/lib/nanoc/extra/checking/checks/w3c_validator.rb +28 -0
  36. data/lib/nanoc/extra/deployers/fog.rb +134 -78
  37. data/lib/nanoc/extra/link_collector.rb +14 -18
  38. data/lib/nanoc/filters/sass.rb +3 -3
  39. data/lib/nanoc/helpers.rb +1 -0
  40. data/lib/nanoc/helpers/capturing.rb +16 -58
  41. data/lib/nanoc/helpers/child_parent.rb +51 -0
  42. data/lib/nanoc/helpers/filtering.rb +0 -1
  43. data/lib/nanoc/helpers/html_escape.rb +5 -0
  44. data/lib/nanoc/helpers/link_to.rb +2 -0
  45. data/lib/nanoc/helpers/rendering.rb +3 -4
  46. data/lib/nanoc/rule_dsl/action_provider.rb +20 -4
  47. data/lib/nanoc/rule_dsl/recording_executor.rb +3 -1
  48. data/lib/nanoc/rule_dsl/rule_context.rb +0 -1
  49. data/lib/nanoc/rule_dsl/rule_memory_calculator.rb +4 -1
  50. data/lib/nanoc/spec.rb +217 -0
  51. data/lib/nanoc/version.rb +1 -1
  52. data/test/base/test_data_source.rb +4 -2
  53. data/test/base/test_dependency_tracker.rb +5 -11
  54. data/test/data_sources/test_filesystem.rb +605 -69
  55. data/test/extra/checking/checks/test_internal_links.rb +25 -0
  56. data/test/extra/deployers/test_fog.rb +0 -177
  57. data/test/filters/test_less.rb +9 -4
  58. data/test/helpers/test_capturing.rb +38 -212
  59. data/test/helpers/test_link_to.rb +0 -205
  60. data/test/helpers/test_xml_sitemap.rb +2 -1
  61. metadata +7 -12
  62. data/lib/nanoc/base/views/site_view.rb +0 -14
  63. data/lib/nanoc/data_sources/filesystem_unified.rb +0 -101
  64. data/test/data_sources/test_filesystem_unified.rb +0 -559
  65. data/test/helpers/test_breadcrumbs.rb +0 -60
  66. data/test/helpers/test_filtering.rb +0 -112
  67. data/test/helpers/test_html_escape.rb +0 -26
  68. data/test/helpers/test_rendering.rb +0 -147
  69. data/test/helpers/test_tagging.rb +0 -92
  70. data/test/helpers/test_text.rb +0 -18
@@ -1,5 +1,6 @@
1
1
  require_relative 'entities/code_snippet'
2
2
  require_relative 'entities/configuration'
3
+ require_relative 'entities/lazy_value'
3
4
  require_relative 'entities/content'
4
5
  require_relative 'entities/document'
5
6
  require_relative 'entities/identifier'
@@ -27,7 +27,7 @@ module Nanoc
27
27
  @filename.freeze
28
28
  end
29
29
 
30
- # @param [String] content The uncompiled item content (if it is textual
30
+ # @param [String, Proc] content The uncompiled item content (if it is textual
31
31
  # content) or the path to the filename containing the content (if this
32
32
  # is binary content).
33
33
  #
@@ -58,11 +58,13 @@ module Nanoc
58
58
  # @api private
59
59
  class TextualContent < Content
60
60
  # @return [String]
61
- attr_reader :string
61
+ def string
62
+ @string.value
63
+ end
62
64
 
63
65
  def initialize(string, filename: nil)
64
66
  super(filename)
65
- @string = string
67
+ @string = Nanoc::Int::LazyValue.new(string)
66
68
  end
67
69
 
68
70
  def freeze
@@ -73,6 +75,15 @@ module Nanoc
73
75
  def binary?
74
76
  false
75
77
  end
78
+
79
+ def marshal_dump
80
+ [filename, string]
81
+ end
82
+
83
+ def marshal_load(array)
84
+ @filename = array[0]
85
+ @string = Nanoc::Int::LazyValue.new(array[1])
86
+ end
76
87
  end
77
88
 
78
89
  # @api private
@@ -6,27 +6,35 @@ module Nanoc
6
6
  attr_reader :content
7
7
 
8
8
  # @return [Hash]
9
- attr_reader :attributes
9
+ def attributes
10
+ @attributes.value
11
+ end
10
12
 
11
13
  # @return [Nanoc::Identifier]
12
14
  attr_accessor :identifier
13
15
 
16
+ # @return [String, nil]
17
+ attr_accessor :checksum_data
18
+
14
19
  # @param [String, Nanoc::Int::Content] content
15
20
  #
16
- # @param [Hash] attributes
21
+ # @param [Hash, Proc] attributes
17
22
  #
18
23
  # @param [String, Nanoc::Identifier] identifier
19
- def initialize(content, attributes, identifier)
24
+ #
25
+ # @param [String, nil] checksum_data Used to determine whether the document has changed
26
+ def initialize(content, attributes, identifier, checksum_data: nil)
20
27
  @content = Nanoc::Int::Content.create(content)
21
- @attributes = attributes.__nanoc_symbolize_keys_recursively
28
+ @attributes = Nanoc::Int::LazyValue.new(attributes).map(&:__nanoc_symbolize_keys_recursively)
22
29
  @identifier = Nanoc::Identifier.from(identifier)
30
+ @checksum_data = checksum_data
23
31
  end
24
32
 
25
33
  # @return [void]
26
34
  def freeze
27
35
  super
28
- attributes.__nanoc_freeze_recursively
29
- content.freeze
36
+ @content.freeze
37
+ @attributes.freeze
30
38
  end
31
39
 
32
40
  # @abstract
@@ -1,13 +1,6 @@
1
1
  module Nanoc::Int
2
2
  # @api private
3
3
  class Item < ::Nanoc::Int::Document
4
- # @see Document#initialize
5
- def initialize(content, attributes, identifier)
6
- super
7
-
8
- @forced_outdated_status = ForcedOutdatedStatus.new
9
- end
10
-
11
4
  # Returns an object that can be used for uniquely identifying objects.
12
5
  #
13
6
  # @api private
@@ -16,29 +9,5 @@ module Nanoc::Int
16
9
  def reference
17
10
  [:item, identifier.to_s]
18
11
  end
19
-
20
- # Hack to allow a frozen item to still have modifiable frozen status.
21
- #
22
- # FIXME: Remove this.
23
- class ForcedOutdatedStatus
24
- attr_accessor :bool
25
-
26
- def initialize
27
- @bool = false
28
- end
29
-
30
- def freeze
31
- end
32
- end
33
-
34
- # @api private
35
- def forced_outdated=(bool)
36
- @forced_outdated_status.bool = bool
37
- end
38
-
39
- # @api private
40
- def forced_outdated?
41
- @forced_outdated_status.bool
42
- end
43
12
  end
44
13
  end
@@ -1,7 +1,7 @@
1
1
  module Nanoc::Int
2
2
  # @api private
3
3
  class ItemRep
4
- # @return [Hash<Symbol,Nanoc::Int::Content]
4
+ # @return [Hash<Symbol,Nanoc::Int::Content>]
5
5
  attr_accessor :snapshot_contents
6
6
 
7
7
  # @return [Boolean]
@@ -0,0 +1,36 @@
1
+ module Nanoc::Int
2
+ # Holds a value that might be generated lazily.
3
+ #
4
+ # @api private
5
+ class LazyValue
6
+ # @param [Object, Proc] value_or_proc A value or a proc to generate the value
7
+ def initialize(value_or_proc)
8
+ @value = { raw: value_or_proc }
9
+ end
10
+
11
+ # @return [Object] The value, generated when needed
12
+ def value
13
+ if @value.key?(:raw)
14
+ value = @value.delete(:raw)
15
+ @value[:final] = value.respond_to?(:call) ? value.call : value
16
+ @value.__nanoc_freeze_recursively if frozen?
17
+ end
18
+ @value[:final]
19
+ end
20
+
21
+ # Returns a new lazy value that will apply the given transformation when the value is requested.
22
+ #
23
+ # @yield resolved value
24
+ #
25
+ # @return [Nanoc::Int::LazyValue]
26
+ def map
27
+ Nanoc::Int::LazyValue.new(-> { yield(value) })
28
+ end
29
+
30
+ # @return [void]
31
+ def freeze
32
+ super
33
+ @value.__nanoc_freeze_recursively unless @value[:raw]
34
+ end
35
+ end
36
+ end
@@ -29,13 +29,14 @@ module Nanoc::Int
29
29
 
30
30
  # @api private
31
31
  class StringPattern
32
+ MATCH_OPTS = File::FNM_PATHNAME | File::FNM_EXTGLOB
33
+
32
34
  def initialize(string)
33
35
  @string = string
34
36
  end
35
37
 
36
38
  def match?(identifier)
37
- opts = File::FNM_PATHNAME | File::FNM_EXTGLOB
38
- File.fnmatch(@string, identifier.to_s, opts)
39
+ File.fnmatch(@string, identifier.to_s, MATCH_OPTS)
39
40
  end
40
41
 
41
42
  def captures(_identifier)
@@ -1,6 +1,8 @@
1
1
  module Nanoc::Int
2
2
  # @api private
3
3
  class Site
4
+ attr_accessor :compiler
5
+
4
6
  # @param [Nanoc::Int::Configuration] config
5
7
  # @param [Enumerable<Nanoc::Int::CodeSnippet>] code_snippets
6
8
  # @param [Enumerable<Nanoc::Int::Item>] items
@@ -1,3 +1,5 @@
1
+ require 'weakref'
2
+
1
3
  module Nanoc::Int
2
4
  # Adds support for memoizing functions.
3
5
  #
@@ -5,6 +7,14 @@ module Nanoc::Int
5
7
  #
6
8
  # @since 3.2.0
7
9
  module Memoization
10
+ class Wrapper
11
+ attr_reader :value
12
+
13
+ def initialize(value)
14
+ @value = value
15
+ end
16
+ end
17
+
8
18
  # Memoizes the method with the given name. The modified method will cache
9
19
  # the results of the original method, so that calling a method twice with
10
20
  # the same arguments will short-circuit and return the cached results
@@ -39,24 +49,21 @@ module Nanoc::Int
39
49
  #
40
50
  # @return [void]
41
51
  def memoize(method_name)
42
- # Alias
43
52
  original_method_name = '__nonmemoized_' + method_name.to_s
44
53
  alias_method original_method_name, method_name
45
54
 
46
- # Redefine
47
55
  define_method(method_name) do |*args|
48
- # Get cache
49
56
  @__memoization_cache ||= {}
50
57
  @__memoization_cache[method_name] ||= {}
58
+ method_cache = @__memoization_cache[method_name]
51
59
 
52
- # Recalculate if necessary
53
- unless @__memoization_cache[method_name].key?(args)
54
- result = send(original_method_name, *args)
55
- @__memoization_cache[method_name][args] = result
60
+ if method_cache.key?(args) && method_cache[args].weakref_alive?
61
+ method_cache[args].value
62
+ else
63
+ send(original_method_name, *args).tap do |r|
64
+ method_cache[args] = WeakRef.new(Wrapper.new(r))
65
+ end
56
66
  end
57
-
58
- # Done
59
- @__memoization_cache[method_name][args]
60
67
  end
61
68
  end
62
69
  end
@@ -5,7 +5,7 @@ module Nanoc::Int
5
5
  # @api private
6
6
  class CompiledContentCache < ::Nanoc::Int::Store
7
7
  def initialize
8
- super('tmp/compiled_content', 1)
8
+ super('tmp/compiled_content', 2)
9
9
 
10
10
  @cache = {}
11
11
  end
@@ -130,18 +130,20 @@ module Nanoc
130
130
  # Creates a new in-memory item instance. This is intended for use within
131
131
  # the {#items} method.
132
132
  #
133
- # @param [String] content The uncompiled item content
133
+ # @param [String, Proc] content The uncompiled item content
134
134
  # (if it is a textual item) or the path to the filename containing the
135
135
  # content (if it is a binary item).
136
136
  #
137
- # @param [Hash] attributes A hash containing this item's attributes.
137
+ # @param [Hash, Proc] attributes A hash containing this item's attributes.
138
138
  #
139
139
  # @param [String] identifier This item's identifier.
140
140
  #
141
141
  # @param [Boolean] binary Whether or not this item is binary
142
- def new_item(content, attributes, identifier, binary: false)
142
+ #
143
+ # @param [String, nil] checksum_data Used to determine whether the item has changed
144
+ def new_item(content, attributes, identifier, binary: false, checksum_data: nil)
143
145
  content = Nanoc::Int::Content.create(content, binary: binary)
144
- Nanoc::Int::Item.new(content, attributes, identifier)
146
+ Nanoc::Int::Item.new(content, attributes, identifier, checksum_data: checksum_data)
145
147
  end
146
148
 
147
149
  # Creates a new in-memory layout instance. This is intended for use within
@@ -152,8 +154,10 @@ module Nanoc
152
154
  # @param [Hash] attributes A hash containing this layout's attributes.
153
155
  #
154
156
  # @param [String] identifier This layout's identifier.
155
- def new_layout(raw_content, attributes, identifier)
156
- Nanoc::Int::Layout.new(raw_content, attributes, identifier)
157
+ #
158
+ # @param [String, nil] checksum_data Used to determine whether the layout has changed
159
+ def new_layout(raw_content, attributes, identifier, checksum_data: nil)
160
+ Nanoc::Int::Layout.new(raw_content, attributes, identifier, checksum_data: checksum_data)
157
161
  end
158
162
  end
159
163
  end
@@ -7,49 +7,36 @@ module Nanoc
7
7
  end
8
8
  end
9
9
 
10
- def initialize(compiler)
10
+ def initialize(compiler, dependency_tracker)
11
11
  @compiler = compiler
12
+ @dependency_tracker = dependency_tracker
12
13
  end
13
14
 
14
15
  def filter(rep, filter_name, filter_args = {})
15
- # Get filter class
16
- klass = Nanoc::Filter.named(filter_name)
17
- raise Nanoc::Int::Errors::UnknownFilter.new(filter_name) if klass.nil?
18
-
19
- # Check whether filter can be applied
20
- if klass.from_binary? && !rep.binary?
21
- raise Nanoc::Int::Errors::CannotUseBinaryFilter.new(rep, klass)
22
- elsif !klass.from_binary? && rep.binary?
23
- raise Nanoc::Int::Errors::CannotUseTextualFilter.new(rep, klass)
24
- end
16
+ filter = filter_for_filtering(rep, filter_name)
25
17
 
26
18
  begin
27
- # Notify start
28
19
  Nanoc::Int::NotificationCenter.post(:filtering_started, rep, filter_name)
29
20
 
30
- # Create filter
31
- filter = klass.new(assigns_for(rep))
32
-
33
21
  # Run filter
34
22
  last = rep.snapshot_contents[:last]
35
23
  source = rep.binary? ? last.filename : last.string
36
24
  result = filter.setup_and_run(source, filter_args)
37
25
  rep.snapshot_contents[:last] =
38
- if klass.to_binary?
26
+ if filter.class.to_binary?
39
27
  Nanoc::Int::BinaryContent.new(filter.output_filename).tap(&:freeze)
40
28
  else
41
29
  Nanoc::Int::TextualContent.new(result).tap(&:freeze)
42
30
  end
43
31
 
44
32
  # Check whether file was written
45
- if klass.to_binary? && !File.file?(filter.output_filename)
33
+ if filter.class.to_binary? && !File.file?(filter.output_filename)
46
34
  raise OutputNotWrittenError.new(filter_name, filter.output_filename)
47
35
  end
48
36
 
49
37
  # Create snapshot
50
38
  snapshot(rep, rep.snapshot_contents[:post] ? :post : :pre, final: false) unless rep.binary?
51
39
  ensure
52
- # Notify end
53
40
  Nanoc::Int::NotificationCenter.post(:filtering_ended, rep, filter_name)
54
41
  end
55
42
  end
@@ -73,12 +60,12 @@ module Nanoc
73
60
  # Create filter
74
61
  klass = Nanoc::Filter.named(filter_name)
75
62
  raise Nanoc::Int::Errors::UnknownFilter.new(filter_name) if klass.nil?
76
- layout_view = Nanoc::LayoutView.new(layout, nil)
63
+ view_context = @compiler.create_view_context(@dependency_tracker)
64
+ layout_view = Nanoc::LayoutView.new(layout, view_context)
77
65
  filter = klass.new(assigns_for(rep).merge({ layout: layout_view }))
78
66
 
79
67
  # Visit
80
- Nanoc::Int::NotificationCenter.post(:visit_started, layout)
81
- Nanoc::Int::NotificationCenter.post(:visit_ended, layout)
68
+ @dependency_tracker.bounce(layout)
82
69
 
83
70
  begin
84
71
  # Notify start
@@ -120,7 +107,7 @@ module Nanoc
120
107
  end
121
108
 
122
109
  def assigns_for(rep)
123
- @compiler.assigns_for(rep)
110
+ @compiler.assigns_for(rep, @dependency_tracker)
124
111
  end
125
112
 
126
113
  def layouts
@@ -141,6 +128,19 @@ module Nanoc
141
128
  raise Nanoc::Int::Errors::UnknownLayout.new(arg)
142
129
  end
143
130
 
131
+ def filter_for_filtering(rep, filter_name)
132
+ klass = Nanoc::Filter.named(filter_name)
133
+ raise Nanoc::Int::Errors::UnknownFilter.new(filter_name) if klass.nil?
134
+
135
+ if klass.from_binary? && !rep.binary?
136
+ raise Nanoc::Int::Errors::CannotUseBinaryFilter.new(rep, klass)
137
+ elsif !klass.from_binary? && rep.binary?
138
+ raise Nanoc::Int::Errors::CannotUseTextualFilter.new(rep, klass)
139
+ end
140
+
141
+ klass.new(assigns_for(rep))
142
+ end
143
+
144
144
  def use_globs?
145
145
  @compiler.site.config[:string_pattern_type] == 'glob'
146
146
  end
@@ -28,7 +28,6 @@ module Nanoc::Int
28
28
  def route_rep(rep, snapshot_action, paths_to_reps)
29
29
  basic_path = snapshot_action.path
30
30
  return if basic_path.nil?
31
- basic_path = basic_path.encode('UTF-8')
32
31
 
33
32
  # Check for duplicate paths
34
33
  if paths_to_reps.key?(basic_path)
@@ -43,10 +42,10 @@ module Nanoc::Int
43
42
 
44
43
  def strip_index_filename(basic_path)
45
44
  @site.config[:index_filenames].each do |index_filename|
46
- slashed_index_filename = '/' + index_filename
47
- if basic_path.end_with?(slashed_index_filename)
48
- return basic_path[0..-index_filename.length - 1]
49
- end
45
+ rep_path_ending = basic_path[-index_filename.length..-1]
46
+ next unless rep_path_ending == index_filename
47
+
48
+ return basic_path[0..-index_filename.length - 1]
50
49
  end
51
50
 
52
51
  basic_path
@@ -21,7 +21,6 @@ require_relative 'views/mutable_item_view'
21
21
  require_relative 'views/mutable_item_collection_view'
22
22
  require_relative 'views/mutable_layout_view'
23
23
  require_relative 'views/mutable_layout_collection_view'
24
- require_relative 'views/site_view'
25
24
 
26
25
  require_relative 'views/post_compile_item_view'
27
26
  require_relative 'views/post_compile_item_collection_view'
@@ -36,9 +36,7 @@ module Nanoc
36
36
  #
37
37
  # @return [String] The content at the given snapshot.
38
38
  def compiled_content(snapshot: nil)
39
- Nanoc::Int::NotificationCenter.post(:visit_started, unwrap.item)
40
- Nanoc::Int::NotificationCenter.post(:visit_ended, unwrap.item)
41
-
39
+ @context.dependency_tracker.bounce(unwrap.item)
42
40
  @item_rep.compiled_content(snapshot: snapshot)
43
41
  end
44
42
 
@@ -52,9 +50,7 @@ module Nanoc
52
50
  #
53
51
  # @return [String] The item rep’s path.
54
52
  def path(snapshot: :last)
55
- Nanoc::Int::NotificationCenter.post(:visit_started, unwrap.item)
56
- Nanoc::Int::NotificationCenter.post(:visit_ended, unwrap.item)
57
-
53
+ @context.dependency_tracker.bounce(unwrap.item)
58
54
  @item_rep.path(snapshot: snapshot)
59
55
  end
60
56
 
@@ -67,9 +63,7 @@ module Nanoc
67
63
 
68
64
  # @api private
69
65
  def raw_path(snapshot: :last)
70
- Nanoc::Int::NotificationCenter.post(:visit_started, unwrap.item)
71
- Nanoc::Int::NotificationCenter.post(:visit_ended, unwrap.item)
72
-
66
+ @context.dependency_tracker.bounce(unwrap.item)
73
67
  @item_rep.raw_path(snapshot: snapshot)
74
68
  end
75
69