Skip to content

Commit 7d8e36e

Browse files
theletterfGPT-5.5cursoragent
committed
feat(cli-reference): support title overrides
Allow cli TOC entries to override the generated root page title and navigation label without changing schema command names. Co-authored-by: GPT-5.5 <gpt-5.5@openai.com> Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 182938f commit 7d8e36e

9 files changed

Lines changed: 88 additions & 12 deletions

File tree

docs/cli/cli-reference-how-to.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,16 @@ toc:
6262
folder: cli-reference
6363
```
6464

65+
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:
66+
67+
```yaml
68+
toc:
69+
- cli: cli-schema.json
70+
folder: cli-reference
71+
title: Elastic CLI reference
72+
navigation_title: CLI reference
73+
```
74+
6575
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:
6676

6777
```yaml
@@ -101,4 +111,6 @@ Your CLI reference section is live. As your CLI evolves, regenerate the schema a
101111
|---|---|
102112
| `cli: <path>` | Path to the schema JSON, relative to `docset.yml` |
103113
| `folder: <path>` | Supplemental docs folder; also sets the URL prefix |
114+
| `title: <title>` | Optional generated CLI root page title |
115+
| `navigation_title: <title>` | Optional generated CLI root navigation label |
104116
| `children:` | Regular toc items prepended before generated pages |

src/Elastic.Documentation.Configuration/Toc/CliReference/CliReferenceRef.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ namespace Elastic.Documentation.Configuration.Toc.CliReference;
1414
public record CliReferenceRef(
1515
string SchemaPath,
1616
string? SupplementalFolder,
17+
string? Title,
18+
string? NavigationTitle,
1719
string PathRelativeToDocumentationSet,
1820
string PathRelativeToContainer,
1921
string Context,

src/Elastic.Documentation.Configuration/Toc/DocumentationSetFile.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -548,7 +548,7 @@ private static ITableOfContentsItem ResolveRuleOverviewReference(IDiagnosticsCol
548548
? ResolveTableOfContents(collector, cliRef.Children, baseDirectory, fileSystem, fullVirtualRoot, containerPath, context)
549549
: [];
550550

551-
return new CliReferenceRef(schemaFullPath, cliRef.SupplementalFolder, fullVirtualRoot, pathRelativeToContainer, context, resolvedChildren);
551+
return new CliReferenceRef(schemaFullPath, cliRef.SupplementalFolder, cliRef.Title, cliRef.NavigationTitle, fullVirtualRoot, pathRelativeToContainer, context, resolvedChildren);
552552
}
553553

554554
/// <summary>

src/Elastic.Documentation.Configuration/Toc/TableOfContentsYamlConverters.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,9 @@ public class TocItemYamlConverter : IYamlTypeConverter
117117
if (dictionary.TryGetValue("cli", out var cliSchemaPath) && cliSchemaPath is string cliSchema)
118118
{
119119
var supplementalFolder = dictionary.TryGetValue("folder", out var f) && f is string fStr ? fStr : null;
120-
return new CliReferenceRef(cliSchema, supplementalFolder, cliSchema, cliSchema, placeholderContext, children);
120+
var title = dictionary.TryGetValue("title", out var t) && t is string titleStr ? titleStr : null;
121+
var navigationTitle = dictionary.TryGetValue("navigation_title", out var nt) && nt is string navigationTitleStr ? navigationTitleStr : null;
122+
return new CliReferenceRef(cliSchema, supplementalFolder, title, navigationTitle, cliSchema, cliSchema, placeholderContext, children);
121123
}
122124

123125
// Check for folder+file combination (e.g., folder: getting-started, file: getting-started.md)

src/Elastic.Markdown/Extensions/CliReference/CliMarkdownGenerator.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,10 @@ namespace Elastic.Markdown.Extensions.CliReference;
1010

1111
internal static partial class CliMarkdownGenerator
1212
{
13-
public static string RootPage(CliSchema schema, CliSupplementalDoc? supplemental)
13+
public static string RootPage(CliSchema schema, CliSupplementalDoc? supplemental, string? title = null)
1414
{
1515
var sb = new StringBuilder();
16-
_ = sb.AppendLine($"# {schema.Name}");
16+
_ = sb.AppendLine($"# {title ?? schema.Name}");
1717
_ = sb.AppendLine();
1818

1919
var description = supplemental?.Description ?? schema.Description?.Trim();

src/Elastic.Markdown/Extensions/CliReference/CliReferenceDocsBuilderExtension.cs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,11 @@ internal sealed record CliEntityInfo(
2424
/// <summary>Ancestor namespace options ordered from closest to furthest (direct parent first).</summary>
2525
IReadOnlyList<(string Segment, List<CliParamSchema>? Options)>? AncestorNamespaceOptions = null,
2626
/// <summary>Relative path from this file to the alias target — set for CliShortcutSchema entities only.</summary>
27-
string? AliasCanonicalRelativePath = null
27+
string? AliasCanonicalRelativePath = null,
28+
/// <summary>Display title for the generated CLI root page.</summary>
29+
string? Title = null,
30+
/// <summary>Navigation title for the generated CLI root page.</summary>
31+
string? NavigationTitle = null
2832
);
2933

3034
public class CliReferenceDocsBuilderExtension(BuildContext build) : IDocsBuilderExtension
@@ -113,7 +117,7 @@ private void EnsureSyntheticFilesBuilt()
113117
private MarkdownFile? CreateCliFileFromInfo(IFileInfo sourceFile, MarkdownParser markdownParser, CliEntityInfo info) =>
114118
info.Entity switch
115119
{
116-
CliSchema schema => new CliRootFile(sourceFile, Build.DocumentationSourceDirectory, markdownParser, Build, schema, info.SupplementalDoc),
120+
CliSchema schema => new CliRootFile(sourceFile, Build.DocumentationSourceDirectory, markdownParser, Build, schema, info.SupplementalDoc, info.Title, info.NavigationTitle),
117121
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),
118122
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),
119123
CliShortcutSchema shortcut => new CliAliasFile(sourceFile, Build.DocumentationSourceDirectory, markdownParser, Build, shortcut, info.Schema.Name, info.AliasCanonicalRelativePath ?? "../"),
@@ -186,7 +190,7 @@ private List<IFileInfo> BuildSyntheticFiles()
186190
var rootSupplemental = FindSupplemental(supplementalDirPath, [], isNamespace: true, matched);
187191
var rootSyntheticPath = SyntheticPath(Build.DocumentationSourceDirectory.FullName, virtualRoot, [], isNamespace: true);
188192
var rootFileInfo = Build.ReadFileSystem.FileInfo.New(rootSyntheticPath);
189-
var rootInfo = new CliEntityInfo(schema, schema, rootSupplemental, rootFileInfo);
193+
var rootInfo = new CliEntityInfo(schema, schema, rootSupplemental, rootFileInfo, Title: cliRef.Title, NavigationTitle: cliRef.NavigationTitle);
190194
_syntheticFiles![rootSyntheticPath] = rootInfo;
191195
if (rootSupplemental != null)
192196
_supplementalFiles![rootSupplemental.FullName] = rootInfo;

src/Elastic.Markdown/Extensions/CliReference/CliRootFile.cs

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,26 +14,32 @@ public record CliRootFile : IO.MarkdownFile
1414
{
1515
private readonly CliSchema _schema;
1616
private readonly IFileInfo? _supplementalDoc;
17+
private readonly string _title;
18+
private readonly string _navigationTitle;
1719

1820
public CliRootFile(
1921
IFileInfo sourceFile,
2022
IDirectoryInfo rootPath,
2123
MarkdownParser parser,
2224
BuildContext build,
2325
CliSchema schema,
24-
IFileInfo? supplementalDoc
26+
IFileInfo? supplementalDoc,
27+
string? title = null,
28+
string? navigationTitle = null
2529
) : base(sourceFile, rootPath, parser, build)
2630
{
2731
_schema = schema;
2832
_supplementalDoc = supplementalDoc;
29-
Title = schema.Name;
33+
_title = string.IsNullOrWhiteSpace(title) ? schema.Name : title;
34+
_navigationTitle = string.IsNullOrWhiteSpace(navigationTitle) ? $"{schema.Name} CLI" : navigationTitle;
35+
Title = _title;
3036
}
3137

32-
public override string NavigationTitle => $"{_schema.Name} CLI";
38+
public override string NavigationTitle => _navigationTitle;
3339

3440
protected override Task<MarkdownDocument> GetMinimalParseDocumentAsync(Cancel ctx)
3541
{
36-
Title = _schema.Name;
42+
Title = _title;
3743
var markdown = BuildMarkdown();
3844
return Task.FromResult(MarkdownParser.MinimalParseStringAsync(markdown, SourceFile, null));
3945
}
@@ -50,6 +56,6 @@ private string BuildMarkdown()
5056
? _supplementalDoc.FileSystem.File.ReadAllText(_supplementalDoc.FullName)
5157
: null;
5258
var supplemental = CliSupplementalDoc.Parse(rawSupplemental);
53-
return CliMarkdownGenerator.RootPage(_schema, supplemental);
59+
return CliMarkdownGenerator.RootPage(_schema, supplemental, _title);
5460
}
5561
}

tests/Elastic.Documentation.Configuration.Tests/PhysicalDocsetTests.cs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,25 @@ namespace Elastic.Documentation.Configuration.Tests;
1010

1111
public class PhysicalDocsetTests
1212
{
13+
[Fact]
14+
public void CliReferenceRefReadsTitleOverrides()
15+
{
16+
const string yaml = """
17+
project: test
18+
toc:
19+
- cli: cli/schema.json
20+
folder: cli
21+
title: Elastic CLI reference
22+
navigation_title: CLI reference
23+
""";
24+
25+
var docSet = ConfigurationFileProvider.Deserializer.Deserialize<DocumentationSetFile>(yaml);
26+
var cliRef = docSet.TableOfContents.OfType<CliReferenceRef>().Single();
27+
28+
cliRef.Title.Should().Be("Elastic CLI reference");
29+
cliRef.NavigationTitle.Should().Be("CLI reference");
30+
}
31+
1332
[Fact]
1433
public void PhysicalDocsetFileCanBeDeserialized()
1534
{
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// Licensed to Elasticsearch B.V under one or more agreements.
2+
// Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
3+
// See the LICENSE file in the project root for more information
4+
5+
using AwesomeAssertions;
6+
using Elastic.Documentation.Configuration.Toc.CliReference;
7+
using Elastic.Markdown.Extensions.CliReference;
8+
9+
namespace Elastic.Markdown.Tests.CliReference;
10+
11+
public class CliMarkdownGeneratorTests
12+
{
13+
[Fact]
14+
public void RootPage_UsesTitleOverrideForHeading()
15+
{
16+
var schema = new CliSchema(
17+
SchemaVersion: 1,
18+
Name: "elastic",
19+
Description: "Interact with Elastic from the command line.",
20+
GlobalOptions: [],
21+
RootDefault: null,
22+
Commands: [],
23+
Namespaces: []
24+
);
25+
26+
var markdown = CliMarkdownGenerator.RootPage(schema, null, "Elastic CLI reference");
27+
28+
markdown.Should().StartWith("# Elastic CLI reference");
29+
markdown.Should().Contain("Interact with Elastic from the command line.");
30+
}
31+
}

0 commit comments

Comments
 (0)