jekyll 4.2.0 → 4.2.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (124) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +350 -347
  3. data/LICENSE +21 -21
  4. data/README.markdown +86 -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 +190 -190
  12. data/lib/jekyll/cleaner.rb +111 -111
  13. data/lib/jekyll/collection.rb +309 -309
  14. data/lib/jekyll/command.rb +105 -105
  15. data/lib/jekyll/commands/build.rb +93 -93
  16. data/lib/jekyll/commands/clean.rb +45 -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 +169 -169
  20. data/lib/jekyll/commands/new_theme.rb +40 -40
  21. data/lib/jekyll/commands/serve/live_reload_reactor.rb +122 -122
  22. data/lib/jekyll/commands/serve/livereload_assets/livereload.js +1183 -1183
  23. data/lib/jekyll/commands/serve/servlet.rb +202 -202
  24. data/lib/jekyll/commands/serve/websockets.rb +81 -81
  25. data/lib/jekyll/commands/serve.rb +362 -365
  26. data/lib/jekyll/configuration.rb +313 -313
  27. data/lib/jekyll/converter.rb +54 -54
  28. data/lib/jekyll/converters/identity.rb +41 -41
  29. data/lib/jekyll/converters/markdown/kramdown_parser.rb +199 -199
  30. data/lib/jekyll/converters/markdown.rb +113 -113
  31. data/lib/jekyll/converters/smartypants.rb +70 -70
  32. data/lib/jekyll/convertible.rb +257 -260
  33. data/lib/jekyll/deprecator.rb +50 -50
  34. data/lib/jekyll/document.rb +544 -544
  35. data/lib/jekyll/drops/collection_drop.rb +20 -20
  36. data/lib/jekyll/drops/document_drop.rb +70 -70
  37. data/lib/jekyll/drops/drop.rb +293 -293
  38. data/lib/jekyll/drops/excerpt_drop.rb +19 -19
  39. data/lib/jekyll/drops/jekyll_drop.rb +32 -32
  40. data/lib/jekyll/drops/site_drop.rb +66 -66
  41. data/lib/jekyll/drops/static_file_drop.rb +14 -14
  42. data/lib/jekyll/drops/unified_payload_drop.rb +26 -26
  43. data/lib/jekyll/drops/url_drop.rb +140 -140
  44. data/lib/jekyll/entry_filter.rb +121 -121
  45. data/lib/jekyll/errors.rb +20 -20
  46. data/lib/jekyll/excerpt.rb +201 -201
  47. data/lib/jekyll/external.rb +79 -79
  48. data/lib/jekyll/filters/date_filters.rb +110 -110
  49. data/lib/jekyll/filters/grouping_filters.rb +64 -64
  50. data/lib/jekyll/filters/url_filters.rb +98 -98
  51. data/lib/jekyll/filters.rb +535 -535
  52. data/lib/jekyll/frontmatter_defaults.rb +240 -240
  53. data/lib/jekyll/generator.rb +5 -5
  54. data/lib/jekyll/hooks.rb +107 -107
  55. data/lib/jekyll/inclusion.rb +32 -32
  56. data/lib/jekyll/layout.rb +67 -67
  57. data/lib/jekyll/liquid_extensions.rb +22 -22
  58. data/lib/jekyll/liquid_renderer/file.rb +77 -77
  59. data/lib/jekyll/liquid_renderer/table.rb +55 -55
  60. data/lib/jekyll/liquid_renderer.rb +80 -80
  61. data/lib/jekyll/log_adapter.rb +151 -151
  62. data/lib/jekyll/mime.types +866 -866
  63. data/lib/jekyll/page.rb +217 -217
  64. data/lib/jekyll/page_excerpt.rb +25 -25
  65. data/lib/jekyll/page_without_a_file.rb +14 -14
  66. data/lib/jekyll/path_manager.rb +74 -74
  67. data/lib/jekyll/plugin.rb +92 -92
  68. data/lib/jekyll/plugin_manager.rb +115 -115
  69. data/lib/jekyll/profiler.rb +58 -58
  70. data/lib/jekyll/publisher.rb +23 -23
  71. data/lib/jekyll/reader.rb +192 -192
  72. data/lib/jekyll/readers/collection_reader.rb +23 -23
  73. data/lib/jekyll/readers/data_reader.rb +79 -79
  74. data/lib/jekyll/readers/layout_reader.rb +62 -62
  75. data/lib/jekyll/readers/page_reader.rb +25 -25
  76. data/lib/jekyll/readers/post_reader.rb +85 -85
  77. data/lib/jekyll/readers/static_file_reader.rb +25 -25
  78. data/lib/jekyll/readers/theme_assets_reader.rb +52 -52
  79. data/lib/jekyll/regenerator.rb +195 -195
  80. data/lib/jekyll/related_posts.rb +52 -52
  81. data/lib/jekyll/renderer.rb +265 -265
  82. data/lib/jekyll/site.rb +551 -551
  83. data/lib/jekyll/static_file.rb +208 -208
  84. data/lib/jekyll/stevenson.rb +60 -60
  85. data/lib/jekyll/tags/highlight.rb +110 -110
  86. data/lib/jekyll/tags/include.rb +275 -270
  87. data/lib/jekyll/tags/link.rb +42 -42
  88. data/lib/jekyll/tags/post_url.rb +106 -106
  89. data/lib/jekyll/theme.rb +86 -86
  90. data/lib/jekyll/theme_builder.rb +121 -121
  91. data/lib/jekyll/url.rb +167 -167
  92. data/lib/jekyll/utils/ansi.rb +57 -57
  93. data/lib/jekyll/utils/exec.rb +26 -26
  94. data/lib/jekyll/utils/internet.rb +37 -37
  95. data/lib/jekyll/utils/platforms.rb +67 -67
  96. data/lib/jekyll/utils/thread_event.rb +31 -31
  97. data/lib/jekyll/utils/win_tz.rb +75 -75
  98. data/lib/jekyll/utils.rb +367 -367
  99. data/lib/jekyll/version.rb +5 -5
  100. data/lib/jekyll.rb +195 -195
  101. data/lib/site_template/.gitignore +5 -5
  102. data/lib/site_template/404.html +25 -25
  103. data/lib/site_template/_config.yml +55 -55
  104. data/lib/site_template/_posts/0000-00-00-welcome-to-jekyll.markdown.erb +29 -29
  105. data/lib/site_template/about.markdown +18 -18
  106. data/lib/site_template/index.markdown +6 -6
  107. data/lib/theme_template/CODE_OF_CONDUCT.md.erb +74 -74
  108. data/lib/theme_template/Gemfile +4 -4
  109. data/lib/theme_template/LICENSE.txt.erb +21 -21
  110. data/lib/theme_template/README.md.erb +52 -52
  111. data/lib/theme_template/_layouts/default.html +1 -1
  112. data/lib/theme_template/_layouts/page.html +5 -5
  113. data/lib/theme_template/_layouts/post.html +5 -5
  114. data/lib/theme_template/example/_config.yml.erb +1 -1
  115. data/lib/theme_template/example/_post.md +12 -12
  116. data/lib/theme_template/example/index.html +14 -14
  117. data/lib/theme_template/example/style.scss +7 -7
  118. data/lib/theme_template/gitignore.erb +6 -6
  119. data/lib/theme_template/theme.gemspec.erb +16 -16
  120. data/rubocop/jekyll/assert_equal_literal_actual.rb +149 -149
  121. data/rubocop/jekyll/no_p_allowed.rb +23 -23
  122. data/rubocop/jekyll/no_puts_allowed.rb +23 -23
  123. data/rubocop/jekyll.rb +5 -5
  124. metadata +3 -3
data/lib/jekyll/page.rb CHANGED
@@ -1,217 +1,217 @@
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 :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,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] ||= 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