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
@@ -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