Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add feature to configure markdig extension by docfx.json configs. #9820

Merged
merged 2 commits into from
Apr 1, 2024
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
19 changes: 18 additions & 1 deletion docs/reference/docfx-json-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,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 @@ -211,7 +228,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