diff --git a/src/Elastic.Markdown/Myst/FrontMatter/AllVersions.cs b/src/Elastic.Markdown/Myst/FrontMatter/AllVersions.cs new file mode 100644 index 0000000..c61a593 --- /dev/null +++ b/src/Elastic.Markdown/Myst/FrontMatter/AllVersions.cs @@ -0,0 +1,50 @@ +// 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.Helpers; +using YamlDotNet.Core; +using YamlDotNet.Core.Events; +using YamlDotNet.Serialization; + +namespace Elastic.Markdown.Myst.FrontMatter; + +public class AllVersions() : SemVersion(9999, 9999, 9999) +{ + public static AllVersions Instance { get; } = new (); +} + +public class SemVersionConverter : IYamlTypeConverter +{ + public bool Accepts(Type type) => type == typeof(SemVersion); + + public object? ReadYaml(IParser parser, Type type, ObjectDeserializer rootDeserializer) + { + var value = parser.Consume(); + if (string.IsNullOrWhiteSpace(value.Value)) + return AllVersions.Instance; + if (string.Equals(value.Value.Trim(), "all", StringComparison.InvariantCultureIgnoreCase)) + return AllVersions.Instance; + return (SemVersion)value.Value; + } + + public void WriteYaml(IEmitter emitter, object? value, Type type, ObjectSerializer serializer) + { + if (value == null) + return; + emitter.Emit(new Scalar(value.ToString()!)); + } + + public static bool TryParse(string? value, out SemVersion? version) + { + version = value?.Trim().ToLowerInvariant() switch + { + null => AllVersions.Instance, + "all" => AllVersions.Instance, + "" => AllVersions.Instance, + _ => SemVersion.TryParse(value, out var v) ? v : SemVersion.TryParse(value + ".0", out v) ? v : null + }; + return version is not null; + } +} + diff --git a/src/Elastic.Markdown/Myst/FrontMatter/Deployment.cs b/src/Elastic.Markdown/Myst/FrontMatter/Deployment.cs new file mode 100644 index 0000000..e491666 --- /dev/null +++ b/src/Elastic.Markdown/Myst/FrontMatter/Deployment.cs @@ -0,0 +1,120 @@ +// 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 YamlDotNet.Core; +using YamlDotNet.Core.Events; +using YamlDotNet.Serialization; + +namespace Elastic.Markdown.Myst.FrontMatter; + +[YamlSerializable] +public record Deployment +{ + [YamlMember(Alias = "self")] + public SelfManagedDeployment? SelfManaged { get; set; } + + [YamlMember(Alias = "cloud")] + public CloudManagedDeployment? Cloud { get; set; } + + public static Deployment All { get; } = new() + { + Cloud = CloudManagedDeployment.All, + SelfManaged = SelfManagedDeployment.All + }; +} + +[YamlSerializable] +public record SelfManagedDeployment +{ + [YamlMember(Alias = "stack")] + public ProductAvailability? Stack { get; set; } + + [YamlMember(Alias = "ece")] + public ProductAvailability? Ece { get; set; } + + [YamlMember(Alias = "eck")] + public ProductAvailability? Eck { get; set; } + + public static SelfManagedDeployment All { get; } = new() + { + Stack = ProductAvailability.GenerallyAvailable, + Ece = ProductAvailability.GenerallyAvailable, + Eck = ProductAvailability.GenerallyAvailable + }; +} + +[YamlSerializable] +public record CloudManagedDeployment +{ + [YamlMember(Alias = "hosted")] + public ProductAvailability? Hosted { get; set; } + + [YamlMember(Alias = "serverless")] + public ProductAvailability? Serverless { get; set; } + + public static CloudManagedDeployment All { get; } = new() + { + Hosted = ProductAvailability.GenerallyAvailable, + Serverless = ProductAvailability.GenerallyAvailable + }; + +} + +public class DeploymentConverter : IYamlTypeConverter +{ + public bool Accepts(Type type) => type == typeof(Deployment); + + public object? ReadYaml(IParser parser, Type type, ObjectDeserializer rootDeserializer) + { + if (parser.TryConsume(out var value)) + { + if (string.IsNullOrWhiteSpace(value.Value)) + return Deployment.All; + if (string.Equals(value.Value, "all", StringComparison.InvariantCultureIgnoreCase)) + return Deployment.All; + } + var x = rootDeserializer.Invoke(typeof(Dictionary)); + if (x is not Dictionary { Count: > 0 } dictionary) + return null; + + var deployment = new Deployment(); + + if (TryGetVersion("stack", out var version)) + { + deployment.SelfManaged ??= new SelfManagedDeployment(); + deployment.SelfManaged.Stack = version; + } + if (TryGetVersion("ece", out version)) + { + deployment.SelfManaged ??= new SelfManagedDeployment(); + deployment.SelfManaged.Ece = version; + } + if (TryGetVersion("eck", out version)) + { + deployment.SelfManaged ??= new SelfManagedDeployment(); + deployment.SelfManaged.Eck = version; + } + if (TryGetVersion("hosted", out version)) + { + deployment.Cloud ??= new CloudManagedDeployment(); + deployment.Cloud.Hosted = version; + } + if (TryGetVersion("serverless", out version)) + { + deployment.Cloud ??= new CloudManagedDeployment(); + deployment.Cloud.Serverless = version; + } + return deployment; + + bool TryGetVersion(string key, out ProductAvailability? semVersion) + { + semVersion = null; + return dictionary.TryGetValue(key, out var v) && ProductAvailability.TryParse(v, out semVersion); + } + + } + + public void WriteYaml(IEmitter emitter, object? value, Type type, ObjectSerializer serializer) => + serializer.Invoke(value, type); +} diff --git a/src/Elastic.Markdown/Myst/FrontMatter/ProductAvailability.cs b/src/Elastic.Markdown/Myst/FrontMatter/ProductAvailability.cs new file mode 100644 index 0000000..88336bc --- /dev/null +++ b/src/Elastic.Markdown/Myst/FrontMatter/ProductAvailability.cs @@ -0,0 +1,61 @@ +// 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.Helpers; +using YamlDotNet.Serialization; + +namespace Elastic.Markdown.Myst.FrontMatter; + +[YamlSerializable] +public record ProductAvailability +{ + public ProductLifecycle Lifecycle { get; init; } + public SemVersion? Version { get; init; } + + public static ProductAvailability GenerallyAvailable { get; } = new() + { + Lifecycle = ProductLifecycle.GenerallyAvailable, Version = AllVersions.Instance + }; + + // [version] + public static bool TryParse(string? value, out ProductAvailability? availability) + { + if (string.IsNullOrWhiteSpace(value) || string.Equals(value.Trim(), "all", StringComparison.InvariantCultureIgnoreCase)) + { + availability = GenerallyAvailable; + return true; + } + + var tokens = value.Split(" ", StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries); + if (tokens.Length < 1) + { + availability = null; + return false; + } + var lifecycle = tokens[0].ToLowerInvariant() switch + { + "preview" => ProductLifecycle.TechnicalPreview, + "tech-preview" => ProductLifecycle.TechnicalPreview, + "beta" => ProductLifecycle.Beta, + "dev" => ProductLifecycle.Development, + "development" => ProductLifecycle.Development, + "deprecated" => ProductLifecycle.Deprecated, + "coming" => ProductLifecycle.Coming, + "discontinued" => ProductLifecycle.Discontinued, + "unavailable" => ProductLifecycle.Unavailable, + "ga" => ProductLifecycle.GenerallyAvailable, + _ => throw new ArgumentOutOfRangeException(nameof(tokens), tokens, $"Unknown product lifecycle: {tokens[0]}") + }; + + var version = tokens.Length < 2 ? null : tokens[1] switch + { + null => AllVersions.Instance, + "all" => AllVersions.Instance, + "" => AllVersions.Instance, + var t => SemVersionConverter.TryParse(t, out var v) ? v : null + }; + availability = new ProductAvailability { Version = version, Lifecycle = lifecycle }; + return true; + } +} diff --git a/src/Elastic.Markdown/Myst/FrontMatter/ProductLifecycle.cs b/src/Elastic.Markdown/Myst/FrontMatter/ProductLifecycle.cs new file mode 100644 index 0000000..8ce224f --- /dev/null +++ b/src/Elastic.Markdown/Myst/FrontMatter/ProductLifecycle.cs @@ -0,0 +1,36 @@ +// 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 YamlDotNet.Serialization; + +namespace Elastic.Markdown.Myst.FrontMatter; + +[YamlSerializable] +public enum ProductLifecycle +{ + // technical preview (exists in current docs system per https://github.com/elastic/docs?tab=readme-ov-file#beta-dev-and-preview-experimental) + [YamlMember(Alias = "preview")] + TechnicalPreview, + // beta (ditto) + [YamlMember(Alias = "beta")] + Beta, + // dev (ditto, though it's uncertain whether it's ever used or still needed) + [YamlMember(Alias = "development")] + Development, + // deprecated (exists in current docs system per https://github.com/elastic/docs?tab=readme-ov-file#additions-and-deprecations) + [YamlMember(Alias = "deprecated")] + Deprecated, + // coming (ditto) + [YamlMember(Alias = "coming")] + Coming, + // discontinued (historically we've immediately removed content when the feature ceases to be supported, but this might not be the case with pages that contain information that spans versions) + [YamlMember(Alias = "discontinued")] + Discontinued, + // unavailable (for content that doesn't exist in a specific context and is never coming or not coming anytime soon) + [YamlMember(Alias = "unavailable")] + Unavailable, + // ga (replaces "added" in the current docs system since it was not entirely clear how/if that overlapped with beta/preview states) + [YamlMember(Alias = "ga")] + GenerallyAvailable +}