jekyll 4.0.0.pre.beta1 → 4.2.0

Sign up to get free protection for your applications and to get access to all the features.
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