jekyll 3.8.7 → 4.1.0

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.
Files changed (104) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +71 -62
  3. data/LICENSE +1 -1
  4. data/README.markdown +46 -17
  5. data/lib/blank_template/_config.yml +3 -0
  6. data/lib/blank_template/_layouts/default.html +12 -0
  7. data/lib/blank_template/_sass/main.scss +9 -0
  8. data/lib/blank_template/assets/css/main.scss +4 -0
  9. data/lib/blank_template/index.md +8 -0
  10. data/lib/jekyll.rb +10 -1
  11. data/lib/jekyll/cache.rb +190 -0
  12. data/lib/jekyll/cleaner.rb +5 -4
  13. data/lib/jekyll/collection.rb +82 -10
  14. data/lib/jekyll/command.rb +33 -6
  15. data/lib/jekyll/commands/build.rb +11 -20
  16. data/lib/jekyll/commands/clean.rb +2 -0
  17. data/lib/jekyll/commands/doctor.rb +15 -8
  18. data/lib/jekyll/commands/help.rb +1 -1
  19. data/lib/jekyll/commands/new.rb +37 -35
  20. data/lib/jekyll/commands/new_theme.rb +30 -28
  21. data/lib/jekyll/commands/serve.rb +55 -81
  22. data/lib/jekyll/commands/serve/live_reload_reactor.rb +6 -10
  23. data/lib/jekyll/commands/serve/servlet.rb +22 -25
  24. data/lib/jekyll/commands/serve/websockets.rb +1 -1
  25. data/lib/jekyll/configuration.rb +61 -149
  26. data/lib/jekyll/converters/identity.rb +18 -0
  27. data/lib/jekyll/converters/markdown.rb +49 -40
  28. data/lib/jekyll/converters/markdown/kramdown_parser.rb +84 -11
  29. data/lib/jekyll/converters/smartypants.rb +34 -14
  30. data/lib/jekyll/convertible.rb +30 -31
  31. data/lib/jekyll/deprecator.rb +1 -3
  32. data/lib/jekyll/document.rb +89 -61
  33. data/lib/jekyll/drops/collection_drop.rb +2 -3
  34. data/lib/jekyll/drops/document_drop.rb +14 -1
  35. data/lib/jekyll/drops/drop.rb +17 -14
  36. data/lib/jekyll/drops/excerpt_drop.rb +4 -0
  37. data/lib/jekyll/drops/page_drop.rb +18 -0
  38. data/lib/jekyll/drops/site_drop.rb +6 -5
  39. data/lib/jekyll/drops/unified_payload_drop.rb +1 -0
  40. data/lib/jekyll/drops/url_drop.rb +53 -1
  41. data/lib/jekyll/entry_filter.rb +42 -45
  42. data/lib/jekyll/excerpt.rb +45 -34
  43. data/lib/jekyll/external.rb +10 -5
  44. data/lib/jekyll/filters.rb +200 -40
  45. data/lib/jekyll/filters/date_filters.rb +6 -3
  46. data/lib/jekyll/filters/grouping_filters.rb +1 -2
  47. data/lib/jekyll/filters/url_filters.rb +46 -14
  48. data/lib/jekyll/frontmatter_defaults.rb +46 -35
  49. data/lib/jekyll/hooks.rb +4 -8
  50. data/lib/jekyll/inclusion.rb +32 -0
  51. data/lib/jekyll/liquid_extensions.rb +0 -2
  52. data/lib/jekyll/liquid_renderer.rb +31 -16
  53. data/lib/jekyll/liquid_renderer/file.rb +24 -3
  54. data/lib/jekyll/liquid_renderer/table.rb +36 -77
  55. data/lib/jekyll/log_adapter.rb +5 -1
  56. data/lib/jekyll/mime.types +53 -11
  57. data/lib/jekyll/page.rb +54 -12
  58. data/lib/jekyll/page_excerpt.rb +26 -0
  59. data/lib/jekyll/page_without_a_file.rb +0 -4
  60. data/lib/jekyll/path_manager.rb +31 -0
  61. data/lib/jekyll/plugin.rb +5 -11
  62. data/lib/jekyll/plugin_manager.rb +2 -0
  63. data/lib/jekyll/profiler.rb +58 -0
  64. data/lib/jekyll/reader.rb +42 -9
  65. data/lib/jekyll/readers/collection_reader.rb +1 -0
  66. data/lib/jekyll/readers/data_reader.rb +8 -9
  67. data/lib/jekyll/readers/layout_reader.rb +3 -12
  68. data/lib/jekyll/readers/page_reader.rb +5 -5
  69. data/lib/jekyll/readers/post_reader.rb +31 -18
  70. data/lib/jekyll/readers/static_file_reader.rb +4 -4
  71. data/lib/jekyll/readers/theme_assets_reader.rb +8 -5
  72. data/lib/jekyll/regenerator.rb +4 -12
  73. data/lib/jekyll/renderer.rb +23 -40
  74. data/lib/jekyll/site.rb +91 -38
  75. data/lib/jekyll/static_file.rb +62 -21
  76. data/lib/jekyll/stevenson.rb +2 -3
  77. data/lib/jekyll/tags/highlight.rb +19 -51
  78. data/lib/jekyll/tags/include.rb +82 -42
  79. data/lib/jekyll/tags/link.rb +11 -7
  80. data/lib/jekyll/tags/post_url.rb +25 -21
  81. data/lib/jekyll/theme.rb +16 -18
  82. data/lib/jekyll/theme_builder.rb +91 -89
  83. data/lib/jekyll/url.rb +10 -5
  84. data/lib/jekyll/utils.rb +18 -21
  85. data/lib/jekyll/utils/ansi.rb +1 -1
  86. data/lib/jekyll/utils/exec.rb +0 -1
  87. data/lib/jekyll/utils/internet.rb +2 -4
  88. data/lib/jekyll/utils/platforms.rb +8 -8
  89. data/lib/jekyll/utils/thread_event.rb +1 -5
  90. data/lib/jekyll/utils/win_tz.rb +2 -2
  91. data/lib/jekyll/version.rb +1 -1
  92. data/lib/site_template/.gitignore +2 -0
  93. data/lib/site_template/404.html +1 -0
  94. data/lib/site_template/_config.yml +17 -5
  95. data/lib/site_template/_posts/0000-00-00-welcome-to-jekyll.markdown.erb +5 -1
  96. data/lib/site_template/{about.md → about.markdown} +0 -0
  97. data/lib/site_template/{index.md → index.markdown} +0 -0
  98. data/lib/theme_template/gitignore.erb +1 -0
  99. data/lib/theme_template/theme.gemspec.erb +1 -4
  100. data/rubocop/jekyll/assert_equal_literal_actual.rb +149 -0
  101. metadata +69 -31
  102. data/lib/jekyll/converters/markdown/rdiscount_parser.rb +0 -37
  103. data/lib/jekyll/converters/markdown/redcarpet_parser.rb +0 -112
  104. data/lib/jekyll/utils/rouge.rb +0 -22
@@ -10,13 +10,17 @@ module Jekyll
10
10
  # Returns the absolute URL as a String.
11
11
  def absolute_url(input)
12
12
  return if input.nil?
13
- input = input.url if input.respond_to?(:url)
14
- return input if Addressable::URI.parse(input.to_s).absolute?
15
- site = @context.registers[:site]
16
- return relative_url(input) if site.config["url"].nil?
17
- Addressable::URI.parse(
18
- site.config["url"].to_s + relative_url(input)
19
- ).normalize.to_s
13
+
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)
20
+
21
+ # Duplicate cached string so that the cached value is never mutated by
22
+ # a subsequent filter.
23
+ cache[input].dup
20
24
  end
21
25
 
22
26
  # Produces a URL relative to the domain root based on site.baseurl
@@ -27,13 +31,17 @@ module Jekyll
27
31
  # Returns a URL relative to the domain root as a String.
28
32
  def relative_url(input)
29
33
  return if input.nil?
30
- input = input.url if input.respond_to?(:url)
31
- return input if Addressable::URI.parse(input.to_s).absolute?
32
34
 
33
- parts = [sanitized_baseurl, input]
34
- Addressable::URI.parse(
35
- parts.compact.map { |part| ensure_leading_slash(part.to_s) }.join
36
- ).normalize.to_s
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)
41
+
42
+ # Duplicate cached string so that the cached value is never mutated by
43
+ # a subsequent filter.
44
+ cache[input].dup
37
45
  end
38
46
 
39
47
  # Strips trailing `/index.html` from URLs to create pretty permalinks
@@ -43,11 +51,35 @@ module Jekyll
43
51
  # Returns a URL with the trailing `/index.html` removed
44
52
  def strip_index(input)
45
53
  return if input.nil? || input.to_s.empty?
54
+
46
55
  input.sub(%r!/index\.html?$!, "/")
47
56
  end
48
57
 
49
58
  private
50
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
+
51
83
  def sanitized_baseurl
52
84
  site = @context.registers[:site]
53
85
  site.config["baseurl"].to_s.chomp("/")
@@ -55,9 +87,9 @@ module Jekyll
55
87
 
56
88
  def ensure_leading_slash(input)
57
89
  return input if input.nil? || input.empty? || input.start_with?("/")
90
+
58
91
  "/#{input}"
59
92
  end
60
-
61
93
  end
62
94
  end
63
95
  end
@@ -12,6 +12,10 @@ module Jekyll
12
12
  @site = site
13
13
  end
14
14
 
15
+ def reset
16
+ @glob_cache = {} if @glob_cache
17
+ end
18
+
15
19
  def update_deprecated_types(set)
16
20
  return set unless set.key?("scope") && set["scope"].key?("type")
17
21
 
@@ -36,6 +40,7 @@ module Jekyll
36
40
  def ensure_time!(set)
37
41
  return set unless set.key?("values") && set["values"].key?("date")
38
42
  return set if set["values"]["date"].is_a?(Time)
43
+
39
44
  set["values"]["date"] = Utils.parse_date(
40
45
  set["values"]["date"],
41
46
  "An invalid date format was found in a front-matter default set: #{set}"
@@ -92,48 +97,51 @@ module Jekyll
92
97
  # path - the path to check for
93
98
  # type - the type (:post, :page or :draft) to check for
94
99
  #
95
- # Returns true if the scope applies to the given path and type
100
+ # Returns true if the scope applies to the given type and path
96
101
  def applies?(scope, path, type)
97
- applies_path?(scope, path) && applies_type?(scope, type)
102
+ applies_type?(scope, type) && applies_path?(scope, path)
98
103
  end
99
104
 
100
- # rubocop:disable Metrics/AbcSize
101
105
  def applies_path?(scope, path)
102
- return true if !scope.key?("path") || scope["path"].empty?
103
-
104
- sanitized_path = Pathname.new(sanitize_path(path))
105
- site_path = Pathname.new(@site.source)
106
- rel_scope_path = Pathname.new(scope["path"])
107
- abs_scope_path = File.join(@site.source, rel_scope_path)
108
-
109
- if scope["path"].to_s.include?("*")
110
- Dir.glob(abs_scope_path).each do |scope_path|
111
- scope_path = Pathname.new(scope_path).relative_path_from(site_path)
112
- scope_path = strip_collections_dir(scope_path)
113
- Jekyll.logger.debug "Globbed Scope Path:", scope_path
114
- return true if path_is_subpath?(sanitized_path, scope_path)
115
- end
116
- false
106
+ rel_scope_path = scope["path"]
107
+ return true if !rel_scope_path.is_a?(String) || rel_scope_path.empty?
108
+
109
+ sanitized_path = sanitize_path(path)
110
+
111
+ if rel_scope_path.include?("*")
112
+ glob_scope(sanitized_path, rel_scope_path)
117
113
  else
118
114
  path_is_subpath?(sanitized_path, strip_collections_dir(rel_scope_path))
119
115
  end
120
116
  end
121
- # rubocop:enable Metrics/AbcSize
122
117
 
123
- def path_is_subpath?(path, parent_path)
124
- path.ascend do |ascended_path|
125
- if ascended_path.to_s == parent_path.to_s
126
- return true
127
- end
128
- end
118
+ def glob_scope(sanitized_path, rel_scope_path)
119
+ site_source = Pathname.new(@site.source)
120
+ abs_scope_path = site_source.join(rel_scope_path).to_s
129
121
 
122
+ glob_cache(abs_scope_path).each do |scope_path|
123
+ scope_path = Pathname.new(scope_path).relative_path_from(site_source).to_s
124
+ scope_path = strip_collections_dir(scope_path)
125
+ Jekyll.logger.debug "Globbed Scope Path:", scope_path
126
+ return true if path_is_subpath?(sanitized_path, scope_path)
127
+ end
130
128
  false
131
129
  end
132
130
 
131
+ def glob_cache(path)
132
+ @glob_cache ||= {}
133
+ @glob_cache[path] ||= Dir.glob(path)
134
+ end
135
+
136
+ def path_is_subpath?(path, parent_path)
137
+ path.start_with?(parent_path)
138
+ end
139
+
133
140
  def strip_collections_dir(path)
134
141
  collections_dir = @site.config["collections_dir"]
135
- slashed_coll_dir = "#{collections_dir}/"
142
+ slashed_coll_dir = collections_dir.empty? ? "/" : "#{collections_dir}/"
136
143
  return path if collections_dir.empty? || !path.to_s.start_with?(slashed_coll_dir)
144
+
137
145
  path.sub(slashed_coll_dir, "")
138
146
  end
139
147
 
@@ -167,7 +175,7 @@ module Jekyll
167
175
  # new_scope - the new scope hash
168
176
  #
169
177
  # Returns true if the new scope has precedence over the older
170
- # rubocop: disable PredicateName
178
+ # rubocop: disable Naming/PredicateName
171
179
  def has_precedence?(old_scope, new_scope)
172
180
  return true if old_scope.nil?
173
181
 
@@ -182,14 +190,18 @@ module Jekyll
182
190
  !old_scope.key? "type"
183
191
  end
184
192
  end
185
- # rubocop: enable PredicateName
193
+ # rubocop: enable Naming/PredicateName
186
194
 
187
195
  # Collects a list of sets that match the given path and type
188
196
  #
189
197
  # Returns an array of hashes
190
198
  def matching_sets(path, type)
191
- valid_sets.select do |set|
192
- !set.key?("scope") || applies?(set["scope"], path, type)
199
+ @matched_set_cache ||= {}
200
+ @matched_set_cache[path] ||= {}
201
+ @matched_set_cache[path][type] ||= begin
202
+ valid_sets.select do |set|
203
+ !set.key?("scope") || applies?(set["scope"], path, type)
204
+ end
193
205
  end
194
206
  end
195
207
 
@@ -214,15 +226,14 @@ module Jekyll
214
226
  end.compact
215
227
  end
216
228
 
217
- # Sanitizes the given path by removing a leading and adding a trailing slash
218
-
219
- SANITIZATION_REGEX = %r!\A/|(?<=[^/])\z!
220
-
229
+ # Sanitizes the given path by removing a leading slash
221
230
  def sanitize_path(path)
222
231
  if path.nil? || path.empty?
223
232
  ""
233
+ elsif path.start_with?("/")
234
+ path.gsub(%r!\A/|(?<=[^/])\z!, "")
224
235
  else
225
- path.gsub(SANITIZATION_REGEX, "")
236
+ path
226
237
  end
227
238
  end
228
239
  end
@@ -60,6 +60,7 @@ module Jekyll
60
60
  # Ensure the priority is a Fixnum
61
61
  def self.priority_value(priority)
62
62
  return priority if priority.is_a?(Integer)
63
+
63
64
  PRIORITY_MAP[priority] || DEFAULT_PRIORITY
64
65
  end
65
66
 
@@ -77,9 +78,7 @@ module Jekyll
77
78
  "following hooks #{@registry[owner].keys.inspect}"
78
79
  end
79
80
 
80
- unless block.respond_to? :call
81
- raise Uncallable, "Hooks must respond to :call"
82
- end
81
+ raise Uncallable, "Hooks must respond to :call" unless block.respond_to? :call
83
82
 
84
83
  insert_hook owner, event, priority, &block
85
84
  end
@@ -92,11 +91,8 @@ module Jekyll
92
91
  # interface for Jekyll core components to trigger hooks
93
92
  def self.trigger(owner, event, *args)
94
93
  # proceed only if there are hooks to call
95
- return unless @registry[owner]
96
- return unless @registry[owner][event]
97
-
98
- # hooks to call for this owner and event
99
- hooks = @registry[owner][event]
94
+ hooks = @registry.dig(owner, event)
95
+ return if hooks.nil? || hooks.empty?
100
96
 
101
97
  # sort and call hooks according to priority and load order
102
98
  hooks.sort_by { |h| @hook_priority[h] }.each do |hook|
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jekyll
4
+ class Inclusion
5
+ attr_reader :site, :name, :path
6
+ private :site
7
+
8
+ def initialize(site, base, name)
9
+ @site = site
10
+ @name = name
11
+ @path = PathManager.join(base, name)
12
+ end
13
+
14
+ def render(context)
15
+ @template ||= site.liquid_renderer.file(path).parse(content)
16
+ @template.render!(context)
17
+ rescue Liquid::Error => e
18
+ e.template_name = path
19
+ e.markup_context = "included " if e.markup_context.nil?
20
+ raise e
21
+ end
22
+
23
+ def content
24
+ @content ||= File.read(path, **site.file_read_opts)
25
+ end
26
+
27
+ def inspect
28
+ "#{self.class} #{path.inspect}"
29
+ end
30
+ alias_method :to_s, :inspect
31
+ end
32
+ end
@@ -2,7 +2,6 @@
2
2
 
3
3
  module Jekyll
4
4
  module LiquidExtensions
5
-
6
5
  # Lookup a Liquid variable in the given context.
7
6
  #
8
7
  # context - the Liquid context in question.
@@ -19,6 +18,5 @@ module Jekyll
19
18
 
20
19
  lookup || variable
21
20
  end
22
-
23
21
  end
24
22
  end
@@ -5,11 +5,6 @@ require_relative "liquid_renderer/table"
5
5
 
6
6
  module Jekyll
7
7
  class LiquidRenderer
8
- extend Forwardable
9
-
10
- private def_delegator :@site, :in_source_dir, :source_dir
11
- private def_delegator :@site, :in_theme_dir, :theme_dir
12
-
13
8
  def initialize(site)
14
9
  @site = site
15
10
  Liquid::Template.error_mode = @site.config["liquid"]["error_mode"].to_sym
@@ -18,19 +13,13 @@ module Jekyll
18
13
 
19
14
  def reset
20
15
  @stats = {}
16
+ @cache = {}
21
17
  end
22
18
 
23
19
  def file(filename)
24
- filename.match(filename_regex)
25
- filename =
26
- if Regexp.last_match(1) == theme_dir("")
27
- ::File.join(::File.basename(Regexp.last_match(1)), Regexp.last_match(2))
28
- else
29
- Regexp.last_match(2)
30
- end
20
+ filename = normalize_path(filename)
31
21
  LiquidRenderer::File.new(self, filename).tap do
32
22
  @stats[filename] ||= new_profile_hash
33
- @stats[filename][:count] += 1
34
23
  end
35
24
  end
36
25
 
@@ -42,6 +31,10 @@ module Jekyll
42
31
  @stats[filename][:time] += time
43
32
  end
44
33
 
34
+ def increment_count(filename)
35
+ @stats[filename][:count] += 1
36
+ end
37
+
45
38
  def stats_table(num_of_rows = 50)
46
39
  LiquidRenderer::Table.new(@stats).to_s(num_of_rows)
47
40
  end
@@ -50,11 +43,33 @@ module Jekyll
50
43
  "#{error.message} in #{path}"
51
44
  end
52
45
 
46
+ # A persistent cache to store and retrieve parsed templates based on the filename
47
+ # via `LiquidRenderer::File#parse`
48
+ #
49
+ # It is emptied when `self.reset` is called.
50
+ def cache
51
+ @cache ||= {}
52
+ end
53
+
53
54
  private
54
55
 
55
- def filename_regex
56
- @filename_regex ||= begin
57
- %r!\A(#{Regexp.escape(source_dir)}/|#{Regexp.escape(theme_dir.to_s)}/|/*)(.*)!i
56
+ def normalize_path(filename)
57
+ @normalize_path ||= {}
58
+ @normalize_path[filename] ||= begin
59
+ theme_dir = @site.theme&.root
60
+ case filename
61
+ when %r!\A(#{Regexp.escape(@site.source)}/)(?<rest>.*)!io
62
+ Regexp.last_match(:rest)
63
+ when %r!(/gems/.*)*/gems/(?<dirname>[^/]+)(?<rest>.*)!,
64
+ %r!(?<dirname>[^/]+/lib)(?<rest>.*)!
65
+ "#{Regexp.last_match(:dirname)}#{Regexp.last_match(:rest)}"
66
+ when theme_dir && %r!\A#{Regexp.escape(theme_dir)}/(?<rest>.*)!io
67
+ PathManager.join(@site.theme.basename, Regexp.last_match(:rest))
68
+ when %r!\A/(.*)!
69
+ Regexp.last_match(1)
70
+ else
71
+ filename
72
+ end
58
73
  end
59
74
  end
60
75
 
@@ -10,24 +10,34 @@ module Jekyll
10
10
 
11
11
  def parse(content)
12
12
  measure_time do
13
- @template = Liquid::Template.parse(content, :line_numbers => true)
13
+ @renderer.cache[@filename] ||= Liquid::Template.parse(content, :line_numbers => true)
14
14
  end
15
+ @template = @renderer.cache[@filename]
15
16
 
16
17
  self
17
18
  end
18
19
 
19
20
  def render(*args)
21
+ reset_template_assigns
22
+
20
23
  measure_time do
21
24
  measure_bytes do
22
- @template.render(*args)
25
+ measure_counts do
26
+ @template.render(*args)
27
+ end
23
28
  end
24
29
  end
25
30
  end
26
31
 
32
+ # This method simply 'rethrows any error' before attempting to render the template.
27
33
  def render!(*args)
34
+ reset_template_assigns
35
+
28
36
  measure_time do
29
37
  measure_bytes do
30
- @template.render!(*args)
38
+ measure_counts do
39
+ @template.render!(*args)
40
+ end
31
41
  end
32
42
  end
33
43
  end
@@ -38,6 +48,17 @@ module Jekyll
38
48
 
39
49
  private
40
50
 
51
+ # clear assigns to `Liquid::Template` instance prior to rendering since
52
+ # `Liquid::Template` instances are cached in Jekyll 4.
53
+ def reset_template_assigns
54
+ @template.instance_assigns.clear
55
+ end
56
+
57
+ def measure_counts
58
+ @renderer.increment_count(@filename)
59
+ yield
60
+ end
61
+
41
62
  def measure_bytes
42
63
  yield.tap do |str|
43
64
  @renderer.increment_bytes(@filename, str.bytesize)