jekyll 4.2.1 → 4.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (128) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +474 -350
  3. data/LICENSE +21 -21
  4. data/README.markdown +83 -86
  5. data/exe/jekyll +57 -57
  6. data/lib/blank_template/_config.yml +3 -3
  7. data/lib/blank_template/_layouts/default.html +12 -12
  8. data/lib/blank_template/_sass/main.scss +9 -9
  9. data/lib/blank_template/assets/css/main.scss +4 -4
  10. data/lib/blank_template/index.md +8 -8
  11. data/lib/jekyll/cache.rb +186 -190
  12. data/lib/jekyll/cleaner.rb +111 -111
  13. data/lib/jekyll/collection.rb +310 -309
  14. data/lib/jekyll/command.rb +105 -105
  15. data/lib/jekyll/commands/build.rb +82 -93
  16. data/lib/jekyll/commands/clean.rb +44 -45
  17. data/lib/jekyll/commands/doctor.rb +177 -177
  18. data/lib/jekyll/commands/help.rb +34 -34
  19. data/lib/jekyll/commands/new.rb +168 -169
  20. data/lib/jekyll/commands/new_theme.rb +39 -40
  21. data/lib/jekyll/commands/serve/live_reload_reactor.rb +119 -122
  22. data/lib/jekyll/commands/serve/livereload_assets/livereload.js +1183 -1183
  23. data/lib/jekyll/commands/serve/mime_types_charset.json +71 -0
  24. data/lib/jekyll/commands/serve/servlet.rb +206 -202
  25. data/lib/jekyll/commands/serve/websockets.rb +81 -81
  26. data/lib/jekyll/commands/serve.rb +367 -362
  27. data/lib/jekyll/configuration.rb +313 -313
  28. data/lib/jekyll/converter.rb +54 -54
  29. data/lib/jekyll/converters/identity.rb +41 -41
  30. data/lib/jekyll/converters/markdown/kramdown_parser.rb +197 -199
  31. data/lib/jekyll/converters/markdown.rb +113 -113
  32. data/lib/jekyll/converters/smartypants.rb +70 -70
  33. data/lib/jekyll/convertible.rb +257 -257
  34. data/lib/jekyll/data_entry.rb +83 -0
  35. data/lib/jekyll/data_hash.rb +61 -0
  36. data/lib/jekyll/deprecator.rb +50 -50
  37. data/lib/jekyll/document.rb +543 -544
  38. data/lib/jekyll/drops/collection_drop.rb +20 -20
  39. data/lib/jekyll/drops/document_drop.rb +71 -70
  40. data/lib/jekyll/drops/drop.rb +293 -293
  41. data/lib/jekyll/drops/excerpt_drop.rb +23 -19
  42. data/lib/jekyll/drops/jekyll_drop.rb +32 -32
  43. data/lib/jekyll/drops/site_drop.rb +71 -66
  44. data/lib/jekyll/drops/static_file_drop.rb +14 -14
  45. data/lib/jekyll/drops/theme_drop.rb +36 -0
  46. data/lib/jekyll/drops/unified_payload_drop.rb +30 -26
  47. data/lib/jekyll/drops/url_drop.rb +140 -140
  48. data/lib/jekyll/entry_filter.rb +117 -121
  49. data/lib/jekyll/errors.rb +20 -20
  50. data/lib/jekyll/excerpt.rb +200 -201
  51. data/lib/jekyll/external.rb +75 -79
  52. data/lib/jekyll/filters/date_filters.rb +110 -110
  53. data/lib/jekyll/filters/grouping_filters.rb +64 -64
  54. data/lib/jekyll/filters/url_filters.rb +98 -98
  55. data/lib/jekyll/filters.rb +532 -535
  56. data/lib/jekyll/frontmatter_defaults.rb +238 -240
  57. data/lib/jekyll/generator.rb +5 -5
  58. data/lib/jekyll/hooks.rb +107 -107
  59. data/lib/jekyll/inclusion.rb +32 -32
  60. data/lib/jekyll/layout.rb +55 -67
  61. data/lib/jekyll/liquid_extensions.rb +22 -22
  62. data/lib/jekyll/liquid_renderer/file.rb +77 -77
  63. data/lib/jekyll/liquid_renderer/table.rb +55 -55
  64. data/lib/jekyll/liquid_renderer.rb +80 -80
  65. data/lib/jekyll/log_adapter.rb +151 -151
  66. data/lib/jekyll/mime.types +939 -866
  67. data/lib/jekyll/page.rb +215 -217
  68. data/lib/jekyll/page_excerpt.rb +25 -25
  69. data/lib/jekyll/page_without_a_file.rb +14 -14
  70. data/lib/jekyll/path_manager.rb +74 -74
  71. data/lib/jekyll/plugin.rb +92 -92
  72. data/lib/jekyll/plugin_manager.rb +123 -115
  73. data/lib/jekyll/profiler.rb +55 -58
  74. data/lib/jekyll/publisher.rb +23 -23
  75. data/lib/jekyll/reader.rb +209 -192
  76. data/lib/jekyll/readers/collection_reader.rb +23 -23
  77. data/lib/jekyll/readers/data_reader.rb +116 -79
  78. data/lib/jekyll/readers/layout_reader.rb +62 -62
  79. data/lib/jekyll/readers/page_reader.rb +25 -25
  80. data/lib/jekyll/readers/post_reader.rb +85 -85
  81. data/lib/jekyll/readers/static_file_reader.rb +25 -25
  82. data/lib/jekyll/readers/theme_assets_reader.rb +52 -52
  83. data/lib/jekyll/regenerator.rb +195 -195
  84. data/lib/jekyll/related_posts.rb +52 -52
  85. data/lib/jekyll/renderer.rb +263 -265
  86. data/lib/jekyll/site.rb +582 -551
  87. data/lib/jekyll/static_file.rb +205 -208
  88. data/lib/jekyll/stevenson.rb +60 -60
  89. data/lib/jekyll/tags/highlight.rb +114 -110
  90. data/lib/jekyll/tags/include.rb +275 -275
  91. data/lib/jekyll/tags/link.rb +42 -42
  92. data/lib/jekyll/tags/post_url.rb +106 -106
  93. data/lib/jekyll/theme.rb +90 -86
  94. data/lib/jekyll/theme_builder.rb +121 -121
  95. data/lib/jekyll/url.rb +167 -167
  96. data/lib/jekyll/utils/ansi.rb +57 -57
  97. data/lib/jekyll/utils/exec.rb +26 -26
  98. data/lib/jekyll/utils/internet.rb +37 -37
  99. data/lib/jekyll/utils/platforms.rb +67 -67
  100. data/lib/jekyll/utils/thread_event.rb +31 -31
  101. data/lib/jekyll/utils/win_tz.rb +46 -75
  102. data/lib/jekyll/utils.rb +378 -367
  103. data/lib/jekyll/version.rb +5 -5
  104. data/lib/jekyll.rb +197 -195
  105. data/lib/site_template/.gitignore +5 -5
  106. data/lib/site_template/404.html +25 -25
  107. data/lib/site_template/_config.yml +55 -55
  108. data/lib/site_template/_posts/0000-00-00-welcome-to-jekyll.markdown.erb +29 -29
  109. data/lib/site_template/about.markdown +18 -18
  110. data/lib/site_template/index.markdown +6 -6
  111. data/lib/theme_template/CODE_OF_CONDUCT.md.erb +74 -74
  112. data/lib/theme_template/Gemfile +4 -4
  113. data/lib/theme_template/LICENSE.txt.erb +21 -21
  114. data/lib/theme_template/README.md.erb +50 -52
  115. data/lib/theme_template/_layouts/default.html +1 -1
  116. data/lib/theme_template/_layouts/page.html +5 -5
  117. data/lib/theme_template/_layouts/post.html +5 -5
  118. data/lib/theme_template/example/_config.yml.erb +1 -1
  119. data/lib/theme_template/example/_post.md +12 -12
  120. data/lib/theme_template/example/index.html +14 -14
  121. data/lib/theme_template/example/style.scss +7 -7
  122. data/lib/theme_template/gitignore.erb +6 -6
  123. data/lib/theme_template/theme.gemspec.erb +16 -16
  124. data/rubocop/jekyll/assert_equal_literal_actual.rb +149 -149
  125. data/rubocop/jekyll/no_p_allowed.rb +23 -23
  126. data/rubocop/jekyll/no_puts_allowed.rb +23 -23
  127. data/rubocop/jekyll.rb +5 -5
  128. metadata +62 -14
data/lib/jekyll/page.rb CHANGED
@@ -1,217 +1,215 @@
1
- # frozen_string_literal: true
2
-
3
- module Jekyll
4
- class Page
5
- include Convertible
6
-
7
- attr_writer :dir
8
- attr_accessor :site, :pager
9
- attr_accessor :name, :ext, :basename
10
- attr_accessor :data, :content, :output
11
-
12
- alias_method :extname, :ext
13
-
14
- # Attributes for Liquid templates
15
- ATTRIBUTES_FOR_LIQUID = %w(
16
- content
17
- dir
18
- excerpt
19
- name
20
- path
21
- url
22
- ).freeze
23
-
24
- # A set of extensions that are considered HTML or HTML-like so we
25
- # should not alter them, this includes .xhtml through XHTM5.
26
-
27
- HTML_EXTENSIONS = %w(
28
- .html
29
- .xhtml
30
- .htm
31
- ).freeze
32
-
33
- # Initialize a new Page.
34
- #
35
- # site - The Site object.
36
- # base - The String path to the source.
37
- # dir - The String path between the source and the file.
38
- # name - The String filename of the file.
39
- def initialize(site, base, dir, name)
40
- @site = site
41
- @base = base
42
- @dir = dir
43
- @name = name
44
- @path = if site.in_theme_dir(base) == base # we're in a theme
45
- site.in_theme_dir(base, dir, name)
46
- else
47
- site.in_source_dir(base, dir, name)
48
- end
49
-
50
- process(name)
51
- read_yaml(PathManager.join(base, dir), name)
52
- generate_excerpt if site.config["page_excerpts"]
53
-
54
- data.default_proc = proc do |_, key|
55
- site.frontmatter_defaults.find(relative_path, type, key)
56
- end
57
-
58
- Jekyll::Hooks.trigger :pages, :post_init, self
59
- end
60
-
61
- # The generated directory into which the page will be placed
62
- # upon generation. This is derived from the permalink or, if
63
- # permalink is absent, will be '/'
64
- #
65
- # Returns the String destination directory.
66
- def dir
67
- url.end_with?("/") ? url : url_dir
68
- end
69
-
70
- # The full path and filename of the post. Defined in the YAML of the post
71
- # body.
72
- #
73
- # Returns the String permalink or nil if none has been set.
74
- def permalink
75
- data.nil? ? nil : data["permalink"]
76
- end
77
-
78
- # The template of the permalink.
79
- #
80
- # Returns the template String.
81
- def template
82
- if !html?
83
- "/:path/:basename:output_ext"
84
- elsif index?
85
- "/:path/"
86
- else
87
- Utils.add_permalink_suffix("/:path/:basename", site.permalink_style)
88
- end
89
- end
90
-
91
- # The generated relative url of this page. e.g. /about.html.
92
- #
93
- # Returns the String url.
94
- def url
95
- @url ||= URL.new(
96
- :template => template,
97
- :placeholders => url_placeholders,
98
- :permalink => permalink
99
- ).to_s
100
- end
101
-
102
- # Returns a hash of URL placeholder names (as symbols) mapping to the
103
- # desired placeholder replacements. For details see "url.rb"
104
- def url_placeholders
105
- {
106
- :path => @dir,
107
- :basename => basename,
108
- :output_ext => output_ext,
109
- }
110
- end
111
-
112
- # Extract information from the page filename.
113
- #
114
- # name - The String filename of the page file.
115
- #
116
- # NOTE: `String#gsub` removes all trailing periods (in comparison to `String#chomp`)
117
- # Returns nothing.
118
- def process(name)
119
- return unless name
120
-
121
- self.ext = File.extname(name)
122
- self.basename = name[0..-ext.length - 1].gsub(%r!\.*\z!, "")
123
- end
124
-
125
- # Add any necessary layouts to this post
126
- #
127
- # layouts - The Hash of {"name" => "layout"}.
128
- # site_payload - The site payload Hash.
129
- #
130
- # Returns String rendered page.
131
- def render(layouts, site_payload)
132
- site_payload["page"] = to_liquid
133
- site_payload["paginator"] = pager.to_liquid
134
-
135
- do_layout(site_payload, layouts)
136
- end
137
-
138
- # The path to the source file
139
- #
140
- # Returns the path to the source file
141
- def path
142
- data.fetch("path") { relative_path }
143
- end
144
-
145
- # The path to the page source file, relative to the site source
146
- def relative_path
147
- @relative_path ||= PathManager.join(@dir, @name).sub(%r!\A/!, "")
148
- end
149
-
150
- # Obtain destination path.
151
- #
152
- # dest - The String path to the destination dir.
153
- #
154
- # Returns the destination file path String.
155
- def destination(dest)
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
- end
164
-
165
- # Returns the object as a debug String.
166
- def inspect
167
- "#<#{self.class} @relative_path=#{relative_path.inspect}>"
168
- end
169
-
170
- # Returns the Boolean of whether this Page is HTML or not.
171
- def html?
172
- HTML_EXTENSIONS.include?(output_ext)
173
- end
174
-
175
- # Returns the Boolean of whether this Page is an index file or not.
176
- def index?
177
- basename == "index"
178
- end
179
-
180
- def trigger_hooks(hook_name, *args)
181
- Jekyll::Hooks.trigger :pages, hook_name, self, *args
182
- end
183
-
184
- def write?
185
- true
186
- end
187
-
188
- def excerpt_separator
189
- @excerpt_separator ||= (data["excerpt_separator"] || site.config["excerpt_separator"]).to_s
190
- end
191
-
192
- def excerpt
193
- return @excerpt if defined?(@excerpt)
194
-
195
- @excerpt = data["excerpt"] ? data["excerpt"].to_s : nil
196
- end
197
-
198
- def generate_excerpt?
199
- !excerpt_separator.empty? && instance_of?(Jekyll::Page) && html?
200
- end
201
-
202
- private
203
-
204
- def generate_excerpt
205
- return unless generate_excerpt?
206
-
207
- data["excerpt"] ||= Jekyll::PageExcerpt.new(self)
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
216
- end
217
- end
1
+ # frozen_string_literal: true
2
+
3
+ module Jekyll
4
+ class Page
5
+ include Convertible
6
+
7
+ attr_writer :dir
8
+ attr_accessor :basename, :content, :data, :ext, :name, :output, :pager, :site
9
+
10
+ alias_method :extname, :ext
11
+
12
+ # Attributes for Liquid templates
13
+ ATTRIBUTES_FOR_LIQUID = %w(
14
+ content
15
+ dir
16
+ excerpt
17
+ name
18
+ path
19
+ url
20
+ ).freeze
21
+
22
+ # A set of extensions that are considered HTML or HTML-like so we
23
+ # should not alter them, this includes .xhtml through XHTM5.
24
+
25
+ HTML_EXTENSIONS = %w(
26
+ .html
27
+ .xhtml
28
+ .htm
29
+ ).freeze
30
+
31
+ # Initialize a new Page.
32
+ #
33
+ # site - The Site object.
34
+ # base - The String path to the source.
35
+ # dir - The String path between the source and the file.
36
+ # name - The String filename of the file.
37
+ def initialize(site, base, dir, name)
38
+ @site = site
39
+ @base = base
40
+ @dir = dir
41
+ @name = name
42
+ @path = if site.in_theme_dir(base) == base # we're in a theme
43
+ site.in_theme_dir(base, dir, name)
44
+ else
45
+ site.in_source_dir(base, dir, name)
46
+ end
47
+
48
+ process(name)
49
+ read_yaml(PathManager.join(base, dir), name)
50
+ generate_excerpt if site.config["page_excerpts"]
51
+
52
+ data.default_proc = proc do |_, key|
53
+ site.frontmatter_defaults.find(relative_path, type, key)
54
+ end
55
+
56
+ Jekyll::Hooks.trigger :pages, :post_init, self
57
+ end
58
+
59
+ # The generated directory into which the page will be placed
60
+ # upon generation. This is derived from the permalink or, if
61
+ # permalink is absent, will be '/'
62
+ #
63
+ # Returns the String destination directory.
64
+ def dir
65
+ url.end_with?("/") ? url : url_dir
66
+ end
67
+
68
+ # The full path and filename of the post. Defined in the YAML of the post
69
+ # body.
70
+ #
71
+ # Returns the String permalink or nil if none has been set.
72
+ def permalink
73
+ data.nil? ? nil : data["permalink"]
74
+ end
75
+
76
+ # The template of the permalink.
77
+ #
78
+ # Returns the template String.
79
+ def template
80
+ if !html?
81
+ "/:path/:basename:output_ext"
82
+ elsif index?
83
+ "/:path/"
84
+ else
85
+ Utils.add_permalink_suffix("/:path/:basename", site.permalink_style)
86
+ end
87
+ end
88
+
89
+ # The generated relative url of this page. e.g. /about.html.
90
+ #
91
+ # Returns the String url.
92
+ def url
93
+ @url ||= URL.new(
94
+ :template => template,
95
+ :placeholders => url_placeholders,
96
+ :permalink => permalink
97
+ ).to_s
98
+ end
99
+
100
+ # Returns a hash of URL placeholder names (as symbols) mapping to the
101
+ # desired placeholder replacements. For details see "url.rb"
102
+ def url_placeholders
103
+ {
104
+ :path => @dir,
105
+ :basename => basename,
106
+ :output_ext => output_ext,
107
+ }
108
+ end
109
+
110
+ # Extract information from the page filename.
111
+ #
112
+ # name - The String filename of the page file.
113
+ #
114
+ # NOTE: `String#gsub` removes all trailing periods (in comparison to `String#chomp`)
115
+ # Returns nothing.
116
+ def process(name)
117
+ return unless name
118
+
119
+ self.ext = File.extname(name)
120
+ self.basename = name[0..-ext.length - 1].gsub(%r!\.*\z!, "")
121
+ end
122
+
123
+ # Add any necessary layouts to this post
124
+ #
125
+ # layouts - The Hash of {"name" => "layout"}.
126
+ # site_payload - The site payload Hash.
127
+ #
128
+ # Returns String rendered page.
129
+ def render(layouts, site_payload)
130
+ site_payload["page"] = to_liquid
131
+ site_payload["paginator"] = pager.to_liquid
132
+
133
+ do_layout(site_payload, layouts)
134
+ end
135
+
136
+ # The path to the source file
137
+ #
138
+ # Returns the path to the source file
139
+ def path
140
+ data.fetch("path") { relative_path }
141
+ end
142
+
143
+ # The path to the page source file, relative to the site source
144
+ def relative_path
145
+ @relative_path ||= PathManager.join(@dir, @name).delete_prefix("/")
146
+ end
147
+
148
+ # Obtain destination path.
149
+ #
150
+ # dest - The String path to the destination dir.
151
+ #
152
+ # Returns the destination file path String.
153
+ def destination(dest)
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
161
+ end
162
+
163
+ # Returns the object as a debug String.
164
+ def inspect
165
+ "#<#{self.class} @relative_path=#{relative_path.inspect}>"
166
+ end
167
+
168
+ # Returns the Boolean of whether this Page is HTML or not.
169
+ def html?
170
+ HTML_EXTENSIONS.include?(output_ext)
171
+ end
172
+
173
+ # Returns the Boolean of whether this Page is an index file or not.
174
+ def index?
175
+ basename == "index"
176
+ end
177
+
178
+ def trigger_hooks(hook_name, *args)
179
+ Jekyll::Hooks.trigger :pages, hook_name, self, *args
180
+ end
181
+
182
+ def write?
183
+ true
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
214
+ end
215
+ end
@@ -1,25 +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
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
@@ -1,14 +1,14 @@
1
- # frozen_string_literal: true
2
-
3
- module Jekyll
4
- # A Jekyll::Page subclass to handle processing files without reading it to
5
- # determine the page-data and page-content based on Front Matter delimiters.
6
- #
7
- # The class instance is basically just a bare-bones entity with just
8
- # attributes "dir", "name", "path", "url" defined on it.
9
- class PageWithoutAFile < Page
10
- def read_yaml(*)
11
- @data ||= {}
12
- end
13
- end
14
- end
1
+ # frozen_string_literal: true
2
+
3
+ module Jekyll
4
+ # A Jekyll::Page subclass to handle processing files without reading it to
5
+ # determine the page-data and page-content based on Front Matter delimiters.
6
+ #
7
+ # The class instance is basically just a bare-bones entity with just
8
+ # attributes "dir", "name", "path", "url" defined on it.
9
+ class PageWithoutAFile < Page
10
+ def read_yaml(*)
11
+ @data ||= {}
12
+ end
13
+ end
14
+ end
@@ -1,74 +1,74 @@
1
- # frozen_string_literal: true
2
-
3
- module Jekyll
4
- # A singleton class that caches frozen instances of path strings returned from its methods.
5
- #
6
- # NOTE:
7
- # This class exists because `File.join` allocates an Array and returns a new String on every
8
- # call using **the same arguments**. Caching the result means reduced memory usage.
9
- # However, the caches are never flushed so that they can be used even when a site is
10
- # regenerating. The results are frozen to deter mutation of the cached string.
11
- #
12
- # Therefore, employ this class only for situations where caching the result is necessary
13
- # for performance reasons.
14
- #
15
- class PathManager
16
- # This class cannot be initialized from outside
17
- private_class_method :new
18
-
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
72
- end
73
- end
74
- end
1
+ # frozen_string_literal: true
2
+
3
+ module Jekyll
4
+ # A singleton class that caches frozen instances of path strings returned from its methods.
5
+ #
6
+ # NOTE:
7
+ # This class exists because `File.join` allocates an Array and returns a new String on every
8
+ # call using **the same arguments**. Caching the result means reduced memory usage.
9
+ # However, the caches are never flushed so that they can be used even when a site is
10
+ # regenerating. The results are frozen to deter mutation of the cached string.
11
+ #
12
+ # Therefore, employ this class only for situations where caching the result is necessary
13
+ # for performance reasons.
14
+ #
15
+ class PathManager
16
+ # This class cannot be initialized from outside
17
+ private_class_method :new
18
+
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] ||= if questionable_path.nil?
40
+ base_directory.freeze
41
+ else
42
+ sanitize_and_join(
43
+ base_directory, questionable_path
44
+ ).freeze
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
72
+ end
73
+ end
74
+ end