Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions Source/Mdu.Test/TagsTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,24 @@ public void WithoutVocabularyTheMostUsedSpellingNamesTheTagAndMduWritesTheOvervi
});
}

[Fact]
public void TagFolderKeepsItsCasingNextToTheVocabularyPageTags()
{
WithSite(StandardSite(), root =>
{
var site = Path.Combine(root, "site");
// A folder left from an earlier render, as the redirect of Tags.md creates it on a case-insensitive disk.
Directory.CreateDirectory(Path.Combine(site, "Tags"));
var (result, errors) = Render(root);
Assert.True(result == 0, errors);

Assert.Contains("tags", Directory.EnumerateDirectories(site).Select(Path.GetFileName), StringComparer.Ordinal);
Assert.True(File.Exists(Path.Combine(site, "tags", "security.html")));
var (check, checkErrors) = Capture(() => CheckSiteCommand.Execute(new DirectoryInfo(site), []));
Assert.True(check == 0, checkErrors);
});
}

[Fact]
public void DocumentationFolderNamedTagsCollidesOnlyWhenTheSiteUsesTags()
{
Expand Down
23 changes: 22 additions & 1 deletion Source/Mdu/Service/Visitors/MarkdigVisitor.Tags.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public IReadOnlyList<string> WriteTagPages()
{
if (_siteTags.Tags.Count == 0) return [];
var folder = Path.Combine(_outPath, SiteTags.Folder);
Directory.CreateDirectory(folder);
CreateTagFolder(folder);
var written = new List<string>();
var anchorPage = _siteTags.VocabularyPage ?? _navigationRoots.FirstOrDefault();
var indexPath = Path.Combine(folder, "index.html");
Expand Down Expand Up @@ -80,6 +80,27 @@ private string ExpandTagLists(MarkdownFile page, string markdown, string outputD
GetRelativeUrl(segments, [SiteTags.Folder, tag.Slug + ".html"]), tag.Pages.Count)).ToArray();
}

/// <summary>
/// Creates <c>tags/</c> with exactly this casing. On a case-insensitive file system the redirect <c>Tags/index.html</c>
/// of a vocabulary page <c>Tags.md</c>, or an earlier render, may already have created the folder as <c>Tags/</c>;
/// links to <c>tags/…</c> would then break on a case-sensitive web server, so the folder is renamed.
/// </summary>
private static void CreateTagFolder(string folder)
{
var parent = Path.GetDirectoryName(folder)!;
var name = Path.GetFileName(folder);
if (Directory.Exists(folder) && Directory.EnumerateDirectories(parent).Select(Path.GetFileName).ToArray() is var names &&
!names.Contains(name, StringComparer.Ordinal) &&
names.FirstOrDefault(entry => string.Equals(entry, name, StringComparison.OrdinalIgnoreCase)) is { } existing)
{
// A case-only rename goes through a temporary name, which every file system accepts.
var temporary = Path.Combine(parent, name + "-" + Guid.NewGuid().ToString("N"));
Directory.Move(Path.Combine(parent, existing), temporary);
Directory.Move(temporary, folder);
}
Directory.CreateDirectory(folder);
}

private bool IsInTagFolder(string outputPath) =>
Path.GetFullPath(outputPath).StartsWith(
Path.GetFullPath(Path.Combine(_outPath, SiteTags.Folder)) + Path.DirectorySeparatorChar, PathComparison);
Expand Down
Loading