jekyll 4.2.1 → 4.3.2

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