jekyll 4.0.0.pre.beta1 → 4.2.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 (61) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +204 -18
  3. data/README.markdown +2 -6
  4. data/lib/blank_template/_layouts/default.html +1 -1
  5. data/lib/jekyll.rb +6 -17
  6. data/lib/jekyll/cleaner.rb +3 -3
  7. data/lib/jekyll/collection.rb +2 -2
  8. data/lib/jekyll/command.rb +4 -2
  9. data/lib/jekyll/commands/doctor.rb +19 -15
  10. data/lib/jekyll/commands/new.rb +4 -4
  11. data/lib/jekyll/commands/new_theme.rb +0 -2
  12. data/lib/jekyll/commands/serve.rb +12 -1
  13. data/lib/jekyll/configuration.rb +18 -19
  14. data/lib/jekyll/converters/identity.rb +2 -2
  15. data/lib/jekyll/converters/markdown/kramdown_parser.rb +70 -1
  16. data/lib/jekyll/convertible.rb +30 -23
  17. data/lib/jekyll/document.rb +41 -19
  18. data/lib/jekyll/drops/collection_drop.rb +3 -3
  19. data/lib/jekyll/drops/document_drop.rb +4 -3
  20. data/lib/jekyll/drops/drop.rb +98 -20
  21. data/lib/jekyll/drops/site_drop.rb +3 -3
  22. data/lib/jekyll/drops/static_file_drop.rb +4 -4
  23. data/lib/jekyll/drops/url_drop.rb +11 -3
  24. data/lib/jekyll/entry_filter.rb +18 -7
  25. data/lib/jekyll/excerpt.rb +1 -1
  26. data/lib/jekyll/filters.rb +112 -28
  27. data/lib/jekyll/filters/url_filters.rb +45 -15
  28. data/lib/jekyll/frontmatter_defaults.rb +14 -19
  29. data/lib/jekyll/hooks.rb +22 -21
  30. data/lib/jekyll/inclusion.rb +32 -0
  31. data/lib/jekyll/layout.rb +5 -0
  32. data/lib/jekyll/liquid_renderer.rb +18 -15
  33. data/lib/jekyll/liquid_renderer/file.rb +10 -0
  34. data/lib/jekyll/liquid_renderer/table.rb +1 -64
  35. data/lib/jekyll/page.rb +42 -11
  36. data/lib/jekyll/page_excerpt.rb +25 -0
  37. data/lib/jekyll/path_manager.rb +53 -10
  38. data/lib/jekyll/profiler.rb +58 -0
  39. data/lib/jekyll/reader.rb +11 -6
  40. data/lib/jekyll/readers/collection_reader.rb +1 -0
  41. data/lib/jekyll/readers/data_reader.rb +4 -0
  42. data/lib/jekyll/readers/layout_reader.rb +1 -0
  43. data/lib/jekyll/readers/page_reader.rb +1 -0
  44. data/lib/jekyll/readers/post_reader.rb +2 -1
  45. data/lib/jekyll/readers/static_file_reader.rb +1 -0
  46. data/lib/jekyll/readers/theme_assets_reader.rb +1 -0
  47. data/lib/jekyll/related_posts.rb +1 -1
  48. data/lib/jekyll/renderer.rb +15 -17
  49. data/lib/jekyll/site.rb +34 -10
  50. data/lib/jekyll/static_file.rb +17 -12
  51. data/lib/jekyll/tags/include.rb +82 -33
  52. data/lib/jekyll/tags/link.rb +2 -1
  53. data/lib/jekyll/tags/post_url.rb +3 -4
  54. data/lib/jekyll/theme.rb +6 -8
  55. data/lib/jekyll/url.rb +8 -5
  56. data/lib/jekyll/utils.rb +5 -5
  57. data/lib/jekyll/utils/platforms.rb +34 -49
  58. data/lib/jekyll/utils/win_tz.rb +1 -1
  59. data/lib/jekyll/version.rb +1 -1
  60. data/lib/theme_template/theme.gemspec.erb +1 -4
  61. metadata +34 -39
@@ -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,9 +57,35 @@ 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.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
- site.config["baseurl"].to_s.chomp("/")
85
+ baseurl = site.config["baseurl"]
86
+ return "" if baseurl.nil?
87
+
88
+ baseurl.to_s.chomp("/")
59
89
  end
60
90
 
61
91
  def ensure_leading_slash(input)
@@ -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)
@@ -161,7 +157,7 @@ module Jekyll
161
157
  # Returns true if either of the above conditions are satisfied,
162
158
  # otherwise returns false
163
159
  def applies_type?(scope, type)
164
- !scope.key?("type") || scope["type"].eql?(type.to_s)
160
+ !scope.key?("type") || type&.to_sym.eql?(scope["type"].to_sym)
165
161
  end
166
162
 
167
163
  # Checks if a given set of default values is valid
@@ -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
  #
@@ -227,18 +223,17 @@ module Jekyll
227
223
  Jekyll.logger.warn set.to_s
228
224
  nil
229
225
  end
230
- end.compact
226
+ end.tap(&: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
@@ -22,22 +22,25 @@ module Jekyll
22
22
  :post_write => [],
23
23
  },
24
24
  :pages => {
25
- :post_init => [],
26
- :pre_render => [],
27
- :post_render => [],
28
- :post_write => [],
25
+ :post_init => [],
26
+ :pre_render => [],
27
+ :post_convert => [],
28
+ :post_render => [],
29
+ :post_write => [],
29
30
  },
30
31
  :posts => {
31
- :post_init => [],
32
- :pre_render => [],
33
- :post_render => [],
34
- :post_write => [],
32
+ :post_init => [],
33
+ :pre_render => [],
34
+ :post_convert => [],
35
+ :post_render => [],
36
+ :post_write => [],
35
37
  },
36
38
  :documents => {
37
- :post_init => [],
38
- :pre_render => [],
39
- :post_render => [],
40
- :post_write => [],
39
+ :post_init => [],
40
+ :pre_render => [],
41
+ :post_convert => [],
42
+ :post_render => [],
43
+ :post_write => [],
41
44
  },
42
45
  :clean => {
43
46
  :on_obsolete => [],
@@ -67,10 +70,11 @@ module Jekyll
67
70
  # register a single hook to be called later, internal API
68
71
  def self.register_one(owner, event, priority, &block)
69
72
  @registry[owner] ||= {
70
- :post_init => [],
71
- :pre_render => [],
72
- :post_render => [],
73
- :post_write => [],
73
+ :post_init => [],
74
+ :pre_render => [],
75
+ :post_convert => [],
76
+ :post_render => [],
77
+ :post_write => [],
74
78
  }
75
79
 
76
80
  unless @registry[owner][event]
@@ -91,11 +95,8 @@ module Jekyll
91
95
  # interface for Jekyll core components to trigger hooks
92
96
  def self.trigger(owner, event, *args)
93
97
  # proceed only if there are hooks to call
94
- return unless @registry[owner]
95
- return unless @registry[owner][event]
96
-
97
- # hooks to call for this owner and event
98
- hooks = @registry[owner][event]
98
+ hooks = @registry.dig(owner, event)
99
+ return if hooks.nil? || hooks.empty?
99
100
 
100
101
  # sort and call hooks according to priority and load order
101
102
  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
@@ -58,5 +58,10 @@ module Jekyll
58
58
  def process(name)
59
59
  self.ext = File.extname(name)
60
60
  end
61
+
62
+ # Returns the object as a debug String.
63
+ def inspect
64
+ "#<#{self.class} @path=#{@path.inspect}>"
65
+ end
61
66
  end
62
67
  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
@@ -22,13 +17,7 @@ module Jekyll
22
17
  end
23
18
 
24
19
  def file(filename)
25
- filename.match(filename_regex)
26
- filename =
27
- if Regexp.last_match(1) == theme_dir("")
28
- ::File.join(::File.basename(Regexp.last_match(1)), Regexp.last_match(2))
29
- else
30
- Regexp.last_match(2)
31
- end
20
+ filename = normalize_path(filename)
32
21
  LiquidRenderer::File.new(self, filename).tap do
33
22
  @stats[filename] ||= new_profile_hash
34
23
  end
@@ -64,9 +53,23 @@ module Jekyll
64
53
 
65
54
  private
66
55
 
67
- def filename_regex
68
- @filename_regex ||= begin
69
- %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
70
73
  end
71
74
  end
72
75
 
@@ -18,6 +18,8 @@ module Jekyll
18
18
  end
19
19
 
20
20
  def render(*args)
21
+ reset_template_assigns
22
+
21
23
  measure_time do
22
24
  measure_bytes do
23
25
  measure_counts do
@@ -29,6 +31,8 @@ module Jekyll
29
31
 
30
32
  # This method simply 'rethrows any error' before attempting to render the template.
31
33
  def render!(*args)
34
+ reset_template_assigns
35
+
32
36
  measure_time do
33
37
  measure_bytes do
34
38
  measure_counts do
@@ -44,6 +48,12 @@ module Jekyll
44
48
 
45
49
  private
46
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
+
47
57
  def measure_counts
48
58
  @renderer.increment_count(@filename)
49
59
  yield
@@ -10,74 +10,11 @@ module Jekyll
10
10
  end
11
11
 
12
12
  def to_s(num_of_rows = 50)
13
- data = data_for_table(num_of_rows)
14
- widths = table_widths(data)
15
- generate_table(data, widths)
13
+ Jekyll::Profiler.tabulate(data_for_table(num_of_rows))
16
14
  end
17
15
 
18
16
  private
19
17
 
20
- def generate_table(data, widths)
21
- str = +"\n"
22
-
23
- table_head = data.shift
24
- table_foot = data.pop
25
-
26
- str << generate_row(table_head, widths)
27
- str << generate_table_head_border(table_head, widths)
28
-
29
- data.each do |row_data|
30
- str << generate_row(row_data, widths)
31
- end
32
-
33
- str << generate_table_head_border(table_foot, widths)
34
- str << generate_row(table_foot, widths).rstrip
35
-
36
- str << "\n"
37
- str
38
- end
39
-
40
- def generate_table_head_border(row_data, widths)
41
- str = +""
42
-
43
- row_data.each_index do |cell_index|
44
- str << "-" * widths[cell_index]
45
- str << "-+-" unless cell_index == row_data.length - 1
46
- end
47
-
48
- str << "\n"
49
- str
50
- end
51
-
52
- def generate_row(row_data, widths)
53
- str = +""
54
-
55
- row_data.each_with_index do |cell_data, cell_index|
56
- str << if cell_index.zero?
57
- cell_data.ljust(widths[cell_index], " ")
58
- else
59
- cell_data.rjust(widths[cell_index], " ")
60
- end
61
-
62
- str << " | " unless cell_index == row_data.length - 1
63
- end
64
-
65
- str << "\n"
66
- str
67
- end
68
-
69
- def table_widths(data)
70
- widths = []
71
-
72
- data.each do |row|
73
- row.each_with_index do |cell, index|
74
- widths[index] = [cell.length, widths[index]].compact.max
75
- end
76
- end
77
-
78
- widths
79
- end
80
-
81
18
  # rubocop:disable Metrics/AbcSize
82
19
  def data_for_table(num_of_rows)
83
20
  sorted = @stats.sort_by { |_, file_stats| -file_stats[:time] }
@@ -15,6 +15,7 @@ module Jekyll
15
15
  ATTRIBUTES_FOR_LIQUID = %w(
16
16
  content
17
17
  dir
18
+ excerpt
18
19
  name
19
20
  path
20
21
  url
@@ -48,6 +49,7 @@ module Jekyll
48
49
 
49
50
  process(name)
50
51
  read_yaml(PathManager.join(base, dir), name)
52
+ generate_excerpt if site.config["page_excerpts"]
51
53
 
52
54
  data.default_proc = proc do |_, key|
53
55
  site.frontmatter_defaults.find(relative_path, type, key)
@@ -62,12 +64,7 @@ module Jekyll
62
64
  #
63
65
  # Returns the String destination directory.
64
66
  def dir
65
- if url.end_with?("/")
66
- url
67
- else
68
- url_dir = File.dirname(url)
69
- url_dir.end_with?("/") ? url_dir : "#{url_dir}/"
70
- end
67
+ url.end_with?("/") ? url : url_dir
71
68
  end
72
69
 
73
70
  # The full path and filename of the post. Defined in the YAML of the post
@@ -119,6 +116,8 @@ module Jekyll
119
116
  # NOTE: `String#gsub` removes all trailing periods (in comparison to `String#chomp`)
120
117
  # Returns nothing.
121
118
  def process(name)
119
+ return unless name
120
+
122
121
  self.ext = File.extname(name)
123
122
  self.basename = name[0..-ext.length - 1].gsub(%r!\.*\z!, "")
124
123
  end
@@ -145,7 +144,7 @@ module Jekyll
145
144
 
146
145
  # The path to the page source file, relative to the site source
147
146
  def relative_path
148
- @relative_path ||= File.join(*[@dir, @name].map(&:to_s).reject(&:empty?)).sub(%r!\A\/!, "")
147
+ @relative_path ||= PathManager.join(@dir, @name).sub(%r!\A/!, "")
149
148
  end
150
149
 
151
150
  # Obtain destination path.
@@ -154,10 +153,13 @@ module Jekyll
154
153
  #
155
154
  # Returns the destination file path String.
156
155
  def destination(dest)
157
- path = site.in_dest_dir(dest, URL.unescape_path(url))
158
- path = File.join(path, "index") if url.end_with?("/")
159
- path << output_ext unless path.end_with? output_ext
160
- path
156
+ @destination ||= {}
157
+ @destination[dest] ||= begin
158
+ path = site.in_dest_dir(dest, URL.unescape_path(url))
159
+ path = File.join(path, "index") if url.end_with?("/")
160
+ path << output_ext unless path.end_with? output_ext
161
+ path
162
+ end
161
163
  end
162
164
 
163
165
  # Returns the object as a debug String.
@@ -182,5 +184,34 @@ module Jekyll
182
184
  def write?
183
185
  true
184
186
  end
187
+
188
+ def excerpt_separator
189
+ @excerpt_separator ||= (data["excerpt_separator"] || site.config["excerpt_separator"]).to_s
190
+ end
191
+
192
+ def excerpt
193
+ return @excerpt if defined?(@excerpt)
194
+
195
+ @excerpt = data["excerpt"] ? data["excerpt"].to_s : nil
196
+ end
197
+
198
+ def generate_excerpt?
199
+ !excerpt_separator.empty? && instance_of?(Jekyll::Page) && html?
200
+ end
201
+
202
+ private
203
+
204
+ def generate_excerpt
205
+ return unless generate_excerpt?
206
+
207
+ data["excerpt"] ||= Jekyll::PageExcerpt.new(self)
208
+ end
209
+
210
+ def url_dir
211
+ @url_dir ||= begin
212
+ value = File.dirname(url)
213
+ value.end_with?("/") ? value : "#{value}/"
214
+ end
215
+ end
185
216
  end
186
217
  end