jekyll 3.9.3 → 4.4.1

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 (108) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +511 -89
  3. data/LICENSE +1 -1
  4. data/README.markdown +48 -27
  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/base.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 +186 -0
  11. data/lib/jekyll/cleaner.rb +8 -7
  12. data/lib/jekyll/collection.rb +84 -11
  13. data/lib/jekyll/command.rb +33 -6
  14. data/lib/jekyll/commands/build.rb +8 -28
  15. data/lib/jekyll/commands/clean.rb +3 -2
  16. data/lib/jekyll/commands/doctor.rb +46 -35
  17. data/lib/jekyll/commands/help.rb +1 -1
  18. data/lib/jekyll/commands/new.rb +44 -50
  19. data/lib/jekyll/commands/new_theme.rb +27 -28
  20. data/lib/jekyll/commands/serve/live_reload_reactor.rb +9 -16
  21. data/lib/jekyll/commands/serve/servlet.rb +21 -22
  22. data/lib/jekyll/commands/serve/websockets.rb +1 -1
  23. data/lib/jekyll/commands/serve.rb +75 -97
  24. data/lib/jekyll/configuration.rb +66 -158
  25. data/lib/jekyll/converters/identity.rb +18 -0
  26. data/lib/jekyll/converters/markdown/kramdown_parser.rb +83 -33
  27. data/lib/jekyll/converters/markdown.rb +49 -40
  28. data/lib/jekyll/converters/smartypants.rb +34 -14
  29. data/lib/jekyll/convertible.rb +36 -34
  30. data/lib/jekyll/deprecator.rb +2 -4
  31. data/lib/jekyll/document.rb +107 -72
  32. data/lib/jekyll/drops/collection_drop.rb +3 -4
  33. data/lib/jekyll/drops/document_drop.rb +9 -3
  34. data/lib/jekyll/drops/drop.rb +115 -33
  35. data/lib/jekyll/drops/excerpt_drop.rb +8 -0
  36. data/lib/jekyll/drops/site_drop.rb +9 -8
  37. data/lib/jekyll/drops/static_file_drop.rb +4 -4
  38. data/lib/jekyll/drops/theme_drop.rb +39 -0
  39. data/lib/jekyll/drops/unified_payload_drop.rb +7 -2
  40. data/lib/jekyll/drops/url_drop.rb +55 -3
  41. data/lib/jekyll/entry_filter.rb +42 -51
  42. data/lib/jekyll/excerpt.rb +48 -38
  43. data/lib/jekyll/external.rb +20 -19
  44. data/lib/jekyll/filters/date_filters.rb +6 -3
  45. data/lib/jekyll/filters/grouping_filters.rb +1 -2
  46. data/lib/jekyll/filters/url_filters.rb +50 -15
  47. data/lib/jekyll/filters.rb +211 -50
  48. data/lib/jekyll/frontmatter_defaults.rb +45 -36
  49. data/lib/jekyll/hooks.rb +26 -26
  50. data/lib/jekyll/inclusion.rb +32 -0
  51. data/lib/jekyll/layout.rb +12 -19
  52. data/lib/jekyll/liquid_extensions.rb +0 -2
  53. data/lib/jekyll/liquid_renderer/file.rb +24 -3
  54. data/lib/jekyll/liquid_renderer/table.rb +26 -77
  55. data/lib/jekyll/liquid_renderer.rb +31 -16
  56. data/lib/jekyll/log_adapter.rb +5 -1
  57. data/lib/jekyll/page.rb +51 -23
  58. data/lib/jekyll/page_excerpt.rb +25 -0
  59. data/lib/jekyll/page_without_a_file.rb +0 -4
  60. data/lib/jekyll/path_manager.rb +74 -0
  61. data/lib/jekyll/plugin.rb +5 -11
  62. data/lib/jekyll/plugin_manager.rb +15 -5
  63. data/lib/jekyll/profiler.rb +51 -0
  64. data/lib/jekyll/reader.rb +65 -10
  65. data/lib/jekyll/readers/collection_reader.rb +1 -0
  66. data/lib/jekyll/readers/data_reader.rb +48 -10
  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 +32 -19
  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/related_posts.rb +1 -1
  74. data/lib/jekyll/renderer.rb +34 -49
  75. data/lib/jekyll/site.rb +151 -58
  76. data/lib/jekyll/static_file.rb +64 -28
  77. data/lib/jekyll/stevenson.rb +4 -8
  78. data/lib/jekyll/tags/highlight.rb +44 -57
  79. data/lib/jekyll/tags/include.rb +114 -80
  80. data/lib/jekyll/tags/link.rb +12 -7
  81. data/lib/jekyll/tags/post_url.rb +33 -30
  82. data/lib/jekyll/theme.rb +20 -18
  83. data/lib/jekyll/theme_builder.rb +91 -89
  84. data/lib/jekyll/url.rb +18 -10
  85. data/lib/jekyll/utils/ansi.rb +2 -2
  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 +37 -52
  89. data/lib/jekyll/utils/thread_event.rb +1 -5
  90. data/lib/jekyll/utils.rb +29 -28
  91. data/lib/jekyll/version.rb +1 -1
  92. data/lib/jekyll.rb +9 -14
  93. data/lib/site_template/.gitignore +2 -0
  94. data/lib/site_template/404.html +2 -1
  95. data/lib/site_template/_config.yml +17 -5
  96. data/lib/site_template/_posts/0000-00-00-welcome-to-jekyll.markdown.erb +5 -1
  97. data/lib/theme_template/README.md.erb +1 -3
  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 +150 -0
  101. data/rubocop/jekyll/no_p_allowed.rb +5 -6
  102. data/rubocop/jekyll/no_puts_allowed.rb +5 -6
  103. metadata +149 -37
  104. data/lib/jekyll/converters/markdown/rdiscount_parser.rb +0 -37
  105. data/lib/jekyll/converters/markdown/redcarpet_parser.rb +0 -112
  106. data/lib/jekyll/utils/rouge.rb +0 -22
  107. /data/lib/site_template/{about.md → about.markdown} +0 -0
  108. /data/lib/site_template/{index.md → index.markdown} +0 -0
@@ -5,14 +5,31 @@ module Jekyll
5
5
  include Comparable
6
6
  extend Forwardable
7
7
 
8
- attr_reader :path, :site, :extname, :collection
8
+ attr_reader :path, :site, :extname, :collection, :type
9
9
  attr_accessor :content, :output
10
10
 
11
11
  def_delegator :self, :read_post_data, :post_read
12
12
 
13
- YAML_FRONT_MATTER_REGEXP = %r!\A(---\s*\n.*?\n?)^((---|\.\.\.)\s*$\n?)!m
14
- DATELESS_FILENAME_MATCHER = %r!^(?:.+/)*(.*)(\.[^.]+)$!
15
- DATE_FILENAME_MATCHER = %r!^(?:.+/)*(\d{2,4}-\d{1,2}-\d{1,2})-(.*)(\.[^.]+)$!
13
+ YAML_FRONT_MATTER_REGEXP = %r!\A(---\s*\n.*?\n?)^((---|\.\.\.)\s*$\n?)!m.freeze
14
+ DATELESS_FILENAME_MATCHER = %r!^(?:.+/)*(.*)(\.[^.]+)$!.freeze
15
+ DATE_FILENAME_MATCHER = %r!^(?>.+/)*?(\d{2,4}-\d{1,2}-\d{1,2})-([^/]*)(\.[^.]+)$!.freeze
16
+
17
+ SASS_FILE_EXTS = %w(.sass .scss).freeze
18
+ YAML_FILE_EXTS = %w(.yaml .yml).freeze
19
+
20
+ #
21
+
22
+ # Class-wide cache to stash and retrieve regexp to detect "super-directories"
23
+ # of a particular Jekyll::Document object.
24
+ #
25
+ # dirname - The *special directory* for the Document.
26
+ # e.g. "_posts" or "_drafts" for Documents from the `site.posts` collection.
27
+ def self.superdirs_regex(dirname)
28
+ @superdirs_regex ||= {}
29
+ @superdirs_regex[dirname] ||= %r!#{dirname}.*!
30
+ end
31
+
32
+ #
16
33
 
17
34
  # Create a new Document.
18
35
  #
@@ -27,6 +44,8 @@ module Jekyll
27
44
  @path = path
28
45
  @extname = File.extname(path)
29
46
  @collection = relations[:collection]
47
+ @type = @collection.label.to_sym
48
+
30
49
  @has_yaml_header = nil
31
50
 
32
51
  if draft?
@@ -36,7 +55,7 @@ module Jekyll
36
55
  end
37
56
 
38
57
  data.default_proc = proc do |_, key|
39
- site.frontmatter_defaults.find(relative_path, collection.label, key)
58
+ site.frontmatter_defaults.find(relative_path, type, key)
40
59
  end
41
60
 
42
61
  trigger_hooks(:post_init)
@@ -60,12 +79,19 @@ module Jekyll
60
79
  data
61
80
  end
62
81
 
82
+ # Returns the document date. If metadata is not present then calculates it
83
+ # based on Jekyll::Site#time or the document file modification time.
84
+ #
85
+ # Return document date string.
63
86
  def date
64
87
  data["date"] ||= (draft? ? source_file_mtime : site.time)
65
88
  end
66
89
 
90
+ # Return document file modification time in the form of a Time object.
91
+ #
92
+ # Return document file modification Time object.
67
93
  def source_file_mtime
68
- @source_file_mtime ||= File.mtime(path)
94
+ File.mtime(path)
69
95
  end
70
96
 
71
97
  # Returns whether the document is a draft. This is only the case if
@@ -90,7 +116,7 @@ module Jekyll
90
116
  #
91
117
  # Returns the output extension
92
118
  def output_ext
93
- @output_ext ||= Jekyll::Renderer.new(site, self).output_ext
119
+ renderer.output_ext
94
120
  end
95
121
 
96
122
  # The base filename of the document, without the file extname.
@@ -107,27 +133,35 @@ module Jekyll
107
133
  @basename ||= File.basename(path)
108
134
  end
109
135
 
136
+ def renderer
137
+ @renderer ||= Jekyll::Renderer.new(site, self)
138
+ end
139
+
110
140
  # Produces a "cleaned" relative path.
111
141
  # The "cleaned" relative path is the relative path without the extname
112
142
  # and with the collection's directory removed as well.
113
143
  # This method is useful when building the URL of the document.
114
144
  #
145
+ # NOTE: `String#gsub` removes all trailing periods (in comparison to `String#chomp`)
146
+ #
115
147
  # Examples:
116
- # When relative_path is "_methods/site/generate.md":
148
+ # When relative_path is "_methods/site/generate...md":
117
149
  # cleaned_relative_path
118
150
  # # => "/site/generate"
119
151
  #
120
152
  # Returns the cleaned relative path of the document.
121
153
  def cleaned_relative_path
122
154
  @cleaned_relative_path ||=
123
- relative_path[0..-extname.length - 1].sub(collection.relative_directory, "")
155
+ relative_path[0..-extname.length - 1]
156
+ .sub(collection.relative_directory, "")
157
+ .gsub(%r!\.*\z!, "")
124
158
  end
125
159
 
126
160
  # Determine whether the document is a YAML file.
127
161
  #
128
162
  # Returns true if the extname is either .yml or .yaml, false otherwise.
129
163
  def yaml_file?
130
- %w(.yaml .yml).include?(extname)
164
+ YAML_FILE_EXTS.include?(extname)
131
165
  end
132
166
 
133
167
  # Determine whether the document is an asset file.
@@ -143,7 +177,7 @@ module Jekyll
143
177
  #
144
178
  # Returns true if extname == .sass or .scss, false otherwise.
145
179
  def sass_file?
146
- %w(.sass .scss).include?(extname)
180
+ SASS_FILE_EXTS.include?(extname)
147
181
  end
148
182
 
149
183
  # Determine whether the document is a CoffeeScript file.
@@ -159,6 +193,8 @@ module Jekyll
159
193
  # or if the document doesn't contain any Liquid Tags or Variables,
160
194
  # true otherwise.
161
195
  def render_with_liquid?
196
+ return false if data["render_with_liquid"] == false
197
+
162
198
  !(coffeescript_file? || yaml_file? || !Utils.has_liquid_construct?(content))
163
199
  end
164
200
 
@@ -204,11 +240,11 @@ module Jekyll
204
240
  #
205
241
  # Returns the computed URL for the document.
206
242
  def url
207
- @url ||= URL.new({
243
+ @url ||= URL.new(
208
244
  :template => url_template,
209
245
  :placeholders => url_placeholders,
210
- :permalink => permalink,
211
- }).to_s
246
+ :permalink => permalink
247
+ ).to_s
212
248
  end
213
249
 
214
250
  def [](key)
@@ -221,14 +257,16 @@ module Jekyll
221
257
  #
222
258
  # Returns the full path to the output file of this document.
223
259
  def destination(base_directory)
224
- dest = site.in_dest_dir(base_directory)
225
- path = site.in_dest_dir(dest, URL.unescape_path(url))
226
- if url.end_with? "/"
227
- path = File.join(path, "index.html")
228
- else
229
- path << output_ext unless path.end_with? output_ext
260
+ @destination ||= {}
261
+ @destination[base_directory] ||= begin
262
+ path = site.in_dest_dir(base_directory, URL.unescape_path(url))
263
+ if url.end_with? "/"
264
+ path = File.join(path, "index.html")
265
+ else
266
+ path << output_ext unless path.end_with? output_ext
267
+ end
268
+ path
230
269
  end
231
- path
232
270
  end
233
271
 
234
272
  # Write the generated Document file to the destination directory.
@@ -286,7 +324,7 @@ module Jekyll
286
324
  #
287
325
  # Returns the inspect string for this document.
288
326
  def inspect
289
- "#<Jekyll::Document #{relative_path} collection=#{collection.label}>"
327
+ "#<#{self.class} #{relative_path} collection=#{collection.label}>"
290
328
  end
291
329
 
292
330
  # The string representation for this document.
@@ -303,6 +341,7 @@ module Jekyll
303
341
  # equal or greater than the other doc's path. See String#<=> for more details.
304
342
  def <=>(other)
305
343
  return nil unless other.respond_to?(:data)
344
+
306
345
  cmp = data["date"] <=> other.data["date"]
307
346
  cmp = path <=> other.path if cmp.nil? || cmp.zero?
308
347
  cmp
@@ -314,16 +353,21 @@ module Jekyll
314
353
  # True if the document has a collection and if that collection's #write?
315
354
  # method returns true, and if the site's Publisher will publish the document.
316
355
  # False otherwise.
356
+ #
357
+ # rubocop:disable Naming/MemoizedInstanceVariableName
317
358
  def write?
318
- collection && collection.write? && site.publisher.publish?(self)
359
+ return @write_p if defined?(@write_p)
360
+
361
+ @write_p = collection&.write? && site.publisher.publish?(self)
319
362
  end
363
+ # rubocop:enable Naming/MemoizedInstanceVariableName
320
364
 
321
365
  # The Document excerpt_separator, from the YAML Front-Matter or site
322
366
  # default excerpt_separator value
323
367
  #
324
368
  # Returns the document excerpt_separator
325
369
  def excerpt_separator
326
- (data["excerpt_separator"] || site.config["excerpt_separator"]).to_s
370
+ @excerpt_separator ||= (data["excerpt_separator"] || site.config["excerpt_separator"]).to_s
327
371
  end
328
372
 
329
373
  # Whether to generate an excerpt
@@ -335,16 +379,12 @@ module Jekyll
335
379
 
336
380
  def next_doc
337
381
  pos = collection.docs.index { |post| post.equal?(self) }
338
- if pos && pos < collection.docs.length - 1
339
- collection.docs[pos + 1]
340
- end
382
+ collection.docs[pos + 1] if pos && pos < collection.docs.length - 1
341
383
  end
342
384
 
343
385
  def previous_doc
344
386
  pos = collection.docs.index { |post| post.equal?(self) }
345
- if pos && pos > 0
346
- collection.docs[pos - 1]
347
- end
387
+ collection.docs[pos - 1] if pos && pos.positive?
348
388
  end
349
389
 
350
390
  def trigger_hooks(hook_name, *args)
@@ -363,18 +403,11 @@ module Jekyll
363
403
  @related_posts ||= Jekyll::RelatedPosts.new(self).build
364
404
  end
365
405
 
366
- # Override of normal respond_to? to match method_missing's logic for
367
- # looking in @data.
368
- def respond_to?(method, include_private = false)
369
- data.key?(method.to_s) || super
370
- end
371
-
372
406
  # Override of method_missing to check in @data for the key.
373
407
  def method_missing(method, *args, &blck)
374
408
  if data.key?(method.to_s)
375
- Jekyll::Deprecator.deprecation_message "Document##{method} is now a key "\
376
- "in the #data hash."
377
- Jekyll::Deprecator.deprecation_message "Called by #{caller(0..0)}."
409
+ Jekyll::Deprecator.deprecation_message "Document##{method} is now a key in the #data hash."
410
+ Jekyll.logger.warn "", "Called by #{caller(1..1)[0]}."
378
411
  data[method.to_s]
379
412
  else
380
413
  super
@@ -391,41 +424,47 @@ module Jekyll
391
424
  #
392
425
  # Returns nothing.
393
426
  def categories_from_path(special_dir)
394
- superdirs = relative_path.sub(%r!#{special_dir}(.*)!, "")
395
- .split(File::SEPARATOR)
396
- .reject do |c|
397
- c.empty? || c == special_dir || c == basename
427
+ if relative_path.start_with?(special_dir)
428
+ superdirs = []
429
+ else
430
+ superdirs = relative_path.sub(Document.superdirs_regex(special_dir), "")
431
+ superdirs = superdirs.split(File::SEPARATOR)
432
+ superdirs.reject! { |c| c.empty? || c == special_dir || c == basename }
398
433
  end
434
+
399
435
  merge_data!({ "categories" => superdirs }, :source => "file path")
400
436
  end
401
437
 
402
438
  def populate_categories
403
- merge_data!({
404
- "categories" => (
405
- Array(data["categories"]) + Utils.pluralized_array_from_hash(
406
- data, "category", "categories"
407
- )
408
- ).map(&:to_s).flatten.uniq,
409
- })
439
+ categories = Array(data["categories"]) + Utils.pluralized_array_from_hash(
440
+ data, "category", "categories"
441
+ )
442
+ categories.map!(&:to_s)
443
+ categories.flatten!
444
+ categories.uniq!
445
+
446
+ merge_data!({ "categories" => categories })
410
447
  end
411
448
 
412
449
  def populate_tags
413
- merge_data!({
414
- "tags" => Utils.pluralized_array_from_hash(data, "tag", "tags").flatten,
415
- })
450
+ tags = Utils.pluralized_array_from_hash(data, "tag", "tags")
451
+ tags.flatten!
452
+
453
+ merge_data!({ "tags" => tags })
416
454
  end
417
455
 
418
456
  private
457
+
419
458
  def merge_categories!(other)
420
459
  if other.key?("categories") && !other["categories"].nil?
421
- if other["categories"].is_a?(String)
422
- other["categories"] = other["categories"].split
460
+ other["categories"] = other["categories"].split if other["categories"].is_a?(String)
461
+
462
+ if data["categories"].is_a?(Array)
463
+ other["categories"] = data["categories"] | other["categories"]
423
464
  end
424
- other["categories"] = (data["categories"] || []) | other["categories"]
425
465
  end
426
466
  end
427
467
 
428
- private
429
468
  def merge_date!(source)
430
469
  if data.key?("date")
431
470
  data["date"] = Utils.parse_date(
@@ -435,26 +474,20 @@ module Jekyll
435
474
  end
436
475
  end
437
476
 
438
- private
439
477
  def merge_defaults
440
- defaults = @site.frontmatter_defaults.all(
441
- relative_path,
442
- collection.label.to_sym
443
- )
478
+ defaults = @site.frontmatter_defaults.all(relative_path, type)
444
479
  merge_data!(defaults, :source => "front matter defaults") unless defaults.empty?
445
480
  end
446
481
 
447
- private
448
482
  def read_content(**opts)
449
483
  self.content = File.read(path, **Utils.merged_file_read_opts(site, opts))
450
484
  if content =~ YAML_FRONT_MATTER_REGEXP
451
- self.content = $POSTMATCH
485
+ self.content = Regexp.last_match.post_match
452
486
  data_file = SafeYAML.load(Regexp.last_match(1))
453
487
  merge_data!(data_file, :source => "YAML front matter") if data_file
454
488
  end
455
489
  end
456
490
 
457
- private
458
491
  def read_post_data
459
492
  populate_title
460
493
  populate_categories
@@ -462,7 +495,6 @@ module Jekyll
462
495
  generate_excerpt
463
496
  end
464
497
 
465
- private
466
498
  def handle_read_error(error)
467
499
  if error.is_a? Psych::SyntaxError
468
500
  Jekyll.logger.error "Error:", "YAML Exception reading #{path}: #{error.message}"
@@ -475,7 +507,6 @@ module Jekyll
475
507
  end
476
508
  end
477
509
 
478
- private
479
510
  def populate_title
480
511
  if relative_path =~ DATE_FILENAME_MATCHER
481
512
  date, slug, ext = Regexp.last_match.captures
@@ -483,6 +514,14 @@ module Jekyll
483
514
  elsif relative_path =~ DATELESS_FILENAME_MATCHER
484
515
  slug, ext = Regexp.last_match.captures
485
516
  end
517
+ # `slug` will be nil for documents without an extension since the regex patterns
518
+ # above tests for an extension as well.
519
+ # In such cases, assign `basename_without_ext` as the slug.
520
+ slug ||= basename_without_ext
521
+
522
+ # slugs shouldn't end with a period
523
+ # `String#gsub!` removes all trailing periods (in comparison to `String#chomp!`)
524
+ slug.gsub!(%r!\.*\z!, "")
486
525
 
487
526
  # Try to ensure the user gets a title.
488
527
  data["title"] ||= Utils.titleize_slug(slug)
@@ -491,18 +530,14 @@ module Jekyll
491
530
  data["ext"] ||= ext
492
531
  end
493
532
 
494
- private
495
533
  def modify_date(date)
496
534
  if !data["date"] || data["date"].to_i == site.time.to_i
497
535
  merge_data!({ "date" => date }, :source => "filename")
498
536
  end
499
537
  end
500
538
 
501
- private
502
539
  def generate_excerpt
503
- if generate_excerpt?
504
- data["excerpt"] ||= Jekyll::Excerpt.new(self)
505
- end
540
+ data["excerpt"] ||= Jekyll::Excerpt.new(self) if generate_excerpt?
506
541
  end
507
542
  end
508
543
  end
@@ -7,11 +7,10 @@ module Jekyll
7
7
 
8
8
  mutable false
9
9
 
10
- def_delegator :@obj, :write?, :output
11
- def_delegators :@obj, :label, :docs, :files, :directory,
12
- :relative_directory
10
+ delegate_method_as :write?, :output
11
+ delegate_methods :label, :docs, :files, :directory, :relative_directory
13
12
 
14
- private def_delegator :@obj, :metadata, :fallback_data
13
+ private delegate_method_as :metadata, :fallback_data
15
14
 
16
15
  def to_s
17
16
  docs.to_s
@@ -11,10 +11,11 @@ module Jekyll
11
11
 
12
12
  mutable false
13
13
 
14
- def_delegator :@obj, :relative_path, :path
15
- def_delegators :@obj, :id, :output, :content, :to_s, :relative_path, :url
14
+ delegate_method_as :relative_path, :path
15
+ private delegate_method_as :data, :fallback_data
16
16
 
17
- private def_delegator :@obj, :data, :fallback_data
17
+ delegate_methods :id, :output, :content, :to_s, :relative_path, :url, :date
18
+ data_delegators "title", "categories", "tags"
18
19
 
19
20
  def collection
20
21
  @obj.collection.label
@@ -24,8 +25,13 @@ module Jekyll
24
25
  fallback_data["excerpt"].to_s
25
26
  end
26
27
 
28
+ def name
29
+ fallback_data["name"] || @obj.basename
30
+ end
31
+
27
32
  def <=>(other)
28
33
  return nil unless other.is_a? DocumentDrop
34
+
29
35
  cmp = self["date"] <=> other["date"]
30
36
  cmp = self["path"] <=> other["path"] if cmp.nil? || cmp.zero?
31
37
  cmp
@@ -6,24 +6,101 @@ module Jekyll
6
6
  include Enumerable
7
7
 
8
8
  NON_CONTENT_METHODS = [:fallback_data, :collapse_document].freeze
9
+ NON_CONTENT_METHOD_NAMES = NON_CONTENT_METHODS.map(&:to_s).freeze
10
+ private_constant :NON_CONTENT_METHOD_NAMES
9
11
 
10
- # Get or set whether the drop class is mutable.
11
- # Mutability determines whether or not pre-defined fields may be
12
- # overwritten.
13
- #
14
- # is_mutable - Boolean set mutability of the class (default: nil)
15
- #
16
- # Returns the mutability of the class
17
- def self.mutable(is_mutable = nil)
18
- @is_mutable = if is_mutable
19
- is_mutable
20
- else
21
- false
22
- end
23
- end
12
+ # A private stash to avoid repeatedly generating the setter method name string for
13
+ # a call to `Drops::Drop#[]=`.
14
+ # The keys of the stash below have a very high probability of being called upon during
15
+ # the course of various `Jekyll::Renderer#run` calls.
16
+ SETTER_KEYS_STASH = {
17
+ "content" => "content=",
18
+ "layout" => "layout=",
19
+ "page" => "page=",
20
+ "paginator" => "paginator=",
21
+ "highlighter_prefix" => "highlighter_prefix=",
22
+ "highlighter_suffix" => "highlighter_suffix=",
23
+ }.freeze
24
+ private_constant :SETTER_KEYS_STASH
25
+
26
+ class << self
27
+ # Get or set whether the drop class is mutable.
28
+ # Mutability determines whether or not pre-defined fields may be
29
+ # overwritten.
30
+ #
31
+ # is_mutable - Boolean set mutability of the class (default: nil)
32
+ #
33
+ # Returns the mutability of the class
34
+ def mutable(is_mutable = nil)
35
+ @is_mutable = is_mutable || false
36
+ end
37
+
38
+ def mutable?
39
+ @is_mutable
40
+ end
41
+
42
+ # public delegation helper methods that calls onto Drop's instance
43
+ # variable `@obj`.
24
44
 
25
- def self.mutable?
26
- @is_mutable
45
+ # Generate private Drop instance_methods for each symbol in the given list.
46
+ #
47
+ # Returns nothing.
48
+ def private_delegate_methods(*symbols)
49
+ symbols.each { |symbol| private delegate_method(symbol) }
50
+ nil
51
+ end
52
+
53
+ # Generate public Drop instance_methods for each symbol in the given list.
54
+ #
55
+ # Returns nothing.
56
+ def delegate_methods(*symbols)
57
+ symbols.each { |symbol| delegate_method(symbol) }
58
+ nil
59
+ end
60
+
61
+ # Generate public Drop instance_method for given symbol that calls `@obj.<sym>`.
62
+ #
63
+ # Returns delegated method symbol.
64
+ def delegate_method(symbol)
65
+ define_method(symbol) { @obj.send(symbol) }
66
+ end
67
+
68
+ # Generate public Drop instance_method named `delegate` that calls `@obj.<original>`.
69
+ #
70
+ # Returns delegated method symbol.
71
+ def delegate_method_as(original, delegate)
72
+ define_method(delegate) { @obj.send(original) }
73
+ end
74
+
75
+ # Generate public Drop instance_methods for each string entry in the given list.
76
+ # The generated method(s) access(es) `@obj`'s data hash.
77
+ #
78
+ # Returns nothing.
79
+ def data_delegators(*strings)
80
+ strings.each do |key|
81
+ data_delegator(key) if key.is_a?(String)
82
+ end
83
+ nil
84
+ end
85
+
86
+ # Generate public Drop instance_methods for given string `key`.
87
+ # The generated method access(es) `@obj`'s data hash.
88
+ #
89
+ # Returns method symbol.
90
+ def data_delegator(key)
91
+ define_method(key.to_sym) { @obj.data[key] }
92
+ end
93
+
94
+ # Array of stringified instance methods that do not end with the assignment operator.
95
+ #
96
+ # (<klass>.instance_methods always generates a new Array object so it can be mutated)
97
+ #
98
+ # Returns array of strings.
99
+ def getter_method_names
100
+ @getter_method_names ||= instance_methods.map!(&:to_s).tap do |list|
101
+ list.reject! { |item| item.end_with?("=") }
102
+ end
103
+ end
27
104
  end
28
105
 
29
106
  # Create a new Drop
@@ -34,7 +111,6 @@ module Jekyll
34
111
  # Returns nothing
35
112
  def initialize(obj)
36
113
  @obj = obj
37
- @mutations = {} # only if mutable: true
38
114
  end
39
115
 
40
116
  # Access a method in the Drop or a field in the underlying hash data.
@@ -46,8 +122,8 @@ module Jekyll
46
122
  #
47
123
  # Returns the value for the given key, or nil if none exists
48
124
  def [](key)
49
- if self.class.mutable? && @mutations.key?(key)
50
- @mutations[key]
125
+ if self.class.mutable? && mutations.key?(key)
126
+ mutations[key]
51
127
  elsif self.class.invokable? key
52
128
  public_send key
53
129
  else
@@ -70,11 +146,12 @@ module Jekyll
70
146
  # and the key matches a method in which case it raises a
71
147
  # DropMutationException.
72
148
  def []=(key, val)
73
- if respond_to?("#{key}=")
74
- public_send("#{key}=", val)
149
+ setter = SETTER_KEYS_STASH[key] || "#{key}="
150
+ if respond_to?(setter)
151
+ public_send(setter, val)
75
152
  elsif respond_to?(key.to_s)
76
153
  if self.class.mutable?
77
- @mutations[key] = val
154
+ mutations[key] = val
78
155
  else
79
156
  raise Errors::DropMutationException, "Key #{key} cannot be set in the drop."
80
157
  end
@@ -88,13 +165,10 @@ module Jekyll
88
165
  #
89
166
  # Returns an Array of strings which represent method-specific keys.
90
167
  def content_methods
91
- @content_methods ||= (
92
- self.class.instance_methods \
93
- - Jekyll::Drops::Drop.instance_methods \
94
- - NON_CONTENT_METHODS
95
- ).map(&:to_s).reject do |method|
96
- method.end_with?("=")
97
- end
168
+ @content_methods ||= \
169
+ self.class.getter_method_names \
170
+ - Jekyll::Drops::Drop.getter_method_names \
171
+ - NON_CONTENT_METHOD_NAMES
98
172
  end
99
173
 
100
174
  # Check if key exists in Drop
@@ -104,7 +178,8 @@ module Jekyll
104
178
  # Returns true if the given key is present
105
179
  def key?(key)
106
180
  return false if key.nil?
107
- return true if self.class.mutable? && @mutations.key?(key)
181
+ return true if self.class.mutable? && mutations.key?(key)
182
+
108
183
  respond_to?(key) || fallback_data.key?(key)
109
184
  end
110
185
 
@@ -116,7 +191,7 @@ module Jekyll
116
191
  # Returns an Array of unique keys for content for the Drop.
117
192
  def keys
118
193
  (content_methods |
119
- @mutations.keys |
194
+ mutations.keys |
120
195
  fallback_data.keys).flatten
121
196
  end
122
197
 
@@ -173,7 +248,7 @@ module Jekyll
173
248
  end
174
249
 
175
250
  def merge(other, &block)
176
- self.dup.tap do |me|
251
+ dup.tap do |me|
177
252
  if block.nil?
178
253
  me.merge!(other)
179
254
  else
@@ -205,7 +280,14 @@ module Jekyll
205
280
  return self[key] if key?(key)
206
281
  raise KeyError, %(key not found: "#{key}") if default.nil? && block.nil?
207
282
  return yield(key) unless block.nil?
208
- return default unless default.nil?
283
+
284
+ default unless default.nil?
285
+ end
286
+
287
+ private
288
+
289
+ def mutations
290
+ @mutations ||= {}
209
291
  end
210
292
  end
211
293
  end
@@ -7,9 +7,17 @@ module Jekyll
7
7
  @obj.doc.data["layout"]
8
8
  end
9
9
 
10
+ def date
11
+ @obj.doc.date
12
+ end
13
+
10
14
  def excerpt
11
15
  nil
12
16
  end
17
+
18
+ def name
19
+ @obj.doc.data["name"] || @obj.doc.basename
20
+ end
13
21
  end
14
22
  end
15
23
  end