jekyll 4.2.1 → 4.2.2

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 (124) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +350 -350
  3. data/LICENSE +21 -21
  4. data/README.markdown +86 -86
  5. data/exe/jekyll +57 -57
  6. data/lib/blank_template/_config.yml +3 -3
  7. data/lib/blank_template/_layouts/default.html +12 -12
  8. data/lib/blank_template/_sass/main.scss +9 -9
  9. data/lib/blank_template/assets/css/main.scss +4 -4
  10. data/lib/blank_template/index.md +8 -8
  11. data/lib/jekyll/cache.rb +190 -190
  12. data/lib/jekyll/cleaner.rb +111 -111
  13. data/lib/jekyll/collection.rb +309 -309
  14. data/lib/jekyll/command.rb +105 -105
  15. data/lib/jekyll/commands/build.rb +93 -93
  16. data/lib/jekyll/commands/clean.rb +45 -45
  17. data/lib/jekyll/commands/doctor.rb +177 -177
  18. data/lib/jekyll/commands/help.rb +34 -34
  19. data/lib/jekyll/commands/new.rb +172 -169
  20. data/lib/jekyll/commands/new_theme.rb +40 -40
  21. data/lib/jekyll/commands/serve/live_reload_reactor.rb +122 -122
  22. data/lib/jekyll/commands/serve/livereload_assets/livereload.js +1183 -1183
  23. data/lib/jekyll/commands/serve/servlet.rb +202 -202
  24. data/lib/jekyll/commands/serve/websockets.rb +81 -81
  25. data/lib/jekyll/commands/serve.rb +362 -362
  26. data/lib/jekyll/configuration.rb +313 -313
  27. data/lib/jekyll/converter.rb +54 -54
  28. data/lib/jekyll/converters/identity.rb +41 -41
  29. data/lib/jekyll/converters/markdown/kramdown_parser.rb +199 -199
  30. data/lib/jekyll/converters/markdown.rb +113 -113
  31. data/lib/jekyll/converters/smartypants.rb +70 -70
  32. data/lib/jekyll/convertible.rb +257 -257
  33. data/lib/jekyll/deprecator.rb +50 -50
  34. data/lib/jekyll/document.rb +544 -544
  35. data/lib/jekyll/drops/collection_drop.rb +20 -20
  36. data/lib/jekyll/drops/document_drop.rb +70 -70
  37. data/lib/jekyll/drops/drop.rb +293 -293
  38. data/lib/jekyll/drops/excerpt_drop.rb +19 -19
  39. data/lib/jekyll/drops/jekyll_drop.rb +32 -32
  40. data/lib/jekyll/drops/site_drop.rb +66 -66
  41. data/lib/jekyll/drops/static_file_drop.rb +14 -14
  42. data/lib/jekyll/drops/unified_payload_drop.rb +26 -26
  43. data/lib/jekyll/drops/url_drop.rb +140 -140
  44. data/lib/jekyll/entry_filter.rb +121 -121
  45. data/lib/jekyll/errors.rb +20 -20
  46. data/lib/jekyll/excerpt.rb +201 -201
  47. data/lib/jekyll/external.rb +79 -79
  48. data/lib/jekyll/filters/date_filters.rb +110 -110
  49. data/lib/jekyll/filters/grouping_filters.rb +64 -64
  50. data/lib/jekyll/filters/url_filters.rb +98 -98
  51. data/lib/jekyll/filters.rb +535 -535
  52. data/lib/jekyll/frontmatter_defaults.rb +240 -240
  53. data/lib/jekyll/generator.rb +5 -5
  54. data/lib/jekyll/hooks.rb +107 -107
  55. data/lib/jekyll/inclusion.rb +32 -32
  56. data/lib/jekyll/layout.rb +67 -67
  57. data/lib/jekyll/liquid_extensions.rb +22 -22
  58. data/lib/jekyll/liquid_renderer/file.rb +77 -77
  59. data/lib/jekyll/liquid_renderer/table.rb +55 -55
  60. data/lib/jekyll/liquid_renderer.rb +80 -80
  61. data/lib/jekyll/log_adapter.rb +151 -151
  62. data/lib/jekyll/mime.types +866 -866
  63. data/lib/jekyll/page.rb +217 -217
  64. data/lib/jekyll/page_excerpt.rb +25 -25
  65. data/lib/jekyll/page_without_a_file.rb +14 -14
  66. data/lib/jekyll/path_manager.rb +74 -74
  67. data/lib/jekyll/plugin.rb +92 -92
  68. data/lib/jekyll/plugin_manager.rb +115 -115
  69. data/lib/jekyll/profiler.rb +58 -58
  70. data/lib/jekyll/publisher.rb +23 -23
  71. data/lib/jekyll/reader.rb +192 -192
  72. data/lib/jekyll/readers/collection_reader.rb +23 -23
  73. data/lib/jekyll/readers/data_reader.rb +79 -79
  74. data/lib/jekyll/readers/layout_reader.rb +62 -62
  75. data/lib/jekyll/readers/page_reader.rb +25 -25
  76. data/lib/jekyll/readers/post_reader.rb +85 -85
  77. data/lib/jekyll/readers/static_file_reader.rb +25 -25
  78. data/lib/jekyll/readers/theme_assets_reader.rb +52 -52
  79. data/lib/jekyll/regenerator.rb +195 -195
  80. data/lib/jekyll/related_posts.rb +52 -52
  81. data/lib/jekyll/renderer.rb +265 -265
  82. data/lib/jekyll/site.rb +551 -551
  83. data/lib/jekyll/static_file.rb +208 -208
  84. data/lib/jekyll/stevenson.rb +60 -60
  85. data/lib/jekyll/tags/highlight.rb +110 -110
  86. data/lib/jekyll/tags/include.rb +275 -275
  87. data/lib/jekyll/tags/link.rb +42 -42
  88. data/lib/jekyll/tags/post_url.rb +106 -106
  89. data/lib/jekyll/theme.rb +86 -86
  90. data/lib/jekyll/theme_builder.rb +121 -121
  91. data/lib/jekyll/url.rb +167 -167
  92. data/lib/jekyll/utils/ansi.rb +57 -57
  93. data/lib/jekyll/utils/exec.rb +26 -26
  94. data/lib/jekyll/utils/internet.rb +37 -37
  95. data/lib/jekyll/utils/platforms.rb +67 -67
  96. data/lib/jekyll/utils/thread_event.rb +31 -31
  97. data/lib/jekyll/utils/win_tz.rb +75 -75
  98. data/lib/jekyll/utils.rb +367 -367
  99. data/lib/jekyll/version.rb +5 -5
  100. data/lib/jekyll.rb +195 -195
  101. data/lib/site_template/.gitignore +5 -5
  102. data/lib/site_template/404.html +25 -25
  103. data/lib/site_template/_config.yml +55 -55
  104. data/lib/site_template/_posts/0000-00-00-welcome-to-jekyll.markdown.erb +29 -29
  105. data/lib/site_template/about.markdown +18 -18
  106. data/lib/site_template/index.markdown +6 -6
  107. data/lib/theme_template/CODE_OF_CONDUCT.md.erb +74 -74
  108. data/lib/theme_template/Gemfile +4 -4
  109. data/lib/theme_template/LICENSE.txt.erb +21 -21
  110. data/lib/theme_template/README.md.erb +52 -52
  111. data/lib/theme_template/_layouts/default.html +1 -1
  112. data/lib/theme_template/_layouts/page.html +5 -5
  113. data/lib/theme_template/_layouts/post.html +5 -5
  114. data/lib/theme_template/example/_config.yml.erb +1 -1
  115. data/lib/theme_template/example/_post.md +12 -12
  116. data/lib/theme_template/example/index.html +14 -14
  117. data/lib/theme_template/example/style.scss +7 -7
  118. data/lib/theme_template/gitignore.erb +6 -6
  119. data/lib/theme_template/theme.gemspec.erb +16 -16
  120. data/rubocop/jekyll/assert_equal_literal_actual.rb +149 -149
  121. data/rubocop/jekyll/no_p_allowed.rb +23 -23
  122. data/rubocop/jekyll/no_puts_allowed.rb +23 -23
  123. data/rubocop/jekyll.rb +5 -5
  124. metadata +3 -3
data/lib/jekyll/site.rb CHANGED
@@ -1,551 +1,551 @@
1
- # frozen_string_literal: true
2
-
3
- module Jekyll
4
- class Site
5
- attr_reader :source, :dest, :cache_dir, :config
6
- attr_accessor :layouts, :pages, :static_files, :drafts, :inclusions,
7
- :exclude, :include, :lsi, :highlighter, :permalink_style,
8
- :time, :future, :unpublished, :safe, :plugins, :limit_posts,
9
- :show_drafts, :keep_files, :baseurl, :data, :file_read_opts,
10
- :gems, :plugin_manager, :theme
11
-
12
- attr_accessor :converters, :generators, :reader
13
- attr_reader :regenerator, :liquid_renderer, :includes_load_paths, :filter_cache, :profiler
14
-
15
- # Public: Initialize a new Site.
16
- #
17
- # config - A Hash containing site configuration details.
18
- def initialize(config)
19
- # Source and destination may not be changed after the site has been created.
20
- @source = File.expand_path(config["source"]).freeze
21
- @dest = File.expand_path(config["destination"]).freeze
22
-
23
- self.config = config
24
-
25
- @cache_dir = in_source_dir(config["cache_dir"])
26
- @filter_cache = {}
27
-
28
- @reader = Reader.new(self)
29
- @profiler = Profiler.new(self)
30
- @regenerator = Regenerator.new(self)
31
- @liquid_renderer = LiquidRenderer.new(self)
32
-
33
- Jekyll.sites << self
34
-
35
- reset
36
- setup
37
-
38
- Jekyll::Hooks.trigger :site, :after_init, self
39
- end
40
-
41
- # Public: Set the site's configuration. This handles side-effects caused by
42
- # changing values in the configuration.
43
- #
44
- # config - a Jekyll::Configuration, containing the new configuration.
45
- #
46
- # Returns the new configuration.
47
- def config=(config)
48
- @config = config.clone
49
-
50
- %w(safe lsi highlighter baseurl exclude include future unpublished
51
- show_drafts limit_posts keep_files).each do |opt|
52
- send("#{opt}=", config[opt])
53
- end
54
-
55
- # keep using `gems` to avoid breaking change
56
- self.gems = config["plugins"]
57
-
58
- configure_cache
59
- configure_plugins
60
- configure_theme
61
- configure_include_paths
62
- configure_file_read_opts
63
-
64
- self.permalink_style = config["permalink"].to_sym
65
-
66
- # Read in a _config.yml from the current theme-gem at the very end.
67
- @config = load_theme_configuration(config) if theme
68
- @config
69
- end
70
-
71
- # Public: Read, process, and write this Site to output.
72
- #
73
- # Returns nothing.
74
- def process
75
- return profiler.profile_process if config["profile"]
76
-
77
- reset
78
- read
79
- generate
80
- render
81
- cleanup
82
- write
83
- end
84
-
85
- def print_stats
86
- Jekyll.logger.info @liquid_renderer.stats_table
87
- end
88
-
89
- # rubocop:disable Metrics/AbcSize
90
- # rubocop:disable Metrics/MethodLength
91
- #
92
- # Reset Site details.
93
- #
94
- # Returns nothing
95
- def reset
96
- self.time = if config["time"]
97
- Utils.parse_date(config["time"].to_s, "Invalid time in _config.yml.")
98
- else
99
- Time.now
100
- end
101
- self.layouts = {}
102
- self.inclusions = {}
103
- self.pages = []
104
- self.static_files = []
105
- self.data = {}
106
- @post_attr_hash = {}
107
- @site_data = nil
108
- @collections = nil
109
- @documents = nil
110
- @docs_to_write = nil
111
- @regenerator.clear_cache
112
- @liquid_renderer.reset
113
- @site_cleaner = nil
114
- frontmatter_defaults.reset
115
-
116
- raise ArgumentError, "limit_posts must be a non-negative number" if limit_posts.negative?
117
-
118
- Jekyll::Cache.clear_if_config_changed config
119
- Jekyll::Hooks.trigger :site, :after_reset, self
120
- nil
121
- end
122
- # rubocop:enable Metrics/MethodLength
123
- # rubocop:enable Metrics/AbcSize
124
-
125
- # Load necessary libraries, plugins, converters, and generators.
126
- #
127
- # Returns nothing.
128
- def setup
129
- ensure_not_in_dest
130
-
131
- plugin_manager.conscientious_require
132
-
133
- self.converters = instantiate_subclasses(Jekyll::Converter)
134
- self.generators = instantiate_subclasses(Jekyll::Generator)
135
- end
136
-
137
- # Check that the destination dir isn't the source dir or a directory
138
- # parent to the source dir.
139
- def ensure_not_in_dest
140
- dest_pathname = Pathname.new(dest)
141
- Pathname.new(source).ascend do |path|
142
- if path == dest_pathname
143
- raise Errors::FatalException,
144
- "Destination directory cannot be or contain the Source directory."
145
- end
146
- end
147
- end
148
-
149
- # The list of collections and their corresponding Jekyll::Collection instances.
150
- # If config['collections'] is set, a new instance is created
151
- # for each item in the collection, a new hash is returned otherwise.
152
- #
153
- # Returns a Hash containing collection name-to-instance pairs.
154
- def collections
155
- @collections ||= collection_names.each_with_object({}) do |name, hsh|
156
- hsh[name] = Jekyll::Collection.new(self, name)
157
- end
158
- end
159
-
160
- # The list of collection names.
161
- #
162
- # Returns an array of collection names from the configuration,
163
- # or an empty array if the `collections` key is not set.
164
- def collection_names
165
- case config["collections"]
166
- when Hash
167
- config["collections"].keys
168
- when Array
169
- config["collections"]
170
- when nil
171
- []
172
- else
173
- raise ArgumentError, "Your `collections` key must be a hash or an array."
174
- end
175
- end
176
-
177
- # Read Site data from disk and load it into internal data structures.
178
- #
179
- # Returns nothing.
180
- def read
181
- reader.read
182
- limit_posts!
183
- Jekyll::Hooks.trigger :site, :post_read, self
184
- nil
185
- end
186
-
187
- # Run each of the Generators.
188
- #
189
- # Returns nothing.
190
- def generate
191
- generators.each do |generator|
192
- start = Time.now
193
- generator.generate(self)
194
- Jekyll.logger.debug "Generating:",
195
- "#{generator.class} finished in #{Time.now - start} seconds."
196
- end
197
- nil
198
- end
199
-
200
- # Render the site to the destination.
201
- #
202
- # Returns nothing.
203
- def render
204
- relative_permalinks_are_deprecated
205
-
206
- payload = site_payload
207
-
208
- Jekyll::Hooks.trigger :site, :pre_render, self, payload
209
-
210
- render_docs(payload)
211
- render_pages(payload)
212
-
213
- Jekyll::Hooks.trigger :site, :post_render, self, payload
214
- nil
215
- end
216
-
217
- # Remove orphaned files and empty directories in destination.
218
- #
219
- # Returns nothing.
220
- def cleanup
221
- site_cleaner.cleanup!
222
- nil
223
- end
224
-
225
- # Write static files, pages, and posts.
226
- #
227
- # Returns nothing.
228
- def write
229
- Jekyll::Commands::Doctor.conflicting_urls(self)
230
- each_site_file do |item|
231
- item.write(dest) if regenerator.regenerate?(item)
232
- end
233
- regenerator.write_metadata
234
- Jekyll::Hooks.trigger :site, :post_write, self
235
- nil
236
- end
237
-
238
- def posts
239
- collections["posts"] ||= Collection.new(self, "posts")
240
- end
241
-
242
- # Construct a Hash of Posts indexed by the specified Post attribute.
243
- #
244
- # post_attr - The String name of the Post attribute.
245
- #
246
- # Examples
247
- #
248
- # post_attr_hash('categories')
249
- # # => { 'tech' => [<Post A>, <Post B>],
250
- # # 'ruby' => [<Post B>] }
251
- #
252
- # Returns the Hash: { attr => posts } where
253
- # attr - One of the values for the requested attribute.
254
- # posts - The Array of Posts with the given attr value.
255
- def post_attr_hash(post_attr)
256
- # Build a hash map based on the specified post attribute ( post attr =>
257
- # array of posts ) then sort each array in reverse order.
258
- @post_attr_hash[post_attr] ||= begin
259
- hash = Hash.new { |h, key| h[key] = [] }
260
- posts.docs.each do |p|
261
- p.data[post_attr]&.each { |t| hash[t] << p }
262
- end
263
- hash.each_value { |posts| posts.sort!.reverse! }
264
- hash
265
- end
266
- end
267
-
268
- def tags
269
- post_attr_hash("tags")
270
- end
271
-
272
- def categories
273
- post_attr_hash("categories")
274
- end
275
-
276
- # Prepare site data for site payload. The method maintains backward compatibility
277
- # if the key 'data' is already used in _config.yml.
278
- #
279
- # Returns the Hash to be hooked to site.data.
280
- def site_data
281
- @site_data ||= (config["data"] || data)
282
- end
283
-
284
- # The Hash payload containing site-wide data.
285
- #
286
- # Returns the Hash: { "site" => data } where data is a Hash with keys:
287
- # "time" - The Time as specified in the configuration or the
288
- # current time if none was specified.
289
- # "posts" - The Array of Posts, sorted chronologically by post date
290
- # and then title.
291
- # "pages" - The Array of all Pages.
292
- # "html_pages" - The Array of HTML Pages.
293
- # "categories" - The Hash of category values and Posts.
294
- # See Site#post_attr_hash for type info.
295
- # "tags" - The Hash of tag values and Posts.
296
- # See Site#post_attr_hash for type info.
297
- def site_payload
298
- Drops::UnifiedPayloadDrop.new self
299
- end
300
- alias_method :to_liquid, :site_payload
301
-
302
- # Get the implementation class for the given Converter.
303
- # Returns the Converter instance implementing the given Converter.
304
- # klass - The Class of the Converter to fetch.
305
- def find_converter_instance(klass)
306
- @find_converter_instance ||= {}
307
- @find_converter_instance[klass] ||= begin
308
- converters.find { |converter| converter.instance_of?(klass) } || \
309
- raise("No Converters found for #{klass}")
310
- end
311
- end
312
-
313
- # klass - class or module containing the subclasses.
314
- # Returns array of instances of subclasses of parameter.
315
- # Create array of instances of the subclasses of the class or module
316
- # passed in as argument.
317
-
318
- def instantiate_subclasses(klass)
319
- klass.descendants.select { |c| !safe || c.safe }.tap do |result|
320
- result.sort!
321
- result.map! { |c| c.new(config) }
322
- end
323
- end
324
-
325
- # Warns the user if permanent links are relative to the parent
326
- # directory. As this is a deprecated function of Jekyll.
327
- #
328
- # Returns
329
- def relative_permalinks_are_deprecated
330
- if config["relative_permalinks"]
331
- Jekyll.logger.abort_with "Since v3.0, permalinks for pages" \
332
- " in subfolders must be relative to the" \
333
- " site source directory, not the parent" \
334
- " directory. Check https://jekyllrb.com/docs/upgrading/"\
335
- " for more info."
336
- end
337
- end
338
-
339
- # Get the to be written documents
340
- #
341
- # Returns an Array of Documents which should be written
342
- def docs_to_write
343
- documents.select(&:write?)
344
- end
345
-
346
- # Get all the documents
347
- #
348
- # Returns an Array of all Documents
349
- def documents
350
- collections.each_with_object(Set.new) do |(_, collection), set|
351
- set.merge(collection.docs).merge(collection.files)
352
- end.to_a
353
- end
354
-
355
- def each_site_file
356
- %w(pages static_files docs_to_write).each do |type|
357
- send(type).each do |item|
358
- yield item
359
- end
360
- end
361
- end
362
-
363
- # Returns the FrontmatterDefaults or creates a new FrontmatterDefaults
364
- # if it doesn't already exist.
365
- #
366
- # Returns The FrontmatterDefaults
367
- def frontmatter_defaults
368
- @frontmatter_defaults ||= FrontmatterDefaults.new(self)
369
- end
370
-
371
- # Whether to perform a full rebuild without incremental regeneration
372
- #
373
- # Returns a Boolean: true for a full rebuild, false for normal build
374
- def incremental?(override = {})
375
- override["incremental"] || config["incremental"]
376
- end
377
-
378
- # Returns the publisher or creates a new publisher if it doesn't
379
- # already exist.
380
- #
381
- # Returns The Publisher
382
- def publisher
383
- @publisher ||= Publisher.new(self)
384
- end
385
-
386
- # Public: Prefix a given path with the source directory.
387
- #
388
- # paths - (optional) path elements to a file or directory within the
389
- # source directory
390
- #
391
- # Returns a path which is prefixed with the source directory.
392
- def in_source_dir(*paths)
393
- paths.reduce(source) do |base, path|
394
- Jekyll.sanitized_path(base, path)
395
- end
396
- end
397
-
398
- # Public: Prefix a given path with the theme directory.
399
- #
400
- # paths - (optional) path elements to a file or directory within the
401
- # theme directory
402
- #
403
- # Returns a path which is prefixed with the theme root directory.
404
- def in_theme_dir(*paths)
405
- return nil unless theme
406
-
407
- paths.reduce(theme.root) do |base, path|
408
- Jekyll.sanitized_path(base, path)
409
- end
410
- end
411
-
412
- # Public: Prefix a given path with the destination directory.
413
- #
414
- # paths - (optional) path elements to a file or directory within the
415
- # destination directory
416
- #
417
- # Returns a path which is prefixed with the destination directory.
418
- def in_dest_dir(*paths)
419
- paths.reduce(dest) do |base, path|
420
- Jekyll.sanitized_path(base, path)
421
- end
422
- end
423
-
424
- # Public: Prefix a given path with the cache directory.
425
- #
426
- # paths - (optional) path elements to a file or directory within the
427
- # cache directory
428
- #
429
- # Returns a path which is prefixed with the cache directory.
430
- def in_cache_dir(*paths)
431
- paths.reduce(cache_dir) do |base, path|
432
- Jekyll.sanitized_path(base, path)
433
- end
434
- end
435
-
436
- # Public: The full path to the directory that houses all the collections registered
437
- # with the current site.
438
- #
439
- # Returns the source directory or the absolute path to the custom collections_dir
440
- def collections_path
441
- dir_str = config["collections_dir"]
442
- @collections_path ||= dir_str.empty? ? source : in_source_dir(dir_str)
443
- end
444
-
445
- # Public
446
- #
447
- # Returns the object as a debug String.
448
- def inspect
449
- "#<#{self.class} @source=#{@source}>"
450
- end
451
-
452
- private
453
-
454
- def load_theme_configuration(config)
455
- return config if config["ignore_theme_config"] == true
456
-
457
- theme_config_file = in_theme_dir("_config.yml")
458
- return config unless File.exist?(theme_config_file)
459
-
460
- # Bail out if the theme_config_file is a symlink file irrespective of safe mode
461
- return config if File.symlink?(theme_config_file)
462
-
463
- theme_config = SafeYAML.load_file(theme_config_file)
464
- return config unless theme_config.is_a?(Hash)
465
-
466
- Jekyll.logger.info "Theme Config file:", theme_config_file
467
-
468
- # theme_config should not be overriding Jekyll's defaults
469
- theme_config.delete_if { |key, _| Configuration::DEFAULTS.key?(key) }
470
-
471
- # Override theme_config with existing config and return the result.
472
- Utils.deep_merge_hashes(theme_config, config)
473
- end
474
-
475
- # Limits the current posts; removes the posts which exceed the limit_posts
476
- #
477
- # Returns nothing
478
- def limit_posts!
479
- if limit_posts.positive?
480
- limit = posts.docs.length < limit_posts ? posts.docs.length : limit_posts
481
- posts.docs = posts.docs[-limit, limit]
482
- end
483
- end
484
-
485
- # Returns the Cleaner or creates a new Cleaner if it doesn't
486
- # already exist.
487
- #
488
- # Returns The Cleaner
489
- def site_cleaner
490
- @site_cleaner ||= Cleaner.new(self)
491
- end
492
-
493
- # Disable Marshaling cache to disk in Safe Mode
494
- def configure_cache
495
- Jekyll::Cache.cache_dir = in_source_dir(config["cache_dir"], "Jekyll/Cache")
496
- Jekyll::Cache.disable_disk_cache! if safe || config["disable_disk_cache"]
497
- end
498
-
499
- def configure_plugins
500
- self.plugin_manager = Jekyll::PluginManager.new(self)
501
- self.plugins = plugin_manager.plugins_path
502
- end
503
-
504
- def configure_theme
505
- self.theme = nil
506
- return if config["theme"].nil?
507
-
508
- self.theme =
509
- if config["theme"].is_a?(String)
510
- Jekyll::Theme.new(config["theme"])
511
- else
512
- Jekyll.logger.warn "Theme:", "value of 'theme' in config should be " \
513
- "String to use gem-based themes, but got #{config["theme"].class}"
514
- nil
515
- end
516
- end
517
-
518
- def configure_include_paths
519
- @includes_load_paths = Array(in_source_dir(config["includes_dir"].to_s))
520
- @includes_load_paths << theme.includes_path if theme&.includes_path
521
- end
522
-
523
- def configure_file_read_opts
524
- self.file_read_opts = {}
525
- file_read_opts[:encoding] = config["encoding"] if config["encoding"]
526
- self.file_read_opts = Jekyll::Utils.merged_file_read_opts(self, {})
527
- end
528
-
529
- def render_docs(payload)
530
- collections.each_value do |collection|
531
- collection.docs.each do |document|
532
- render_regenerated(document, payload)
533
- end
534
- end
535
- end
536
-
537
- def render_pages(payload)
538
- pages.each do |page|
539
- render_regenerated(page, payload)
540
- end
541
- end
542
-
543
- def render_regenerated(document, payload)
544
- return unless regenerator.regenerate?(document)
545
-
546
- document.renderer.payload = payload
547
- document.output = document.renderer.run
548
- document.trigger_hooks(:post_render)
549
- end
550
- end
551
- end
1
+ # frozen_string_literal: true
2
+
3
+ module Jekyll
4
+ class Site
5
+ attr_reader :source, :dest, :cache_dir, :config
6
+ attr_accessor :layouts, :pages, :static_files, :drafts, :inclusions,
7
+ :exclude, :include, :lsi, :highlighter, :permalink_style,
8
+ :time, :future, :unpublished, :safe, :plugins, :limit_posts,
9
+ :show_drafts, :keep_files, :baseurl, :data, :file_read_opts,
10
+ :gems, :plugin_manager, :theme
11
+
12
+ attr_accessor :converters, :generators, :reader
13
+ attr_reader :regenerator, :liquid_renderer, :includes_load_paths, :filter_cache, :profiler
14
+
15
+ # Public: Initialize a new Site.
16
+ #
17
+ # config - A Hash containing site configuration details.
18
+ def initialize(config)
19
+ # Source and destination may not be changed after the site has been created.
20
+ @source = File.expand_path(config["source"]).freeze
21
+ @dest = File.expand_path(config["destination"]).freeze
22
+
23
+ self.config = config
24
+
25
+ @cache_dir = in_source_dir(config["cache_dir"])
26
+ @filter_cache = {}
27
+
28
+ @reader = Reader.new(self)
29
+ @profiler = Profiler.new(self)
30
+ @regenerator = Regenerator.new(self)
31
+ @liquid_renderer = LiquidRenderer.new(self)
32
+
33
+ Jekyll.sites << self
34
+
35
+ reset
36
+ setup
37
+
38
+ Jekyll::Hooks.trigger :site, :after_init, self
39
+ end
40
+
41
+ # Public: Set the site's configuration. This handles side-effects caused by
42
+ # changing values in the configuration.
43
+ #
44
+ # config - a Jekyll::Configuration, containing the new configuration.
45
+ #
46
+ # Returns the new configuration.
47
+ def config=(config)
48
+ @config = config.clone
49
+
50
+ %w(safe lsi highlighter baseurl exclude include future unpublished
51
+ show_drafts limit_posts keep_files).each do |opt|
52
+ send("#{opt}=", config[opt])
53
+ end
54
+
55
+ # keep using `gems` to avoid breaking change
56
+ self.gems = config["plugins"]
57
+
58
+ configure_cache
59
+ configure_plugins
60
+ configure_theme
61
+ configure_include_paths
62
+ configure_file_read_opts
63
+
64
+ self.permalink_style = config["permalink"].to_sym
65
+
66
+ # Read in a _config.yml from the current theme-gem at the very end.
67
+ @config = load_theme_configuration(config) if theme
68
+ @config
69
+ end
70
+
71
+ # Public: Read, process, and write this Site to output.
72
+ #
73
+ # Returns nothing.
74
+ def process
75
+ return profiler.profile_process if config["profile"]
76
+
77
+ reset
78
+ read
79
+ generate
80
+ render
81
+ cleanup
82
+ write
83
+ end
84
+
85
+ def print_stats
86
+ Jekyll.logger.info @liquid_renderer.stats_table
87
+ end
88
+
89
+ # rubocop:disable Metrics/AbcSize
90
+ # rubocop:disable Metrics/MethodLength
91
+ #
92
+ # Reset Site details.
93
+ #
94
+ # Returns nothing
95
+ def reset
96
+ self.time = if config["time"]
97
+ Utils.parse_date(config["time"].to_s, "Invalid time in _config.yml.")
98
+ else
99
+ Time.now
100
+ end
101
+ self.layouts = {}
102
+ self.inclusions = {}
103
+ self.pages = []
104
+ self.static_files = []
105
+ self.data = {}
106
+ @post_attr_hash = {}
107
+ @site_data = nil
108
+ @collections = nil
109
+ @documents = nil
110
+ @docs_to_write = nil
111
+ @regenerator.clear_cache
112
+ @liquid_renderer.reset
113
+ @site_cleaner = nil
114
+ frontmatter_defaults.reset
115
+
116
+ raise ArgumentError, "limit_posts must be a non-negative number" if limit_posts.negative?
117
+
118
+ Jekyll::Cache.clear_if_config_changed config
119
+ Jekyll::Hooks.trigger :site, :after_reset, self
120
+ nil
121
+ end
122
+ # rubocop:enable Metrics/MethodLength
123
+ # rubocop:enable Metrics/AbcSize
124
+
125
+ # Load necessary libraries, plugins, converters, and generators.
126
+ #
127
+ # Returns nothing.
128
+ def setup
129
+ ensure_not_in_dest
130
+
131
+ plugin_manager.conscientious_require
132
+
133
+ self.converters = instantiate_subclasses(Jekyll::Converter)
134
+ self.generators = instantiate_subclasses(Jekyll::Generator)
135
+ end
136
+
137
+ # Check that the destination dir isn't the source dir or a directory
138
+ # parent to the source dir.
139
+ def ensure_not_in_dest
140
+ dest_pathname = Pathname.new(dest)
141
+ Pathname.new(source).ascend do |path|
142
+ if path == dest_pathname
143
+ raise Errors::FatalException,
144
+ "Destination directory cannot be or contain the Source directory."
145
+ end
146
+ end
147
+ end
148
+
149
+ # The list of collections and their corresponding Jekyll::Collection instances.
150
+ # If config['collections'] is set, a new instance is created
151
+ # for each item in the collection, a new hash is returned otherwise.
152
+ #
153
+ # Returns a Hash containing collection name-to-instance pairs.
154
+ def collections
155
+ @collections ||= collection_names.each_with_object({}) do |name, hsh|
156
+ hsh[name] = Jekyll::Collection.new(self, name)
157
+ end
158
+ end
159
+
160
+ # The list of collection names.
161
+ #
162
+ # Returns an array of collection names from the configuration,
163
+ # or an empty array if the `collections` key is not set.
164
+ def collection_names
165
+ case config["collections"]
166
+ when Hash
167
+ config["collections"].keys
168
+ when Array
169
+ config["collections"]
170
+ when nil
171
+ []
172
+ else
173
+ raise ArgumentError, "Your `collections` key must be a hash or an array."
174
+ end
175
+ end
176
+
177
+ # Read Site data from disk and load it into internal data structures.
178
+ #
179
+ # Returns nothing.
180
+ def read
181
+ reader.read
182
+ limit_posts!
183
+ Jekyll::Hooks.trigger :site, :post_read, self
184
+ nil
185
+ end
186
+
187
+ # Run each of the Generators.
188
+ #
189
+ # Returns nothing.
190
+ def generate
191
+ generators.each do |generator|
192
+ start = Time.now
193
+ generator.generate(self)
194
+ Jekyll.logger.debug "Generating:",
195
+ "#{generator.class} finished in #{Time.now - start} seconds."
196
+ end
197
+ nil
198
+ end
199
+
200
+ # Render the site to the destination.
201
+ #
202
+ # Returns nothing.
203
+ def render
204
+ relative_permalinks_are_deprecated
205
+
206
+ payload = site_payload
207
+
208
+ Jekyll::Hooks.trigger :site, :pre_render, self, payload
209
+
210
+ render_docs(payload)
211
+ render_pages(payload)
212
+
213
+ Jekyll::Hooks.trigger :site, :post_render, self, payload
214
+ nil
215
+ end
216
+
217
+ # Remove orphaned files and empty directories in destination.
218
+ #
219
+ # Returns nothing.
220
+ def cleanup
221
+ site_cleaner.cleanup!
222
+ nil
223
+ end
224
+
225
+ # Write static files, pages, and posts.
226
+ #
227
+ # Returns nothing.
228
+ def write
229
+ Jekyll::Commands::Doctor.conflicting_urls(self)
230
+ each_site_file do |item|
231
+ item.write(dest) if regenerator.regenerate?(item)
232
+ end
233
+ regenerator.write_metadata
234
+ Jekyll::Hooks.trigger :site, :post_write, self
235
+ nil
236
+ end
237
+
238
+ def posts
239
+ collections["posts"] ||= Collection.new(self, "posts")
240
+ end
241
+
242
+ # Construct a Hash of Posts indexed by the specified Post attribute.
243
+ #
244
+ # post_attr - The String name of the Post attribute.
245
+ #
246
+ # Examples
247
+ #
248
+ # post_attr_hash('categories')
249
+ # # => { 'tech' => [<Post A>, <Post B>],
250
+ # # 'ruby' => [<Post B>] }
251
+ #
252
+ # Returns the Hash: { attr => posts } where
253
+ # attr - One of the values for the requested attribute.
254
+ # posts - The Array of Posts with the given attr value.
255
+ def post_attr_hash(post_attr)
256
+ # Build a hash map based on the specified post attribute ( post attr =>
257
+ # array of posts ) then sort each array in reverse order.
258
+ @post_attr_hash[post_attr] ||= begin
259
+ hash = Hash.new { |h, key| h[key] = [] }
260
+ posts.docs.each do |p|
261
+ p.data[post_attr]&.each { |t| hash[t] << p }
262
+ end
263
+ hash.each_value { |posts| posts.sort!.reverse! }
264
+ hash
265
+ end
266
+ end
267
+
268
+ def tags
269
+ post_attr_hash("tags")
270
+ end
271
+
272
+ def categories
273
+ post_attr_hash("categories")
274
+ end
275
+
276
+ # Prepare site data for site payload. The method maintains backward compatibility
277
+ # if the key 'data' is already used in _config.yml.
278
+ #
279
+ # Returns the Hash to be hooked to site.data.
280
+ def site_data
281
+ @site_data ||= (config["data"] || data)
282
+ end
283
+
284
+ # The Hash payload containing site-wide data.
285
+ #
286
+ # Returns the Hash: { "site" => data } where data is a Hash with keys:
287
+ # "time" - The Time as specified in the configuration or the
288
+ # current time if none was specified.
289
+ # "posts" - The Array of Posts, sorted chronologically by post date
290
+ # and then title.
291
+ # "pages" - The Array of all Pages.
292
+ # "html_pages" - The Array of HTML Pages.
293
+ # "categories" - The Hash of category values and Posts.
294
+ # See Site#post_attr_hash for type info.
295
+ # "tags" - The Hash of tag values and Posts.
296
+ # See Site#post_attr_hash for type info.
297
+ def site_payload
298
+ Drops::UnifiedPayloadDrop.new self
299
+ end
300
+ alias_method :to_liquid, :site_payload
301
+
302
+ # Get the implementation class for the given Converter.
303
+ # Returns the Converter instance implementing the given Converter.
304
+ # klass - The Class of the Converter to fetch.
305
+ def find_converter_instance(klass)
306
+ @find_converter_instance ||= {}
307
+ @find_converter_instance[klass] ||= begin
308
+ converters.find { |converter| converter.instance_of?(klass) } || \
309
+ raise("No Converters found for #{klass}")
310
+ end
311
+ end
312
+
313
+ # klass - class or module containing the subclasses.
314
+ # Returns array of instances of subclasses of parameter.
315
+ # Create array of instances of the subclasses of the class or module
316
+ # passed in as argument.
317
+
318
+ def instantiate_subclasses(klass)
319
+ klass.descendants.select { |c| !safe || c.safe }.tap do |result|
320
+ result.sort!
321
+ result.map! { |c| c.new(config) }
322
+ end
323
+ end
324
+
325
+ # Warns the user if permanent links are relative to the parent
326
+ # directory. As this is a deprecated function of Jekyll.
327
+ #
328
+ # Returns
329
+ def relative_permalinks_are_deprecated
330
+ if config["relative_permalinks"]
331
+ Jekyll.logger.abort_with "Since v3.0, permalinks for pages" \
332
+ " in subfolders must be relative to the" \
333
+ " site source directory, not the parent" \
334
+ " directory. Check https://jekyllrb.com/docs/upgrading/"\
335
+ " for more info."
336
+ end
337
+ end
338
+
339
+ # Get the to be written documents
340
+ #
341
+ # Returns an Array of Documents which should be written
342
+ def docs_to_write
343
+ documents.select(&:write?)
344
+ end
345
+
346
+ # Get all the documents
347
+ #
348
+ # Returns an Array of all Documents
349
+ def documents
350
+ collections.each_with_object(Set.new) do |(_, collection), set|
351
+ set.merge(collection.docs).merge(collection.files)
352
+ end.to_a
353
+ end
354
+
355
+ def each_site_file
356
+ %w(pages static_files docs_to_write).each do |type|
357
+ send(type).each do |item|
358
+ yield item
359
+ end
360
+ end
361
+ end
362
+
363
+ # Returns the FrontmatterDefaults or creates a new FrontmatterDefaults
364
+ # if it doesn't already exist.
365
+ #
366
+ # Returns The FrontmatterDefaults
367
+ def frontmatter_defaults
368
+ @frontmatter_defaults ||= FrontmatterDefaults.new(self)
369
+ end
370
+
371
+ # Whether to perform a full rebuild without incremental regeneration
372
+ #
373
+ # Returns a Boolean: true for a full rebuild, false for normal build
374
+ def incremental?(override = {})
375
+ override["incremental"] || config["incremental"]
376
+ end
377
+
378
+ # Returns the publisher or creates a new publisher if it doesn't
379
+ # already exist.
380
+ #
381
+ # Returns The Publisher
382
+ def publisher
383
+ @publisher ||= Publisher.new(self)
384
+ end
385
+
386
+ # Public: Prefix a given path with the source directory.
387
+ #
388
+ # paths - (optional) path elements to a file or directory within the
389
+ # source directory
390
+ #
391
+ # Returns a path which is prefixed with the source directory.
392
+ def in_source_dir(*paths)
393
+ paths.reduce(source) do |base, path|
394
+ Jekyll.sanitized_path(base, path)
395
+ end
396
+ end
397
+
398
+ # Public: Prefix a given path with the theme directory.
399
+ #
400
+ # paths - (optional) path elements to a file or directory within the
401
+ # theme directory
402
+ #
403
+ # Returns a path which is prefixed with the theme root directory.
404
+ def in_theme_dir(*paths)
405
+ return nil unless theme
406
+
407
+ paths.reduce(theme.root) do |base, path|
408
+ Jekyll.sanitized_path(base, path)
409
+ end
410
+ end
411
+
412
+ # Public: Prefix a given path with the destination directory.
413
+ #
414
+ # paths - (optional) path elements to a file or directory within the
415
+ # destination directory
416
+ #
417
+ # Returns a path which is prefixed with the destination directory.
418
+ def in_dest_dir(*paths)
419
+ paths.reduce(dest) do |base, path|
420
+ Jekyll.sanitized_path(base, path)
421
+ end
422
+ end
423
+
424
+ # Public: Prefix a given path with the cache directory.
425
+ #
426
+ # paths - (optional) path elements to a file or directory within the
427
+ # cache directory
428
+ #
429
+ # Returns a path which is prefixed with the cache directory.
430
+ def in_cache_dir(*paths)
431
+ paths.reduce(cache_dir) do |base, path|
432
+ Jekyll.sanitized_path(base, path)
433
+ end
434
+ end
435
+
436
+ # Public: The full path to the directory that houses all the collections registered
437
+ # with the current site.
438
+ #
439
+ # Returns the source directory or the absolute path to the custom collections_dir
440
+ def collections_path
441
+ dir_str = config["collections_dir"]
442
+ @collections_path ||= dir_str.empty? ? source : in_source_dir(dir_str)
443
+ end
444
+
445
+ # Public
446
+ #
447
+ # Returns the object as a debug String.
448
+ def inspect
449
+ "#<#{self.class} @source=#{@source}>"
450
+ end
451
+
452
+ private
453
+
454
+ def load_theme_configuration(config)
455
+ return config if config["ignore_theme_config"] == true
456
+
457
+ theme_config_file = in_theme_dir("_config.yml")
458
+ return config unless File.exist?(theme_config_file)
459
+
460
+ # Bail out if the theme_config_file is a symlink file irrespective of safe mode
461
+ return config if File.symlink?(theme_config_file)
462
+
463
+ theme_config = SafeYAML.load_file(theme_config_file)
464
+ return config unless theme_config.is_a?(Hash)
465
+
466
+ Jekyll.logger.info "Theme Config file:", theme_config_file
467
+
468
+ # theme_config should not be overriding Jekyll's defaults
469
+ theme_config.delete_if { |key, _| Configuration::DEFAULTS.key?(key) }
470
+
471
+ # Override theme_config with existing config and return the result.
472
+ Utils.deep_merge_hashes(theme_config, config)
473
+ end
474
+
475
+ # Limits the current posts; removes the posts which exceed the limit_posts
476
+ #
477
+ # Returns nothing
478
+ def limit_posts!
479
+ if limit_posts.positive?
480
+ limit = posts.docs.length < limit_posts ? posts.docs.length : limit_posts
481
+ posts.docs = posts.docs[-limit, limit]
482
+ end
483
+ end
484
+
485
+ # Returns the Cleaner or creates a new Cleaner if it doesn't
486
+ # already exist.
487
+ #
488
+ # Returns The Cleaner
489
+ def site_cleaner
490
+ @site_cleaner ||= Cleaner.new(self)
491
+ end
492
+
493
+ # Disable Marshaling cache to disk in Safe Mode
494
+ def configure_cache
495
+ Jekyll::Cache.cache_dir = in_source_dir(config["cache_dir"], "Jekyll/Cache")
496
+ Jekyll::Cache.disable_disk_cache! if safe || config["disable_disk_cache"]
497
+ end
498
+
499
+ def configure_plugins
500
+ self.plugin_manager = Jekyll::PluginManager.new(self)
501
+ self.plugins = plugin_manager.plugins_path
502
+ end
503
+
504
+ def configure_theme
505
+ self.theme = nil
506
+ return if config["theme"].nil?
507
+
508
+ self.theme =
509
+ if config["theme"].is_a?(String)
510
+ Jekyll::Theme.new(config["theme"])
511
+ else
512
+ Jekyll.logger.warn "Theme:", "value of 'theme' in config should be " \
513
+ "String to use gem-based themes, but got #{config["theme"].class}"
514
+ nil
515
+ end
516
+ end
517
+
518
+ def configure_include_paths
519
+ @includes_load_paths = Array(in_source_dir(config["includes_dir"].to_s))
520
+ @includes_load_paths << theme.includes_path if theme&.includes_path
521
+ end
522
+
523
+ def configure_file_read_opts
524
+ self.file_read_opts = {}
525
+ file_read_opts[:encoding] = config["encoding"] if config["encoding"]
526
+ self.file_read_opts = Jekyll::Utils.merged_file_read_opts(self, {})
527
+ end
528
+
529
+ def render_docs(payload)
530
+ collections.each_value do |collection|
531
+ collection.docs.each do |document|
532
+ render_regenerated(document, payload)
533
+ end
534
+ end
535
+ end
536
+
537
+ def render_pages(payload)
538
+ pages.each do |page|
539
+ render_regenerated(page, payload)
540
+ end
541
+ end
542
+
543
+ def render_regenerated(document, payload)
544
+ return unless regenerator.regenerate?(document)
545
+
546
+ document.renderer.payload = payload
547
+ document.output = document.renderer.run
548
+ document.trigger_hooks(:post_render)
549
+ end
550
+ end
551
+ end