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.
- checksums.yaml +4 -4
- data/.rubocop.yml +350 -350
- data/LICENSE +21 -21
- data/README.markdown +86 -86
- data/exe/jekyll +57 -57
- data/lib/blank_template/_config.yml +3 -3
- data/lib/blank_template/_layouts/default.html +12 -12
- data/lib/blank_template/_sass/main.scss +9 -9
- data/lib/blank_template/assets/css/main.scss +4 -4
- data/lib/blank_template/index.md +8 -8
- data/lib/jekyll/cache.rb +190 -190
- data/lib/jekyll/cleaner.rb +111 -111
- data/lib/jekyll/collection.rb +309 -309
- data/lib/jekyll/command.rb +105 -105
- data/lib/jekyll/commands/build.rb +93 -93
- data/lib/jekyll/commands/clean.rb +45 -45
- data/lib/jekyll/commands/doctor.rb +177 -177
- data/lib/jekyll/commands/help.rb +34 -34
- data/lib/jekyll/commands/new.rb +172 -169
- data/lib/jekyll/commands/new_theme.rb +40 -40
- data/lib/jekyll/commands/serve/live_reload_reactor.rb +122 -122
- data/lib/jekyll/commands/serve/livereload_assets/livereload.js +1183 -1183
- data/lib/jekyll/commands/serve/servlet.rb +202 -202
- data/lib/jekyll/commands/serve/websockets.rb +81 -81
- data/lib/jekyll/commands/serve.rb +362 -362
- data/lib/jekyll/configuration.rb +313 -313
- data/lib/jekyll/converter.rb +54 -54
- data/lib/jekyll/converters/identity.rb +41 -41
- data/lib/jekyll/converters/markdown/kramdown_parser.rb +199 -199
- data/lib/jekyll/converters/markdown.rb +113 -113
- data/lib/jekyll/converters/smartypants.rb +70 -70
- data/lib/jekyll/convertible.rb +257 -257
- data/lib/jekyll/deprecator.rb +50 -50
- data/lib/jekyll/document.rb +544 -544
- data/lib/jekyll/drops/collection_drop.rb +20 -20
- data/lib/jekyll/drops/document_drop.rb +70 -70
- data/lib/jekyll/drops/drop.rb +293 -293
- data/lib/jekyll/drops/excerpt_drop.rb +19 -19
- data/lib/jekyll/drops/jekyll_drop.rb +32 -32
- data/lib/jekyll/drops/site_drop.rb +66 -66
- data/lib/jekyll/drops/static_file_drop.rb +14 -14
- data/lib/jekyll/drops/unified_payload_drop.rb +26 -26
- data/lib/jekyll/drops/url_drop.rb +140 -140
- data/lib/jekyll/entry_filter.rb +121 -121
- data/lib/jekyll/errors.rb +20 -20
- data/lib/jekyll/excerpt.rb +201 -201
- data/lib/jekyll/external.rb +79 -79
- data/lib/jekyll/filters/date_filters.rb +110 -110
- data/lib/jekyll/filters/grouping_filters.rb +64 -64
- data/lib/jekyll/filters/url_filters.rb +98 -98
- data/lib/jekyll/filters.rb +535 -535
- data/lib/jekyll/frontmatter_defaults.rb +240 -240
- data/lib/jekyll/generator.rb +5 -5
- data/lib/jekyll/hooks.rb +107 -107
- data/lib/jekyll/inclusion.rb +32 -32
- data/lib/jekyll/layout.rb +67 -67
- data/lib/jekyll/liquid_extensions.rb +22 -22
- data/lib/jekyll/liquid_renderer/file.rb +77 -77
- data/lib/jekyll/liquid_renderer/table.rb +55 -55
- data/lib/jekyll/liquid_renderer.rb +80 -80
- data/lib/jekyll/log_adapter.rb +151 -151
- data/lib/jekyll/mime.types +866 -866
- data/lib/jekyll/page.rb +217 -217
- data/lib/jekyll/page_excerpt.rb +25 -25
- data/lib/jekyll/page_without_a_file.rb +14 -14
- data/lib/jekyll/path_manager.rb +74 -74
- data/lib/jekyll/plugin.rb +92 -92
- data/lib/jekyll/plugin_manager.rb +115 -115
- data/lib/jekyll/profiler.rb +58 -58
- data/lib/jekyll/publisher.rb +23 -23
- data/lib/jekyll/reader.rb +192 -192
- data/lib/jekyll/readers/collection_reader.rb +23 -23
- data/lib/jekyll/readers/data_reader.rb +79 -79
- data/lib/jekyll/readers/layout_reader.rb +62 -62
- data/lib/jekyll/readers/page_reader.rb +25 -25
- data/lib/jekyll/readers/post_reader.rb +85 -85
- data/lib/jekyll/readers/static_file_reader.rb +25 -25
- data/lib/jekyll/readers/theme_assets_reader.rb +52 -52
- data/lib/jekyll/regenerator.rb +195 -195
- data/lib/jekyll/related_posts.rb +52 -52
- data/lib/jekyll/renderer.rb +265 -265
- data/lib/jekyll/site.rb +551 -551
- data/lib/jekyll/static_file.rb +208 -208
- data/lib/jekyll/stevenson.rb +60 -60
- data/lib/jekyll/tags/highlight.rb +110 -110
- data/lib/jekyll/tags/include.rb +275 -275
- data/lib/jekyll/tags/link.rb +42 -42
- data/lib/jekyll/tags/post_url.rb +106 -106
- data/lib/jekyll/theme.rb +86 -86
- data/lib/jekyll/theme_builder.rb +121 -121
- data/lib/jekyll/url.rb +167 -167
- data/lib/jekyll/utils/ansi.rb +57 -57
- data/lib/jekyll/utils/exec.rb +26 -26
- data/lib/jekyll/utils/internet.rb +37 -37
- data/lib/jekyll/utils/platforms.rb +67 -67
- data/lib/jekyll/utils/thread_event.rb +31 -31
- data/lib/jekyll/utils/win_tz.rb +75 -75
- data/lib/jekyll/utils.rb +367 -367
- data/lib/jekyll/version.rb +5 -5
- data/lib/jekyll.rb +195 -195
- data/lib/site_template/.gitignore +5 -5
- data/lib/site_template/404.html +25 -25
- data/lib/site_template/_config.yml +55 -55
- data/lib/site_template/_posts/0000-00-00-welcome-to-jekyll.markdown.erb +29 -29
- data/lib/site_template/about.markdown +18 -18
- data/lib/site_template/index.markdown +6 -6
- data/lib/theme_template/CODE_OF_CONDUCT.md.erb +74 -74
- data/lib/theme_template/Gemfile +4 -4
- data/lib/theme_template/LICENSE.txt.erb +21 -21
- data/lib/theme_template/README.md.erb +52 -52
- data/lib/theme_template/_layouts/default.html +1 -1
- data/lib/theme_template/_layouts/page.html +5 -5
- data/lib/theme_template/_layouts/post.html +5 -5
- data/lib/theme_template/example/_config.yml.erb +1 -1
- data/lib/theme_template/example/_post.md +12 -12
- data/lib/theme_template/example/index.html +14 -14
- data/lib/theme_template/example/style.scss +7 -7
- data/lib/theme_template/gitignore.erb +6 -6
- data/lib/theme_template/theme.gemspec.erb +16 -16
- data/rubocop/jekyll/assert_equal_literal_actual.rb +149 -149
- data/rubocop/jekyll/no_p_allowed.rb +23 -23
- data/rubocop/jekyll/no_puts_allowed.rb +23 -23
- data/rubocop/jekyll.rb +5 -5
- metadata +3 -3
data/lib/jekyll/collection.rb
CHANGED
|
@@ -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
|