jekyll 4.2.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.
Files changed (124) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +350 -350
  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 +172 -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 -362
  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 -257
  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 -275
  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