jekyll 4.2.0 → 4.2.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (124) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +350 -347
  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 +169 -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 -365
  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 -260
  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 -270
  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