From d56376b7dd678f580fa8c48f234a83e084cdf715 Mon Sep 17 00:00:00 2001 From: Martijn Laarman Date: Fri, 10 Jan 2025 13:43:47 +0100 Subject: [PATCH] Introduce toc.yml, allows toc's to be split one directory deep --- docs-builder.sln.DotSettings | 1 + docs/source/development/index.md | 6 ++ docs/source/development/toc.yml | 2 + docs/source/docset.yml | 3 + src/Elastic.Markdown/IO/ConfigurationFile.cs | 63 +++++++++++++++---- .../IO/DocumentationFolder.cs | 9 +-- src/Elastic.Markdown/IO/ITocItem.cs | 6 +- src/Elastic.Markdown/IO/MarkdownFile.cs | 7 ++- .../{SiteMap => DocSet}/BreadCrumbTests.cs | 2 +- .../{SiteMap => DocSet}/LinkReferenceTests.cs | 2 +- .../{SiteMap => DocSet}/NavigationTests.cs | 2 +- .../NavigationTestsBase.cs | 2 +- .../DocSet/NestedTocTests.cs | 31 +++++++++ 13 files changed, 112 insertions(+), 24 deletions(-) create mode 100644 docs/source/development/index.md create mode 100644 docs/source/development/toc.yml rename tests/Elastic.Markdown.Tests/{SiteMap => DocSet}/BreadCrumbTests.cs (94%) rename tests/Elastic.Markdown.Tests/{SiteMap => DocSet}/LinkReferenceTests.cs (95%) rename tests/Elastic.Markdown.Tests/{SiteMap => DocSet}/NavigationTests.cs (95%) rename tests/Elastic.Markdown.Tests/{SiteMap => DocSet}/NavigationTestsBase.cs (97%) create mode 100644 tests/Elastic.Markdown.Tests/DocSet/NestedTocTests.cs diff --git a/docs-builder.sln.DotSettings b/docs-builder.sln.DotSettings index bf2f2c73d..aca4c0cce 100644 --- a/docs-builder.sln.DotSettings +++ b/docs-builder.sln.DotSettings @@ -1,3 +1,4 @@  + True True True \ No newline at end of file diff --git a/docs/source/development/index.md b/docs/source/development/index.md new file mode 100644 index 000000000..9ee2868d5 --- /dev/null +++ b/docs/source/development/index.md @@ -0,0 +1,6 @@ +--- +title: Development Guide +navigation_title: Development +--- + +TODO write development documentation here \ No newline at end of file diff --git a/docs/source/development/toc.yml b/docs/source/development/toc.yml new file mode 100644 index 000000000..dec679b99 --- /dev/null +++ b/docs/source/development/toc.yml @@ -0,0 +1,2 @@ +toc: + - file: index.md diff --git a/docs/source/docset.yml b/docs/source/docset.yml index f4f7e75b0..c5deef422 100644 --- a/docs/source/docset.yml +++ b/docs/source/docset.yml @@ -69,6 +69,9 @@ toc: - file: tabs.md - file: tagged_regions.md - file: titles.md + # nested TOCs are only allowed from docset.yml + # to prevent them from being nested deeply arbitrarily + - toc: development - folder: testing children: - file: index.md diff --git a/src/Elastic.Markdown/IO/ConfigurationFile.cs b/src/Elastic.Markdown/IO/ConfigurationFile.cs index 5d498413a..1a8bf1591 100644 --- a/src/Elastic.Markdown/IO/ConfigurationFile.cs +++ b/src/Elastic.Markdown/IO/ConfigurationFile.cs @@ -15,6 +15,7 @@ public record ConfigurationFile : DocumentationFile private readonly IFileInfo _sourceFile; private readonly IDirectoryInfo _rootPath; private readonly BuildContext _context; + private readonly int _depth; public string? Project { get; } public Glob[] Exclude { get; } = []; @@ -28,12 +29,13 @@ public record ConfigurationFile : DocumentationFile private readonly Dictionary _substitutions = new(StringComparer.OrdinalIgnoreCase); public IReadOnlyDictionary Substitutions => _substitutions; - public ConfigurationFile(IFileInfo sourceFile, IDirectoryInfo rootPath, BuildContext context) + public ConfigurationFile(IFileInfo sourceFile, IDirectoryInfo rootPath, BuildContext context, int depth = 0, string parentPath = "") : base(sourceFile, rootPath) { _sourceFile = sourceFile; _rootPath = rootPath; _context = context; + _depth = depth; if (!sourceFile.Exists) { Project = "unknown"; @@ -81,7 +83,13 @@ public ConfigurationFile(IFileInfo sourceFile, IDirectoryInfo rootPath, BuildCon ExternalLinkHosts.Add(host); break; case "toc": - var entries = ReadChildren(entry, string.Empty); + if (depth > 1) + { + EmitError($"toc.yml files may only be linked from docset.yml", entry.Key); + break; + } + + var entries = ReadChildren(entry, parentPath); TableOfContents = entries; break; @@ -115,20 +123,19 @@ private List ReadChildren(KeyValuePair entry, stri return entries; } - foreach (var tocEntry in sequence.Children.OfType()) - { - var tocItem = ReadChild(tocEntry, parentPath); - if (tocItem is not null) - entries.Add(tocItem); - } + entries.AddRange( + sequence.Children.OfType() + .SelectMany(tocEntry => ReadChild(tocEntry, parentPath) ?? []) + ); return entries; } - private ITocItem? ReadChild(YamlMappingNode tocEntry, string parentPath) + private IEnumerable? ReadChild(YamlMappingNode tocEntry, string parentPath) { string? file = null; string? folder = null; + ConfigurationFile? toc = null; var fileFound = false; var folderFound = false; IReadOnlyCollection? children = null; @@ -137,6 +144,9 @@ private List ReadChildren(KeyValuePair entry, stri var key = ((YamlScalarNode)entry.Key).Value; switch (key) { + case "toc": + toc = ReadNestedToc(entry, parentPath, out fileFound); + break; case "file": file = ReadFile(entry, parentPath, out fileFound); break; @@ -150,15 +160,23 @@ private List ReadChildren(KeyValuePair entry, stri } } + if (toc is not null) + { + foreach (var f in toc.Files) + Files.Add(f); + + return [new FolderReference($"{parentPath}".TrimStart('/'), folderFound, toc.TableOfContents)]; + } + if (file is not null) - return new TocFile($"{parentPath}/{file}".TrimStart('/'), fileFound, children ?? []); + return [new FileReference($"{parentPath}/{file}".TrimStart('/'), fileFound, children ?? [])]; if (folder is not null) { if (children is null) ImplicitFolders.Add(parentPath.TrimStart('/')); - return new TocFolder($"{parentPath}".TrimStart('/'), folderFound, children ?? []); + return [new FolderReference($"{parentPath}".TrimStart('/'), folderFound, children ?? [])]; } return null; @@ -226,6 +244,29 @@ private Dictionary ReadDictionary(KeyValuePair entry, string parentPath, out bool found) + { + found = false; + var tocPath = ReadString(entry); + if (tocPath is null) + { + EmitError($"Empty toc: reference", entry.Key); + return null; + } + + var rootPath = _context.ReadFileSystem.DirectoryInfo.New(Path.Combine(_rootPath.FullName, tocPath)); + var path = Path.Combine(rootPath.FullName, "toc.yml"); + var source = _context.ReadFileSystem.FileInfo.New(path); + if (!source.Exists) + EmitError($"Nested toc: '{source.FullName}' does not exist", entry.Key); + else + found = true; + + var nestedConfiguration = new ConfigurationFile(source, _rootPath, _context, _depth + 1, tocPath); + return nestedConfiguration; + } + + private string? ReadString(KeyValuePair entry) { if (entry.Value is YamlScalarNode scalar) diff --git a/src/Elastic.Markdown/IO/DocumentationFolder.cs b/src/Elastic.Markdown/IO/DocumentationFolder.cs index 3991d60d8..9207d7b7c 100644 --- a/src/Elastic.Markdown/IO/DocumentationFolder.cs +++ b/src/Elastic.Markdown/IO/DocumentationFolder.cs @@ -33,10 +33,7 @@ public DocumentationFolder( Index = index ?? foundIndex; if (Index is not null) - { FilesInOrder = FilesInOrder.Except([Index]).ToList(); - Index.Parent ??= this; - } OwnFiles = [.. FilesInOrder]; } @@ -55,7 +52,7 @@ out List filesInOrder MarkdownFile? index = null; foreach (var tocItem in toc) { - if (tocItem is TocFile file) + if (tocItem is FileReference file) { if (!lookup.TryGetValue(file.Path, out var d) || d is not MarkdownFile md) continue; @@ -76,14 +73,14 @@ out List filesInOrder if (file.Path.EndsWith("index.md") && d is MarkdownFile i) index ??= i; } - else if (tocItem is TocFolder folder) + else if (tocItem is FolderReference folder) { var children = folder.Children; if (children.Count == 0 && folderLookup.TryGetValue(folder.Path, out var documentationFiles)) { children = documentationFiles - .Select(d => new TocFile(d.RelativePath, true, [])) + .Select(d => new FileReference(d.RelativePath, true, [])) .ToArray(); } diff --git a/src/Elastic.Markdown/IO/ITocItem.cs b/src/Elastic.Markdown/IO/ITocItem.cs index cbc0aa92a..803ffedfa 100644 --- a/src/Elastic.Markdown/IO/ITocItem.cs +++ b/src/Elastic.Markdown/IO/ITocItem.cs @@ -6,6 +6,8 @@ namespace Elastic.Markdown.IO; public interface ITocItem; -public record TocFile(string Path, bool Found, IReadOnlyCollection Children) : ITocItem; +public record FileReference(string Path, bool Found, IReadOnlyCollection Children) : ITocItem; -public record TocFolder(string Path, bool Found, IReadOnlyCollection Children) : ITocItem; +public record FolderReference(string Path, bool Found, IReadOnlyCollection Children) : ITocItem; + +public record TocReference(string Path, bool Found, IReadOnlyCollection Children) : ITocItem; diff --git a/src/Elastic.Markdown/IO/MarkdownFile.cs b/src/Elastic.Markdown/IO/MarkdownFile.cs index 0e287bb27..aaff75eff 100644 --- a/src/Elastic.Markdown/IO/MarkdownFile.cs +++ b/src/Elastic.Markdown/IO/MarkdownFile.cs @@ -31,7 +31,11 @@ public MarkdownFile(IFileInfo sourceFile, IDirectoryInfo rootPath, MarkdownParse private DiagnosticsCollector Collector { get; } - public DocumentationFolder? Parent { get; set; } + public DocumentationFolder? Parent + { + get => FileName == "index.md" ? _parent?.Parent : _parent; + set => _parent = value; + } public string? UrlPathPrefix { get; } private MarkdownParser MarkdownParser { get; } @@ -55,6 +59,7 @@ public string? NavigationTitle public string Url => $"{UrlPathPrefix}/{RelativePath.Replace(".md", ".html")}"; private bool _instructionsParsed; + private DocumentationFolder? _parent; public MarkdownFile[] YieldParents() { diff --git a/tests/Elastic.Markdown.Tests/SiteMap/BreadCrumbTests.cs b/tests/Elastic.Markdown.Tests/DocSet/BreadCrumbTests.cs similarity index 94% rename from tests/Elastic.Markdown.Tests/SiteMap/BreadCrumbTests.cs rename to tests/Elastic.Markdown.Tests/DocSet/BreadCrumbTests.cs index d5f85bc37..c6df8e3d4 100644 --- a/tests/Elastic.Markdown.Tests/SiteMap/BreadCrumbTests.cs +++ b/tests/Elastic.Markdown.Tests/DocSet/BreadCrumbTests.cs @@ -6,7 +6,7 @@ using FluentAssertions; using Xunit.Abstractions; -namespace Elastic.Markdown.Tests.SiteMap; +namespace Elastic.Markdown.Tests.DocSet; public class BreadCrumbTests(ITestOutputHelper output) : NavigationTestsBase(output) { diff --git a/tests/Elastic.Markdown.Tests/SiteMap/LinkReferenceTests.cs b/tests/Elastic.Markdown.Tests/DocSet/LinkReferenceTests.cs similarity index 95% rename from tests/Elastic.Markdown.Tests/SiteMap/LinkReferenceTests.cs rename to tests/Elastic.Markdown.Tests/DocSet/LinkReferenceTests.cs index 38968cc7f..f48b2bc27 100644 --- a/tests/Elastic.Markdown.Tests/SiteMap/LinkReferenceTests.cs +++ b/tests/Elastic.Markdown.Tests/DocSet/LinkReferenceTests.cs @@ -6,7 +6,7 @@ using FluentAssertions; using Xunit.Abstractions; -namespace Elastic.Markdown.Tests.SiteMap; +namespace Elastic.Markdown.Tests.DocSet; public class LinkReferenceTests(ITestOutputHelper output) : NavigationTestsBase(output) { diff --git a/tests/Elastic.Markdown.Tests/SiteMap/NavigationTests.cs b/tests/Elastic.Markdown.Tests/DocSet/NavigationTests.cs similarity index 95% rename from tests/Elastic.Markdown.Tests/SiteMap/NavigationTests.cs rename to tests/Elastic.Markdown.Tests/DocSet/NavigationTests.cs index c93d7e557..1b324aae2 100644 --- a/tests/Elastic.Markdown.Tests/SiteMap/NavigationTests.cs +++ b/tests/Elastic.Markdown.Tests/DocSet/NavigationTests.cs @@ -5,7 +5,7 @@ using FluentAssertions; using Xunit.Abstractions; -namespace Elastic.Markdown.Tests.SiteMap; +namespace Elastic.Markdown.Tests.DocSet; public class NavigationTests(ITestOutputHelper output) : NavigationTestsBase(output) { diff --git a/tests/Elastic.Markdown.Tests/SiteMap/NavigationTestsBase.cs b/tests/Elastic.Markdown.Tests/DocSet/NavigationTestsBase.cs similarity index 97% rename from tests/Elastic.Markdown.Tests/SiteMap/NavigationTestsBase.cs rename to tests/Elastic.Markdown.Tests/DocSet/NavigationTestsBase.cs index f1cd126e3..e34b41e9f 100644 --- a/tests/Elastic.Markdown.Tests/SiteMap/NavigationTestsBase.cs +++ b/tests/Elastic.Markdown.Tests/DocSet/NavigationTestsBase.cs @@ -9,7 +9,7 @@ using FluentAssertions; using Xunit.Abstractions; -namespace Elastic.Markdown.Tests.SiteMap; +namespace Elastic.Markdown.Tests.DocSet; public class NavigationTestsBase : IAsyncLifetime { diff --git a/tests/Elastic.Markdown.Tests/DocSet/NestedTocTests.cs b/tests/Elastic.Markdown.Tests/DocSet/NestedTocTests.cs new file mode 100644 index 000000000..39210e6bc --- /dev/null +++ b/tests/Elastic.Markdown.Tests/DocSet/NestedTocTests.cs @@ -0,0 +1,31 @@ +// Licensed to Elasticsearch B.V under one or more agreements. +// Elasticsearch B.V licenses this file to you under the Apache 2.0 License. +// See the LICENSE file in the project root for more information + +using Elastic.Markdown.IO; +using FluentAssertions; +using Xunit.Abstractions; + +namespace Elastic.Markdown.Tests.DocSet; + +public class NestedTocTests(ITestOutputHelper output) : NavigationTestsBase(output) +{ + [Fact] + public void InjectsNestedTocsIntoDocumentationSet() + { + var doc = Generator.DocumentationSet.Files.FirstOrDefault(f => f.RelativePath == "development/index.md") as MarkdownFile; + + doc.Should().NotBeNull(); + + // ensure we link back up to main toc in docset yaml + doc!.Parent.Should().NotBeNull(); + + // its parent should be null + doc.Parent!.Parent.Should().BeNull(); + + // its parent should point to an index + doc.Parent.Index.Should().NotBeNull(); + doc.Parent.Index!.RelativePath.Should().Be("index.md"); + + } +}