Skip to content

Commit 3514345

Browse files
reakaleekCopilotbmorelli25Mpdreamz
authored
Add ability to set code block arguments (#699)
* Add ability to set code block arguments * Update src/Elastic.Markdown/Myst/CodeBlocks/CodeBlockArguments.cs Co-authored-by: Copilot <[email protected]> * Fix documentation * fix * fix * Use TryParse pattern and cleanup * Better error message * Apply suggestions from code review Co-authored-by: Brandon Morelli <[email protected]> * Use spans instead of string and convert to record * Refactor * Update src/Elastic.Markdown/Myst/CodeBlocks/CodeBlockArguments.cs Co-authored-by: Martijn Laarman <[email protected]> * Fix renaming * Assign CodeBlockArguments in TryParse --------- Co-authored-by: Copilot <[email protected]> Co-authored-by: Brandon Morelli <[email protected]> Co-authored-by: Martijn Laarman <[email protected]>
1 parent f07e35d commit 3514345

File tree

7 files changed

+405
-39
lines changed

7 files changed

+405
-39
lines changed

docs/syntax/code.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,40 @@ var client = new ElasticsearchClient("<CLOUD_ID>", apiKey);
195195

196196
::::
197197

198+
#### Disable callouts
199+
200+
You can disable callouts by adding a code block argument `callouts=false`.
201+
202+
::::{tab-set}
203+
204+
:::{tab-item} Output
205+
206+
```yaml callouts=false
207+
project:
208+
license:
209+
content: CC-BY-4.0 <1>
210+
```
211+
212+
1. The license
213+
214+
:::
215+
216+
:::{tab-item} Markdown
217+
218+
````markdown
219+
```yaml callouts=false
220+
project:
221+
license:
222+
content: CC-BY-4.0 <1>
223+
```
224+
225+
1. The license
226+
````
227+
228+
:::
229+
230+
::::
231+
198232
### Console code blocks
199233
200234
:::{note}

docs/syntax/substitutions.md

Lines changed: 76 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,19 +36,92 @@ Here are some variable substitutions:
3636
| {{a-key-with-dashes}} | Front Matter |
3737
| {{a-global-variable}} | `docset.yml` |
3838

39-
Substitutions should work in code blocks too.
39+
## Code blocks
4040

41-
```{code} sh
41+
Substitutions are supported in code blocks but are disabled by default. Enable substitutions by adding `subs=true` to the code block.
42+
43+
````markdown
44+
```bash subs=true
45+
# Your code with variables
46+
```
47+
````
48+
49+
### Code directive with subs enabled
50+
51+
::::{tab-set}
52+
53+
:::{tab-item} Output
54+
55+
```{code} sh subs=true
4256
wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-{{version}}-linux-x86_64.tar.gz
4357
wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-{{version}}-linux-x86_64.tar.gz.sha512
4458
shasum -a 512 -c elasticsearch-{{version}}-linux-x86_64.tar.gz.sha512
4559
tar -xzf elasticsearch-{{version}}-linux-x86_64.tar.gz
4660
cd elasticsearch-{{version}}/
4761
```
4862

63+
:::
64+
65+
:::{tab-item} Markdown
66+
67+
````markdown
68+
```{code} sh subs=true
69+
wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-{{version}}-linux-x86_64.tar.gz
70+
wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-{{version}}-linux-x86_64.tar.gz.sha512
71+
shasum -a 512 -c elasticsearch-{{version}}-linux-x86_64.tar.gz.sha512
72+
tar -xzf elasticsearch-{{version}}-linux-x86_64.tar.gz
73+
cd elasticsearch-{{version}}/
74+
```
75+
````
76+
:::
77+
78+
::::
79+
80+
81+
### MD code block with subs enabled
82+
83+
::::{tab-set}
84+
85+
:::{tab-item} Output
86+
87+
```bash subs=true
88+
echo "{{a-global-variable}}"
89+
```
90+
91+
:::
92+
93+
:::{tab-item} Markdown
94+
95+
````markdown
96+
```bash subs=true
97+
echo "{{a-global-variable}}"
98+
```
99+
100+
````
101+
:::
102+
103+
### MD code block without subs enabled
104+
105+
::::
106+
107+
::::{tab-set}
108+
109+
:::{tab-item} Output
110+
49111
```bash
50112
echo "{{a-global-variable}}"
51113
```
52114

115+
:::
116+
117+
:::{tab-item} Markdown
118+
119+
````markdown
120+
```bash
121+
echo "{{a-global-variable}}"
122+
```
123+
124+
````
125+
:::
53126

54-
Here is a variable with dashes: {{a-key-with-dashes}}
127+
::::
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
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.Collections.Immutable;
6+
using System.Diagnostics.CodeAnalysis;
7+
using Elastic.Markdown.Diagnostics;
8+
9+
namespace Elastic.Markdown.Myst.CodeBlocks;
10+
11+
public record CodeBlockArguments
12+
{
13+
public static CodeBlockArguments Default { get; } = new();
14+
public static string[] KnownKeys { get; } = ["callouts", "subs"];
15+
public static string KnownKeysString { get; } = string.Join(", ", KnownKeys);
16+
17+
public bool UseCallouts { get; private set; } = true;
18+
public bool UseSubstitutions { get; private set; }
19+
20+
private CodeBlockArguments() { }
21+
22+
public static bool TryParse(ReadOnlySpan<char> args, [NotNullWhen(true)] out CodeBlockArguments? codeBlockArgs)
23+
{
24+
codeBlockArgs = null;
25+
26+
if (args.IsWhiteSpace())
27+
{
28+
codeBlockArgs = Default;
29+
return true;
30+
}
31+
32+
var blockArgs = new CodeBlockArguments();
33+
foreach (var part in args.Split(','))
34+
{
35+
var currentPart = args[part];
36+
if (currentPart.Contains('='))
37+
{
38+
var equalIndex = currentPart.IndexOf('=');
39+
var key = currentPart[..equalIndex].Trim();
40+
var value = currentPart[(equalIndex + 1)..].Trim();
41+
42+
if (!Assign(key, blockArgs, bool.TryParse(value, out var b) ? b : null))
43+
return false;
44+
}
45+
else
46+
{
47+
var key = currentPart.Trim();
48+
if (!Assign(key, blockArgs, true))
49+
return false;
50+
}
51+
}
52+
53+
codeBlockArgs = blockArgs;
54+
return true;
55+
}
56+
57+
private static bool Assign(ReadOnlySpan<char> key, CodeBlockArguments blockArgs, bool? value = null)
58+
{
59+
switch (key)
60+
{
61+
case "callouts":
62+
blockArgs.UseCallouts = value ?? true;
63+
break;
64+
case "subs":
65+
blockArgs.UseSubstitutions = value ?? false;
66+
break;
67+
default:
68+
return false;
69+
}
70+
71+
return true;
72+
}
73+
}

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

Lines changed: 67 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,14 @@ protected override EnhancedCodeBlock CreateFencedBlock(BlockProcessor processor)
3333

3434
var lineSpan = processor.Line.AsSpan();
3535
var codeBlock = lineSpan.IndexOf("{applies_to}") > -1
36-
? new AppliesToDirective(this, context) { IndentCount = processor.Indent }
37-
: new EnhancedCodeBlock(this, context) { IndentCount = processor.Indent };
36+
? new AppliesToDirective(this, context)
37+
{
38+
IndentCount = processor.Indent
39+
}
40+
: new EnhancedCodeBlock(this, context)
41+
{
42+
IndentCount = processor.Indent
43+
};
3844

3945
if (processor.TrackTrivia)
4046
{
@@ -78,7 +84,7 @@ public override bool Close(BlockProcessor processor, Block block)
7884

7985
codeBlock.Language = (
8086
(codeBlock.Info?.IndexOf('{') ?? -1) != -1
81-
? codeBlock.Arguments
87+
? codeBlock.Arguments?.Split()[0]
8288
: codeBlock.Info
8389
) ?? "unknown";
8490

@@ -101,7 +107,7 @@ public override bool Close(BlockProcessor processor, Block block)
101107
return base.Close(processor, block);
102108

103109
if (codeBlock is not AppliesToDirective appliesToDirective)
104-
ProcessCallOuts(lines, language, codeBlock, context);
110+
ProcessCodeBlock(lines, language, codeBlock, context);
105111
else
106112
ProcessAppliesToDirective(appliesToDirective, lines);
107113

@@ -128,9 +134,32 @@ private static void ProcessAppliesToDirective(AppliesToDirective appliesToDirect
128134
}
129135
}
130136

131-
private static void ProcessCallOuts(StringLineGroup lines, string language, EnhancedCodeBlock codeBlock,
137+
private static void ProcessCodeBlock(
138+
StringLineGroup lines,
139+
string language,
140+
EnhancedCodeBlock codeBlock,
132141
ParserContext context)
133142
{
143+
string argsString;
144+
if (codeBlock.Arguments == null)
145+
argsString = "";
146+
else if (codeBlock.Info?.IndexOf('{') == -1)
147+
argsString = codeBlock.Arguments ?? "";
148+
else
149+
{
150+
// if the code block starts with {code-block} and is followed by a language, we need to skip the language
151+
var parts = codeBlock.Arguments.Split();
152+
argsString = parts.Length > 1 && CodeBlock.Languages.Contains(parts[0])
153+
? string.Join(" ", parts[1..])
154+
: codeBlock.Arguments;
155+
}
156+
157+
var codeBlockArgs = CodeBlockArguments.Default;
158+
if (!CodeBlockArguments.TryParse(argsString, out var codeArgs))
159+
codeBlock.EmitError($"Unable to parse code block arguments: {argsString}. Valid arguments are {CodeBlockArguments.KnownKeysString}.");
160+
else
161+
codeBlockArgs = codeArgs;
162+
134163
var callOutIndex = 0;
135164
var originatingLine = 0;
136165
for (var index = 0; index < lines.Lines.Length; index++)
@@ -146,45 +175,49 @@ private static void ProcessCallOuts(StringLineGroup lines, string language, Enha
146175
}
147176

148177
var span = line.Slice.AsSpan();
149-
150-
if (span.ReplaceSubstitutions(context.YamlFrontMatter?.Properties, out var frontMatterReplacement))
178+
if (codeBlockArgs.UseSubstitutions)
151179
{
152-
var s = new StringSlice(frontMatterReplacement);
153-
lines.Lines[index] = new StringLine(ref s);
154-
span = lines.Lines[index].Slice.AsSpan();
155-
}
180+
if (span.ReplaceSubstitutions(context.YamlFrontMatter?.Properties, out var frontMatterReplacement))
181+
{
182+
var s = new StringSlice(frontMatterReplacement);
183+
lines.Lines[index] = new StringLine(ref s);
184+
span = lines.Lines[index].Slice.AsSpan();
185+
}
156186

157-
if (span.ReplaceSubstitutions(context.Substitutions, out var globalReplacement))
158-
{
159-
var s = new StringSlice(globalReplacement);
160-
lines.Lines[index] = new StringLine(ref s);
161-
span = lines.Lines[index].Slice.AsSpan();
187+
if (span.ReplaceSubstitutions(context.Substitutions, out var globalReplacement))
188+
{
189+
var s = new StringSlice(globalReplacement);
190+
lines.Lines[index] = new StringLine(ref s);
191+
span = lines.Lines[index].Slice.AsSpan();
192+
}
162193
}
163194

164-
165195
if (codeBlock.OpeningFencedCharCount > 3)
166196
continue;
167197

168-
List<CallOut> callOuts = [];
169-
var hasClassicCallout = span.IndexOf("<") > 0 && span.LastIndexOf(">") == span.Length - 1;
170-
if (hasClassicCallout)
198+
if (codeBlockArgs.UseCallouts)
171199
{
172-
var matchClassicCallout = CallOutParser.CallOutNumber().EnumerateMatches(span);
173-
callOuts.AddRange(
174-
EnumerateAnnotations(matchClassicCallout, ref span, ref callOutIndex, originatingLine, false)
175-
);
176-
}
200+
List<CallOut> callOuts = [];
201+
var hasClassicCallout = span.IndexOf("<") > 0 && span.LastIndexOf(">") == span.Length - 1;
202+
if (hasClassicCallout)
203+
{
204+
var matchClassicCallout = CallOutParser.CallOutNumber().EnumerateMatches(span);
205+
callOuts.AddRange(
206+
EnumerateAnnotations(matchClassicCallout, ref span, ref callOutIndex, originatingLine, false)
207+
);
208+
}
209+
210+
// only support magic callouts for smaller line lengths
211+
if (callOuts.Count == 0 && span.Length < 200)
212+
{
213+
var matchInline = CallOutParser.MathInlineAnnotation().EnumerateMatches(span);
214+
callOuts.AddRange(
215+
EnumerateAnnotations(matchInline, ref span, ref callOutIndex, originatingLine, true)
216+
);
217+
}
177218

178-
// only support magic callouts for smaller line lengths
179-
if (callOuts.Count == 0 && span.Length < 200)
180-
{
181-
var matchInline = CallOutParser.MathInlineAnnotation().EnumerateMatches(span);
182-
callOuts.AddRange(
183-
EnumerateAnnotations(matchInline, ref span, ref callOutIndex, originatingLine, true)
184-
);
219+
codeBlock.CallOuts.AddRange(callOuts);
185220
}
186-
187-
codeBlock.CallOuts.AddRange(callOuts);
188221
}
189222

190223
//update string slices to ignore call outs

0 commit comments

Comments
 (0)