Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
<PackageVersion Include="Markdig" Version="0.41.1" />
<PackageVersion Include="NetEscapades.EnumGenerators" Version="1.0.0-beta12" PrivateAssets="all" ExcludeAssets="runtime" />
<PackageVersion Include="Proc" Version="0.9.1" />
<PackageVersion Include="RazorSlices" Version="0.8.1" />
<PackageVersion Include="RazorSlices" Version="0.9.1" />
<PackageVersion Include="Samboy063.Tomlet" Version="6.0.0" />
<PackageVersion Include="Slugify.Core" Version="4.0.1" />
<PackageVersion Include="SoftCircuits.IniFileParser" Version="2.7.0" />
Expand Down
73 changes: 73 additions & 0 deletions src/Elastic.Markdown/Helpers/DocumentationObjectPoolProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// Licensed to Elasticsearch B.V under one or more agreements.
// Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
// See the LICENSE file in the project root for more information

using System.Text;
using Elastic.Markdown.Myst;
using Markdig.Renderers;
using Microsoft.Extensions.ObjectPool;

namespace Elastic.Markdown.Helpers;

internal static class DocumentationObjectPoolProvider
{
private static readonly ObjectPoolProvider PoolProvider = new DefaultObjectPoolProvider();

public static readonly ObjectPool<StringBuilder> StringBuilderPool = PoolProvider.CreateStringBuilderPool(256, 4 * 1024);
public static readonly ObjectPool<ReusableStringWriter> StringWriterPool = PoolProvider.Create(new ReusableStringWriterPooledObjectPolicy());
public static readonly ObjectPool<HtmlRenderSubscription> HtmlRendererPool = PoolProvider.Create(new HtmlRendererPooledObjectPolicy());


private sealed class ReusableStringWriterPooledObjectPolicy : IPooledObjectPolicy<ReusableStringWriter>
{
public ReusableStringWriter Create() => new();

public bool Return(ReusableStringWriter obj)
{
obj.Reset();
return true;
}
}

public sealed class HtmlRenderSubscription
{
public required HtmlRenderer HtmlRenderer { get; init; }
public StringBuilder? RentedStringBuilder { get; internal set; }
}

private sealed class HtmlRendererPooledObjectPolicy : IPooledObjectPolicy<HtmlRenderSubscription>
{
public HtmlRenderSubscription Create()
{
var stringBuilder = StringBuilderPool.Get();
using var stringWriter = StringWriterPool.Get();
stringWriter.SetStringBuilder(stringBuilder);
var renderer = new HtmlRenderer(stringWriter);
MarkdownParser.Pipeline.Setup(renderer);

return new HtmlRenderSubscription { HtmlRenderer = renderer, RentedStringBuilder = stringBuilder };
}

public bool Return(HtmlRenderSubscription subscription)
{
//subscription.RentedStringBuilder = null;
//return string builder
if (subscription.RentedStringBuilder is not null)
StringBuilderPool.Return(subscription.RentedStringBuilder);

subscription.RentedStringBuilder = null;

var renderer = subscription.HtmlRenderer;

//reset string writer
((ReusableStringWriter)renderer.Writer).Reset();

// reseed string writer with string builder
var stringBuilder = StringBuilderPool.Get();
subscription.RentedStringBuilder = stringBuilder;
((ReusableStringWriter)renderer.Writer).SetStringBuilder(stringBuilder);
return true;
}
}

}
135 changes: 135 additions & 0 deletions src/Elastic.Markdown/Helpers/ReusableStringWriter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
// Licensed to Elasticsearch B.V under one or more agreements.
// Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
// See the LICENSE file in the project root for more information

using System.Text;

namespace Elastic.Markdown.Helpers;

internal sealed class ReusableStringWriter : TextWriter
{
private static UnicodeEncoding? CurrentEncoding;

private StringBuilder? _sb;

public override Encoding Encoding => CurrentEncoding ??= new UnicodeEncoding(false, false);

public void SetStringBuilder(StringBuilder sb) => _sb = sb;

public void Reset() => _sb = null;

public override void Write(char value) => _sb?.Append(value);

public override void Write(char[] buffer, int index, int count)
{
ArgumentNullException.ThrowIfNull(buffer);
ArgumentOutOfRangeException.ThrowIfNegative(index);
ArgumentOutOfRangeException.ThrowIfNegative(count);

if (buffer.Length - index < count)
throw new ArgumentException("Out of range");

_ = _sb?.Append(buffer, index, count);
}

public override void Write(ReadOnlySpan<char> buffer) => _sb?.Append(buffer);

public override void Write(string? value)
{
if (value is not null)
_ = _sb?.Append(value);
}

public override void Write(StringBuilder? value) => _sb?.Append(value);

public override void WriteLine(ReadOnlySpan<char> buffer)
{
_ = _sb?.Append(buffer);
WriteLine();
}

public override void WriteLine(StringBuilder? value)
{
_ = _sb?.Append(value);
WriteLine();
}

#region Task based Async APIs

public override Task WriteAsync(char value)
{
Write(value);
return Task.CompletedTask;
}

public override Task WriteAsync(string? value)
{
Write(value);
return Task.CompletedTask;
}

public override Task WriteAsync(char[] buffer, int index, int count)
{
Write(buffer, index, count);
return Task.CompletedTask;
}

public override Task WriteAsync(ReadOnlyMemory<char> buffer, CancellationToken cancellationToken = default)
{
if (cancellationToken.IsCancellationRequested)
return Task.FromCanceled(cancellationToken);

Write(buffer.Span);
return Task.CompletedTask;
}

public override Task WriteAsync(StringBuilder? value, CancellationToken cancellationToken = default)
{
if (cancellationToken.IsCancellationRequested)
return Task.FromCanceled(cancellationToken);

_ = _sb?.Append(value);
return Task.CompletedTask;
}

public override Task WriteLineAsync(char value)
{
WriteLine(value);
return Task.CompletedTask;
}

public override Task WriteLineAsync(string? value)
{
WriteLine(value);
return Task.CompletedTask;
}

public override Task WriteLineAsync(StringBuilder? value, CancellationToken cancellationToken = default)
{
if (cancellationToken.IsCancellationRequested)
return Task.FromCanceled(cancellationToken);

_ = _sb?.Append(value);
WriteLine();
return Task.CompletedTask;
}

public override Task WriteLineAsync(char[] buffer, int index, int count)
{
WriteLine(buffer, index, count);
return Task.CompletedTask;
}

public override Task WriteLineAsync(ReadOnlyMemory<char> buffer, CancellationToken cancellationToken = default)
{
if (cancellationToken.IsCancellationRequested)
return Task.FromCanceled(cancellationToken);

WriteLine(buffer.Span);
return Task.CompletedTask;
}

public override Task FlushAsync() => Task.CompletedTask;

#endregion
}
2 changes: 1 addition & 1 deletion src/Elastic.Markdown/IO/MarkdownFile.cs
Original file line number Diff line number Diff line change
Expand Up @@ -357,7 +357,7 @@ private YamlFrontMatter ReadYamlFrontMatter(string raw)
}
}

public string CreateHtml(MarkdownDocument document)
public static string CreateHtml(MarkdownDocument document)
{
//we manually render title and optionally append an applies block embedded in yaml front matter.
var h1 = document.Descendants<HeadingBlock>().FirstOrDefault(h => h.Level == 1);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,20 +20,14 @@ public class EnhancedCodeBlockHtmlRenderer : HtmlObjectRenderer<EnhancedCodeBloc
private const int TabWidth = 4;

[SuppressMessage("Reliability", "CA2012:Use ValueTasks correctly")]
private static void RenderRazorSlice<T>(RazorSlice<T> slice, HtmlRenderer renderer, EnhancedCodeBlock block)
{
var html = slice.RenderAsync().GetAwaiter().GetResult();
var blocks = html.Split("[CONTENT]", 2, StringSplitOptions.RemoveEmptyEntries);
_ = renderer.Write(blocks[0]);
RenderCodeBlockLines(renderer, block);
_ = renderer.Write(blocks[1]);
}
private static void RenderRazorSlice<T>(RazorSlice<T> slice, HtmlRenderer renderer) =>
slice.RenderAsync(renderer.Writer).GetAwaiter().GetResult();

/// <summary>
/// Renders the code block lines while also removing the common indentation level.
/// Required because EnableTrackTrivia preserves extra indentation.
/// </summary>
private static void RenderCodeBlockLines(HtmlRenderer renderer, EnhancedCodeBlock block)
public static void RenderCodeBlockLines(HtmlRenderer renderer, EnhancedCodeBlock block)
{
var commonIndent = GetCommonIndent(block);
var hasCode = false;
Expand Down Expand Up @@ -134,10 +128,11 @@ protected override void Write(HtmlRenderer renderer, EnhancedCodeBlock block)
CrossReferenceName = string.Empty,// block.CrossReferenceName,
Language = block.Language,
Caption = block.Caption,
ApiCallHeader = block.ApiCallHeader
ApiCallHeader = block.ApiCallHeader,
EnhancedCodeBlock = block
});

RenderRazorSlice(slice, renderer, block);
RenderRazorSlice(slice, renderer);
if (!block.InlineAnnotations && callOuts.Count > 0)
{
var index = block.Parent!.IndexOf(block);
Expand Down Expand Up @@ -240,7 +235,6 @@ private static void RenderAppliesToHtml(HtmlRenderer renderer, AppliesToDirectiv
var slice = ApplicableToDirective.Create(appliesTo);
if (appliesTo is null || appliesTo == FrontMatter.ApplicableTo.All)
return;
var html = slice.RenderAsync().GetAwaiter().GetResult();
_ = renderer.Write(html);
slice.RenderAsync(renderer.Writer).GetAwaiter().GetResult();
}
}
Loading
Loading