jekyll 4.0.1 → 4.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +48 -19
  3. data/lib/jekyll.rb +3 -0
  4. data/lib/jekyll/collection.rb +1 -1
  5. data/lib/jekyll/command.rb +4 -2
  6. data/lib/jekyll/commands/new.rb +2 -2
  7. data/lib/jekyll/commands/serve.rb +9 -1
  8. data/lib/jekyll/configuration.rb +1 -1
  9. data/lib/jekyll/converters/identity.rb +2 -2
  10. data/lib/jekyll/converters/markdown/kramdown_parser.rb +70 -1
  11. data/lib/jekyll/convertible.rb +15 -15
  12. data/lib/jekyll/document.rb +18 -4
  13. data/lib/jekyll/drops/document_drop.rb +12 -0
  14. data/lib/jekyll/drops/page_drop.rb +18 -0
  15. data/lib/jekyll/drops/url_drop.rb +8 -0
  16. data/lib/jekyll/entry_filter.rb +19 -6
  17. data/lib/jekyll/excerpt.rb +1 -1
  18. data/lib/jekyll/filters.rb +99 -14
  19. data/lib/jekyll/filters/url_filters.rb +41 -14
  20. data/lib/jekyll/frontmatter_defaults.rb +12 -17
  21. data/lib/jekyll/hooks.rb +2 -5
  22. data/lib/jekyll/inclusion.rb +32 -0
  23. data/lib/jekyll/liquid_renderer.rb +18 -15
  24. data/lib/jekyll/liquid_renderer/table.rb +1 -21
  25. data/lib/jekyll/page.rb +43 -0
  26. data/lib/jekyll/page_excerpt.rb +26 -0
  27. data/lib/jekyll/profiler.rb +58 -0
  28. data/lib/jekyll/readers/collection_reader.rb +1 -0
  29. data/lib/jekyll/readers/data_reader.rb +1 -0
  30. data/lib/jekyll/readers/layout_reader.rb +1 -0
  31. data/lib/jekyll/readers/page_reader.rb +1 -0
  32. data/lib/jekyll/readers/post_reader.rb +1 -0
  33. data/lib/jekyll/readers/static_file_reader.rb +1 -0
  34. data/lib/jekyll/readers/theme_assets_reader.rb +1 -0
  35. data/lib/jekyll/renderer.rb +9 -15
  36. data/lib/jekyll/site.rb +14 -5
  37. data/lib/jekyll/static_file.rb +14 -9
  38. data/lib/jekyll/tags/include.rb +58 -3
  39. data/lib/jekyll/theme.rb +6 -0
  40. data/lib/jekyll/utils.rb +4 -4
  41. data/lib/jekyll/utils/win_tz.rb +1 -1
  42. data/lib/jekyll/version.rb +1 -1
  43. data/lib/theme_template/theme.gemspec.erb +1 -4
  44. metadata +14 -31
@@ -116,7 +116,7 @@ module Jekyll
116
116
  #
117
117
  # Returns the output extension
118
118
  def output_ext
119
- @output_ext ||= Jekyll::Renderer.new(site, self).output_ext
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
- superdirs = relative_path.sub(Document.superdirs_regex(special_dir), "")
418
- superdirs = superdirs.split(File::SEPARATOR)
419
- superdirs.reject! { |c| c.empty? || c == special_dir || c == basename }
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")
@@ -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
- # Reject this entry if it is a symlink.
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
- # Reject this entry if it is special, a backup file, or excluded.
41
- special?(e) || backup?(e) || excluded?(e)
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
@@ -56,7 +56,7 @@ module Jekyll
56
56
  #
57
57
  # Returns true if the string passed in
58
58
  def include?(something)
59
- (output&.include?(something)) || content.include?(something)
59
+ output&.include?(something) || content.include?(something)
60
60
  end
61
61
 
62
62
  # The UID for this doc (useful in feeds).
@@ -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
- input.split.length
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
- if item.respond_to?(:to_liquid)
360
- property.to_s.split(".").reduce(item.to_liquid) do |subvalue, attribute|
361
- parse_sort_input(subvalue[attribute])
362
- end
363
- elsif item.respond_to?(:data)
364
- parse_sort_input(item.data[property.to_s])
365
- else
366
- parse_sort_input(item[property.to_s])
367
- end
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).tap do |condition|
427
- binary_operator = parser.id?("and") || parser.id?("or")
428
- condition.send(binary_operator, parse_comparison(parser)) if binary_operator
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
- input = input.url if input.respond_to?(:url)
15
- return input if Addressable::URI.parse(input.to_s).absolute?
16
-
17
- site = @context.registers[:site]
18
- return relative_url(input) if site.config["url"].nil?
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
- Addressable::URI.parse(
21
- site.config["url"].to_s + relative_url(input)
22
- ).normalize.to_s
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
- input = input.url if input.respond_to?(:url)
35
- return input if Addressable::URI.parse(input.to_s).absolute?
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
- parts = [sanitized_baseurl, input]
38
- Addressable::URI.parse(
39
- parts.compact.map { |part| ensure_leading_slash(part.to_s) }.join
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
- return true if !scope.key?("path") || scope["path"].empty?
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 = Pathname.new(sanitize_path(path))
109
- rel_scope_path = Pathname.new(scope["path"])
109
+ sanitized_path = sanitize_path(path)
110
110
 
111
- if scope["path"].to_s.include?("*")
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(scope["path"]))
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.ascend do |ascended_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 and adding a trailing slash
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.gsub(SANITIZATION_REGEX, "")
236
+ path
242
237
  end
243
238
  end
244
239
  end