jekyll 4.0.1 → 4.2.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (124) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +350 -163
  3. data/LICENSE +21 -21
  4. data/README.markdown +86 -90
  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 -103
  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 -173
  18. data/lib/jekyll/commands/help.rb +34 -34
  19. data/lib/jekyll/commands/new.rb +169 -169
  20. data/lib/jekyll/commands/new_theme.rb +40 -42
  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 -354
  26. data/lib/jekyll/configuration.rb +313 -316
  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 -130
  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 -254
  33. data/lib/jekyll/deprecator.rb +50 -50
  34. data/lib/jekyll/document.rb +544 -522
  35. data/lib/jekyll/drops/collection_drop.rb +20 -20
  36. data/lib/jekyll/drops/document_drop.rb +70 -69
  37. data/lib/jekyll/drops/drop.rb +293 -215
  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 -132
  44. data/lib/jekyll/entry_filter.rb +121 -110
  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 -68
  51. data/lib/jekyll/filters.rb +535 -454
  52. data/lib/jekyll/frontmatter_defaults.rb +240 -245
  53. data/lib/jekyll/generator.rb +5 -5
  54. data/lib/jekyll/hooks.rb +107 -106
  55. data/lib/jekyll/inclusion.rb +32 -0
  56. data/lib/jekyll/layout.rb +67 -62
  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 -75
  60. data/lib/jekyll/liquid_renderer.rb +80 -77
  61. data/lib/jekyll/log_adapter.rb +151 -151
  62. data/lib/jekyll/mime.types +866 -866
  63. data/lib/jekyll/page.rb +217 -186
  64. data/lib/jekyll/page_excerpt.rb +25 -0
  65. data/lib/jekyll/page_without_a_file.rb +14 -14
  66. data/lib/jekyll/path_manager.rb +74 -31
  67. data/lib/jekyll/plugin.rb +92 -92
  68. data/lib/jekyll/plugin_manager.rb +115 -115
  69. data/lib/jekyll/profiler.rb +58 -0
  70. data/lib/jekyll/publisher.rb +23 -23
  71. data/lib/jekyll/reader.rb +192 -187
  72. data/lib/jekyll/readers/collection_reader.rb +23 -22
  73. data/lib/jekyll/readers/data_reader.rb +79 -75
  74. data/lib/jekyll/readers/layout_reader.rb +62 -61
  75. data/lib/jekyll/readers/page_reader.rb +25 -24
  76. data/lib/jekyll/readers/post_reader.rb +85 -84
  77. data/lib/jekyll/readers/static_file_reader.rb +25 -24
  78. data/lib/jekyll/readers/theme_assets_reader.rb +52 -51
  79. data/lib/jekyll/regenerator.rb +195 -195
  80. data/lib/jekyll/related_posts.rb +52 -52
  81. data/lib/jekyll/renderer.rb +265 -267
  82. data/lib/jekyll/site.rb +551 -527
  83. data/lib/jekyll/static_file.rb +208 -203
  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 -221
  87. data/lib/jekyll/tags/link.rb +42 -41
  88. data/lib/jekyll/tags/post_url.rb +106 -107
  89. data/lib/jekyll/theme.rb +86 -80
  90. data/lib/jekyll/theme_builder.rb +121 -121
  91. data/lib/jekyll/url.rb +167 -164
  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 -82
  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 -206
  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 -19
  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 +20 -38
@@ -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.zero? || order.nil?
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