jekyll 4.0.0.pre.beta1 → 4.2.0
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 +204 -18
- data/README.markdown +2 -6
- data/lib/blank_template/_layouts/default.html +1 -1
- data/lib/jekyll.rb +6 -17
- data/lib/jekyll/cleaner.rb +3 -3
- data/lib/jekyll/collection.rb +2 -2
- data/lib/jekyll/command.rb +4 -2
- data/lib/jekyll/commands/doctor.rb +19 -15
- data/lib/jekyll/commands/new.rb +4 -4
- data/lib/jekyll/commands/new_theme.rb +0 -2
- data/lib/jekyll/commands/serve.rb +12 -1
- data/lib/jekyll/configuration.rb +18 -19
- data/lib/jekyll/converters/identity.rb +2 -2
- data/lib/jekyll/converters/markdown/kramdown_parser.rb +70 -1
- data/lib/jekyll/convertible.rb +30 -23
- data/lib/jekyll/document.rb +41 -19
- data/lib/jekyll/drops/collection_drop.rb +3 -3
- data/lib/jekyll/drops/document_drop.rb +4 -3
- data/lib/jekyll/drops/drop.rb +98 -20
- data/lib/jekyll/drops/site_drop.rb +3 -3
- data/lib/jekyll/drops/static_file_drop.rb +4 -4
- data/lib/jekyll/drops/url_drop.rb +11 -3
- data/lib/jekyll/entry_filter.rb +18 -7
- data/lib/jekyll/excerpt.rb +1 -1
- data/lib/jekyll/filters.rb +112 -28
- data/lib/jekyll/filters/url_filters.rb +45 -15
- data/lib/jekyll/frontmatter_defaults.rb +14 -19
- data/lib/jekyll/hooks.rb +22 -21
- data/lib/jekyll/inclusion.rb +32 -0
- data/lib/jekyll/layout.rb +5 -0
- data/lib/jekyll/liquid_renderer.rb +18 -15
- data/lib/jekyll/liquid_renderer/file.rb +10 -0
- data/lib/jekyll/liquid_renderer/table.rb +1 -64
- data/lib/jekyll/page.rb +42 -11
- data/lib/jekyll/page_excerpt.rb +25 -0
- data/lib/jekyll/path_manager.rb +53 -10
- data/lib/jekyll/profiler.rb +58 -0
- data/lib/jekyll/reader.rb +11 -6
- data/lib/jekyll/readers/collection_reader.rb +1 -0
- data/lib/jekyll/readers/data_reader.rb +4 -0
- data/lib/jekyll/readers/layout_reader.rb +1 -0
- data/lib/jekyll/readers/page_reader.rb +1 -0
- data/lib/jekyll/readers/post_reader.rb +2 -1
- data/lib/jekyll/readers/static_file_reader.rb +1 -0
- data/lib/jekyll/readers/theme_assets_reader.rb +1 -0
- data/lib/jekyll/related_posts.rb +1 -1
- data/lib/jekyll/renderer.rb +15 -17
- data/lib/jekyll/site.rb +34 -10
- data/lib/jekyll/static_file.rb +17 -12
- data/lib/jekyll/tags/include.rb +82 -33
- data/lib/jekyll/tags/link.rb +2 -1
- data/lib/jekyll/tags/post_url.rb +3 -4
- data/lib/jekyll/theme.rb +6 -8
- data/lib/jekyll/url.rb +8 -5
- data/lib/jekyll/utils.rb +5 -5
- data/lib/jekyll/utils/platforms.rb +34 -49
- data/lib/jekyll/utils/win_tz.rb +1 -1
- data/lib/jekyll/version.rb +1 -1
- data/lib/theme_template/theme.gemspec.erb +1 -4
- metadata +34 -39
data/lib/jekyll/document.rb
CHANGED
@@ -116,7 +116,7 @@ module Jekyll
|
|
116
116
|
#
|
117
117
|
# Returns the output extension
|
118
118
|
def output_ext
|
119
|
-
|
119
|
+
renderer.output_ext
|
120
120
|
end
|
121
121
|
|
122
122
|
# The base filename of the document, without the file extname.
|
@@ -133,6 +133,10 @@ module Jekyll
|
|
133
133
|
@basename ||= File.basename(path)
|
134
134
|
end
|
135
135
|
|
136
|
+
def renderer
|
137
|
+
@renderer ||= Jekyll::Renderer.new(site, self)
|
138
|
+
end
|
139
|
+
|
136
140
|
# Produces a "cleaned" relative path.
|
137
141
|
# The "cleaned" relative path is the relative path without the extname
|
138
142
|
# and with the collection's directory removed as well.
|
@@ -253,14 +257,16 @@ module Jekyll
|
|
253
257
|
#
|
254
258
|
# Returns the full path to the output file of this document.
|
255
259
|
def destination(base_directory)
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
260
|
+
@destination ||= {}
|
261
|
+
@destination[base_directory] ||= begin
|
262
|
+
path = site.in_dest_dir(base_directory, URL.unescape_path(url))
|
263
|
+
if url.end_with? "/"
|
264
|
+
path = File.join(path, "index.html")
|
265
|
+
else
|
266
|
+
path << output_ext unless path.end_with? output_ext
|
267
|
+
end
|
268
|
+
path
|
262
269
|
end
|
263
|
-
path
|
264
270
|
end
|
265
271
|
|
266
272
|
# Write the generated Document file to the destination directory.
|
@@ -298,7 +304,7 @@ module Jekyll
|
|
298
304
|
else
|
299
305
|
begin
|
300
306
|
merge_defaults
|
301
|
-
read_content(opts)
|
307
|
+
read_content(**opts)
|
302
308
|
read_post_data
|
303
309
|
rescue StandardError => e
|
304
310
|
handle_read_error(e)
|
@@ -347,9 +353,14 @@ module Jekyll
|
|
347
353
|
# True if the document has a collection and if that collection's #write?
|
348
354
|
# method returns true, and if the site's Publisher will publish the document.
|
349
355
|
# False otherwise.
|
356
|
+
#
|
357
|
+
# rubocop:disable Naming/MemoizedInstanceVariableName
|
350
358
|
def write?
|
351
|
-
|
359
|
+
return @write_p if defined?(@write_p)
|
360
|
+
|
361
|
+
@write_p = collection&.write? && site.publisher.publish?(self)
|
352
362
|
end
|
363
|
+
# rubocop:enable Naming/MemoizedInstanceVariableName
|
353
364
|
|
354
365
|
# The Document excerpt_separator, from the YAML Front-Matter or site
|
355
366
|
# default excerpt_separator value
|
@@ -414,9 +425,13 @@ module Jekyll
|
|
414
425
|
#
|
415
426
|
# Returns nothing.
|
416
427
|
def categories_from_path(special_dir)
|
417
|
-
|
418
|
-
|
419
|
-
|
428
|
+
if relative_path.start_with?(special_dir)
|
429
|
+
superdirs = []
|
430
|
+
else
|
431
|
+
superdirs = relative_path.sub(Document.superdirs_regex(special_dir), "")
|
432
|
+
superdirs = superdirs.split(File::SEPARATOR)
|
433
|
+
superdirs.reject! { |c| c.empty? || c == special_dir || c == basename }
|
434
|
+
end
|
420
435
|
|
421
436
|
merge_data!({ "categories" => superdirs }, :source => "file path")
|
422
437
|
end
|
@@ -429,14 +444,14 @@ module Jekyll
|
|
429
444
|
categories.flatten!
|
430
445
|
categories.uniq!
|
431
446
|
|
432
|
-
merge_data!("categories" => categories)
|
447
|
+
merge_data!({ "categories" => categories })
|
433
448
|
end
|
434
449
|
|
435
450
|
def populate_tags
|
436
451
|
tags = Utils.pluralized_array_from_hash(data, "tag", "tags")
|
437
452
|
tags.flatten!
|
438
453
|
|
439
|
-
merge_data!("tags" => tags)
|
454
|
+
merge_data!({ "tags" => tags })
|
440
455
|
end
|
441
456
|
|
442
457
|
private
|
@@ -444,7 +459,10 @@ module Jekyll
|
|
444
459
|
def merge_categories!(other)
|
445
460
|
if other.key?("categories") && !other["categories"].nil?
|
446
461
|
other["categories"] = other["categories"].split if other["categories"].is_a?(String)
|
447
|
-
|
462
|
+
|
463
|
+
if data["categories"].is_a?(Array)
|
464
|
+
other["categories"] = data["categories"] | other["categories"]
|
465
|
+
end
|
448
466
|
end
|
449
467
|
end
|
450
468
|
|
@@ -462,10 +480,10 @@ module Jekyll
|
|
462
480
|
merge_data!(defaults, :source => "front matter defaults") unless defaults.empty?
|
463
481
|
end
|
464
482
|
|
465
|
-
def read_content(opts)
|
466
|
-
self.content = File.read(path, Utils.merged_file_read_opts(site, opts))
|
483
|
+
def read_content(**opts)
|
484
|
+
self.content = File.read(path, **Utils.merged_file_read_opts(site, opts))
|
467
485
|
if content =~ YAML_FRONT_MATTER_REGEXP
|
468
|
-
self.content =
|
486
|
+
self.content = Regexp.last_match.post_match
|
469
487
|
data_file = SafeYAML.load(Regexp.last_match(1))
|
470
488
|
merge_data!(data_file, :source => "YAML front matter") if data_file
|
471
489
|
end
|
@@ -497,6 +515,10 @@ module Jekyll
|
|
497
515
|
elsif relative_path =~ DATELESS_FILENAME_MATCHER
|
498
516
|
slug, ext = Regexp.last_match.captures
|
499
517
|
end
|
518
|
+
# `slug` will be nil for documents without an extension since the regex patterns
|
519
|
+
# above tests for an extension as well.
|
520
|
+
# In such cases, assign `basename_without_ext` as the slug.
|
521
|
+
slug ||= basename_without_ext
|
500
522
|
|
501
523
|
# slugs shouldn't end with a period
|
502
524
|
# `String#gsub!` removes all trailing periods (in comparison to `String#chomp!`)
|
@@ -7,10 +7,10 @@ module Jekyll
|
|
7
7
|
|
8
8
|
mutable false
|
9
9
|
|
10
|
-
|
11
|
-
|
10
|
+
delegate_method_as :write?, :output
|
11
|
+
delegate_methods :label, :docs, :files, :directory, :relative_directory
|
12
12
|
|
13
|
-
private
|
13
|
+
private delegate_method_as :metadata, :fallback_data
|
14
14
|
|
15
15
|
def to_s
|
16
16
|
docs.to_s
|
@@ -11,10 +11,11 @@ module Jekyll
|
|
11
11
|
|
12
12
|
mutable false
|
13
13
|
|
14
|
-
|
15
|
-
|
14
|
+
delegate_method_as :relative_path, :path
|
15
|
+
private delegate_method_as :data, :fallback_data
|
16
16
|
|
17
|
-
|
17
|
+
delegate_methods :id, :output, :content, :to_s, :relative_path, :url, :date
|
18
|
+
data_delegators "title", "categories", "tags"
|
18
19
|
|
19
20
|
def collection
|
20
21
|
@obj.collection.label
|
data/lib/jekyll/drops/drop.rb
CHANGED
@@ -6,20 +6,101 @@ module Jekyll
|
|
6
6
|
include Enumerable
|
7
7
|
|
8
8
|
NON_CONTENT_METHODS = [:fallback_data, :collapse_document].freeze
|
9
|
+
NON_CONTENT_METHOD_NAMES = NON_CONTENT_METHODS.map(&:to_s).freeze
|
10
|
+
private_constant :NON_CONTENT_METHOD_NAMES
|
9
11
|
|
10
|
-
#
|
11
|
-
#
|
12
|
-
#
|
13
|
-
#
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
12
|
+
# A private stash to avoid repeatedly generating the setter method name string for
|
13
|
+
# a call to `Drops::Drop#[]=`.
|
14
|
+
# The keys of the stash below have a very high probability of being called upon during
|
15
|
+
# the course of various `Jekyll::Renderer#run` calls.
|
16
|
+
SETTER_KEYS_STASH = {
|
17
|
+
"content" => "content=",
|
18
|
+
"layout" => "layout=",
|
19
|
+
"page" => "page=",
|
20
|
+
"paginator" => "paginator=",
|
21
|
+
"highlighter_prefix" => "highlighter_prefix=",
|
22
|
+
"highlighter_suffix" => "highlighter_suffix=",
|
23
|
+
}.freeze
|
24
|
+
private_constant :SETTER_KEYS_STASH
|
25
|
+
|
26
|
+
class << self
|
27
|
+
# Get or set whether the drop class is mutable.
|
28
|
+
# Mutability determines whether or not pre-defined fields may be
|
29
|
+
# overwritten.
|
30
|
+
#
|
31
|
+
# is_mutable - Boolean set mutability of the class (default: nil)
|
32
|
+
#
|
33
|
+
# Returns the mutability of the class
|
34
|
+
def mutable(is_mutable = nil)
|
35
|
+
@is_mutable = is_mutable || false
|
36
|
+
end
|
37
|
+
|
38
|
+
def mutable?
|
39
|
+
@is_mutable
|
40
|
+
end
|
41
|
+
|
42
|
+
# public delegation helper methods that calls onto Drop's instance
|
43
|
+
# variable `@obj`.
|
44
|
+
|
45
|
+
# Generate private Drop instance_methods for each symbol in the given list.
|
46
|
+
#
|
47
|
+
# Returns nothing.
|
48
|
+
def private_delegate_methods(*symbols)
|
49
|
+
symbols.each { |symbol| private delegate_method(symbol) }
|
50
|
+
nil
|
51
|
+
end
|
52
|
+
|
53
|
+
# Generate public Drop instance_methods for each symbol in the given list.
|
54
|
+
#
|
55
|
+
# Returns nothing.
|
56
|
+
def delegate_methods(*symbols)
|
57
|
+
symbols.each { |symbol| delegate_method(symbol) }
|
58
|
+
nil
|
59
|
+
end
|
20
60
|
|
21
|
-
|
22
|
-
|
61
|
+
# Generate public Drop instance_method for given symbol that calls `@obj.<sym>`.
|
62
|
+
#
|
63
|
+
# Returns delegated method symbol.
|
64
|
+
def delegate_method(symbol)
|
65
|
+
define_method(symbol) { @obj.send(symbol) }
|
66
|
+
end
|
67
|
+
|
68
|
+
# Generate public Drop instance_method named `delegate` that calls `@obj.<original>`.
|
69
|
+
#
|
70
|
+
# Returns delegated method symbol.
|
71
|
+
def delegate_method_as(original, delegate)
|
72
|
+
define_method(delegate) { @obj.send(original) }
|
73
|
+
end
|
74
|
+
|
75
|
+
# Generate public Drop instance_methods for each string entry in the given list.
|
76
|
+
# The generated method(s) access(es) `@obj`'s data hash.
|
77
|
+
#
|
78
|
+
# Returns nothing.
|
79
|
+
def data_delegators(*strings)
|
80
|
+
strings.each do |key|
|
81
|
+
data_delegator(key) if key.is_a?(String)
|
82
|
+
end
|
83
|
+
nil
|
84
|
+
end
|
85
|
+
|
86
|
+
# Generate public Drop instance_methods for given string `key`.
|
87
|
+
# The generated method access(es) `@obj`'s data hash.
|
88
|
+
#
|
89
|
+
# Returns method symbol.
|
90
|
+
def data_delegator(key)
|
91
|
+
define_method(key.to_sym) { @obj.data[key] }
|
92
|
+
end
|
93
|
+
|
94
|
+
# Array of stringified instance methods that do not end with the assignment operator.
|
95
|
+
#
|
96
|
+
# (<klass>.instance_methods always generates a new Array object so it can be mutated)
|
97
|
+
#
|
98
|
+
# Returns array of strings.
|
99
|
+
def getter_method_names
|
100
|
+
@getter_method_names ||= instance_methods.map!(&:to_s).tap do |list|
|
101
|
+
list.reject! { |item| item.end_with?("=") }
|
102
|
+
end
|
103
|
+
end
|
23
104
|
end
|
24
105
|
|
25
106
|
# Create a new Drop
|
@@ -65,7 +146,7 @@ module Jekyll
|
|
65
146
|
# and the key matches a method in which case it raises a
|
66
147
|
# DropMutationException.
|
67
148
|
def []=(key, val)
|
68
|
-
setter = "#{key}="
|
149
|
+
setter = SETTER_KEYS_STASH[key] || "#{key}="
|
69
150
|
if respond_to?(setter)
|
70
151
|
public_send(setter, val)
|
71
152
|
elsif respond_to?(key.to_s)
|
@@ -84,13 +165,10 @@ module Jekyll
|
|
84
165
|
#
|
85
166
|
# Returns an Array of strings which represent method-specific keys.
|
86
167
|
def content_methods
|
87
|
-
@content_methods ||=
|
88
|
-
self.class.
|
89
|
-
- Jekyll::Drops::Drop.
|
90
|
-
-
|
91
|
-
).map(&:to_s).reject do |method|
|
92
|
-
method.end_with?("=")
|
93
|
-
end
|
168
|
+
@content_methods ||= \
|
169
|
+
self.class.getter_method_names \
|
170
|
+
- Jekyll::Drops::Drop.getter_method_names \
|
171
|
+
- NON_CONTENT_METHOD_NAMES
|
94
172
|
end
|
95
173
|
|
96
174
|
# Check if key exists in Drop
|
@@ -7,10 +7,10 @@ module Jekyll
|
|
7
7
|
|
8
8
|
mutable false
|
9
9
|
|
10
|
-
|
11
|
-
|
10
|
+
delegate_method_as :site_data, :data
|
11
|
+
delegate_methods :time, :pages, :static_files, :tags, :categories
|
12
12
|
|
13
|
-
private
|
13
|
+
private delegate_method_as :config, :fallback_data
|
14
14
|
|
15
15
|
def [](key)
|
16
16
|
if key != "posts" && @obj.collections.key?(key)
|
@@ -4,11 +4,11 @@ module Jekyll
|
|
4
4
|
module Drops
|
5
5
|
class StaticFileDrop < Drop
|
6
6
|
extend Forwardable
|
7
|
-
|
8
|
-
|
9
|
-
|
7
|
+
delegate_methods :name, :extname, :modified_time, :basename
|
8
|
+
delegate_method_as :relative_path, :path
|
9
|
+
delegate_method_as :type, :collection
|
10
10
|
|
11
|
-
private
|
11
|
+
private delegate_method_as :data, :fallback_data
|
12
12
|
end
|
13
13
|
end
|
14
14
|
end
|
@@ -7,8 +7,8 @@ module Jekyll
|
|
7
7
|
|
8
8
|
mutable false
|
9
9
|
|
10
|
-
|
11
|
-
|
10
|
+
delegate_method :output_ext
|
11
|
+
delegate_method_as :cleaned_relative_path, :path
|
12
12
|
|
13
13
|
def collection
|
14
14
|
@obj.collection.label
|
@@ -35,6 +35,14 @@ module Jekyll
|
|
35
35
|
category_set.to_a.join("/")
|
36
36
|
end
|
37
37
|
|
38
|
+
# Similar to output from #categories, but each category will be downcased and
|
39
|
+
# all non-alphanumeric characters of the category replaced with a hyphen.
|
40
|
+
def slugified_categories
|
41
|
+
Array(@obj.data["categories"]).each_with_object(Set.new) do |category, set|
|
42
|
+
set << Utils.slugify(category.to_s)
|
43
|
+
end.to_a.join("/")
|
44
|
+
end
|
45
|
+
|
38
46
|
# CCYY
|
39
47
|
def year
|
40
48
|
@obj.date.strftime("%Y")
|
@@ -125,7 +133,7 @@ module Jekyll
|
|
125
133
|
private
|
126
134
|
|
127
135
|
def fallback_data
|
128
|
-
{}
|
136
|
+
@fallback_data ||= {}
|
129
137
|
end
|
130
138
|
end
|
131
139
|
end
|
data/lib/jekyll/entry_filter.rb
CHANGED
@@ -3,6 +3,7 @@
|
|
3
3
|
module Jekyll
|
4
4
|
class EntryFilter
|
5
5
|
attr_reader :site
|
6
|
+
|
6
7
|
SPECIAL_LEADING_CHAR_REGEX = %r!\A#{Regexp.union([".", "_", "#", "~"])}!o.freeze
|
7
8
|
|
8
9
|
def initialize(site, base_directory = nil)
|
@@ -32,13 +33,21 @@ module Jekyll
|
|
32
33
|
# Reject this entry if it is just a "dot" representation.
|
33
34
|
# e.g.: '.', '..', '_movies/.', 'music/..', etc
|
34
35
|
next true if e.end_with?(".")
|
35
|
-
|
36
|
+
|
37
|
+
# Check if the current entry is explicitly included and cache the result
|
38
|
+
included = included?(e)
|
39
|
+
|
40
|
+
# Reject current entry if it is excluded but not explicitly included as well.
|
41
|
+
next true if excluded?(e) && !included
|
42
|
+
|
43
|
+
# Reject current entry if it is a symlink.
|
36
44
|
next true if symlink?(e)
|
37
|
-
# Do not reject this entry if it is included.
|
38
|
-
next false if included?(e)
|
39
45
|
|
40
|
-
#
|
41
|
-
|
46
|
+
# Do not reject current entry if it is explicitly included.
|
47
|
+
next false if included
|
48
|
+
|
49
|
+
# Reject current entry if it is special or a backup file.
|
50
|
+
special?(e) || backup?(e)
|
42
51
|
end
|
43
52
|
end
|
44
53
|
|
@@ -53,7 +62,7 @@ module Jekyll
|
|
53
62
|
end
|
54
63
|
|
55
64
|
def backup?(entry)
|
56
|
-
entry
|
65
|
+
entry.end_with?("~")
|
57
66
|
end
|
58
67
|
|
59
68
|
def excluded?(entry)
|
@@ -91,6 +100,7 @@ module Jekyll
|
|
91
100
|
# Returns true if path matches against any glob pattern, else false.
|
92
101
|
def glob_include?(enumerator, entry)
|
93
102
|
entry_with_source = PathManager.join(site.source, entry)
|
103
|
+
entry_is_directory = File.directory?(entry_with_source)
|
94
104
|
|
95
105
|
enumerator.any? do |pattern|
|
96
106
|
case pattern
|
@@ -98,7 +108,8 @@ module Jekyll
|
|
98
108
|
pattern_with_source = PathManager.join(site.source, pattern)
|
99
109
|
|
100
110
|
File.fnmatch?(pattern_with_source, entry_with_source) ||
|
101
|
-
entry_with_source.start_with?(pattern_with_source)
|
111
|
+
entry_with_source.start_with?(pattern_with_source) ||
|
112
|
+
(pattern_with_source == "#{entry_with_source}/" if entry_is_directory)
|
102
113
|
when Regexp
|
103
114
|
pattern.match?(entry_with_source)
|
104
115
|
else
|
data/lib/jekyll/excerpt.rb
CHANGED
@@ -56,7 +56,7 @@ module Jekyll
|
|
56
56
|
#
|
57
57
|
# Returns true if the string passed in
|
58
58
|
def include?(something)
|
59
|
-
|
59
|
+
output&.include?(something) || content.include?(something)
|
60
60
|
end
|
61
61
|
|
62
62
|
# The UID for this doc (useful in feeds).
|
data/lib/jekyll/filters.rb
CHANGED
@@ -113,7 +113,7 @@ module Jekyll
|
|
113
113
|
#
|
114
114
|
# Returns the formatted String
|
115
115
|
def normalize_whitespace(input)
|
116
|
-
input.to_s.gsub(%r!\s+!, " ").strip
|
116
|
+
input.to_s.gsub(%r!\s+!, " ").tap(&:strip!)
|
117
117
|
end
|
118
118
|
|
119
119
|
# Count the number of words in the input string.
|
@@ -121,8 +121,20 @@ module Jekyll
|
|
121
121
|
# input - The String on which to operate.
|
122
122
|
#
|
123
123
|
# Returns the Integer word count.
|
124
|
-
def number_of_words(input)
|
125
|
-
|
124
|
+
def number_of_words(input, mode = nil)
|
125
|
+
cjk_charset = '\p{Han}\p{Katakana}\p{Hiragana}\p{Hangul}'
|
126
|
+
cjk_regex = %r![#{cjk_charset}]!o
|
127
|
+
word_regex = %r![^#{cjk_charset}\s]+!o
|
128
|
+
|
129
|
+
case mode
|
130
|
+
when "cjk"
|
131
|
+
input.scan(cjk_regex).length + input.scan(word_regex).length
|
132
|
+
when "auto"
|
133
|
+
cjk_count = input.scan(cjk_regex).length
|
134
|
+
cjk_count.zero? ? input.split.length : cjk_count + input.scan(word_regex).length
|
135
|
+
else
|
136
|
+
input.split.length
|
137
|
+
end
|
126
138
|
end
|
127
139
|
|
128
140
|
# Join an array of things into a string by separating with commas and the
|
@@ -210,6 +222,66 @@ module Jekyll
|
|
210
222
|
end || []
|
211
223
|
end
|
212
224
|
|
225
|
+
# Search an array of objects and returns the first object that has the queried attribute
|
226
|
+
# with the given value or returns nil otherwise.
|
227
|
+
#
|
228
|
+
# input - the object array.
|
229
|
+
# property - the property within each object to search by.
|
230
|
+
# value - the desired value.
|
231
|
+
# Cannot be an instance of Array nor Hash since calling #to_s on them returns
|
232
|
+
# their `#inspect` string object.
|
233
|
+
#
|
234
|
+
# Returns the found object or nil
|
235
|
+
#
|
236
|
+
# rubocop:disable Metrics/CyclomaticComplexity
|
237
|
+
def find(input, property, value)
|
238
|
+
return input if !property || value.is_a?(Array) || value.is_a?(Hash)
|
239
|
+
return input unless input.respond_to?(:find)
|
240
|
+
|
241
|
+
input = input.values if input.is_a?(Hash)
|
242
|
+
input_id = input.hash
|
243
|
+
|
244
|
+
# implement a hash based on method parameters to cache the end-result for given parameters.
|
245
|
+
@find_filter_cache ||= {}
|
246
|
+
@find_filter_cache[input_id] ||= {}
|
247
|
+
@find_filter_cache[input_id][property] ||= {}
|
248
|
+
|
249
|
+
# stash or retrive results to return
|
250
|
+
# Since `enum.find` can return nil or false, we use a placeholder string "<__NO MATCH__>"
|
251
|
+
# to validate caching.
|
252
|
+
result = @find_filter_cache[input_id][property][value] ||= begin
|
253
|
+
input.find do |object|
|
254
|
+
compare_property_vs_target(item_property(object, property), value)
|
255
|
+
end || "<__NO MATCH__>"
|
256
|
+
end
|
257
|
+
return nil if result == "<__NO MATCH__>"
|
258
|
+
|
259
|
+
result
|
260
|
+
end
|
261
|
+
# rubocop:enable Metrics/CyclomaticComplexity
|
262
|
+
|
263
|
+
# Searches an array of objects against an expression and returns the first object for which
|
264
|
+
# the expression evaluates to true, or returns nil otherwise.
|
265
|
+
#
|
266
|
+
# input - the object array
|
267
|
+
# variable - the variable to assign each item to in the expression
|
268
|
+
# expression - a Liquid comparison expression passed in as a string
|
269
|
+
#
|
270
|
+
# Returns the found object or nil
|
271
|
+
def find_exp(input, variable, expression)
|
272
|
+
return input unless input.respond_to?(:find)
|
273
|
+
|
274
|
+
input = input.values if input.is_a?(Hash)
|
275
|
+
|
276
|
+
condition = parse_condition(expression)
|
277
|
+
@context.stack do
|
278
|
+
input.find do |object|
|
279
|
+
@context[variable] = object
|
280
|
+
condition.evaluate(@context)
|
281
|
+
end
|
282
|
+
end
|
283
|
+
end
|
284
|
+
|
213
285
|
# Convert the input into integer
|
214
286
|
#
|
215
287
|
# input - the object string
|
@@ -235,9 +307,10 @@ module Jekyll
|
|
235
307
|
if property.nil?
|
236
308
|
input.sort
|
237
309
|
else
|
238
|
-
|
310
|
+
case nils
|
311
|
+
when "first"
|
239
312
|
order = - 1
|
240
|
-
|
313
|
+
when "last"
|
241
314
|
order = + 1
|
242
315
|
else
|
243
316
|
raise ArgumentError, "Invalid nils order: " \
|
@@ -327,8 +400,6 @@ module Jekyll
|
|
327
400
|
|
328
401
|
# `where` filter helper
|
329
402
|
#
|
330
|
-
# rubocop:disable Metrics/CyclomaticComplexity
|
331
|
-
# rubocop:disable Metrics/PerceivedComplexity
|
332
403
|
def compare_property_vs_target(property, target)
|
333
404
|
case target
|
334
405
|
when NilClass
|
@@ -349,40 +420,49 @@ module Jekyll
|
|
349
420
|
|
350
421
|
false
|
351
422
|
end
|
352
|
-
# rubocop:enable Metrics/CyclomaticComplexity
|
353
|
-
# rubocop:enable Metrics/PerceivedComplexity
|
354
423
|
|
355
424
|
def item_property(item, property)
|
356
|
-
@item_property_cache ||= {}
|
425
|
+
@item_property_cache ||= @context.registers[:site].filter_cache[:item_property] ||= {}
|
357
426
|
@item_property_cache[property] ||= {}
|
358
427
|
@item_property_cache[property][item] ||= begin
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
428
|
+
property = property.to_s
|
429
|
+
property = if item.respond_to?(:to_liquid)
|
430
|
+
read_liquid_attribute(item.to_liquid, property)
|
431
|
+
elsif item.respond_to?(:data)
|
432
|
+
item.data[property]
|
433
|
+
else
|
434
|
+
item[property]
|
435
|
+
end
|
436
|
+
|
437
|
+
parse_sort_input(property)
|
438
|
+
end
|
439
|
+
end
|
440
|
+
|
441
|
+
def read_liquid_attribute(liquid_data, property)
|
442
|
+
return liquid_data[property] unless property.include?(".")
|
443
|
+
|
444
|
+
property.split(".").reduce(liquid_data) do |data, key|
|
445
|
+
data.respond_to?(:[]) && data[key]
|
368
446
|
end
|
369
447
|
end
|
370
448
|
|
371
|
-
|
449
|
+
FLOAT_LIKE = %r!\A\s*-?(?:\d+\.?\d*|\.\d+)\s*\Z!.freeze
|
450
|
+
INTEGER_LIKE = %r!\A\s*-?\d+\s*\Z!.freeze
|
451
|
+
private_constant :FLOAT_LIKE, :INTEGER_LIKE
|
452
|
+
|
372
453
|
# return numeric values as numbers for proper sorting
|
373
454
|
def parse_sort_input(property)
|
374
|
-
|
375
|
-
return property.
|
455
|
+
stringified = property.to_s
|
456
|
+
return property.to_i if INTEGER_LIKE.match?(stringified)
|
457
|
+
return property.to_f if FLOAT_LIKE.match?(stringified)
|
376
458
|
|
377
459
|
property
|
378
460
|
end
|
379
|
-
# rubocop:enable Performance/RegexpMatch
|
380
461
|
|
381
462
|
def as_liquid(item)
|
382
463
|
case item
|
383
464
|
when Hash
|
384
|
-
|
385
|
-
Hash[pairs]
|
465
|
+
item.each_with_object({}) { |(k, v), result| result[as_liquid(k)] = as_liquid(v) }
|
386
466
|
when Array
|
387
467
|
item.map { |i| as_liquid(i) }
|
388
468
|
else
|
@@ -420,10 +500,14 @@ module Jekyll
|
|
420
500
|
#
|
421
501
|
# Returns an instance of Liquid::Condition
|
422
502
|
def parse_binary_comparison(parser)
|
423
|
-
parse_comparison(parser)
|
424
|
-
|
425
|
-
|
503
|
+
condition = parse_comparison(parser)
|
504
|
+
first_condition = condition
|
505
|
+
while (binary_operator = parser.id?("and") || parser.id?("or"))
|
506
|
+
child_condition = parse_comparison(parser)
|
507
|
+
condition.send(binary_operator, child_condition)
|
508
|
+
condition = child_condition
|
426
509
|
end
|
510
|
+
first_condition
|
427
511
|
end
|
428
512
|
|
429
513
|
# Generates a Liquid::Condition object from a Liquid::Parser object based on whether the parsed
|