jekyll 3.9.1 → 4.0.0.pre.alpha1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (94) 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.rb +5 -0
  11. data/lib/jekyll/cache.rb +183 -0
  12. data/lib/jekyll/cleaner.rb +2 -1
  13. data/lib/jekyll/collection.rb +78 -8
  14. data/lib/jekyll/command.rb +31 -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 -39
  20. data/lib/jekyll/commands/new_theme.rb +30 -28
  21. data/lib/jekyll/commands/serve.rb +46 -80
  22. data/lib/jekyll/commands/serve/live_reload_reactor.rb +6 -10
  23. data/lib/jekyll/commands/serve/servlet.rb +9 -11
  24. data/lib/jekyll/configuration.rb +26 -26
  25. data/lib/jekyll/converters/identity.rb +18 -0
  26. data/lib/jekyll/converters/markdown.rb +49 -40
  27. data/lib/jekyll/converters/markdown/kramdown_parser.rb +1 -10
  28. data/lib/jekyll/converters/smartypants.rb +34 -14
  29. data/lib/jekyll/convertible.rb +11 -13
  30. data/lib/jekyll/deprecator.rb +1 -3
  31. data/lib/jekyll/document.rb +44 -41
  32. data/lib/jekyll/drops/collection_drop.rb +2 -3
  33. data/lib/jekyll/drops/document_drop.rb +2 -1
  34. data/lib/jekyll/drops/drop.rb +3 -6
  35. data/lib/jekyll/drops/excerpt_drop.rb +4 -0
  36. data/lib/jekyll/drops/site_drop.rb +4 -13
  37. data/lib/jekyll/drops/unified_payload_drop.rb +1 -0
  38. data/lib/jekyll/drops/url_drop.rb +1 -0
  39. data/lib/jekyll/entry_filter.rb +2 -1
  40. data/lib/jekyll/excerpt.rb +45 -34
  41. data/lib/jekyll/external.rb +10 -5
  42. data/lib/jekyll/filters.rb +72 -31
  43. data/lib/jekyll/filters/date_filters.rb +6 -3
  44. data/lib/jekyll/filters/grouping_filters.rb +1 -2
  45. data/lib/jekyll/filters/url_filters.rb +6 -1
  46. data/lib/jekyll/frontmatter_defaults.rb +35 -19
  47. data/lib/jekyll/hooks.rb +2 -3
  48. data/lib/jekyll/liquid_extensions.rb +0 -2
  49. data/lib/jekyll/liquid_renderer.rb +13 -1
  50. data/lib/jekyll/liquid_renderer/file.rb +14 -3
  51. data/lib/jekyll/liquid_renderer/table.rb +67 -65
  52. data/lib/jekyll/log_adapter.rb +5 -1
  53. data/lib/jekyll/page.rb +10 -11
  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 +7 -9
  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 +2 -3
  68. data/lib/jekyll/tags/highlight.rb +22 -52
  69. data/lib/jekyll/tags/include.rb +22 -38
  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.rb +5 -4
  76. data/lib/jekyll/utils/ansi.rb +1 -1
  77. data/lib/jekyll/utils/exec.rb +0 -1
  78. data/lib/jekyll/utils/internet.rb +2 -4
  79. data/lib/jekyll/utils/platforms.rb +8 -8
  80. data/lib/jekyll/utils/thread_event.rb +1 -5
  81. data/lib/jekyll/utils/win_tz.rb +1 -1
  82. data/lib/jekyll/version.rb +1 -1
  83. data/lib/site_template/.gitignore +2 -0
  84. data/lib/site_template/404.html +1 -0
  85. data/lib/site_template/_config.yml +17 -5
  86. data/lib/site_template/_posts/0000-00-00-welcome-to-jekyll.markdown.erb +5 -1
  87. data/lib/site_template/{about.md → about.markdown} +0 -0
  88. data/lib/site_template/{index.md → index.markdown} +0 -0
  89. data/lib/theme_template/gitignore.erb +1 -0
  90. data/rubocop/jekyll/assert_equal_literal_actual.rb +149 -0
  91. metadata +85 -51
  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
@@ -9,6 +9,7 @@ module Jekyll
9
9
  @site = site
10
10
  @document = document
11
11
  @payload = site_payload
12
+ @layouts = nil
12
13
  end
13
14
 
14
15
  # Fetches the payload used in Liquid rendering.
@@ -101,8 +102,8 @@ module Jekyll
101
102
  converter.convert output
102
103
  rescue StandardError => e
103
104
  Jekyll.logger.error "Conversion error:",
104
- "#{converter.class} encountered an error while "\
105
- "converting '#{document.relative_path}':"
105
+ "#{converter.class} encountered an error while "\
106
+ "converting '#{document.relative_path}':"
106
107
  Jekyll.logger.error("", e.to_s)
107
108
  raise e
108
109
  end
@@ -121,13 +122,13 @@ module Jekyll
121
122
  template = site.liquid_renderer.file(path).parse(content)
122
123
  template.warnings.each do |e|
123
124
  Jekyll.logger.warn "Liquid Warning:",
124
- LiquidRenderer.format_error(e, path || document.relative_path)
125
+ LiquidRenderer.format_error(e, path || document.relative_path)
125
126
  end
126
127
  template.render!(payload, info)
127
128
  # rubocop: disable RescueException
128
129
  rescue Exception => e
129
130
  Jekyll.logger.error "Liquid Exception:",
130
- LiquidRenderer.format_error(e, path || document.relative_path)
131
+ LiquidRenderer.format_error(e, path || document.relative_path)
131
132
  raise e
132
133
  end
133
134
  # rubocop: enable RescueException
@@ -158,19 +159,20 @@ module Jekyll
158
159
  output = render_layout(output, layout, info)
159
160
  add_regenerator_dependencies(layout)
160
161
 
161
- if (layout = site.layouts[layout.data["layout"]])
162
- break if used.include?(layout)
163
- used << layout
164
- end
162
+ next unless (layout = site.layouts[layout.data["layout"]])
163
+ break if used.include?(layout)
164
+
165
+ used << layout
165
166
  end
166
167
  output
167
168
  end
168
169
 
170
+ private
171
+
169
172
  # Checks if the layout specified in the document actually exists
170
173
  #
171
174
  # layout - the layout to check
172
175
  # Returns nothing
173
- private
174
176
  def validate_layout(layout)
175
177
  if invalid_layout?(layout)
176
178
  Jekyll.logger.warn(
@@ -187,7 +189,6 @@ module Jekyll
187
189
  # Render layout content into document.output
188
190
  #
189
191
  # Returns String rendered content
190
- private
191
192
  def render_layout(output, layout, info)
192
193
  payload["content"] = output
193
194
  payload["layout"] = Utils.deep_merge_hashes(layout.data, payload["layout"] || {})
@@ -200,9 +201,9 @@ module Jekyll
200
201
  )
201
202
  end
202
203
 
203
- private
204
204
  def add_regenerator_dependencies(layout)
205
205
  return unless document.write?
206
+
206
207
  site.regenerator.add_dependency(
207
208
  site.in_source_dir(document.path),
208
209
  layout.path
@@ -212,18 +213,14 @@ module Jekyll
212
213
  # Set page content to payload and assign pager if document has one.
213
214
  #
214
215
  # Returns nothing
215
- private
216
216
  def assign_pages!
217
217
  payload["page"] = document.to_liquid
218
- payload["paginator"] = if document.respond_to?(:pager)
219
- document.pager.to_liquid
220
- end
218
+ payload["paginator"] = (document.pager.to_liquid if document.respond_to?(:pager))
221
219
  end
222
220
 
223
221
  # Set related posts to payload if document is a post.
224
222
  #
225
223
  # Returns nothing
226
- private
227
224
  def assign_current_document!
228
225
  payload["site"].current_document = document
229
226
  end
@@ -231,21 +228,16 @@ module Jekyll
231
228
  # Set highlighter prefix and suffix
232
229
  #
233
230
  # Returns nothing
234
- private
235
231
  def assign_highlighter_options!
236
232
  payload["highlighter_prefix"] = converters.first.highlighter_prefix
237
233
  payload["highlighter_suffix"] = converters.first.highlighter_suffix
238
234
  end
239
235
 
240
- private
241
236
  def assign_layout_data!
242
237
  layout = layouts[document.data["layout"]]
243
- if layout
244
- payload["layout"] = Utils.deep_merge_hashes(layout.data, payload["layout"] || {})
245
- end
238
+ payload["layout"] = Utils.deep_merge_hashes(layout.data, payload["layout"] || {}) if layout
246
239
  end
247
240
 
248
- private
249
241
  def permalink_ext
250
242
  document_permalink = document.permalink
251
243
  if document_permalink && !document_permalink.end_with?("/")
@@ -254,7 +246,6 @@ module Jekyll
254
246
  end
255
247
  end
256
248
 
257
- private
258
249
  def converter_output_ext
259
250
  if output_exts.size == 1
260
251
  output_exts.last
@@ -263,14 +254,12 @@ module Jekyll
263
254
  end
264
255
  end
265
256
 
266
- private
267
257
  def output_exts
268
258
  @output_exts ||= converters.map do |c|
269
259
  c.output_ext(document.extname)
270
260
  end.compact
271
261
  end
272
262
 
273
- private
274
263
  def liquid_options
275
264
  @liquid_options ||= site.config["liquid"]
276
265
  end
data/lib/jekyll/site.rb CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Jekyll
4
4
  class Site
5
- attr_reader :source, :dest, :config
5
+ attr_reader :source, :dest, :cache_dir, :config
6
6
  attr_accessor :layouts, :pages, :static_files, :drafts,
7
7
  :exclude, :include, :lsi, :highlighter, :permalink_style,
8
8
  :time, :future, :unpublished, :safe, :plugins, :limit_posts,
@@ -22,6 +22,8 @@ module Jekyll
22
22
 
23
23
  self.config = config
24
24
 
25
+ @cache_dir = in_source_dir(config["cache_dir"])
26
+
25
27
  @reader = Reader.new(self)
26
28
  @regenerator = Regenerator.new(self)
27
29
  @liquid_renderer = LiquidRenderer.new(self)
@@ -44,13 +46,14 @@ module Jekyll
44
46
  @config = config.clone
45
47
 
46
48
  %w(safe lsi highlighter baseurl exclude include future unpublished
47
- show_drafts limit_posts keep_files).each do |opt|
48
- self.send("#{opt}=", config[opt])
49
+ show_drafts limit_posts keep_files).each do |opt|
50
+ send("#{opt}=", config[opt])
49
51
  end
50
52
 
51
53
  # keep using `gems` to avoid breaking change
52
54
  self.gems = config["plugins"]
53
55
 
56
+ configure_cache
54
57
  configure_plugins
55
58
  configure_theme
56
59
  configure_include_paths
@@ -58,6 +61,8 @@ module Jekyll
58
61
 
59
62
  self.permalink_style = config["permalink"].to_sym
60
63
 
64
+ # Read in a _config.yml from the current theme-gem at the very end.
65
+ @config = load_theme_configuration(config) if theme
61
66
  @config
62
67
  end
63
68
 
@@ -78,6 +83,8 @@ module Jekyll
78
83
  Jekyll.logger.info @liquid_renderer.stats_table
79
84
  end
80
85
 
86
+ # rubocop:disable Metrics/MethodLength
87
+ #
81
88
  # Reset Site details.
82
89
  #
83
90
  # Returns nothing
@@ -91,19 +98,22 @@ module Jekyll
91
98
  self.pages = []
92
99
  self.static_files = []
93
100
  self.data = {}
101
+ @post_attr_hash = {}
94
102
  @site_data = nil
95
103
  @collections = nil
104
+ @documents = nil
96
105
  @docs_to_write = nil
97
106
  @regenerator.clear_cache
98
107
  @liquid_renderer.reset
99
108
  @site_cleaner = nil
109
+ frontmatter_defaults.reset
100
110
 
101
- if limit_posts < 0
102
- raise ArgumentError, "limit_posts must be a non-negative number"
103
- end
111
+ raise ArgumentError, "limit_posts must be a non-negative number" if limit_posts.negative?
104
112
 
113
+ Jekyll::Cache.clear_if_config_changed config
105
114
  Jekyll::Hooks.trigger :site, :after_reset, self
106
115
  end
116
+ # rubocop:enable Metrics/MethodLength
107
117
 
108
118
  # Load necessary libraries, plugins, converters, and generators.
109
119
  #
@@ -124,7 +134,7 @@ module Jekyll
124
134
  Pathname.new(source).ascend do |path|
125
135
  if path == dest_pathname
126
136
  raise Errors::FatalException,
127
- "Destination directory cannot be or contain the Source directory."
137
+ "Destination directory cannot be or contain the Source directory."
128
138
  end
129
139
  end
130
140
  end
@@ -174,7 +184,7 @@ module Jekyll
174
184
  start = Time.now
175
185
  generator.generate(self)
176
186
  Jekyll.logger.debug "Generating:",
177
- "#{generator.class} finished in #{Time.now - start} seconds."
187
+ "#{generator.class} finished in #{Time.now - start} seconds."
178
188
  end
179
189
  end
180
190
 
@@ -232,12 +242,14 @@ module Jekyll
232
242
  def post_attr_hash(post_attr)
233
243
  # Build a hash map based on the specified post attribute ( post attr =>
234
244
  # array of posts ) then sort each array in reverse order.
235
- hash = Hash.new { |h, key| h[key] = [] }
236
- posts.docs.each do |p|
237
- p.data[post_attr].each { |t| hash[t] << p } if p.data[post_attr]
245
+ @post_attr_hash[post_attr] ||= begin
246
+ hash = Hash.new { |h, key| h[key] = [] }
247
+ posts.docs.each do |p|
248
+ p.data[post_attr]&.each { |t| hash[t] << p }
249
+ end
250
+ hash.each_value { |posts| posts.sort!.reverse! }
251
+ hash
238
252
  end
239
- hash.each_value { |posts| posts.sort!.reverse! }
240
- hash
241
253
  end
242
254
 
243
255
  def tags
@@ -303,10 +315,10 @@ module Jekyll
303
315
  def relative_permalinks_are_deprecated
304
316
  if config["relative_permalinks"]
305
317
  Jekyll.logger.abort_with "Since v3.0, permalinks for pages" \
306
- " in subfolders must be relative to the" \
307
- " site source directory, not the parent" \
308
- " directory. Check https://jekyllrb.com/docs/upgrading/"\
309
- " for more info."
318
+ " in subfolders must be relative to the" \
319
+ " site source directory, not the parent" \
320
+ " directory. Check https://jekyllrb.com/docs/upgrading/"\
321
+ " for more info."
310
322
  end
311
323
  end
312
324
 
@@ -314,15 +326,15 @@ module Jekyll
314
326
  #
315
327
  # Returns an Array of Documents which should be written
316
328
  def docs_to_write
317
- documents.select(&:write?)
329
+ @docs_to_write ||= documents.select(&:write?)
318
330
  end
319
331
 
320
332
  # Get all the documents
321
333
  #
322
334
  # Returns an Array of all Documents
323
335
  def documents
324
- collections.each_with_object(Set.new) do |(_, collection), set|
325
- set.merge(collection.docs).merge(collection.files)
336
+ @documents ||= collections.reduce(Set.new) do |docs, (_, collection)|
337
+ docs + collection.docs + collection.files
326
338
  end.to_a
327
339
  end
328
340
 
@@ -377,6 +389,7 @@ module Jekyll
377
389
  # Returns a path which is prefixed with the theme root directory.
378
390
  def in_theme_dir(*paths)
379
391
  return nil unless theme
392
+
380
393
  paths.reduce(theme.root) do |base, path|
381
394
  Jekyll.sanitized_path(base, path)
382
395
  end
@@ -394,6 +407,18 @@ module Jekyll
394
407
  end
395
408
  end
396
409
 
410
+ # Public: Prefix a given path with the cache directory.
411
+ #
412
+ # paths - (optional) path elements to a file or directory within the
413
+ # cache directory
414
+ #
415
+ # Returns a path which is prefixed with the cache directory.
416
+ def in_cache_dir(*paths)
417
+ paths.reduce(cache_dir) do |base, path|
418
+ Jekyll.sanitized_path(base, path)
419
+ end
420
+ end
421
+
397
422
  # Public: The full path to the directory that houses all the collections registered
398
423
  # with the current site.
399
424
  #
@@ -403,14 +428,34 @@ module Jekyll
403
428
  @collections_path ||= dir_str.empty? ? source : in_source_dir(dir_str)
404
429
  end
405
430
 
431
+ private
432
+
433
+ def load_theme_configuration(config)
434
+ theme_config_file = in_theme_dir("_config.yml")
435
+ return config unless File.exist?(theme_config_file)
436
+
437
+ # Bail out if the theme_config_file is a symlink file irrespective of safe mode
438
+ return config if File.symlink?(theme_config_file)
439
+
440
+ theme_config = SafeYAML.load_file(theme_config_file)
441
+ return config unless theme_config.is_a?(Hash)
442
+
443
+ Jekyll.logger.info "Theme Config file:", theme_config_file
444
+
445
+ # theme_config should not be overriding Jekyll's defaults
446
+ theme_config.delete_if { |key, _| Configuration::DEFAULTS.key?(key) }
447
+
448
+ # Override theme_config with existing config and return the result.
449
+ Utils.deep_merge_hashes(theme_config, config)
450
+ end
451
+
406
452
  # Limits the current posts; removes the posts which exceed the limit_posts
407
453
  #
408
454
  # Returns nothing
409
- private
410
455
  def limit_posts!
411
- if limit_posts > 0
456
+ if limit_posts.positive?
412
457
  limit = posts.docs.length < limit_posts ? posts.docs.length : limit_posts
413
- self.posts.docs = posts.docs[-limit, limit]
458
+ posts.docs = posts.docs[-limit, limit]
414
459
  end
415
460
  end
416
461
 
@@ -418,18 +463,21 @@ module Jekyll
418
463
  # already exist.
419
464
  #
420
465
  # Returns The Cleaner
421
- private
422
466
  def site_cleaner
423
467
  @site_cleaner ||= Cleaner.new(self)
424
468
  end
425
469
 
426
- private
470
+ # Disable Marshaling cache to disk in Safe Mode
471
+ def configure_cache
472
+ Jekyll::Cache.base_dir = in_source_dir(config["cache_dir"], "Jekyll/Cache")
473
+ Jekyll::Cache.disable_disk_cache! if safe
474
+ end
475
+
427
476
  def configure_plugins
428
477
  self.plugin_manager = Jekyll::PluginManager.new(self)
429
478
  self.plugins = plugin_manager.plugins_path
430
479
  end
431
480
 
432
- private
433
481
  def configure_theme
434
482
  self.theme = nil
435
483
  return if config["theme"].nil?
@@ -444,20 +492,17 @@ module Jekyll
444
492
  end
445
493
  end
446
494
 
447
- private
448
495
  def configure_include_paths
449
496
  @includes_load_paths = Array(in_source_dir(config["includes_dir"].to_s))
450
- @includes_load_paths << theme.includes_path if theme && theme.includes_path
497
+ @includes_load_paths << theme.includes_path if theme&.includes_path
451
498
  end
452
499
 
453
- private
454
500
  def configure_file_read_opts
455
501
  self.file_read_opts = {}
456
- self.file_read_opts[:encoding] = config["encoding"] if config["encoding"]
502
+ file_read_opts[:encoding] = config["encoding"] if config["encoding"]
457
503
  self.file_read_opts = Jekyll::Utils.merged_file_read_opts(self, {})
458
504
  end
459
505
 
460
- private
461
506
  def render_docs(payload)
462
507
  collections.each_value do |collection|
463
508
  collection.docs.each do |document|
@@ -466,16 +511,15 @@ module Jekyll
466
511
  end
467
512
  end
468
513
 
469
- private
470
514
  def render_pages(payload)
471
- pages.flatten.each do |page|
515
+ pages.each do |page|
472
516
  render_regenerated(page, payload)
473
517
  end
474
518
  end
475
519
 
476
- private
477
520
  def render_regenerated(document, payload)
478
521
  return unless regenerator.regenerate?(document)
522
+
479
523
  document.output = Jekyll::Renderer.new(self, document, payload).run
480
524
  document.trigger_hooks(:post_render)
481
525
  end
@@ -54,7 +54,8 @@ module Jekyll
54
54
  #
55
55
  # Returns destination file path.
56
56
  def destination(dest)
57
- @site.in_dest_dir(*[dest, destination_rel_dir, @name].compact)
57
+ dest = @site.in_dest_dir(dest)
58
+ @site.in_dest_dir(dest, Jekyll::URL.unescape_path(url))
58
59
  end
59
60
 
60
61
  def destination_rel_dir
@@ -86,7 +87,10 @@ module Jekyll
86
87
  # Returns true unless the defaults for the destination path from
87
88
  # _config.yml contain `published: false`.
88
89
  def write?
89
- defaults.fetch("published", true)
90
+ publishable = defaults.fetch("published", true)
91
+ return publishable unless @collection
92
+
93
+ publishable && @collection.write?
90
94
  end
91
95
 
92
96
  # Write the static file to the destination directory (if modified).
@@ -96,8 +100,8 @@ module Jekyll
96
100
  # Returns false if the file was not modified since last time (no-op).
97
101
  def write(dest)
98
102
  dest_path = destination(dest)
99
-
100
103
  return false if File.exist?(dest_path) && !modified?
104
+
101
105
  self.class.mtimes[path] = mtime
102
106
 
103
107
  FileUtils.mkdir_p(File.dirname(dest_path))
@@ -111,33 +115,58 @@ module Jekyll
111
115
  @to_liquid ||= Drops::StaticFileDrop.new(self)
112
116
  end
113
117
 
118
+ # Generate "basename without extension" and strip away any trailing periods.
119
+ # NOTE: `String#gsub` removes all trailing periods (in comparison to `String#chomp`)
114
120
  def basename
115
- File.basename(name, extname)
121
+ @basename ||= File.basename(name, extname).gsub(%r!\.*\z!, "")
116
122
  end
117
123
 
118
124
  def placeholders
119
125
  {
120
126
  :collection => @collection.label,
121
- :path => relative_path[
122
- @collection.relative_directory.size..relative_path.size],
127
+ :path => cleaned_relative_path,
123
128
  :output_ext => "",
124
129
  :name => "",
125
130
  :title => "",
126
131
  }
127
132
  end
128
133
 
134
+ # Similar to Jekyll::Document#cleaned_relative_path.
135
+ # Generates a relative path with the collection's directory removed when applicable
136
+ # and additionally removes any multiple periods in the string.
137
+ #
138
+ # NOTE: `String#gsub!` removes all trailing periods (in comparison to `String#chomp!`)
139
+ #
140
+ # Examples:
141
+ # When `relative_path` is "_methods/site/my-cool-avatar...png":
142
+ # cleaned_relative_path
143
+ # # => "/site/my-cool-avatar"
144
+ #
145
+ # Returns the cleaned relative path of the static file.
146
+ def cleaned_relative_path
147
+ @cleaned_relative_path ||= begin
148
+ cleaned = relative_path[0..-extname.length - 1]
149
+ cleaned.gsub!(%r!\.*\z!, "")
150
+ cleaned.sub!(@collection.relative_directory, "") if @collection
151
+ cleaned
152
+ end
153
+ end
154
+
129
155
  # Applies a similar URL-building technique as Jekyll::Document that takes
130
156
  # the collection's URL template into account. The default URL template can
131
157
  # be overriden in the collection's configuration in _config.yml.
132
158
  def url
133
- @url ||= if @collection.nil?
134
- relative_path
159
+ @url ||= begin
160
+ base = if @collection.nil?
161
+ cleaned_relative_path
135
162
  else
136
- ::Jekyll::URL.new({
163
+ Jekyll::URL.new(
137
164
  :template => @collection.url_template,
138
- :placeholders => placeholders,
139
- })
165
+ :placeholders => placeholders
166
+ )
140
167
  end.to_s.chomp("/")
168
+ base << extname
169
+ end
141
170
  end
142
171
 
143
172
  # Returns the type of the collection if present, nil otherwise.
@@ -151,7 +180,14 @@ module Jekyll
151
180
  @defaults ||= @site.frontmatter_defaults.all url, type
152
181
  end
153
182
 
183
+ # Returns a debug string on inspecting the static file.
184
+ # Includes only the relative path of the object.
185
+ def inspect
186
+ "#<#{self.class} @relative_path=#{relative_path.inspect}>"
187
+ end
188
+
154
189
  private
190
+
155
191
  def copy_file(dest_path)
156
192
  if @site.safe || Jekyll.env == "production"
157
193
  FileUtils.cp(path, dest_path)