diff --git a/docs/cli/cli-reference-how-to.md b/docs/cli/cli-reference-how-to.md index adfa46bbb..df0cb0aa5 100644 --- a/docs/cli/cli-reference-how-to.md +++ b/docs/cli/cli-reference-how-to.md @@ -62,6 +62,16 @@ toc: folder: cli-reference ``` +Use `title:` to customize the generated CLI root page title, and `navigation_title:` to customize the sidebar and breadcrumb label without changing generated command examples: + +```yaml +toc: + - cli: cli-schema.json + folder: cli-reference + title: Elastic CLI reference + navigation_title: CLI reference +``` + Use `children:` to prepend hand-written pages — installation guides, conceptual overviews, or quick-start tutorials — before the auto-generated reference. All schema-generated pages follow the listed children: ```yaml @@ -101,4 +111,6 @@ Your CLI reference section is live. As your CLI evolves, regenerate the schema a |---|---| | `cli: ` | Path to the schema JSON, relative to `docset.yml` | | `folder: ` | Supplemental docs folder; also sets the URL prefix | +| `title: ` | Optional generated CLI root page title | +| `navigation_title: <title>` | Optional generated CLI root navigation label | | `children:` | Regular toc items prepended before generated pages | diff --git a/src/Elastic.Documentation.Configuration/Toc/CliReference/CliReferenceRef.cs b/src/Elastic.Documentation.Configuration/Toc/CliReference/CliReferenceRef.cs index f67f023a2..e41c661ec 100644 --- a/src/Elastic.Documentation.Configuration/Toc/CliReference/CliReferenceRef.cs +++ b/src/Elastic.Documentation.Configuration/Toc/CliReference/CliReferenceRef.cs @@ -14,6 +14,8 @@ namespace Elastic.Documentation.Configuration.Toc.CliReference; public record CliReferenceRef( string SchemaPath, string? SupplementalFolder, + string? Title, + string? NavigationTitle, string PathRelativeToDocumentationSet, string PathRelativeToContainer, string Context, diff --git a/src/Elastic.Documentation.Configuration/Toc/DocumentationSetFile.cs b/src/Elastic.Documentation.Configuration/Toc/DocumentationSetFile.cs index 5b94ab7f7..f82f87150 100644 --- a/src/Elastic.Documentation.Configuration/Toc/DocumentationSetFile.cs +++ b/src/Elastic.Documentation.Configuration/Toc/DocumentationSetFile.cs @@ -548,7 +548,7 @@ private static ITableOfContentsItem ResolveRuleOverviewReference(IDiagnosticsCol ? ResolveTableOfContents(collector, cliRef.Children, baseDirectory, fileSystem, fullVirtualRoot, containerPath, context) : []; - return new CliReferenceRef(schemaFullPath, cliRef.SupplementalFolder, fullVirtualRoot, pathRelativeToContainer, context, resolvedChildren); + return new CliReferenceRef(schemaFullPath, cliRef.SupplementalFolder, cliRef.Title, cliRef.NavigationTitle, fullVirtualRoot, pathRelativeToContainer, context, resolvedChildren); } /// <summary> diff --git a/src/Elastic.Documentation.Configuration/Toc/TableOfContentsYamlConverters.cs b/src/Elastic.Documentation.Configuration/Toc/TableOfContentsYamlConverters.cs index 664c18085..baee79ca4 100644 --- a/src/Elastic.Documentation.Configuration/Toc/TableOfContentsYamlConverters.cs +++ b/src/Elastic.Documentation.Configuration/Toc/TableOfContentsYamlConverters.cs @@ -117,7 +117,9 @@ public class TocItemYamlConverter : IYamlTypeConverter if (dictionary.TryGetValue("cli", out var cliSchemaPath) && cliSchemaPath is string cliSchema) { var supplementalFolder = dictionary.TryGetValue("folder", out var f) && f is string fStr ? fStr : null; - return new CliReferenceRef(cliSchema, supplementalFolder, cliSchema, cliSchema, placeholderContext, children); + var title = dictionary.TryGetValue("title", out var t) && t is string titleStr ? titleStr : null; + var navigationTitle = dictionary.TryGetValue("navigation_title", out var nt) && nt is string navigationTitleStr ? navigationTitleStr : null; + return new CliReferenceRef(cliSchema, supplementalFolder, title, navigationTitle, cliSchema, cliSchema, placeholderContext, children); } // Check for folder+file combination (e.g., folder: getting-started, file: getting-started.md) diff --git a/src/Elastic.Markdown/Extensions/CliReference/CliMarkdownGenerator.cs b/src/Elastic.Markdown/Extensions/CliReference/CliMarkdownGenerator.cs index 0a6c1df78..0b718330e 100644 --- a/src/Elastic.Markdown/Extensions/CliReference/CliMarkdownGenerator.cs +++ b/src/Elastic.Markdown/Extensions/CliReference/CliMarkdownGenerator.cs @@ -10,11 +10,12 @@ namespace Elastic.Markdown.Extensions.CliReference; internal static partial class CliMarkdownGenerator { - public static string RootPage(CliSchema schema, CliSupplementalDoc? supplemental) + public static string RootPage(CliSchema schema, CliSupplementalDoc? supplemental, string? title = null) { var sb = new StringBuilder(); AppendFrontMatter(sb, supplemental); - _ = sb.AppendLine($"# {schema.Name}"); + var pageTitle = string.IsNullOrWhiteSpace(title) ? schema.Name : title.Trim(); + _ = sb.AppendLine($"# {pageTitle}"); _ = sb.AppendLine(); var description = supplemental?.Description ?? schema.Description?.Trim(); diff --git a/src/Elastic.Markdown/Extensions/CliReference/CliReferenceDocsBuilderExtension.cs b/src/Elastic.Markdown/Extensions/CliReference/CliReferenceDocsBuilderExtension.cs index 3bc9e2e01..9204df692 100644 --- a/src/Elastic.Markdown/Extensions/CliReference/CliReferenceDocsBuilderExtension.cs +++ b/src/Elastic.Markdown/Extensions/CliReference/CliReferenceDocsBuilderExtension.cs @@ -24,7 +24,11 @@ internal sealed record CliEntityInfo( /// <summary>Ancestor namespace options ordered from closest to furthest (direct parent first).</summary> IReadOnlyList<(string Segment, List<CliParamSchema>? Options)>? AncestorNamespaceOptions = null, /// <summary>Relative path from this file to the alias target — set for CliShortcutSchema entities only.</summary> - string? AliasCanonicalRelativePath = null + string? AliasCanonicalRelativePath = null, + /// <summary>Display title for the generated CLI root page.</summary> + string? Title = null, + /// <summary>Navigation title for the generated CLI root page.</summary> + string? NavigationTitle = null ); public class CliReferenceDocsBuilderExtension(BuildContext build) : IDocsBuilderExtension @@ -113,7 +117,7 @@ private void EnsureSyntheticFilesBuilt() private MarkdownFile? CreateCliFileFromInfo(IFileInfo sourceFile, MarkdownParser markdownParser, CliEntityInfo info) => info.Entity switch { - CliSchema schema => new CliRootFile(sourceFile, Build.DocumentationSourceDirectory, markdownParser, Build, schema, info.SupplementalDoc), + CliSchema schema => new CliRootFile(sourceFile, Build.DocumentationSourceDirectory, markdownParser, Build, schema, info.SupplementalDoc, info.Title, info.NavigationTitle), CliNamespaceSchema ns => new CliNamespaceFile(sourceFile, Build.DocumentationSourceDirectory, markdownParser, Build, ns, info.SupplementalDoc, info.FullPath ?? [ns.Segment], info.Schema.Name, info.Schema.ReservedMetaCommands, info.Schema.Shortcuts), CliCommandSchema cmd => new CliCommandFile(sourceFile, Build.DocumentationSourceDirectory, markdownParser, Build, cmd, info.SupplementalDoc, info.FullPath ?? [cmd.Name], info.Schema.Name, info.Schema.ReservedMetaCommands, info.AncestorNamespaceOptions, info.Schema.GlobalOptions, info.Schema.Shortcuts), CliShortcutSchema shortcut => new CliAliasFile(sourceFile, Build.DocumentationSourceDirectory, markdownParser, Build, shortcut, info.Schema.Name, info.AliasCanonicalRelativePath ?? "../"), @@ -186,7 +190,7 @@ private List<IFileInfo> BuildSyntheticFiles() var rootSupplemental = FindSupplemental(supplementalDirPath, [], isNamespace: true, matched); var rootSyntheticPath = SyntheticPath(Build.DocumentationSourceDirectory.FullName, virtualRoot, [], isNamespace: true); var rootFileInfo = Build.ReadFileSystem.FileInfo.New(rootSyntheticPath); - var rootInfo = new CliEntityInfo(schema, schema, rootSupplemental, rootFileInfo); + var rootInfo = new CliEntityInfo(schema, schema, rootSupplemental, rootFileInfo, Title: cliRef.Title, NavigationTitle: cliRef.NavigationTitle); _syntheticFiles![rootSyntheticPath] = rootInfo; if (rootSupplemental != null) _supplementalFiles![rootSupplemental.FullName] = rootInfo; diff --git a/src/Elastic.Markdown/Extensions/CliReference/CliRootFile.cs b/src/Elastic.Markdown/Extensions/CliReference/CliRootFile.cs index 991622158..1eefadd2b 100644 --- a/src/Elastic.Markdown/Extensions/CliReference/CliRootFile.cs +++ b/src/Elastic.Markdown/Extensions/CliReference/CliRootFile.cs @@ -14,6 +14,8 @@ public record CliRootFile : IO.MarkdownFile { private readonly CliSchema _schema; private readonly IFileInfo? _supplementalDoc; + private readonly string _title; + private readonly string _navigationTitle; public CliRootFile( IFileInfo sourceFile, @@ -21,19 +23,23 @@ public CliRootFile( MarkdownParser parser, BuildContext build, CliSchema schema, - IFileInfo? supplementalDoc + IFileInfo? supplementalDoc, + string? title = null, + string? navigationTitle = null ) : base(sourceFile, rootPath, parser, build) { _schema = schema; _supplementalDoc = supplementalDoc; - Title = schema.Name; + _title = string.IsNullOrWhiteSpace(title) ? schema.Name : title; + _navigationTitle = string.IsNullOrWhiteSpace(navigationTitle) ? $"{schema.Name} CLI" : navigationTitle; + Title = _title; } - public override string NavigationTitle => $"{_schema.Name} CLI"; + public override string NavigationTitle => _navigationTitle; protected override Task<MarkdownDocument> GetMinimalParseDocumentAsync(Cancel ctx) { - Title = _schema.Name; + Title = _title; var markdown = BuildMarkdown(); return Task.FromResult(MarkdownParser.MinimalParseStringAsync(markdown, SourceFile, null)); } @@ -50,6 +56,6 @@ private string BuildMarkdown() ? _supplementalDoc.FileSystem.File.ReadAllText(_supplementalDoc.FullName) : null; var supplemental = CliSupplementalDoc.Parse(rawSupplemental); - return CliMarkdownGenerator.RootPage(_schema, supplemental); + return CliMarkdownGenerator.RootPage(_schema, supplemental, _title); } } diff --git a/tests/Elastic.Documentation.Configuration.Tests/PhysicalDocsetTests.cs b/tests/Elastic.Documentation.Configuration.Tests/PhysicalDocsetTests.cs index ffb96d96e..9a7d41b80 100644 --- a/tests/Elastic.Documentation.Configuration.Tests/PhysicalDocsetTests.cs +++ b/tests/Elastic.Documentation.Configuration.Tests/PhysicalDocsetTests.cs @@ -10,6 +10,25 @@ namespace Elastic.Documentation.Configuration.Tests; public class PhysicalDocsetTests { + [Fact] + public void CliReferenceRefReadsTitleOverrides() + { + const string yaml = """ + project: test + toc: + - cli: cli/schema.json + folder: cli + title: Elastic CLI reference + navigation_title: CLI reference + """; + + var docSet = ConfigurationFileProvider.Deserializer.Deserialize<DocumentationSetFile>(yaml); + var cliRef = docSet.TableOfContents.OfType<CliReferenceRef>().Single(); + + cliRef.Title.Should().Be("Elastic CLI reference"); + cliRef.NavigationTitle.Should().Be("CLI reference"); + } + [Fact] public void PhysicalDocsetFileCanBeDeserialized() { diff --git a/tests/Elastic.Markdown.Tests/CliReference/CliMarkdownGeneratorTests.cs b/tests/Elastic.Markdown.Tests/CliReference/CliMarkdownGeneratorTests.cs new file mode 100644 index 000000000..796a3c377 --- /dev/null +++ b/tests/Elastic.Markdown.Tests/CliReference/CliMarkdownGeneratorTests.cs @@ -0,0 +1,51 @@ +// 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 AwesomeAssertions; +using Elastic.Documentation.Configuration.Toc.CliReference; +using Elastic.Markdown.Extensions.CliReference; + +namespace Elastic.Markdown.Tests.CliReference; + +public class CliMarkdownGeneratorTests +{ + [Fact] + public void RootPage_UsesTitleOverrideForHeading() + { + var schema = new CliSchema( + SchemaVersion: 1, + Name: "elastic", + Description: "Interact with Elastic from the command line.", + GlobalOptions: [], + RootDefault: null, + Commands: [], + Namespaces: [] + ); + + var markdown = CliMarkdownGenerator.RootPage(schema, null, "Elastic CLI reference"); + + markdown.Should().StartWith("# Elastic CLI reference"); + markdown.Should().Contain("Interact with Elastic from the command line."); + } + + [Theory] + [InlineData("")] + [InlineData(" ")] + public void RootPage_FallsBackToSchemaNameForBlankTitleOverride(string title) + { + var schema = new CliSchema( + SchemaVersion: 1, + Name: "elastic", + Description: "Interact with Elastic from the command line.", + GlobalOptions: [], + RootDefault: null, + Commands: [], + Namespaces: [] + ); + + var markdown = CliMarkdownGenerator.RootPage(schema, null, title); + + markdown.Should().StartWith("# elastic"); + } +}