Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,18 @@
// 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.Documentation.AppliesTo;

namespace Elastic.Documentation.Configuration.Toc.CliReference;

/// <summary>
/// Represents a CLI reference entry in the table of contents, parsed from:
/// <code>
/// - cli: schema/cli.json
/// folder: cli-reference/
/// applies_to:
/// stack: preview
/// serverless: preview
/// </code>
/// </summary>
public record CliReferenceRef(
Expand All @@ -19,5 +24,6 @@ public record CliReferenceRef(
string PathRelativeToDocumentationSet,
string PathRelativeToContainer,
string Context,
IReadOnlyCollection<ITableOfContentsItem> Children
IReadOnlyCollection<ITableOfContentsItem> Children,
ApplicableTo? AppliesTo = null
) : ITableOfContentsItem;
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@
// 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.Documentation.AppliesTo;
using Elastic.Documentation.Configuration.Toc.CliReference;
using Elastic.Documentation.Configuration.Toc.DetectionRules;
using Elastic.Documentation.Diagnostics;
using YamlDotNet.Core;
using YamlDotNet.Core.Events;
using YamlDotNet.Serialization;
Expand Down Expand Up @@ -94,8 +96,10 @@ public class TocItemYamlConverter : IYamlTypeConverter
}
else if (parser.Accept<MappingStart>(out _))
{
// This is a nested mapping - skip it
parser.SkipThisAndNestedEvents();
if (key.Value == "applies_to")
value = ParseAppliesTo(parser);
else
parser.SkipThisAndNestedEvents();
}

dictionary[key.Value] = value;
Expand All @@ -119,7 +123,8 @@ public class TocItemYamlConverter : IYamlTypeConverter
var supplementalFolder = dictionary.TryGetValue("folder", out var f) && f is string fStr ? fStr : null;
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);
var appliesTo = dictionary.TryGetValue("applies_to", out var at) && at is ApplicableTo a ? a : null;
return new CliReferenceRef(cliSchema, supplementalFolder, title, navigationTitle, cliSchema, cliSchema, placeholderContext, children, appliesTo);
}

// Check for folder+file combination (e.g., folder: getting-started, file: getting-started.md)
Expand Down Expand Up @@ -183,6 +188,39 @@ public class TocItemYamlConverter : IYamlTypeConverter
return null;
}

private static ApplicableTo ParseAppliesTo(IParser parser)
{
_ = parser.Consume<MappingStart>();
var diagnostics = new List<(Severity, string)>();
var appliesTo = new ApplicableTo();
while (!parser.TryConsume<MappingEnd>(out _))
{
var applyKey = parser.Consume<Scalar>();
if (!parser.Accept<Scalar>(out var applyValue))
{
parser.SkipThisAndNestedEvents();
continue;
}
_ = parser.MoveNext();
switch (applyKey.Value)
{
case "stack":
if (AppliesCollection.TryParse(applyValue.Value, diagnostics, out var stack) && stack is not null)
appliesTo = appliesTo with { Stack = stack };
break;
case "serverless":
if (AppliesCollection.TryParse(applyValue.Value, diagnostics, out var sv) && sv is not null)
appliesTo = appliesTo with { Serverless = new ServerlessProjectApplicability { Elasticsearch = sv, Observability = sv, Security = sv } };
break;
case "deployment":
if (AppliesCollection.TryParse(applyValue.Value, diagnostics, out var dep) && dep is not null)
appliesTo = appliesTo with { Deployment = new DeploymentApplicability { Self = dep, Ece = dep, Eck = dep, Ess = dep } };
break;
}
}
return appliesTo;
}

private static IReadOnlyCollection<ITableOfContentsItem> GetChildren(Dictionary<string, object?> dictionary)
{
if (!dictionary.TryGetValue("children", out var childrenObj))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// See the LICENSE file in the project root for more information

using System.IO.Abstractions;
using Elastic.Documentation.AppliesTo;
using Elastic.Documentation.Configuration;
using Elastic.Documentation.Configuration.Toc.CliReference;
using Elastic.Markdown.Myst;
Expand Down Expand Up @@ -33,7 +34,8 @@ public CliCommandFile(
string[]? reservedMetaCommands = null,
IReadOnlyList<(string Segment, List<CliParamSchema>? Options)>? ancestorNamespaceOptions = null,
List<CliParamSchema>? globalOptions = null,
List<CliShortcutSchema>? shortcuts = null
List<CliShortcutSchema>? shortcuts = null,
ApplicableTo? appliesTo = null
) : base(sourceFile, rootPath, parser, build)
{
_command = command;
Expand All @@ -45,6 +47,7 @@ public CliCommandFile(
_globalOptions = globalOptions;
_shortcuts = shortcuts;
Title = command.Name;
FallbackAppliesTo = appliesTo;
}

public override string NavigationTitle => $"[cmd]{_command.Name}";
Expand All @@ -68,8 +71,10 @@ private string BuildMarkdown()
? _supplementalDoc.FileSystem.File.ReadAllText(_supplementalDoc.FullName)
: null;
var supplemental = CliSupplementalDoc.Parse(rawSupplemental);
return CliMarkdownGenerator.CommandPage(_command, supplemental, _fullPath, _binaryName, _reservedMetaCommands,
var body = CliMarkdownGenerator.CommandPage(_command, supplemental, _fullPath, _binaryName, _reservedMetaCommands,
error => Collector.EmitError(_supplementalDoc ?? SourceFile, error),
_ancestorNamespaceOptions, _globalOptions, _shortcuts);
// Prepend supplemental front matter so applies_to (or any other field) in cmd-*.md overrides the fallback
return supplemental?.FrontMatter is { } fm ? $"{fm}\n\n{body}" : body;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// See the LICENSE file in the project root for more information

using System.IO.Abstractions;
using Elastic.Documentation.AppliesTo;
using Elastic.Documentation.Configuration;
using Elastic.Documentation.Configuration.Toc.CliReference;
using Elastic.Markdown.Myst;
Expand All @@ -29,7 +30,8 @@ public CliNamespaceFile(
string[]? fullPath = null,
string? binaryName = null,
string[]? reservedMetaCommands = null,
List<CliShortcutSchema>? shortcuts = null
List<CliShortcutSchema>? shortcuts = null,
ApplicableTo? appliesTo = null
) : base(sourceFile, rootPath, parser, build)
{
_namespace = @namespace;
Expand All @@ -39,6 +41,7 @@ public CliNamespaceFile(
_reservedMetaCommands = reservedMetaCommands;
_shortcuts = shortcuts;
Title = @namespace.Segment;
FallbackAppliesTo = appliesTo;
}

public override string NavigationTitle => $"[ns]{_namespace.Segment}";
Expand All @@ -62,7 +65,9 @@ private string BuildMarkdown()
? _supplementalDoc.FileSystem.File.ReadAllText(_supplementalDoc.FullName)
: null;
var supplemental = CliSupplementalDoc.Parse(rawSupplemental);
return CliMarkdownGenerator.NamespacePage(_namespace, supplemental, _fullPath, _binaryName, _reservedMetaCommands,
var body = CliMarkdownGenerator.NamespacePage(_namespace, supplemental, _fullPath, _binaryName, _reservedMetaCommands,
error => Collector.EmitError(_supplementalDoc ?? SourceFile, error), _shortcuts);
// Prepend supplemental front matter so applies_to (or any other field) in ns-*.md overrides the fallback
return supplemental?.FrontMatter is { } fm ? $"{fm}\n\n{body}" : body;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// See the LICENSE file in the project root for more information

using System.IO.Abstractions;
using Elastic.Documentation.AppliesTo;
using Elastic.Documentation.Configuration;
using Elastic.Documentation.Configuration.Toc;
using Elastic.Documentation.Configuration.Toc.CliReference;
Expand All @@ -28,7 +29,9 @@ internal sealed record CliEntityInfo(
/// <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
string? NavigationTitle = null,
/// <summary>Default applies_to inherited from the cli: toc entry; overridden by supplemental doc front matter.</summary>
ApplicableTo? AppliesTo = null
);

public class CliReferenceDocsBuilderExtension(BuildContext build) : IDocsBuilderExtension
Expand Down Expand Up @@ -117,9 +120,9 @@ 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, 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),
CliSchema schema => new CliRootFile(sourceFile, Build.DocumentationSourceDirectory, markdownParser, Build, schema, info.SupplementalDoc, info.Title, info.NavigationTitle, info.AppliesTo),
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, info.AppliesTo),
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, info.AppliesTo),
CliShortcutSchema shortcut => new CliAliasFile(sourceFile, Build.DocumentationSourceDirectory, markdownParser, Build, shortcut, info.Schema.Name, info.AliasCanonicalRelativePath ?? "../"),
_ => null
};
Expand Down Expand Up @@ -186,11 +189,13 @@ private List<IFileInfo> BuildSyntheticFiles()

var matched = new HashSet<string>(StringComparer.OrdinalIgnoreCase);

var appliesTo = cliRef.AppliesTo;

// Root page
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, Title: cliRef.Title, NavigationTitle: cliRef.NavigationTitle);
var rootInfo = new CliEntityInfo(schema, schema, rootSupplemental, rootFileInfo, Title: cliRef.Title, NavigationTitle: cliRef.NavigationTitle, AppliesTo: appliesTo);
_syntheticFiles![rootSyntheticPath] = rootInfo;
if (rootSupplemental != null)
_supplementalFiles![rootSupplemental.FullName] = rootInfo;
Expand All @@ -202,15 +207,15 @@ private List<IFileInfo> BuildSyntheticFiles()
var path = SyntheticPath(Build.DocumentationSourceDirectory.FullName, virtualRoot, [cmd.Name], isNamespace: false);
var fileInfo = Build.ReadFileSystem.FileInfo.New(path);
var supplemental = FindSupplemental(supplementalDirPath, [cmd.Name], isNamespace: false, matched);
var cmdInfo = new CliEntityInfo(schema, cmd, supplemental, fileInfo, FullPath: [cmd.Name]);
var cmdInfo = new CliEntityInfo(schema, cmd, supplemental, fileInfo, FullPath: [cmd.Name], AppliesTo: appliesTo);
_syntheticFiles[path] = cmdInfo;
if (supplemental != null)
_supplementalFiles![supplemental.FullName] = cmdInfo;
fileInfos.Add(fileInfo);
}

// Namespaces (recursive)
CollectNamespaceFiles(Build.DocumentationSourceDirectory.FullName, virtualRoot, supplementalDirPath, schema.Namespaces, [], matched, fileInfos, schema);
CollectNamespaceFiles(Build.DocumentationSourceDirectory.FullName, virtualRoot, supplementalDirPath, schema.Namespaces, [], matched, fileInfos, schema, appliesTo: appliesTo);

// Shortcut alias pages
foreach (var shortcut in schema.Shortcuts ?? [])
Expand Down Expand Up @@ -246,7 +251,8 @@ private void CollectNamespaceFiles(
HashSet<string> matched,
List<IFileInfo> fileInfos,
CliSchema schema,
IReadOnlyList<(string Segment, List<CliParamSchema>? Options)>? ancestorOptions = null)
IReadOnlyList<(string Segment, List<CliParamSchema>? Options)>? ancestorOptions = null,
ApplicableTo? appliesTo = null)
{
foreach (var ns in namespaces)
{
Expand All @@ -255,7 +261,7 @@ private void CollectNamespaceFiles(
var nsFilePath = SyntheticPath(docSourceDir, virtualRoot, fullNsPath, isNamespace: true);
var nsFileInfo = Build.ReadFileSystem.FileInfo.New(nsFilePath);
var nsSupplemental = FindSupplemental(supplementalDirPath, fullNsPath, isNamespace: true, matched);
var nsInfo = new CliEntityInfo(schema, ns, nsSupplemental, nsFileInfo, FullPath: fullNsPath);
var nsInfo = new CliEntityInfo(schema, ns, nsSupplemental, nsFileInfo, FullPath: fullNsPath, AppliesTo: appliesTo);
_syntheticFiles![nsFilePath] = nsInfo;
if (nsSupplemental != null)
_supplementalFiles![nsSupplemental.FullName] = nsInfo;
Expand All @@ -272,14 +278,14 @@ private void CollectNamespaceFiles(
var cmdPath = SyntheticPath(docSourceDir, virtualRoot, cmdSegments, isNamespace: false);
var cmdFileInfo = Build.ReadFileSystem.FileInfo.New(cmdPath);
var cmdSupplemental = FindSupplemental(supplementalDirPath, cmdSegments, isNamespace: false, matched);
var cmdInfo = new CliEntityInfo(schema, cmd, cmdSupplemental, cmdFileInfo, FullPath: cmdSegments, AncestorNamespaceOptions: cmdAncestors);
var cmdInfo = new CliEntityInfo(schema, cmd, cmdSupplemental, cmdFileInfo, FullPath: cmdSegments, AncestorNamespaceOptions: cmdAncestors, AppliesTo: appliesTo);
_syntheticFiles[cmdPath] = cmdInfo;
if (cmdSupplemental != null)
_supplementalFiles![cmdSupplemental.FullName] = cmdInfo;
fileInfos.Add(cmdFileInfo);
}

CollectNamespaceFiles(docSourceDir, virtualRoot, supplementalDirPath, ns.Namespaces ?? [], fullNsPath, matched, fileInfos, schema, cmdAncestors);
CollectNamespaceFiles(docSourceDir, virtualRoot, supplementalDirPath, ns.Namespaces ?? [], fullNsPath, matched, fileInfos, schema, cmdAncestors, appliesTo);
}
}

Expand Down
9 changes: 7 additions & 2 deletions src/Elastic.Markdown/Extensions/CliReference/CliRootFile.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// See the LICENSE file in the project root for more information

using System.IO.Abstractions;
using Elastic.Documentation.AppliesTo;
using Elastic.Documentation.Configuration;
using Elastic.Documentation.Configuration.Toc.CliReference;
using Elastic.Markdown.Myst;
Expand All @@ -25,14 +26,16 @@ public CliRootFile(
CliSchema schema,
IFileInfo? supplementalDoc,
string? title = null,
string? navigationTitle = null
string? navigationTitle = null,
ApplicableTo? appliesTo = null
) : base(sourceFile, rootPath, parser, build)
{
_schema = schema;
_supplementalDoc = supplementalDoc;
_title = string.IsNullOrWhiteSpace(title) ? schema.Name : title.Trim();
_navigationTitle = string.IsNullOrWhiteSpace(navigationTitle) ? $"{schema.Name} CLI" : navigationTitle.Trim();
Title = _title;
FallbackAppliesTo = appliesTo;
}

public override string NavigationTitle => _navigationTitle;
Expand All @@ -56,6 +59,8 @@ private string BuildMarkdown()
? _supplementalDoc.FileSystem.File.ReadAllText(_supplementalDoc.FullName)
: null;
var supplemental = CliSupplementalDoc.Parse(rawSupplemental);
return CliMarkdownGenerator.RootPage(_schema, supplemental, _title);
var body = CliMarkdownGenerator.RootPage(_schema, supplemental, _title);
// Prepend supplemental front matter so applies_to (or any other field) in index.md overrides the fallback
return supplemental?.FrontMatter is { } fm ? $"{fm}\n\n{body}" : body;
}
}
10 changes: 9 additions & 1 deletion src/Elastic.Markdown/IO/MarkdownFile.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

using System.Diagnostics.CodeAnalysis;
using System.IO.Abstractions;
using Elastic.Documentation.AppliesTo;
using Elastic.Documentation.Configuration;
using Elastic.Documentation.Configuration.Products;
using Elastic.Documentation.Diagnostics;
Expand Down Expand Up @@ -61,6 +62,12 @@ BuildContext build
public string? UrlPathPrefix { get; }
protected MarkdownParser MarkdownParser { get; }
public YamlFrontMatter? YamlFrontMatter { get; private set; }

/// <summary>
/// When set, provides a default <see cref="ApplicableTo"/> used for generated pages that have no
/// <c>applies_to</c> in their YAML front matter. Page-level front matter always takes priority.
/// </summary>
protected ApplicableTo? FallbackAppliesTo { get; set; }
public string? TitleRaw { get; protected set; }

public string Title
Expand Down Expand Up @@ -382,10 +389,11 @@ private static bool IsNestedInOtherDirective(DirectiveBlock block)
private YamlFrontMatter ProcessYamlFrontMatter(MarkdownDocument document)
{
if (document.FirstOrDefault() is not YamlFrontMatterBlock yaml)
return new YamlFrontMatter { Title = Title };
return new YamlFrontMatter { Title = Title, AppliesTo = FallbackAppliesTo };

var raw = string.Join(Environment.NewLine, yaml.Lines.Lines);
var fm = ReadYamlFrontMatter(raw);
fm.AppliesTo ??= FallbackAppliesTo;

if (fm.AppliesTo?.Diagnostics is not null)
{
Expand Down
Loading