jekyll 4.2.1 → 4.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (124) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +350 -350
  3. data/LICENSE +21 -21
  4. data/README.markdown +86 -86
  5. data/exe/jekyll +57 -57
  6. data/lib/blank_template/_config.yml +3 -3
  7. data/lib/blank_template/_layouts/default.html +12 -12
  8. data/lib/blank_template/_sass/main.scss +9 -9
  9. data/lib/blank_template/assets/css/main.scss +4 -4
  10. data/lib/blank_template/index.md +8 -8
  11. data/lib/jekyll/cache.rb +190 -190
  12. data/lib/jekyll/cleaner.rb +111 -111
  13. data/lib/jekyll/collection.rb +309 -309
  14. data/lib/jekyll/command.rb +105 -105
  15. data/lib/jekyll/commands/build.rb +93 -93
  16. data/lib/jekyll/commands/clean.rb +45 -45
  17. data/lib/jekyll/commands/doctor.rb +177 -177
  18. data/lib/jekyll/commands/help.rb +34 -34
  19. data/lib/jekyll/commands/new.rb +172 -169
  20. data/lib/jekyll/commands/new_theme.rb +40 -40
  21. data/lib/jekyll/commands/serve/live_reload_reactor.rb +122 -122
  22. data/lib/jekyll/commands/serve/livereload_assets/livereload.js +1183 -1183
  23. data/lib/jekyll/commands/serve/servlet.rb +202 -202
  24. data/lib/jekyll/commands/serve/websockets.rb +81 -81
  25. data/lib/jekyll/commands/serve.rb +362 -362
  26. data/lib/jekyll/configuration.rb +313 -313
  27. data/lib/jekyll/converter.rb +54 -54
  28. data/lib/jekyll/converters/identity.rb +41 -41
  29. data/lib/jekyll/converters/markdown/kramdown_parser.rb +199 -199
  30. data/lib/jekyll/converters/markdown.rb +113 -113
  31. data/lib/jekyll/converters/smartypants.rb +70 -70
  32. data/lib/jekyll/convertible.rb +257 -257
  33. data/lib/jekyll/deprecator.rb +50 -50
  34. data/lib/jekyll/document.rb +544 -544
  35. data/lib/jekyll/drops/collection_drop.rb +20 -20
  36. data/lib/jekyll/drops/document_drop.rb +70 -70
  37. data/lib/jekyll/drops/drop.rb +293 -293
  38. data/lib/jekyll/drops/excerpt_drop.rb +19 -19
  39. data/lib/jekyll/drops/jekyll_drop.rb +32 -32
  40. data/lib/jekyll/drops/site_drop.rb +66 -66
  41. data/lib/jekyll/drops/static_file_drop.rb +14 -14
  42. data/lib/jekyll/drops/unified_payload_drop.rb +26 -26
  43. data/lib/jekyll/drops/url_drop.rb +140 -140
  44. data/lib/jekyll/entry_filter.rb +121 -121
  45. data/lib/jekyll/errors.rb +20 -20
  46. data/lib/jekyll/excerpt.rb +201 -201
  47. data/lib/jekyll/external.rb +79 -79
  48. data/lib/jekyll/filters/date_filters.rb +110 -110
  49. data/lib/jekyll/filters/grouping_filters.rb +64 -64
  50. data/lib/jekyll/filters/url_filters.rb +98 -98
  51. data/lib/jekyll/filters.rb +535 -535
  52. data/lib/jekyll/frontmatter_defaults.rb +240 -240
  53. data/lib/jekyll/generator.rb +5 -5
  54. data/lib/jekyll/hooks.rb +107 -107
  55. data/lib/jekyll/inclusion.rb +32 -32
  56. data/lib/jekyll/layout.rb +67 -67
  57. data/lib/jekyll/liquid_extensions.rb +22 -22
  58. data/lib/jekyll/liquid_renderer/file.rb +77 -77
  59. data/lib/jekyll/liquid_renderer/table.rb +55 -55
  60. data/lib/jekyll/liquid_renderer.rb +80 -80
  61. data/lib/jekyll/log_adapter.rb +151 -151
  62. data/lib/jekyll/mime.types +866 -866
  63. data/lib/jekyll/page.rb +217 -217
  64. data/lib/jekyll/page_excerpt.rb +25 -25
  65. data/lib/jekyll/page_without_a_file.rb +14 -14
  66. data/lib/jekyll/path_manager.rb +74 -74
  67. data/lib/jekyll/plugin.rb +92 -92
  68. data/lib/jekyll/plugin_manager.rb +115 -115
  69. data/lib/jekyll/profiler.rb +58 -58
  70. data/lib/jekyll/publisher.rb +23 -23
  71. data/lib/jekyll/reader.rb +192 -192
  72. data/lib/jekyll/readers/collection_reader.rb +23 -23
  73. data/lib/jekyll/readers/data_reader.rb +79 -79
  74. data/lib/jekyll/readers/layout_reader.rb +62 -62
  75. data/lib/jekyll/readers/page_reader.rb +25 -25
  76. data/lib/jekyll/readers/post_reader.rb +85 -85
  77. data/lib/jekyll/readers/static_file_reader.rb +25 -25
  78. data/lib/jekyll/readers/theme_assets_reader.rb +52 -52
  79. data/lib/jekyll/regenerator.rb +195 -195
  80. data/lib/jekyll/related_posts.rb +52 -52
  81. data/lib/jekyll/renderer.rb +265 -265
  82. data/lib/jekyll/site.rb +551 -551
  83. data/lib/jekyll/static_file.rb +208 -208
  84. data/lib/jekyll/stevenson.rb +60 -60
  85. data/lib/jekyll/tags/highlight.rb +110 -110
  86. data/lib/jekyll/tags/include.rb +275 -275
  87. data/lib/jekyll/tags/link.rb +42 -42
  88. data/lib/jekyll/tags/post_url.rb +106 -106
  89. data/lib/jekyll/theme.rb +86 -86
  90. data/lib/jekyll/theme_builder.rb +121 -121
  91. data/lib/jekyll/url.rb +167 -167
  92. data/lib/jekyll/utils/ansi.rb +57 -57
  93. data/lib/jekyll/utils/exec.rb +26 -26
  94. data/lib/jekyll/utils/internet.rb +37 -37
  95. data/lib/jekyll/utils/platforms.rb +67 -67
  96. data/lib/jekyll/utils/thread_event.rb +31 -31
  97. data/lib/jekyll/utils/win_tz.rb +75 -75
  98. data/lib/jekyll/utils.rb +367 -367
  99. data/lib/jekyll/version.rb +5 -5
  100. data/lib/jekyll.rb +195 -195
  101. data/lib/site_template/.gitignore +5 -5
  102. data/lib/site_template/404.html +25 -25
  103. data/lib/site_template/_config.yml +55 -55
  104. data/lib/site_template/_posts/0000-00-00-welcome-to-jekyll.markdown.erb +29 -29
  105. data/lib/site_template/about.markdown +18 -18
  106. data/lib/site_template/index.markdown +6 -6
  107. data/lib/theme_template/CODE_OF_CONDUCT.md.erb +74 -74
  108. data/lib/theme_template/Gemfile +4 -4
  109. data/lib/theme_template/LICENSE.txt.erb +21 -21
  110. data/lib/theme_template/README.md.erb +52 -52
  111. data/lib/theme_template/_layouts/default.html +1 -1
  112. data/lib/theme_template/_layouts/page.html +5 -5
  113. data/lib/theme_template/_layouts/post.html +5 -5
  114. data/lib/theme_template/example/_config.yml.erb +1 -1
  115. data/lib/theme_template/example/_post.md +12 -12
  116. data/lib/theme_template/example/index.html +14 -14
  117. data/lib/theme_template/example/style.scss +7 -7
  118. data/lib/theme_template/gitignore.erb +6 -6
  119. data/lib/theme_template/theme.gemspec.erb +16 -16
  120. data/rubocop/jekyll/assert_equal_literal_actual.rb +149 -149
  121. data/rubocop/jekyll/no_p_allowed.rb +23 -23
  122. data/rubocop/jekyll/no_puts_allowed.rb +23 -23
  123. data/rubocop/jekyll.rb +5 -5
  124. metadata +3 -3
@@ -1,309 +1,309 @@
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
+ 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