jekyll 3.10.0 → 4.0.0.pre.alpha1

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 (96) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +27 -50
  3. data/LICENSE +1 -1
  4. data/README.markdown +46 -17
  5. data/lib/blank_template/_config.yml +3 -0
  6. data/lib/blank_template/_layouts/default.html +12 -0
  7. data/lib/blank_template/_sass/main.scss +9 -0
  8. data/lib/blank_template/assets/css/main.scss +4 -0
  9. data/lib/blank_template/index.md +8 -0
  10. data/lib/jekyll/cache.rb +183 -0
  11. data/lib/jekyll/cleaner.rb +2 -1
  12. data/lib/jekyll/collection.rb +78 -8
  13. data/lib/jekyll/command.rb +31 -6
  14. data/lib/jekyll/commands/build.rb +11 -20
  15. data/lib/jekyll/commands/clean.rb +2 -0
  16. data/lib/jekyll/commands/doctor.rb +15 -8
  17. data/lib/jekyll/commands/help.rb +1 -1
  18. data/lib/jekyll/commands/new.rb +37 -42
  19. data/lib/jekyll/commands/new_theme.rb +30 -28
  20. data/lib/jekyll/commands/serve/live_reload_reactor.rb +6 -10
  21. data/lib/jekyll/commands/serve/servlet.rb +15 -19
  22. data/lib/jekyll/commands/serve.rb +46 -86
  23. data/lib/jekyll/configuration.rb +26 -26
  24. data/lib/jekyll/converters/identity.rb +18 -0
  25. data/lib/jekyll/converters/markdown/kramdown_parser.rb +1 -10
  26. data/lib/jekyll/converters/markdown.rb +49 -40
  27. data/lib/jekyll/converters/smartypants.rb +34 -14
  28. data/lib/jekyll/convertible.rb +11 -13
  29. data/lib/jekyll/deprecator.rb +1 -3
  30. data/lib/jekyll/document.rb +44 -41
  31. data/lib/jekyll/drops/collection_drop.rb +2 -3
  32. data/lib/jekyll/drops/document_drop.rb +2 -1
  33. data/lib/jekyll/drops/drop.rb +3 -6
  34. data/lib/jekyll/drops/excerpt_drop.rb +4 -0
  35. data/lib/jekyll/drops/site_drop.rb +4 -13
  36. data/lib/jekyll/drops/unified_payload_drop.rb +1 -0
  37. data/lib/jekyll/drops/url_drop.rb +1 -0
  38. data/lib/jekyll/entry_filter.rb +2 -1
  39. data/lib/jekyll/excerpt.rb +45 -34
  40. data/lib/jekyll/external.rb +10 -5
  41. data/lib/jekyll/filters/date_filters.rb +6 -3
  42. data/lib/jekyll/filters/grouping_filters.rb +1 -2
  43. data/lib/jekyll/filters/url_filters.rb +6 -1
  44. data/lib/jekyll/filters.rb +72 -31
  45. data/lib/jekyll/frontmatter_defaults.rb +35 -19
  46. data/lib/jekyll/hooks.rb +2 -3
  47. data/lib/jekyll/liquid_extensions.rb +0 -2
  48. data/lib/jekyll/liquid_renderer/file.rb +14 -3
  49. data/lib/jekyll/liquid_renderer/table.rb +67 -65
  50. data/lib/jekyll/liquid_renderer.rb +13 -1
  51. data/lib/jekyll/log_adapter.rb +5 -1
  52. data/lib/jekyll/mime.types +80 -195
  53. data/lib/jekyll/page.rb +10 -26
  54. data/lib/jekyll/page_without_a_file.rb +0 -4
  55. data/lib/jekyll/plugin.rb +5 -11
  56. data/lib/jekyll/plugin_manager.rb +2 -0
  57. data/lib/jekyll/reader.rb +38 -8
  58. data/lib/jekyll/readers/data_reader.rb +5 -5
  59. data/lib/jekyll/readers/layout_reader.rb +2 -12
  60. data/lib/jekyll/readers/post_reader.rb +29 -17
  61. data/lib/jekyll/readers/static_file_reader.rb +1 -1
  62. data/lib/jekyll/readers/theme_assets_reader.rb +7 -5
  63. data/lib/jekyll/regenerator.rb +4 -12
  64. data/lib/jekyll/renderer.rb +14 -25
  65. data/lib/jekyll/site.rb +78 -34
  66. data/lib/jekyll/static_file.rb +47 -11
  67. data/lib/jekyll/stevenson.rb +7 -5
  68. data/lib/jekyll/tags/highlight.rb +22 -52
  69. data/lib/jekyll/tags/include.rb +27 -48
  70. data/lib/jekyll/tags/link.rb +11 -7
  71. data/lib/jekyll/tags/post_url.rb +17 -16
  72. data/lib/jekyll/theme.rb +12 -23
  73. data/lib/jekyll/theme_builder.rb +91 -89
  74. data/lib/jekyll/url.rb +3 -2
  75. data/lib/jekyll/utils/ansi.rb +1 -1
  76. data/lib/jekyll/utils/exec.rb +0 -1
  77. data/lib/jekyll/utils/internet.rb +2 -4
  78. data/lib/jekyll/utils/platforms.rb +8 -8
  79. data/lib/jekyll/utils/thread_event.rb +1 -5
  80. data/lib/jekyll/utils/win_tz.rb +47 -18
  81. data/lib/jekyll/utils.rb +5 -4
  82. data/lib/jekyll/version.rb +1 -1
  83. data/lib/jekyll.rb +5 -0
  84. data/lib/site_template/.gitignore +2 -0
  85. data/lib/site_template/404.html +1 -0
  86. data/lib/site_template/_config.yml +17 -5
  87. data/lib/site_template/_posts/0000-00-00-welcome-to-jekyll.markdown.erb +5 -1
  88. data/lib/theme_template/gitignore.erb +1 -0
  89. data/rubocop/jekyll/assert_equal_literal_actual.rb +149 -0
  90. metadata +70 -71
  91. data/lib/jekyll/commands/serve/mime_types_charset.json +0 -71
  92. data/lib/jekyll/converters/markdown/rdiscount_parser.rb +0 -37
  93. data/lib/jekyll/converters/markdown/redcarpet_parser.rb +0 -112
  94. data/lib/jekyll/utils/rouge.rb +0 -22
  95. /data/lib/site_template/{about.md → about.markdown} +0 -0
  96. /data/lib/site_template/{index.md → index.markdown} +0 -0
@@ -8,12 +8,12 @@ module Jekyll
8
8
  mutable false
9
9
 
10
10
  def_delegator :@obj, :site_data, :data
11
- def_delegators :@obj, :time, :pages, :static_files, :tags, :categories
11
+ def_delegators :@obj, :time, :pages, :static_files, :documents, :tags, :categories
12
12
 
13
13
  private def_delegator :@obj, :config, :fallback_data
14
14
 
15
15
  def [](key)
16
- if @obj.collections.key?(key) && key != "posts"
16
+ if key != "posts" && @obj.collections.key?(key)
17
17
  @obj.collections[key].docs
18
18
  else
19
19
  super(key)
@@ -21,7 +21,7 @@ module Jekyll
21
21
  end
22
22
 
23
23
  def key?(key)
24
- (@obj.collections.key?(key) && key != "posts") || super
24
+ (key != "posts" && @obj.collections.key?(key)) || super
25
25
  end
26
26
 
27
27
  def posts
@@ -38,22 +38,13 @@ module Jekyll
38
38
  @site_collections ||= @obj.collections.values.sort_by(&:label).map(&:to_liquid)
39
39
  end
40
40
 
41
- # `Site#documents` cannot be memoized so that `Site#docs_to_write` can access the
42
- # latest state of the attribute.
43
- #
44
- # Since this method will be called after `Site#pre_render` hook,
45
- # the `Site#documents` array shouldn't thereafter change and can therefore be
46
- # safely memoized to prevent additional computation of `Site#documents`.
47
- def documents
48
- @documents ||= @obj.documents
49
- end
50
-
51
41
  # `{{ site.related_posts }}` is how posts can get posts related to
52
42
  # them, either through LSI if it's enabled, or through the most
53
43
  # recent posts.
54
44
  # We should remove this in 4.0 and switch to `{{ post.related_posts }}`.
55
45
  def related_posts
56
46
  return nil unless @current_document.is_a?(Jekyll::Document)
47
+
57
48
  @current_document.related_posts
58
49
  end
59
50
  attr_writer :current_document
@@ -17,6 +17,7 @@ module Jekyll
17
17
  end
18
18
 
19
19
  private
20
+
20
21
  def fallback_data
21
22
  @fallback_data ||= {}
22
23
  end
@@ -80,6 +80,7 @@ module Jekyll
80
80
  end
81
81
 
82
82
  private
83
+
83
84
  def fallback_data
84
85
  {}
85
86
  end
@@ -35,6 +35,7 @@ module Jekyll
35
35
  next true if symlink?(e)
36
36
  # Do not reject this entry if it is included.
37
37
  next false if included?(e)
38
+
38
39
  # Reject this entry if it is special, a backup file, or excluded.
39
40
  special?(e) || backup?(e) || excluded?(e)
40
41
  end
@@ -55,7 +56,7 @@ module Jekyll
55
56
  end
56
57
 
57
58
  def excluded?(entry)
58
- glob_include?(site.exclude, relative_to_source(entry)).tap do |excluded|
59
+ glob_include?(site.exclude - site.include, relative_to_source(entry)).tap do |excluded|
59
60
  if excluded
60
61
  Jekyll.logger.debug(
61
62
  "EntryFilter:",
@@ -8,10 +8,11 @@ module Jekyll
8
8
  attr_accessor :content, :ext
9
9
  attr_writer :output
10
10
 
11
- def_delegators :@doc, :site, :name, :ext, :extname,
12
- :collection, :related_posts,
13
- :coffeescript_file?, :yaml_file?,
14
- :url, :next_doc, :previous_doc
11
+ def_delegators :@doc,
12
+ :site, :name, :ext, :extname,
13
+ :collection, :related_posts,
14
+ :coffeescript_file?, :yaml_file?,
15
+ :url, :next_doc, :previous_doc
15
16
 
16
17
  private :coffeescript_file?, :yaml_file?
17
18
 
@@ -48,14 +49,14 @@ module Jekyll
48
49
  #
49
50
  # Returns the relative_path for the doc this excerpt belongs to with #excerpt appended
50
51
  def relative_path
51
- File.join(doc.relative_path, "#excerpt")
52
+ @relative_path ||= File.join(doc.relative_path, "#excerpt")
52
53
  end
53
54
 
54
55
  # Check if excerpt includes a string
55
56
  #
56
57
  # Returns true if the string passed in
57
58
  def include?(something)
58
- (output && output.include?(something)) || content.include?(something)
59
+ (output&.include?(something)) || content.include?(something)
59
60
  end
60
61
 
61
62
  # The UID for this doc (useful in feeds).
@@ -76,7 +77,7 @@ module Jekyll
76
77
 
77
78
  # Returns the shorthand String identifier of this doc.
78
79
  def inspect
79
- "<Excerpt: #{self.id}>"
80
+ "<#{self.class} id=#{id}>"
80
81
  end
81
82
 
82
83
  def output
@@ -88,6 +89,8 @@ module Jekyll
88
89
  end
89
90
 
90
91
  def render_with_liquid?
92
+ return false if data["render_with_liquid"] == false
93
+
91
94
  !(coffeescript_file? || yaml_file? || !Utils.has_liquid_construct?(content))
92
95
  end
93
96
 
@@ -128,36 +131,47 @@ module Jekyll
128
131
  #
129
132
  # Returns excerpt String
130
133
 
131
- LIQUID_TAG_REGEX = %r!{%-?\s*(\w+)\s*.*?-?%}!m
132
- MKDWN_LINK_REF_REGEX = %r!^ {0,3}\[[^\]]+\]:.+$!
134
+ LIQUID_TAG_REGEX = %r!{%-?\s*(\w+)\s*.*?-?%}!m.freeze
135
+ MKDWN_LINK_REF_REGEX = %r!^ {0,3}(?:(\[[^\]]+\])(:.+))$!.freeze
133
136
 
134
137
  def extract_excerpt(doc_content)
135
138
  head, _, tail = doc_content.to_s.partition(doc.excerpt_separator)
139
+ return head if tail.empty?
136
140
 
137
- # append appropriate closing tag(s) (for each Liquid block), to the `head`
138
- # if the partitioning resulted in leaving the closing tag somewhere
139
- # in the `tail` partition.
141
+ head = sanctify_liquid_tags(head) if head.include?("{%")
142
+ definitions = extract_markdown_link_reference_defintions(head, tail)
143
+ return head if definitions.empty?
140
144
 
141
- if head.include?("{%")
142
- modified = false
143
- tag_names = head.scan(LIQUID_TAG_REGEX)
144
- tag_names.flatten!
145
- tag_names.reverse_each do |tag_name|
146
- next unless liquid_block?(tag_name)
147
- next if head =~ endtag_regex_stash(tag_name)
145
+ head << "\n\n" << definitions.join("\n")
146
+ end
148
147
 
149
- modified = true
150
- head << "\n{% end#{tag_name} %}"
151
- end
152
- print_build_warning if modified
153
- end
148
+ private
154
149
 
155
- return head if tail.empty?
150
+ # append appropriate closing tag(s) (for each Liquid block), to the `head` if the
151
+ # partitioning resulted in leaving the closing tag somewhere in the `tail` partition.
152
+ def sanctify_liquid_tags(head)
153
+ modified = false
154
+ tag_names = head.scan(LIQUID_TAG_REGEX)
155
+ tag_names.flatten!
156
+ tag_names.reverse_each do |tag_name|
157
+ next unless liquid_block?(tag_name)
158
+ next if head =~ endtag_regex_stash(tag_name)
159
+
160
+ modified = true
161
+ head << "\n{% end#{tag_name} %}"
162
+ end
156
163
 
157
- head << "\n\n" << tail.scan(MKDWN_LINK_REF_REGEX).join("\n")
164
+ print_build_warning if modified
165
+ head
158
166
  end
159
167
 
160
- private
168
+ def extract_markdown_link_reference_defintions(head, tail)
169
+ [].tap do |definitions|
170
+ tail.scan(MKDWN_LINK_REF_REGEX).each do |segments|
171
+ definitions << segments.join if head.include?(segments[0])
172
+ end
173
+ end
174
+ end
161
175
 
162
176
  def endtag_regex_stash(tag_name)
163
177
  @endtag_regex_stash ||= {}
@@ -171,8 +185,7 @@ module Jekyll
171
185
  Liquid::Template.tags[tag_name].ancestors.include?(Liquid::Block)
172
186
  rescue NoMethodError
173
187
  Jekyll.logger.error "Error:",
174
- "A Liquid tag in the excerpt of #{doc.relative_path} couldn't be " \
175
- "parsed."
188
+ "A Liquid tag in the excerpt of #{doc.relative_path} couldn't be parsed."
176
189
  raise
177
190
  end
178
191
 
@@ -180,11 +193,9 @@ module Jekyll
180
193
  Jekyll.logger.warn "Warning:", "Excerpt modified in #{doc.relative_path}!"
181
194
  Jekyll.logger.warn "", "Found a Liquid block containing the excerpt separator" \
182
195
  " #{doc.excerpt_separator.inspect}. "
183
- Jekyll.logger.warn "", "The block has been modified with the appropriate" \
184
- " closing tag."
185
- Jekyll.logger.warn "", "Feel free to define a custom excerpt or" \
186
- " excerpt_separator in the document's front matter" \
187
- " if the generated excerpt is unsatisfactory."
196
+ Jekyll.logger.warn "", "The block has been modified with the appropriate closing tag."
197
+ Jekyll.logger.warn "", "Feel free to define a custom excerpt or excerpt_separator in the"
198
+ Jekyll.logger.warn "", "document's Front Matter if the generated excerpt is unsatisfactory."
188
199
  end
189
200
  end
190
201
  end
@@ -9,6 +9,7 @@ module Jekyll
9
9
  #
10
10
  def blessed_gems
11
11
  %w(
12
+ jekyll-compose
12
13
  jekyll-docs
13
14
  jekyll-import
14
15
  )
@@ -41,6 +42,7 @@ module Jekyll
41
42
  # RubyGems.
42
43
  def version_constraint(gem_name)
43
44
  return "= #{Jekyll::VERSION}" if gem_name.to_s.eql?("jekyll-docs")
45
+
44
46
  "> 0"
45
47
  end
46
48
 
@@ -57,13 +59,16 @@ module Jekyll
57
59
  Jekyll.logger.debug "Requiring:", name.to_s
58
60
  require name
59
61
  rescue LoadError => e
60
- Jekyll.logger.error "Dependency Error:", <<-MSG
61
- Yikes! It looks like you don't have #{name} or one of its dependencies installed.
62
- In order to use Jekyll as currently configured, you'll need to install this gem.
62
+ Jekyll.logger.error "Dependency Error:", <<~MSG
63
+ Yikes! It looks like you don't have #{name} or one of its dependencies installed.
64
+ In order to use Jekyll as currently configured, you'll need to install this gem.
65
+
66
+ If you've run Jekyll with `bundle exec`, ensure that you have included the #{name}
67
+ gem in your Gemfile as well.
63
68
 
64
- The full error message from Ruby is: '#{e.message}'
69
+ The full error message from Ruby is: '#{e.message}'
65
70
 
66
- If you run into trouble, you can find helpful resources at https://jekyllrb.com/help/!
71
+ If you run into trouble, you can find helpful resources at https://jekyllrb.com/help/!
67
72
  MSG
68
73
  raise Jekyll::Errors::MissingDependencyException, name
69
74
  end
@@ -45,6 +45,7 @@ module Jekyll
45
45
  # Returns the formatted String.
46
46
  def date_to_xmlschema(date)
47
47
  return date if date.to_s.empty?
48
+
48
49
  time(date).xmlschema
49
50
  end
50
51
 
@@ -60,10 +61,12 @@ module Jekyll
60
61
  # Returns the formatted String.
61
62
  def date_to_rfc822(date)
62
63
  return date if date.to_s.empty?
64
+
63
65
  time(date).rfc822
64
66
  end
65
67
 
66
68
  private
69
+
67
70
  # month_type: Notations that evaluate to 'Month' via `Time#strftime` ("%b", "%B")
68
71
  # type: nil (default) or "ordinal"
69
72
  # style: nil (default) or "US"
@@ -71,17 +74,18 @@ module Jekyll
71
74
  # Returns a stringified date or the empty input.
72
75
  def stringify_date(date, month_type, type = nil, style = nil)
73
76
  return date if date.to_s.empty?
77
+
74
78
  time = time(date)
75
79
  if type == "ordinal"
76
80
  day = time.day
77
81
  ordinal_day = "#{day}#{ordinal(day)}"
78
82
  return time.strftime("#{month_type} #{ordinal_day}, %Y") if style == "US"
83
+
79
84
  return time.strftime("#{ordinal_day} #{month_type} %Y")
80
85
  end
81
86
  time.strftime("%d #{month_type} %Y")
82
87
  end
83
88
 
84
- private
85
89
  def ordinal(number)
86
90
  return "th" if (11..13).cover?(number)
87
91
 
@@ -93,12 +97,11 @@ module Jekyll
93
97
  end
94
98
  end
95
99
 
96
- private
97
100
  def time(input)
98
101
  date = Liquid::Utils.to_date(input)
99
102
  unless date.respond_to?(:to_time)
100
103
  raise Errors::InvalidDateError,
101
- "Invalid Date: '#{input.inspect}' is not a valid datetime."
104
+ "Invalid Date: '#{input.inspect}' is not a valid datetime."
102
105
  end
103
106
  date.to_time.dup.localtime
104
107
  end
@@ -41,16 +41,15 @@ module Jekyll
41
41
  end
42
42
 
43
43
  private
44
+
44
45
  def parse_expression(str)
45
46
  Liquid::Variable.new(str, Liquid::ParseContext.new)
46
47
  end
47
48
 
48
- private
49
49
  def groupable?(element)
50
50
  element.respond_to?(:group_by)
51
51
  end
52
52
 
53
- private
54
53
  def grouped_array(groups)
55
54
  groups.each_with_object([]) do |item, array|
56
55
  array << {
@@ -10,10 +10,13 @@ module Jekyll
10
10
  # Returns the absolute URL as a String.
11
11
  def absolute_url(input)
12
12
  return if input.nil?
13
+
13
14
  input = input.url if input.respond_to?(:url)
14
15
  return input if Addressable::URI.parse(input.to_s).absolute?
16
+
15
17
  site = @context.registers[:site]
16
18
  return relative_url(input) if site.config["url"].nil?
19
+
17
20
  Addressable::URI.parse(
18
21
  site.config["url"].to_s + relative_url(input)
19
22
  ).normalize.to_s
@@ -27,6 +30,7 @@ module Jekyll
27
30
  # Returns a URL relative to the domain root as a String.
28
31
  def relative_url(input)
29
32
  return if input.nil?
33
+
30
34
  input = input.url if input.respond_to?(:url)
31
35
  return input if Addressable::URI.parse(input.to_s).absolute?
32
36
 
@@ -43,6 +47,7 @@ module Jekyll
43
47
  # Returns a URL with the trailing `/index.html` removed
44
48
  def strip_index(input)
45
49
  return if input.nil? || input.to_s.empty?
50
+
46
51
  input.sub(%r!/index\.html?$!, "/")
47
52
  end
48
53
 
@@ -55,9 +60,9 @@ module Jekyll
55
60
 
56
61
  def ensure_leading_slash(input)
57
62
  return input if input.nil? || input.empty? || input.start_with?("/")
63
+
58
64
  "/#{input}"
59
65
  end
60
-
61
66
  end
62
67
  end
63
68
  end
@@ -169,6 +169,7 @@ module Jekyll
169
169
  def where(input, property, value)
170
170
  return input if property.nil? || value.nil?
171
171
  return input unless input.respond_to?(:select)
172
+
172
173
  input = input.values if input.is_a?(Hash)
173
174
  input_id = input.hash
174
175
 
@@ -195,6 +196,7 @@ module Jekyll
195
196
  # Returns the filtered array of objects
196
197
  def where_exp(input, variable, expression)
197
198
  return input unless input.respond_to?(:select)
199
+
198
200
  input = input.values if input.is_a?(Hash) # FIXME
199
201
 
200
202
  condition = parse_condition(expression)
@@ -214,6 +216,7 @@ module Jekyll
214
216
  def to_integer(input)
215
217
  return 1 if input == true
216
218
  return 0 if input == false
219
+
217
220
  input.to_i
218
221
  end
219
222
 
@@ -225,9 +228,8 @@ module Jekyll
225
228
  #
226
229
  # Returns the filtered array of objects
227
230
  def sort(input, property = nil, nils = "first")
228
- if input.nil?
229
- raise ArgumentError, "Cannot sort a null object."
230
- end
231
+ raise ArgumentError, "Cannot sort a null object." if input.nil?
232
+
231
233
  if property.nil?
232
234
  input.sort
233
235
  else
@@ -246,6 +248,7 @@ module Jekyll
246
248
 
247
249
  def pop(array, num = 1)
248
250
  return array unless array.is_a?(Array)
251
+
249
252
  num = Liquid::Utils.to_integer(num)
250
253
  new_ary = array.dup
251
254
  new_ary.pop(num)
@@ -254,6 +257,7 @@ module Jekyll
254
257
 
255
258
  def push(array, input)
256
259
  return array unless array.is_a?(Array)
260
+
257
261
  new_ary = array.dup
258
262
  new_ary.push(input)
259
263
  new_ary
@@ -261,6 +265,7 @@ module Jekyll
261
265
 
262
266
  def shift(array, num = 1)
263
267
  return array unless array.is_a?(Array)
268
+
264
269
  num = Liquid::Utils.to_integer(num)
265
270
  new_ary = array.dup
266
271
  new_ary.shift(num)
@@ -269,6 +274,7 @@ module Jekyll
269
274
 
270
275
  def unshift(array, input)
271
276
  return array unless array.is_a?(Array)
277
+
272
278
  new_ary = array.dup
273
279
  new_ary.unshift(input)
274
280
  new_ary
@@ -276,6 +282,7 @@ module Jekyll
276
282
 
277
283
  def sample(input, num = 1)
278
284
  return input unless input.respond_to?(:sample)
285
+
279
286
  num = Liquid::Utils.to_integer(num) rescue 1
280
287
  if num == 1
281
288
  input.sample
@@ -301,35 +308,45 @@ module Jekyll
301
308
  # We also utilize the Schwartzian transform to make this more efficient.
302
309
  def sort_input(input, property, order)
303
310
  input.map { |item| [item_property(item, property), item] }
304
- .sort! do |apple_info, orange_info|
305
- apple_property = apple_info.first
306
- orange_property = orange_info.first
311
+ .sort! do |a_info, b_info|
312
+ a_property = a_info.first
313
+ b_property = b_info.first
307
314
 
308
- if !apple_property.nil? && orange_property.nil?
315
+ if !a_property.nil? && b_property.nil?
309
316
  - order
310
- elsif apple_property.nil? && !orange_property.nil?
317
+ elsif a_property.nil? && !b_property.nil?
311
318
  + order
312
319
  else
313
- apple_property <=> orange_property
320
+ a_property <=> b_property || a_property.to_s <=> b_property.to_s
314
321
  end
315
322
  end
316
323
  .map!(&:last)
317
324
  end
318
325
 
319
- private
320
326
  def item_property(item, property)
321
- if item.respond_to?(:to_liquid)
322
- property.to_s.split(".").reduce(item.to_liquid) do |subvalue, attribute|
323
- subvalue[attribute]
327
+ @item_property_cache ||= {}
328
+ @item_property_cache[property] ||= {}
329
+ @item_property_cache[property][item] ||= begin
330
+ if item.respond_to?(:to_liquid)
331
+ property.to_s.split(".").reduce(item.to_liquid) do |subvalue, attribute|
332
+ parse_sort_input(subvalue[attribute])
333
+ end
334
+ elsif item.respond_to?(:data)
335
+ parse_sort_input(item.data[property.to_s])
336
+ else
337
+ parse_sort_input(item[property.to_s])
324
338
  end
325
- elsif item.respond_to?(:data)
326
- item.data[property.to_s]
327
- else
328
- item[property.to_s]
329
339
  end
330
340
  end
331
341
 
332
- private
342
+ # return numeric values as numbers for proper sorting
343
+ def parse_sort_input(property)
344
+ number_like = %r!\A\s*-?(?:\d+\.?\d*|\.\d+)\s*\Z!
345
+ return property.to_f if property =~ number_like
346
+
347
+ property
348
+ end
349
+
333
350
  def as_liquid(item)
334
351
  case item
335
352
  when Hash
@@ -352,25 +369,49 @@ module Jekyll
352
369
  end
353
370
  end
354
371
 
372
+ # ----------- The following set of code was *adapted* from Liquid::If
373
+ # ----------- ref: https://git.io/vp6K6
374
+
355
375
  # Parse a string to a Liquid Condition
356
- private
357
376
  def parse_condition(exp)
358
- parser = Liquid::Parser.new(exp)
359
- left_expr = parser.expression
360
- operator = parser.consume?(:comparison)
361
- condition =
362
- if operator
363
- Liquid::Condition.new(Liquid::Expression.parse(left_expr),
364
- operator,
365
- Liquid::Expression.parse(parser.expression))
366
- else
367
- Liquid::Condition.new(Liquid::Expression.parse(left_expr))
368
- end
369
- parser.consume(:end_of_string)
377
+ parser = Liquid::Parser.new(exp)
378
+ condition = parse_binary_comparison(parser)
370
379
 
380
+ parser.consume(:end_of_string)
371
381
  condition
372
382
  end
373
383
 
384
+ # Generate a Liquid::Condition object from a Liquid::Parser object additionally processing
385
+ # the parsed expression based on whether the expression consists of binary operations with
386
+ # Liquid operators `and` or `or`
387
+ #
388
+ # - parser: an instance of Liquid::Parser
389
+ #
390
+ # Returns an instance of Liquid::Condition
391
+ def parse_binary_comparison(parser)
392
+ parse_comparison(parser).tap do |condition|
393
+ binary_operator = parser.id?("and") || parser.id?("or")
394
+ condition.send(binary_operator, parse_comparison(parser)) if binary_operator
395
+ end
396
+ end
397
+
398
+ # Generates a Liquid::Condition object from a Liquid::Parser object based on whether the parsed
399
+ # expression involves a "comparison" operator (e.g. <, ==, >, !=, etc)
400
+ #
401
+ # - parser: an instance of Liquid::Parser
402
+ #
403
+ # Returns an instance of Liquid::Condition
404
+ def parse_comparison(parser)
405
+ left_operand = Liquid::Expression.parse(parser.expression)
406
+ operator = parser.consume?(:comparison)
407
+
408
+ # No comparison-operator detected. Initialize a Liquid::Condition using only left operand
409
+ return Liquid::Condition.new(left_operand) unless operator
410
+
411
+ # Parse what remained after extracting the left operand and the `:comparison` operator
412
+ # and initialize a Liquid::Condition object using the operands and the comparison-operator
413
+ Liquid::Condition.new(left_operand, operator, Liquid::Expression.parse(parser.expression))
414
+ end
374
415
  end
375
416
  end
376
417
 
@@ -10,6 +10,11 @@ module Jekyll
10
10
  # Initializes a new instance.
11
11
  def initialize(site)
12
12
  @site = site
13
+ reset
14
+ end
15
+
16
+ def reset
17
+ @glob_cache = {}
13
18
  end
14
19
 
15
20
  def update_deprecated_types(set)
@@ -36,6 +41,7 @@ module Jekyll
36
41
  def ensure_time!(set)
37
42
  return set unless set.key?("values") && set["values"].key?("date")
38
43
  return set if set["values"]["date"].is_a?(Time)
44
+
39
45
  set["values"]["date"] = Utils.parse_date(
40
46
  set["values"]["date"],
41
47
  "An invalid date format was found in a front-matter default set: #{set}"
@@ -92,39 +98,44 @@ module Jekyll
92
98
  # path - the path to check for
93
99
  # type - the type (:post, :page or :draft) to check for
94
100
  #
95
- # Returns true if the scope applies to the given path and type
101
+ # Returns true if the scope applies to the given type and path
96
102
  def applies?(scope, path, type)
97
- applies_path?(scope, path) && applies_type?(scope, type)
103
+ applies_type?(scope, type) && applies_path?(scope, path)
98
104
  end
99
105
 
100
- # rubocop:disable Metrics/AbcSize
101
106
  def applies_path?(scope, path)
102
107
  return true if !scope.key?("path") || scope["path"].empty?
103
108
 
104
109
  sanitized_path = Pathname.new(sanitize_path(path))
105
- site_path = Pathname.new(@site.source)
106
110
  rel_scope_path = Pathname.new(scope["path"])
107
- abs_scope_path = File.join(@site.source, rel_scope_path)
108
111
 
109
112
  if scope["path"].to_s.include?("*")
110
- Dir.glob(abs_scope_path).each do |scope_path|
111
- scope_path = Pathname.new(scope_path).relative_path_from(site_path)
112
- scope_path = strip_collections_dir(scope_path)
113
- Jekyll.logger.debug "Globbed Scope Path:", scope_path
114
- return true if path_is_subpath?(sanitized_path, scope_path)
115
- end
116
- false
113
+ glob_scope(sanitized_path, rel_scope_path)
117
114
  else
118
115
  path_is_subpath?(sanitized_path, strip_collections_dir(rel_scope_path))
119
116
  end
120
117
  end
121
- # rubocop:enable Metrics/AbcSize
118
+
119
+ def glob_scope(sanitized_path, rel_scope_path)
120
+ site_source = Pathname.new(@site.source)
121
+ abs_scope_path = site_source.join(rel_scope_path).to_s
122
+
123
+ glob_cache(abs_scope_path).each do |scope_path|
124
+ scope_path = Pathname.new(scope_path).relative_path_from(site_source)
125
+ scope_path = strip_collections_dir(scope_path)
126
+ Jekyll.logger.debug "Globbed Scope Path:", scope_path
127
+ return true if path_is_subpath?(sanitized_path, scope_path)
128
+ end
129
+ false
130
+ end
131
+
132
+ def glob_cache(path)
133
+ @glob_cache[path] ||= Dir.glob(path)
134
+ end
122
135
 
123
136
  def path_is_subpath?(path, parent_path)
124
137
  path.ascend do |ascended_path|
125
- if ascended_path.to_s == parent_path.to_s
126
- return true
127
- end
138
+ return true if ascended_path.to_s == parent_path.to_s
128
139
  end
129
140
 
130
141
  false
@@ -134,6 +145,7 @@ module Jekyll
134
145
  collections_dir = @site.config["collections_dir"]
135
146
  slashed_coll_dir = "#{collections_dir}/"
136
147
  return path if collections_dir.empty? || !path.to_s.start_with?(slashed_coll_dir)
148
+
137
149
  path.sub(slashed_coll_dir, "")
138
150
  end
139
151
 
@@ -188,8 +200,12 @@ module Jekyll
188
200
  #
189
201
  # Returns an array of hashes
190
202
  def matching_sets(path, type)
191
- valid_sets.select do |set|
192
- !set.key?("scope") || applies?(set["scope"], path, type)
203
+ @matched_set_cache ||= {}
204
+ @matched_set_cache[path] ||= {}
205
+ @matched_set_cache[path][type] ||= begin
206
+ valid_sets.select do |set|
207
+ !set.key?("scope") || applies?(set["scope"], path, type)
208
+ end
193
209
  end
194
210
  end
195
211
 
@@ -216,7 +232,7 @@ module Jekyll
216
232
 
217
233
  # Sanitizes the given path by removing a leading and adding a trailing slash
218
234
 
219
- SANITIZATION_REGEX = %r!\A/|(?<=[^/])\z!
235
+ SANITIZATION_REGEX = %r!\A/|(?<=[^/])\z!.freeze
220
236
 
221
237
  def sanitize_path(path)
222
238
  if path.nil? || path.empty?