Skip to content

Commit 0703c74

Browse files
Merge branch 'main' into charlotte-applies-to
2 parents 3ac792a + 965471f commit 0703c74

Some content is hidden

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

49 files changed

+2447
-1336
lines changed

Directory.Packages.props

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
<PackageVersion Include="AWSSDK.Core" Version="4.0.0.2" />
1717
<PackageVersion Include="AWSSDK.SQS" Version="4.0.0.1" />
1818
<PackageVersion Include="AWSSDK.S3" Version="4.0.0.1" />
19+
<PackageVersion Include="FakeItEasy" Version="8.3.0" />
1920
<PackageVersion Include="Elastic.Ingest.Elasticsearch" Version="0.11.3" />
2021
</ItemGroup>
2122
<!-- Build -->
@@ -39,7 +40,7 @@
3940
<PackageVersion Include="Markdig" Version="0.41.1" />
4041
<PackageVersion Include="NetEscapades.EnumGenerators" Version="1.0.0-beta12" PrivateAssets="all" ExcludeAssets="runtime" />
4142
<PackageVersion Include="Proc" Version="0.9.1" />
42-
<PackageVersion Include="RazorSlices" Version="0.8.1" />
43+
<PackageVersion Include="RazorSlices" Version="0.9.1" />
4344
<PackageVersion Include="Samboy063.Tomlet" Version="6.0.0" />
4445
<PackageVersion Include="Slugify.Core" Version="4.0.1" />
4546
<PackageVersion Include="SoftCircuits.IniFileParser" Version="2.7.0" />

docs/contribute/redirects.md

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,19 @@ redirects:
4949
'testing/redirects/third-page.md':
5050
anchors:
5151
'removed-anchor':
52+
'testing/redirects/cross-repo-page.md': 'other-repo://reference/section/new-cross-repo-page.md'
53+
'testing/redirects/8th-page.md':
54+
to: 'other-repo://reference/section/new-cross-repo-page.md'
55+
anchors: '!'
56+
many:
57+
- to: 'testing/redirects/second-page.md'
58+
anchors:
59+
'item-a': 'yy'
60+
- to: 'testing/redirects/third-page.md'
61+
anchors:
62+
'item-b':
63+
64+
5265
```
5366

5467
### Redirect preserving all anchors
@@ -104,3 +117,49 @@ redirects:
104117
'old-anchor': 'active-anchor'
105118
'removed-anchor':
106119
```
120+
121+
### Redirecting to other repositories
122+
123+
It is possible to redirect to other repositories. The syntax is the same as when linking on documentation sets:
124+
125+
* 'other-repo://reference/section/new-cross-repo-page.md'
126+
127+
```yaml
128+
redirects:
129+
'testing/redirects/cross-repo-page.md': 'other-repo://reference/section/new-cross-repo-page.md'
130+
```
131+
132+
### Managing complex scenarios with anchors
133+
134+
* `to`, `anchor` and `many` can be used together to support more complex scenarios.
135+
* Setting `to` at the top level determines the default case, which can be used for partial redirects.
136+
* Cross-repository links are supported, with the same syntax as in the previous example.
137+
* The existing rules for `anchors` also apply here. To define a catch-all redirect, use `{}`.
138+
139+
```yaml
140+
redirects:
141+
# In this first scenario, the default redirection target remains the same page, with anchors being preserved.
142+
# Omitting the ``anchors`` tag or explicitly setting it as empty are both supported.
143+
'testing/redirects/8th-page.md':
144+
to: 'testing/redirects/8th-page.md'
145+
many:
146+
- to: 'testing/redirects/second-page.md'
147+
anchors:
148+
'item-a': 'yy'
149+
- to: 'testing/redirects/third-page.md'
150+
anchors:
151+
'item-b':
152+
153+
# In this scenario, the default redirection target is a different page, and anchors are dropped.
154+
'testing/redirects/deleted-page.md':
155+
to: 'testing/redirects/5th-page.md'
156+
anchors: '!'
157+
many:
158+
- to: "testing/redirects/second-page.md"
159+
anchors:
160+
"aa": "zz"
161+
"removed-anchor":
162+
- to: "other-repo://reference/section/partial-content.md"
163+
anchors:
164+
"bb": "yy"
165+
```
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.Text;
6+
using Elastic.Markdown.Myst;
7+
using Markdig.Renderers;
8+
using Microsoft.Extensions.ObjectPool;
9+
10+
namespace Elastic.Markdown.Helpers;
11+
12+
internal static class DocumentationObjectPoolProvider
13+
{
14+
private static readonly ObjectPoolProvider PoolProvider = new DefaultObjectPoolProvider();
15+
16+
public static readonly ObjectPool<StringBuilder> StringBuilderPool = PoolProvider.CreateStringBuilderPool(256, 4 * 1024);
17+
public static readonly ObjectPool<ReusableStringWriter> StringWriterPool = PoolProvider.Create(new ReusableStringWriterPooledObjectPolicy());
18+
public static readonly ObjectPool<HtmlRenderSubscription> HtmlRendererPool = PoolProvider.Create(new HtmlRendererPooledObjectPolicy());
19+
20+
21+
private sealed class ReusableStringWriterPooledObjectPolicy : IPooledObjectPolicy<ReusableStringWriter>
22+
{
23+
public ReusableStringWriter Create() => new();
24+
25+
public bool Return(ReusableStringWriter obj)
26+
{
27+
obj.Reset();
28+
return true;
29+
}
30+
}
31+
32+
public sealed class HtmlRenderSubscription
33+
{
34+
public required HtmlRenderer HtmlRenderer { get; init; }
35+
public StringBuilder? RentedStringBuilder { get; internal set; }
36+
}
37+
38+
private sealed class HtmlRendererPooledObjectPolicy : IPooledObjectPolicy<HtmlRenderSubscription>
39+
{
40+
public HtmlRenderSubscription Create()
41+
{
42+
var stringBuilder = StringBuilderPool.Get();
43+
using var stringWriter = StringWriterPool.Get();
44+
stringWriter.SetStringBuilder(stringBuilder);
45+
var renderer = new HtmlRenderer(stringWriter);
46+
MarkdownParser.Pipeline.Setup(renderer);
47+
48+
return new HtmlRenderSubscription { HtmlRenderer = renderer, RentedStringBuilder = stringBuilder };
49+
}
50+
51+
public bool Return(HtmlRenderSubscription subscription)
52+
{
53+
//subscription.RentedStringBuilder = null;
54+
//return string builder
55+
if (subscription.RentedStringBuilder is not null)
56+
StringBuilderPool.Return(subscription.RentedStringBuilder);
57+
58+
subscription.RentedStringBuilder = null;
59+
60+
var renderer = subscription.HtmlRenderer;
61+
62+
//reset string writer
63+
((ReusableStringWriter)renderer.Writer).Reset();
64+
65+
// reseed string writer with string builder
66+
var stringBuilder = StringBuilderPool.Get();
67+
subscription.RentedStringBuilder = stringBuilder;
68+
((ReusableStringWriter)renderer.Writer).SetStringBuilder(stringBuilder);
69+
return true;
70+
}
71+
}
72+
73+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
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.Security.Cryptography;
6+
using System.Text;
7+
8+
namespace Elastic.Markdown.Helpers;
9+
10+
public static class ShortId
11+
{
12+
public static string Create(params string[] components) => Convert.ToHexString(SHA256.HashData(Encoding.UTF8.GetBytes(string.Join("", components))))[..8];
13+
}
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
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;
6+
7+
namespace Elastic.Markdown.Helpers;
8+
9+
internal sealed class ReusableStringWriter : TextWriter
10+
{
11+
private static UnicodeEncoding? CurrentEncoding;
12+
13+
private StringBuilder? _sb;
14+
15+
public override Encoding Encoding => CurrentEncoding ??= new UnicodeEncoding(false, false);
16+
17+
public void SetStringBuilder(StringBuilder sb) => _sb = sb;
18+
19+
public void Reset() => _sb = null;
20+
21+
public override void Write(char value) => _sb?.Append(value);
22+
23+
public override void Write(char[] buffer, int index, int count)
24+
{
25+
ArgumentNullException.ThrowIfNull(buffer);
26+
ArgumentOutOfRangeException.ThrowIfNegative(index);
27+
ArgumentOutOfRangeException.ThrowIfNegative(count);
28+
29+
if (buffer.Length - index < count)
30+
throw new ArgumentException("Out of range");
31+
32+
_ = _sb?.Append(buffer, index, count);
33+
}
34+
35+
public override void Write(ReadOnlySpan<char> buffer) => _sb?.Append(buffer);
36+
37+
public override void Write(string? value)
38+
{
39+
if (value is not null)
40+
_ = _sb?.Append(value);
41+
}
42+
43+
public override void Write(StringBuilder? value) => _sb?.Append(value);
44+
45+
public override void WriteLine(ReadOnlySpan<char> buffer)
46+
{
47+
_ = _sb?.Append(buffer);
48+
WriteLine();
49+
}
50+
51+
public override void WriteLine(StringBuilder? value)
52+
{
53+
_ = _sb?.Append(value);
54+
WriteLine();
55+
}
56+
57+
#region Task based Async APIs
58+
59+
public override Task WriteAsync(char value)
60+
{
61+
Write(value);
62+
return Task.CompletedTask;
63+
}
64+
65+
public override Task WriteAsync(string? value)
66+
{
67+
Write(value);
68+
return Task.CompletedTask;
69+
}
70+
71+
public override Task WriteAsync(char[] buffer, int index, int count)
72+
{
73+
Write(buffer, index, count);
74+
return Task.CompletedTask;
75+
}
76+
77+
public override Task WriteAsync(ReadOnlyMemory<char> buffer, CancellationToken cancellationToken = default)
78+
{
79+
if (cancellationToken.IsCancellationRequested)
80+
return Task.FromCanceled(cancellationToken);
81+
82+
Write(buffer.Span);
83+
return Task.CompletedTask;
84+
}
85+
86+
public override Task WriteAsync(StringBuilder? value, CancellationToken cancellationToken = default)
87+
{
88+
if (cancellationToken.IsCancellationRequested)
89+
return Task.FromCanceled(cancellationToken);
90+
91+
_ = _sb?.Append(value);
92+
return Task.CompletedTask;
93+
}
94+
95+
public override Task WriteLineAsync(char value)
96+
{
97+
WriteLine(value);
98+
return Task.CompletedTask;
99+
}
100+
101+
public override Task WriteLineAsync(string? value)
102+
{
103+
WriteLine(value);
104+
return Task.CompletedTask;
105+
}
106+
107+
public override Task WriteLineAsync(StringBuilder? value, CancellationToken cancellationToken = default)
108+
{
109+
if (cancellationToken.IsCancellationRequested)
110+
return Task.FromCanceled(cancellationToken);
111+
112+
_ = _sb?.Append(value);
113+
WriteLine();
114+
return Task.CompletedTask;
115+
}
116+
117+
public override Task WriteLineAsync(char[] buffer, int index, int count)
118+
{
119+
WriteLine(buffer, index, count);
120+
return Task.CompletedTask;
121+
}
122+
123+
public override Task WriteLineAsync(ReadOnlyMemory<char> buffer, CancellationToken cancellationToken = default)
124+
{
125+
if (cancellationToken.IsCancellationRequested)
126+
return Task.FromCanceled(cancellationToken);
127+
128+
WriteLine(buffer.Span);
129+
return Task.CompletedTask;
130+
}
131+
132+
public override Task FlushAsync() => Task.CompletedTask;
133+
134+
#endregion
135+
}

src/Elastic.Markdown/IO/DocumentationSet.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,14 @@ private void ValidateRedirectsExists()
256256

257257
void ValidateExists(string from, string to, IReadOnlyDictionary<string, string?>? valueAnchors)
258258
{
259+
if (to.Contains("://"))
260+
{
261+
if (!Uri.TryCreate(to, UriKind.Absolute, out _))
262+
Context.EmitError(Configuration.SourceFile, $"Redirect {from} points to {to} which is not a valid URI");
263+
264+
return;
265+
}
266+
259267
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
260268
to = to.Replace('/', Path.DirectorySeparatorChar);
261269

src/Elastic.Markdown/IO/MarkdownFile.cs

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

55
using System.IO.Abstractions;
66
using System.Runtime.InteropServices;
7+
using System.Security.Cryptography;
8+
using System.Text;
79
using Elastic.Documentation.Diagnostics;
810
using Elastic.Documentation.Navigation;
911
using Elastic.Markdown.Diagnostics;
@@ -51,6 +53,7 @@ DocumentationSet set
5153
_configurationFile = build.Configuration.SourceFile;
5254
_globalSubstitutions = build.Configuration.Substitutions;
5355
_set = set;
56+
Id = ShortId.Create(FilePath);
5457
//may be updated by DocumentationGroup.ProcessTocItems
5558
//todo refactor mutability of MarkdownFile as a whole
5659
ScopeDirectory = build.Configuration.ScopeDirectory;
@@ -65,7 +68,7 @@ DocumentationSet set
6568

6669
public Uri NavigationSource { get; set; }
6770

68-
public string Id { get; } = Guid.NewGuid().ToString("N")[..8];
71+
public string Id { get; }
6972

7073
private IDiagnosticsCollector Collector { get; }
7174

@@ -354,7 +357,7 @@ private YamlFrontMatter ReadYamlFrontMatter(string raw)
354357
}
355358
}
356359

357-
public string CreateHtml(MarkdownDocument document)
360+
public static string CreateHtml(MarkdownDocument document)
358361
{
359362
//we manually render title and optionally append an applies block embedded in yaml front matter.
360363
var h1 = document.Descendants<HeadingBlock>().FirstOrDefault(h => h.Level == 1);

src/Elastic.Markdown/IO/Navigation/DocumentationGroup.cs

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

55
using System.Diagnostics;
66
using System.Diagnostics.CodeAnalysis;
7+
using System.Security.Cryptography;
8+
using System.Text;
79
using Elastic.Documentation;
810
using Elastic.Documentation.Configuration.TableOfContents;
11+
using Elastic.Markdown.Helpers;
912

1013
namespace Elastic.Markdown.IO.Navigation;
1114

@@ -125,7 +128,7 @@ public class DocumentationGroup : INavigationGroup
125128
{
126129
private readonly TableOfContentsTreeCollector _treeCollector;
127130

128-
public string Id { get; } = Guid.NewGuid().ToString("N")[..8];
131+
public string Id { get; }
129132

130133
public string NavigationRootId => NavigationRoot.Id;
131134

@@ -196,7 +199,7 @@ protected DocumentationGroup(
196199
GroupsInOrder = groups;
197200
FilesInOrder = files;
198201
NavigationItems = navigationItems;
199-
202+
Id = ShortId.Create(NavigationSource.ToString(), FolderName);
200203
if (Index is not null)
201204
FilesInOrder = [.. FilesInOrder.Except([Index])];
202205
}

0 commit comments

Comments
 (0)