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
@@ -4,7 +4,7 @@ module Jekyll
4
4
  class StaticFile
5
5
  extend Forwardable
6
6
 
7
- attr_reader :relative_path, :extname, :name, :data
7
+ attr_reader :relative_path, :extname, :name
8
8
 
9
9
  def_delegator :to_liquid, :to_json, :to_json
10
10
 
@@ -25,7 +25,7 @@ module Jekyll
25
25
  # base - The String path to the <source>.
26
26
  # dir - The String path between <source> and the file.
27
27
  # name - The String filename of the file.
28
- # rubocop: disable ParameterLists
28
+ # rubocop: disable Metrics/ParameterLists
29
29
  def initialize(site, base, dir, name, collection = nil)
30
30
  @site = site
31
31
  @base = base
@@ -34,17 +34,18 @@ module Jekyll
34
34
  @collection = collection
35
35
  @relative_path = File.join(*[@dir, @name].compact)
36
36
  @extname = File.extname(@name)
37
- @data = @site.frontmatter_defaults.all(relative_path, type)
38
37
  end
39
- # rubocop: enable ParameterLists
38
+ # rubocop: enable Metrics/ParameterLists
40
39
 
41
40
  # Returns source file path.
42
41
  def path
43
- # Static file is from a collection inside custom collections directory
44
- if !@collection.nil? && !@site.config["collections_dir"].empty?
45
- File.join(*[@base, @site.config["collections_dir"], @dir, @name].compact)
46
- else
47
- File.join(*[@base, @dir, @name].compact)
42
+ @path ||= begin
43
+ # Static file is from a collection inside custom collections directory
44
+ if !@collection.nil? && !@site.config["collections_dir"].empty?
45
+ File.join(*[@base, @site.config["collections_dir"], @dir, @name].compact)
46
+ else
47
+ File.join(*[@base, @dir, @name].compact)
48
+ end
48
49
  end
49
50
  end
50
51
 
@@ -54,7 +55,8 @@ module Jekyll
54
55
  #
55
56
  # Returns destination file path.
56
57
  def destination(dest)
57
- @site.in_dest_dir(*[dest, destination_rel_dir, @name].compact)
58
+ dest = @site.in_dest_dir(dest)
59
+ @site.in_dest_dir(dest, Jekyll::URL.unescape_path(url))
58
60
  end
59
61
 
60
62
  def destination_rel_dir
@@ -86,7 +88,10 @@ module Jekyll
86
88
  # Returns true unless the defaults for the destination path from
87
89
  # _config.yml contain `published: false`.
88
90
  def write?
89
- defaults.fetch("published", true)
91
+ publishable = defaults.fetch("published", true)
92
+ return publishable unless @collection
93
+
94
+ publishable && @collection.write?
90
95
  end
91
96
 
92
97
  # Write the static file to the destination directory (if modified).
@@ -96,8 +101,8 @@ module Jekyll
96
101
  # Returns false if the file was not modified since last time (no-op).
97
102
  def write(dest)
98
103
  dest_path = destination(dest)
99
-
100
104
  return false if File.exist?(dest_path) && !modified?
105
+
101
106
  self.class.mtimes[path] = mtime
102
107
 
103
108
  FileUtils.mkdir_p(File.dirname(dest_path))
@@ -107,37 +112,66 @@ module Jekyll
107
112
  true
108
113
  end
109
114
 
115
+ def data
116
+ @data ||= @site.frontmatter_defaults.all(relative_path, type)
117
+ end
118
+
110
119
  def to_liquid
111
120
  @to_liquid ||= Drops::StaticFileDrop.new(self)
112
121
  end
113
122
 
123
+ # Generate "basename without extension" and strip away any trailing periods.
124
+ # NOTE: `String#gsub` removes all trailing periods (in comparison to `String#chomp`)
114
125
  def basename
115
- File.basename(name, extname)
126
+ @basename ||= File.basename(name, extname).gsub(%r!\.*\z!, "")
116
127
  end
117
128
 
118
129
  def placeholders
119
130
  {
120
131
  :collection => @collection.label,
121
- :path => relative_path[
122
- @collection.relative_directory.size..relative_path.size],
132
+ :path => cleaned_relative_path,
123
133
  :output_ext => "",
124
- :name => "",
134
+ :name => basename,
125
135
  :title => "",
126
136
  }
127
137
  end
128
138
 
139
+ # Similar to Jekyll::Document#cleaned_relative_path.
140
+ # Generates a relative path with the collection's directory removed when applicable
141
+ # and additionally removes any multiple periods in the string.
142
+ #
143
+ # NOTE: `String#gsub!` removes all trailing periods (in comparison to `String#chomp!`)
144
+ #
145
+ # Examples:
146
+ # When `relative_path` is "_methods/site/my-cool-avatar...png":
147
+ # cleaned_relative_path
148
+ # # => "/site/my-cool-avatar"
149
+ #
150
+ # Returns the cleaned relative path of the static file.
151
+ def cleaned_relative_path
152
+ @cleaned_relative_path ||= begin
153
+ cleaned = relative_path[0..-extname.length - 1]
154
+ cleaned.gsub!(%r!\.*\z!, "")
155
+ cleaned.sub!(@collection.relative_directory, "") if @collection
156
+ cleaned
157
+ end
158
+ end
159
+
129
160
  # Applies a similar URL-building technique as Jekyll::Document that takes
130
161
  # the collection's URL template into account. The default URL template can
131
162
  # be overriden in the collection's configuration in _config.yml.
132
163
  def url
133
- @url ||= if @collection.nil?
134
- relative_path
164
+ @url ||= begin
165
+ base = if @collection.nil?
166
+ cleaned_relative_path
135
167
  else
136
- ::Jekyll::URL.new({
168
+ Jekyll::URL.new(
137
169
  :template => @collection.url_template,
138
- :placeholders => placeholders,
139
- })
170
+ :placeholders => placeholders
171
+ )
140
172
  end.to_s.chomp("/")
173
+ base << extname
174
+ end
141
175
  end
142
176
 
143
177
  # Returns the type of the collection if present, nil otherwise.
@@ -151,7 +185,14 @@ module Jekyll
151
185
  @defaults ||= @site.frontmatter_defaults.all url, type
152
186
  end
153
187
 
188
+ # Returns a debug string on inspecting the static file.
189
+ # Includes only the relative path of the object.
190
+ def inspect
191
+ "#<#{self.class} @relative_path=#{relative_path.inspect}>"
192
+ end
193
+
154
194
  private
195
+
155
196
  def copy_file(dest_path)
156
197
  if @site.safe || Jekyll.env == "production"
157
198
  FileUtils.cp(path, dest_path)
@@ -16,9 +16,8 @@ module Jekyll
16
16
  severity ||= UNKNOWN
17
17
  @logdev = logdevice(severity)
18
18
 
19
- if @logdev.nil? || severity < @level
20
- return true
21
- end
19
+ return true if @logdev.nil? || severity < @level
20
+
22
21
  progname ||= @progname
23
22
  if message.nil?
24
23
  if block_given?
@@ -10,7 +10,7 @@ module Jekyll
10
10
  # forms: name, name=value, or name="<quoted list>"
11
11
  #
12
12
  # <quoted list> is a space-separated list of numbers
13
- SYNTAX = %r!^([a-zA-Z0-9.+#_-]+)((\s+\w+(=(\w+|"([0-9]+\s)*[0-9]+"))?)*)$!
13
+ SYNTAX = %r!^([a-zA-Z0-9.+#_-]+)((\s+\w+(=(\w+|"([0-9]+\s)*[0-9]+"))?)*)$!.freeze
14
14
 
15
15
  def initialize(tag_name, markup, tokens)
16
16
  super
@@ -18,29 +18,29 @@ module Jekyll
18
18
  @lang = Regexp.last_match(1).downcase
19
19
  @highlight_options = parse_options(Regexp.last_match(2))
20
20
  else
21
- raise SyntaxError, <<-MSG
22
- Syntax Error in tag 'highlight' while parsing the following markup:
21
+ raise SyntaxError, <<~MSG
22
+ Syntax Error in tag 'highlight' while parsing the following markup:
23
23
 
24
- #{markup}
24
+ #{markup}
25
25
 
26
- Valid syntax: highlight <lang> [linenos]
27
- MSG
26
+ Valid syntax: highlight <lang> [linenos]
27
+ MSG
28
28
  end
29
29
  end
30
30
 
31
+ LEADING_OR_TRAILING_LINE_TERMINATORS = %r!\A(\n|\r)+|(\n|\r)+\z!.freeze
32
+
31
33
  def render(context)
32
34
  prefix = context["highlighter_prefix"] || ""
33
35
  suffix = context["highlighter_suffix"] || ""
34
- code = super.to_s.gsub(%r!\A(\n|\r)+|(\n|\r)+\z!, "")
35
-
36
- is_safe = !!context.registers[:site].safe
36
+ code = super.to_s.gsub(LEADING_OR_TRAILING_LINE_TERMINATORS, "")
37
37
 
38
38
  output =
39
39
  case context.registers[:site].highlighter
40
- when "pygments"
41
- render_pygments(code, is_safe)
42
40
  when "rouge"
43
41
  render_rouge(code)
42
+ when "pygments"
43
+ render_pygments(code, context)
44
44
  else
45
45
  render_codehighlighter(code)
46
46
  end
@@ -49,23 +49,9 @@ MSG
49
49
  prefix + rendered_output + suffix
50
50
  end
51
51
 
52
- def sanitized_opts(opts, is_safe)
53
- if is_safe
54
- Hash[[
55
- [:startinline, opts.fetch(:startinline, nil)],
56
- [:hl_lines, opts.fetch(:hl_lines, nil)],
57
- [:linenos, opts.fetch(:linenos, nil)],
58
- [:encoding, opts.fetch(:encoding, "utf-8")],
59
- [:cssclass, opts.fetch(:cssclass, nil)],
60
- ].reject { |f| f.last.nil? }]
61
- else
62
- opts
63
- end
64
- end
65
-
66
52
  private
67
53
 
68
- OPTIONS_REGEX = %r!(?:\w="[^"]*"|\w=\w|\w)+!
54
+ OPTIONS_REGEX = %r!(?:\w="[^"]*"|\w=\w|\w)+!.freeze
69
55
 
70
56
  def parse_options(input)
71
57
  options = {}
@@ -75,7 +61,7 @@ MSG
75
61
  input.scan(OPTIONS_REGEX) do |opt|
76
62
  key, value = opt.split("=")
77
63
  # If a quoted list, convert to array
78
- if value && value.include?('"')
64
+ if value&.include?('"')
79
65
  value.delete!('"')
80
66
  value = value.split
81
67
  end
@@ -86,33 +72,15 @@ MSG
86
72
  options
87
73
  end
88
74
 
89
- def render_pygments(code, is_safe)
90
- Jekyll::External.require_with_graceful_fail("pygments") unless defined?(Pygments)
91
-
92
- highlighted_code = Pygments.highlight(
93
- code,
94
- :lexer => @lang,
95
- :options => sanitized_opts(@highlight_options, is_safe)
96
- )
97
-
98
- if highlighted_code.nil?
99
- Jekyll.logger.error <<-MSG
100
- There was an error highlighting your code:
101
-
102
- #{code}
103
-
104
- While attempting to convert the above code, Pygments.rb returned an unacceptable value.
105
- This is usually a timeout problem solved by running `jekyll build` again.
106
- MSG
107
- raise ArgumentError, "Pygments.rb returned an unacceptable value "\
108
- "when attempting to highlight some code."
109
- end
110
-
111
- highlighted_code.sub('<div class="highlight"><pre>', "").sub("</pre></div>", "")
75
+ def render_pygments(code, _context)
76
+ Jekyll.logger.warn "Warning:", "Highlight Tag no longer supports rendering with Pygments."
77
+ Jekyll.logger.warn "", "Using the default highlighter, Rouge, instead."
78
+ render_rouge(code)
112
79
  end
113
80
 
114
81
  def render_rouge(code)
115
- formatter = Jekyll::Utils::Rouge.html_formatter(
82
+ require "rouge"
83
+ formatter = ::Rouge::Formatters::HTMLLegacy.new(
116
84
  :line_numbers => @highlight_options[:linenos],
117
85
  :wrap => false,
118
86
  :css_class => "highlight",
@@ -2,37 +2,29 @@
2
2
 
3
3
  module Jekyll
4
4
  module Tags
5
- class IncludeTagError < StandardError
6
- attr_accessor :path
7
-
8
- def initialize(msg, path)
9
- super(msg)
10
- @path = path
11
- end
12
- end
13
-
14
5
  class IncludeTag < Liquid::Tag
15
6
  VALID_SYNTAX = %r!
16
7
  ([\w-]+)\s*=\s*
17
8
  (?:"([^"\\]*(?:\\.[^"\\]*)*)"|'([^'\\]*(?:\\.[^'\\]*)*)'|([\w\.-]+))
18
- !x
9
+ !x.freeze
19
10
  VARIABLE_SYNTAX = %r!
20
11
  (?<variable>[^{]*(\{\{\s*[\w\-\.]+\s*(\|.*)?\}\}[^\s{}]*)+)
21
12
  (?<params>.*)
22
- !mx
13
+ !mx.freeze
23
14
 
24
- FULL_VALID_SYNTAX = %r!\A\s*(?:#{VALID_SYNTAX}(?=\s|\z)\s*)*\z!
25
- VALID_FILENAME_CHARS = %r!^[\w/\.-]+$!
26
- INVALID_SEQUENCES = %r![./]{2,}!
15
+ FULL_VALID_SYNTAX = %r!\A\s*(?:#{VALID_SYNTAX}(?=\s|\z)\s*)*\z!.freeze
16
+ VALID_FILENAME_CHARS = %r!^[\w/\.-]+$!.freeze
17
+ INVALID_SEQUENCES = %r![./]{2,}!.freeze
27
18
 
28
19
  def initialize(tag_name, markup, tokens)
29
20
  super
30
- matched = markup.strip.match(VARIABLE_SYNTAX)
21
+ markup = markup.strip
22
+ matched = markup.match(VARIABLE_SYNTAX)
31
23
  if matched
32
24
  @file = matched["variable"].strip
33
25
  @params = matched["params"].strip
34
26
  else
35
- @file, @params = markup.strip.split(%r!\s+!, 2)
27
+ @file, @params = markup.split(%r!\s+!, 2)
36
28
  end
37
29
  validate_params if @params
38
30
  @tag_name = tag_name
@@ -50,9 +42,9 @@ module Jekyll
50
42
  markup = markup[match.end(0)..-1]
51
43
 
52
44
  value = if match[2]
53
- match[2].gsub(%r!\\"!, '"')
45
+ match[2].gsub('\\"', '"')
54
46
  elsif match[3]
55
- match[3].gsub(%r!\\'!, "'")
47
+ match[3].gsub("\\'", "'")
56
48
  elsif match[4]
57
49
  context[match[4]]
58
50
  end
@@ -63,32 +55,32 @@ module Jekyll
63
55
  end
64
56
 
65
57
  def validate_file_name(file)
66
- if file =~ INVALID_SEQUENCES || file !~ VALID_FILENAME_CHARS
67
- raise ArgumentError, <<-MSG
68
- Invalid syntax for include tag. File contains invalid characters or sequences:
58
+ if INVALID_SEQUENCES.match?(file) || !VALID_FILENAME_CHARS.match?(file)
59
+ raise ArgumentError, <<~MSG
60
+ Invalid syntax for include tag. File contains invalid characters or sequences:
69
61
 
70
- #{file}
62
+ #{file}
71
63
 
72
- Valid syntax:
64
+ Valid syntax:
73
65
 
74
- #{syntax_example}
66
+ #{syntax_example}
75
67
 
76
- MSG
68
+ MSG
77
69
  end
78
70
  end
79
71
 
80
72
  def validate_params
81
- unless @params =~ FULL_VALID_SYNTAX
82
- raise ArgumentError, <<-MSG
83
- Invalid syntax for include tag:
73
+ unless FULL_VALID_SYNTAX.match?(@params)
74
+ raise ArgumentError, <<~MSG
75
+ Invalid syntax for include tag:
84
76
 
85
- #{@params}
77
+ #{@params}
86
78
 
87
- Valid syntax:
79
+ Valid syntax:
88
80
 
89
- #{syntax_example}
81
+ #{syntax_example}
90
82
 
91
- MSG
83
+ MSG
92
84
  end
93
85
  end
94
86
 
@@ -99,13 +91,7 @@ MSG
99
91
 
100
92
  # Render the variable if required
101
93
  def render_variable(context)
102
- if @file =~ VARIABLE_SYNTAX
103
- partial = context.registers[:site]
104
- .liquid_renderer
105
- .file("(variable)")
106
- .parse(@file)
107
- partial.render!(context)
108
- end
94
+ Liquid::Template.parse(@file).render(context) if VARIABLE_SYNTAX.match?(@file)
109
95
  end
110
96
 
111
97
  def tag_includes_dirs(context)
@@ -115,7 +101,7 @@ MSG
115
101
  def locate_include_file(context, file, safe)
116
102
  includes_dirs = tag_includes_dirs(context)
117
103
  includes_dirs.each do |dir|
118
- path = File.join(dir.to_s, file.to_s)
104
+ path = PathManager.join(dir, file)
119
105
  return path if valid_include_file?(path, dir.to_s, safe)
120
106
  end
121
107
  raise IOError, could_not_locate_message(file, includes_dirs, safe)
@@ -147,7 +133,7 @@ MSG
147
133
  end
148
134
 
149
135
  def add_include_to_dependency(site, path, context)
150
- if context.registers[:page] && context.registers[:page].key?("path")
136
+ if context.registers[:page]&.key?("path")
151
137
  site.regenerator.add_dependency(
152
138
  site.in_source_dir(context.registers[:page]["path"]),
153
139
  path
@@ -207,6 +193,60 @@ MSG
207
193
  end
208
194
  end
209
195
 
196
+ # Do not inherit from this class.
197
+ # TODO: Merge into the `Jekyll::Tags::IncludeTag` in v5.0
198
+ class OptimizedIncludeTag < IncludeTag
199
+ def render(context)
200
+ @site ||= context.registers[:site]
201
+
202
+ file = render_variable(context) || @file
203
+ validate_file_name(file)
204
+
205
+ @site.inclusions[file] ||= locate_include_file(file)
206
+ inclusion = @site.inclusions[file]
207
+
208
+ add_include_to_dependency(inclusion, context) if @site.incremental?
209
+
210
+ context.stack do
211
+ context["include"] = parse_params(context) if @params
212
+ inclusion.render(context)
213
+ end
214
+ end
215
+
216
+ private
217
+
218
+ def locate_include_file(file)
219
+ @site.includes_load_paths.each do |dir|
220
+ path = PathManager.join(dir, file)
221
+ return Inclusion.new(@site, dir, file) if valid_include_file?(path, dir)
222
+ end
223
+ raise IOError, could_not_locate_message(file, @site.includes_load_paths, @site.safe)
224
+ end
225
+
226
+ def valid_include_file?(path, dir)
227
+ File.file?(path) && !outside_scope?(path, dir)
228
+ end
229
+
230
+ def outside_scope?(path, dir)
231
+ @site.safe && !realpath_prefixed_with?(path, dir)
232
+ end
233
+
234
+ def realpath_prefixed_with?(path, dir)
235
+ File.realpath(path).start_with?(dir)
236
+ rescue StandardError
237
+ false
238
+ end
239
+
240
+ def add_include_to_dependency(inclusion, context)
241
+ return unless context.registers[:page]&.key?("path")
242
+
243
+ @site.regenerator.add_dependency(
244
+ @site.in_source_dir(context.registers[:page]["path"]),
245
+ inclusion.path
246
+ )
247
+ end
248
+ end
249
+
210
250
  class IncludeRelativeTag < IncludeTag
211
251
  def tag_includes_dirs(context)
212
252
  Array(page_path(context)).freeze
@@ -232,5 +272,5 @@ MSG
232
272
  end
233
273
  end
234
274
 
235
- Liquid::Template.register_tag("include", Jekyll::Tags::IncludeTag)
275
+ Liquid::Template.register_tag("include", Jekyll::Tags::OptimizedIncludeTag)
236
276
  Liquid::Template.register_tag("include_relative", Jekyll::Tags::IncludeRelativeTag)