From 513f3008b0164d4890c6061371c539cd8f5df1cb Mon Sep 17 00:00:00 2001 From: Jan Calanog Date: Mon, 10 Feb 2025 23:23:09 +0100 Subject: [PATCH] Add support for extensionless URLs (pretty URLs) (#274) * Add support for extensionless URLs * format * Refactor * Remove commented line * Fix tests * Also strip index * Update src/Elastic.Markdown/Myst/InlineParsers/DiagnosticLinkInlineParser.cs * Fix test --- src/Elastic.Markdown/IO/MarkdownFile.cs | 4 +++- .../DiagnosticLinkInlineParser.cs | 6 +++++- src/Elastic.Markdown/Slices/HtmlWriter.cs | 20 ++++++++++++++++++- src/docs-builder/Http/DocumentationWebHost.cs | 15 +++++++++++--- .../Inline/AnchorLinkTests.cs | 10 +++++----- .../Inline/DirectiveBlockLinkTests.cs | 2 +- .../Inline/InlineAnchorTests.cs | 2 +- .../Inline/InlineLinkTests.cs | 11 +++++----- 8 files changed, 52 insertions(+), 18 deletions(-) diff --git a/src/Elastic.Markdown/IO/MarkdownFile.cs b/src/Elastic.Markdown/IO/MarkdownFile.cs index 89ef23ef..c141126a 100644 --- a/src/Elastic.Markdown/IO/MarkdownFile.cs +++ b/src/Elastic.Markdown/IO/MarkdownFile.cs @@ -70,7 +70,9 @@ public string? NavigationTitle public string FilePath { get; } public string FileName { get; } - public string Url => $"{UrlPathPrefix}/{RelativePath.Replace(".md", ".html")}"; + public string Url => Path.GetFileName(RelativePath) == "index.md" + ? $"{UrlPathPrefix}/{RelativePath.Remove(RelativePath.LastIndexOf("index.md", StringComparison.Ordinal), "index.md".Length)}" + : $"{UrlPathPrefix}/{RelativePath.Remove(RelativePath.LastIndexOf(".md", StringComparison.Ordinal), 3)}"; public int NavigationIndex { get; internal set; } = -1; diff --git a/src/Elastic.Markdown/Myst/InlineParsers/DiagnosticLinkInlineParser.cs b/src/Elastic.Markdown/Myst/InlineParsers/DiagnosticLinkInlineParser.cs index 0900fdcf..c098ed70 100644 --- a/src/Elastic.Markdown/Myst/InlineParsers/DiagnosticLinkInlineParser.cs +++ b/src/Elastic.Markdown/Myst/InlineParsers/DiagnosticLinkInlineParser.cs @@ -243,7 +243,11 @@ private static void UpdateLinkUrl(LinkInline link, string url, ParserContext con url = GetRootRelativePath(context, file); if (url.EndsWith(".md")) - url = Path.ChangeExtension(url, ".html"); + { + url = url.EndsWith("/index.md") + ? url.Remove(url.LastIndexOf("index.md", StringComparison.Ordinal), "index.md".Length) + : url.Remove(url.LastIndexOf(".md", StringComparison.Ordinal), ".md".Length); + } if (!string.IsNullOrWhiteSpace(url) && !string.IsNullOrWhiteSpace(urlPathPrefix)) url = $"{urlPathPrefix.TrimEnd('/')}{url}"; diff --git a/src/Elastic.Markdown/Slices/HtmlWriter.cs b/src/Elastic.Markdown/Slices/HtmlWriter.cs index 0616b769..5332e883 100644 --- a/src/Elastic.Markdown/Slices/HtmlWriter.cs +++ b/src/Elastic.Markdown/Slices/HtmlWriter.cs @@ -81,7 +81,25 @@ public async Task WriteAsync(IFileInfo outputFile, MarkdownFile markdown, Cancel outputFile.Directory.Create(); var rendered = await RenderLayout(markdown, ctx); - var path = Path.ChangeExtension(outputFile.FullName, ".html"); + string path; + if (outputFile.Name == "index.md") + { + path = Path.ChangeExtension(outputFile.FullName, ".html"); + } + else + { + var dir = outputFile.Directory is null + ? null + : Path.Combine(outputFile.Directory.FullName, Path.GetFileNameWithoutExtension(outputFile.Name)); + + if (dir is not null && !_writeFileSystem.Directory.Exists(dir)) + _writeFileSystem.Directory.CreateDirectory(dir); + + path = dir is null + ? Path.GetFileNameWithoutExtension(outputFile.Name) + ".html" + : Path.Combine(dir, "index.html"); + } + await _writeFileSystem.File.WriteAllTextAsync(path, rendered, ctx); } diff --git a/src/docs-builder/Http/DocumentationWebHost.cs b/src/docs-builder/Http/DocumentationWebHost.cs index ee5711d9..8e1795b3 100644 --- a/src/docs-builder/Http/DocumentationWebHost.cs +++ b/src/docs-builder/Http/DocumentationWebHost.cs @@ -99,9 +99,18 @@ private void SetUpRoutes() private static async Task ServeDocumentationFile(ReloadableGeneratorState holder, string slug, Cancel ctx) { var generator = holder.Generator; - slug = slug.Replace(".html", ".md"); - if (!generator.DocumentationSet.FlatMappedFiles.TryGetValue(slug, out var documentationFile)) - return Results.NotFound(); + + // For now, the logic is backwards compatible. + // Hence, both http://localhost:5000/migration/versioning.html and http://localhost:5000/migration/versioning works, + // so it's easier to copy links from issues created during the bug bounty. + // However, we can remove this logic in the future and only support links without the .html extension. + var s = Path.GetExtension(slug) == string.Empty ? Path.Combine(slug, "index.md") : slug.Replace(".html", ".md"); + if (!generator.DocumentationSet.FlatMappedFiles.TryGetValue(s, out var documentationFile)) + { + s = Path.GetExtension(slug) == string.Empty ? slug + ".md" : s.Replace("/index.md", ".md"); + if (!generator.DocumentationSet.FlatMappedFiles.TryGetValue(s, out documentationFile)) + return Results.NotFound(); + } switch (documentationFile) { diff --git a/tests/Elastic.Markdown.Tests/Inline/AnchorLinkTests.cs b/tests/Elastic.Markdown.Tests/Inline/AnchorLinkTests.cs index 39e1468f..43a5c88a 100644 --- a/tests/Elastic.Markdown.Tests/Inline/AnchorLinkTests.cs +++ b/tests/Elastic.Markdown.Tests/Inline/AnchorLinkTests.cs @@ -75,7 +75,7 @@ [Sub Requirements](testing/req.md#sub-requirements) public void GeneratesHtml() => // language=html Html.Should().Contain( - """

Sub Requirements

""" + """

Sub Requirements

""" ); [Fact] @@ -93,7 +93,7 @@ [Sub Requirements](testing/req.md#new-reqs) public void GeneratesHtml() => // language=html Html.Should().Contain( - """

Sub Requirements

""" + """

Sub Requirements

""" ); [Fact] @@ -110,7 +110,7 @@ public class ExternalPageAnchorAutoTitleTests(ITestOutputHelper output) : Anchor public void GeneratesHtml() => // language=html Html.Should().Contain( - """

Special Requirements > Sub Requirements

""" + """

Special Requirements > Sub Requirements

""" ); [Fact] @@ -146,7 +146,7 @@ [Sub Requirements](testing/req.md#sub-requirements2) public void GeneratesHtml() => // language=html Html.Should().Contain( - """

Sub Requirements

""" + """

Sub Requirements

""" ); [Fact] @@ -165,7 +165,7 @@ [Heading inside dropdown](testing/req.md#heading-inside-dropdown) public void GeneratesHtml() => // language=html Html.Should().Contain( - """Heading inside dropdown""" + """Heading inside dropdown""" ); [Fact] public void HasError() => Collector.Diagnostics.Should().HaveCount(0); diff --git a/tests/Elastic.Markdown.Tests/Inline/DirectiveBlockLinkTests.cs b/tests/Elastic.Markdown.Tests/Inline/DirectiveBlockLinkTests.cs index b582f4fc..20962f32 100644 --- a/tests/Elastic.Markdown.Tests/Inline/DirectiveBlockLinkTests.cs +++ b/tests/Elastic.Markdown.Tests/Inline/DirectiveBlockLinkTests.cs @@ -66,7 +66,7 @@ [Sub Requirements](testing/req.md#hint_ref) public void GeneratesHtml() => // language=html Html.Should().Contain( - """

Sub Requirements

""" + """

Sub Requirements

""" ); [Fact] diff --git a/tests/Elastic.Markdown.Tests/Inline/InlineAnchorTests.cs b/tests/Elastic.Markdown.Tests/Inline/InlineAnchorTests.cs index 8f564745..ee14633d 100644 --- a/tests/Elastic.Markdown.Tests/Inline/InlineAnchorTests.cs +++ b/tests/Elastic.Markdown.Tests/Inline/InlineAnchorTests.cs @@ -202,7 +202,7 @@ [Sub Requirements](testing/req.md#custom-anchor) public void GeneratesHtml() => // language=html Html.Should().Contain( - """

Sub Requirements

""" + """

Sub Requirements

""" ); [Fact] diff --git a/tests/Elastic.Markdown.Tests/Inline/InlineLinkTests.cs b/tests/Elastic.Markdown.Tests/Inline/InlineLinkTests.cs index a3702153..37729e2b 100644 --- a/tests/Elastic.Markdown.Tests/Inline/InlineLinkTests.cs +++ b/tests/Elastic.Markdown.Tests/Inline/InlineLinkTests.cs @@ -57,7 +57,7 @@ public class LinkToPageTests(ITestOutputHelper output) : LinkTestBase(output, public void GeneratesHtml() => // language=html Html.Should().Contain( - """

Requirements

""" + """

Requirements

""" ); [Fact] @@ -80,7 +80,7 @@ public class InsertPageTitleTests(ITestOutputHelper output) : LinkTestBase(outpu public void GeneratesHtml() => // language=html Html.Should().Contain( - """

Special Requirements

""" + """

Special Requirements

""" ); [Fact] @@ -105,7 +105,7 @@ public class LinkReferenceTest(ITestOutputHelper output) : LinkTestBase(output, public void GeneratesHtml() => // language=html Html.Should().Contain( - """

test

""" + """

test

""" ); [Fact] @@ -131,6 +131,7 @@ public void GeneratesHtml() => // language=html Html.Should().Contain( // TODO: The link is not rendered correctly yet, will be fixed in a follow-up + """

test

""" ); @@ -224,10 +225,10 @@ public void GeneratesHtml() => Html.TrimEnd().Should().Be("""

Links:

""");