Skip to content

Commit 1ae90f5

Browse files
committed
Add directive block parsing for applies_to
1 parent 8387e54 commit 1ae90f5

File tree

12 files changed

+254
-38
lines changed

12 files changed

+254
-38
lines changed

docs/syntax/applies.md

Lines changed: 107 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -15,38 +15,42 @@ applies_to:
1515

1616
# Applies to
1717

18+
Allows you to annotate a page or section's applicability.
1819

19-
Using yaml frontmatter pages can explicitly indicate to each deployment targets availability and lifecycle status
20-
20+
### Syntax
2121

22-
```yaml
23-
applies_to:
24-
stack: ga 9.1
25-
deployment:
26-
eck: ga 9.0
27-
ess: beta 9.1
28-
ece: discontinued 9.2.0
29-
self: unavailable 9.3.0
30-
serverless:
31-
security: ga 9.0.0
32-
elasticsearch: beta 9.1.0
33-
observability: discontinued 9.2.0
34-
product: coming 9.5, discontinued 9.7
22+
```
23+
<life-cycle> [version], <life-cycle> [version]
3524
```
3625

37-
Its syntax is
26+
Taking a mandatory [life-cycle](#life-cycle) with an optional version.
27+
28+
#### Life cycle:
29+
* `preview`
30+
* `beta`
31+
* `development`
32+
* `deprecated`
33+
* `coming`
34+
* `discontinued`
35+
* `unavailable`
36+
* `ga`
37+
38+
#### Version
39+
40+
Can be in either `major.minor` or `major.minor.patch` format
41+
42+
#### Examples
3843

3944
```
40-
<product>: <lifecycle> [version]
45+
coming 9.5, discontinued 9.7
46+
discontinued 9.2.0
47+
all
4148
```
4249

43-
Where version is optional.
44-
4550
`all` and empty string mean generally available for all active versions
4651

4752
```yaml
4853
applies:
49-
stack:
5054
serverless: all
5155
```
5256
@@ -58,19 +62,94 @@ applies:
5862
serverless: beta
5963
```
6064

61-
Are equivalent, note `all` just means we won't be rendering the version portion in the html.
65+
Both are equivalent, note `all` just means we won't be rendering the version portion in the html.
6266

6367

64-
## This section has its own applies annotations [#sections]
68+
## Structured model
6569

66-
:::{applies}
67-
:serverless: unavailable
68-
:::
70+
![Applies To Model](img/applies.png)
71+
72+
The above model is projected to the following structured yaml.
73+
74+
```yaml
75+
---
76+
applies_to:
77+
stack:
78+
deployment:
79+
eck:
80+
ess:
81+
ece:
82+
self:
83+
serverless:
84+
security:
85+
elasticsearch:
86+
observability:
87+
product:
88+
---
89+
```
90+
This allows you to annotate various facets as defined in [](../migration/versioning.md)
91+
92+
## Page annotations
93+
94+
Using yaml frontmatter pages can explicitly indicate to each deployment targets availability and lifecycle status
95+
96+
97+
```yaml
98+
---
99+
applies_to:
100+
stack: ga 9.1
101+
deployment:
102+
eck: ga 9.0
103+
ess: beta 9.1
104+
ece: discontinued 9.2.0
105+
self: unavailable 9.3.0
106+
serverless:
107+
security: ga 9.0.0
108+
elasticsearch: beta 9.1.0
109+
observability: discontinued 9.2.0
110+
product: coming 9.5, discontinued 9.7
111+
---
112+
```
113+
114+
115+
## Section annotation [#sections]
116+
117+
```yaml {applies_to}
118+
stack: ga 9.1
119+
deployment:
120+
eck: ga 9.0
121+
ess: beta 9.1
122+
ece: discontinued 9.2.0
123+
self: unavailable 9.3.0
124+
serverless:
125+
security: ga 9.0.0
126+
elasticsearch: beta 9.1.0
127+
observability: discontinued 9.2.0
128+
product: coming 9.5, discontinued 9.7
129+
```
130+
131+
A header may be followed by an `{applies_to}` directive which will contextualize the applicability
132+
of the section further.
69133

70134
:::{note}
71-
the `{applies}` directive **MUST** be preceded by a heading.
135+
the `{applies_to}` directive **MUST** be preceded by a heading directly.
72136
:::
73137

74138

75-
This section describes a feature that's unavailable in `stack` and `ga` in all cloud products
76-
however its tech preview on `serverless` since it overrides what `cloud` specified.
139+
Note that this directive needs triple backticks since its content is literal. See also [](index.md#literal-directives)
140+
141+
````markdown
142+
```{applies_to}
143+
stack: ga 9.1
144+
```
145+
````
146+
147+
In order to play even better with markdown editors the following is also supported:
148+
149+
````markdown
150+
```yaml {applies_to}
151+
stack: ga 9.1
152+
```
153+
````
154+
155+
This will allow the yaml inside the `{applies-to}` directive to be fully highlighted.

docs/syntax/img/applies.png

246 KB
Loading

docs/syntax/index.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ Nested content that will be parsed as markdown
2929

3030
Defining directives with `:::` allows the nested markdown syntax to be highlighted properly by editors and web viewers.
3131

32+
33+
3234
### Nesting Directives
3335

3436
Increase the number of leading semicolons to include nested directives.
@@ -46,6 +48,13 @@ Content displayed in the note admonition
4648

4749
## Literal directives
4850

51+
All directive are indicated with semicolons except literal blocks. For these you need to use triple backticks.
52+
53+
* [Code blocks](code.md)
54+
* [{applies-to} blocks](applies.md)
55+
56+
Since their contents **should not** be parsed as markdown they use backticks. This also ensures maximum interopability with existing markdown editors and previews.
57+
4958
Many Markdown editors support syntax highlighting for embedded code blocks. For compatibility with this feature, use triple backticks instead of triple colons for content that needs to be displayed literally:
5059

5160
````markdown

src/Elastic.Markdown/Myst/CodeBlocks/EnhancedCodeBlock.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,18 @@
44

55
using System.IO.Abstractions;
66
using Elastic.Markdown.Myst.Directives;
7+
using Elastic.Markdown.Myst.FrontMatter;
78
using Markdig.Parsers;
89
using Markdig.Syntax;
910

1011
namespace Elastic.Markdown.Myst.CodeBlocks;
1112

13+
public class AppliesToDirective(BlockParser parser, ParserContext context)
14+
: EnhancedCodeBlock(parser, context)
15+
{
16+
public ApplicableTo? AppliesTo { get; set; }
17+
}
18+
1219
public class EnhancedCodeBlock(BlockParser parser, ParserContext context)
1320
: FencedCodeBlock(parser), IBlockExtension
1421
{

src/Elastic.Markdown/Myst/CodeBlocks/EnhancedCodeBlockHtmlRenderer.cs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,12 @@ private static int CountIndentation(StringSlice slice)
108108

109109
protected override void Write(HtmlRenderer renderer, EnhancedCodeBlock block)
110110
{
111+
if (block is AppliesToDirective appliesToDirective)
112+
{
113+
RenderAppliesToHtml(renderer, appliesToDirective);
114+
return;
115+
}
116+
111117
var callOuts = block.UniqueCallOuts;
112118

113119
var slice = Code.Create(new CodeViewModel
@@ -184,4 +190,14 @@ protected override void Write(HtmlRenderer renderer, EnhancedCodeBlock block)
184190
renderer.WriteLine("</ol>");
185191
}
186192
}
193+
194+
private static void RenderAppliesToHtml(HtmlRenderer renderer, AppliesToDirective appliesToDirective)
195+
{
196+
var appliesTo = appliesToDirective.AppliesTo;
197+
var slice2 = ApplicableTo.Create(appliesTo);
198+
if (appliesTo is null || appliesTo == FrontMatter.ApplicableTo.All)
199+
return;
200+
var html = slice2.RenderAsync().GetAwaiter().GetResult();
201+
renderer.Write(html);
202+
}
187203
}

src/Elastic.Markdown/Myst/CodeBlocks/EnhancedCodeBlockParser.cs

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using System.Text.RegularExpressions;
66
using Elastic.Markdown.Diagnostics;
77
using Elastic.Markdown.Helpers;
8+
using Elastic.Markdown.Myst.FrontMatter;
89
using Markdig.Helpers;
910
using Markdig.Parsers;
1011
using Markdig.Syntax;
@@ -30,7 +31,10 @@ protected override EnhancedCodeBlock CreateFencedBlock(BlockProcessor processor)
3031
if (processor.Context is not ParserContext context)
3132
throw new Exception("Expected parser context to be of type ParserContext");
3233

33-
var codeBlock = new EnhancedCodeBlock(this, context) { IndentCount = processor.Indent };
34+
var lineSpan = processor.Line.AsSpan();
35+
var codeBlock = lineSpan.IndexOf("{applies_to}") > -1
36+
? new AppliesToDirective(this, context) { IndentCount = processor.Indent }
37+
: new EnhancedCodeBlock(this, context) { IndentCount = processor.Indent };
3438

3539
if (processor.TrackTrivia)
3640
{
@@ -91,8 +95,30 @@ public override bool Close(BlockProcessor processor, Block block)
9195
codeBlock.EmitWarning($"Unknown language: {codeBlock.Language}");
9296

9397
var lines = codeBlock.Lines;
94-
var callOutIndex = 0;
98+
// ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
99+
if (lines.Lines is null)
100+
return base.Close(processor, block);
101+
102+
if (codeBlock is not AppliesToDirective appliesToDirective)
103+
ProcessCallOuts(lines, language, codeBlock, context);
104+
else
105+
ProcessAppliesToDirective(appliesToDirective, lines, context);
106+
107+
return base.Close(processor, block);
108+
}
109+
110+
private static void ProcessAppliesToDirective(AppliesToDirective appliesToDirective, StringLineGroup lines, ParserContext context)
111+
{
112+
var yaml = lines.ToSlice().AsSpan().ToString();
95113

114+
var applicableTo = YamlSerialization.Deserialize<ApplicableTo>(yaml);
115+
appliesToDirective.AppliesTo = applicableTo;
116+
}
117+
118+
private static void ProcessCallOuts(StringLineGroup lines, string language, EnhancedCodeBlock codeBlock,
119+
ParserContext context)
120+
{
121+
var callOutIndex = 0;
96122
var originatingLine = 0;
97123
for (var index = 0; index < lines.Lines.Length; index++)
98124
{
@@ -141,7 +167,6 @@ public override bool Close(BlockProcessor processor, Block block)
141167
//update string slices to ignore call outs
142168
if (codeBlock.CallOuts.Count > 0)
143169
{
144-
145170
var callouts = codeBlock.CallOuts.Aggregate(new Dictionary<int, CallOut>(), (acc, curr) =>
146171
{
147172
if (acc.TryAdd(curr.Line, curr))
@@ -168,8 +193,6 @@ public override bool Close(BlockProcessor processor, Block block)
168193

169194
if (inlineAnnotations > 0)
170195
codeBlock.InlineAnnotations = true;
171-
172-
return base.Close(processor, block);
173196
}
174197

175198
private static List<CallOut> EnumerateAnnotations(Regex.ValueMatchEnumerator matches,

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

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,12 +153,19 @@ public override BlockState TryOpen(BlockProcessor processor)
153153
return BlockState.None;
154154
}
155155

156-
if (line.IndexOf("{") == -1)
156+
if (line.IndexOf("{") <= -1)
157157
return BlockState.None;
158158

159159
if (line.IndexOf("}") == -1)
160160
return BlockState.None;
161161

162+
var span = line.AsSpan();
163+
var lastIndent = span.LastIndexOf(":");
164+
var startApplies = span.IndexOf("{applies_to}");
165+
var startOpen = span.IndexOf("{");
166+
if (startOpen > lastIndent + 1 || startApplies != -1)
167+
return BlockState.None;
168+
162169
return base.TryOpen(processor);
163170
}
164171

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
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+
module ``product availability``.``yaml directive``
6+
7+
open Elastic.Markdown.Myst.FrontMatter
8+
open authoring
9+
open authoring.MarkdownDocumentAssertions
10+
open Swensen.Unquote
11+
open Xunit
12+
open Elastic.Markdown.Myst.CodeBlocks
13+
14+
type ``piggy back off yaml formatting`` () =
15+
static let markdown = Setup.Markdown """
16+
```yaml {applies_to}
17+
serverless:
18+
security: ga 9.0.0
19+
elasticsearch: beta 9.1.0
20+
observability: discontinued 9.2.0
21+
```
22+
"""
23+
24+
[<Fact>]
25+
let ``parses to AppliesDirective`` () =
26+
let directives = markdown |> converts "index.md" |> parses<AppliesToDirective>
27+
test <@ directives.Length = 1 @>
28+
29+
directives |> appliesToDirective (ApplicableTo(
30+
Serverless=ServerlessProjectApplicability(
31+
Security=ApplicabilityOverTime.op_Explicit "ga 9.0.0",
32+
Elasticsearch=ApplicabilityOverTime.op_Explicit "beta 9.1.0",
33+
Observability=ApplicabilityOverTime.op_Explicit "discontinued 9.2.0"
34+
)
35+
))
36+
37+
type ``plain block`` () =
38+
static let markdown = Setup.Markdown """
39+
```{applies_to}
40+
serverless:
41+
security: ga 9.0.0
42+
elasticsearch: beta 9.1.0
43+
observability: discontinued 9.2.0
44+
```
45+
"""
46+
47+
[<Fact>]
48+
let ``parses to AppliesDirective`` () =
49+
let directives = markdown |> converts "index.md" |> parses<AppliesToDirective>
50+
test <@ directives.Length = 1 @>
51+
52+
directives |> appliesToDirective (ApplicableTo(
53+
Serverless=ServerlessProjectApplicability(
54+
Security=ApplicabilityOverTime.op_Explicit "ga 9.0.0",
55+
Elasticsearch=ApplicabilityOverTime.op_Explicit "beta 9.1.0",
56+
Observability=ApplicabilityOverTime.op_Explicit "discontinued 9.2.0"
57+
)
58+
))

tests/authoring/Availability/ApplyYaml.fs renamed to tests/authoring/Applicability/AppliesToFrontMatter.fs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,6 @@
44

55
module ``product availability``.``yaml frontmatter``
66

7-
open System.Collections
8-
open Elastic.Markdown.Helpers
97
open Elastic.Markdown.Myst.FrontMatter
108
open JetBrains.Annotations
119
open Xunit

0 commit comments

Comments
 (0)