Skip to content

Commit

Permalink
feat: Added extensions configuration point and PlantUml support (#9574)
Browse files Browse the repository at this point in the history
* Added extensions configuration point and PlantUmlExtension

* Address review comments and improve error handling of PlantUmlExtension

* Remove output formatters

* Update JsonConverterTest.cs to ignoreLineEndingDifferences

* Update JsonConverterTest.cs

---------

Co-authored-by: Yufei Huang <yufeih@live.com>
  • Loading branch information
cjlotz and yufeih authored Jan 3, 2024
1 parent 7ecccf6 commit 0cfc17f
Show file tree
Hide file tree
Showing 16 changed files with 365 additions and 247 deletions.
3 changes: 2 additions & 1 deletion Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
<PackageVersion Include="OneOf" Version="3.0.263" />
<PackageVersion Include="OneOf.SourceGenerator" Version="3.0.263" />
<PackageVersion Include="PdfPig" Version="0.1.9-alpha-20231119-4537e" />
<PackageVersion Include="PlantUml.Net" Version="1.4.80" />
<PackageVersion Include="Spectre.Console" Version="0.48.0" />
<PackageVersion Include="Spectre.Console.Cli" Version="0.48.0" />
<PackageVersion Include="Stubble.Core" Version="1.10.8" />
Expand All @@ -46,4 +47,4 @@
<PackageVersion Include="xunit.runner.visualstudio" Version="2.5.4" />
<PackageVersion Include="xunit" Version="2.6.2" />
</ItemGroup>
</Project>
</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
<ItemGroup>
<PackageReference Include="Markdig" />
<PackageReference Include="Newtonsoft.Json" />
<PackageReference Include="PlantUml.Net" />
</ItemGroup>

</Project>
16 changes: 15 additions & 1 deletion src/Docfx.MarkdigEngine.Extensions/MarkdownContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,13 @@ public class MarkdownContext
/// <returns>Image url bound to the path</returns>
public delegate string GetImageLinkDelegate(string path, MarkdownObject origin, string altText);

/// <summary>
/// Allows configuration of extensions
/// </summary>
/// <param name="extension">Name of the extension being configured</param>
/// <returns>Object representing the configuration for the extension</returns>
public delegate object GetExtensionConfigurationDelegate(string extension);

/// <summary>
/// Reads a file as text.
/// </summary>
Expand All @@ -51,6 +58,11 @@ public class MarkdownContext
/// </summary>
public GetImageLinkDelegate GetImageLink { get; }

/// <summary>
/// Get the configuration for a given extension
/// </summary>
public GetExtensionConfigurationDelegate GetExtensionConfiguration { get; }

/// <summary>
/// Log info
/// </summary>
Expand Down Expand Up @@ -86,12 +98,14 @@ public MarkdownContext(
LogActionDelegate logError = null,
ReadFileDelegate readFile = null,
GetLinkDelegate getLink = null,
GetImageLinkDelegate getImageLink = null)
GetImageLinkDelegate getImageLink = null,
GetExtensionConfigurationDelegate getConfig = null)
{
_getToken = getToken ?? (_ => null);
ReadFile = readFile ?? ((a, b) => (a, a));
GetLink = getLink ?? ((a, b) => a);
GetImageLink = getImageLink ?? ((a, b, c) => a);
GetExtensionConfiguration = getConfig ?? (_ => null);
LogInfo = logInfo ?? ((a, b, c, d) => { });
LogSuggestion = logSuggestion ?? ((a, b, c, d) => { });
LogWarning = logWarning ?? ((a, b, c, d) => { });
Expand Down
15 changes: 11 additions & 4 deletions src/Docfx.MarkdigEngine.Extensions/MarkdownExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ namespace Docfx.MarkdigEngine.Extensions;

public static class MarkdownExtensions
{
public static MarkdownPipelineBuilder UseDocfxExtensions(this MarkdownPipelineBuilder pipeline, MarkdownContext context, Dictionary<string, string> notes = null)
public static MarkdownPipelineBuilder UseDocfxExtensions(this MarkdownPipelineBuilder pipeline, MarkdownContext context)
{
return pipeline
.UseMathematics()
Expand All @@ -24,7 +24,7 @@ public static MarkdownPipelineBuilder UseDocfxExtensions(this MarkdownPipelineBu
.UseIncludeFile(context)
.UseCodeSnippet(context)
.UseDFMCodeInfoPrefix()
.UseQuoteSectionNote(context, notes)
.UseQuoteSectionNote(context)
.UseXref()
.UseEmojiAndSmiley(false)
.UseTabGroup(context)
Expand All @@ -35,6 +35,7 @@ public static MarkdownPipelineBuilder UseDocfxExtensions(this MarkdownPipelineBu
.UseTripleColon(context)
.UseNoloc()
.UseResolveLink(context)
.UsePlantUml(context)
.RemoveUnusedExtensions();
}

Expand Down Expand Up @@ -99,9 +100,9 @@ public static MarkdownPipelineBuilder UseDFMCodeInfoPrefix(this MarkdownPipeline
return pipeline;
}

public static MarkdownPipelineBuilder UseQuoteSectionNote(this MarkdownPipelineBuilder pipeline, MarkdownContext context, Dictionary<string, string> notes = null)
public static MarkdownPipelineBuilder UseQuoteSectionNote(this MarkdownPipelineBuilder pipeline, MarkdownContext context)
{
pipeline.Extensions.AddIfNotAlready(new QuoteSectionNoteExtension(context, notes));
pipeline.Extensions.AddIfNotAlready(new QuoteSectionNoteExtension(context));
return pipeline;
}

Expand All @@ -111,6 +112,12 @@ public static MarkdownPipelineBuilder UseLineNumber(this MarkdownPipelineBuilder
return pipeline;
}

public static MarkdownPipelineBuilder UsePlantUml(this MarkdownPipelineBuilder pipeline, MarkdownContext context)
{
pipeline.Extensions.AddIfNotAlready(new PlantUmlExtension(context));
return pipeline;
}

public static MarkdownPipelineBuilder UseResolveLink(this MarkdownPipelineBuilder pipeline, MarkdownContext context)
{
pipeline.Extensions.AddIfNotAlready(new ResolveLinkExtension(context));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
using static System.Text.Encoding;

using Markdig.Renderers;
using Markdig.Syntax;
using Markdig.Renderers.Html;
using PlantUml.Net;

namespace Docfx.MarkdigEngine.Extensions;

/// <summary>
/// An HTML renderer for a <see cref="CodeBlock"/> and <see cref="FencedCodeBlock"/>.
/// </summary>
/// <seealso cref="HtmlObjectRenderer{CodeBlock}" />
public class CustomCodeBlockRenderer : CodeBlockRenderer
{
private readonly MarkdownContext _context;
private readonly DocfxPlantUmlSettings _settings;
private readonly RendererFactory rendererFactory;

/// <summary>
/// Initializes a new instance of the <see cref="CodeBlockRenderer"/> class.
/// </summary>
/// <param name="context"></param>
/// <param name="settings"></param>
public CustomCodeBlockRenderer(MarkdownContext context, DocfxPlantUmlSettings settings)
{
_context = context;
_settings = settings;

rendererFactory = new RendererFactory();
}

protected override void Write(HtmlRenderer renderer, CodeBlock obj)
{
if (obj is FencedCodeBlock fencedCodeBlock
&& fencedCodeBlock.Info is string info
&& info.Equals("plantuml", StringComparison.OrdinalIgnoreCase))
{
IPlantUmlRenderer plantUmlRenderer = rendererFactory.CreateRenderer(_settings);

// Get PlantUML code.
var plantUmlCode = fencedCodeBlock.Lines.ToString();

try
{
byte[] output = plantUmlRenderer.Render(plantUmlCode, _settings.OutputFormat);

renderer.EnsureLine();
renderer.Write(FormatOutput(_settings.OutputFormat, output));
renderer.EnsureLine();
}
catch (RenderingException ex)
{
_context.LogWarning(nameof(PlantUmlExtension), ex.Message, null);
}
catch (Exception ex)
{
_context.LogError(nameof(PlantUmlExtension), ex.Message, null);

// If the error is not related to rendering a specific diagram, re-throw to abort
throw;
}

return;
}

// Fallback to default CodeBlockRenderer
base.Write(renderer, obj);
}

private static string FormatOutput(OutputFormat format, byte[] output)
{
switch (format)
{
case OutputFormat.Svg:
string svg = UTF8.GetString(output);
return $"<div class=\"lang-plantUml\">{svg}</div>";

case OutputFormat.Ascii:
string ascii = ASCII.GetString(output);
return $"<div class=\"lang-plantUml\"><pre>{ascii}</pre></div>";

case OutputFormat.Ascii_Unicode:
string asciiUnicode = UTF8.GetString(output);
return $"<div class=\"lang-plantUml\"><pre>{asciiUnicode}</pre></div>";

case OutputFormat.Png:
case OutputFormat.Eps:
case OutputFormat.Pdf:
case OutputFormat.Vdx:
case OutputFormat.Xmi:
case OutputFormat.Scxml:
case OutputFormat.Html:
case OutputFormat.LaTeX:
default:
throw new NotSupportedException($"Output format {format} is currently not supported");
}
}
}
68 changes: 68 additions & 0 deletions src/Docfx.MarkdigEngine.Extensions/PlantUml/PlantUmlExtension.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
using Markdig;
using Markdig.Renderers;
using Markdig.Renderers.Html;
using PlantUml.Net;

namespace Docfx.MarkdigEngine.Extensions;

public class DocfxPlantUmlSettings : PlantUmlSettings
{
public DocfxPlantUmlSettings() : base()
{
}

public DocfxPlantUmlSettings(IReadOnlyDictionary<string, string> config) : this()
{
if (config.TryGetValue("remoteUrl", out var url))
RemoteUrl = url;
if (config.TryGetValue("outputFormat", out var format))
OutputFormat = Enum.Parse<OutputFormat>(format, true);
if (config.TryGetValue("javaPath", out var path))
JavaPath = path;
if (config.TryGetValue("localPlantUmlPath", out path))
LocalPlantUmlPath = path;
if (config.TryGetValue("localGraphvizDotPath", out path))
LocalGraphvizDotPath = path;
if (config.TryGetValue("renderingMode", out var renderMode))
RenderingMode = Enum.Parse<RenderingMode>(renderMode, true);
}

public OutputFormat OutputFormat { get; set; } = OutputFormat.Svg;
}

internal class PlantUmlExtension : IMarkdownExtension
{
private readonly MarkdownContext _context;
private readonly DocfxPlantUmlSettings _settings;

public PlantUmlExtension(MarkdownContext context)
{
_context = context;
_settings = new();

if (_context.GetExtensionConfiguration("PlantUml") is Dictionary<string, string> config)
_settings = new DocfxPlantUmlSettings(config);
}

public void Setup(MarkdownPipelineBuilder pipeline)
{
}

public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer)
{
if (renderer is HtmlRenderer { ObjectRenderers: not null } htmlRenderer)
{
var customRenderer = new CustomCodeBlockRenderer(_context, _settings);
var renderers = htmlRenderer.ObjectRenderers;

if (renderers.Contains<CodeBlockRenderer>())
{
renderers.InsertBefore<CodeBlockRenderer>(customRenderer);
}
else
{
renderers.AddIfNotAlready(customRenderer);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Text.Json.Serialization;
using Markdig;
using Markdig.Parsers;
using Markdig.Renderers;
using Markdig.Renderers.Html;
using Newtonsoft.Json;

namespace Docfx.MarkdigEngine.Extensions;

Expand All @@ -20,13 +22,13 @@ public class QuoteSectionNoteExtension : IMarkdownExtension
["CAUTION"] = "CAUTION",
};

public QuoteSectionNoteExtension(MarkdownContext context, Dictionary<string, string> notes = null)
public QuoteSectionNoteExtension(MarkdownContext context)
{
_context = context;

if (notes != null)
if (_context.GetExtensionConfiguration("Alerts") is Dictionary<string, string> config)
{
foreach (var (key, value) in notes)
foreach (var (key, value) in config)
_notes[key] = value;
}
}
Expand Down
8 changes: 6 additions & 2 deletions src/Docfx.MarkdigEngine/MarkdigMarkdownService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections.Immutable;
using System.Text;
using Docfx.Common;
using Docfx.MarkdigEngine.Extensions;
using Docfx.Plugins;
Expand Down Expand Up @@ -33,7 +34,8 @@ public MarkdigMarkdownService(
(code, message, origin, line) => Logger.LogError(message, null, InclusionContext.File.ToString(), line?.ToString(), code),
ReadFile,
GetLink,
GetImageLink);
GetImageLink,
GetExtensionConfiguration);
}

public MarkupResult Markup(string content, string filePath)
Expand Down Expand Up @@ -127,7 +129,7 @@ private MarkdownPipeline CreateMarkdownPipeline(bool isInline, bool multipleYaml

var builder = new MarkdownPipelineBuilder();

builder.UseDocfxExtensions(_context, _parameters.Extensions?.Alerts);
builder.UseDocfxExtensions(_context);
builder.Extensions.Insert(0, new YamlHeaderExtension(_context) { AllowInMiddleOfDocument = multipleYamlHeader });

if (enableSourceInfo)
Expand Down Expand Up @@ -185,6 +187,8 @@ private static string GetLink(string path, MarkdownObject origin)
return path;
}

private object GetExtensionConfiguration(string extension) => _parameters.GetExtensionConfiguration(extension);

private static string GetImageLink(string href, MarkdownObject origin, string altText) => GetLink(href, origin);

private static void ReportDependency(RelativePath filePathToDocset, string parentFileDirectoryToDocset)
Expand Down
19 changes: 19 additions & 0 deletions src/Docfx.Plugins/MarkdownServiceParameters.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,13 @@ public class MarkdownServiceProperties
[JsonProperty("alerts")]
[JsonPropertyName("alerts")]
public Dictionary<string, string> Alerts { get; set; }

/// <summary>
/// PlantUml extension configuration parameters
/// </summary>
[JsonProperty("plantUml")]
[JsonPropertyName("plantUml")]
public Dictionary<string, string> PlantUml { get; set; }
}

public class MarkdownServiceParameters
Expand All @@ -43,4 +50,16 @@ public class MarkdownServiceParameters
public string TemplateDir { get; set; }
public MarkdownServiceProperties Extensions { get; set; } = new();
public ImmutableDictionary<string, string> Tokens { get; set; } = ImmutableDictionary<string, string>.Empty;

public object GetExtensionConfiguration(string extension)
{
if (!string.IsNullOrEmpty(extension) && Extensions != null)
{
var property = typeof(MarkdownServiceProperties).GetProperty(extension);
if (property != null)
return property.GetValue(Extensions);
}

return null;
}
}
Loading

0 comments on commit 0cfc17f

Please sign in to comment.