jekyll 3.8.7 → 4.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (104) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +71 -62
  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.rb +10 -1
  11. data/lib/jekyll/cache.rb +190 -0
  12. data/lib/jekyll/cleaner.rb +5 -4
  13. data/lib/jekyll/collection.rb +82 -10
  14. data/lib/jekyll/command.rb +33 -6
  15. data/lib/jekyll/commands/build.rb +11 -20
  16. data/lib/jekyll/commands/clean.rb +2 -0
  17. data/lib/jekyll/commands/doctor.rb +15 -8
  18. data/lib/jekyll/commands/help.rb +1 -1
  19. data/lib/jekyll/commands/new.rb +37 -35
  20. data/lib/jekyll/commands/new_theme.rb +30 -28
  21. data/lib/jekyll/commands/serve.rb +55 -81
  22. data/lib/jekyll/commands/serve/live_reload_reactor.rb +6 -10
  23. data/lib/jekyll/commands/serve/servlet.rb +22 -25
  24. data/lib/jekyll/commands/serve/websockets.rb +1 -1
  25. data/lib/jekyll/configuration.rb +61 -149
  26. data/lib/jekyll/converters/identity.rb +18 -0
  27. data/lib/jekyll/converters/markdown.rb +49 -40
  28. data/lib/jekyll/converters/markdown/kramdown_parser.rb +84 -11
  29. data/lib/jekyll/converters/smartypants.rb +34 -14
  30. data/lib/jekyll/convertible.rb +30 -31
  31. data/lib/jekyll/deprecator.rb +1 -3
  32. data/lib/jekyll/document.rb +89 -61
  33. data/lib/jekyll/drops/collection_drop.rb +2 -3
  34. data/lib/jekyll/drops/document_drop.rb +14 -1
  35. data/lib/jekyll/drops/drop.rb +17 -14
  36. data/lib/jekyll/drops/excerpt_drop.rb +4 -0
  37. data/lib/jekyll/drops/page_drop.rb +18 -0
  38. data/lib/jekyll/drops/site_drop.rb +6 -5
  39. data/lib/jekyll/drops/unified_payload_drop.rb +1 -0
  40. data/lib/jekyll/drops/url_drop.rb +53 -1
  41. data/lib/jekyll/entry_filter.rb +42 -45
  42. data/lib/jekyll/excerpt.rb +45 -34
  43. data/lib/jekyll/external.rb +10 -5
  44. data/lib/jekyll/filters.rb +200 -40
  45. data/lib/jekyll/filters/date_filters.rb +6 -3
  46. data/lib/jekyll/filters/grouping_filters.rb +1 -2
  47. data/lib/jekyll/filters/url_filters.rb +46 -14
  48. data/lib/jekyll/frontmatter_defaults.rb +46 -35
  49. data/lib/jekyll/hooks.rb +4 -8
  50. data/lib/jekyll/inclusion.rb +32 -0
  51. data/lib/jekyll/liquid_extensions.rb +0 -2
  52. data/lib/jekyll/liquid_renderer.rb +31 -16
  53. data/lib/jekyll/liquid_renderer/file.rb +24 -3
  54. data/lib/jekyll/liquid_renderer/table.rb +36 -77
  55. data/lib/jekyll/log_adapter.rb +5 -1
  56. data/lib/jekyll/mime.types +53 -11
  57. data/lib/jekyll/page.rb +54 -12
  58. data/lib/jekyll/page_excerpt.rb +26 -0
  59. data/lib/jekyll/page_without_a_file.rb +0 -4
  60. data/lib/jekyll/path_manager.rb +31 -0
  61. data/lib/jekyll/plugin.rb +5 -11
  62. data/lib/jekyll/plugin_manager.rb +2 -0
  63. data/lib/jekyll/profiler.rb +58 -0
  64. data/lib/jekyll/reader.rb +42 -9
  65. data/lib/jekyll/readers/collection_reader.rb +1 -0
  66. data/lib/jekyll/readers/data_reader.rb +8 -9
  67. data/lib/jekyll/readers/layout_reader.rb +3 -12
  68. data/lib/jekyll/readers/page_reader.rb +5 -5
  69. data/lib/jekyll/readers/post_reader.rb +31 -18
  70. data/lib/jekyll/readers/static_file_reader.rb +4 -4
  71. data/lib/jekyll/readers/theme_assets_reader.rb +8 -5
  72. data/lib/jekyll/regenerator.rb +4 -12
  73. data/lib/jekyll/renderer.rb +23 -40
  74. data/lib/jekyll/site.rb +91 -38
  75. data/lib/jekyll/static_file.rb +62 -21
  76. data/lib/jekyll/stevenson.rb +2 -3
  77. data/lib/jekyll/tags/highlight.rb +19 -51
  78. data/lib/jekyll/tags/include.rb +82 -42
  79. data/lib/jekyll/tags/link.rb +11 -7
  80. data/lib/jekyll/tags/post_url.rb +25 -21
  81. data/lib/jekyll/theme.rb +16 -18
  82. data/lib/jekyll/theme_builder.rb +91 -89
  83. data/lib/jekyll/url.rb +10 -5
  84. data/lib/jekyll/utils.rb +18 -21
  85. data/lib/jekyll/utils/ansi.rb +1 -1
  86. data/lib/jekyll/utils/exec.rb +0 -1
  87. data/lib/jekyll/utils/internet.rb +2 -4
  88. data/lib/jekyll/utils/platforms.rb +8 -8
  89. data/lib/jekyll/utils/thread_event.rb +1 -5
  90. data/lib/jekyll/utils/win_tz.rb +2 -2
  91. data/lib/jekyll/version.rb +1 -1
  92. data/lib/site_template/.gitignore +2 -0
  93. data/lib/site_template/404.html +1 -0
  94. data/lib/site_template/_config.yml +17 -5
  95. data/lib/site_template/_posts/0000-00-00-welcome-to-jekyll.markdown.erb +5 -1
  96. data/lib/site_template/{about.md → about.markdown} +0 -0
  97. data/lib/site_template/{index.md → index.markdown} +0 -0
  98. data/lib/theme_template/gitignore.erb +1 -0
  99. data/lib/theme_template/theme.gemspec.erb +1 -4
  100. data/rubocop/jekyll/assert_equal_literal_actual.rb +149 -0
  101. metadata +69 -31
  102. data/lib/jekyll/converters/markdown/rdiscount_parser.rb +0 -37
  103. data/lib/jekyll/converters/markdown/redcarpet_parser.rb +0 -112
  104. data/lib/jekyll/utils/rouge.rb +0 -22
@@ -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
@@ -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
- input.split.length
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
@@ -161,14 +173,17 @@ module Jekyll
161
173
 
162
174
  # Filter an array of objects
163
175
  #
164
- # input - the object array
165
- # property - property within each object to filter by
166
- # value - desired value
176
+ # input - the object array.
177
+ # property - the property within each object to filter by.
178
+ # value - the desired value.
179
+ # Cannot be an instance of Array nor Hash since calling #to_s on them returns
180
+ # their `#inspect` string object.
167
181
  #
168
182
  # Returns the filtered array of objects
169
183
  def where(input, property, value)
170
- return input if property.nil? || value.nil?
184
+ return input if !property || value.is_a?(Array) || value.is_a?(Hash)
171
185
  return input unless input.respond_to?(:select)
186
+
172
187
  input = input.values if input.is_a?(Hash)
173
188
  input_id = input.hash
174
189
 
@@ -181,8 +196,8 @@ module Jekyll
181
196
  # stash or retrive results to return
182
197
  @where_filter_cache[input_id][property][value] ||= begin
183
198
  input.select do |object|
184
- Array(item_property(object, property)).map!(&:to_s).include?(value.to_s)
185
- end || []
199
+ compare_property_vs_target(item_property(object, property), value)
200
+ end.to_a
186
201
  end
187
202
  end
188
203
 
@@ -195,6 +210,7 @@ module Jekyll
195
210
  # Returns the filtered array of objects
196
211
  def where_exp(input, variable, expression)
197
212
  return input unless input.respond_to?(:select)
213
+
198
214
  input = input.values if input.is_a?(Hash) # FIXME
199
215
 
200
216
  condition = parse_condition(expression)
@@ -206,6 +222,66 @@ module Jekyll
206
222
  end || []
207
223
  end
208
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
+
209
285
  # Convert the input into integer
210
286
  #
211
287
  # input - the object string
@@ -214,6 +290,7 @@ module Jekyll
214
290
  def to_integer(input)
215
291
  return 1 if input == true
216
292
  return 0 if input == false
293
+
217
294
  input.to_i
218
295
  end
219
296
 
@@ -225,9 +302,8 @@ module Jekyll
225
302
  #
226
303
  # Returns the filtered array of objects
227
304
  def sort(input, property = nil, nils = "first")
228
- if input.nil?
229
- raise ArgumentError, "Cannot sort a null object."
230
- end
305
+ raise ArgumentError, "Cannot sort a null object." if input.nil?
306
+
231
307
  if property.nil?
232
308
  input.sort
233
309
  else
@@ -246,6 +322,7 @@ module Jekyll
246
322
 
247
323
  def pop(array, num = 1)
248
324
  return array unless array.is_a?(Array)
325
+
249
326
  num = Liquid::Utils.to_integer(num)
250
327
  new_ary = array.dup
251
328
  new_ary.pop(num)
@@ -254,6 +331,7 @@ module Jekyll
254
331
 
255
332
  def push(array, input)
256
333
  return array unless array.is_a?(Array)
334
+
257
335
  new_ary = array.dup
258
336
  new_ary.push(input)
259
337
  new_ary
@@ -261,6 +339,7 @@ module Jekyll
261
339
 
262
340
  def shift(array, num = 1)
263
341
  return array unless array.is_a?(Array)
342
+
264
343
  num = Liquid::Utils.to_integer(num)
265
344
  new_ary = array.dup
266
345
  new_ary.shift(num)
@@ -269,6 +348,7 @@ module Jekyll
269
348
 
270
349
  def unshift(array, input)
271
350
  return array unless array.is_a?(Array)
351
+
272
352
  new_ary = array.dup
273
353
  new_ary.unshift(input)
274
354
  new_ary
@@ -276,6 +356,7 @@ module Jekyll
276
356
 
277
357
  def sample(input, num = 1)
278
358
  return input unless input.respond_to?(:sample)
359
+
279
360
  num = Liquid::Utils.to_integer(num) rescue 1
280
361
  if num == 1
281
362
  input.sample
@@ -301,35 +382,86 @@ module Jekyll
301
382
  # We also utilize the Schwartzian transform to make this more efficient.
302
383
  def sort_input(input, property, order)
303
384
  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
385
+ .sort! do |a_info, b_info|
386
+ a_property = a_info.first
387
+ b_property = b_info.first
307
388
 
308
- if !apple_property.nil? && orange_property.nil?
389
+ if !a_property.nil? && b_property.nil?
309
390
  - order
310
- elsif apple_property.nil? && !orange_property.nil?
391
+ elsif a_property.nil? && !b_property.nil?
311
392
  + order
312
393
  else
313
- apple_property <=> orange_property
394
+ a_property <=> b_property || a_property.to_s <=> b_property.to_s
314
395
  end
315
396
  end
316
397
  .map!(&:last)
317
398
  end
318
399
 
319
- private
320
- 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]
324
- end
325
- elsif item.respond_to?(:data)
326
- item.data[property.to_s]
400
+ # `where` filter helper
401
+ #
402
+ # rubocop:disable Metrics/CyclomaticComplexity
403
+ # rubocop:disable Metrics/PerceivedComplexity
404
+ def compare_property_vs_target(property, target)
405
+ case target
406
+ when NilClass
407
+ return true if property.nil?
408
+ when Liquid::Expression::MethodLiteral # `empty` or `blank`
409
+ target = target.to_s
410
+ return true if property == target || Array(property).join == target
327
411
  else
328
- item[property.to_s]
412
+ target = target.to_s
413
+ if property.is_a? String
414
+ return true if property == target
415
+ else
416
+ Array(property).each do |prop|
417
+ return true if prop.to_s == target
418
+ end
419
+ end
420
+ end
421
+
422
+ false
423
+ end
424
+ # rubocop:enable Metrics/CyclomaticComplexity
425
+ # rubocop:enable Metrics/PerceivedComplexity
426
+
427
+ def item_property(item, property)
428
+ @item_property_cache ||= {}
429
+ @item_property_cache[property] ||= {}
430
+ @item_property_cache[property][item] ||= begin
431
+ property = property.to_s
432
+ property = if item.respond_to?(:to_liquid)
433
+ read_liquid_attribute(item.to_liquid, property)
434
+ elsif item.respond_to?(:data)
435
+ item.data[property]
436
+ else
437
+ item[property]
438
+ end
439
+
440
+ parse_sort_input(property)
329
441
  end
330
442
  end
331
443
 
332
- private
444
+ def read_liquid_attribute(liquid_data, property)
445
+ return liquid_data[property] unless property.include?(".")
446
+
447
+ property.split(".").reduce(liquid_data) do |data, key|
448
+ data.respond_to?(:[]) && data[key]
449
+ end
450
+ end
451
+
452
+ FLOAT_LIKE = %r!\A\s*-?(?:\d+\.?\d*|\.\d+)\s*\Z!.freeze
453
+ INTEGER_LIKE = %r!\A\s*-?\d+\s*\Z!.freeze
454
+ private_constant :FLOAT_LIKE, :INTEGER_LIKE
455
+
456
+ # return numeric values as numbers for proper sorting
457
+ def parse_sort_input(property)
458
+ stringified = property.to_s
459
+ return property.to_i if INTEGER_LIKE.match?(stringified)
460
+ return property.to_f if FLOAT_LIKE.match?(stringified)
461
+
462
+ property
463
+ end
464
+
333
465
  def as_liquid(item)
334
466
  case item
335
467
  when Hash
@@ -352,25 +484,53 @@ module Jekyll
352
484
  end
353
485
  end
354
486
 
487
+ # ----------- The following set of code was *adapted* from Liquid::If
488
+ # ----------- ref: https://git.io/vp6K6
489
+
355
490
  # Parse a string to a Liquid Condition
356
- private
357
491
  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)
492
+ parser = Liquid::Parser.new(exp)
493
+ condition = parse_binary_comparison(parser)
370
494
 
495
+ parser.consume(:end_of_string)
371
496
  condition
372
497
  end
373
498
 
499
+ # Generate a Liquid::Condition object from a Liquid::Parser object additionally processing
500
+ # the parsed expression based on whether the expression consists of binary operations with
501
+ # Liquid operators `and` or `or`
502
+ #
503
+ # - parser: an instance of Liquid::Parser
504
+ #
505
+ # Returns an instance of Liquid::Condition
506
+ def parse_binary_comparison(parser)
507
+ condition = parse_comparison(parser)
508
+ first_condition = condition
509
+ while (binary_operator = parser.id?("and") || parser.id?("or"))
510
+ child_condition = parse_comparison(parser)
511
+ condition.send(binary_operator, child_condition)
512
+ condition = child_condition
513
+ end
514
+ first_condition
515
+ end
516
+
517
+ # Generates a Liquid::Condition object from a Liquid::Parser object based on whether the parsed
518
+ # expression involves a "comparison" operator (e.g. <, ==, >, !=, etc)
519
+ #
520
+ # - parser: an instance of Liquid::Parser
521
+ #
522
+ # Returns an instance of Liquid::Condition
523
+ def parse_comparison(parser)
524
+ left_operand = Liquid::Expression.parse(parser.expression)
525
+ operator = parser.consume?(:comparison)
526
+
527
+ # No comparison-operator detected. Initialize a Liquid::Condition using only left operand
528
+ return Liquid::Condition.new(left_operand) unless operator
529
+
530
+ # Parse what remained after extracting the left operand and the `:comparison` operator
531
+ # and initialize a Liquid::Condition object using the operands and the comparison-operator
532
+ Liquid::Condition.new(left_operand, operator, Liquid::Expression.parse(parser.expression))
533
+ end
374
534
  end
375
535
  end
376
536
 
@@ -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 << {