From dedfb0748f0059e98491901f6a86eedfb6eaafc0 Mon Sep 17 00:00:00 2001 From: Ashwin Maroli Date: Sat, 16 Feb 2019 22:01:14 +0530 Subject: [PATCH] Handle files with trailing dots in their basename (#7315) Merge pull request 7315 --- features/collections.feature | 22 +++++----- lib/jekyll/document.rb | 12 +++++- lib/jekyll/page.rb | 3 +- lib/jekyll/static_file.rb | 41 +++++++++++++++---- test/source/_methods/trailing-dots...md | 3 ++ .../2018-10-12-trailing-dots...markdown | 3 ++ test/source/trailing-dots...md | 3 ++ test/test_collections.rb | 1 + test/test_document.rb | 19 +++++++++ test/test_filters.rb | 4 +- test/test_page.rb | 5 +++ test/test_site.rb | 1 + 12 files changed, 93 insertions(+), 24 deletions(-) create mode 100644 test/source/_methods/trailing-dots...md create mode 100644 test/source/_posts/2018-10-12-trailing-dots...markdown create mode 100644 test/source/trailing-dots...md diff --git a/features/collections.feature b/features/collections.feature index 8ee1b41f..387e6acd 100644 --- a/features/collections.feature +++ b/features/collections.feature @@ -82,8 +82,8 @@ Feature: Collections When I run jekyll build Then I should get a zero exit status Then the _site directory should exist - And I should see "Collections: _methods/3940394-21-9393050-fifif1323-test.md _methods/collection/entries _methods/configuration.md _methods/escape-\+ #%20\[\].md _methods/sanitized_path.md _methods/site/generate.md _methods/site/initialize.md _methods/um_hi.md" in "_site/index.html" unless Windows - And I should see "Collections: _methods/3940394-21-9393050-fifif1323-test.md _methods/collection/entries _methods/configuration.md _methods/escape-\+ #%20\[\].md _methods/sanitized_path.md _methods/site/generate.md _methods/site/initialize.md _methods/yaml_with_dots.md" in "_site/index.html" if on Windows + And I should see "Collections: _methods/3940394-21-9393050-fifif1323-test.md _methods/collection/entries _methods/configuration.md _methods/escape-\+ #%20\[\].md _methods/sanitized_path.md _methods/site/generate.md _methods/site/initialize.md _methods/trailing-dots...md _methods/um_hi.md" in "_site/index.html" unless Windows + And I should see "Collections: _methods/3940394-21-9393050-fifif1323-test.md _methods/collection/entries _methods/configuration.md _methods/escape-\+ #%20\[\].md _methods/sanitized_path.md _methods/site/generate.md _methods/site/initialize.md _methods/trailing-dots...md _methods/yaml_with_dots.md" in "_site/index.html" if on Windows Scenario: Collections specified as an hash Given I have an "index.html" page that contains "Collections: {% for method in site.methods %}{{ method.relative_path }} {% endfor %}" @@ -96,8 +96,8 @@ Feature: Collections When I run jekyll build Then I should get a zero exit status Then the _site directory should exist - And I should see "Collections: _methods/3940394-21-9393050-fifif1323-test.md _methods/collection/entries _methods/configuration.md _methods/escape-\+ #%20\[\].md _methods/sanitized_path.md _methods/site/generate.md _methods/site/initialize.md _methods/um_hi.md" in "_site/index.html" unless Windows - And I should see "Collections: _methods/3940394-21-9393050-fifif1323-test.md _methods/collection/entries _methods/configuration.md _methods/escape-\+ #%20\[\].md _methods/sanitized_path.md _methods/site/generate.md _methods/site/initialize.md _methods/yaml_with_dots.md" in "_site/index.html" if on Windows + And I should see "Collections: _methods/3940394-21-9393050-fifif1323-test.md _methods/collection/entries _methods/configuration.md _methods/escape-\+ #%20\[\].md _methods/sanitized_path.md _methods/site/generate.md _methods/site/initialize.md _methods/trailing-dots...md _methods/um_hi.md" in "_site/index.html" unless Windows + And I should see "Collections: _methods/3940394-21-9393050-fifif1323-test.md _methods/collection/entries _methods/configuration.md _methods/escape-\+ #%20\[\].md _methods/sanitized_path.md _methods/site/generate.md _methods/site/initialize.md _methods/trailing-dots...md _methods/yaml_with_dots.md" in "_site/index.html" if on Windows Scenario: Rendered collection with document with future date Given I have a _puppies directory @@ -377,8 +377,8 @@ Feature: Collections When I run jekyll build Then I should get a zero exit status Then the _site directory should exist - And I should see "All documents: _methods/3940394-21-9393050-fifif1323-test.md _methods/collection/entries _methods/configuration.md _methods/escape-\+ #%20\[\].md _methods/sanitized_path.md _methods/site/generate.md _methods/site/initialize.md _methods/um_hi.md" in "_site/index.html" unless Windows - And I should see "All documents: _methods/3940394-21-9393050-fifif1323-test.md _methods/collection/entries _methods/configuration.md _methods/escape-\+ #%20\[\].md _methods/sanitized_path.md _methods/site/generate.md _methods/site/initialize.md _methods/yaml_with_dots.md" in "_site/index.html" if on Windows + And I should see "All documents: _methods/3940394-21-9393050-fifif1323-test.md _methods/collection/entries _methods/configuration.md _methods/escape-\+ #%20\[\].md _methods/sanitized_path.md _methods/site/generate.md _methods/site/initialize.md _methods/trailing-dots...md _methods/um_hi.md" in "_site/index.html" unless Windows + And I should see "All documents: _methods/3940394-21-9393050-fifif1323-test.md _methods/collection/entries _methods/configuration.md _methods/escape-\+ #%20\[\].md _methods/sanitized_path.md _methods/site/generate.md _methods/site/initialize.md _methods/trailing-dots...md _methods/yaml_with_dots.md" in "_site/index.html" if on Windows Scenario: Documents have an output attribute, which is the converted HTML Given I have an "index.html" page that contains "Second document's output: {{ site.documents[2].output }}" @@ -407,7 +407,7 @@ Feature: Collections And I should see "Item count: 2" in "_site/index.html" Scenario: Sort by title - Given I have an "index.html" page that contains "{% assign items = site.methods | sort: 'title' %}2. of {{ items.size }}: {{ items[1].output }}" + Given I have an "index.html" page that contains "{% assign items = site.methods | sort: 'title' %}2. of {{ items.size }}: {{ items[2].output }}" And I have fixture collections And I have a "_config.yml" file with content: """ @@ -417,8 +417,8 @@ Feature: Collections When I run jekyll build Then I should get a zero exit status And the _site directory should exist - And I should see "2. of 9:

Page without title.

" in "_site/index.html" unless Windows - And I should see "2. of 8:

Page without title.

" in "_site/index.html" if on Windows + And I should see "2. of 10:

Page without title.

" in "_site/index.html" unless Windows + And I should see "2. of 9:

Page without title.

" in "_site/index.html" if on Windows Scenario: Sort by relative_path Given I have an "index.html" page that contains "Collections: {% assign methods = site.methods | sort: 'relative_path' %}{{ methods | map:"title" | join: ", " }}" @@ -431,8 +431,8 @@ Feature: Collections When I run jekyll build Then I should get a zero exit status Then the _site directory should exist - And I should see "Collections: this is a test!, Collection#entries, Jekyll.configuration, Jekyll.escape, Jekyll.sanitized_path, Site#generate, Initialize, Site#generate, YAML with Dots" in "_site/index.html" unless Windows - And I should see "Collections: this is a test!, Collection#entries, Jekyll.configuration, Jekyll.escape, Jekyll.sanitized_path, Site#generate, Initialize, YAML with Dots" in "_site/index.html" if on Windows + And I should see "Collections: this is a test!, Collection#entries, Jekyll.configuration, Jekyll.escape, Jekyll.sanitized_path, Site#generate, Initialize, Ellipsis Path, Site#generate, YAML with Dots" in "_site/index.html" unless Windows + And I should see "Collections: this is a test!, Collection#entries, Jekyll.configuration, Jekyll.escape, Jekyll.sanitized_path, Site#generate, Initialize, Ellipsis Path, YAML with Dots" in "_site/index.html" if on Windows Scenario: Sort all entries by a Front Matter key defined in all entries Given I have an "index.html" page that contains "Collections: {{ site.tutorials | map: 'title' | join: ', ' }}" diff --git a/lib/jekyll/document.rb b/lib/jekyll/document.rb index 30cecee2..48124f50 100644 --- a/lib/jekyll/document.rb +++ b/lib/jekyll/document.rb @@ -119,15 +119,19 @@ module Jekyll # and with the collection's directory removed as well. # This method is useful when building the URL of the document. # + # NOTE: `String#gsub` removes all trailing periods (in comparison to `String#chomp`) + # # Examples: - # When relative_path is "_methods/site/generate.md": + # When relative_path is "_methods/site/generate...md": # cleaned_relative_path # # => "/site/generate" # # Returns the cleaned relative path of the document. def cleaned_relative_path @cleaned_relative_path ||= - relative_path[0..-extname.length - 1].sub(collection.relative_directory, "") + relative_path[0..-extname.length - 1] + .sub(collection.relative_directory, "") + .gsub(%r!\.*\z!, "") end # Determine whether the document is a YAML file. @@ -483,6 +487,10 @@ module Jekyll slug, ext = Regexp.last_match.captures end + # slugs shouldn't end with a period + # `String#gsub!` removes all trailing periods (in comparison to `String#chomp!`) + slug.gsub!(%r!\.*\z!, "") + # Try to ensure the user gets a title. data["title"] ||= Utils.titleize_slug(slug) # Only overwrite slug & ext if they aren't specified. diff --git a/lib/jekyll/page.rb b/lib/jekyll/page.rb index 8e516255..c777044c 100644 --- a/lib/jekyll/page.rb +++ b/lib/jekyll/page.rb @@ -116,10 +116,11 @@ module Jekyll # # name - The String filename of the page file. # + # NOTE: `String#gsub` removes all trailing periods (in comparison to `String#chomp`) # Returns nothing. def process(name) self.ext = File.extname(name) - self.basename = name[0..-ext.length - 1] + self.basename = name[0..-ext.length - 1].gsub(%r!\.*\z!, "") end # Add any necessary layouts to this post diff --git a/lib/jekyll/static_file.rb b/lib/jekyll/static_file.rb index 80d992c1..0dce9640 100644 --- a/lib/jekyll/static_file.rb +++ b/lib/jekyll/static_file.rb @@ -54,7 +54,8 @@ module Jekyll # # Returns destination file path. def destination(dest) - @site.in_dest_dir(*[dest, destination_rel_dir, @name].compact) + dest = @site.in_dest_dir(dest) + @site.in_dest_dir(dest, Jekyll::URL.unescape_path(url)) end def destination_rel_dir @@ -99,7 +100,6 @@ module Jekyll # Returns false if the file was not modified since last time (no-op). def write(dest) dest_path = destination(dest) - return false if File.exist?(dest_path) && !modified? self.class.mtimes[path] = mtime @@ -115,33 +115,58 @@ module Jekyll @to_liquid ||= Drops::StaticFileDrop.new(self) end + # Generate "basename without extension" and strip away any trailing periods. + # NOTE: `String#gsub` removes all trailing periods (in comparison to `String#chomp`) def basename - File.basename(name, extname) + @basename ||= File.basename(name, extname).gsub(%r!\.*\z!, "") end def placeholders { :collection => @collection.label, - :path => relative_path[ - @collection.relative_directory.size..relative_path.size], + :path => cleaned_relative_path, :output_ext => "", :name => "", :title => "", } end + # Similar to Jekyll::Document#cleaned_relative_path. + # Generates a relative path with the collection's directory removed when applicable + # and additionally removes any multiple periods in the string. + # + # NOTE: `String#gsub!` removes all trailing periods (in comparison to `String#chomp!`) + # + # Examples: + # When `relative_path` is "_methods/site/my-cool-avatar...png": + # cleaned_relative_path + # # => "/site/my-cool-avatar" + # + # Returns the cleaned relative path of the static file. + def cleaned_relative_path + @cleaned_relative_path ||= begin + cleaned = relative_path[0..-extname.length - 1] + cleaned.gsub!(%r!\.*\z!, "") + cleaned.sub!(@collection.relative_directory, "") if @collection + cleaned + end + end + # Applies a similar URL-building technique as Jekyll::Document that takes # the collection's URL template into account. The default URL template can # be overriden in the collection's configuration in _config.yml. def url - @url ||= if @collection.nil? - relative_path + @url ||= begin + base = if @collection.nil? + cleaned_relative_path else - ::Jekyll::URL.new( + Jekyll::URL.new( :template => @collection.url_template, :placeholders => placeholders ) end.to_s.chomp("/") + base << extname + end end # Returns the type of the collection if present, nil otherwise. diff --git a/test/source/_methods/trailing-dots...md b/test/source/_methods/trailing-dots...md new file mode 100644 index 00000000..2ec72a7f --- /dev/null +++ b/test/source/_methods/trailing-dots...md @@ -0,0 +1,3 @@ +--- +title: Ellipsis Path +--- diff --git a/test/source/_posts/2018-10-12-trailing-dots...markdown b/test/source/_posts/2018-10-12-trailing-dots...markdown new file mode 100644 index 00000000..2ec72a7f --- /dev/null +++ b/test/source/_posts/2018-10-12-trailing-dots...markdown @@ -0,0 +1,3 @@ +--- +title: Ellipsis Path +--- diff --git a/test/source/trailing-dots...md b/test/source/trailing-dots...md new file mode 100644 index 00000000..2ec72a7f --- /dev/null +++ b/test/source/trailing-dots...md @@ -0,0 +1,3 @@ +--- +title: Ellipsis Path +--- diff --git a/test/test_collections.rb b/test/test_collections.rb index 872cb5f3..65f8b964 100644 --- a/test/test_collections.rb +++ b/test/test_collections.rb @@ -140,6 +140,7 @@ class TestCollections < JekyllUnitTest _methods/escape-+\ #%20[].md _methods/yaml_with_dots.md _methods/3940394-21-9393050-fifif1323-test.md + _methods/trailing-dots...md ), doc.relative_path end end diff --git a/test/test_document.rb b/test/test_document.rb index d9757a10..1e135418 100644 --- a/test/test_document.rb +++ b/test/test_document.rb @@ -114,6 +114,25 @@ class TestDocument < JekyllUnitTest assert_nil next_next_doc["previous"]["output"] end + context "with the basename (without extension) ending with dot(s)" do + setup do + @site = fixture_site("collections" => ["methods"]) + @site.process + @document = @site.collections["methods"].docs.detect do |d| + d.relative_path == "_methods/trailing-dots...md" + end + end + + should "render into the proper url" do + assert_equal "/methods/trailing-dots.html", @document.url + + trailing_dots_doc = @site.posts.docs.detect do |d| + d.relative_path == "_posts/2018-10-12-trailing-dots...markdown" + end + assert_equal "/2018/10/12/trailing-dots.html", trailing_dots_doc.url + end + end + context "with YAML ending in three dots" do setup do @site = fixture_site("collections" => ["methods"]) diff --git a/test/test_filters.rb b/test/test_filters.rb index c007dc86..fcdc0208 100644 --- a/test/test_filters.rb +++ b/test/test_filters.rb @@ -811,7 +811,7 @@ class TestFilters < JekyllUnitTest "The list of grouped items for '' is not an Array." ) # adjust array.size to ignore symlinked page in Windows - qty = Utils::Platforms.really_windows? ? 14 : 15 + qty = Utils::Platforms.really_windows? ? 15 : 16 assert_equal qty, g["items"].size end end @@ -1031,7 +1031,7 @@ class TestFilters < JekyllUnitTest "The list of grouped items for '' is not an Array." ) # adjust array.size to ignore symlinked page in Windows - qty = Utils::Platforms.really_windows? ? 14 : 15 + qty = Utils::Platforms.really_windows? ? 15 : 16 assert_equal qty, g["items"].size end end diff --git a/test/test_page.rb b/test/test_page.rb index a34a5694..16aa3ff9 100644 --- a/test/test_page.rb +++ b/test/test_page.rb @@ -35,6 +35,11 @@ class TestPage < JekyllUnitTest assert_equal "/contacts.html", @page.url end + should "create proper URL from filename" do + @page = setup_page("trailing-dots...md") + assert_equal "/trailing-dots.html", @page.url + end + should "not published when published yaml is false" do @page = setup_page("unpublished.html") assert_equal false, @page.published? diff --git a/test/test_site.rb b/test/test_site.rb index 0463823a..f18c9a96 100644 --- a/test/test_site.rb +++ b/test/test_site.rb @@ -239,6 +239,7 @@ class TestSite < JekyllUnitTest properties.html sitemap.xml static_files.html + trailing-dots...md ) unless Utils::Platforms.really_windows? # files in symlinked directories may appear twice