Skip to content

Commit a6ea1ab

Browse files
committed
Add dependencies for mermaid and related libraries
Included `"mermaid": "11.0.2"` and other required dependencies to support additional functionalities.
1 parent f60bdb8 commit a6ea1ab

File tree

45 files changed

+2606
-0
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+2606
-0
lines changed
Lines changed: 338 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,338 @@
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 System.Text.RegularExpressions;
6+
using Elastic.Markdown.Diagnostics;
7+
using Elastic.Markdown.Helpers;
8+
using Elastic.Markdown.Myst.Directives;
9+
using Elastic.Markdown.Myst.FrontMatter;
10+
using Markdig.Helpers;
11+
using Markdig.Parsers;
12+
using Markdig.Syntax;
13+
14+
namespace Elastic.Markdown.Myst.CodeBlocks;
15+
16+
public class AppliesToDirectiveParser : FencedBlockParserBase<AppliesToDirective>
17+
{
18+
private const string DefaultInfoPrefix = "language-";
19+
20+
/// <summary>
21+
/// Initializes a new instance of the <see cref="FencedCodeBlockParser"/> class.
22+
/// </summary>
23+
public AppliesToDirectiveParser()
24+
{
25+
OpeningCharacters = ['`'];
26+
InfoPrefix = DefaultInfoPrefix;
27+
InfoParser = RoundtripInfoParser;
28+
}
29+
30+
protected override EnhancedCodeBlock CreateFencedBlock(BlockProcessor processor)
31+
{
32+
if (processor.Context is not ParserContext context)
33+
throw new Exception("Expected parser context to be of type ParserContext");
34+
35+
var lineSpan = processor.Line.AsSpan();
36+
var codeBlock = lineSpan.IndexOf("{applies_to}") > -1
37+
? new AppliesToDirective(this, context)
38+
{
39+
IndentCount = processor.Indent
40+
}
41+
: new EnhancedCodeBlock(this, context)
42+
{
43+
IndentCount = processor.Indent
44+
};
45+
46+
if (processor.TrackTrivia)
47+
{
48+
// mimic what internal method LinesBefore() does
49+
codeBlock.LinesBefore = processor.LinesBefore;
50+
processor.LinesBefore = null;
51+
52+
codeBlock.TriviaBefore = processor.UseTrivia(processor.Start - 1);
53+
codeBlock.NewLine = processor.Line.NewLine;
54+
}
55+
56+
return codeBlock;
57+
}
58+
59+
public override BlockState TryContinue(BlockProcessor processor, Block block)
60+
{
61+
var result = base.TryContinue(processor, block);
62+
if (result == BlockState.Continue && !processor.TrackTrivia)
63+
{
64+
var fence = (EnhancedCodeBlock)block;
65+
// Remove any indent spaces
66+
var c = processor.CurrentChar;
67+
var indentCount = fence.IndentCount;
68+
while (indentCount > 0 && c.IsSpace())
69+
{
70+
indentCount--;
71+
c = processor.NextChar();
72+
}
73+
}
74+
75+
return result;
76+
}
77+
78+
public override bool Close(BlockProcessor processor, Block block)
79+
{
80+
if (block is not EnhancedCodeBlock codeBlock)
81+
return base.Close(processor, block);
82+
83+
if (processor.Context is not ParserContext context)
84+
throw new Exception("Expected parser context to be of type ParserContext");
85+
86+
codeBlock.Language = (
87+
(codeBlock.Info?.IndexOf('{') ?? -1) != -1
88+
? codeBlock.Arguments?.Split()[0]
89+
: codeBlock.Info
90+
) ?? "unknown";
91+
92+
var language = codeBlock.Language;
93+
codeBlock.Language = language switch
94+
{
95+
"console" => "json",
96+
"console-response" => "json",
97+
"console-result" => "json",
98+
"terminal" => "bash",
99+
"painless" => "java",
100+
//TODO support these natively
101+
"kuery" => "json",
102+
"lucene" => "json",
103+
_ => codeBlock.Language
104+
};
105+
if (!string.IsNullOrEmpty(codeBlock.Language) && !CodeBlock.Languages.Contains(codeBlock.Language))
106+
codeBlock.EmitWarning($"Unknown language: {codeBlock.Language}");
107+
108+
var lines = codeBlock.Lines;
109+
// ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
110+
if (lines.Lines is null)
111+
return base.Close(processor, block);
112+
113+
if (codeBlock is not AppliesToDirective appliesToDirective)
114+
ProcessCodeBlock(lines, language, codeBlock, context);
115+
else
116+
ProcessAppliesToDirective(appliesToDirective, lines);
117+
118+
return base.Close(processor, block);
119+
}
120+
121+
private static void ProcessAppliesToDirective(AppliesToDirective appliesToDirective, StringLineGroup lines)
122+
{
123+
var yaml = lines.ToSlice().AsSpan().ToString();
124+
125+
try
126+
{
127+
var applicableTo = YamlSerialization.Deserialize<ApplicableTo>(yaml);
128+
appliesToDirective.AppliesTo = applicableTo;
129+
if (appliesToDirective.AppliesTo.Warnings is null)
130+
return;
131+
foreach (var warning in appliesToDirective.AppliesTo.Warnings)
132+
appliesToDirective.EmitWarning(warning);
133+
applicableTo.Warnings = null;
134+
}
135+
catch (Exception e)
136+
{
137+
appliesToDirective.EmitError($"Unable to parse applies_to directive: {yaml}", e);
138+
}
139+
}
140+
141+
private static void ProcessCodeBlock(
142+
StringLineGroup lines,
143+
string language,
144+
EnhancedCodeBlock codeBlock,
145+
ParserContext context)
146+
{
147+
string argsString;
148+
if (codeBlock.Arguments == null)
149+
argsString = "";
150+
else if (codeBlock.Info?.IndexOf('{') == -1)
151+
argsString = codeBlock.Arguments ?? "";
152+
else
153+
{
154+
// if the code block starts with {code-block} and is followed by a language, we need to skip the language
155+
var parts = codeBlock.Arguments.Split();
156+
argsString = parts.Length > 1 && CodeBlock.Languages.Contains(parts[0])
157+
? string.Join(" ", parts[1..])
158+
: codeBlock.Arguments;
159+
}
160+
161+
var codeBlockArgs = CodeBlockArguments.Default;
162+
if (!CodeBlockArguments.TryParse(argsString, out var codeArgs))
163+
codeBlock.EmitError($"Unable to parse code block arguments: {argsString}. Valid arguments are {CodeBlockArguments.KnownKeysString}.");
164+
else
165+
codeBlockArgs = codeArgs;
166+
167+
var callOutIndex = 0;
168+
var originatingLine = 0;
169+
for (var index = 0; index < lines.Lines.Length; index++)
170+
{
171+
originatingLine++;
172+
var line = lines.Lines[index];
173+
if (index == 0 && language == "console")
174+
{
175+
codeBlock.ApiCallHeader = line.ToString();
176+
var s = new StringSlice("");
177+
lines.Lines[index] = new StringLine(ref s);
178+
continue;
179+
}
180+
181+
var span = line.Slice.AsSpan();
182+
if (codeBlockArgs.UseSubstitutions)
183+
{
184+
if (span.ReplaceSubstitutions(context.YamlFrontMatter?.Properties, context.Build.Collector, out var frontMatterReplacement))
185+
{
186+
var s = new StringSlice(frontMatterReplacement);
187+
lines.Lines[index] = new StringLine(ref s);
188+
span = lines.Lines[index].Slice.AsSpan();
189+
}
190+
191+
if (span.ReplaceSubstitutions(context.Substitutions, context.Build.Collector, out var globalReplacement))
192+
{
193+
var s = new StringSlice(globalReplacement);
194+
lines.Lines[index] = new StringLine(ref s);
195+
span = lines.Lines[index].Slice.AsSpan();
196+
}
197+
}
198+
199+
if (codeBlock.OpeningFencedCharCount > 3)
200+
continue;
201+
202+
if (codeBlockArgs.UseCallouts)
203+
{
204+
List<CallOut> callOuts = [];
205+
var hasClassicCallout = span.IndexOf("<") > 0 && span.LastIndexOf(">") == span.Length - 1;
206+
if (hasClassicCallout)
207+
{
208+
var matchClassicCallout = CallOutParser.CallOutNumber().EnumerateMatches(span);
209+
callOuts.AddRange(
210+
EnumerateAnnotations(matchClassicCallout, ref span, ref callOutIndex, originatingLine, false)
211+
);
212+
}
213+
214+
// only support magic callouts for smaller line lengths
215+
if (callOuts.Count == 0 && span.Length < 200)
216+
{
217+
var matchInline = CallOutParser.MathInlineAnnotation().EnumerateMatches(span);
218+
callOuts.AddRange(
219+
EnumerateAnnotations(matchInline, ref span, ref callOutIndex, originatingLine, true)
220+
);
221+
}
222+
223+
codeBlock.CallOuts.AddRange(callOuts);
224+
}
225+
}
226+
227+
//update string slices to ignore call outs
228+
if (codeBlock.CallOuts.Count > 0)
229+
{
230+
var callouts = codeBlock.CallOuts.Aggregate(new Dictionary<int, CallOut>(), (acc, curr) =>
231+
{
232+
if (acc.TryAdd(curr.Line, curr))
233+
return acc;
234+
if (acc[curr.Line].SliceStart > curr.SliceStart)
235+
acc[curr.Line] = curr;
236+
return acc;
237+
});
238+
239+
foreach (var callout in callouts.Values)
240+
{
241+
var line = lines.Lines[callout.Line - 1];
242+
var newSpan = line.Slice.AsSpan()[..callout.SliceStart];
243+
var s = new StringSlice(newSpan.ToString());
244+
lines.Lines[callout.Line - 1] = new StringLine(ref s);
245+
}
246+
}
247+
248+
var inlineAnnotations = codeBlock.CallOuts.Count(c => c.InlineCodeAnnotation);
249+
var classicAnnotations = codeBlock.CallOuts.Count - inlineAnnotations;
250+
if (inlineAnnotations > 0 && classicAnnotations > 0)
251+
codeBlock.EmitError("Both inline and classic callouts are not supported");
252+
253+
if (inlineAnnotations > 0)
254+
codeBlock.InlineAnnotations = true;
255+
}
256+
257+
private static List<CallOut> EnumerateAnnotations(Regex.ValueMatchEnumerator matches,
258+
ref ReadOnlySpan<char> span,
259+
ref int callOutIndex,
260+
int originatingLine,
261+
bool inlineCodeAnnotation)
262+
{
263+
var callOuts = new List<CallOut>();
264+
foreach (var match in matches)
265+
{
266+
if (match.Length == 0)
267+
continue;
268+
269+
if (inlineCodeAnnotation)
270+
{
271+
var callOut = ParseMagicCallout(match, ref span, ref callOutIndex, originatingLine);
272+
if (callOut != null)
273+
return [callOut];
274+
continue;
275+
}
276+
277+
var classicCallOuts = ParseClassicCallOuts(match, ref span, ref callOutIndex, originatingLine);
278+
callOuts.AddRange(classicCallOuts);
279+
}
280+
281+
return callOuts;
282+
}
283+
284+
private static CallOut? ParseMagicCallout(ValueMatch match, ref ReadOnlySpan<char> span, ref int callOutIndex, int originatingLine)
285+
{
286+
var startIndex = Math.Max(span.LastIndexOf(" // "), span.LastIndexOf(" # "));
287+
if (startIndex <= 0)
288+
return null;
289+
290+
callOutIndex++;
291+
var callout = span.Slice(match.Index + startIndex, match.Length - startIndex);
292+
293+
return new CallOut
294+
{
295+
Index = callOutIndex,
296+
Text = callout.TrimStart().TrimStart('/').TrimStart('#').TrimStart().ToString(),
297+
InlineCodeAnnotation = true,
298+
SliceStart = startIndex,
299+
Line = originatingLine,
300+
};
301+
}
302+
303+
private static List<CallOut> ParseClassicCallOuts(ValueMatch match, ref ReadOnlySpan<char> span, ref int callOutIndex, int originatingLine)
304+
{
305+
var indexOfLastComment = Math.Max(span.LastIndexOf(" # "), span.LastIndexOf(" // "));
306+
var startIndex = span.LastIndexOf('<');
307+
if (startIndex <= 0)
308+
return [];
309+
310+
var allStartIndices = new List<int>();
311+
for (var i = 0; i < span.Length; i++)
312+
{
313+
if (span[i] == '<')
314+
allStartIndices.Add(i);
315+
}
316+
317+
var callOuts = new List<CallOut>();
318+
foreach (var individualStartIndex in allStartIndices)
319+
{
320+
callOutIndex++;
321+
var endIndex = span[(match.Index + individualStartIndex)..].IndexOf('>') + 1;
322+
var callout = span.Slice(match.Index + individualStartIndex, endIndex);
323+
if (int.TryParse(callout.Trim(['<', '>']), out var index))
324+
{
325+
callOuts.Add(new CallOut
326+
{
327+
Index = index,
328+
Text = callout.TrimStart('/').TrimStart('#').TrimStart().ToString(),
329+
InlineCodeAnnotation = false,
330+
SliceStart = indexOfLastComment > 0 ? indexOfLastComment : startIndex,
331+
Line = originatingLine,
332+
});
333+
}
334+
}
335+
336+
return callOuts;
337+
}
338+
}

0 commit comments

Comments
 (0)