jekyll 3.9.5 → 4.0.0.pre.alpha1

Sign up to get free protection for your applications and to get access to all the features.
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 +75 -48
  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?