jekyll 3.9.3 → 4.4.1

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 (108) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +511 -89
  3. data/LICENSE +1 -1
  4. data/README.markdown +48 -27
  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/base.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/cache.rb +186 -0
  11. data/lib/jekyll/cleaner.rb +8 -7
  12. data/lib/jekyll/collection.rb +84 -11
  13. data/lib/jekyll/command.rb +33 -6
  14. data/lib/jekyll/commands/build.rb +8 -28
  15. data/lib/jekyll/commands/clean.rb +3 -2
  16. data/lib/jekyll/commands/doctor.rb +46 -35
  17. data/lib/jekyll/commands/help.rb +1 -1
  18. data/lib/jekyll/commands/new.rb +44 -50
  19. data/lib/jekyll/commands/new_theme.rb +27 -28
  20. data/lib/jekyll/commands/serve/live_reload_reactor.rb +9 -16
  21. data/lib/jekyll/commands/serve/servlet.rb +21 -22
  22. data/lib/jekyll/commands/serve/websockets.rb +1 -1
  23. data/lib/jekyll/commands/serve.rb +75 -97
  24. data/lib/jekyll/configuration.rb +66 -158
  25. data/lib/jekyll/converters/identity.rb +18 -0
  26. data/lib/jekyll/converters/markdown/kramdown_parser.rb +83 -33
  27. data/lib/jekyll/converters/markdown.rb +49 -40
  28. data/lib/jekyll/converters/smartypants.rb +34 -14
  29. data/lib/jekyll/convertible.rb +36 -34
  30. data/lib/jekyll/deprecator.rb +2 -4
  31. data/lib/jekyll/document.rb +107 -72
  32. data/lib/jekyll/drops/collection_drop.rb +3 -4
  33. data/lib/jekyll/drops/document_drop.rb +9 -3
  34. data/lib/jekyll/drops/drop.rb +115 -33
  35. data/lib/jekyll/drops/excerpt_drop.rb +8 -0
  36. data/lib/jekyll/drops/site_drop.rb +9 -8
  37. data/lib/jekyll/drops/static_file_drop.rb +4 -4
  38. data/lib/jekyll/drops/theme_drop.rb +39 -0
  39. data/lib/jekyll/drops/unified_payload_drop.rb +7 -2
  40. data/lib/jekyll/drops/url_drop.rb +55 -3
  41. data/lib/jekyll/entry_filter.rb +42 -51
  42. data/lib/jekyll/excerpt.rb +48 -38
  43. data/lib/jekyll/external.rb +20 -19
  44. data/lib/jekyll/filters/date_filters.rb +6 -3
  45. data/lib/jekyll/filters/grouping_filters.rb +1 -2
  46. data/lib/jekyll/filters/url_filters.rb +50 -15
  47. data/lib/jekyll/filters.rb +211 -50
  48. data/lib/jekyll/frontmatter_defaults.rb +45 -36
  49. data/lib/jekyll/hooks.rb +26 -26
  50. data/lib/jekyll/inclusion.rb +32 -0
  51. data/lib/jekyll/layout.rb +12 -19
  52. data/lib/jekyll/liquid_extensions.rb +0 -2
  53. data/lib/jekyll/liquid_renderer/file.rb +24 -3
  54. data/lib/jekyll/liquid_renderer/table.rb +26 -77
  55. data/lib/jekyll/liquid_renderer.rb +31 -16
  56. data/lib/jekyll/log_adapter.rb +5 -1
  57. data/lib/jekyll/page.rb +51 -23
  58. data/lib/jekyll/page_excerpt.rb +25 -0
  59. data/lib/jekyll/page_without_a_file.rb +0 -4
  60. data/lib/jekyll/path_manager.rb +74 -0
  61. data/lib/jekyll/plugin.rb +5 -11
  62. data/lib/jekyll/plugin_manager.rb +15 -5
  63. data/lib/jekyll/profiler.rb +51 -0
  64. data/lib/jekyll/reader.rb +65 -10
  65. data/lib/jekyll/readers/collection_reader.rb +1 -0
  66. data/lib/jekyll/readers/data_reader.rb +48 -10
  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 +32 -19
  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/related_posts.rb +1 -1
  74. data/lib/jekyll/renderer.rb +34 -49
  75. data/lib/jekyll/site.rb +151 -58
  76. data/lib/jekyll/static_file.rb +64 -28
  77. data/lib/jekyll/stevenson.rb +4 -8
  78. data/lib/jekyll/tags/highlight.rb +44 -57
  79. data/lib/jekyll/tags/include.rb +114 -80
  80. data/lib/jekyll/tags/link.rb +12 -7
  81. data/lib/jekyll/tags/post_url.rb +33 -30
  82. data/lib/jekyll/theme.rb +20 -18
  83. data/lib/jekyll/theme_builder.rb +91 -89
  84. data/lib/jekyll/url.rb +18 -10
  85. data/lib/jekyll/utils/ansi.rb +2 -2
  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 +37 -52
  89. data/lib/jekyll/utils/thread_event.rb +1 -5
  90. data/lib/jekyll/utils.rb +29 -28
  91. data/lib/jekyll/version.rb +1 -1
  92. data/lib/jekyll.rb +9 -14
  93. data/lib/site_template/.gitignore +2 -0
  94. data/lib/site_template/404.html +2 -1
  95. data/lib/site_template/_config.yml +17 -5
  96. data/lib/site_template/_posts/0000-00-00-welcome-to-jekyll.markdown.erb +5 -1
  97. data/lib/theme_template/README.md.erb +1 -3
  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 +150 -0
  101. data/rubocop/jekyll/no_p_allowed.rb +5 -6
  102. data/rubocop/jekyll/no_puts_allowed.rb +5 -6
  103. metadata +149 -37
  104. data/lib/jekyll/converters/markdown/rdiscount_parser.rb +0 -37
  105. data/lib/jekyll/converters/markdown/redcarpet_parser.rb +0 -112
  106. data/lib/jekyll/utils/rouge.rb +0 -22
  107. /data/lib/site_template/{about.md → about.markdown} +0 -0
  108. /data/lib/site_template/{index.md → index.markdown} +0 -0
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 => [],
@@ -60,26 +63,26 @@ module Jekyll
60
63
  # Ensure the priority is a Fixnum
61
64
  def self.priority_value(priority)
62
65
  return priority if priority.is_a?(Integer)
66
+
63
67
  PRIORITY_MAP[priority] || DEFAULT_PRIORITY
64
68
  end
65
69
 
66
70
  # register a single hook to be called later, internal API
67
71
  def self.register_one(owner, event, priority, &block)
68
72
  @registry[owner] ||= {
69
- :post_init => [],
70
- :pre_render => [],
71
- :post_render => [],
72
- :post_write => [],
73
+ :post_init => [],
74
+ :pre_render => [],
75
+ :post_convert => [],
76
+ :post_render => [],
77
+ :post_write => [],
73
78
  }
74
79
 
75
80
  unless @registry[owner][event]
76
- raise NotAvailable, "Invalid hook. #{owner} supports only the " \
77
- "following hooks #{@registry[owner].keys.inspect}"
81
+ raise NotAvailable, "Invalid hook. #{owner} supports only the following hooks " \
82
+ "#{@registry[owner].keys.inspect}"
78
83
  end
79
84
 
80
- unless block.respond_to? :call
81
- raise Uncallable, "Hooks must respond to :call"
82
- end
85
+ raise Uncallable, "Hooks must respond to :call" unless block.respond_to? :call
83
86
 
84
87
  insert_hook owner, event, priority, &block
85
88
  end
@@ -92,11 +95,8 @@ module Jekyll
92
95
  # interface for Jekyll core components to trigger hooks
93
96
  def self.trigger(owner, event, *args)
94
97
  # proceed only if there are hooks to call
95
- return unless @registry[owner]
96
- return unless @registry[owner][event]
97
-
98
- # hooks to call for this owner and event
99
- hooks = @registry[owner][event]
98
+ hooks = @registry.dig(owner, event)
99
+ return if hooks.nil? || hooks.empty?
100
100
 
101
101
  # sort and call hooks according to priority and load order
102
102
  hooks.sort_by { |h| @hook_priority[h] }.each do |hook|
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jekyll
4
+ class Inclusion
5
+ attr_reader :site, :name, :path
6
+ private :site
7
+
8
+ def initialize(site, base, name)
9
+ @site = site
10
+ @name = name
11
+ @path = PathManager.join(base, name)
12
+ end
13
+
14
+ def render(context)
15
+ @template ||= site.liquid_renderer.file(path).parse(content)
16
+ @template.render!(context)
17
+ rescue Liquid::Error => e
18
+ e.template_name = path
19
+ e.markup_context = "included " if e.markup_context.nil?
20
+ raise e
21
+ end
22
+
23
+ def content
24
+ @content ||= File.read(path, **site.file_read_opts)
25
+ end
26
+
27
+ def inspect
28
+ "#{self.class} #{path.inspect}"
29
+ end
30
+ alias_method :to_s, :inspect
31
+ end
32
+ end
data/lib/jekyll/layout.rb CHANGED
@@ -4,26 +4,14 @@ module Jekyll
4
4
  class Layout
5
5
  include Convertible
6
6
 
7
- # Gets the Site object.
8
- attr_reader :site
7
+ attr_accessor :content, # content of layout
8
+ :data, # the Hash that holds the metadata for this layout
9
+ :ext # extension of layout
9
10
 
10
- # Gets the name of this layout.
11
- attr_reader :name
12
-
13
- # Gets the path to this layout.
14
- attr_reader :path
15
-
16
- # Gets the path to this layout relative to its base
17
- attr_reader :relative_path
18
-
19
- # Gets/Sets the extension of this layout.
20
- attr_accessor :ext
21
-
22
- # Gets/Sets the Hash that holds the metadata for this layout.
23
- attr_accessor :data
24
-
25
- # Gets/Sets the content of this layout.
26
- attr_accessor :content
11
+ attr_reader :name, # name of layout
12
+ :path, # path to layout
13
+ :site, # the Site object
14
+ :relative_path # path to layout relative to its base
27
15
 
28
16
  # Initialize a new Layout.
29
17
  #
@@ -58,5 +46,10 @@ module Jekyll
58
46
  def process(name)
59
47
  self.ext = File.extname(name)
60
48
  end
49
+
50
+ # Returns the object as a debug String.
51
+ def inspect
52
+ "#<#{self.class} @path=#{@path.inspect}>"
53
+ end
61
54
  end
62
55
  end
@@ -2,7 +2,6 @@
2
2
 
3
3
  module Jekyll
4
4
  module LiquidExtensions
5
-
6
5
  # Lookup a Liquid variable in the given context.
7
6
  #
8
7
  # context - the Liquid context in question.
@@ -19,6 +18,5 @@ module Jekyll
19
18
 
20
19
  lookup || variable
21
20
  end
22
-
23
21
  end
24
22
  end
@@ -10,24 +10,34 @@ module Jekyll
10
10
 
11
11
  def parse(content)
12
12
  measure_time do
13
- @template = Liquid::Template.parse(content, :line_numbers => true)
13
+ @renderer.cache[@filename] ||= Liquid::Template.parse(content, :line_numbers => true)
14
14
  end
15
+ @template = @renderer.cache[@filename]
15
16
 
16
17
  self
17
18
  end
18
19
 
19
20
  def render(*args)
21
+ reset_template_assigns
22
+
20
23
  measure_time do
21
24
  measure_bytes do
22
- @template.render(*args)
25
+ measure_counts do
26
+ @template.render(*args)
27
+ end
23
28
  end
24
29
  end
25
30
  end
26
31
 
32
+ # This method simply 'rethrows any error' before attempting to render the template.
27
33
  def render!(*args)
34
+ reset_template_assigns
35
+
28
36
  measure_time do
29
37
  measure_bytes do
30
- @template.render!(*args)
38
+ measure_counts do
39
+ @template.render!(*args)
40
+ end
31
41
  end
32
42
  end
33
43
  end
@@ -38,6 +48,17 @@ module Jekyll
38
48
 
39
49
  private
40
50
 
51
+ # clear assigns to `Liquid::Template` instance prior to rendering since
52
+ # `Liquid::Template` instances are cached in Jekyll 4.
53
+ def reset_template_assigns
54
+ @template.instance_assigns.clear
55
+ end
56
+
57
+ def measure_counts
58
+ @renderer.increment_count(@filename)
59
+ yield
60
+ end
61
+
41
62
  def measure_bytes
42
63
  yield.tap do |str|
43
64
  @renderer.increment_bytes(@filename, str.bytesize)
@@ -1,96 +1,45 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Jekyll
4
- class LiquidRenderer::Table
5
- def initialize(stats)
6
- @stats = stats
7
- end
8
-
9
- def to_s(num_of_rows = 50)
10
- data = data_for_table(num_of_rows)
11
- widths = table_widths(data)
12
- generate_table(data, widths)
13
- end
14
-
15
- private
16
-
17
- def generate_table(data, widths)
18
- str = String.new("\n")
19
-
20
- table_head = data.shift
21
- str << generate_row(table_head, widths)
22
- str << generate_table_head_border(table_head, widths)
4
+ class LiquidRenderer
5
+ class Table
6
+ GAUGES = [:count, :bytes, :time].freeze
23
7
 
24
- data.each do |row_data|
25
- str << generate_row(row_data, widths)
8
+ def initialize(stats)
9
+ @stats = stats
26
10
  end
27
11
 
28
- str << "\n"
29
- str
30
- end
31
-
32
- def generate_table_head_border(row_data, widths)
33
- str = String.new("")
34
-
35
- row_data.each_index do |cell_index|
36
- str << "-" * widths[cell_index]
37
- str << "-+-" unless cell_index == row_data.length - 1
12
+ def to_s(num_of_rows = 50)
13
+ Jekyll::Profiler.tabulate(data_for_table(num_of_rows))
38
14
  end
39
15
 
40
- str << "\n"
41
- str
42
- end
16
+ private
43
17
 
44
- def generate_row(row_data, widths)
45
- str = String.new("")
18
+ def data_for_table(num_of_rows)
19
+ sorted = @stats.sort_by { |_, file_stats| -file_stats[:time] }
20
+ sorted = sorted.slice(0, num_of_rows)
46
21
 
47
- row_data.each_with_index do |cell_data, cell_index|
48
- str << if cell_index.zero?
49
- cell_data.ljust(widths[cell_index], " ")
50
- else
51
- cell_data.rjust(widths[cell_index], " ")
52
- end
22
+ table = [header_labels]
23
+ sorted.each do |filename, file_stats|
24
+ row = []
25
+ row << filename
26
+ row << file_stats[:count].to_s
27
+ row << format_bytes(file_stats[:bytes])
28
+ row << format("%.3f", file_stats[:time])
29
+ table << row
30
+ end
53
31
 
54
- str << " | " unless cell_index == row_data.length - 1
32
+ table
55
33
  end
56
34
 
57
- str << "\n"
58
- str
59
- end
60
-
61
- def table_widths(data)
62
- widths = []
63
-
64
- data.each do |row|
65
- row.each_with_index do |cell, index|
66
- widths[index] = [cell.length, widths[index]].compact.max
67
- end
35
+ def header_labels
36
+ GAUGES.map { |gauge| gauge.to_s.capitalize }.unshift("Filename")
68
37
  end
69
38
 
70
- widths
71
- end
72
-
73
- def data_for_table(num_of_rows)
74
- sorted = @stats.sort_by { |_, file_stats| -file_stats[:time] }
75
- sorted = sorted.slice(0, num_of_rows)
76
-
77
- table = [%w(Filename Count Bytes Time)]
78
-
79
- sorted.each do |filename, file_stats|
80
- row = []
81
- row << filename
82
- row << file_stats[:count].to_s
83
- row << format_bytes(file_stats[:bytes])
84
- row << format("%.3f", file_stats[:time])
85
- table << row
39
+ def format_bytes(bytes)
40
+ bytes /= 1024.0
41
+ format("%.2fK", bytes)
86
42
  end
87
-
88
- table
89
- end
90
-
91
- def format_bytes(bytes)
92
- bytes /= 1024.0
93
- format("%.2fK", bytes)
94
43
  end
95
44
  end
96
45
  end
@@ -5,11 +5,6 @@ require_relative "liquid_renderer/table"
5
5
 
6
6
  module Jekyll
7
7
  class LiquidRenderer
8
- extend Forwardable
9
-
10
- private def_delegator :@site, :in_source_dir, :source_dir
11
- private def_delegator :@site, :in_theme_dir, :theme_dir
12
-
13
8
  def initialize(site)
14
9
  @site = site
15
10
  Liquid::Template.error_mode = @site.config["liquid"]["error_mode"].to_sym
@@ -18,19 +13,13 @@ module Jekyll
18
13
 
19
14
  def reset
20
15
  @stats = {}
16
+ @cache = {}
21
17
  end
22
18
 
23
19
  def file(filename)
24
- filename.match(filename_regex)
25
- filename =
26
- if Regexp.last_match(1) == theme_dir("")
27
- ::File.join(::File.basename(Regexp.last_match(1)), Regexp.last_match(2))
28
- else
29
- Regexp.last_match(2)
30
- end
20
+ filename = normalize_path(filename)
31
21
  LiquidRenderer::File.new(self, filename).tap do
32
22
  @stats[filename] ||= new_profile_hash
33
- @stats[filename][:count] += 1
34
23
  end
35
24
  end
36
25
 
@@ -42,6 +31,10 @@ module Jekyll
42
31
  @stats[filename][:time] += time
43
32
  end
44
33
 
34
+ def increment_count(filename)
35
+ @stats[filename][:count] += 1
36
+ end
37
+
45
38
  def stats_table(num_of_rows = 50)
46
39
  LiquidRenderer::Table.new(@stats).to_s(num_of_rows)
47
40
  end
@@ -50,11 +43,33 @@ module Jekyll
50
43
  "#{error.message} in #{path}"
51
44
  end
52
45
 
46
+ # A persistent cache to store and retrieve parsed templates based on the filename
47
+ # via `LiquidRenderer::File#parse`
48
+ #
49
+ # It is emptied when `self.reset` is called.
50
+ def cache
51
+ @cache ||= {}
52
+ end
53
+
53
54
  private
54
55
 
55
- def filename_regex
56
- @filename_regex ||= begin
57
- %r!\A(#{Regexp.escape(source_dir)}/|#{Regexp.escape(theme_dir.to_s)}/|/*)(.*)!i
56
+ def normalize_path(filename)
57
+ @normalize_path ||= {}
58
+ @normalize_path[filename] ||= begin
59
+ theme_dir = @site.theme&.root
60
+ case filename
61
+ when %r!\A(#{Regexp.escape(@site.source)}/)(?<rest>.*)!io
62
+ Regexp.last_match(:rest)
63
+ when %r!(/gems/.*)*/gems/(?<dirname>[^/]+)(?<rest>.*)!,
64
+ %r!(?<dirname>[^/]+/lib)(?<rest>.*)!
65
+ "#{Regexp.last_match(:dirname)}#{Regexp.last_match(:rest)}"
66
+ when theme_dir && %r!\A#{Regexp.escape(theme_dir)}/(?<rest>.*)!io
67
+ PathManager.join(@site.theme.basename, Regexp.last_match(:rest))
68
+ when %r!\A/(.*)!
69
+ Regexp.last_match(1)
70
+ else
71
+ filename
72
+ end
58
73
  end
59
74
  end
60
75
 
@@ -29,7 +29,9 @@ module Jekyll
29
29
  #
30
30
  # Returns nothing
31
31
  def log_level=(level)
32
- writer.level = LOG_LEVELS.fetch(level)
32
+ writer.level = level if level.is_a?(Integer) && level.between?(0, 3)
33
+ writer.level = LOG_LEVELS[level] ||
34
+ raise(ArgumentError, "unknown log level")
33
35
  @level = level
34
36
  end
35
37
 
@@ -41,6 +43,7 @@ module Jekyll
41
43
  self.log_level = :debug
42
44
  end
43
45
  debug "Logging at level:", LOG_LEVELS.key(writer.level).to_s
46
+ debug "Jekyll Version:", Jekyll::VERSION
44
47
  end
45
48
 
46
49
  # Public: Print a debug message
@@ -141,6 +144,7 @@ module Jekyll
141
144
  # the appropriate writer method, e.g. writer.info.
142
145
  def write(level_of_message, topic, message = nil, &block)
143
146
  return false unless write_message?(level_of_message)
147
+
144
148
  writer.public_send(level_of_message, message(topic, message, &block))
145
149
  end
146
150
  end
data/lib/jekyll/page.rb CHANGED
@@ -5,18 +5,15 @@ module Jekyll
5
5
  include Convertible
6
6
 
7
7
  attr_writer :dir
8
- attr_accessor :site, :pager
9
- attr_accessor :name, :ext, :basename
10
- attr_accessor :data, :content, :output
8
+ attr_accessor :basename, :content, :data, :ext, :name, :output, :pager, :site
11
9
 
12
10
  alias_method :extname, :ext
13
11
 
14
- FORWARD_SLASH = "/".freeze
15
-
16
12
  # Attributes for Liquid templates
17
13
  ATTRIBUTES_FOR_LIQUID = %w(
18
14
  content
19
15
  dir
16
+ excerpt
20
17
  name
21
18
  path
22
19
  url
@@ -49,10 +46,11 @@ module Jekyll
49
46
  end
50
47
 
51
48
  process(name)
52
- read_yaml(File.join(base, dir), name)
49
+ read_yaml(PathManager.join(base, dir), name)
50
+ generate_excerpt if site.config["page_excerpts"]
53
51
 
54
52
  data.default_proc = proc do |_, key|
55
- site.frontmatter_defaults.find(File.join(dir, name), type, key)
53
+ site.frontmatter_defaults.find(relative_path, type, key)
56
54
  end
57
55
 
58
56
  Jekyll::Hooks.trigger :pages, :post_init, self
@@ -64,12 +62,7 @@ module Jekyll
64
62
  #
65
63
  # Returns the String destination directory.
66
64
  def dir
67
- if url.end_with?(FORWARD_SLASH)
68
- url
69
- else
70
- url_dir = File.dirname(url)
71
- url_dir.end_with?(FORWARD_SLASH) ? url_dir : "#{url_dir}/"
72
- end
65
+ url.end_with?("/") ? url : url_dir
73
66
  end
74
67
 
75
68
  # The full path and filename of the post. Defined in the YAML of the post
@@ -97,11 +90,11 @@ module Jekyll
97
90
  #
98
91
  # Returns the String url.
99
92
  def url
100
- @url ||= URL.new({
93
+ @url ||= URL.new(
101
94
  :template => template,
102
95
  :placeholders => url_placeholders,
103
- :permalink => permalink,
104
- }).to_s
96
+ :permalink => permalink
97
+ ).to_s
105
98
  end
106
99
 
107
100
  # Returns a hash of URL placeholder names (as symbols) mapping to the
@@ -118,10 +111,13 @@ module Jekyll
118
111
  #
119
112
  # name - The String filename of the page file.
120
113
  #
114
+ # NOTE: `String#gsub` removes all trailing periods (in comparison to `String#chomp`)
121
115
  # Returns nothing.
122
116
  def process(name)
117
+ return unless name
118
+
123
119
  self.ext = File.extname(name)
124
- self.basename = name[0..-ext.length - 1]
120
+ self.basename = name[0..-ext.length - 1].gsub(%r!\.*\z!, "")
125
121
  end
126
122
 
127
123
  # Add any necessary layouts to this post
@@ -146,7 +142,7 @@ module Jekyll
146
142
 
147
143
  # The path to the page source file, relative to the site source
148
144
  def relative_path
149
- File.join(*[@dir, @name].map(&:to_s).reject(&:empty?)).sub(%r!\A\/!, "")
145
+ @relative_path ||= PathManager.join(@dir, @name).delete_prefix("/")
150
146
  end
151
147
 
152
148
  # Obtain destination path.
@@ -155,15 +151,18 @@ module Jekyll
155
151
  #
156
152
  # Returns the destination file path String.
157
153
  def destination(dest)
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
154
+ @destination ||= {}
155
+ @destination[dest] ||= begin
156
+ path = site.in_dest_dir(dest, URL.unescape_path(url))
157
+ path = File.join(path, "index") if url.end_with?("/")
158
+ path << output_ext unless path.end_with? output_ext
159
+ path
160
+ end
162
161
  end
163
162
 
164
163
  # Returns the object as a debug String.
165
164
  def inspect
166
- "#<Jekyll::Page @name=#{name.inspect}>"
165
+ "#<#{self.class} @relative_path=#{relative_path.inspect}>"
167
166
  end
168
167
 
169
168
  # Returns the Boolean of whether this Page is HTML or not.
@@ -183,5 +182,34 @@ module Jekyll
183
182
  def write?
184
183
  true
185
184
  end
185
+
186
+ def excerpt_separator
187
+ @excerpt_separator ||= (data["excerpt_separator"] || site.config["excerpt_separator"]).to_s
188
+ end
189
+
190
+ def excerpt
191
+ return @excerpt if defined?(@excerpt)
192
+
193
+ @excerpt = data["excerpt"] ? data["excerpt"].to_s : nil
194
+ end
195
+
196
+ def generate_excerpt?
197
+ !excerpt_separator.empty? && instance_of?(Jekyll::Page) && html?
198
+ end
199
+
200
+ private
201
+
202
+ def generate_excerpt
203
+ return unless generate_excerpt?
204
+
205
+ data["excerpt"] ||= Jekyll::PageExcerpt.new(self)
206
+ end
207
+
208
+ def url_dir
209
+ @url_dir ||= begin
210
+ value = File.dirname(url)
211
+ value.end_with?("/") ? value : "#{value}/"
212
+ end
213
+ end
186
214
  end
187
215
  end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jekyll
4
+ class PageExcerpt < Excerpt
5
+ attr_reader :doc
6
+ alias_method :id, :relative_path
7
+
8
+ EXCERPT_ATTRIBUTES = (Page::ATTRIBUTES_FOR_LIQUID - %w(excerpt)).freeze
9
+ private_constant :EXCERPT_ATTRIBUTES
10
+
11
+ def to_liquid
12
+ @to_liquid ||= doc.to_liquid(EXCERPT_ATTRIBUTES)
13
+ end
14
+
15
+ def render_with_liquid?
16
+ return false if data["render_with_liquid"] == false
17
+
18
+ Jekyll::Utils.has_liquid_construct?(content)
19
+ end
20
+
21
+ def inspect
22
+ "#<#{self.class} id=#{id.inspect}>"
23
+ end
24
+ end
25
+ end
@@ -10,9 +10,5 @@ module Jekyll
10
10
  def read_yaml(*)
11
11
  @data ||= {}
12
12
  end
13
-
14
- def inspect
15
- "#<Jekyll:PageWithoutAFile @name=#{name.inspect}>"
16
- end
17
13
  end
18
14
  end