jekyll 4.2.1 → 4.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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
@@ -1,309 +1,310 @@
1
- # frozen_string_literal: true
2
-
3
- module Jekyll
4
- class Collection
5
- attr_reader :site, :label, :metadata
6
- attr_writer :docs
7
-
8
- # Create a new Collection.
9
- #
10
- # site - the site to which this collection belongs.
11
- # label - the name of the collection
12
- #
13
- # Returns nothing.
14
- def initialize(site, label)
15
- @site = site
16
- @label = sanitize_label(label)
17
- @metadata = extract_metadata
18
- end
19
-
20
- # Fetch the Documents in this collection.
21
- # Defaults to an empty array if no documents have been read in.
22
- #
23
- # Returns an array of Jekyll::Document objects.
24
- def docs
25
- @docs ||= []
26
- end
27
-
28
- # Override of normal respond_to? to match method_missing's logic for
29
- # looking in @data.
30
- def respond_to_missing?(method, include_private = false)
31
- docs.respond_to?(method.to_sym, include_private) || super
32
- end
33
-
34
- # Override of method_missing to check in @data for the key.
35
- def method_missing(method, *args, &blck)
36
- if docs.respond_to?(method.to_sym)
37
- Jekyll.logger.warn "Deprecation:",
38
- "#{label}.#{method} should be changed to #{label}.docs.#{method}."
39
- Jekyll.logger.warn "", "Called by #{caller(0..0)}."
40
- docs.public_send(method.to_sym, *args, &blck)
41
- else
42
- super
43
- end
44
- end
45
-
46
- # Fetch the static files in this collection.
47
- # Defaults to an empty array if no static files have been read in.
48
- #
49
- # Returns an array of Jekyll::StaticFile objects.
50
- def files
51
- @files ||= []
52
- end
53
-
54
- # Read the allowed documents into the collection's array of docs.
55
- #
56
- # Returns the sorted array of docs.
57
- def read
58
- filtered_entries.each do |file_path|
59
- full_path = collection_dir(file_path)
60
- next if File.directory?(full_path)
61
-
62
- if Utils.has_yaml_header? full_path
63
- read_document(full_path)
64
- else
65
- read_static_file(file_path, full_path)
66
- end
67
- end
68
- sort_docs!
69
- end
70
-
71
- # All the entries in this collection.
72
- #
73
- # Returns an Array of file paths to the documents in this collection
74
- # relative to the collection's directory
75
- def entries
76
- return [] unless exists?
77
-
78
- @entries ||= begin
79
- collection_dir_slash = "#{collection_dir}/"
80
- Utils.safe_glob(collection_dir, ["**", "*"], File::FNM_DOTMATCH).map do |entry|
81
- entry[collection_dir_slash] = ""
82
- entry
83
- end
84
- end
85
- end
86
-
87
- # Filtered version of the entries in this collection.
88
- # See `Jekyll::EntryFilter#filter` for more information.
89
- #
90
- # Returns a list of filtered entry paths.
91
- def filtered_entries
92
- return [] unless exists?
93
-
94
- @filtered_entries ||=
95
- Dir.chdir(directory) do
96
- entry_filter.filter(entries).reject do |f|
97
- path = collection_dir(f)
98
- File.directory?(path) || entry_filter.symlink?(f)
99
- end
100
- end
101
- end
102
-
103
- # The directory for this Collection, relative to the site source or the directory
104
- # containing the collection.
105
- #
106
- # Returns a String containing the directory name where the collection
107
- # is stored on the filesystem.
108
- def relative_directory
109
- @relative_directory ||= "_#{label}"
110
- end
111
-
112
- # The full path to the directory containing the collection.
113
- #
114
- # Returns a String containing th directory name where the collection
115
- # is stored on the filesystem.
116
- def directory
117
- @directory ||= site.in_source_dir(
118
- File.join(container, relative_directory)
119
- )
120
- end
121
-
122
- # The full path to the directory containing the collection, with
123
- # optional subpaths.
124
- #
125
- # *files - (optional) any other path pieces relative to the
126
- # directory to append to the path
127
- #
128
- # Returns a String containing th directory name where the collection
129
- # is stored on the filesystem.
130
- def collection_dir(*files)
131
- return directory if files.empty?
132
-
133
- site.in_source_dir(container, relative_directory, *files)
134
- end
135
-
136
- # Checks whether the directory "exists" for this collection.
137
- # The directory must exist on the filesystem and must not be a symlink
138
- # if in safe mode.
139
- #
140
- # Returns false if the directory doesn't exist or if it's a symlink
141
- # and we're in safe mode.
142
- def exists?
143
- File.directory?(directory) && !entry_filter.symlink?(directory)
144
- end
145
-
146
- # The entry filter for this collection.
147
- # Creates an instance of Jekyll::EntryFilter.
148
- #
149
- # Returns the instance of Jekyll::EntryFilter for this collection.
150
- def entry_filter
151
- @entry_filter ||= Jekyll::EntryFilter.new(site, relative_directory)
152
- end
153
-
154
- # An inspect string.
155
- #
156
- # Returns the inspect string
157
- def inspect
158
- "#<#{self.class} @label=#{label} docs=#{docs}>"
159
- end
160
-
161
- # Produce a sanitized label name
162
- # Label names may not contain anything but alphanumeric characters,
163
- # underscores, and hyphens.
164
- #
165
- # label - the possibly-unsafe label
166
- #
167
- # Returns a sanitized version of the label.
168
- def sanitize_label(label)
169
- label.gsub(%r![^a-z0-9_\-.]!i, "")
170
- end
171
-
172
- # Produce a representation of this Collection for use in Liquid.
173
- # Exposes two attributes:
174
- # - label
175
- # - docs
176
- #
177
- # Returns a representation of this collection for use in Liquid.
178
- def to_liquid
179
- Drops::CollectionDrop.new self
180
- end
181
-
182
- # Whether the collection's documents ought to be written as individual
183
- # files in the output.
184
- #
185
- # Returns true if the 'write' metadata is true, false otherwise.
186
- def write?
187
- !!metadata.fetch("output", false)
188
- end
189
-
190
- # The URL template to render collection's documents at.
191
- #
192
- # Returns the URL template to render collection's documents at.
193
- def url_template
194
- @url_template ||= metadata.fetch("permalink") do
195
- Utils.add_permalink_suffix("/:collection/:path", site.permalink_style)
196
- end
197
- end
198
-
199
- # Extract options for this collection from the site configuration.
200
- #
201
- # Returns the metadata for this collection
202
- def extract_metadata
203
- if site.config["collections"].is_a?(Hash)
204
- site.config["collections"][label] || {}
205
- else
206
- {}
207
- end
208
- end
209
-
210
- private
211
-
212
- def container
213
- @container ||= site.config["collections_dir"]
214
- end
215
-
216
- def read_document(full_path)
217
- doc = Document.new(full_path, :site => site, :collection => self)
218
- doc.read
219
- docs << doc if site.unpublished || doc.published?
220
- end
221
-
222
- def sort_docs!
223
- if metadata["order"].is_a?(Array)
224
- rearrange_docs!
225
- elsif metadata["sort_by"].is_a?(String)
226
- sort_docs_by_key!
227
- else
228
- docs.sort!
229
- end
230
- end
231
-
232
- # A custom sort function based on Schwartzian transform
233
- # Refer https://byparker.com/blog/2017/schwartzian-transform-faster-sorting/ for details
234
- def sort_docs_by_key!
235
- meta_key = metadata["sort_by"]
236
- # Modify `docs` array to cache document's property along with the Document instance
237
- docs.map! { |doc| [doc.data[meta_key], doc] }.sort! do |apples, olives|
238
- order = determine_sort_order(meta_key, apples, olives)
239
-
240
- # Fall back to `Document#<=>` if the properties were equal or were non-sortable
241
- # Otherwise continue with current sort-order
242
- if order.nil? || order.zero?
243
- apples[-1] <=> olives[-1]
244
- else
245
- order
246
- end
247
-
248
- # Finally restore the `docs` array with just the Document objects themselves
249
- end.map!(&:last)
250
- end
251
-
252
- def determine_sort_order(sort_key, apples, olives)
253
- apple_property, apple_document = apples
254
- olive_property, olive_document = olives
255
-
256
- if apple_property.nil? && !olive_property.nil?
257
- order_with_warning(sort_key, apple_document, 1)
258
- elsif !apple_property.nil? && olive_property.nil?
259
- order_with_warning(sort_key, olive_document, -1)
260
- else
261
- apple_property <=> olive_property
262
- end
263
- end
264
-
265
- def order_with_warning(sort_key, document, order)
266
- Jekyll.logger.warn "Sort warning:", "'#{sort_key}' not defined in #{document.relative_path}"
267
- order
268
- end
269
-
270
- # Rearrange documents within the `docs` array as listed in the `metadata["order"]` array.
271
- #
272
- # Involves converting the two arrays into hashes based on relative_paths as keys first, then
273
- # merging them to remove duplicates and finally retrieving the Document instances from the
274
- # merged array.
275
- def rearrange_docs!
276
- docs_table = {}
277
- custom_order = {}
278
-
279
- # pre-sort to normalize default array across platforms and then proceed to create a Hash
280
- # from that sorted array.
281
- docs.sort.each do |doc|
282
- docs_table[doc.relative_path] = doc
283
- end
284
-
285
- metadata["order"].each do |entry|
286
- custom_order[File.join(relative_directory, entry)] = nil
287
- end
288
-
289
- result = Jekyll::Utils.deep_merge_hashes(custom_order, docs_table).values
290
- result.compact!
291
- self.docs = result
292
- end
293
-
294
- def read_static_file(file_path, full_path)
295
- relative_dir = Jekyll.sanitized_path(
296
- relative_directory,
297
- File.dirname(file_path)
298
- ).chomp("/.")
299
-
300
- files << StaticFile.new(
301
- site,
302
- site.source,
303
- relative_dir,
304
- File.basename(full_path),
305
- self
306
- )
307
- end
308
- end
309
- end
1
+ # frozen_string_literal: true
2
+
3
+ module Jekyll
4
+ class Collection
5
+ attr_reader :site, :label, :metadata
6
+ attr_writer :docs
7
+
8
+ # Create a new Collection.
9
+ #
10
+ # site - the site to which this collection belongs.
11
+ # label - the name of the collection
12
+ #
13
+ # Returns nothing.
14
+ def initialize(site, label)
15
+ @site = site
16
+ @label = sanitize_label(label)
17
+ @metadata = extract_metadata
18
+ end
19
+
20
+ # Fetch the Documents in this collection.
21
+ # Defaults to an empty array if no documents have been read in.
22
+ #
23
+ # Returns an array of Jekyll::Document objects.
24
+ def docs
25
+ @docs ||= []
26
+ end
27
+
28
+ # Override of normal respond_to? to match method_missing's logic for
29
+ # looking in @data.
30
+ def respond_to_missing?(method, include_private = false)
31
+ docs.respond_to?(method.to_sym, include_private) || super
32
+ end
33
+
34
+ # Override of method_missing to check in @data for the key.
35
+ def method_missing(method, *args, &blck)
36
+ if docs.respond_to?(method.to_sym)
37
+ Jekyll.logger.warn "Deprecation:",
38
+ "#{label}.#{method} should be changed to #{label}.docs.#{method}."
39
+ Jekyll.logger.warn "", "Called by #{caller(0..0)}."
40
+ docs.public_send(method.to_sym, *args, &blck)
41
+ else
42
+ super
43
+ end
44
+ end
45
+
46
+ # Fetch the static files in this collection.
47
+ # Defaults to an empty array if no static files have been read in.
48
+ #
49
+ # Returns an array of Jekyll::StaticFile objects.
50
+ def files
51
+ @files ||= []
52
+ end
53
+
54
+ # Read the allowed documents into the collection's array of docs.
55
+ #
56
+ # Returns the sorted array of docs.
57
+ def read
58
+ filtered_entries.each do |file_path|
59
+ full_path = collection_dir(file_path)
60
+ next if File.directory?(full_path)
61
+
62
+ if Utils.has_yaml_header? full_path
63
+ read_document(full_path)
64
+ else
65
+ read_static_file(file_path, full_path)
66
+ end
67
+ end
68
+ site.static_files.concat(files) unless files.empty?
69
+ sort_docs!
70
+ end
71
+
72
+ # All the entries in this collection.
73
+ #
74
+ # Returns an Array of file paths to the documents in this collection
75
+ # relative to the collection's directory
76
+ def entries
77
+ return [] unless exists?
78
+
79
+ @entries ||= begin
80
+ collection_dir_slash = "#{collection_dir}/"
81
+ Utils.safe_glob(collection_dir, ["**", "*"], File::FNM_DOTMATCH).map do |entry|
82
+ entry[collection_dir_slash] = ""
83
+ entry
84
+ end
85
+ end
86
+ end
87
+
88
+ # Filtered version of the entries in this collection.
89
+ # See `Jekyll::EntryFilter#filter` for more information.
90
+ #
91
+ # Returns a list of filtered entry paths.
92
+ def filtered_entries
93
+ return [] unless exists?
94
+
95
+ @filtered_entries ||=
96
+ Dir.chdir(directory) do
97
+ entry_filter.filter(entries).reject do |f|
98
+ path = collection_dir(f)
99
+ File.directory?(path) || entry_filter.symlink?(f)
100
+ end
101
+ end
102
+ end
103
+
104
+ # The directory for this Collection, relative to the site source or the directory
105
+ # containing the collection.
106
+ #
107
+ # Returns a String containing the directory name where the collection
108
+ # is stored on the filesystem.
109
+ def relative_directory
110
+ @relative_directory ||= "_#{label}"
111
+ end
112
+
113
+ # The full path to the directory containing the collection.
114
+ #
115
+ # Returns a String containing th directory name where the collection
116
+ # is stored on the filesystem.
117
+ def directory
118
+ @directory ||= site.in_source_dir(
119
+ File.join(container, relative_directory)
120
+ )
121
+ end
122
+
123
+ # The full path to the directory containing the collection, with
124
+ # optional subpaths.
125
+ #
126
+ # *files - (optional) any other path pieces relative to the
127
+ # directory to append to the path
128
+ #
129
+ # Returns a String containing th directory name where the collection
130
+ # is stored on the filesystem.
131
+ def collection_dir(*files)
132
+ return directory if files.empty?
133
+
134
+ site.in_source_dir(container, relative_directory, *files)
135
+ end
136
+
137
+ # Checks whether the directory "exists" for this collection.
138
+ # The directory must exist on the filesystem and must not be a symlink
139
+ # if in safe mode.
140
+ #
141
+ # Returns false if the directory doesn't exist or if it's a symlink
142
+ # and we're in safe mode.
143
+ def exists?
144
+ File.directory?(directory) && !entry_filter.symlink?(directory)
145
+ end
146
+
147
+ # The entry filter for this collection.
148
+ # Creates an instance of Jekyll::EntryFilter.
149
+ #
150
+ # Returns the instance of Jekyll::EntryFilter for this collection.
151
+ def entry_filter
152
+ @entry_filter ||= Jekyll::EntryFilter.new(site, relative_directory)
153
+ end
154
+
155
+ # An inspect string.
156
+ #
157
+ # Returns the inspect string
158
+ def inspect
159
+ "#<#{self.class} @label=#{label} docs=#{docs}>"
160
+ end
161
+
162
+ # Produce a sanitized label name
163
+ # Label names may not contain anything but alphanumeric characters,
164
+ # underscores, and hyphens.
165
+ #
166
+ # label - the possibly-unsafe label
167
+ #
168
+ # Returns a sanitized version of the label.
169
+ def sanitize_label(label)
170
+ label.gsub(%r![^a-z0-9_\-.]!i, "")
171
+ end
172
+
173
+ # Produce a representation of this Collection for use in Liquid.
174
+ # Exposes two attributes:
175
+ # - label
176
+ # - docs
177
+ #
178
+ # Returns a representation of this collection for use in Liquid.
179
+ def to_liquid
180
+ Drops::CollectionDrop.new self
181
+ end
182
+
183
+ # Whether the collection's documents ought to be written as individual
184
+ # files in the output.
185
+ #
186
+ # Returns true if the 'write' metadata is true, false otherwise.
187
+ def write?
188
+ !!metadata.fetch("output", false)
189
+ end
190
+
191
+ # The URL template to render collection's documents at.
192
+ #
193
+ # Returns the URL template to render collection's documents at.
194
+ def url_template
195
+ @url_template ||= metadata.fetch("permalink") do
196
+ Utils.add_permalink_suffix("/:collection/:path", site.permalink_style)
197
+ end
198
+ end
199
+
200
+ # Extract options for this collection from the site configuration.
201
+ #
202
+ # Returns the metadata for this collection
203
+ def extract_metadata
204
+ if site.config["collections"].is_a?(Hash)
205
+ site.config["collections"][label] || {}
206
+ else
207
+ {}
208
+ end
209
+ end
210
+
211
+ private
212
+
213
+ def container
214
+ @container ||= site.config["collections_dir"]
215
+ end
216
+
217
+ def read_document(full_path)
218
+ doc = Document.new(full_path, :site => site, :collection => self)
219
+ doc.read
220
+ docs << doc if site.unpublished || doc.published?
221
+ end
222
+
223
+ def sort_docs!
224
+ if metadata["order"].is_a?(Array)
225
+ rearrange_docs!
226
+ elsif metadata["sort_by"].is_a?(String)
227
+ sort_docs_by_key!
228
+ else
229
+ docs.sort!
230
+ end
231
+ end
232
+
233
+ # A custom sort function based on Schwartzian transform
234
+ # Refer https://byparker.com/blog/2017/schwartzian-transform-faster-sorting/ for details
235
+ def sort_docs_by_key!
236
+ meta_key = metadata["sort_by"]
237
+ # Modify `docs` array to cache document's property along with the Document instance
238
+ docs.map! { |doc| [doc.data[meta_key], doc] }.sort! do |apples, olives|
239
+ order = determine_sort_order(meta_key, apples, olives)
240
+
241
+ # Fall back to `Document#<=>` if the properties were equal or were non-sortable
242
+ # Otherwise continue with current sort-order
243
+ if order.nil? || order.zero?
244
+ apples[-1] <=> olives[-1]
245
+ else
246
+ order
247
+ end
248
+
249
+ # Finally restore the `docs` array with just the Document objects themselves
250
+ end.map!(&:last)
251
+ end
252
+
253
+ def determine_sort_order(sort_key, apples, olives)
254
+ apple_property, apple_document = apples
255
+ olive_property, olive_document = olives
256
+
257
+ if apple_property.nil? && !olive_property.nil?
258
+ order_with_warning(sort_key, apple_document, 1)
259
+ elsif !apple_property.nil? && olive_property.nil?
260
+ order_with_warning(sort_key, olive_document, -1)
261
+ else
262
+ apple_property <=> olive_property
263
+ end
264
+ end
265
+
266
+ def order_with_warning(sort_key, document, order)
267
+ Jekyll.logger.warn "Sort warning:", "'#{sort_key}' not defined in #{document.relative_path}"
268
+ order
269
+ end
270
+
271
+ # Rearrange documents within the `docs` array as listed in the `metadata["order"]` array.
272
+ #
273
+ # Involves converting the two arrays into hashes based on relative_paths as keys first, then
274
+ # merging them to remove duplicates and finally retrieving the Document instances from the
275
+ # merged array.
276
+ def rearrange_docs!
277
+ docs_table = {}
278
+ custom_order = {}
279
+
280
+ # pre-sort to normalize default array across platforms and then proceed to create a Hash
281
+ # from that sorted array.
282
+ docs.sort.each do |doc|
283
+ docs_table[doc.relative_path] = doc
284
+ end
285
+
286
+ metadata["order"].each do |entry|
287
+ custom_order[File.join(relative_directory, entry)] = nil
288
+ end
289
+
290
+ result = Jekyll::Utils.deep_merge_hashes(custom_order, docs_table).values
291
+ result.compact!
292
+ self.docs = result
293
+ end
294
+
295
+ def read_static_file(file_path, full_path)
296
+ relative_dir = Jekyll.sanitized_path(
297
+ relative_directory,
298
+ File.dirname(file_path)
299
+ ).chomp("/.")
300
+
301
+ files << StaticFile.new(
302
+ site,
303
+ site.source,
304
+ relative_dir,
305
+ File.basename(full_path),
306
+ self
307
+ )
308
+ end
309
+ end
310
+ end