jekyll 4.2.1 → 4.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (128) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +474 -350
  3. data/LICENSE +21 -21
  4. data/README.markdown +83 -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 +186 -190
  12. data/lib/jekyll/cleaner.rb +111 -111
  13. data/lib/jekyll/collection.rb +310 -309
  14. data/lib/jekyll/command.rb +105 -105
  15. data/lib/jekyll/commands/build.rb +82 -93
  16. data/lib/jekyll/commands/clean.rb +44 -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 +168 -169
  20. data/lib/jekyll/commands/new_theme.rb +39 -40
  21. data/lib/jekyll/commands/serve/live_reload_reactor.rb +119 -122
  22. data/lib/jekyll/commands/serve/livereload_assets/livereload.js +1183 -1183
  23. data/lib/jekyll/commands/serve/mime_types_charset.json +71 -0
  24. data/lib/jekyll/commands/serve/servlet.rb +206 -202
  25. data/lib/jekyll/commands/serve/websockets.rb +81 -81
  26. data/lib/jekyll/commands/serve.rb +367 -362
  27. data/lib/jekyll/configuration.rb +313 -313
  28. data/lib/jekyll/converter.rb +54 -54
  29. data/lib/jekyll/converters/identity.rb +41 -41
  30. data/lib/jekyll/converters/markdown/kramdown_parser.rb +197 -199
  31. data/lib/jekyll/converters/markdown.rb +113 -113
  32. data/lib/jekyll/converters/smartypants.rb +70 -70
  33. data/lib/jekyll/convertible.rb +257 -257
  34. data/lib/jekyll/data_entry.rb +83 -0
  35. data/lib/jekyll/data_hash.rb +61 -0
  36. data/lib/jekyll/deprecator.rb +50 -50
  37. data/lib/jekyll/document.rb +543 -544
  38. data/lib/jekyll/drops/collection_drop.rb +20 -20
  39. data/lib/jekyll/drops/document_drop.rb +71 -70
  40. data/lib/jekyll/drops/drop.rb +293 -293
  41. data/lib/jekyll/drops/excerpt_drop.rb +23 -19
  42. data/lib/jekyll/drops/jekyll_drop.rb +32 -32
  43. data/lib/jekyll/drops/site_drop.rb +71 -66
  44. data/lib/jekyll/drops/static_file_drop.rb +14 -14
  45. data/lib/jekyll/drops/theme_drop.rb +36 -0
  46. data/lib/jekyll/drops/unified_payload_drop.rb +30 -26
  47. data/lib/jekyll/drops/url_drop.rb +140 -140
  48. data/lib/jekyll/entry_filter.rb +117 -121
  49. data/lib/jekyll/errors.rb +20 -20
  50. data/lib/jekyll/excerpt.rb +200 -201
  51. data/lib/jekyll/external.rb +75 -79
  52. data/lib/jekyll/filters/date_filters.rb +110 -110
  53. data/lib/jekyll/filters/grouping_filters.rb +64 -64
  54. data/lib/jekyll/filters/url_filters.rb +98 -98
  55. data/lib/jekyll/filters.rb +532 -535
  56. data/lib/jekyll/frontmatter_defaults.rb +238 -240
  57. data/lib/jekyll/generator.rb +5 -5
  58. data/lib/jekyll/hooks.rb +107 -107
  59. data/lib/jekyll/inclusion.rb +32 -32
  60. data/lib/jekyll/layout.rb +55 -67
  61. data/lib/jekyll/liquid_extensions.rb +22 -22
  62. data/lib/jekyll/liquid_renderer/file.rb +77 -77
  63. data/lib/jekyll/liquid_renderer/table.rb +55 -55
  64. data/lib/jekyll/liquid_renderer.rb +80 -80
  65. data/lib/jekyll/log_adapter.rb +151 -151
  66. data/lib/jekyll/mime.types +939 -866
  67. data/lib/jekyll/page.rb +215 -217
  68. data/lib/jekyll/page_excerpt.rb +25 -25
  69. data/lib/jekyll/page_without_a_file.rb +14 -14
  70. data/lib/jekyll/path_manager.rb +74 -74
  71. data/lib/jekyll/plugin.rb +92 -92
  72. data/lib/jekyll/plugin_manager.rb +123 -115
  73. data/lib/jekyll/profiler.rb +55 -58
  74. data/lib/jekyll/publisher.rb +23 -23
  75. data/lib/jekyll/reader.rb +209 -192
  76. data/lib/jekyll/readers/collection_reader.rb +23 -23
  77. data/lib/jekyll/readers/data_reader.rb +116 -79
  78. data/lib/jekyll/readers/layout_reader.rb +62 -62
  79. data/lib/jekyll/readers/page_reader.rb +25 -25
  80. data/lib/jekyll/readers/post_reader.rb +85 -85
  81. data/lib/jekyll/readers/static_file_reader.rb +25 -25
  82. data/lib/jekyll/readers/theme_assets_reader.rb +52 -52
  83. data/lib/jekyll/regenerator.rb +195 -195
  84. data/lib/jekyll/related_posts.rb +52 -52
  85. data/lib/jekyll/renderer.rb +263 -265
  86. data/lib/jekyll/site.rb +582 -551
  87. data/lib/jekyll/static_file.rb +205 -208
  88. data/lib/jekyll/stevenson.rb +60 -60
  89. data/lib/jekyll/tags/highlight.rb +114 -110
  90. data/lib/jekyll/tags/include.rb +275 -275
  91. data/lib/jekyll/tags/link.rb +42 -42
  92. data/lib/jekyll/tags/post_url.rb +106 -106
  93. data/lib/jekyll/theme.rb +90 -86
  94. data/lib/jekyll/theme_builder.rb +121 -121
  95. data/lib/jekyll/url.rb +167 -167
  96. data/lib/jekyll/utils/ansi.rb +57 -57
  97. data/lib/jekyll/utils/exec.rb +26 -26
  98. data/lib/jekyll/utils/internet.rb +37 -37
  99. data/lib/jekyll/utils/platforms.rb +67 -67
  100. data/lib/jekyll/utils/thread_event.rb +31 -31
  101. data/lib/jekyll/utils/win_tz.rb +46 -75
  102. data/lib/jekyll/utils.rb +378 -367
  103. data/lib/jekyll/version.rb +5 -5
  104. data/lib/jekyll.rb +197 -195
  105. data/lib/site_template/.gitignore +5 -5
  106. data/lib/site_template/404.html +25 -25
  107. data/lib/site_template/_config.yml +55 -55
  108. data/lib/site_template/_posts/0000-00-00-welcome-to-jekyll.markdown.erb +29 -29
  109. data/lib/site_template/about.markdown +18 -18
  110. data/lib/site_template/index.markdown +6 -6
  111. data/lib/theme_template/CODE_OF_CONDUCT.md.erb +74 -74
  112. data/lib/theme_template/Gemfile +4 -4
  113. data/lib/theme_template/LICENSE.txt.erb +21 -21
  114. data/lib/theme_template/README.md.erb +50 -52
  115. data/lib/theme_template/_layouts/default.html +1 -1
  116. data/lib/theme_template/_layouts/page.html +5 -5
  117. data/lib/theme_template/_layouts/post.html +5 -5
  118. data/lib/theme_template/example/_config.yml.erb +1 -1
  119. data/lib/theme_template/example/_post.md +12 -12
  120. data/lib/theme_template/example/index.html +14 -14
  121. data/lib/theme_template/example/style.scss +7 -7
  122. data/lib/theme_template/gitignore.erb +6 -6
  123. data/lib/theme_template/theme.gemspec.erb +16 -16
  124. data/rubocop/jekyll/assert_equal_literal_actual.rb +149 -149
  125. data/rubocop/jekyll/no_p_allowed.rb +23 -23
  126. data/rubocop/jekyll/no_puts_allowed.rb +23 -23
  127. data/rubocop/jekyll.rb +5 -5
  128. metadata +62 -14
data/lib/jekyll/site.rb CHANGED
@@ -1,551 +1,582 @@
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_accessor :baseurl, :converters, :data, :drafts, :exclude,
6
+ :file_read_opts, :future, :gems, :generators, :highlighter,
7
+ :include, :inclusions, :keep_files, :layouts, :limit_posts,
8
+ :lsi, :pages, :permalink_style, :plugin_manager, :plugins,
9
+ :reader, :safe, :show_drafts, :static_files, :theme, :time,
10
+ :unpublished
11
+
12
+ attr_reader :cache_dir, :config, :dest, :filter_cache, :includes_load_paths,
13
+ :liquid_renderer, :profiler, :regenerator, :source
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] ||= converters.find do |converter|
308
+ converter.instance_of?(klass)
309
+ end || \
310
+ raise("No Converters found for #{klass}")
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 the to be written static files
347
+ #
348
+ # Returns an Array of StaticFiles which should be written
349
+ def static_files_to_write
350
+ static_files.select(&:write?)
351
+ end
352
+
353
+ # Get all the documents
354
+ #
355
+ # Returns an Array of all Documents
356
+ def documents
357
+ collections.each_with_object(Set.new) do |(_, collection), set|
358
+ set.merge(collection.docs).merge(collection.files)
359
+ end.to_a
360
+ end
361
+
362
+ def each_site_file
363
+ seen_files = []
364
+ %w(pages static_files_to_write docs_to_write).each do |type|
365
+ send(type).each do |item|
366
+ next if seen_files.include?(item)
367
+
368
+ yield item
369
+ seen_files << item
370
+ end
371
+ end
372
+ end
373
+
374
+ # Returns the FrontmatterDefaults or creates a new FrontmatterDefaults
375
+ # if it doesn't already exist.
376
+ #
377
+ # Returns The FrontmatterDefaults
378
+ def frontmatter_defaults
379
+ @frontmatter_defaults ||= FrontmatterDefaults.new(self)
380
+ end
381
+
382
+ # Whether to perform a full rebuild without incremental regeneration
383
+ #
384
+ # Returns a Boolean: true for a full rebuild, false for normal build
385
+ def incremental?(override = {})
386
+ override["incremental"] || config["incremental"]
387
+ end
388
+
389
+ # Returns the publisher or creates a new publisher if it doesn't
390
+ # already exist.
391
+ #
392
+ # Returns The Publisher
393
+ def publisher
394
+ @publisher ||= Publisher.new(self)
395
+ end
396
+
397
+ # Public: Prefix a given path with the source directory.
398
+ #
399
+ # paths - (optional) path elements to a file or directory within the
400
+ # source directory
401
+ #
402
+ # Returns a path which is prefixed with the source directory.
403
+ def in_source_dir(*paths)
404
+ paths.reduce(source) do |base, path|
405
+ Jekyll.sanitized_path(base, path)
406
+ end
407
+ end
408
+
409
+ # Public: Prefix a given path with the theme directory.
410
+ #
411
+ # paths - (optional) path elements to a file or directory within the
412
+ # theme directory
413
+ #
414
+ # Returns a path which is prefixed with the theme root directory.
415
+ def in_theme_dir(*paths)
416
+ return nil unless theme
417
+
418
+ paths.reduce(theme.root) do |base, path|
419
+ Jekyll.sanitized_path(base, path)
420
+ end
421
+ end
422
+
423
+ # Public: Prefix a given path with the destination directory.
424
+ #
425
+ # paths - (optional) path elements to a file or directory within the
426
+ # destination directory
427
+ #
428
+ # Returns a path which is prefixed with the destination directory.
429
+ def in_dest_dir(*paths)
430
+ paths.reduce(dest) do |base, path|
431
+ Jekyll.sanitized_path(base, path)
432
+ end
433
+ end
434
+
435
+ # Public: Prefix a given path with the cache directory.
436
+ #
437
+ # paths - (optional) path elements to a file or directory within the
438
+ # cache directory
439
+ #
440
+ # Returns a path which is prefixed with the cache directory.
441
+ def in_cache_dir(*paths)
442
+ paths.reduce(cache_dir) do |base, path|
443
+ Jekyll.sanitized_path(base, path)
444
+ end
445
+ end
446
+
447
+ # Public: The full path to the directory that houses all the collections registered
448
+ # with the current site.
449
+ #
450
+ # Returns the source directory or the absolute path to the custom collections_dir
451
+ def collections_path
452
+ dir_str = config["collections_dir"]
453
+ @collections_path ||= dir_str.empty? ? source : in_source_dir(dir_str)
454
+ end
455
+
456
+ # Public
457
+ #
458
+ # Returns the object as a debug String.
459
+ def inspect
460
+ "#<#{self.class} @source=#{@source}>"
461
+ end
462
+
463
+ private
464
+
465
+ def load_theme_configuration(config)
466
+ return config if config["ignore_theme_config"] == true
467
+
468
+ theme_config_file = in_theme_dir("_config.yml")
469
+ return config unless File.exist?(theme_config_file)
470
+
471
+ # Bail out if the theme_config_file is a symlink file irrespective of safe mode
472
+ return config if File.symlink?(theme_config_file)
473
+
474
+ theme_config = SafeYAML.load_file(theme_config_file)
475
+ return config unless theme_config.is_a?(Hash)
476
+
477
+ Jekyll.logger.info "Theme Config file:", theme_config_file
478
+
479
+ # theme_config should not be overriding Jekyll's defaults
480
+ theme_config.delete_if { |key, _| Configuration::DEFAULTS.key?(key) }
481
+
482
+ # Override theme_config with existing config and return the result.
483
+ # Additionally ensure we return a `Jekyll::Configuration` instance instead of a Hash.
484
+ Utils.deep_merge_hashes(theme_config, config)
485
+ .each_with_object(Jekyll::Configuration.new) do |(key, value), conf|
486
+ conf[key] = value
487
+ end
488
+ end
489
+
490
+ # Limits the current posts; removes the posts which exceed the limit_posts
491
+ #
492
+ # Returns nothing
493
+ def limit_posts!
494
+ if limit_posts.positive?
495
+ limit = posts.docs.length < limit_posts ? posts.docs.length : limit_posts
496
+ posts.docs = posts.docs[-limit, limit]
497
+ end
498
+ end
499
+
500
+ # Returns the Cleaner or creates a new Cleaner if it doesn't
501
+ # already exist.
502
+ #
503
+ # Returns The Cleaner
504
+ def site_cleaner
505
+ @site_cleaner ||= Cleaner.new(self)
506
+ end
507
+
508
+ def hide_cache_dir_from_git
509
+ @cache_gitignore_path ||= in_source_dir(config["cache_dir"], ".gitignore")
510
+ return if File.exist?(@cache_gitignore_path)
511
+
512
+ cache_dir_path = in_source_dir(config["cache_dir"])
513
+ FileUtils.mkdir_p(cache_dir_path) unless File.directory?(cache_dir_path)
514
+
515
+ File.open(@cache_gitignore_path, "wb") do |file|
516
+ file.puts("# ignore everything in this directory\n*")
517
+ end
518
+ end
519
+
520
+ # Disable Marshaling cache to disk in Safe Mode
521
+ def configure_cache
522
+ Jekyll::Cache.cache_dir = in_source_dir(config["cache_dir"], "Jekyll/Cache")
523
+ if safe || config["disable_disk_cache"]
524
+ Jekyll::Cache.disable_disk_cache!
525
+ else
526
+ hide_cache_dir_from_git
527
+ end
528
+ end
529
+
530
+ def configure_plugins
531
+ self.plugin_manager = Jekyll::PluginManager.new(self)
532
+ self.plugins = plugin_manager.plugins_path
533
+ end
534
+
535
+ def configure_theme
536
+ self.theme = nil
537
+ return if config["theme"].nil?
538
+
539
+ self.theme =
540
+ if config["theme"].is_a?(String)
541
+ Jekyll::Theme.new(config["theme"])
542
+ else
543
+ Jekyll.logger.warn "Theme:", "value of 'theme' in config should be String to use " \
544
+ "gem-based themes, but got #{config["theme"].class}"
545
+ nil
546
+ end
547
+ end
548
+
549
+ def configure_include_paths
550
+ @includes_load_paths = Array(in_source_dir(config["includes_dir"].to_s))
551
+ @includes_load_paths << theme.includes_path if theme&.includes_path
552
+ end
553
+
554
+ def configure_file_read_opts
555
+ self.file_read_opts = {}
556
+ file_read_opts[:encoding] = config["encoding"] if config["encoding"]
557
+ self.file_read_opts = Jekyll::Utils.merged_file_read_opts(self, {})
558
+ end
559
+
560
+ def render_docs(payload)
561
+ collections.each_value do |collection|
562
+ collection.docs.each do |document|
563
+ render_regenerated(document, payload)
564
+ end
565
+ end
566
+ end
567
+
568
+ def render_pages(payload)
569
+ pages.each do |page|
570
+ render_regenerated(page, payload)
571
+ end
572
+ end
573
+
574
+ def render_regenerated(document, payload)
575
+ return unless regenerator.regenerate?(document)
576
+
577
+ document.renderer.payload = payload
578
+ document.output = document.renderer.run
579
+ document.trigger_hooks(:post_render)
580
+ end
581
+ end
582
+ end