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.
- checksums.yaml +4 -4
- data/.rubocop.yml +350 -350
- data/LICENSE +21 -21
- data/README.markdown +86 -86
- data/exe/jekyll +57 -57
- data/lib/blank_template/_config.yml +3 -3
- data/lib/blank_template/_layouts/default.html +12 -12
- data/lib/blank_template/_sass/main.scss +9 -9
- data/lib/blank_template/assets/css/main.scss +4 -4
- data/lib/blank_template/index.md +8 -8
- data/lib/jekyll/cache.rb +190 -190
- data/lib/jekyll/cleaner.rb +111 -111
- data/lib/jekyll/collection.rb +309 -309
- data/lib/jekyll/command.rb +105 -105
- data/lib/jekyll/commands/build.rb +93 -93
- data/lib/jekyll/commands/clean.rb +45 -45
- data/lib/jekyll/commands/doctor.rb +177 -177
- data/lib/jekyll/commands/help.rb +34 -34
- data/lib/jekyll/commands/new.rb +172 -169
- data/lib/jekyll/commands/new_theme.rb +40 -40
- data/lib/jekyll/commands/serve/live_reload_reactor.rb +122 -122
- data/lib/jekyll/commands/serve/livereload_assets/livereload.js +1183 -1183
- data/lib/jekyll/commands/serve/servlet.rb +202 -202
- data/lib/jekyll/commands/serve/websockets.rb +81 -81
- data/lib/jekyll/commands/serve.rb +362 -362
- data/lib/jekyll/configuration.rb +313 -313
- data/lib/jekyll/converter.rb +54 -54
- data/lib/jekyll/converters/identity.rb +41 -41
- data/lib/jekyll/converters/markdown/kramdown_parser.rb +199 -199
- data/lib/jekyll/converters/markdown.rb +113 -113
- data/lib/jekyll/converters/smartypants.rb +70 -70
- data/lib/jekyll/convertible.rb +257 -257
- data/lib/jekyll/deprecator.rb +50 -50
- data/lib/jekyll/document.rb +544 -544
- data/lib/jekyll/drops/collection_drop.rb +20 -20
- data/lib/jekyll/drops/document_drop.rb +70 -70
- data/lib/jekyll/drops/drop.rb +293 -293
- data/lib/jekyll/drops/excerpt_drop.rb +19 -19
- data/lib/jekyll/drops/jekyll_drop.rb +32 -32
- data/lib/jekyll/drops/site_drop.rb +66 -66
- data/lib/jekyll/drops/static_file_drop.rb +14 -14
- data/lib/jekyll/drops/unified_payload_drop.rb +26 -26
- data/lib/jekyll/drops/url_drop.rb +140 -140
- data/lib/jekyll/entry_filter.rb +121 -121
- data/lib/jekyll/errors.rb +20 -20
- data/lib/jekyll/excerpt.rb +201 -201
- data/lib/jekyll/external.rb +79 -79
- data/lib/jekyll/filters/date_filters.rb +110 -110
- data/lib/jekyll/filters/grouping_filters.rb +64 -64
- data/lib/jekyll/filters/url_filters.rb +98 -98
- data/lib/jekyll/filters.rb +535 -535
- data/lib/jekyll/frontmatter_defaults.rb +240 -240
- data/lib/jekyll/generator.rb +5 -5
- data/lib/jekyll/hooks.rb +107 -107
- data/lib/jekyll/inclusion.rb +32 -32
- data/lib/jekyll/layout.rb +67 -67
- data/lib/jekyll/liquid_extensions.rb +22 -22
- data/lib/jekyll/liquid_renderer/file.rb +77 -77
- data/lib/jekyll/liquid_renderer/table.rb +55 -55
- data/lib/jekyll/liquid_renderer.rb +80 -80
- data/lib/jekyll/log_adapter.rb +151 -151
- data/lib/jekyll/mime.types +866 -866
- data/lib/jekyll/page.rb +217 -217
- data/lib/jekyll/page_excerpt.rb +25 -25
- data/lib/jekyll/page_without_a_file.rb +14 -14
- data/lib/jekyll/path_manager.rb +74 -74
- data/lib/jekyll/plugin.rb +92 -92
- data/lib/jekyll/plugin_manager.rb +115 -115
- data/lib/jekyll/profiler.rb +58 -58
- data/lib/jekyll/publisher.rb +23 -23
- data/lib/jekyll/reader.rb +192 -192
- data/lib/jekyll/readers/collection_reader.rb +23 -23
- data/lib/jekyll/readers/data_reader.rb +79 -79
- data/lib/jekyll/readers/layout_reader.rb +62 -62
- data/lib/jekyll/readers/page_reader.rb +25 -25
- data/lib/jekyll/readers/post_reader.rb +85 -85
- data/lib/jekyll/readers/static_file_reader.rb +25 -25
- data/lib/jekyll/readers/theme_assets_reader.rb +52 -52
- data/lib/jekyll/regenerator.rb +195 -195
- data/lib/jekyll/related_posts.rb +52 -52
- data/lib/jekyll/renderer.rb +265 -265
- data/lib/jekyll/site.rb +551 -551
- data/lib/jekyll/static_file.rb +208 -208
- data/lib/jekyll/stevenson.rb +60 -60
- data/lib/jekyll/tags/highlight.rb +110 -110
- data/lib/jekyll/tags/include.rb +275 -275
- data/lib/jekyll/tags/link.rb +42 -42
- data/lib/jekyll/tags/post_url.rb +106 -106
- data/lib/jekyll/theme.rb +86 -86
- data/lib/jekyll/theme_builder.rb +121 -121
- data/lib/jekyll/url.rb +167 -167
- data/lib/jekyll/utils/ansi.rb +57 -57
- data/lib/jekyll/utils/exec.rb +26 -26
- data/lib/jekyll/utils/internet.rb +37 -37
- data/lib/jekyll/utils/platforms.rb +67 -67
- data/lib/jekyll/utils/thread_event.rb +31 -31
- data/lib/jekyll/utils/win_tz.rb +75 -75
- data/lib/jekyll/utils.rb +367 -367
- data/lib/jekyll/version.rb +5 -5
- data/lib/jekyll.rb +195 -195
- data/lib/site_template/.gitignore +5 -5
- data/lib/site_template/404.html +25 -25
- data/lib/site_template/_config.yml +55 -55
- data/lib/site_template/_posts/0000-00-00-welcome-to-jekyll.markdown.erb +29 -29
- data/lib/site_template/about.markdown +18 -18
- data/lib/site_template/index.markdown +6 -6
- data/lib/theme_template/CODE_OF_CONDUCT.md.erb +74 -74
- data/lib/theme_template/Gemfile +4 -4
- data/lib/theme_template/LICENSE.txt.erb +21 -21
- data/lib/theme_template/README.md.erb +52 -52
- data/lib/theme_template/_layouts/default.html +1 -1
- data/lib/theme_template/_layouts/page.html +5 -5
- data/lib/theme_template/_layouts/post.html +5 -5
- data/lib/theme_template/example/_config.yml.erb +1 -1
- data/lib/theme_template/example/_post.md +12 -12
- data/lib/theme_template/example/index.html +14 -14
- data/lib/theme_template/example/style.scss +7 -7
- data/lib/theme_template/gitignore.erb +6 -6
- data/lib/theme_template/theme.gemspec.erb +16 -16
- data/rubocop/jekyll/assert_equal_literal_actual.rb +149 -149
- data/rubocop/jekyll/no_p_allowed.rb +23 -23
- data/rubocop/jekyll/no_puts_allowed.rb +23 -23
- data/rubocop/jekyll.rb +5 -5
- metadata +3 -3
data/lib/jekyll/utils.rb
CHANGED
|
@@ -1,367 +1,367 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Jekyll
|
|
4
|
-
module Utils
|
|
5
|
-
extend self
|
|
6
|
-
autoload :Ansi, "jekyll/utils/ansi"
|
|
7
|
-
autoload :Exec, "jekyll/utils/exec"
|
|
8
|
-
autoload :Internet, "jekyll/utils/internet"
|
|
9
|
-
autoload :Platforms, "jekyll/utils/platforms"
|
|
10
|
-
autoload :ThreadEvent, "jekyll/utils/thread_event"
|
|
11
|
-
autoload :WinTZ, "jekyll/utils/win_tz"
|
|
12
|
-
|
|
13
|
-
# Constants for use in #slugify
|
|
14
|
-
SLUGIFY_MODES = %w(raw default pretty ascii latin).freeze
|
|
15
|
-
SLUGIFY_RAW_REGEXP = Regexp.new('\\s+').freeze
|
|
16
|
-
SLUGIFY_DEFAULT_REGEXP = Regexp.new("[^\\p{M}\\p{L}\\p{Nd}]+").freeze
|
|
17
|
-
SLUGIFY_PRETTY_REGEXP = Regexp.new("[^\\p{M}\\p{L}\\p{Nd}._~!$&'()+,;=@]+").freeze
|
|
18
|
-
SLUGIFY_ASCII_REGEXP = Regexp.new("[^[A-Za-z0-9]]+").freeze
|
|
19
|
-
|
|
20
|
-
# Takes a slug and turns it into a simple title.
|
|
21
|
-
def titleize_slug(slug)
|
|
22
|
-
slug.split("-").map!(&:capitalize).join(" ")
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
# Non-destructive version of deep_merge_hashes! See that method.
|
|
26
|
-
#
|
|
27
|
-
# Returns the merged hashes.
|
|
28
|
-
def deep_merge_hashes(master_hash, other_hash)
|
|
29
|
-
deep_merge_hashes!(master_hash.dup, other_hash)
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
# Merges a master hash with another hash, recursively.
|
|
33
|
-
#
|
|
34
|
-
# master_hash - the "parent" hash whose values will be overridden
|
|
35
|
-
# other_hash - the other hash whose values will be persisted after the merge
|
|
36
|
-
#
|
|
37
|
-
# This code was lovingly stolen from some random gem:
|
|
38
|
-
# http://gemjack.com/gems/tartan-0.1.1/classes/Hash.html
|
|
39
|
-
#
|
|
40
|
-
# Thanks to whoever made it.
|
|
41
|
-
def deep_merge_hashes!(target, overwrite)
|
|
42
|
-
merge_values(target, overwrite)
|
|
43
|
-
merge_default_proc(target, overwrite)
|
|
44
|
-
duplicate_frozen_values(target)
|
|
45
|
-
|
|
46
|
-
target
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
def mergable?(value)
|
|
50
|
-
value.is_a?(Hash) || value.is_a?(Drops::Drop)
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
def duplicable?(obj)
|
|
54
|
-
case obj
|
|
55
|
-
when nil, false, true, Symbol, Numeric
|
|
56
|
-
false
|
|
57
|
-
else
|
|
58
|
-
true
|
|
59
|
-
end
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
# Read array from the supplied hash favouring the singular key
|
|
63
|
-
# and then the plural key, and handling any nil entries.
|
|
64
|
-
#
|
|
65
|
-
# hash - the hash to read from
|
|
66
|
-
# singular_key - the singular key
|
|
67
|
-
# plural_key - the plural key
|
|
68
|
-
#
|
|
69
|
-
# Returns an array
|
|
70
|
-
def pluralized_array_from_hash(hash, singular_key, plural_key)
|
|
71
|
-
array = []
|
|
72
|
-
value = value_from_singular_key(hash, singular_key)
|
|
73
|
-
value ||= value_from_plural_key(hash, plural_key)
|
|
74
|
-
|
|
75
|
-
array << value
|
|
76
|
-
array.flatten!
|
|
77
|
-
array.compact!
|
|
78
|
-
array
|
|
79
|
-
end
|
|
80
|
-
|
|
81
|
-
def value_from_singular_key(hash, key)
|
|
82
|
-
hash[key] if hash.key?(key) || (hash.default_proc && hash[key])
|
|
83
|
-
end
|
|
84
|
-
|
|
85
|
-
def value_from_plural_key(hash, key)
|
|
86
|
-
if hash.key?(key) || (hash.default_proc && hash[key])
|
|
87
|
-
val = hash[key]
|
|
88
|
-
case val
|
|
89
|
-
when String
|
|
90
|
-
val.split
|
|
91
|
-
when Array
|
|
92
|
-
val.compact
|
|
93
|
-
end
|
|
94
|
-
end
|
|
95
|
-
end
|
|
96
|
-
|
|
97
|
-
def transform_keys(hash)
|
|
98
|
-
result = {}
|
|
99
|
-
hash.each_key do |key|
|
|
100
|
-
result[yield(key)] = hash[key]
|
|
101
|
-
end
|
|
102
|
-
result
|
|
103
|
-
end
|
|
104
|
-
|
|
105
|
-
# Apply #to_sym to all keys in the hash
|
|
106
|
-
#
|
|
107
|
-
# hash - the hash to which to apply this transformation
|
|
108
|
-
#
|
|
109
|
-
# Returns a new hash with symbolized keys
|
|
110
|
-
def symbolize_hash_keys(hash)
|
|
111
|
-
transform_keys(hash) { |key| key.to_sym rescue key }
|
|
112
|
-
end
|
|
113
|
-
|
|
114
|
-
# Apply #to_s to all keys in the Hash
|
|
115
|
-
#
|
|
116
|
-
# hash - the hash to which to apply this transformation
|
|
117
|
-
#
|
|
118
|
-
# Returns a new hash with stringified keys
|
|
119
|
-
def stringify_hash_keys(hash)
|
|
120
|
-
transform_keys(hash) { |key| key.to_s rescue key }
|
|
121
|
-
end
|
|
122
|
-
|
|
123
|
-
# Parse a date/time and throw an error if invalid
|
|
124
|
-
#
|
|
125
|
-
# input - the date/time to parse
|
|
126
|
-
# msg - (optional) the error message to show the user
|
|
127
|
-
#
|
|
128
|
-
# Returns the parsed date if successful, throws a FatalException
|
|
129
|
-
# if not
|
|
130
|
-
def parse_date(input, msg = "Input could not be parsed.")
|
|
131
|
-
Time.parse(input).localtime
|
|
132
|
-
rescue ArgumentError
|
|
133
|
-
raise Errors::InvalidDateError, "Invalid date '#{input}': #{msg}"
|
|
134
|
-
end
|
|
135
|
-
|
|
136
|
-
# Determines whether a given file has
|
|
137
|
-
#
|
|
138
|
-
# Returns true if the YAML front matter is present.
|
|
139
|
-
# rubocop: disable Naming/PredicateName
|
|
140
|
-
def has_yaml_header?(file)
|
|
141
|
-
File.open(file, "rb", &:readline).match? %r!\A---\s*\r?\n!
|
|
142
|
-
rescue EOFError
|
|
143
|
-
false
|
|
144
|
-
end
|
|
145
|
-
|
|
146
|
-
# Determine whether the given content string contains Liquid Tags or Vaiables
|
|
147
|
-
#
|
|
148
|
-
# Returns true is the string contains sequences of `{%` or `{{`
|
|
149
|
-
def has_liquid_construct?(content)
|
|
150
|
-
return false if content.nil? || content.empty?
|
|
151
|
-
|
|
152
|
-
content.include?("{%") || content.include?("{{")
|
|
153
|
-
end
|
|
154
|
-
# rubocop: enable Naming/PredicateName
|
|
155
|
-
|
|
156
|
-
# Slugify a filename or title.
|
|
157
|
-
#
|
|
158
|
-
# string - the filename or title to slugify
|
|
159
|
-
# mode - how string is slugified
|
|
160
|
-
# cased - whether to replace all uppercase letters with their
|
|
161
|
-
# lowercase counterparts
|
|
162
|
-
#
|
|
163
|
-
# When mode is "none", return the given string.
|
|
164
|
-
#
|
|
165
|
-
# When mode is "raw", return the given string,
|
|
166
|
-
# with every sequence of spaces characters replaced with a hyphen.
|
|
167
|
-
#
|
|
168
|
-
# When mode is "default" or nil, non-alphabetic characters are
|
|
169
|
-
# replaced with a hyphen too.
|
|
170
|
-
#
|
|
171
|
-
# When mode is "pretty", some non-alphabetic characters (._~!$&'()+,;=@)
|
|
172
|
-
# are not replaced with hyphen.
|
|
173
|
-
#
|
|
174
|
-
# When mode is "ascii", some everything else except ASCII characters
|
|
175
|
-
# a-z (lowercase), A-Z (uppercase) and 0-9 (numbers) are not replaced with hyphen.
|
|
176
|
-
#
|
|
177
|
-
# When mode is "latin", the input string is first preprocessed so that
|
|
178
|
-
# any letters with accents are replaced with the plain letter. Afterwards,
|
|
179
|
-
# it follows the "default" mode of operation.
|
|
180
|
-
#
|
|
181
|
-
# If cased is true, all uppercase letters in the result string are
|
|
182
|
-
# replaced with their lowercase counterparts.
|
|
183
|
-
#
|
|
184
|
-
# Examples:
|
|
185
|
-
# slugify("The _config.yml file")
|
|
186
|
-
# # => "the-config-yml-file"
|
|
187
|
-
#
|
|
188
|
-
# slugify("The _config.yml file", "pretty")
|
|
189
|
-
# # => "the-_config.yml-file"
|
|
190
|
-
#
|
|
191
|
-
# slugify("The _config.yml file", "pretty", true)
|
|
192
|
-
# # => "The-_config.yml file"
|
|
193
|
-
#
|
|
194
|
-
# slugify("The _config.yml file", "ascii")
|
|
195
|
-
# # => "the-config-yml-file"
|
|
196
|
-
#
|
|
197
|
-
# slugify("The _config.yml file", "latin")
|
|
198
|
-
# # => "the-config-yml-file"
|
|
199
|
-
#
|
|
200
|
-
# Returns the slugified string.
|
|
201
|
-
def slugify(string, mode: nil, cased: false)
|
|
202
|
-
mode ||= "default"
|
|
203
|
-
return nil if string.nil?
|
|
204
|
-
|
|
205
|
-
unless SLUGIFY_MODES.include?(mode)
|
|
206
|
-
return cased ? string : string.downcase
|
|
207
|
-
end
|
|
208
|
-
|
|
209
|
-
# Drop accent marks from latin characters. Everything else turns to ?
|
|
210
|
-
if mode == "latin"
|
|
211
|
-
I18n.config.available_locales = :en if I18n.config.available_locales.empty?
|
|
212
|
-
string = I18n.transliterate(string)
|
|
213
|
-
end
|
|
214
|
-
|
|
215
|
-
slug = replace_character_sequence_with_hyphen(string, :mode => mode)
|
|
216
|
-
|
|
217
|
-
# Remove leading/trailing hyphen
|
|
218
|
-
slug.gsub!(%r!^-|-$!i, "")
|
|
219
|
-
|
|
220
|
-
slug.downcase! unless cased
|
|
221
|
-
Jekyll.logger.warn("Warning:", "Empty `slug` generated for '#{string}'.") if slug.empty?
|
|
222
|
-
slug
|
|
223
|
-
end
|
|
224
|
-
|
|
225
|
-
# Add an appropriate suffix to template so that it matches the specified
|
|
226
|
-
# permalink style.
|
|
227
|
-
#
|
|
228
|
-
# template - permalink template without trailing slash or file extension
|
|
229
|
-
# permalink_style - permalink style, either built-in or custom
|
|
230
|
-
#
|
|
231
|
-
# The returned permalink template will use the same ending style as
|
|
232
|
-
# specified in permalink_style. For example, if permalink_style contains a
|
|
233
|
-
# trailing slash (or is :pretty, which indirectly has a trailing slash),
|
|
234
|
-
# then so will the returned template. If permalink_style has a trailing
|
|
235
|
-
# ":output_ext" (or is :none, :date, or :ordinal) then so will the returned
|
|
236
|
-
# template. Otherwise, template will be returned without modification.
|
|
237
|
-
#
|
|
238
|
-
# Examples:
|
|
239
|
-
# add_permalink_suffix("/:basename", :pretty)
|
|
240
|
-
# # => "/:basename/"
|
|
241
|
-
#
|
|
242
|
-
# add_permalink_suffix("/:basename", :date)
|
|
243
|
-
# # => "/:basename:output_ext"
|
|
244
|
-
#
|
|
245
|
-
# add_permalink_suffix("/:basename", "/:year/:month/:title/")
|
|
246
|
-
# # => "/:basename/"
|
|
247
|
-
#
|
|
248
|
-
# add_permalink_suffix("/:basename", "/:year/:month/:title")
|
|
249
|
-
# # => "/:basename"
|
|
250
|
-
#
|
|
251
|
-
# Returns the updated permalink template
|
|
252
|
-
def add_permalink_suffix(template, permalink_style)
|
|
253
|
-
template = template.dup
|
|
254
|
-
|
|
255
|
-
case permalink_style
|
|
256
|
-
when :pretty
|
|
257
|
-
template << "/"
|
|
258
|
-
when :date, :ordinal, :none
|
|
259
|
-
template << ":output_ext"
|
|
260
|
-
else
|
|
261
|
-
template << "/" if permalink_style.to_s.end_with?("/")
|
|
262
|
-
template << ":output_ext" if permalink_style.to_s.end_with?(":output_ext")
|
|
263
|
-
end
|
|
264
|
-
|
|
265
|
-
template
|
|
266
|
-
end
|
|
267
|
-
|
|
268
|
-
# Work the same way as Dir.glob but seperating the input into two parts
|
|
269
|
-
# ('dir' + '/' + 'pattern') to make sure the first part('dir') does not act
|
|
270
|
-
# as a pattern.
|
|
271
|
-
#
|
|
272
|
-
# For example, Dir.glob("path[/*") always returns an empty array,
|
|
273
|
-
# because the method fails to find the closing pattern to '[' which is ']'
|
|
274
|
-
#
|
|
275
|
-
# Examples:
|
|
276
|
-
# safe_glob("path[", "*")
|
|
277
|
-
# # => ["path[/file1", "path[/file2"]
|
|
278
|
-
#
|
|
279
|
-
# safe_glob("path", "*", File::FNM_DOTMATCH)
|
|
280
|
-
# # => ["path/.", "path/..", "path/file1"]
|
|
281
|
-
#
|
|
282
|
-
# safe_glob("path", ["**", "*"])
|
|
283
|
-
# # => ["path[/file1", "path[/folder/file2"]
|
|
284
|
-
#
|
|
285
|
-
# dir - the dir where glob will be executed under
|
|
286
|
-
# (the dir will be included to each result)
|
|
287
|
-
# patterns - the patterns (or the pattern) which will be applied under the dir
|
|
288
|
-
# flags - the flags which will be applied to the pattern
|
|
289
|
-
#
|
|
290
|
-
# Returns matched pathes
|
|
291
|
-
def safe_glob(dir, patterns, flags = 0)
|
|
292
|
-
return [] unless Dir.exist?(dir)
|
|
293
|
-
|
|
294
|
-
pattern = File.join(Array(patterns))
|
|
295
|
-
return [dir] if pattern.empty?
|
|
296
|
-
|
|
297
|
-
Dir.chdir(dir) do
|
|
298
|
-
Dir.glob(pattern, flags).map { |f| File.join(dir, f) }
|
|
299
|
-
end
|
|
300
|
-
end
|
|
301
|
-
|
|
302
|
-
# Returns merged option hash for File.read of self.site (if exists)
|
|
303
|
-
# and a given param
|
|
304
|
-
def merged_file_read_opts(site, opts)
|
|
305
|
-
merged = (site ? site.file_read_opts : {}).merge(opts)
|
|
306
|
-
if merged[:encoding] && !merged[:encoding].start_with?("bom|")
|
|
307
|
-
merged[:encoding] = "bom|#{merged[:encoding]}"
|
|
308
|
-
end
|
|
309
|
-
if merged["encoding"] && !merged["encoding"].start_with?("bom|")
|
|
310
|
-
merged["encoding"] = "bom|#{merged["encoding"]}"
|
|
311
|
-
end
|
|
312
|
-
merged
|
|
313
|
-
end
|
|
314
|
-
|
|
315
|
-
private
|
|
316
|
-
|
|
317
|
-
def merge_values(target, overwrite)
|
|
318
|
-
target.merge!(overwrite) do |_key, old_val, new_val|
|
|
319
|
-
if new_val.nil?
|
|
320
|
-
old_val
|
|
321
|
-
elsif mergable?(old_val) && mergable?(new_val)
|
|
322
|
-
deep_merge_hashes(old_val, new_val)
|
|
323
|
-
else
|
|
324
|
-
new_val
|
|
325
|
-
end
|
|
326
|
-
end
|
|
327
|
-
end
|
|
328
|
-
|
|
329
|
-
def merge_default_proc(target, overwrite)
|
|
330
|
-
if target.is_a?(Hash) && overwrite.is_a?(Hash) && target.default_proc.nil?
|
|
331
|
-
target.default_proc = overwrite.default_proc
|
|
332
|
-
end
|
|
333
|
-
end
|
|
334
|
-
|
|
335
|
-
def duplicate_frozen_values(target)
|
|
336
|
-
target.each do |key, val|
|
|
337
|
-
target[key] = val.dup if val.frozen? && duplicable?(val)
|
|
338
|
-
end
|
|
339
|
-
end
|
|
340
|
-
|
|
341
|
-
# Replace each character sequence with a hyphen.
|
|
342
|
-
#
|
|
343
|
-
# See Utils#slugify for a description of the character sequence specified
|
|
344
|
-
# by each mode.
|
|
345
|
-
def replace_character_sequence_with_hyphen(string, mode: "default")
|
|
346
|
-
replaceable_char =
|
|
347
|
-
case mode
|
|
348
|
-
when "raw"
|
|
349
|
-
SLUGIFY_RAW_REGEXP
|
|
350
|
-
when "pretty"
|
|
351
|
-
# "._~!$&'()+,;=@" is human readable (not URI-escaped) in URL
|
|
352
|
-
# and is allowed in both extN and NTFS.
|
|
353
|
-
SLUGIFY_PRETTY_REGEXP
|
|
354
|
-
when "ascii"
|
|
355
|
-
# For web servers not being able to handle Unicode, the safe
|
|
356
|
-
# method is to ditch anything else but latin letters and numeric
|
|
357
|
-
# digits.
|
|
358
|
-
SLUGIFY_ASCII_REGEXP
|
|
359
|
-
else
|
|
360
|
-
SLUGIFY_DEFAULT_REGEXP
|
|
361
|
-
end
|
|
362
|
-
|
|
363
|
-
# Strip according to the mode
|
|
364
|
-
string.gsub(replaceable_char, "-")
|
|
365
|
-
end
|
|
366
|
-
end
|
|
367
|
-
end
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Jekyll
|
|
4
|
+
module Utils
|
|
5
|
+
extend self
|
|
6
|
+
autoload :Ansi, "jekyll/utils/ansi"
|
|
7
|
+
autoload :Exec, "jekyll/utils/exec"
|
|
8
|
+
autoload :Internet, "jekyll/utils/internet"
|
|
9
|
+
autoload :Platforms, "jekyll/utils/platforms"
|
|
10
|
+
autoload :ThreadEvent, "jekyll/utils/thread_event"
|
|
11
|
+
autoload :WinTZ, "jekyll/utils/win_tz"
|
|
12
|
+
|
|
13
|
+
# Constants for use in #slugify
|
|
14
|
+
SLUGIFY_MODES = %w(raw default pretty ascii latin).freeze
|
|
15
|
+
SLUGIFY_RAW_REGEXP = Regexp.new('\\s+').freeze
|
|
16
|
+
SLUGIFY_DEFAULT_REGEXP = Regexp.new("[^\\p{M}\\p{L}\\p{Nd}]+").freeze
|
|
17
|
+
SLUGIFY_PRETTY_REGEXP = Regexp.new("[^\\p{M}\\p{L}\\p{Nd}._~!$&'()+,;=@]+").freeze
|
|
18
|
+
SLUGIFY_ASCII_REGEXP = Regexp.new("[^[A-Za-z0-9]]+").freeze
|
|
19
|
+
|
|
20
|
+
# Takes a slug and turns it into a simple title.
|
|
21
|
+
def titleize_slug(slug)
|
|
22
|
+
slug.split("-").map!(&:capitalize).join(" ")
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Non-destructive version of deep_merge_hashes! See that method.
|
|
26
|
+
#
|
|
27
|
+
# Returns the merged hashes.
|
|
28
|
+
def deep_merge_hashes(master_hash, other_hash)
|
|
29
|
+
deep_merge_hashes!(master_hash.dup, other_hash)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Merges a master hash with another hash, recursively.
|
|
33
|
+
#
|
|
34
|
+
# master_hash - the "parent" hash whose values will be overridden
|
|
35
|
+
# other_hash - the other hash whose values will be persisted after the merge
|
|
36
|
+
#
|
|
37
|
+
# This code was lovingly stolen from some random gem:
|
|
38
|
+
# http://gemjack.com/gems/tartan-0.1.1/classes/Hash.html
|
|
39
|
+
#
|
|
40
|
+
# Thanks to whoever made it.
|
|
41
|
+
def deep_merge_hashes!(target, overwrite)
|
|
42
|
+
merge_values(target, overwrite)
|
|
43
|
+
merge_default_proc(target, overwrite)
|
|
44
|
+
duplicate_frozen_values(target)
|
|
45
|
+
|
|
46
|
+
target
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def mergable?(value)
|
|
50
|
+
value.is_a?(Hash) || value.is_a?(Drops::Drop)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def duplicable?(obj)
|
|
54
|
+
case obj
|
|
55
|
+
when nil, false, true, Symbol, Numeric
|
|
56
|
+
false
|
|
57
|
+
else
|
|
58
|
+
true
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Read array from the supplied hash favouring the singular key
|
|
63
|
+
# and then the plural key, and handling any nil entries.
|
|
64
|
+
#
|
|
65
|
+
# hash - the hash to read from
|
|
66
|
+
# singular_key - the singular key
|
|
67
|
+
# plural_key - the plural key
|
|
68
|
+
#
|
|
69
|
+
# Returns an array
|
|
70
|
+
def pluralized_array_from_hash(hash, singular_key, plural_key)
|
|
71
|
+
array = []
|
|
72
|
+
value = value_from_singular_key(hash, singular_key)
|
|
73
|
+
value ||= value_from_plural_key(hash, plural_key)
|
|
74
|
+
|
|
75
|
+
array << value
|
|
76
|
+
array.flatten!
|
|
77
|
+
array.compact!
|
|
78
|
+
array
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def value_from_singular_key(hash, key)
|
|
82
|
+
hash[key] if hash.key?(key) || (hash.default_proc && hash[key])
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def value_from_plural_key(hash, key)
|
|
86
|
+
if hash.key?(key) || (hash.default_proc && hash[key])
|
|
87
|
+
val = hash[key]
|
|
88
|
+
case val
|
|
89
|
+
when String
|
|
90
|
+
val.split
|
|
91
|
+
when Array
|
|
92
|
+
val.compact
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def transform_keys(hash)
|
|
98
|
+
result = {}
|
|
99
|
+
hash.each_key do |key|
|
|
100
|
+
result[yield(key)] = hash[key]
|
|
101
|
+
end
|
|
102
|
+
result
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# Apply #to_sym to all keys in the hash
|
|
106
|
+
#
|
|
107
|
+
# hash - the hash to which to apply this transformation
|
|
108
|
+
#
|
|
109
|
+
# Returns a new hash with symbolized keys
|
|
110
|
+
def symbolize_hash_keys(hash)
|
|
111
|
+
transform_keys(hash) { |key| key.to_sym rescue key }
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# Apply #to_s to all keys in the Hash
|
|
115
|
+
#
|
|
116
|
+
# hash - the hash to which to apply this transformation
|
|
117
|
+
#
|
|
118
|
+
# Returns a new hash with stringified keys
|
|
119
|
+
def stringify_hash_keys(hash)
|
|
120
|
+
transform_keys(hash) { |key| key.to_s rescue key }
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# Parse a date/time and throw an error if invalid
|
|
124
|
+
#
|
|
125
|
+
# input - the date/time to parse
|
|
126
|
+
# msg - (optional) the error message to show the user
|
|
127
|
+
#
|
|
128
|
+
# Returns the parsed date if successful, throws a FatalException
|
|
129
|
+
# if not
|
|
130
|
+
def parse_date(input, msg = "Input could not be parsed.")
|
|
131
|
+
Time.parse(input).localtime
|
|
132
|
+
rescue ArgumentError
|
|
133
|
+
raise Errors::InvalidDateError, "Invalid date '#{input}': #{msg}"
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# Determines whether a given file has
|
|
137
|
+
#
|
|
138
|
+
# Returns true if the YAML front matter is present.
|
|
139
|
+
# rubocop: disable Naming/PredicateName
|
|
140
|
+
def has_yaml_header?(file)
|
|
141
|
+
File.open(file, "rb", &:readline).match? %r!\A---\s*\r?\n!
|
|
142
|
+
rescue EOFError
|
|
143
|
+
false
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
# Determine whether the given content string contains Liquid Tags or Vaiables
|
|
147
|
+
#
|
|
148
|
+
# Returns true is the string contains sequences of `{%` or `{{`
|
|
149
|
+
def has_liquid_construct?(content)
|
|
150
|
+
return false if content.nil? || content.empty?
|
|
151
|
+
|
|
152
|
+
content.include?("{%") || content.include?("{{")
|
|
153
|
+
end
|
|
154
|
+
# rubocop: enable Naming/PredicateName
|
|
155
|
+
|
|
156
|
+
# Slugify a filename or title.
|
|
157
|
+
#
|
|
158
|
+
# string - the filename or title to slugify
|
|
159
|
+
# mode - how string is slugified
|
|
160
|
+
# cased - whether to replace all uppercase letters with their
|
|
161
|
+
# lowercase counterparts
|
|
162
|
+
#
|
|
163
|
+
# When mode is "none", return the given string.
|
|
164
|
+
#
|
|
165
|
+
# When mode is "raw", return the given string,
|
|
166
|
+
# with every sequence of spaces characters replaced with a hyphen.
|
|
167
|
+
#
|
|
168
|
+
# When mode is "default" or nil, non-alphabetic characters are
|
|
169
|
+
# replaced with a hyphen too.
|
|
170
|
+
#
|
|
171
|
+
# When mode is "pretty", some non-alphabetic characters (._~!$&'()+,;=@)
|
|
172
|
+
# are not replaced with hyphen.
|
|
173
|
+
#
|
|
174
|
+
# When mode is "ascii", some everything else except ASCII characters
|
|
175
|
+
# a-z (lowercase), A-Z (uppercase) and 0-9 (numbers) are not replaced with hyphen.
|
|
176
|
+
#
|
|
177
|
+
# When mode is "latin", the input string is first preprocessed so that
|
|
178
|
+
# any letters with accents are replaced with the plain letter. Afterwards,
|
|
179
|
+
# it follows the "default" mode of operation.
|
|
180
|
+
#
|
|
181
|
+
# If cased is true, all uppercase letters in the result string are
|
|
182
|
+
# replaced with their lowercase counterparts.
|
|
183
|
+
#
|
|
184
|
+
# Examples:
|
|
185
|
+
# slugify("The _config.yml file")
|
|
186
|
+
# # => "the-config-yml-file"
|
|
187
|
+
#
|
|
188
|
+
# slugify("The _config.yml file", "pretty")
|
|
189
|
+
# # => "the-_config.yml-file"
|
|
190
|
+
#
|
|
191
|
+
# slugify("The _config.yml file", "pretty", true)
|
|
192
|
+
# # => "The-_config.yml file"
|
|
193
|
+
#
|
|
194
|
+
# slugify("The _config.yml file", "ascii")
|
|
195
|
+
# # => "the-config-yml-file"
|
|
196
|
+
#
|
|
197
|
+
# slugify("The _config.yml file", "latin")
|
|
198
|
+
# # => "the-config-yml-file"
|
|
199
|
+
#
|
|
200
|
+
# Returns the slugified string.
|
|
201
|
+
def slugify(string, mode: nil, cased: false)
|
|
202
|
+
mode ||= "default"
|
|
203
|
+
return nil if string.nil?
|
|
204
|
+
|
|
205
|
+
unless SLUGIFY_MODES.include?(mode)
|
|
206
|
+
return cased ? string : string.downcase
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
# Drop accent marks from latin characters. Everything else turns to ?
|
|
210
|
+
if mode == "latin"
|
|
211
|
+
I18n.config.available_locales = :en if I18n.config.available_locales.empty?
|
|
212
|
+
string = I18n.transliterate(string)
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
slug = replace_character_sequence_with_hyphen(string, :mode => mode)
|
|
216
|
+
|
|
217
|
+
# Remove leading/trailing hyphen
|
|
218
|
+
slug.gsub!(%r!^-|-$!i, "")
|
|
219
|
+
|
|
220
|
+
slug.downcase! unless cased
|
|
221
|
+
Jekyll.logger.warn("Warning:", "Empty `slug` generated for '#{string}'.") if slug.empty?
|
|
222
|
+
slug
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
# Add an appropriate suffix to template so that it matches the specified
|
|
226
|
+
# permalink style.
|
|
227
|
+
#
|
|
228
|
+
# template - permalink template without trailing slash or file extension
|
|
229
|
+
# permalink_style - permalink style, either built-in or custom
|
|
230
|
+
#
|
|
231
|
+
# The returned permalink template will use the same ending style as
|
|
232
|
+
# specified in permalink_style. For example, if permalink_style contains a
|
|
233
|
+
# trailing slash (or is :pretty, which indirectly has a trailing slash),
|
|
234
|
+
# then so will the returned template. If permalink_style has a trailing
|
|
235
|
+
# ":output_ext" (or is :none, :date, or :ordinal) then so will the returned
|
|
236
|
+
# template. Otherwise, template will be returned without modification.
|
|
237
|
+
#
|
|
238
|
+
# Examples:
|
|
239
|
+
# add_permalink_suffix("/:basename", :pretty)
|
|
240
|
+
# # => "/:basename/"
|
|
241
|
+
#
|
|
242
|
+
# add_permalink_suffix("/:basename", :date)
|
|
243
|
+
# # => "/:basename:output_ext"
|
|
244
|
+
#
|
|
245
|
+
# add_permalink_suffix("/:basename", "/:year/:month/:title/")
|
|
246
|
+
# # => "/:basename/"
|
|
247
|
+
#
|
|
248
|
+
# add_permalink_suffix("/:basename", "/:year/:month/:title")
|
|
249
|
+
# # => "/:basename"
|
|
250
|
+
#
|
|
251
|
+
# Returns the updated permalink template
|
|
252
|
+
def add_permalink_suffix(template, permalink_style)
|
|
253
|
+
template = template.dup
|
|
254
|
+
|
|
255
|
+
case permalink_style
|
|
256
|
+
when :pretty
|
|
257
|
+
template << "/"
|
|
258
|
+
when :date, :ordinal, :none
|
|
259
|
+
template << ":output_ext"
|
|
260
|
+
else
|
|
261
|
+
template << "/" if permalink_style.to_s.end_with?("/")
|
|
262
|
+
template << ":output_ext" if permalink_style.to_s.end_with?(":output_ext")
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
template
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
# Work the same way as Dir.glob but seperating the input into two parts
|
|
269
|
+
# ('dir' + '/' + 'pattern') to make sure the first part('dir') does not act
|
|
270
|
+
# as a pattern.
|
|
271
|
+
#
|
|
272
|
+
# For example, Dir.glob("path[/*") always returns an empty array,
|
|
273
|
+
# because the method fails to find the closing pattern to '[' which is ']'
|
|
274
|
+
#
|
|
275
|
+
# Examples:
|
|
276
|
+
# safe_glob("path[", "*")
|
|
277
|
+
# # => ["path[/file1", "path[/file2"]
|
|
278
|
+
#
|
|
279
|
+
# safe_glob("path", "*", File::FNM_DOTMATCH)
|
|
280
|
+
# # => ["path/.", "path/..", "path/file1"]
|
|
281
|
+
#
|
|
282
|
+
# safe_glob("path", ["**", "*"])
|
|
283
|
+
# # => ["path[/file1", "path[/folder/file2"]
|
|
284
|
+
#
|
|
285
|
+
# dir - the dir where glob will be executed under
|
|
286
|
+
# (the dir will be included to each result)
|
|
287
|
+
# patterns - the patterns (or the pattern) which will be applied under the dir
|
|
288
|
+
# flags - the flags which will be applied to the pattern
|
|
289
|
+
#
|
|
290
|
+
# Returns matched pathes
|
|
291
|
+
def safe_glob(dir, patterns, flags = 0)
|
|
292
|
+
return [] unless Dir.exist?(dir)
|
|
293
|
+
|
|
294
|
+
pattern = File.join(Array(patterns))
|
|
295
|
+
return [dir] if pattern.empty?
|
|
296
|
+
|
|
297
|
+
Dir.chdir(dir) do
|
|
298
|
+
Dir.glob(pattern, flags).map { |f| File.join(dir, f) }
|
|
299
|
+
end
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
# Returns merged option hash for File.read of self.site (if exists)
|
|
303
|
+
# and a given param
|
|
304
|
+
def merged_file_read_opts(site, opts)
|
|
305
|
+
merged = (site ? site.file_read_opts : {}).merge(opts)
|
|
306
|
+
if merged[:encoding] && !merged[:encoding].start_with?("bom|")
|
|
307
|
+
merged[:encoding] = "bom|#{merged[:encoding]}"
|
|
308
|
+
end
|
|
309
|
+
if merged["encoding"] && !merged["encoding"].start_with?("bom|")
|
|
310
|
+
merged["encoding"] = "bom|#{merged["encoding"]}"
|
|
311
|
+
end
|
|
312
|
+
merged
|
|
313
|
+
end
|
|
314
|
+
|
|
315
|
+
private
|
|
316
|
+
|
|
317
|
+
def merge_values(target, overwrite)
|
|
318
|
+
target.merge!(overwrite) do |_key, old_val, new_val|
|
|
319
|
+
if new_val.nil?
|
|
320
|
+
old_val
|
|
321
|
+
elsif mergable?(old_val) && mergable?(new_val)
|
|
322
|
+
deep_merge_hashes(old_val, new_val)
|
|
323
|
+
else
|
|
324
|
+
new_val
|
|
325
|
+
end
|
|
326
|
+
end
|
|
327
|
+
end
|
|
328
|
+
|
|
329
|
+
def merge_default_proc(target, overwrite)
|
|
330
|
+
if target.is_a?(Hash) && overwrite.is_a?(Hash) && target.default_proc.nil?
|
|
331
|
+
target.default_proc = overwrite.default_proc
|
|
332
|
+
end
|
|
333
|
+
end
|
|
334
|
+
|
|
335
|
+
def duplicate_frozen_values(target)
|
|
336
|
+
target.each do |key, val|
|
|
337
|
+
target[key] = val.dup if val.frozen? && duplicable?(val)
|
|
338
|
+
end
|
|
339
|
+
end
|
|
340
|
+
|
|
341
|
+
# Replace each character sequence with a hyphen.
|
|
342
|
+
#
|
|
343
|
+
# See Utils#slugify for a description of the character sequence specified
|
|
344
|
+
# by each mode.
|
|
345
|
+
def replace_character_sequence_with_hyphen(string, mode: "default")
|
|
346
|
+
replaceable_char =
|
|
347
|
+
case mode
|
|
348
|
+
when "raw"
|
|
349
|
+
SLUGIFY_RAW_REGEXP
|
|
350
|
+
when "pretty"
|
|
351
|
+
# "._~!$&'()+,;=@" is human readable (not URI-escaped) in URL
|
|
352
|
+
# and is allowed in both extN and NTFS.
|
|
353
|
+
SLUGIFY_PRETTY_REGEXP
|
|
354
|
+
when "ascii"
|
|
355
|
+
# For web servers not being able to handle Unicode, the safe
|
|
356
|
+
# method is to ditch anything else but latin letters and numeric
|
|
357
|
+
# digits.
|
|
358
|
+
SLUGIFY_ASCII_REGEXP
|
|
359
|
+
else
|
|
360
|
+
SLUGIFY_DEFAULT_REGEXP
|
|
361
|
+
end
|
|
362
|
+
|
|
363
|
+
# Strip according to the mode
|
|
364
|
+
string.gsub(replaceable_char, "-")
|
|
365
|
+
end
|
|
366
|
+
end
|
|
367
|
+
end
|