jekyll 4.2.1 → 4.3.0

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