jekyll 4.0.1 → 4.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +48 -19
- data/lib/jekyll.rb +3 -0
- data/lib/jekyll/collection.rb +1 -1
- data/lib/jekyll/command.rb +4 -2
- data/lib/jekyll/commands/new.rb +2 -2
- data/lib/jekyll/commands/serve.rb +9 -1
- data/lib/jekyll/configuration.rb +1 -1
- data/lib/jekyll/converters/identity.rb +2 -2
- data/lib/jekyll/converters/markdown/kramdown_parser.rb +70 -1
- data/lib/jekyll/convertible.rb +15 -15
- data/lib/jekyll/document.rb +18 -4
- data/lib/jekyll/drops/document_drop.rb +12 -0
- data/lib/jekyll/drops/page_drop.rb +18 -0
- data/lib/jekyll/drops/url_drop.rb +8 -0
- data/lib/jekyll/entry_filter.rb +19 -6
- data/lib/jekyll/excerpt.rb +1 -1
- data/lib/jekyll/filters.rb +99 -14
- data/lib/jekyll/filters/url_filters.rb +41 -14
- data/lib/jekyll/frontmatter_defaults.rb +12 -17
- data/lib/jekyll/hooks.rb +2 -5
- data/lib/jekyll/inclusion.rb +32 -0
- data/lib/jekyll/liquid_renderer.rb +18 -15
- data/lib/jekyll/liquid_renderer/table.rb +1 -21
- data/lib/jekyll/page.rb +43 -0
- data/lib/jekyll/page_excerpt.rb +26 -0
- data/lib/jekyll/profiler.rb +58 -0
- data/lib/jekyll/readers/collection_reader.rb +1 -0
- data/lib/jekyll/readers/data_reader.rb +1 -0
- data/lib/jekyll/readers/layout_reader.rb +1 -0
- data/lib/jekyll/readers/page_reader.rb +1 -0
- data/lib/jekyll/readers/post_reader.rb +1 -0
- data/lib/jekyll/readers/static_file_reader.rb +1 -0
- data/lib/jekyll/readers/theme_assets_reader.rb +1 -0
- data/lib/jekyll/renderer.rb +9 -15
- data/lib/jekyll/site.rb +14 -5
- data/lib/jekyll/static_file.rb +14 -9
- data/lib/jekyll/tags/include.rb +58 -3
- data/lib/jekyll/theme.rb +6 -0
- data/lib/jekyll/utils.rb +4 -4
- data/lib/jekyll/utils/win_tz.rb +1 -1
- data/lib/jekyll/version.rb +1 -1
- data/lib/theme_template/theme.gemspec.erb +1 -4
- metadata +14 -31
data/lib/jekyll/document.rb
CHANGED
@@ -116,7 +116,7 @@ module Jekyll
|
|
116
116
|
#
|
117
117
|
# Returns the output extension
|
118
118
|
def output_ext
|
119
|
-
|
119
|
+
renderer.output_ext
|
120
120
|
end
|
121
121
|
|
122
122
|
# The base filename of the document, without the file extname.
|
@@ -133,6 +133,10 @@ module Jekyll
|
|
133
133
|
@basename ||= File.basename(path)
|
134
134
|
end
|
135
135
|
|
136
|
+
def renderer
|
137
|
+
@renderer ||= Jekyll::Renderer.new(site, self)
|
138
|
+
end
|
139
|
+
|
136
140
|
# Produces a "cleaned" relative path.
|
137
141
|
# The "cleaned" relative path is the relative path without the extname
|
138
142
|
# and with the collection's directory removed as well.
|
@@ -414,9 +418,13 @@ module Jekyll
|
|
414
418
|
#
|
415
419
|
# Returns nothing.
|
416
420
|
def categories_from_path(special_dir)
|
417
|
-
|
418
|
-
|
419
|
-
|
421
|
+
if relative_path.start_with?(special_dir)
|
422
|
+
superdirs = []
|
423
|
+
else
|
424
|
+
superdirs = relative_path.sub(Document.superdirs_regex(special_dir), "")
|
425
|
+
superdirs = superdirs.split(File::SEPARATOR)
|
426
|
+
superdirs.reject! { |c| c.empty? || c == special_dir || c == basename }
|
427
|
+
end
|
420
428
|
|
421
429
|
merge_data!({ "categories" => superdirs }, :source => "file path")
|
422
430
|
end
|
@@ -490,6 +498,7 @@ module Jekyll
|
|
490
498
|
end
|
491
499
|
end
|
492
500
|
|
501
|
+
# rubocop:disable Metrics/AbcSize
|
493
502
|
def populate_title
|
494
503
|
if relative_path =~ DATE_FILENAME_MATCHER
|
495
504
|
date, slug, ext = Regexp.last_match.captures
|
@@ -497,6 +506,10 @@ module Jekyll
|
|
497
506
|
elsif relative_path =~ DATELESS_FILENAME_MATCHER
|
498
507
|
slug, ext = Regexp.last_match.captures
|
499
508
|
end
|
509
|
+
# `slug` will be nil for documents without an extension since the regex patterns
|
510
|
+
# above tests for an extension as well.
|
511
|
+
# In such cases, assign `basename_without_ext` as the slug.
|
512
|
+
slug ||= basename_without_ext
|
500
513
|
|
501
514
|
# slugs shouldn't end with a period
|
502
515
|
# `String#gsub!` removes all trailing periods (in comparison to `String#chomp!`)
|
@@ -508,6 +521,7 @@ module Jekyll
|
|
508
521
|
data["slug"] ||= slug
|
509
522
|
data["ext"] ||= ext
|
510
523
|
end
|
524
|
+
# rubocop:enable Metrics/AbcSize
|
511
525
|
|
512
526
|
def modify_date(date)
|
513
527
|
if !data["date"] || data["date"].to_i == site.time.to_i
|
@@ -64,6 +64,18 @@ module Jekyll
|
|
64
64
|
result[key] = doc[key] unless NESTED_OBJECT_FIELD_BLACKLIST.include?(key)
|
65
65
|
end
|
66
66
|
end
|
67
|
+
|
68
|
+
def title
|
69
|
+
@obj.data["title"]
|
70
|
+
end
|
71
|
+
|
72
|
+
def categories
|
73
|
+
@obj.data["categories"]
|
74
|
+
end
|
75
|
+
|
76
|
+
def tags
|
77
|
+
@obj.data["tags"]
|
78
|
+
end
|
67
79
|
end
|
68
80
|
end
|
69
81
|
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Jekyll
|
4
|
+
module Drops
|
5
|
+
class PageDrop < Drop
|
6
|
+
extend Forwardable
|
7
|
+
|
8
|
+
mutable false
|
9
|
+
|
10
|
+
def_delegators :@obj, :content, :dir, :name, :path, :url, :excerpt
|
11
|
+
private def_delegator :@obj, :data, :fallback_data
|
12
|
+
|
13
|
+
def title
|
14
|
+
@obj.data["title"]
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -35,6 +35,14 @@ module Jekyll
|
|
35
35
|
category_set.to_a.join("/")
|
36
36
|
end
|
37
37
|
|
38
|
+
# Similar to output from #categories, but each category will be downcased and
|
39
|
+
# all non-alphanumeric characters of the category replaced with a hyphen.
|
40
|
+
def slugified_categories
|
41
|
+
Array(@obj.data["categories"]).each_with_object(Set.new) do |category, set|
|
42
|
+
set << Utils.slugify(category.to_s)
|
43
|
+
end.to_a.join("/")
|
44
|
+
end
|
45
|
+
|
38
46
|
# CCYY
|
39
47
|
def year
|
40
48
|
@obj.date.strftime("%Y")
|
data/lib/jekyll/entry_filter.rb
CHANGED
@@ -3,6 +3,7 @@
|
|
3
3
|
module Jekyll
|
4
4
|
class EntryFilter
|
5
5
|
attr_reader :site
|
6
|
+
|
6
7
|
SPECIAL_LEADING_CHAR_REGEX = %r!\A#{Regexp.union([".", "_", "#", "~"])}!o.freeze
|
7
8
|
|
8
9
|
def initialize(site, base_directory = nil)
|
@@ -27,20 +28,30 @@ module Jekyll
|
|
27
28
|
)
|
28
29
|
end
|
29
30
|
|
31
|
+
# rubocop:disable Metrics/CyclomaticComplexity
|
30
32
|
def filter(entries)
|
31
33
|
entries.reject do |e|
|
32
34
|
# Reject this entry if it is just a "dot" representation.
|
33
35
|
# e.g.: '.', '..', '_movies/.', 'music/..', etc
|
34
36
|
next true if e.end_with?(".")
|
35
|
-
|
37
|
+
|
38
|
+
# Check if the current entry is explicitly included and cache the result
|
39
|
+
included = included?(e)
|
40
|
+
|
41
|
+
# Reject current entry if it is excluded but not explicitly included as well.
|
42
|
+
next true if excluded?(e) && !included
|
43
|
+
|
44
|
+
# Reject current entry if it is a symlink.
|
36
45
|
next true if symlink?(e)
|
37
|
-
# Do not reject this entry if it is included.
|
38
|
-
next false if included?(e)
|
39
46
|
|
40
|
-
#
|
41
|
-
|
47
|
+
# Do not reject current entry if it is explicitly included.
|
48
|
+
next false if included
|
49
|
+
|
50
|
+
# Reject current entry if it is special or a backup file.
|
51
|
+
special?(e) || backup?(e)
|
42
52
|
end
|
43
53
|
end
|
54
|
+
# rubocop:enable Metrics/CyclomaticComplexity
|
44
55
|
|
45
56
|
def included?(entry)
|
46
57
|
glob_include?(site.include, entry) ||
|
@@ -91,6 +102,7 @@ module Jekyll
|
|
91
102
|
# Returns true if path matches against any glob pattern, else false.
|
92
103
|
def glob_include?(enumerator, entry)
|
93
104
|
entry_with_source = PathManager.join(site.source, entry)
|
105
|
+
entry_is_directory = File.directory?(entry_with_source)
|
94
106
|
|
95
107
|
enumerator.any? do |pattern|
|
96
108
|
case pattern
|
@@ -98,7 +110,8 @@ module Jekyll
|
|
98
110
|
pattern_with_source = PathManager.join(site.source, pattern)
|
99
111
|
|
100
112
|
File.fnmatch?(pattern_with_source, entry_with_source) ||
|
101
|
-
entry_with_source.start_with?(pattern_with_source)
|
113
|
+
entry_with_source.start_with?(pattern_with_source) ||
|
114
|
+
(pattern_with_source == "#{entry_with_source}/" if entry_is_directory)
|
102
115
|
when Regexp
|
103
116
|
pattern.match?(entry_with_source)
|
104
117
|
else
|
data/lib/jekyll/excerpt.rb
CHANGED
@@ -56,7 +56,7 @@ module Jekyll
|
|
56
56
|
#
|
57
57
|
# Returns true if the string passed in
|
58
58
|
def include?(something)
|
59
|
-
|
59
|
+
output&.include?(something) || content.include?(something)
|
60
60
|
end
|
61
61
|
|
62
62
|
# The UID for this doc (useful in feeds).
|
data/lib/jekyll/filters.rb
CHANGED
@@ -121,8 +121,20 @@ module Jekyll
|
|
121
121
|
# input - The String on which to operate.
|
122
122
|
#
|
123
123
|
# Returns the Integer word count.
|
124
|
-
def number_of_words(input)
|
125
|
-
|
124
|
+
def number_of_words(input, mode = nil)
|
125
|
+
cjk_charset = '\p{Han}\p{Katakana}\p{Hiragana}\p{Hangul}'
|
126
|
+
cjk_regex = %r![#{cjk_charset}]!o
|
127
|
+
word_regex = %r![^#{cjk_charset}\s]+!o
|
128
|
+
|
129
|
+
case mode
|
130
|
+
when "cjk"
|
131
|
+
input.scan(cjk_regex).length + input.scan(word_regex).length
|
132
|
+
when "auto"
|
133
|
+
cjk_count = input.scan(cjk_regex).length
|
134
|
+
cjk_count.zero? ? input.split.length : cjk_count + input.scan(word_regex).length
|
135
|
+
else
|
136
|
+
input.split.length
|
137
|
+
end
|
126
138
|
end
|
127
139
|
|
128
140
|
# Join an array of things into a string by separating with commas and the
|
@@ -210,6 +222,66 @@ module Jekyll
|
|
210
222
|
end || []
|
211
223
|
end
|
212
224
|
|
225
|
+
# Search an array of objects and returns the first object that has the queried attribute
|
226
|
+
# with the given value or returns nil otherwise.
|
227
|
+
#
|
228
|
+
# input - the object array.
|
229
|
+
# property - the property within each object to search by.
|
230
|
+
# value - the desired value.
|
231
|
+
# Cannot be an instance of Array nor Hash since calling #to_s on them returns
|
232
|
+
# their `#inspect` string object.
|
233
|
+
#
|
234
|
+
# Returns the found object or nil
|
235
|
+
#
|
236
|
+
# rubocop:disable Metrics/CyclomaticComplexity
|
237
|
+
def find(input, property, value)
|
238
|
+
return input if !property || value.is_a?(Array) || value.is_a?(Hash)
|
239
|
+
return input unless input.respond_to?(:find)
|
240
|
+
|
241
|
+
input = input.values if input.is_a?(Hash)
|
242
|
+
input_id = input.hash
|
243
|
+
|
244
|
+
# implement a hash based on method parameters to cache the end-result for given parameters.
|
245
|
+
@find_filter_cache ||= {}
|
246
|
+
@find_filter_cache[input_id] ||= {}
|
247
|
+
@find_filter_cache[input_id][property] ||= {}
|
248
|
+
|
249
|
+
# stash or retrive results to return
|
250
|
+
# Since `enum.find` can return nil or false, we use a placeholder string "<__NO MATCH__>"
|
251
|
+
# to validate caching.
|
252
|
+
result = @find_filter_cache[input_id][property][value] ||= begin
|
253
|
+
input.find do |object|
|
254
|
+
compare_property_vs_target(item_property(object, property), value)
|
255
|
+
end || "<__NO MATCH__>"
|
256
|
+
end
|
257
|
+
return nil if result == "<__NO MATCH__>"
|
258
|
+
|
259
|
+
result
|
260
|
+
end
|
261
|
+
# rubocop:enable Metrics/CyclomaticComplexity
|
262
|
+
|
263
|
+
# Searches an array of objects against an expression and returns the first object for which
|
264
|
+
# the expression evaluates to true, or returns nil otherwise.
|
265
|
+
#
|
266
|
+
# input - the object array
|
267
|
+
# variable - the variable to assign each item to in the expression
|
268
|
+
# expression - a Liquid comparison expression passed in as a string
|
269
|
+
#
|
270
|
+
# Returns the found object or nil
|
271
|
+
def find_exp(input, variable, expression)
|
272
|
+
return input unless input.respond_to?(:find)
|
273
|
+
|
274
|
+
input = input.values if input.is_a?(Hash)
|
275
|
+
|
276
|
+
condition = parse_condition(expression)
|
277
|
+
@context.stack do
|
278
|
+
input.find do |object|
|
279
|
+
@context[variable] = object
|
280
|
+
condition.evaluate(@context)
|
281
|
+
end
|
282
|
+
end
|
283
|
+
end
|
284
|
+
|
213
285
|
# Convert the input into integer
|
214
286
|
#
|
215
287
|
# input - the object string
|
@@ -356,15 +428,24 @@ module Jekyll
|
|
356
428
|
@item_property_cache ||= {}
|
357
429
|
@item_property_cache[property] ||= {}
|
358
430
|
@item_property_cache[property][item] ||= begin
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
431
|
+
property = property.to_s
|
432
|
+
property = if item.respond_to?(:to_liquid)
|
433
|
+
read_liquid_attribute(item.to_liquid, property)
|
434
|
+
elsif item.respond_to?(:data)
|
435
|
+
item.data[property]
|
436
|
+
else
|
437
|
+
item[property]
|
438
|
+
end
|
439
|
+
|
440
|
+
parse_sort_input(property)
|
441
|
+
end
|
442
|
+
end
|
443
|
+
|
444
|
+
def read_liquid_attribute(liquid_data, property)
|
445
|
+
return liquid_data[property] unless property.include?(".")
|
446
|
+
|
447
|
+
property.split(".").reduce(liquid_data) do |data, key|
|
448
|
+
data.respond_to?(:[]) && data[key]
|
368
449
|
end
|
369
450
|
end
|
370
451
|
|
@@ -423,10 +504,14 @@ module Jekyll
|
|
423
504
|
#
|
424
505
|
# Returns an instance of Liquid::Condition
|
425
506
|
def parse_binary_comparison(parser)
|
426
|
-
parse_comparison(parser)
|
427
|
-
|
428
|
-
|
507
|
+
condition = parse_comparison(parser)
|
508
|
+
first_condition = condition
|
509
|
+
while (binary_operator = parser.id?("and") || parser.id?("or"))
|
510
|
+
child_condition = parse_comparison(parser)
|
511
|
+
condition.send(binary_operator, child_condition)
|
512
|
+
condition = child_condition
|
429
513
|
end
|
514
|
+
first_condition
|
430
515
|
end
|
431
516
|
|
432
517
|
# Generates a Liquid::Condition object from a Liquid::Parser object based on whether the parsed
|
@@ -11,15 +11,16 @@ module Jekyll
|
|
11
11
|
def absolute_url(input)
|
12
12
|
return if input.nil?
|
13
13
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
14
|
+
cache = if input.is_a?(String)
|
15
|
+
(@context.registers[:site].filter_cache[:absolute_url] ||= {})
|
16
|
+
else
|
17
|
+
(@context.registers[:cached_absolute_url] ||= {})
|
18
|
+
end
|
19
|
+
cache[input] ||= compute_absolute_url(input)
|
19
20
|
|
20
|
-
|
21
|
-
|
22
|
-
|
21
|
+
# Duplicate cached string so that the cached value is never mutated by
|
22
|
+
# a subsequent filter.
|
23
|
+
cache[input].dup
|
23
24
|
end
|
24
25
|
|
25
26
|
# Produces a URL relative to the domain root based on site.baseurl
|
@@ -31,13 +32,16 @@ module Jekyll
|
|
31
32
|
def relative_url(input)
|
32
33
|
return if input.nil?
|
33
34
|
|
34
|
-
|
35
|
-
|
35
|
+
cache = if input.is_a?(String)
|
36
|
+
(@context.registers[:site].filter_cache[:relative_url] ||= {})
|
37
|
+
else
|
38
|
+
(@context.registers[:cached_relative_url] ||= {})
|
39
|
+
end
|
40
|
+
cache[input] ||= compute_relative_url(input)
|
36
41
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
).normalize.to_s
|
42
|
+
# Duplicate cached string so that the cached value is never mutated by
|
43
|
+
# a subsequent filter.
|
44
|
+
cache[input].dup
|
41
45
|
end
|
42
46
|
|
43
47
|
# Strips trailing `/index.html` from URLs to create pretty permalinks
|
@@ -53,6 +57,29 @@ module Jekyll
|
|
53
57
|
|
54
58
|
private
|
55
59
|
|
60
|
+
def compute_absolute_url(input)
|
61
|
+
input = input.url if input.respond_to?(:url)
|
62
|
+
return input if Addressable::URI.parse(input.to_s).absolute?
|
63
|
+
|
64
|
+
site = @context.registers[:site]
|
65
|
+
site_url = site.config["url"]
|
66
|
+
return relative_url(input) if site_url.nil? || site_url == ""
|
67
|
+
|
68
|
+
Addressable::URI.parse(
|
69
|
+
site_url.to_s + relative_url(input)
|
70
|
+
).normalize.to_s
|
71
|
+
end
|
72
|
+
|
73
|
+
def compute_relative_url(input)
|
74
|
+
input = input.url if input.respond_to?(:url)
|
75
|
+
return input if Addressable::URI.parse(input.to_s).absolute?
|
76
|
+
|
77
|
+
parts = [sanitized_baseurl, input]
|
78
|
+
Addressable::URI.parse(
|
79
|
+
parts.compact.map { |part| ensure_leading_slash(part.to_s) }.join
|
80
|
+
).normalize.to_s
|
81
|
+
end
|
82
|
+
|
56
83
|
def sanitized_baseurl
|
57
84
|
site = @context.registers[:site]
|
58
85
|
site.config["baseurl"].to_s.chomp("/")
|
@@ -103,15 +103,15 @@ module Jekyll
|
|
103
103
|
end
|
104
104
|
|
105
105
|
def applies_path?(scope, path)
|
106
|
-
|
106
|
+
rel_scope_path = scope["path"]
|
107
|
+
return true if !rel_scope_path.is_a?(String) || rel_scope_path.empty?
|
107
108
|
|
108
|
-
sanitized_path =
|
109
|
-
rel_scope_path = Pathname.new(scope["path"])
|
109
|
+
sanitized_path = sanitize_path(path)
|
110
110
|
|
111
|
-
if
|
111
|
+
if rel_scope_path.include?("*")
|
112
112
|
glob_scope(sanitized_path, rel_scope_path)
|
113
113
|
else
|
114
|
-
path_is_subpath?(sanitized_path, strip_collections_dir(
|
114
|
+
path_is_subpath?(sanitized_path, strip_collections_dir(rel_scope_path))
|
115
115
|
end
|
116
116
|
end
|
117
117
|
|
@@ -134,11 +134,7 @@ module Jekyll
|
|
134
134
|
end
|
135
135
|
|
136
136
|
def path_is_subpath?(path, parent_path)
|
137
|
-
path.
|
138
|
-
return true if ascended_path.to_s == parent_path.to_s
|
139
|
-
end
|
140
|
-
|
141
|
-
false
|
137
|
+
path.start_with?(parent_path)
|
142
138
|
end
|
143
139
|
|
144
140
|
def strip_collections_dir(path)
|
@@ -179,7 +175,7 @@ module Jekyll
|
|
179
175
|
# new_scope - the new scope hash
|
180
176
|
#
|
181
177
|
# Returns true if the new scope has precedence over the older
|
182
|
-
# rubocop: disable PredicateName
|
178
|
+
# rubocop: disable Naming/PredicateName
|
183
179
|
def has_precedence?(old_scope, new_scope)
|
184
180
|
return true if old_scope.nil?
|
185
181
|
|
@@ -194,7 +190,7 @@ module Jekyll
|
|
194
190
|
!old_scope.key? "type"
|
195
191
|
end
|
196
192
|
end
|
197
|
-
# rubocop: enable PredicateName
|
193
|
+
# rubocop: enable Naming/PredicateName
|
198
194
|
|
199
195
|
# Collects a list of sets that match the given path and type
|
200
196
|
#
|
@@ -230,15 +226,14 @@ module Jekyll
|
|
230
226
|
end.compact
|
231
227
|
end
|
232
228
|
|
233
|
-
# Sanitizes the given path by removing a leading
|
234
|
-
|
235
|
-
SANITIZATION_REGEX = %r!\A/|(?<=[^/])\z!.freeze
|
236
|
-
|
229
|
+
# Sanitizes the given path by removing a leading slash
|
237
230
|
def sanitize_path(path)
|
238
231
|
if path.nil? || path.empty?
|
239
232
|
""
|
233
|
+
elsif path.start_with?("/")
|
234
|
+
path.gsub(%r!\A/|(?<=[^/])\z!, "")
|
240
235
|
else
|
241
|
-
path
|
236
|
+
path
|
242
237
|
end
|
243
238
|
end
|
244
239
|
end
|