jekyll 4.0.0.pre.beta1 → 4.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|