Skip to content

Commit af7048c

Browse files
authored
Include heading level overried for deployment applicability (#104)
1 parent 20d5f86 commit af7048c

File tree

7 files changed

+196
-8
lines changed

7 files changed

+196
-8
lines changed

docs/source/markup/applies.md

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,4 +45,16 @@ applies:
4545
serverless: beta
4646
```
4747

48-
Are equivalent, note `all` just means we won't be rendering the version portion in the html.
48+
Are equivalent, note `all` just means we won't be rendering the version portion in the html.
49+
50+
51+
## This section has its own applies annotations
52+
```{applies}
53+
:stack: unavailable
54+
:serverless: tech-preview
55+
```
56+
57+
This section describes a feature that's unavailable in `stack` and in tech preview on `serverless`
58+
59+
60+
the `{applies}` directive **MUST** be preceded by a heading.
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
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 Elastic.Markdown.Myst.FrontMatter;
6+
using Markdig.Syntax;
7+
8+
namespace Elastic.Markdown.Myst.Directives;
9+
10+
public class AppliesBlock(DirectiveBlockParser parser, Dictionary<string, string> properties)
11+
: DirectiveBlock(parser, properties)
12+
{
13+
public override string Directive => "mermaid";
14+
15+
public Deployment? Deployment { get; private set; }
16+
17+
public override void FinalizeAndValidate(ParserContext context)
18+
{
19+
if (TryGetAvailability("stack", out var version))
20+
{
21+
Deployment ??= new Deployment();
22+
Deployment.SelfManaged ??= new SelfManagedDeployment();
23+
Deployment.SelfManaged.Stack = version;
24+
}
25+
if (TryGetAvailability("ece", out version))
26+
{
27+
Deployment ??= new Deployment();
28+
Deployment.SelfManaged ??= new SelfManagedDeployment();
29+
Deployment.SelfManaged.Ece = version;
30+
}
31+
if (TryGetAvailability("eck", out version))
32+
{
33+
Deployment ??= new Deployment();
34+
Deployment.SelfManaged ??= new SelfManagedDeployment();
35+
Deployment.SelfManaged.Eck = version;
36+
}
37+
if (TryGetAvailability("hosted", out version))
38+
{
39+
Deployment ??= new Deployment();
40+
Deployment.Cloud ??= new CloudManagedDeployment();
41+
Deployment.Cloud.Hosted = version;
42+
}
43+
if (TryGetAvailability("serverless", out version))
44+
{
45+
Deployment ??= new Deployment();
46+
Deployment.Cloud ??= new CloudManagedDeployment();
47+
Deployment.Cloud.Serverless = version;
48+
}
49+
50+
if (Deployment is null)
51+
EmitError(context, "{applies} block with no product availability specified");
52+
53+
var index = Parent?.IndexOf(this);
54+
if (Parent is not null && index > 0)
55+
{
56+
var i = index - 1 ?? 0;
57+
var prevSib = Parent[i];
58+
if (prevSib is not HeadingBlock)
59+
EmitError(context, "{applies} should follow a heading");
60+
}
61+
62+
bool TryGetAvailability(string key, out ProductAvailability? semVersion)
63+
{
64+
semVersion = null;
65+
return Prop(key) is {} v && ProductAvailability.TryParse(v, out semVersion);
66+
}
67+
}
68+
}

src/Elastic.Markdown/Myst/Directives/DirectiveBlockParser.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,9 @@ protected override DirectiveBlock CreateFencedBlock(BlockProcessor processor)
111111
if (info.IndexOf("{literalinclude}") > 0)
112112
return new LiteralIncludeBlock(this, _admonitionData, context);
113113

114+
if (info.IndexOf("{applies}") > 0)
115+
return new AppliesBlock(this, _admonitionData);
116+
114117
foreach (var admonition in _admonitions)
115118
{
116119
if (info.IndexOf($"{{{admonition}}}") > 0)

src/Elastic.Markdown/Myst/Directives/DirectiveHtmlRenderer.cs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
// This file is licensed under the BSD-Clause 2 license.
66
// See the license.txt file in the project root for more information.
77

8+
using Elastic.Markdown.Myst.FrontMatter;
89
using Elastic.Markdown.Myst.Substitution;
910
using Elastic.Markdown.Slices;
1011
using Elastic.Markdown.Slices.Directives;
@@ -34,6 +35,9 @@ protected override void Write(HtmlRenderer renderer, DirectiveBlock directiveBlo
3435
case MermaidBlock mermaidBlock:
3536
WriteMermaid(renderer, mermaidBlock);
3637
return;
38+
case AppliesBlock appliesBlock:
39+
WriteApplies(renderer, appliesBlock);
40+
return;
3741
case FigureBlock imageBlock:
3842
WriteFigure(renderer, imageBlock);
3943
return;
@@ -179,6 +183,15 @@ private void WriteMermaid(HtmlRenderer renderer, MermaidBlock block)
179183
RenderRazorSliceRawContent(slice, renderer, block);
180184
}
181185

186+
private void WriteApplies(HtmlRenderer renderer, AppliesBlock block)
187+
{
188+
if (block.Deployment is null || block.Deployment == Deployment.All)
189+
return;
190+
191+
var slice = Applies.Create(block.Deployment);
192+
RenderRazorSliceNoContent(slice, renderer);
193+
}
194+
182195
private void WriteTabItem(HtmlRenderer renderer, TabItemBlock block)
183196
{
184197
var slice = TabItem.Create(new TabItemViewModel
@@ -240,6 +253,12 @@ private static void RenderRazorSlice<T>(RazorSlice<T> slice, HtmlRenderer render
240253
renderer.Write(blocks[1]);
241254
}
242255

256+
private static void RenderRazorSliceNoContent<T>(RazorSlice<T> slice, HtmlRenderer renderer)
257+
{
258+
var html = slice.RenderAsync().GetAwaiter().GetResult();
259+
renderer.Write(html);
260+
}
261+
243262
private static void RenderRazorSliceRawContent<T>(RazorSlice<T> slice, HtmlRenderer renderer, DirectiveBlock obj)
244263
{
245264
var html = slice.RenderAsync().GetAwaiter().GetResult();

src/Elastic.Markdown/Myst/FrontMatter/Deployment.cs

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -79,35 +79,34 @@ public class DeploymentConverter : IYamlTypeConverter
7979
return null;
8080

8181
var deployment = new Deployment();
82-
83-
if (TryGetVersion("stack", out var version))
82+
if (TryGetAvailability("stack", out var version))
8483
{
8584
deployment.SelfManaged ??= new SelfManagedDeployment();
8685
deployment.SelfManaged.Stack = version;
8786
}
88-
if (TryGetVersion("ece", out version))
87+
if (TryGetAvailability("ece", out version))
8988
{
9089
deployment.SelfManaged ??= new SelfManagedDeployment();
9190
deployment.SelfManaged.Ece = version;
9291
}
93-
if (TryGetVersion("eck", out version))
92+
if (TryGetAvailability("eck", out version))
9493
{
9594
deployment.SelfManaged ??= new SelfManagedDeployment();
9695
deployment.SelfManaged.Eck = version;
9796
}
98-
if (TryGetVersion("hosted", out version))
97+
if (TryGetAvailability("hosted", out version))
9998
{
10099
deployment.Cloud ??= new CloudManagedDeployment();
101100
deployment.Cloud.Hosted = version;
102101
}
103-
if (TryGetVersion("serverless", out version))
102+
if (TryGetAvailability("serverless", out version))
104103
{
105104
deployment.Cloud ??= new CloudManagedDeployment();
106105
deployment.Cloud.Serverless = version;
107106
}
108107
return deployment;
109108

110-
bool TryGetVersion(string key, out ProductAvailability? semVersion)
109+
bool TryGetAvailability(string key, out ProductAvailability? semVersion)
111110
{
112111
semVersion = null;
113112
return dictionary.TryGetValue(key, out var v) && ProductAvailability.TryParse(v, out semVersion);

src/Elastic.Markdown/_static/custom.css

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,9 @@ h1 {
4747
}
4848
.product-availability {
4949
padding-bottom: 0.8em;
50+
}
51+
52+
h1 + .product-availability {
5053
border-bottom: 1px solid #dfdfdf;
5154
}
5255

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

62+
section:has(+ .product-availability) h2 {
63+
margin-bottom: 0.0em;
64+
padding-bottom: 0;
65+
border-bottom: none;
66+
}
67+
5968
.applies-to-label {
6069
font-size: 1em;
6170
margin-top: 0.4em;
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
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 Elastic.Markdown.Diagnostics;
6+
using Elastic.Markdown.Myst.Directives;
7+
using FluentAssertions;
8+
using Xunit.Abstractions;
9+
10+
namespace Elastic.Markdown.Tests.Directives;
11+
12+
public class AppliesBlockTests(ITestOutputHelper output) : DirectiveTest<AppliesBlock>(output,
13+
"""
14+
# heading
15+
```{applies}
16+
:eck: unavailable
17+
```
18+
"""
19+
)
20+
{
21+
[Fact]
22+
public void ParsesBlock() => Block.Should().NotBeNull();
23+
24+
[Fact]
25+
public void IncludesProductAvailability() =>
26+
Html.Should().Contain("Unavailable</span>")
27+
.And.Contain("Elastic Cloud Kubernetes")
28+
.And.Contain("Applies To:");
29+
30+
31+
[Fact]
32+
public void NoErrors() => Collector.Diagnostics.Should().BeEmpty();
33+
}
34+
35+
public class EmptyAppliesBlock(ITestOutputHelper output) : DirectiveTest<AppliesBlock>(output,
36+
"""
37+
```{applies}
38+
```
39+
"""
40+
)
41+
{
42+
[Fact]
43+
public void ParsesBlock() => Block.Should().NotBeNull();
44+
45+
[Fact]
46+
public void DoesNotRender() =>
47+
Html.Should().BeNullOrWhiteSpace();
48+
49+
[Fact]
50+
public void EmitErrorOnEmptyBlock()
51+
{
52+
Collector.Diagnostics.Should().NotBeNullOrEmpty().And.HaveCount(2);
53+
Collector.Diagnostics.Should().OnlyContain(d => d.Severity == Severity.Error);
54+
Collector.Diagnostics.Should()
55+
.Contain(d => d.Message.Contains("{applies} block with no product availability specified"));
56+
57+
Collector.Diagnostics.Should()
58+
.Contain(d => d.Message.Contains("{applies} should follow a heading"));
59+
}
60+
}
61+
62+
// ensures we allow for empty lines between heading and applies block
63+
public class AppliesHeadingTests(ITestOutputHelper output) : DirectiveTest<AppliesBlock>(output,
64+
"""
65+
# heading
66+
67+
68+
69+
```{applies}
70+
:eck: unavailable
71+
```
72+
"""
73+
)
74+
{
75+
[Fact]
76+
public void NoErrors() => Collector.Diagnostics.Should().BeEmpty();
77+
}
78+

0 commit comments

Comments
 (0)