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
@@ -1,5 +1,73 @@
1
1
  # Frozen-string-literal: true
2
2
 
3
+ module Kramdown
4
+ # A Kramdown::Document subclass meant to optimize memory usage from initializing
5
+ # a kramdown document for parsing.
6
+ #
7
+ # The optimization is by using the same options Hash (and its derivatives) for
8
+ # converting all Markdown documents in a Jekyll site.
9
+ class JekyllDocument < Document
10
+ class << self
11
+ attr_reader :options, :parser
12
+
13
+ # The implementation is basically the core logic in +Kramdown::Document#initialize+
14
+ #
15
+ # rubocop:disable Naming/MemoizedInstanceVariableName
16
+ def setup(options)
17
+ @cache ||= {}
18
+
19
+ # reset variables on a subsequent set up with a different options Hash
20
+ unless @cache[:id] == options.hash
21
+ @options = @parser = nil
22
+ @cache[:id] = options.hash
23
+ end
24
+
25
+ @options ||= Options.merge(options).freeze
26
+ @parser ||= begin
27
+ parser_name = (@options[:input] || "kramdown").to_s
28
+ parser_name = parser_name[0..0].upcase + parser_name[1..-1]
29
+ try_require("parser", parser_name)
30
+
31
+ if Parser.const_defined?(parser_name)
32
+ Parser.const_get(parser_name)
33
+ else
34
+ raise Kramdown::Error, "kramdown has no parser to handle the specified " \
35
+ "input format: #{@options[:input]}"
36
+ end
37
+ end
38
+ end
39
+ # rubocop:enable Naming/MemoizedInstanceVariableName
40
+
41
+ private
42
+
43
+ def try_require(type, name)
44
+ require "kramdown/#{type}/#{Utils.snake_case(name)}"
45
+ rescue LoadError
46
+ false
47
+ end
48
+ end
49
+
50
+ def initialize(source, options = {})
51
+ JekyllDocument.setup(options)
52
+
53
+ @options = JekyllDocument.options
54
+ @root, @warnings = JekyllDocument.parser.parse(source, @options)
55
+ end
56
+
57
+ # Use Kramdown::Converter::Html class to convert this document into HTML.
58
+ #
59
+ # The implementation is basically an optimized version of core logic in
60
+ # +Kramdown::Document#method_missing+ from kramdown-2.1.0.
61
+ def to_html
62
+ output, warnings = Kramdown::Converter::Html.convert(@root, @options)
63
+ @warnings.concat(warnings)
64
+ output
65
+ end
66
+ end
67
+ end
68
+
69
+ #
70
+
3
71
  module Jekyll
4
72
  module Converters
5
73
  class Markdown
@@ -18,6 +86,7 @@ module Jekyll
18
86
  @config = config["kramdown"] || {}
19
87
  @highlighter = nil
20
88
  setup
89
+ load_dependencies
21
90
  end
22
91
 
23
92
  # Setup and normalize the configuration:
@@ -30,13 +99,14 @@ module Jekyll
30
99
  def setup
31
100
  @config["syntax_highlighter"] ||= highlighter
32
101
  @config["syntax_highlighter_opts"] ||= {}
102
+ @config["syntax_highlighter_opts"]["default_lang"] ||= "plaintext"
103
+ @config["syntax_highlighter_opts"]["guess_lang"] = @config["guess_lang"]
33
104
  @config["coderay"] ||= {} # XXX: Legacy.
34
105
  modernize_coderay_config
35
- make_accessible
36
106
  end
37
107
 
38
108
  def convert(content)
39
- document = Kramdown::Document.new(content, @config)
109
+ document = Kramdown::JekyllDocument.new(content, @config)
40
110
  html_output = document.to_html
41
111
  if @config["show_warnings"]
42
112
  document.warnings.each do |warning|
@@ -47,10 +117,18 @@ module Jekyll
47
117
  end
48
118
 
49
119
  private
50
- def make_accessible(hash = @config)
51
- hash.keys.each do |key|
52
- hash[key.to_sym] = hash[key]
53
- make_accessible(hash[key]) if hash[key].is_a?(Hash)
120
+
121
+ def load_dependencies
122
+ require "kramdown-parser-gfm" if @config["input"] == "GFM"
123
+
124
+ if highlighter == "coderay"
125
+ Jekyll::External.require_with_graceful_fail("kramdown-syntax-coderay")
126
+ end
127
+
128
+ # `mathjax` emgine is bundled within kramdown-2.x and will be handled by
129
+ # kramdown itself.
130
+ if (math_engine = @config["math_engine"]) && math_engine != "mathjax"
131
+ Jekyll::External.require_with_graceful_fail("kramdown-math-#{math_engine}")
54
132
  end
55
133
  end
56
134
 
@@ -59,8 +137,6 @@ module Jekyll
59
137
  # config[highlighter]
60
138
  # Where `enable_coderay` is now deprecated because Kramdown
61
139
  # supports Rouge now too.
62
-
63
- private
64
140
  def highlighter
65
141
  return @highlighter if @highlighter
66
142
 
@@ -84,7 +160,6 @@ module Jekyll
84
160
  end
85
161
  end
86
162
 
87
- private
88
163
  def strip_coderay_prefix(hash)
89
164
  hash.each_with_object({}) do |(key, val), hsh|
90
165
  cleaned_key = key.to_s.gsub(%r!\Acoderay_!, "")
@@ -102,8 +177,6 @@ module Jekyll
102
177
  # If our highlighter is CodeRay we go in to merge the CodeRay defaults
103
178
  # with your "coderay" key if it's there, deprecating it in the
104
179
  # process of you using it.
105
-
106
- private
107
180
  def modernize_coderay_config
108
181
  unless @config["coderay"].empty?
109
182
  Jekyll::Deprecator.deprecation_message(
@@ -1,40 +1,60 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- class Kramdown::Parser::SmartyPants < Kramdown::Parser::Kramdown
4
- def initialize(source, options)
5
- super
6
- @block_parsers = [:block_html, :content]
7
- @span_parsers = [:smart_quotes, :html_entity, :typographic_syms, :span_html]
8
- end
3
+ module Kramdown
4
+ module Parser
5
+ class SmartyPants < Kramdown::Parser::Kramdown
6
+ def initialize(source, options)
7
+ super
8
+ @block_parsers = [:block_html, :content]
9
+ @span_parsers = [:smart_quotes, :html_entity, :typographic_syms, :span_html]
10
+ end
9
11
 
10
- def parse_content
11
- add_text @src.scan(%r!\A.*\n!)
12
+ def parse_content
13
+ add_text @src.scan(%r!\A.*\n!)
14
+ end
15
+ define_parser(:content, %r!\A!)
16
+ end
12
17
  end
13
- define_parser(:content, %r!\A!)
14
18
  end
15
19
 
16
20
  module Jekyll
17
21
  module Converters
22
+ # SmartyPants converter.
23
+ # For more info on converters see https://jekyllrb.com/docs/plugins/converters/
18
24
  class SmartyPants < Converter
19
25
  safe true
20
26
  priority :low
21
27
 
22
28
  def initialize(config)
23
- unless defined?(Kramdown)
24
- Jekyll::External.require_with_graceful_fail "kramdown"
25
- end
29
+ Jekyll::External.require_with_graceful_fail "kramdown" unless defined?(Kramdown)
26
30
  @config = config["kramdown"].dup || {}
27
31
  @config[:input] = :SmartyPants
28
32
  end
29
33
 
30
- def matches(_)
34
+ # Does the given extension match this converter's list of acceptable extensions?
35
+ # Takes one argument: the file's extension (including the dot).
36
+ #
37
+ # ext - The String extension to check.
38
+ #
39
+ # Returns true if it matches, false otherwise.
40
+ def matches(_ext)
31
41
  false
32
42
  end
33
43
 
34
- def output_ext(_)
44
+ # Public: The extension to be given to the output file (including the dot).
45
+ #
46
+ # ext - The String extension or original file.
47
+ #
48
+ # Returns The String output file extension.
49
+ def output_ext(_ext)
35
50
  nil
36
51
  end
37
52
 
53
+ # Logic to do the content conversion.
54
+ #
55
+ # content - String content of file (without front matter).
56
+ #
57
+ # Returns a String of the converted content.
38
58
  def convert(content)
39
59
  document = Kramdown::Document.new(content, @config)
40
60
  html_output = document.to_html.chomp
@@ -46,10 +46,10 @@ module Jekyll
46
46
  end
47
47
  rescue Psych::SyntaxError => e
48
48
  Jekyll.logger.warn "YAML Exception reading #{filename}: #{e.message}"
49
- raise e if self.site.config["strict_front_matter"]
49
+ raise e if site.config["strict_front_matter"]
50
50
  rescue StandardError => e
51
51
  Jekyll.logger.warn "Error reading file #{filename}: #{e.message}"
52
- raise e if self.site.config["strict_front_matter"]
52
+ raise e if site.config["strict_front_matter"]
53
53
  end
54
54
 
55
55
  self.data ||= {}
@@ -64,12 +64,12 @@ module Jekyll
64
64
  def validate_data!(filename)
65
65
  unless self.data.is_a?(Hash)
66
66
  raise Errors::InvalidYAMLFrontMatterError,
67
- "Invalid YAML front matter in #{filename}"
67
+ "Invalid YAML front matter in #{filename}"
68
68
  end
69
69
  end
70
70
 
71
71
  def validate_permalink!(filename)
72
- if self.data["permalink"] && self.data["permalink"].to_s.empty?
72
+ if self.data["permalink"]&.to_s&.empty?
73
73
  raise Errors::InvalidPermalinkError, "Invalid permalink in #{filename}"
74
74
  end
75
75
  end
@@ -78,7 +78,7 @@ module Jekyll
78
78
  #
79
79
  # Returns the transformed contents.
80
80
  def transform
81
- _renderer.convert(content)
81
+ renderer.convert(content)
82
82
  end
83
83
 
84
84
  # Determine the extension depending on content_type.
@@ -86,7 +86,7 @@ module Jekyll
86
86
  # Returns the String extension for the output file.
87
87
  # e.g. ".html" for an HTML output file.
88
88
  def output_ext
89
- _renderer.output_ext
89
+ renderer.output_ext
90
90
  end
91
91
 
92
92
  # Determine which converter to use based on this convertible's
@@ -94,7 +94,7 @@ module Jekyll
94
94
  #
95
95
  # Returns the Converter instance.
96
96
  def converters
97
- _renderer.converters
97
+ renderer.converters
98
98
  end
99
99
 
100
100
  # Render Liquid in the content
@@ -105,16 +105,17 @@ module Jekyll
105
105
  #
106
106
  # Returns the converted content
107
107
  def render_liquid(content, payload, info, path)
108
- _renderer.render_liquid(content, payload, info, path)
108
+ renderer.render_liquid(content, payload, info, path)
109
109
  end
110
110
 
111
111
  # Convert this Convertible's data to a Hash suitable for use by Liquid.
112
112
  #
113
113
  # Returns the Hash representation of this Convertible.
114
114
  def to_liquid(attrs = nil)
115
- further_data = Hash[(attrs || self.class::ATTRIBUTES_FOR_LIQUID).map do |attribute|
116
- [attribute, send(attribute)]
117
- end]
115
+ further_data = \
116
+ (attrs || self.class::ATTRIBUTES_FOR_LIQUID).each_with_object({}) do |attribute, hsh|
117
+ hsh[attribute] = send(attribute)
118
+ end
118
119
 
119
120
  defaults = site.frontmatter_defaults.all(relative_path, type)
120
121
  Utils.deep_merge_hashes defaults, Utils.deep_merge_hashes(data, further_data)
@@ -125,16 +126,12 @@ module Jekyll
125
126
  #
126
127
  # Returns the type of self.
127
128
  def type
128
- if is_a?(Page)
129
- :pages
130
- end
129
+ :pages if is_a?(Page)
131
130
  end
132
131
 
133
132
  # returns the owner symbol for hook triggering
134
133
  def hook_owner
135
- if is_a?(Page)
136
- :pages
137
- end
134
+ :pages if is_a?(Page)
138
135
  end
139
136
 
140
137
  # Determine whether the document is an asset file.
@@ -150,7 +147,7 @@ module Jekyll
150
147
  #
151
148
  # Returns true if extname == .sass or .scss, false otherwise.
152
149
  def sass_file?
153
- %w(.sass .scss).include?(ext)
150
+ Jekyll::Document::SASS_FILE_EXTS.include?(ext)
154
151
  end
155
152
 
156
153
  # Determine whether the document is a CoffeeScript file.
@@ -164,6 +161,8 @@ module Jekyll
164
161
  #
165
162
  # Returns true if the file has Liquid Tags or Variables, false otherwise.
166
163
  def render_with_liquid?
164
+ return false if data["render_with_liquid"] == false
165
+
167
166
  Jekyll::Utils.has_liquid_construct?(content)
168
167
  end
169
168
 
@@ -181,7 +180,7 @@ module Jekyll
181
180
  #
182
181
  # Returns true if the layout is invalid, false if otherwise
183
182
  def invalid_layout?(layout)
184
- !data["layout"].nil? && layout.nil? && !(self.is_a? Jekyll::Excerpt)
183
+ !data["layout"].nil? && layout.nil? && !(is_a? Jekyll::Excerpt)
185
184
  end
186
185
 
187
186
  # Recursively render layouts
@@ -192,10 +191,10 @@ module Jekyll
192
191
  #
193
192
  # Returns nothing
194
193
  def render_all_layouts(layouts, payload, info)
195
- _renderer.layouts = layouts
196
- self.output = _renderer.place_in_layouts(output, payload, info)
194
+ renderer.layouts = layouts
195
+ self.output = renderer.place_in_layouts(output, payload, info)
197
196
  ensure
198
- @_renderer = nil # this will allow the modifications above to disappear
197
+ @renderer = nil # this will allow the modifications above to disappear
199
198
  end
200
199
 
201
200
  # Add any necessary layouts to this convertible document.
@@ -205,15 +204,15 @@ module Jekyll
205
204
  #
206
205
  # Returns nothing.
207
206
  def do_layout(payload, layouts)
208
- self.output = _renderer.tap do |renderer|
209
- renderer.layouts = layouts
210
- renderer.payload = payload
207
+ self.output = renderer.tap do |doc_renderer|
208
+ doc_renderer.layouts = layouts
209
+ doc_renderer.payload = payload
211
210
  end.run
212
211
 
213
- Jekyll.logger.debug "Post-Render Hooks:", self.relative_path
212
+ Jekyll.logger.debug "Post-Render Hooks:", relative_path
214
213
  Jekyll::Hooks.trigger hook_owner, :post_render, self
215
214
  ensure
216
- @_renderer = nil # this will allow the modifications above to disappear
215
+ @renderer = nil # this will allow the modifications above to disappear
217
216
  end
218
217
 
219
218
  # Write the generated page file to the destination directory.
@@ -242,12 +241,12 @@ module Jekyll
242
241
  end
243
242
  end
244
243
 
245
- private
246
-
247
- def _renderer
248
- @_renderer ||= Jekyll::Renderer.new(site, self)
244
+ def renderer
245
+ @renderer ||= Jekyll::Renderer.new(site, self)
249
246
  end
250
247
 
248
+ private
249
+
251
250
  def no_layout?
252
251
  data["layout"] == "none"
253
252
  end
@@ -34,9 +34,7 @@ module Jekyll
34
34
  end
35
35
 
36
36
  def arg_is_present?(args, deprecated_argument, message)
37
- if args.include?(deprecated_argument)
38
- deprecation_message(message)
39
- end
37
+ deprecation_message(message) if args.include?(deprecated_argument)
40
38
  end
41
39
 
42
40
  def deprecation_message(message)
@@ -5,14 +5,31 @@ module Jekyll
5
5
  include Comparable
6
6
  extend Forwardable
7
7
 
8
- attr_reader :path, :site, :extname, :collection
8
+ attr_reader :path, :site, :extname, :collection, :type
9
9
  attr_accessor :content, :output
10
10
 
11
11
  def_delegator :self, :read_post_data, :post_read
12
12
 
13
- YAML_FRONT_MATTER_REGEXP = %r!\A(---\s*\n.*?\n?)^((---|\.\.\.)\s*$\n?)!m
14
- DATELESS_FILENAME_MATCHER = %r!^(?:.+/)*(.*)(\.[^.]+)$!
15
- DATE_FILENAME_MATCHER = %r!^(?:.+/)*(\d{2,4}-\d{1,2}-\d{1,2})-(.*)(\.[^.]+)$!
13
+ YAML_FRONT_MATTER_REGEXP = %r!\A(---\s*\n.*?\n?)^((---|\.\.\.)\s*$\n?)!m.freeze
14
+ DATELESS_FILENAME_MATCHER = %r!^(?:.+/)*(.*)(\.[^.]+)$!.freeze
15
+ DATE_FILENAME_MATCHER = %r!^(?>.+/)*?(\d{2,4}-\d{1,2}-\d{1,2})-([^/]*)(\.[^.]+)$!.freeze
16
+
17
+ SASS_FILE_EXTS = %w(.sass .scss).freeze
18
+ YAML_FILE_EXTS = %w(.yaml .yml).freeze
19
+
20
+ #
21
+
22
+ # Class-wide cache to stash and retrieve regexp to detect "super-directories"
23
+ # of a particular Jekyll::Document object.
24
+ #
25
+ # dirname - The *special directory* for the Document.
26
+ # e.g. "_posts" or "_drafts" for Documents from the `site.posts` collection.
27
+ def self.superdirs_regex(dirname)
28
+ @superdirs_regex ||= {}
29
+ @superdirs_regex[dirname] ||= %r!#{dirname}.*!
30
+ end
31
+
32
+ #
16
33
 
17
34
  # Create a new Document.
18
35
  #
@@ -27,6 +44,8 @@ module Jekyll
27
44
  @path = path
28
45
  @extname = File.extname(path)
29
46
  @collection = relations[:collection]
47
+ @type = @collection.label.to_sym
48
+
30
49
  @has_yaml_header = nil
31
50
 
32
51
  if draft?
@@ -36,7 +55,7 @@ module Jekyll
36
55
  end
37
56
 
38
57
  data.default_proc = proc do |_, key|
39
- site.frontmatter_defaults.find(relative_path, collection.label, key)
58
+ site.frontmatter_defaults.find(relative_path, type, key)
40
59
  end
41
60
 
42
61
  trigger_hooks(:post_init)
@@ -60,12 +79,19 @@ module Jekyll
60
79
  data
61
80
  end
62
81
 
82
+ # Returns the document date. If metadata is not present then calculates it
83
+ # based on Jekyll::Site#time or the document file modification time.
84
+ #
85
+ # Return document date string.
63
86
  def date
64
87
  data["date"] ||= (draft? ? source_file_mtime : site.time)
65
88
  end
66
89
 
90
+ # Return document file modification time in the form of a Time object.
91
+ #
92
+ # Return document file modification Time object.
67
93
  def source_file_mtime
68
- @source_file_mtime ||= File.mtime(path)
94
+ File.mtime(path)
69
95
  end
70
96
 
71
97
  # Returns whether the document is a draft. This is only the case if
@@ -90,7 +116,7 @@ module Jekyll
90
116
  #
91
117
  # Returns the output extension
92
118
  def output_ext
93
- @output_ext ||= Jekyll::Renderer.new(site, self).output_ext
119
+ renderer.output_ext
94
120
  end
95
121
 
96
122
  # The base filename of the document, without the file extname.
@@ -107,27 +133,35 @@ module Jekyll
107
133
  @basename ||= File.basename(path)
108
134
  end
109
135
 
136
+ def renderer
137
+ @renderer ||= Jekyll::Renderer.new(site, self)
138
+ end
139
+
110
140
  # Produces a "cleaned" relative path.
111
141
  # The "cleaned" relative path is the relative path without the extname
112
142
  # and with the collection's directory removed as well.
113
143
  # This method is useful when building the URL of the document.
114
144
  #
145
+ # NOTE: `String#gsub` removes all trailing periods (in comparison to `String#chomp`)
146
+ #
115
147
  # Examples:
116
- # When relative_path is "_methods/site/generate.md":
148
+ # When relative_path is "_methods/site/generate...md":
117
149
  # cleaned_relative_path
118
150
  # # => "/site/generate"
119
151
  #
120
152
  # Returns the cleaned relative path of the document.
121
153
  def cleaned_relative_path
122
154
  @cleaned_relative_path ||=
123
- relative_path[0..-extname.length - 1].sub(collection.relative_directory, "")
155
+ relative_path[0..-extname.length - 1]
156
+ .sub(collection.relative_directory, "")
157
+ .gsub(%r!\.*\z!, "")
124
158
  end
125
159
 
126
160
  # Determine whether the document is a YAML file.
127
161
  #
128
162
  # Returns true if the extname is either .yml or .yaml, false otherwise.
129
163
  def yaml_file?
130
- %w(.yaml .yml).include?(extname)
164
+ YAML_FILE_EXTS.include?(extname)
131
165
  end
132
166
 
133
167
  # Determine whether the document is an asset file.
@@ -143,7 +177,7 @@ module Jekyll
143
177
  #
144
178
  # Returns true if extname == .sass or .scss, false otherwise.
145
179
  def sass_file?
146
- %w(.sass .scss).include?(extname)
180
+ SASS_FILE_EXTS.include?(extname)
147
181
  end
148
182
 
149
183
  # Determine whether the document is a CoffeeScript file.
@@ -159,6 +193,8 @@ module Jekyll
159
193
  # or if the document doesn't contain any Liquid Tags or Variables,
160
194
  # true otherwise.
161
195
  def render_with_liquid?
196
+ return false if data["render_with_liquid"] == false
197
+
162
198
  !(coffeescript_file? || yaml_file? || !Utils.has_liquid_construct?(content))
163
199
  end
164
200
 
@@ -204,11 +240,11 @@ module Jekyll
204
240
  #
205
241
  # Returns the computed URL for the document.
206
242
  def url
207
- @url ||= URL.new({
243
+ @url ||= URL.new(
208
244
  :template => url_template,
209
245
  :placeholders => url_placeholders,
210
- :permalink => permalink,
211
- }).to_s
246
+ :permalink => permalink
247
+ ).to_s
212
248
  end
213
249
 
214
250
  def [](key)
@@ -286,7 +322,7 @@ module Jekyll
286
322
  #
287
323
  # Returns the inspect string for this document.
288
324
  def inspect
289
- "#<Jekyll::Document #{relative_path} collection=#{collection.label}>"
325
+ "#<#{self.class} #{relative_path} collection=#{collection.label}>"
290
326
  end
291
327
 
292
328
  # The string representation for this document.
@@ -303,6 +339,7 @@ module Jekyll
303
339
  # equal or greater than the other doc's path. See String#<=> for more details.
304
340
  def <=>(other)
305
341
  return nil unless other.respond_to?(:data)
342
+
306
343
  cmp = data["date"] <=> other.data["date"]
307
344
  cmp = path <=> other.path if cmp.nil? || cmp.zero?
308
345
  cmp
@@ -315,7 +352,7 @@ module Jekyll
315
352
  # method returns true, and if the site's Publisher will publish the document.
316
353
  # False otherwise.
317
354
  def write?
318
- collection && collection.write? && site.publisher.publish?(self)
355
+ collection&.write? && site.publisher.publish?(self)
319
356
  end
320
357
 
321
358
  # The Document excerpt_separator, from the YAML Front-Matter or site
@@ -323,7 +360,7 @@ module Jekyll
323
360
  #
324
361
  # Returns the document excerpt_separator
325
362
  def excerpt_separator
326
- (data["excerpt_separator"] || site.config["excerpt_separator"]).to_s
363
+ @excerpt_separator ||= (data["excerpt_separator"] || site.config["excerpt_separator"]).to_s
327
364
  end
328
365
 
329
366
  # Whether to generate an excerpt
@@ -335,16 +372,12 @@ module Jekyll
335
372
 
336
373
  def next_doc
337
374
  pos = collection.docs.index { |post| post.equal?(self) }
338
- if pos && pos < collection.docs.length - 1
339
- collection.docs[pos + 1]
340
- end
375
+ collection.docs[pos + 1] if pos && pos < collection.docs.length - 1
341
376
  end
342
377
 
343
378
  def previous_doc
344
379
  pos = collection.docs.index { |post| post.equal?(self) }
345
- if pos && pos > 0
346
- collection.docs[pos - 1]
347
- end
380
+ collection.docs[pos - 1] if pos && pos.positive?
348
381
  end
349
382
 
350
383
  def trigger_hooks(hook_name, *args)
@@ -363,12 +396,6 @@ module Jekyll
363
396
  @related_posts ||= Jekyll::RelatedPosts.new(self).build
364
397
  end
365
398
 
366
- # Override of normal respond_to? to match method_missing's logic for
367
- # looking in @data.
368
- def respond_to?(method, include_private = false)
369
- data.key?(method.to_s) || super
370
- end
371
-
372
399
  # Override of method_missing to check in @data for the key.
373
400
  def method_missing(method, *args, &blck)
374
401
  if data.key?(method.to_s)
@@ -391,41 +418,44 @@ module Jekyll
391
418
  #
392
419
  # Returns nothing.
393
420
  def categories_from_path(special_dir)
394
- superdirs = relative_path.sub(%r!#{special_dir}(.*)!, "")
395
- .split(File::SEPARATOR)
396
- .reject do |c|
397
- c.empty? || c == special_dir || c == basename
421
+ if relative_path.start_with?(special_dir)
422
+ superdirs = []
423
+ else
424
+ superdirs = relative_path.sub(Document.superdirs_regex(special_dir), "")
425
+ superdirs = superdirs.split(File::SEPARATOR)
426
+ superdirs.reject! { |c| c.empty? || c == special_dir || c == basename }
398
427
  end
428
+
399
429
  merge_data!({ "categories" => superdirs }, :source => "file path")
400
430
  end
401
431
 
402
432
  def populate_categories
403
- merge_data!({
404
- "categories" => (
405
- Array(data["categories"]) + Utils.pluralized_array_from_hash(
406
- data, "category", "categories"
407
- )
408
- ).map(&:to_s).flatten.uniq,
409
- })
433
+ categories = Array(data["categories"]) + Utils.pluralized_array_from_hash(
434
+ data, "category", "categories"
435
+ )
436
+ categories.map!(&:to_s)
437
+ categories.flatten!
438
+ categories.uniq!
439
+
440
+ merge_data!({ "categories" => categories })
410
441
  end
411
442
 
412
443
  def populate_tags
413
- merge_data!({
414
- "tags" => Utils.pluralized_array_from_hash(data, "tag", "tags").flatten,
415
- })
444
+ tags = Utils.pluralized_array_from_hash(data, "tag", "tags")
445
+ tags.flatten!
446
+
447
+ merge_data!({ "tags" => tags })
416
448
  end
417
449
 
418
450
  private
451
+
419
452
  def merge_categories!(other)
420
453
  if other.key?("categories") && !other["categories"].nil?
421
- if other["categories"].is_a?(String)
422
- other["categories"] = other["categories"].split
423
- end
454
+ other["categories"] = other["categories"].split if other["categories"].is_a?(String)
424
455
  other["categories"] = (data["categories"] || []) | other["categories"]
425
456
  end
426
457
  end
427
458
 
428
- private
429
459
  def merge_date!(source)
430
460
  if data.key?("date")
431
461
  data["date"] = Utils.parse_date(
@@ -435,16 +465,11 @@ module Jekyll
435
465
  end
436
466
  end
437
467
 
438
- private
439
468
  def merge_defaults
440
- defaults = @site.frontmatter_defaults.all(
441
- relative_path,
442
- collection.label.to_sym
443
- )
469
+ defaults = @site.frontmatter_defaults.all(relative_path, type)
444
470
  merge_data!(defaults, :source => "front matter defaults") unless defaults.empty?
445
471
  end
446
472
 
447
- private
448
473
  def read_content(**opts)
449
474
  self.content = File.read(path, **Utils.merged_file_read_opts(site, opts))
450
475
  if content =~ YAML_FRONT_MATTER_REGEXP
@@ -454,7 +479,6 @@ module Jekyll
454
479
  end
455
480
  end
456
481
 
457
- private
458
482
  def read_post_data
459
483
  populate_title
460
484
  populate_categories
@@ -462,7 +486,6 @@ module Jekyll
462
486
  generate_excerpt
463
487
  end
464
488
 
465
- private
466
489
  def handle_read_error(error)
467
490
  if error.is_a? Psych::SyntaxError
468
491
  Jekyll.logger.error "Error:", "YAML Exception reading #{path}: #{error.message}"
@@ -475,7 +498,7 @@ module Jekyll
475
498
  end
476
499
  end
477
500
 
478
- private
501
+ # rubocop:disable Metrics/AbcSize
479
502
  def populate_title
480
503
  if relative_path =~ DATE_FILENAME_MATCHER
481
504
  date, slug, ext = Regexp.last_match.captures
@@ -483,6 +506,14 @@ module Jekyll
483
506
  elsif relative_path =~ DATELESS_FILENAME_MATCHER
484
507
  slug, ext = Regexp.last_match.captures
485
508
  end
509
+ # `slug` will be nil for documents without an extension since the regex patterns
510
+ # above tests for an extension as well.
511
+ # In such cases, assign `basename_without_ext` as the slug.
512
+ slug ||= basename_without_ext
513
+
514
+ # slugs shouldn't end with a period
515
+ # `String#gsub!` removes all trailing periods (in comparison to `String#chomp!`)
516
+ slug.gsub!(%r!\.*\z!, "")
486
517
 
487
518
  # Try to ensure the user gets a title.
488
519
  data["title"] ||= Utils.titleize_slug(slug)
@@ -490,19 +521,16 @@ module Jekyll
490
521
  data["slug"] ||= slug
491
522
  data["ext"] ||= ext
492
523
  end
524
+ # rubocop:enable Metrics/AbcSize
493
525
 
494
- private
495
526
  def modify_date(date)
496
527
  if !data["date"] || data["date"].to_i == site.time.to_i
497
528
  merge_data!({ "date" => date }, :source => "filename")
498
529
  end
499
530
  end
500
531
 
501
- private
502
532
  def generate_excerpt
503
- if generate_excerpt?
504
- data["excerpt"] ||= Jekyll::Excerpt.new(self)
505
- end
533
+ data["excerpt"] ||= Jekyll::Excerpt.new(self) if generate_excerpt?
506
534
  end
507
535
  end
508
536
  end