nanoc-core 4.11.12 → 4.11.17

Sign up to get free protection for your applications and to get access to all the features.
Files changed (78) hide show
  1. checksums.yaml +4 -4
  2. data/lib/nanoc/core.rb +37 -0
  3. data/lib/nanoc/core/basic_item_rep_collection_view.rb +88 -0
  4. data/lib/nanoc/core/basic_item_rep_view.rb +83 -0
  5. data/lib/nanoc/core/basic_item_view.rb +54 -0
  6. data/lib/nanoc/core/changes_stream.rb +55 -0
  7. data/lib/nanoc/core/checksum_store.rb +1 -1
  8. data/lib/nanoc/core/checksummer.rb +4 -2
  9. data/lib/nanoc/core/compilation_item_rep_collection_view.rb +12 -0
  10. data/lib/nanoc/core/compilation_item_rep_view.rb +57 -0
  11. data/lib/nanoc/core/compilation_item_view.rb +47 -0
  12. data/lib/nanoc/core/compilation_phases/abstract.rb +48 -0
  13. data/lib/nanoc/core/compilation_phases/cache.rb +43 -0
  14. data/lib/nanoc/core/compilation_phases/mark_done.rb +23 -0
  15. data/lib/nanoc/core/compilation_phases/notify.rb +19 -0
  16. data/lib/nanoc/core/compilation_phases/recalculate.rb +49 -0
  17. data/lib/nanoc/core/compilation_phases/resume.rb +52 -0
  18. data/lib/nanoc/core/compilation_phases/write.rb +84 -0
  19. data/lib/nanoc/core/compilation_stages/build_reps.rb +36 -0
  20. data/lib/nanoc/core/compilation_stages/calculate_checksums.rb +42 -0
  21. data/lib/nanoc/core/compilation_stages/cleanup.rb +43 -0
  22. data/lib/nanoc/core/compilation_stages/compile_reps.rb +96 -0
  23. data/lib/nanoc/core/compilation_stages/determine_outdatedness.rb +49 -0
  24. data/lib/nanoc/core/compilation_stages/forget_outdated_dependencies.rb +20 -0
  25. data/lib/nanoc/core/compilation_stages/load_stores.rb +35 -0
  26. data/lib/nanoc/core/compilation_stages/postprocess.rb +21 -0
  27. data/lib/nanoc/core/compilation_stages/preprocess.rb +32 -0
  28. data/lib/nanoc/core/compilation_stages/prune.rb +30 -0
  29. data/lib/nanoc/core/compilation_stages/store_post_compilation_state.rb +20 -0
  30. data/lib/nanoc/core/compilation_stages/store_pre_compilation_state.rb +32 -0
  31. data/lib/nanoc/core/compiled_content_cache.rb +2 -2
  32. data/lib/nanoc/core/compiler.rb +214 -0
  33. data/lib/nanoc/core/compiler_loader.rb +48 -0
  34. data/lib/nanoc/core/config_loader.rb +95 -0
  35. data/lib/nanoc/core/config_view.rb +67 -0
  36. data/lib/nanoc/core/configuration.rb +2 -4
  37. data/lib/nanoc/core/contracts_support.rb +20 -0
  38. data/lib/nanoc/core/dependency_store.rb +3 -3
  39. data/lib/nanoc/core/document_view_mixin.rb +87 -0
  40. data/lib/nanoc/core/errors.rb +108 -0
  41. data/lib/nanoc/core/executor.rb +134 -0
  42. data/lib/nanoc/core/feature.rb +92 -0
  43. data/lib/nanoc/core/filter.rb +269 -0
  44. data/lib/nanoc/core/identifiable_collection_view.rb +111 -0
  45. data/lib/nanoc/core/item_collection_with_reps_view.rb +12 -0
  46. data/lib/nanoc/core/item_collection_without_reps_view.rb +12 -0
  47. data/lib/nanoc/core/item_rep_builder.rb +54 -0
  48. data/lib/nanoc/core/item_rep_selector.rb +67 -0
  49. data/lib/nanoc/core/item_rep_writer.rb +85 -0
  50. data/lib/nanoc/core/layout_collection_view.rb +12 -0
  51. data/lib/nanoc/core/layout_view.rb +9 -0
  52. data/lib/nanoc/core/mutable_config_view.rb +16 -0
  53. data/lib/nanoc/core/mutable_document_view_mixin.rb +60 -0
  54. data/lib/nanoc/core/mutable_identifiable_collection_view.rb +19 -0
  55. data/lib/nanoc/core/mutable_item_collection_view.rb +34 -0
  56. data/lib/nanoc/core/mutable_item_view.rb +9 -0
  57. data/lib/nanoc/core/mutable_layout_collection_view.rb +26 -0
  58. data/lib/nanoc/core/mutable_layout_view.rb +9 -0
  59. data/lib/nanoc/core/outdatedness_checker.rb +222 -0
  60. data/lib/nanoc/core/outdatedness_rules/attributes_modified.rb +41 -0
  61. data/lib/nanoc/core/outdatedness_rules/code_snippets_modified.rb +31 -0
  62. data/lib/nanoc/core/outdatedness_rules/content_modified.rb +21 -0
  63. data/lib/nanoc/core/outdatedness_rules/item_collection_extended.rb +20 -0
  64. data/lib/nanoc/core/outdatedness_rules/layout_collection_extended.rb +20 -0
  65. data/lib/nanoc/core/outdatedness_rules/not_written.rb +17 -0
  66. data/lib/nanoc/core/outdatedness_rules/rules_modified.rb +45 -0
  67. data/lib/nanoc/core/outdatedness_rules/uses_always_outdated_filter.rb +26 -0
  68. data/lib/nanoc/core/post_compile_item_collection_view.rb +12 -0
  69. data/lib/nanoc/core/post_compile_item_rep_collection_view.rb +12 -0
  70. data/lib/nanoc/core/post_compile_item_rep_view.rb +33 -0
  71. data/lib/nanoc/core/post_compile_item_view.rb +20 -0
  72. data/lib/nanoc/core/pruner.rb +123 -0
  73. data/lib/nanoc/core/site_loader.rb +102 -0
  74. data/lib/nanoc/core/trivial_error.rb +10 -0
  75. data/lib/nanoc/core/version.rb +1 -1
  76. data/lib/nanoc/core/view.rb +43 -0
  77. data/lib/nanoc/core/view_context_for_compilation.rb +6 -6
  78. metadata +97 -3
@@ -0,0 +1,134 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nanoc
4
+ module Core
5
+ class Executor
6
+ def initialize(rep, compilation_context, dependency_tracker)
7
+ @rep = rep
8
+ @compilation_context = compilation_context
9
+ @dependency_tracker = dependency_tracker
10
+ end
11
+
12
+ def filter(filter_name, filter_args = {})
13
+ filter = filter_for_filtering(filter_name)
14
+
15
+ begin
16
+ Nanoc::Core::NotificationCenter.post(:filtering_started, @rep, filter_name)
17
+
18
+ # Run filter
19
+ last = @compilation_context.compiled_content_store.get_current(@rep)
20
+ source = last.binary? ? last.filename : last.string
21
+ filter_args.freeze
22
+ result = filter.setup_and_run(source, filter_args)
23
+ last =
24
+ if filter.class.to_binary?
25
+ Nanoc::Core::BinaryContent.new(filter.output_filename).tap(&:freeze)
26
+ else
27
+ Nanoc::Core::TextualContent.new(result).tap(&:freeze)
28
+ end
29
+
30
+ # Store
31
+ @compilation_context.compiled_content_store.set_current(@rep, last)
32
+ ensure
33
+ Nanoc::Core::NotificationCenter.post(:filtering_ended, @rep, filter_name)
34
+ end
35
+ end
36
+
37
+ def layout(layout_identifier, extra_filter_args = nil)
38
+ layout = find_layout(layout_identifier)
39
+ filter_name_and_args = @compilation_context.filter_name_and_args_for_layout(layout)
40
+ filter_name = filter_name_and_args.name
41
+ if filter_name.nil?
42
+ raise Nanoc::Core::Errors::CannotDetermineFilter.new(layout_identifier)
43
+ end
44
+
45
+ filter_args = filter_name_and_args.args
46
+ filter_args = filter_args.merge(extra_filter_args || {})
47
+ filter_args.freeze
48
+
49
+ # Check whether item can be laid out
50
+ last = @compilation_context.compiled_content_store.get_current(@rep)
51
+ raise Nanoc::Core::Errors::CannotLayoutBinaryItem.new(@rep) if last.binary?
52
+
53
+ # Create filter
54
+ klass = Nanoc::Core::Filter.named!(filter_name)
55
+ layout_view = Nanoc::Core::LayoutView.new(layout, view_context)
56
+ filter = klass.new(assigns.merge(layout: layout_view))
57
+
58
+ # Visit
59
+ @dependency_tracker.bounce(layout, raw_content: true)
60
+
61
+ begin
62
+ Nanoc::Core::NotificationCenter.post(:filtering_started, @rep, filter_name)
63
+
64
+ # Layout
65
+ content = layout.content
66
+ arg = content.binary? ? content.filename : content.string
67
+ res = filter.setup_and_run(arg, filter_args)
68
+
69
+ # Store
70
+ last = Nanoc::Core::TextualContent.new(res).tap(&:freeze)
71
+ @compilation_context.compiled_content_store.set_current(@rep, last)
72
+ ensure
73
+ Nanoc::Core::NotificationCenter.post(:filtering_ended, @rep, filter_name)
74
+ end
75
+ end
76
+
77
+ def snapshot(snapshot_name)
78
+ last = @compilation_context.compiled_content_store.get_current(@rep)
79
+ @compilation_context.compiled_content_store.set(@rep, snapshot_name, last)
80
+ Nanoc::Core::NotificationCenter.post(:snapshot_created, @rep, snapshot_name)
81
+ end
82
+
83
+ def assigns
84
+ view_context.assigns_for(@rep, site: @compilation_context.site)
85
+ end
86
+
87
+ def layouts
88
+ @compilation_context.site.layouts
89
+ end
90
+
91
+ def find_layout(arg)
92
+ req_id = arg.__nanoc_cleaned_identifier
93
+ layout = layouts[req_id]
94
+ return layout if layout
95
+
96
+ if use_globs?
97
+ pat = Nanoc::Core::Pattern.from(arg)
98
+ layout = layouts.find { |l| pat.match?(l.identifier) }
99
+ return layout if layout
100
+ end
101
+
102
+ raise Nanoc::Core::Errors::UnknownLayout.new(arg)
103
+ end
104
+
105
+ def filter_for_filtering(filter_name)
106
+ klass = Nanoc::Core::Filter.named!(filter_name)
107
+
108
+ last = @compilation_context.compiled_content_store.get_current(@rep)
109
+ if klass.from_binary? && !last.binary?
110
+ raise Nanoc::Core::Errors::CannotUseBinaryFilter.new(@rep, klass)
111
+ elsif !klass.from_binary? && last.binary?
112
+ raise Nanoc::Core::Errors::CannotUseTextualFilter.new(@rep, klass)
113
+ end
114
+
115
+ klass.new(assigns)
116
+ end
117
+
118
+ def use_globs?
119
+ @compilation_context.site.config[:string_pattern_type] == 'glob'
120
+ end
121
+
122
+ def view_context
123
+ @_view_context ||=
124
+ Nanoc::Core::ViewContextForCompilation.new(
125
+ reps: @compilation_context.reps,
126
+ items: @compilation_context.site.items,
127
+ dependency_tracker: @dependency_tracker,
128
+ compilation_context: @compilation_context,
129
+ compiled_content_store: @compilation_context.compiled_content_store,
130
+ )
131
+ end
132
+ end
133
+ end
134
+ end
@@ -0,0 +1,92 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nanoc
4
+ module Core
5
+ # @example Defining a feature and checking its enabledness
6
+ #
7
+ # Nanoc::Core::Feature.define('environments', version: '4.3')
8
+ # Nanoc::Core::Feature.enabled?(Nanoc::Core::Feature::ENVIRONMENTS)
9
+ #
10
+ module Feature
11
+ # Defines a new feature with the given name, experimental in the given
12
+ # version. The feature will be made available as a constant with the same
13
+ # name, in uppercase, on the Nanoc::Core::Feature module.
14
+ #
15
+ # @example Defining Nanoc::Core::Feature::ENVIRONMENTS
16
+ #
17
+ # Nanoc::Core::Feature.define('environments', version: '4.3')
18
+ #
19
+ # @param name The name of the feature
20
+ #
21
+ # @param version The minor version in which the feature is considered
22
+ # experimental.
23
+ #
24
+ # @return [void]
25
+ def self.define(name, version:)
26
+ repo[name] = version
27
+ const_set(name.upcase, name)
28
+ end
29
+
30
+ # Undefines the feature with the given name. For testing purposes only.
31
+ #
32
+ # @param name The name of the feature
33
+ #
34
+ # @return [void]
35
+ #
36
+ # @private
37
+ def self.undefine(name)
38
+ repo.delete(name)
39
+ remove_const(name.upcase)
40
+ end
41
+
42
+ # @param [String] feature_name
43
+ #
44
+ # @return [Boolean] Whether or not the feature with the given name is enabled
45
+ def self.enabled?(feature_name)
46
+ enabled_features.include?(feature_name) ||
47
+ enabled_features.include?('all')
48
+ end
49
+
50
+ # @api private
51
+ def self.enable(feature_name)
52
+ raise ArgumentError, 'no block given' unless block_given?
53
+
54
+ if enabled?(feature_name)
55
+ yield
56
+ else
57
+ begin
58
+ enabled_features << feature_name
59
+ yield
60
+ ensure
61
+ enabled_features.delete(feature_name)
62
+ end
63
+ end
64
+ end
65
+
66
+ # @api private
67
+ def self.reset_caches
68
+ @enabled_features = nil
69
+ end
70
+
71
+ # @api private
72
+ def self.enabled_features
73
+ @enabled_features ||= Set.new(ENV.fetch('NANOC_FEATURES', '').split(','))
74
+ end
75
+
76
+ # @api private
77
+ def self.repo
78
+ @repo ||= {}
79
+ end
80
+
81
+ # @return [Enumerable<String>] Names of features that still exist, but
82
+ # should not be considered as experimental in the current version of
83
+ # Nanoc.
84
+ def self.all_outdated
85
+ repo.keys.reject do |name|
86
+ version = repo[name]
87
+ Nanoc::VERSION.start_with?(version)
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,269 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nanoc
4
+ module Core
5
+ # Nanoc::Core::Filter is responsible for filtering items. It is the superclass
6
+ # for all textual filters.
7
+ #
8
+ # A filter instance should only be used once. Filters should not be reused
9
+ # since they store state.
10
+ #
11
+ # When creating a filter with a hash containing assigned variables, those
12
+ # variables will be made available in the `@assigns` instance variable and
13
+ # the {#assigns} method. The assigns itself will also be available as
14
+ # instance variables and instance methods.
15
+ #
16
+ # @example Accessing assigns in different ways
17
+ #
18
+ # filter = SomeFilter.new({ :foo => 'bar' })
19
+ # filter.instance_eval { @assigns[:foo] }
20
+ # # => 'bar'
21
+ # filter.instance_eval { assigns[:foo] }
22
+ # # => 'bar'
23
+ # filter.instance_eval { @foo }
24
+ # # => 'bar'
25
+ # filter.instance_eval { foo }
26
+ # # => 'bar'
27
+ #
28
+ # @abstract Subclass and override {#run} to implement a custom filter.
29
+ class Filter < Nanoc::Core::Context
30
+ extend DDPlugin::Plugin
31
+
32
+ include Nanoc::Core::ContractsSupport
33
+
34
+ # @api private
35
+ TMP_BINARY_ITEMS_DIR = 'binary_items'
36
+
37
+ class UnknownFilterError < Nanoc::Core::Error
38
+ include Nanoc::Core::ContractsSupport
39
+
40
+ contract C::Or[String, Symbol] => self
41
+ def initialize(filter_name)
42
+ super("The requested filter, “#{filter_name}”, does not exist.")
43
+ end
44
+ end
45
+
46
+ class OutputNotWrittenError < Nanoc::Core::Error
47
+ include Nanoc::Core::ContractsSupport
48
+
49
+ contract C::Or[String, Symbol], String => self
50
+ def initialize(filter_name, output_filename)
51
+ super("The #{filter_name.inspect} filter did not write anything to the required output file, #{output_filename}.")
52
+ end
53
+ end
54
+
55
+ class FilterReturnedNilError < Nanoc::Core::Error
56
+ include Nanoc::Core::ContractsSupport
57
+
58
+ contract C::Or[String, Symbol] => self
59
+ def initialize(filter_name)
60
+ super("The #{filter_name.inspect} filter returned nil, but is required to return a String.")
61
+ end
62
+ end
63
+
64
+ class << self
65
+ def define(ident, &block)
66
+ filter_class = Class.new(::Nanoc::Core::Filter) { identifier(ident) }
67
+ filter_class.send(:define_method, :run) do |content, params = {}|
68
+ instance_exec(content, params, &block)
69
+ end
70
+ end
71
+
72
+ def named!(name)
73
+ klass = named(name)
74
+ raise UnknownFilterError.new(name) if klass.nil?
75
+
76
+ klass
77
+ end
78
+
79
+ # Sets the new type for the filter. The type can be `:binary` (default)
80
+ # or `:text`. The given argument can either be a symbol indicating both
81
+ # “from” and “to” types, or a hash where the only key is the “from” type
82
+ # and the only value is the “to” type.
83
+ #
84
+ # @example Specifying a text-to-text filter
85
+ #
86
+ # type :text
87
+ #
88
+ # @example Specifying a text-to-binary filter
89
+ #
90
+ # type :text => :binary
91
+ #
92
+ # @param [Symbol, Hash] arg The new type of this filter
93
+ #
94
+ # @return [void]
95
+ def type(arg)
96
+ if arg.is_a?(Hash)
97
+ @from = arg.keys[0]
98
+ @to = arg.values[0]
99
+ else
100
+ @from = arg
101
+ @to = arg
102
+ end
103
+ end
104
+
105
+ # @return [Boolean] True if this filter can be applied to binary item
106
+ # representations, false otherwise
107
+ #
108
+ # @api private
109
+ def from_binary?
110
+ (@from || :text) == :binary
111
+ end
112
+
113
+ # @return [Boolean] True if this filter results in a binary item
114
+ # representation, false otherwise
115
+ #
116
+ # @api private
117
+ def to_binary?
118
+ (@to || :text) == :binary
119
+ end
120
+
121
+ # @api private
122
+ def to_text?
123
+ (@to || :text) == :text
124
+ end
125
+
126
+ # @return [Boolean]
127
+ #
128
+ # @api private
129
+ def always_outdated?
130
+ @always_outdated || false
131
+ end
132
+
133
+ # Marks this filter as always causing the item rep to be outdated. This is useful for filters
134
+ # that cannot do dependency tracking properly.
135
+ #
136
+ # @return [void]
137
+ def always_outdated
138
+ @always_outdated = true
139
+ end
140
+
141
+ # @overload requires(*requires)
142
+ # Sets the required libraries for this filter.
143
+ # @param [Array<String>] requires A list of library names that are required
144
+ # @return [void]
145
+ # @overload requires
146
+ # Returns the required libraries for this filter.
147
+ # @return [Enumerable<String>] This filter’s list of library names that are required
148
+ def requires(*requires)
149
+ if requires.any?
150
+ @requires = requires
151
+ else
152
+ @requires || []
153
+ end
154
+ end
155
+
156
+ # Requires the filter’s required library if necessary.
157
+ #
158
+ # @return [void]
159
+ #
160
+ # @api private
161
+ def setup
162
+ @setup ||= begin
163
+ requires.each { |r| require r }
164
+ true
165
+ end
166
+ end
167
+ end
168
+
169
+ # Creates a new filter that has access to the given assigns.
170
+ #
171
+ # @param [Hash] hash A hash containing variables that should be made
172
+ # available during filtering.
173
+ #
174
+ # @api private
175
+ def initialize(hash = {})
176
+ @assigns = hash
177
+ super
178
+ end
179
+
180
+ # Sets up the filter and runs the filter. This method passes its arguments
181
+ # to {#run} unchanged and returns the return value from {#run}.
182
+ #
183
+ # @see #run
184
+ #
185
+ # @api private
186
+ def setup_and_run(*args)
187
+ self.class.setup
188
+ run(*args).tap { |res| verify(res) }
189
+ end
190
+
191
+ # Runs the filter on the given content or filename.
192
+ #
193
+ # @abstract
194
+ #
195
+ # @param [String] content_or_filename The unprocessed content that should
196
+ # be filtered (if the item is a textual item) or the path to the file
197
+ # that should be filtered (if the item is a binary item)
198
+ #
199
+ # @param [Hash] params A hash containing parameters. Filter subclasses can
200
+ # use these parameters to allow modifying the filter's behaviour.
201
+ #
202
+ # @return [String, void] If the filter output binary content, the return
203
+ # value is undefined; if the filter outputs textual content, the return
204
+ # value will be the filtered content.
205
+ def run(content_or_filename, params = {}) # rubocop:disable Lint/UnusedMethodArgument
206
+ raise NotImplementedError.new('Nanoc::Core::Filter subclasses must implement #run')
207
+ end
208
+
209
+ def verify(res)
210
+ if self.class.to_binary?
211
+ unless File.file?(output_filename)
212
+ raise Nanoc::Core::Filter::OutputNotWrittenError.new(self.class.identifier, output_filename)
213
+ end
214
+ elsif self.class.to_text?
215
+ unless res
216
+ raise Nanoc::Core::Filter::FilterReturnedNilError.new(self.class.identifier)
217
+ end
218
+ end
219
+ end
220
+
221
+ contract C::None => String
222
+ # Returns a filename that is used to write data to. This method is only
223
+ # used on binary items. When running a binary filter on a file, the
224
+ # resulting file must end up in the location returned by this method.
225
+ #
226
+ # The returned filename will be absolute, so it is safe to change to
227
+ # another directory inside the filter.
228
+ #
229
+ # @return [String] The output filename
230
+ def output_filename
231
+ @output_filename ||=
232
+ Nanoc::Core::TempFilenameFactory.instance.create(TMP_BINARY_ITEMS_DIR)
233
+ end
234
+
235
+ contract C::None => String
236
+ # Returns the filename associated with the item that is being filtered.
237
+ # It is in the format `item <identifier> (rep <name>)`.
238
+ #
239
+ # @return [String] The filename
240
+ #
241
+ # @api private
242
+ def filename
243
+ if @layout
244
+ "layout #{@layout.identifier}"
245
+ elsif @item
246
+ "item #{@item.identifier} (rep #{@item_rep.name})"
247
+ else
248
+ '?'
249
+ end
250
+ end
251
+
252
+ # @api private
253
+ def on_main_fiber(&block)
254
+ Fiber.yield(block)
255
+ end
256
+
257
+ contract C::ArrayOf[C::Named['Nanoc::Core::BasicItemView']] => C::Any
258
+ # Creates a dependency from the item that is currently being filtered onto
259
+ # the given collection of items. In other words, require the given items
260
+ # to be compiled first before this items is processed.
261
+ #
262
+ # @return [void]
263
+ def depend_on(items)
264
+ items.flat_map(&:reps).flat_map(&:raw_path)
265
+ items.each(&:raw_filename)
266
+ end
267
+ end
268
+ end
269
+ end