nanoc-core 4.11.12 → 4.11.13

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