Skip to content

Commit

Permalink
feat: Add support to configure markdig extension by configuration.
Browse files Browse the repository at this point in the history
  • Loading branch information
filzrev committed Mar 27, 2024
1 parent 0699543 commit 93ee11d
Show file tree
Hide file tree
Showing 16 changed files with 902 additions and 37 deletions.
19 changes: 18 additions & 1 deletion docs/reference/docfx-json-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,23 @@ Sets the max parallelism. Setting 0 (default) is the same as setting to the coun

Sets the parameters for the markdown engine, value is a JSON object.

For example.
It can enable additional `FootNotes` markdig extensions with following settings.

```json
{
"build": {
"markdownEngineProperties": {
"markdigExtensions": [
"FootNotes"
]
}
}
}
```

List of available built-in markdig extensions are available at [this URL](https://github.com/xoofx/markdig/blob/master/src/Markdig/MarkdownExtensions.cs#L552-L668).

### `customLinkResolver`

Set the name of the `ICustomHrefGenerator` derived class.
Expand All @@ -208,7 +225,7 @@ Set the name of the `ICustomHrefGenerator` derived class.
Specifies the output folder of specified group name.

```json
"groups":
"groups": {
"v1": {
"dest": "output_dir_v1"
}
Expand Down
113 changes: 113 additions & 0 deletions src/Docfx.MarkdigEngine.Extensions/MarkdigExtensionSetting.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Diagnostics;
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Text.Json.Serialization;

#nullable enable

namespace Docfx.MarkdigEngine.Extensions;

/// <summary>
/// Markdig extension setting.
/// </summary>
[DebuggerDisplay(@"Name = {Name}")]
[Newtonsoft.Json.JsonConverter(typeof(MarkdigExtensionSettingConverter))]
public class MarkdigExtensionSetting
{
private static readonly JsonSerializerOptions DefaultSerializerOptions = new()
{
AllowTrailingCommas = true,
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
Converters = {
new JsonStringEnumConverter()
},
};

/// <summary>
/// Initializes a new instance of the <see cref="MarkdigExtensionSetting"/> class.
/// </summary>
public MarkdigExtensionSetting(string name, JsonObject? options = null)
{
Name = name;
if (options != null)
{
Options = options.Deserialize<JsonElement>();
}
else
{
Options = null;
}
}

/// <summary>
/// Name of markdig extension
/// </summary>
public string Name { get; init; }

/// <summary>
/// Options of markdig extension.
/// This option is storead as immutable JsonElement object.
/// </summary>
public JsonElement? Options { get; init; }

/// <summary>
/// Gets markdig extension options as specified class object.
/// </summary>
public T GetOptions<T>(T fallbackValue)
{
if (Options == null)
{
return fallbackValue;
}

var jsonObject = JsonSerializer.SerializeToNode(Options)?.AsObject();

if (jsonObject != null
&& jsonObject.TryGetPropertyValue("options", out var optionsNode)
&& optionsNode != null)
{
return optionsNode.Deserialize<T>(DefaultSerializerOptions)!;
}
else
{
return fallbackValue;
}
}

/// <summary>
/// Gets markdig extension options as specified class object.
/// </summary>
public T GetOptionsValue<T>(string key, T fallbackValue)
{
if (Options == null)
{
return fallbackValue;
}

var jsonNode = JsonSerializer.SerializeToNode(Options)?.AsObject();

// Try to read options property that have specified key.
if (jsonNode != null
&& jsonNode.TryGetPropertyValue("options", out var optionsNode)
&& optionsNode != null
&& optionsNode.AsObject().TryGetPropertyValue(key, out var valueNode))
{
return valueNode!.GetValue<T>()!;
}
else
{
return fallbackValue;
}
}

/// <summary>
/// Allow implicit cast from markdig extension name.
/// </summary>
public static implicit operator MarkdigExtensionSetting(string name)
{
return new MarkdigExtensionSetting(name);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using Newtonsoft.Json.Linq;
using Newtonsoft.Json;

namespace Docfx.MarkdigEngine.Extensions;

internal class MarkdigExtensionSettingConverter : Newtonsoft.Json.JsonConverter
{
// JsonSerializerOptions that used to deserialize MarkdigExtension options.
internal static readonly System.Text.Json.JsonSerializerOptions DefaultSerializerOptions = new()
{
IncludeFields = true,
AllowTrailingCommas = true,
DictionaryKeyPolicy = System.Text.Json.JsonNamingPolicy.CamelCase,
PropertyNamingPolicy = System.Text.Json.JsonNamingPolicy.CamelCase,
PropertyNameCaseInsensitive = true,
Converters = {
new System.Text.Json.Serialization.JsonStringEnumConverter()
},
WriteIndented = false,
};

/// <inheritdoc/>
public override bool CanConvert(Type objectType)
{
return objectType == typeof(MarkdigExtensionSetting);
}

/// <inheritdoc/>
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
// var value = reader.Value;
switch (reader.TokenType)
{
case JsonToken.String:
{
var name = (string)reader.Value;
return new MarkdigExtensionSetting(name);
}
case JsonToken.StartObject:
{
var jObj = JObject.Load(reader);

var props = jObj.Properties().ToArray();

// Object key must be the name of markdig extension.
if (props.Length != 1)
return null;

var prop = props[0];
var name = prop.Name;

var options = prop.Value;
if (options.Count() == 0)
{
return new MarkdigExtensionSetting(name);
}

// Serialize options to JsonElement.
var jsonElement = System.Text.Json.JsonSerializer.SerializeToElement(options, DefaultSerializerOptions);

return new MarkdigExtensionSetting(name)
{
Options = jsonElement,
};
}

default:
return null;
}
}

/// <inheritdoc/>
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
if (value == null)
return;

var model = (MarkdigExtensionSetting)value;

if (model.Options == null || !model.Options.HasValue)
{
writer.WriteValue(model.Name);
}
else
{
writer.WriteStartObject();
writer.WritePropertyName(model.Name);
var json = model.Options.ToString();
writer.WriteRawValue(json);
writer.WriteEndObject();
}
}
}
Loading

0 comments on commit 93ee11d

Please sign in to comment.