jekyll 3.8.7 → 4.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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 << {