nanoc 4.1.6 → 4.2.0b1

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