jekyll 3.8.7 → 4.1.0

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