jekyll 4.1.1 → 4.2.2

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.
@@ -6,20 +6,101 @@ module Jekyll
6
6
  include Enumerable
7
7
 
8
8
  NON_CONTENT_METHODS = [:fallback_data, :collapse_document].freeze
9
+ NON_CONTENT_METHOD_NAMES = NON_CONTENT_METHODS.map(&:to_s).freeze
10
+ private_constant :NON_CONTENT_METHOD_NAMES
9
11
 
10
- # Get or set whether the drop class is mutable.
11
- # Mutability determines whether or not pre-defined fields may be
12
- # overwritten.
13
- #
14
- # is_mutable - Boolean set mutability of the class (default: nil)
15
- #
16
- # Returns the mutability of the class
17
- def self.mutable(is_mutable = nil)
18
- @is_mutable = is_mutable || false
19
- end
12
+ # A private stash to avoid repeatedly generating the setter method name string for
13
+ # a call to `Drops::Drop#[]=`.
14
+ # The keys of the stash below have a very high probability of being called upon during
15
+ # the course of various `Jekyll::Renderer#run` calls.
16
+ SETTER_KEYS_STASH = {
17
+ "content" => "content=",
18
+ "layout" => "layout=",
19
+ "page" => "page=",
20
+ "paginator" => "paginator=",
21
+ "highlighter_prefix" => "highlighter_prefix=",
22
+ "highlighter_suffix" => "highlighter_suffix=",
23
+ }.freeze
24
+ private_constant :SETTER_KEYS_STASH
25
+
26
+ class << self
27
+ # Get or set whether the drop class is mutable.
28
+ # Mutability determines whether or not pre-defined fields may be
29
+ # overwritten.
30
+ #
31
+ # is_mutable - Boolean set mutability of the class (default: nil)
32
+ #
33
+ # Returns the mutability of the class
34
+ def mutable(is_mutable = nil)
35
+ @is_mutable = is_mutable || false
36
+ end
37
+
38
+ def mutable?
39
+ @is_mutable
40
+ end
41
+
42
+ # public delegation helper methods that calls onto Drop's instance
43
+ # variable `@obj`.
44
+
45
+ # Generate private Drop instance_methods for each symbol in the given list.
46
+ #
47
+ # Returns nothing.
48
+ def private_delegate_methods(*symbols)
49
+ symbols.each { |symbol| private delegate_method(symbol) }
50
+ nil
51
+ end
52
+
53
+ # Generate public Drop instance_methods for each symbol in the given list.
54
+ #
55
+ # Returns nothing.
56
+ def delegate_methods(*symbols)
57
+ symbols.each { |symbol| delegate_method(symbol) }
58
+ nil
59
+ end
20
60
 
21
- def self.mutable?
22
- @is_mutable
61
+ # Generate public Drop instance_method for given symbol that calls `@obj.<sym>`.
62
+ #
63
+ # Returns delegated method symbol.
64
+ def delegate_method(symbol)
65
+ define_method(symbol) { @obj.send(symbol) }
66
+ end
67
+
68
+ # Generate public Drop instance_method named `delegate` that calls `@obj.<original>`.
69
+ #
70
+ # Returns delegated method symbol.
71
+ def delegate_method_as(original, delegate)
72
+ define_method(delegate) { @obj.send(original) }
73
+ end
74
+
75
+ # Generate public Drop instance_methods for each string entry in the given list.
76
+ # The generated method(s) access(es) `@obj`'s data hash.
77
+ #
78
+ # Returns nothing.
79
+ def data_delegators(*strings)
80
+ strings.each do |key|
81
+ data_delegator(key) if key.is_a?(String)
82
+ end
83
+ nil
84
+ end
85
+
86
+ # Generate public Drop instance_methods for given string `key`.
87
+ # The generated method access(es) `@obj`'s data hash.
88
+ #
89
+ # Returns method symbol.
90
+ def data_delegator(key)
91
+ define_method(key.to_sym) { @obj.data[key] }
92
+ end
93
+
94
+ # Array of stringified instance methods that do not end with the assignment operator.
95
+ #
96
+ # (<klass>.instance_methods always generates a new Array object so it can be mutated)
97
+ #
98
+ # Returns array of strings.
99
+ def getter_method_names
100
+ @getter_method_names ||= instance_methods.map!(&:to_s).tap do |list|
101
+ list.reject! { |item| item.end_with?("=") }
102
+ end
103
+ end
23
104
  end
24
105
 
25
106
  # Create a new Drop
@@ -65,7 +146,7 @@ module Jekyll
65
146
  # and the key matches a method in which case it raises a
66
147
  # DropMutationException.
67
148
  def []=(key, val)
68
- setter = "#{key}="
149
+ setter = SETTER_KEYS_STASH[key] || "#{key}="
69
150
  if respond_to?(setter)
70
151
  public_send(setter, val)
71
152
  elsif respond_to?(key.to_s)
@@ -84,13 +165,10 @@ module Jekyll
84
165
  #
85
166
  # Returns an Array of strings which represent method-specific keys.
86
167
  def content_methods
87
- @content_methods ||= (
88
- self.class.instance_methods \
89
- - Jekyll::Drops::Drop.instance_methods \
90
- - NON_CONTENT_METHODS
91
- ).map(&:to_s).reject do |method|
92
- method.end_with?("=")
93
- end
168
+ @content_methods ||= \
169
+ self.class.getter_method_names \
170
+ - Jekyll::Drops::Drop.getter_method_names \
171
+ - NON_CONTENT_METHOD_NAMES
94
172
  end
95
173
 
96
174
  # Check if key exists in Drop
@@ -7,10 +7,10 @@ module Jekyll
7
7
 
8
8
  mutable false
9
9
 
10
- def_delegator :@obj, :site_data, :data
11
- def_delegators :@obj, :time, :pages, :static_files, :tags, :categories
10
+ delegate_method_as :site_data, :data
11
+ delegate_methods :time, :pages, :static_files, :tags, :categories
12
12
 
13
- private def_delegator :@obj, :config, :fallback_data
13
+ private delegate_method_as :config, :fallback_data
14
14
 
15
15
  def [](key)
16
16
  if key != "posts" && @obj.collections.key?(key)
@@ -4,11 +4,11 @@ module Jekyll
4
4
  module Drops
5
5
  class StaticFileDrop < Drop
6
6
  extend Forwardable
7
- def_delegators :@obj, :name, :extname, :modified_time, :basename
8
- def_delegator :@obj, :relative_path, :path
9
- def_delegator :@obj, :type, :collection
7
+ delegate_methods :name, :extname, :modified_time, :basename
8
+ delegate_method_as :relative_path, :path
9
+ delegate_method_as :type, :collection
10
10
 
11
- private def_delegator :@obj, :data, :fallback_data
11
+ private delegate_method_as :data, :fallback_data
12
12
  end
13
13
  end
14
14
  end
@@ -7,8 +7,8 @@ module Jekyll
7
7
 
8
8
  mutable false
9
9
 
10
- def_delegator :@obj, :cleaned_relative_path, :path
11
- def_delegator :@obj, :output_ext, :output_ext
10
+ delegate_method :output_ext
11
+ delegate_method_as :cleaned_relative_path, :path
12
12
 
13
13
  def collection
14
14
  @obj.collection.label
@@ -76,13 +76,16 @@ module Jekyll
76
76
 
77
77
  parts = [sanitized_baseurl, input]
78
78
  Addressable::URI.parse(
79
- parts.compact.map { |part| ensure_leading_slash(part.to_s) }.join
79
+ parts.map! { |part| ensure_leading_slash(part.to_s) }.join
80
80
  ).normalize.to_s
81
81
  end
82
82
 
83
83
  def sanitized_baseurl
84
84
  site = @context.registers[:site]
85
- site.config["baseurl"].to_s.chomp("/")
85
+ baseurl = site.config["baseurl"]
86
+ return "" if baseurl.nil?
87
+
88
+ baseurl.to_s.chomp("/")
86
89
  end
87
90
 
88
91
  def ensure_leading_slash(input)
@@ -113,7 +113,7 @@ module Jekyll
113
113
  #
114
114
  # Returns the formatted String
115
115
  def normalize_whitespace(input)
116
- input.to_s.gsub(%r!\s+!, " ").strip
116
+ input.to_s.gsub(%r!\s+!, " ").tap(&:strip!)
117
117
  end
118
118
 
119
119
  # Count the number of words in the input string.
@@ -307,9 +307,10 @@ module Jekyll
307
307
  if property.nil?
308
308
  input.sort
309
309
  else
310
- if nils == "first"
310
+ case nils
311
+ when "first"
311
312
  order = - 1
312
- elsif nils == "last"
313
+ when "last"
313
314
  order = + 1
314
315
  else
315
316
  raise ArgumentError, "Invalid nils order: " \
@@ -399,7 +400,6 @@ module Jekyll
399
400
 
400
401
  # `where` filter helper
401
402
  #
402
- # rubocop:disable Metrics/PerceivedComplexity
403
403
  def compare_property_vs_target(property, target)
404
404
  case target
405
405
  when NilClass
@@ -421,10 +421,8 @@ module Jekyll
421
421
  false
422
422
  end
423
423
 
424
- # rubocop:enable Metrics/PerceivedComplexity
425
-
426
424
  def item_property(item, property)
427
- @item_property_cache ||= {}
425
+ @item_property_cache ||= @context.registers[:site].filter_cache[:item_property] ||= {}
428
426
  @item_property_cache[property] ||= {}
429
427
  @item_property_cache[property][item] ||= begin
430
428
  property = property.to_s
@@ -464,8 +462,7 @@ module Jekyll
464
462
  def as_liquid(item)
465
463
  case item
466
464
  when Hash
467
- pairs = item.map { |k, v| as_liquid([k, v]) }
468
- Hash[pairs]
465
+ item.each_with_object({}) { |(k, v), result| result[as_liquid(k)] = as_liquid(v) }
469
466
  when Array
470
467
  item.map { |i| as_liquid(i) }
471
468
  else
@@ -157,7 +157,7 @@ module Jekyll
157
157
  # Returns true if either of the above conditions are satisfied,
158
158
  # otherwise returns false
159
159
  def applies_type?(scope, type)
160
- !scope.key?("type") || scope["type"].eql?(type.to_s)
160
+ !scope.key?("type") || type&.to_sym.eql?(scope["type"].to_sym)
161
161
  end
162
162
 
163
163
  # Checks if a given set of default values is valid
@@ -223,7 +223,7 @@ module Jekyll
223
223
  Jekyll.logger.warn set.to_s
224
224
  nil
225
225
  end
226
- end.compact
226
+ end.tap(&:compact!)
227
227
  end
228
228
 
229
229
  # Sanitizes the given path by removing a leading slash
data/lib/jekyll/hooks.rb CHANGED
@@ -22,22 +22,25 @@ module Jekyll
22
22
  :post_write => [],
23
23
  },
24
24
  :pages => {
25
- :post_init => [],
26
- :pre_render => [],
27
- :post_render => [],
28
- :post_write => [],
25
+ :post_init => [],
26
+ :pre_render => [],
27
+ :post_convert => [],
28
+ :post_render => [],
29
+ :post_write => [],
29
30
  },
30
31
  :posts => {
31
- :post_init => [],
32
- :pre_render => [],
33
- :post_render => [],
34
- :post_write => [],
32
+ :post_init => [],
33
+ :pre_render => [],
34
+ :post_convert => [],
35
+ :post_render => [],
36
+ :post_write => [],
35
37
  },
36
38
  :documents => {
37
- :post_init => [],
38
- :pre_render => [],
39
- :post_render => [],
40
- :post_write => [],
39
+ :post_init => [],
40
+ :pre_render => [],
41
+ :post_convert => [],
42
+ :post_render => [],
43
+ :post_write => [],
41
44
  },
42
45
  :clean => {
43
46
  :on_obsolete => [],
@@ -67,10 +70,11 @@ module Jekyll
67
70
  # register a single hook to be called later, internal API
68
71
  def self.register_one(owner, event, priority, &block)
69
72
  @registry[owner] ||= {
70
- :post_init => [],
71
- :pre_render => [],
72
- :post_render => [],
73
- :post_write => [],
73
+ :post_init => [],
74
+ :pre_render => [],
75
+ :post_convert => [],
76
+ :post_render => [],
77
+ :post_write => [],
74
78
  }
75
79
 
76
80
  unless @registry[owner][event]
data/lib/jekyll/layout.rb CHANGED
@@ -58,5 +58,10 @@ module Jekyll
58
58
  def process(name)
59
59
  self.ext = File.extname(name)
60
60
  end
61
+
62
+ # Returns the object as a debug String.
63
+ def inspect
64
+ "#<#{self.class} @path=#{@path.inspect}>"
65
+ end
61
66
  end
62
67
  end
data/lib/jekyll/page.rb CHANGED
@@ -64,12 +64,7 @@ module Jekyll
64
64
  #
65
65
  # Returns the String destination directory.
66
66
  def dir
67
- if url.end_with?("/")
68
- url
69
- else
70
- url_dir = File.dirname(url)
71
- url_dir.end_with?("/") ? url_dir : "#{url_dir}/"
72
- end
67
+ url.end_with?("/") ? url : url_dir
73
68
  end
74
69
 
75
70
  # The full path and filename of the post. Defined in the YAML of the post
@@ -121,6 +116,8 @@ module Jekyll
121
116
  # NOTE: `String#gsub` removes all trailing periods (in comparison to `String#chomp`)
122
117
  # Returns nothing.
123
118
  def process(name)
119
+ return unless name
120
+
124
121
  self.ext = File.extname(name)
125
122
  self.basename = name[0..-ext.length - 1].gsub(%r!\.*\z!, "")
126
123
  end
@@ -147,7 +144,7 @@ module Jekyll
147
144
 
148
145
  # The path to the page source file, relative to the site source
149
146
  def relative_path
150
- @relative_path ||= File.join(*[@dir, @name].map(&:to_s).reject(&:empty?)).sub(%r!\A/!, "")
147
+ @relative_path ||= PathManager.join(@dir, @name).sub(%r!\A/!, "")
151
148
  end
152
149
 
153
150
  # Obtain destination path.
@@ -156,10 +153,13 @@ module Jekyll
156
153
  #
157
154
  # Returns the destination file path String.
158
155
  def destination(dest)
159
- path = site.in_dest_dir(dest, URL.unescape_path(url))
160
- path = File.join(path, "index") if url.end_with?("/")
161
- path << output_ext unless path.end_with? output_ext
162
- path
156
+ @destination ||= {}
157
+ @destination[dest] ||= begin
158
+ path = site.in_dest_dir(dest, URL.unescape_path(url))
159
+ path = File.join(path, "index") if url.end_with?("/")
160
+ path << output_ext unless path.end_with? output_ext
161
+ path
162
+ end
163
163
  end
164
164
 
165
165
  # Returns the object as a debug String.
@@ -192,11 +192,11 @@ module Jekyll
192
192
  def excerpt
193
193
  return @excerpt if defined?(@excerpt)
194
194
 
195
- @excerpt = data["excerpt"]&.to_s
195
+ @excerpt = data["excerpt"] ? data["excerpt"].to_s : nil
196
196
  end
197
197
 
198
198
  def generate_excerpt?
199
- !excerpt_separator.empty? && self.class == Jekyll::Page && html?
199
+ !excerpt_separator.empty? && instance_of?(Jekyll::Page) && html?
200
200
  end
201
201
 
202
202
  private
@@ -206,5 +206,12 @@ module Jekyll
206
206
 
207
207
  data["excerpt"] ||= Jekyll::PageExcerpt.new(self)
208
208
  end
209
+
210
+ def url_dir
211
+ @url_dir ||= begin
212
+ value = File.dirname(url)
213
+ value.end_with?("/") ? value : "#{value}/"
214
+ end
215
+ end
209
216
  end
210
217
  end
@@ -16,16 +16,59 @@ module Jekyll
16
16
  # This class cannot be initialized from outside
17
17
  private_class_method :new
18
18
 
19
- # Wraps `File.join` to cache the frozen result.
20
- # Reassigns `nil`, empty strings and empty arrays to a frozen empty string beforehand.
21
- #
22
- # Returns a frozen string.
23
- def self.join(base, item)
24
- base = "" if base.nil? || base.empty?
25
- item = "" if item.nil? || item.empty?
26
- @join ||= {}
27
- @join[base] ||= {}
28
- @join[base][item] ||= File.join(base, item).freeze
19
+ class << self
20
+ # Wraps `File.join` to cache the frozen result.
21
+ # Reassigns `nil`, empty strings and empty arrays to a frozen empty string beforehand.
22
+ #
23
+ # Returns a frozen string.
24
+ def join(base, item)
25
+ base = "" if base.nil? || base.empty?
26
+ item = "" if item.nil? || item.empty?
27
+ @join ||= {}
28
+ @join[base] ||= {}
29
+ @join[base][item] ||= File.join(base, item).freeze
30
+ end
31
+
32
+ # Ensures the questionable path is prefixed with the base directory
33
+ # and prepends the questionable path with the base directory if false.
34
+ #
35
+ # Returns a frozen string.
36
+ def sanitized_path(base_directory, questionable_path)
37
+ @sanitized_path ||= {}
38
+ @sanitized_path[base_directory] ||= {}
39
+ @sanitized_path[base_directory][questionable_path] ||= begin
40
+ if questionable_path.nil?
41
+ base_directory.freeze
42
+ else
43
+ sanitize_and_join(base_directory, questionable_path).freeze
44
+ end
45
+ end
46
+ end
47
+
48
+ private
49
+
50
+ def sanitize_and_join(base_directory, questionable_path)
51
+ clean_path = if questionable_path.start_with?("~")
52
+ questionable_path.dup.insert(0, "/")
53
+ else
54
+ questionable_path
55
+ end
56
+ clean_path = File.expand_path(clean_path, "/")
57
+ return clean_path if clean_path.eql?(base_directory)
58
+
59
+ # remove any remaining extra leading slashes not stripped away by calling
60
+ # `File.expand_path` above.
61
+ clean_path.squeeze!("/")
62
+ return clean_path if clean_path.start_with?(slashed_dir_cache(base_directory))
63
+
64
+ clean_path.sub!(%r!\A\w:/!, "/")
65
+ join(base_directory, clean_path)
66
+ end
67
+
68
+ def slashed_dir_cache(base_directory)
69
+ @slashed_dir_cache ||= {}
70
+ @slashed_dir_cache[base_directory] ||= base_directory.sub(%r!\z!, "/")
71
+ end
29
72
  end
30
73
  end
31
74
  end
data/lib/jekyll/reader.rb CHANGED
@@ -164,8 +164,6 @@ module Jekyll
164
164
  entry_filter = EntryFilter.new(site)
165
165
 
166
166
  site.include.each do |entry|
167
- next if entry == ".htaccess"
168
-
169
167
  entry_path = site.in_source_dir(entry)
170
168
  next if File.directory?(entry_path)
171
169
  next if entry_filter.symlink?(entry_path)
@@ -175,13 +173,20 @@ module Jekyll
175
173
  end
176
174
 
177
175
  def read_included_file(entry_path)
178
- dir = File.dirname(entry_path).sub(site.source, "")
179
- file = Array(File.basename(entry_path))
180
176
  if Utils.has_yaml_header?(entry_path)
181
- site.pages.concat(PageReader.new(site, dir).read(file))
177
+ conditionally_generate_entry(entry_path, site.pages, PageReader)
182
178
  else
183
- site.static_files.concat(StaticFileReader.new(site, dir).read(file))
179
+ conditionally_generate_entry(entry_path, site.static_files, StaticFileReader)
184
180
  end
185
181
  end
182
+
183
+ def conditionally_generate_entry(entry_path, container, reader)
184
+ return if container.find { |item| site.in_source_dir(item.relative_path) == entry_path }
185
+
186
+ dir, files = File.split(entry_path)
187
+ dir.sub!(site.source, "")
188
+ files = Array(files)
189
+ container.concat(reader.new(site, dir).read(files))
190
+ end
186
191
  end
187
192
  end
@@ -8,6 +8,7 @@ module Jekyll
8
8
  @site = site
9
9
  @content = {}
10
10
  @entry_filter = EntryFilter.new(site)
11
+ @source_dir = site.in_source_dir("/")
11
12
  end
12
13
 
13
14
  # Read all the files in <dir> and adds them to @content
@@ -53,6 +54,8 @@ module Jekyll
53
54
  #
54
55
  # Returns the contents of the data file.
55
56
  def read_data_file(path)
57
+ Jekyll.logger.debug "Reading:", path.sub(@source_dir, "")
58
+
56
59
  case File.extname(path).downcase
57
60
  when ".csv"
58
61
  CSV.read(path,
@@ -57,7 +57,7 @@ module Jekyll
57
57
  Document.new(path,
58
58
  :site => @site,
59
59
  :collection => @site.posts)
60
- end.reject(&:nil?)
60
+ end.tap(&:compact!)
61
61
  end
62
62
 
63
63
  private
@@ -46,7 +46,7 @@ module Jekyll
46
46
  end
47
47
 
48
48
  def most_recent_posts
49
- @most_recent_posts ||= (site.posts.docs.last(11).reverse - [post]).first(10)
49
+ @most_recent_posts ||= (site.posts.docs.last(11).reverse! - [post]).first(10)
50
50
  end
51
51
  end
52
52
  end
@@ -36,7 +36,7 @@ module Jekyll
36
36
  #
37
37
  # Returns Array of Converter instances.
38
38
  def converters
39
- @converters ||= site.converters.select { |c| c.matches(document.extname) }.sort
39
+ @converters ||= site.converters.select { |c| c.matches(document.extname) }.tap(&:sort!)
40
40
  end
41
41
 
42
42
  # Determine the extname the outputted file should have
@@ -66,7 +66,7 @@ module Jekyll
66
66
  # Render the document.
67
67
  #
68
68
  # Returns String rendered document output
69
- # rubocop: disable Metrics/AbcSize
69
+ # rubocop: disable Metrics/AbcSize, Metrics/MethodLength
70
70
  def render_document
71
71
  info = {
72
72
  :registers => { :site => site, :page => payload["page"] },
@@ -84,6 +84,10 @@ module Jekyll
84
84
  output = convert(output.to_s)
85
85
  document.content = output
86
86
 
87
+ Jekyll.logger.debug "Post-Convert Hooks:", document.relative_path
88
+ document.trigger_hooks(:post_convert)
89
+ output = document.content
90
+
87
91
  if document.place_in_layout?
88
92
  Jekyll.logger.debug "Rendering Layout:", document.relative_path
89
93
  output = place_in_layouts(output, payload, info)
@@ -91,7 +95,7 @@ module Jekyll
91
95
 
92
96
  output
93
97
  end
94
- # rubocop: enable Metrics/AbcSize
98
+ # rubocop: enable Metrics/AbcSize, Metrics/MethodLength
95
99
 
96
100
  # Convert the document using the converters which match this renderer's document.
97
101
  #
@@ -251,7 +255,7 @@ module Jekyll
251
255
  def output_exts
252
256
  @output_exts ||= converters.map do |c|
253
257
  c.output_ext(document.extname)
254
- end.compact
258
+ end.tap(&:compact!)
255
259
  end
256
260
 
257
261
  def liquid_options