Skip to content

Commit

Permalink
Include heading level overried for deployment applicability (#104)
Browse files Browse the repository at this point in the history
  • Loading branch information
Mpdreamz authored Dec 13, 2024
1 parent 20d5f86 commit af7048c
Show file tree
Hide file tree
Showing 7 changed files with 196 additions and 8 deletions.
14 changes: 13 additions & 1 deletion docs/source/markup/applies.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,4 +45,16 @@ applies:
serverless: beta
```

Are equivalent, note `all` just means we won't be rendering the version portion in the html.
Are equivalent, note `all` just means we won't be rendering the version portion in the html.


## This section has its own applies annotations
```{applies}
:stack: unavailable
:serverless: tech-preview
```

This section describes a feature that's unavailable in `stack` and in tech preview on `serverless`


the `{applies}` directive **MUST** be preceded by a heading.
68 changes: 68 additions & 0 deletions src/Elastic.Markdown/Myst/Directives/AppliesBlock.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
// 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.Myst.FrontMatter;
using Markdig.Syntax;

namespace Elastic.Markdown.Myst.Directives;

public class AppliesBlock(DirectiveBlockParser parser, Dictionary<string, string> properties)
: DirectiveBlock(parser, properties)
{
public override string Directive => "mermaid";

public Deployment? Deployment { get; private set; }

public override void FinalizeAndValidate(ParserContext context)
{
if (TryGetAvailability("stack", out var version))
{
Deployment ??= new Deployment();
Deployment.SelfManaged ??= new SelfManagedDeployment();
Deployment.SelfManaged.Stack = version;
}
if (TryGetAvailability("ece", out version))
{
Deployment ??= new Deployment();
Deployment.SelfManaged ??= new SelfManagedDeployment();
Deployment.SelfManaged.Ece = version;
}
if (TryGetAvailability("eck", out version))
{
Deployment ??= new Deployment();
Deployment.SelfManaged ??= new SelfManagedDeployment();
Deployment.SelfManaged.Eck = version;
}
if (TryGetAvailability("hosted", out version))
{
Deployment ??= new Deployment();
Deployment.Cloud ??= new CloudManagedDeployment();
Deployment.Cloud.Hosted = version;
}
if (TryGetAvailability("serverless", out version))
{
Deployment ??= new Deployment();
Deployment.Cloud ??= new CloudManagedDeployment();
Deployment.Cloud.Serverless = version;
}

if (Deployment is null)
EmitError(context, "{applies} block with no product availability specified");

var index = Parent?.IndexOf(this);
if (Parent is not null && index > 0)
{
var i = index - 1 ?? 0;
var prevSib = Parent[i];
if (prevSib is not HeadingBlock)
EmitError(context, "{applies} should follow a heading");
}

bool TryGetAvailability(string key, out ProductAvailability? semVersion)
{
semVersion = null;
return Prop(key) is {} v && ProductAvailability.TryParse(v, out semVersion);
}
}
}
3 changes: 3 additions & 0 deletions src/Elastic.Markdown/Myst/Directives/DirectiveBlockParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,9 @@ protected override DirectiveBlock CreateFencedBlock(BlockProcessor processor)
if (info.IndexOf("{literalinclude}") > 0)
return new LiteralIncludeBlock(this, _admonitionData, context);

if (info.IndexOf("{applies}") > 0)
return new AppliesBlock(this, _admonitionData);

foreach (var admonition in _admonitions)
{
if (info.IndexOf($"{{{admonition}}}") > 0)
Expand Down
19 changes: 19 additions & 0 deletions src/Elastic.Markdown/Myst/Directives/DirectiveHtmlRenderer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.

using Elastic.Markdown.Myst.FrontMatter;
using Elastic.Markdown.Myst.Substitution;
using Elastic.Markdown.Slices;
using Elastic.Markdown.Slices.Directives;
Expand Down Expand Up @@ -34,6 +35,9 @@ protected override void Write(HtmlRenderer renderer, DirectiveBlock directiveBlo
case MermaidBlock mermaidBlock:
WriteMermaid(renderer, mermaidBlock);
return;
case AppliesBlock appliesBlock:
WriteApplies(renderer, appliesBlock);
return;
case FigureBlock imageBlock:
WriteFigure(renderer, imageBlock);
return;
Expand Down Expand Up @@ -179,6 +183,15 @@ private void WriteMermaid(HtmlRenderer renderer, MermaidBlock block)
RenderRazorSliceRawContent(slice, renderer, block);
}

private void WriteApplies(HtmlRenderer renderer, AppliesBlock block)
{
if (block.Deployment is null || block.Deployment == Deployment.All)
return;

var slice = Applies.Create(block.Deployment);
RenderRazorSliceNoContent(slice, renderer);
}

private void WriteTabItem(HtmlRenderer renderer, TabItemBlock block)
{
var slice = TabItem.Create(new TabItemViewModel
Expand Down Expand Up @@ -240,6 +253,12 @@ private static void RenderRazorSlice<T>(RazorSlice<T> slice, HtmlRenderer render
renderer.Write(blocks[1]);
}

private static void RenderRazorSliceNoContent<T>(RazorSlice<T> slice, HtmlRenderer renderer)
{
var html = slice.RenderAsync().GetAwaiter().GetResult();
renderer.Write(html);
}

private static void RenderRazorSliceRawContent<T>(RazorSlice<T> slice, HtmlRenderer renderer, DirectiveBlock obj)
{
var html = slice.RenderAsync().GetAwaiter().GetResult();
Expand Down
13 changes: 6 additions & 7 deletions src/Elastic.Markdown/Myst/FrontMatter/Deployment.cs
Original file line number Diff line number Diff line change
Expand Up @@ -79,35 +79,34 @@ public class DeploymentConverter : IYamlTypeConverter
return null;

var deployment = new Deployment();

if (TryGetVersion("stack", out var version))
if (TryGetAvailability("stack", out var version))
{
deployment.SelfManaged ??= new SelfManagedDeployment();
deployment.SelfManaged.Stack = version;
}
if (TryGetVersion("ece", out version))
if (TryGetAvailability("ece", out version))
{
deployment.SelfManaged ??= new SelfManagedDeployment();
deployment.SelfManaged.Ece = version;
}
if (TryGetVersion("eck", out version))
if (TryGetAvailability("eck", out version))
{
deployment.SelfManaged ??= new SelfManagedDeployment();
deployment.SelfManaged.Eck = version;
}
if (TryGetVersion("hosted", out version))
if (TryGetAvailability("hosted", out version))
{
deployment.Cloud ??= new CloudManagedDeployment();
deployment.Cloud.Hosted = version;
}
if (TryGetVersion("serverless", out version))
if (TryGetAvailability("serverless", out version))
{
deployment.Cloud ??= new CloudManagedDeployment();
deployment.Cloud.Serverless = version;
}
return deployment;

bool TryGetVersion(string key, out ProductAvailability? semVersion)
bool TryGetAvailability(string key, out ProductAvailability? semVersion)
{
semVersion = null;
return dictionary.TryGetValue(key, out var v) && ProductAvailability.TryParse(v, out semVersion);
Expand Down
9 changes: 9 additions & 0 deletions src/Elastic.Markdown/_static/custom.css
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ h1 {
}
.product-availability {
padding-bottom: 0.8em;
}

h1 + .product-availability {
border-bottom: 1px solid #dfdfdf;
}

Expand All @@ -56,6 +59,12 @@ h1:has(+ .product-availability) {
border-bottom: none;
}

section:has(+ .product-availability) h2 {
margin-bottom: 0.0em;
padding-bottom: 0;
border-bottom: none;
}

.applies-to-label {
font-size: 1em;
margin-top: 0.4em;
Expand Down
78 changes: 78 additions & 0 deletions tests/Elastic.Markdown.Tests/Directives/AppliesBlockTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
// 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.Diagnostics;
using Elastic.Markdown.Myst.Directives;
using FluentAssertions;
using Xunit.Abstractions;

namespace Elastic.Markdown.Tests.Directives;

public class AppliesBlockTests(ITestOutputHelper output) : DirectiveTest<AppliesBlock>(output,
"""
# heading
```{applies}
:eck: unavailable
```
"""
)
{
[Fact]
public void ParsesBlock() => Block.Should().NotBeNull();

[Fact]
public void IncludesProductAvailability() =>
Html.Should().Contain("Unavailable</span>")
.And.Contain("Elastic Cloud Kubernetes")
.And.Contain("Applies To:");


[Fact]
public void NoErrors() => Collector.Diagnostics.Should().BeEmpty();
}

public class EmptyAppliesBlock(ITestOutputHelper output) : DirectiveTest<AppliesBlock>(output,
"""
```{applies}
```
"""
)
{
[Fact]
public void ParsesBlock() => Block.Should().NotBeNull();

[Fact]
public void DoesNotRender() =>
Html.Should().BeNullOrWhiteSpace();

[Fact]
public void EmitErrorOnEmptyBlock()
{
Collector.Diagnostics.Should().NotBeNullOrEmpty().And.HaveCount(2);
Collector.Diagnostics.Should().OnlyContain(d => d.Severity == Severity.Error);
Collector.Diagnostics.Should()
.Contain(d => d.Message.Contains("{applies} block with no product availability specified"));

Collector.Diagnostics.Should()
.Contain(d => d.Message.Contains("{applies} should follow a heading"));
}
}

// ensures we allow for empty lines between heading and applies block
public class AppliesHeadingTests(ITestOutputHelper output) : DirectiveTest<AppliesBlock>(output,
"""
# heading
```{applies}
:eck: unavailable
```
"""
)
{
[Fact]
public void NoErrors() => Collector.Diagnostics.Should().BeEmpty();
}

0 comments on commit af7048c

Please sign in to comment.