nanoc-core 4.11.12 → 4.11.13

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 (73) 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/checksummer.rb +2 -0
  7. data/lib/nanoc/core/compilation_item_rep_collection_view.rb +12 -0
  8. data/lib/nanoc/core/compilation_item_rep_view.rb +51 -0
  9. data/lib/nanoc/core/compilation_item_view.rb +47 -0
  10. data/lib/nanoc/core/compilation_phases/abstract.rb +48 -0
  11. data/lib/nanoc/core/compilation_phases/cache.rb +43 -0
  12. data/lib/nanoc/core/compilation_phases/mark_done.rb +23 -0
  13. data/lib/nanoc/core/compilation_phases/notify.rb +19 -0
  14. data/lib/nanoc/core/compilation_phases/recalculate.rb +49 -0
  15. data/lib/nanoc/core/compilation_phases/resume.rb +52 -0
  16. data/lib/nanoc/core/compilation_phases/write.rb +84 -0
  17. data/lib/nanoc/core/compilation_stages/build_reps.rb +36 -0
  18. data/lib/nanoc/core/compilation_stages/calculate_checksums.rb +42 -0
  19. data/lib/nanoc/core/compilation_stages/cleanup.rb +43 -0
  20. data/lib/nanoc/core/compilation_stages/compile_reps.rb +96 -0
  21. data/lib/nanoc/core/compilation_stages/determine_outdatedness.rb +49 -0
  22. data/lib/nanoc/core/compilation_stages/forget_outdated_dependencies.rb +20 -0
  23. data/lib/nanoc/core/compilation_stages/load_stores.rb +35 -0
  24. data/lib/nanoc/core/compilation_stages/postprocess.rb +21 -0
  25. data/lib/nanoc/core/compilation_stages/preprocess.rb +32 -0
  26. data/lib/nanoc/core/compilation_stages/prune.rb +30 -0
  27. data/lib/nanoc/core/compilation_stages/store_post_compilation_state.rb +20 -0
  28. data/lib/nanoc/core/compilation_stages/store_pre_compilation_state.rb +32 -0
  29. data/lib/nanoc/core/compiler.rb +214 -0
  30. data/lib/nanoc/core/compiler_loader.rb +48 -0
  31. data/lib/nanoc/core/config_loader.rb +95 -0
  32. data/lib/nanoc/core/config_view.rb +67 -0
  33. data/lib/nanoc/core/configuration.rb +2 -4
  34. data/lib/nanoc/core/document_view_mixin.rb +87 -0
  35. data/lib/nanoc/core/errors.rb +97 -0
  36. data/lib/nanoc/core/executor.rb +134 -0
  37. data/lib/nanoc/core/feature.rb +92 -0
  38. data/lib/nanoc/core/filter.rb +269 -0
  39. data/lib/nanoc/core/identifiable_collection_view.rb +111 -0
  40. data/lib/nanoc/core/item_collection_with_reps_view.rb +12 -0
  41. data/lib/nanoc/core/item_collection_without_reps_view.rb +12 -0
  42. data/lib/nanoc/core/item_rep_builder.rb +54 -0
  43. data/lib/nanoc/core/item_rep_selector.rb +67 -0
  44. data/lib/nanoc/core/item_rep_writer.rb +85 -0
  45. data/lib/nanoc/core/layout_collection_view.rb +12 -0
  46. data/lib/nanoc/core/layout_view.rb +9 -0
  47. data/lib/nanoc/core/mutable_config_view.rb +16 -0
  48. data/lib/nanoc/core/mutable_document_view_mixin.rb +60 -0
  49. data/lib/nanoc/core/mutable_identifiable_collection_view.rb +19 -0
  50. data/lib/nanoc/core/mutable_item_collection_view.rb +34 -0
  51. data/lib/nanoc/core/mutable_item_view.rb +9 -0
  52. data/lib/nanoc/core/mutable_layout_collection_view.rb +26 -0
  53. data/lib/nanoc/core/mutable_layout_view.rb +9 -0
  54. data/lib/nanoc/core/outdatedness_checker.rb +222 -0
  55. data/lib/nanoc/core/outdatedness_rules/attributes_modified.rb +41 -0
  56. data/lib/nanoc/core/outdatedness_rules/code_snippets_modified.rb +31 -0
  57. data/lib/nanoc/core/outdatedness_rules/content_modified.rb +21 -0
  58. data/lib/nanoc/core/outdatedness_rules/item_collection_extended.rb +20 -0
  59. data/lib/nanoc/core/outdatedness_rules/layout_collection_extended.rb +20 -0
  60. data/lib/nanoc/core/outdatedness_rules/not_written.rb +17 -0
  61. data/lib/nanoc/core/outdatedness_rules/rules_modified.rb +45 -0
  62. data/lib/nanoc/core/outdatedness_rules/uses_always_outdated_filter.rb +26 -0
  63. data/lib/nanoc/core/post_compile_item_collection_view.rb +12 -0
  64. data/lib/nanoc/core/post_compile_item_rep_collection_view.rb +12 -0
  65. data/lib/nanoc/core/post_compile_item_rep_view.rb +33 -0
  66. data/lib/nanoc/core/post_compile_item_view.rb +20 -0
  67. data/lib/nanoc/core/pruner.rb +119 -0
  68. data/lib/nanoc/core/site_loader.rb +102 -0
  69. data/lib/nanoc/core/trivial_error.rb +10 -0
  70. data/lib/nanoc/core/version.rb +1 -1
  71. data/lib/nanoc/core/view.rb +43 -0
  72. data/lib/nanoc/core/view_context_for_compilation.rb +6 -6
  73. metadata +95 -2
@@ -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
@@ -0,0 +1,111 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nanoc
4
+ module Core
5
+ class IdentifiableCollectionView < ::Nanoc::Core::View
6
+ include Enumerable
7
+
8
+ NOTHING = Object.new
9
+
10
+ # @api private
11
+ def initialize(objects, context)
12
+ super(context)
13
+ @objects = objects
14
+ end
15
+
16
+ # @api private
17
+ def _unwrap
18
+ @objects
19
+ end
20
+
21
+ # @abstract
22
+ #
23
+ # @api private
24
+ def view_class
25
+ raise NotImplementedError
26
+ end
27
+
28
+ # Calls the given block once for each object, passing that object as a parameter.
29
+ #
30
+ # @yieldparam [#identifier] object
31
+ #
32
+ # @yieldreturn [void]
33
+ #
34
+ # @return [self]
35
+ def each
36
+ @context.dependency_tracker.bounce(_unwrap, raw_content: true)
37
+ @objects.each { |i| yield view_class.new(i, @context) }
38
+ self
39
+ end
40
+
41
+ # @return [Integer]
42
+ def size
43
+ @context.dependency_tracker.bounce(_unwrap, raw_content: true)
44
+ @objects.size
45
+ end
46
+
47
+ # Finds all objects whose identifier matches the given argument.
48
+ #
49
+ # @param [String, Regex] arg
50
+ #
51
+ # @return [Enumerable]
52
+ def find_all(arg = NOTHING, &block)
53
+ if NOTHING.equal?(arg)
54
+ @context.dependency_tracker.bounce(_unwrap, raw_content: true)
55
+ return @objects.map { |i| view_class.new(i, @context) }.select(&block)
56
+ end
57
+
58
+ prop_attribute =
59
+ case arg
60
+ when String, Nanoc::Core::Identifier
61
+ [arg.to_s]
62
+ when Regexp
63
+ [arg]
64
+ else
65
+ true
66
+ end
67
+
68
+ @context.dependency_tracker.bounce(_unwrap, raw_content: prop_attribute)
69
+ @objects.find_all(arg).map { |i| view_class.new(i, @context) }
70
+ end
71
+
72
+ # @overload [](string)
73
+ #
74
+ # Finds the object whose identifier matches the given string.
75
+ #
76
+ # If the glob syntax is enabled, the string can be a glob, in which case
77
+ # this method finds the first object that matches the given glob.
78
+ #
79
+ # @param [String] string
80
+ #
81
+ # @return [nil] if no object matches the string
82
+ #
83
+ # @return [#identifier] if an object was found
84
+ #
85
+ # @overload [](regex)
86
+ #
87
+ # Finds the object whose identifier matches the given regular expression.
88
+ #
89
+ # @param [Regex] regex
90
+ #
91
+ # @return [nil] if no object matches the regex
92
+ #
93
+ # @return [#identifier] if an object was found
94
+ def [](arg)
95
+ prop_attribute =
96
+ case arg
97
+ when String, Nanoc::Core::Identifier
98
+ [arg.to_s]
99
+ when Regexp
100
+ [arg]
101
+ else
102
+ true
103
+ end
104
+
105
+ @context.dependency_tracker.bounce(_unwrap, raw_content: prop_attribute)
106
+ res = @objects[arg]
107
+ res && view_class.new(res, @context)
108
+ end
109
+ end
110
+ end
111
+ end