From 93ee11d7c7fa2f930d47808735b43c7bf7243791 Mon Sep 17 00:00:00 2001 From: filzrev <103790468+filzrev@users.noreply.github.com> Date: Thu, 28 Mar 2024 06:28:19 +0900 Subject: [PATCH] feat: Add support to configure markdig extension by configuration. --- docs/reference/docfx-json-reference.md | 19 ++- .../MarkdigExtensionSetting.cs | 113 ++++++++++++++ .../MarkdigExtensionSettingConverter.cs | 96 ++++++++++++ .../MarkdownExtensions.cs | 144 +++++++++++++++++- .../MarkdownServiceParameters.cs | 7 +- .../EmojiTest.cs | 18 --- .../GeneralTest.cs | 16 +- .../AutoIdentifierTest.cs | 70 +++++++++ .../AutoLinkTest.cs | 50 ++++++ .../MarkdigBuiltinExtensionTests/EmojiTest.cs | 64 ++++++++ .../EmphasisExtraTest.cs | 114 ++++++++++++++ .../MediaLinksTest.cs | 53 +++++++ .../PipeTableTest.cs | 95 ++++++++++++ .../SmartyPantsTest.cs | 61 ++++++++ .../TestUtility.cs | 4 +- test/docfx.Tests/Api.verified.cs | 15 +- 16 files changed, 902 insertions(+), 37 deletions(-) create mode 100644 src/Docfx.MarkdigEngine.Extensions/MarkdigExtensionSetting.cs create mode 100644 src/Docfx.MarkdigEngine.Extensions/MarkdigExtensionSettingConverter.cs delete mode 100644 test/Docfx.MarkdigEngine.Extensions.Tests/EmojiTest.cs create mode 100644 test/Docfx.MarkdigEngine.Extensions.Tests/MarkdigBuiltinExtensionTests/AutoIdentifierTest.cs create mode 100644 test/Docfx.MarkdigEngine.Extensions.Tests/MarkdigBuiltinExtensionTests/AutoLinkTest.cs create mode 100644 test/Docfx.MarkdigEngine.Extensions.Tests/MarkdigBuiltinExtensionTests/EmojiTest.cs create mode 100644 test/Docfx.MarkdigEngine.Extensions.Tests/MarkdigBuiltinExtensionTests/EmphasisExtraTest.cs create mode 100644 test/Docfx.MarkdigEngine.Extensions.Tests/MarkdigBuiltinExtensionTests/MediaLinksTest.cs create mode 100644 test/Docfx.MarkdigEngine.Extensions.Tests/MarkdigBuiltinExtensionTests/PipeTableTest.cs create mode 100644 test/Docfx.MarkdigEngine.Extensions.Tests/MarkdigBuiltinExtensionTests/SmartyPantsTest.cs diff --git a/docs/reference/docfx-json-reference.md b/docs/reference/docfx-json-reference.md index 35dd78001dc..c0312e31836 100644 --- a/docs/reference/docfx-json-reference.md +++ b/docs/reference/docfx-json-reference.md @@ -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. @@ -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" } diff --git a/src/Docfx.MarkdigEngine.Extensions/MarkdigExtensionSetting.cs b/src/Docfx.MarkdigEngine.Extensions/MarkdigExtensionSetting.cs new file mode 100644 index 00000000000..bfe3ca65797 --- /dev/null +++ b/src/Docfx.MarkdigEngine.Extensions/MarkdigExtensionSetting.cs @@ -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; + +/// +/// Markdig extension setting. +/// +[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() + }, + }; + + /// + /// Initializes a new instance of the class. + /// + public MarkdigExtensionSetting(string name, JsonObject? options = null) + { + Name = name; + if (options != null) + { + Options = options.Deserialize(); + } + else + { + Options = null; + } + } + + /// + /// Name of markdig extension + /// + public string Name { get; init; } + + /// + /// Options of markdig extension. + /// This option is storead as immutable JsonElement object. + /// + public JsonElement? Options { get; init; } + + /// + /// Gets markdig extension options as specified class object. + /// + public T GetOptions(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(DefaultSerializerOptions)!; + } + else + { + return fallbackValue; + } + } + + /// + /// Gets markdig extension options as specified class object. + /// + public T GetOptionsValue(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()!; + } + else + { + return fallbackValue; + } + } + + /// + /// Allow implicit cast from markdig extension name. + /// + public static implicit operator MarkdigExtensionSetting(string name) + { + return new MarkdigExtensionSetting(name); + } +} diff --git a/src/Docfx.MarkdigEngine.Extensions/MarkdigExtensionSettingConverter.cs b/src/Docfx.MarkdigEngine.Extensions/MarkdigExtensionSettingConverter.cs new file mode 100644 index 00000000000..a8c9bf2a1be --- /dev/null +++ b/src/Docfx.MarkdigEngine.Extensions/MarkdigExtensionSettingConverter.cs @@ -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, + }; + + /// + public override bool CanConvert(Type objectType) + { + return objectType == typeof(MarkdigExtensionSetting); + } + + /// + 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; + } + } + + /// + 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(); + } + } +} diff --git a/src/Docfx.MarkdigEngine.Extensions/MarkdownExtensions.cs b/src/Docfx.MarkdigEngine.Extensions/MarkdownExtensions.cs index 4d5ea251bd5..46c4d387f99 100644 --- a/src/Docfx.MarkdigEngine.Extensions/MarkdownExtensions.cs +++ b/src/Docfx.MarkdigEngine.Extensions/MarkdownExtensions.cs @@ -1,10 +1,17 @@ // 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 Markdig; using Markdig.Extensions.AutoIdentifiers; +using Markdig.Extensions.AutoLinks; using Markdig.Extensions.CustomContainers; +using Markdig.Extensions.Emoji; using Markdig.Extensions.EmphasisExtras; +using Markdig.Extensions.MediaLinks; +using Markdig.Extensions.SmartyPants; +using Markdig.Extensions.Tables; using Markdig.Parsers; namespace Docfx.MarkdigEngine.Extensions; @@ -17,7 +24,7 @@ public static MarkdownPipelineBuilder UseDocfxExtensions( { return pipeline .UseMathematics() - .UseEmphasisExtras() + .UseEmphasisExtras(EmphasisExtraOptions.Strikethrough) .UseAutoIdentifiers(AutoIdentifierOptions.GitHub) .UseMediaLinks() .UsePipeTables() @@ -49,18 +56,149 @@ public static MarkdownPipelineBuilder UseDocfxExtensions( /// The pipeline with optional extensions enabled public static MarkdownPipelineBuilder UseOptionalExtensions( this MarkdownPipelineBuilder pipeline, - IEnumerable optionalExtensions) + MarkdigExtensionSetting[] optionalExtensions) { if (!optionalExtensions.Any()) { return pipeline; } - pipeline.Configure(string.Join("+", optionalExtensions)); + // Process markdig extensions that requires custom handling. + var results = new List(); + foreach (var extension in optionalExtensions) + { + if (TryAddOrReplaceMarkdigExtension(pipeline, extension)) + continue; + + // If markdig extension options are specified. These extension should be handled by above method. + Debug.Assert(extension.Options == null); + + results.Add(extension); + } + optionalExtensions = results.ToArray(); + + // Enable remaining markdig extensions with default options. + pipeline.Configure(string.Join("+", optionalExtensions.Select(x => x.Name))); return pipeline; } + /// + /// + /// + /// Return true when Markdig extension is added or replaced by this method. + private static bool TryAddOrReplaceMarkdigExtension( + MarkdownPipelineBuilder pipeline, + MarkdigExtensionSetting extension) + { + // See: https://github.com/xoofx/markdig/blob/master/src/Markdig/MarkdownExtensions.cs + switch (extension.Name.ToLowerInvariant()) + { + // PipeTableExtension + case "pipetables": + { + var options = extension.GetOptions(fallbackValue: new PipeTableOptions()); + pipeline.Extensions.ReplaceOrAdd(new PipeTableExtension(options)); + return true; + } + + // PipeTableExtension (with GitHub Flavored Markdown compatible settings) + case "gfm-pipetables": + { + var options = extension.GetOptions(fallbackValue: new PipeTableOptions { UseHeaderForColumnCount = true }); + pipeline.Extensions.ReplaceOrAdd(new PipeTableExtension(options)); + return true; + } + + // EmphasisExtraExtension (Docfx default: AutoIdentifierOptions.Strikethrough) + case "emphasisextras": + { + var options = extension.GetOptions(fallbackValue: EmphasisExtraOptions.Default); + pipeline.Extensions.ReplaceOrAdd(new EmphasisExtraExtension(options)); + return true; + } + + // EmojiExtension (Docfx default: enableSmileys: false) + case "emojis": + { + var enableSmileys = extension.GetOptions(fallbackValue: true); + EmojiMapping emojiMapping = enableSmileys + ? EmojiMapping.DefaultEmojisAndSmileysMapping + : EmojiMapping.DefaultEmojisOnlyMapping; + pipeline.Extensions.ReplaceOrAdd(new EmojiExtension(emojiMapping)); + return true; + } + + // MediaLinkExtension + case "medialinks": + { + var options = extension.GetOptions(fallbackValue: new MediaOptions()); + pipeline.Extensions.ReplaceOrAdd(new MediaLinkExtension(options)); + return true; + } + + // SmartyPantsExtension + case "smartypants": + { + var options = extension.GetOptions(fallbackValue: new SmartyPantOptions()); + pipeline.Extensions.ReplaceOrAdd(new SmartyPantsExtension(options)); + return true; + } + + // AutoIdentifierExtension (Docfx default:AutoIdentifierOptions.GitHub) + case "autoidentifiers": + { + var options = extension.GetOptions(fallbackValue: AutoIdentifierOptions.Default); + pipeline.Extensions.ReplaceOrAdd(new AutoIdentifierExtension(options)); + return true; + } + + // AutoLinkExtension + case "autolinks": + { + var options = extension.GetOptions(fallbackValue: new AutoLinkOptions()); + pipeline.Extensions.ReplaceOrAdd(new AutoLinkExtension(options)); + return true; + } + + // Other builtin markdig extensions. + case "advanced": + case "alerts": + case "listextras": + case "hardlinebreak": + case "footnotes": + case "footers": + case "citations": + case "attributes": + case "gridtables": + case "abbreviations": + case "definitionlists": + case "customcontainers": + case "figures": + case "mathematics": + case "bootstrap": + case "tasklists": + case "diagrams": + case "nofollowlinks": + case "noopenerlinks": + case "noreferrerlinks": + case "nohtml": + case "yaml": + case "nonascii-noescape": + case "globalization": + case "common": + default: + // Throw exception if options are specified. + if (extension.Options != null) + { + throw new Exception($"Unknown markdig extension({extension.Name}) is specified. {extension.Options}"); + } + + // These extensions are handled by `MarkdownPipelineBuilder.Configure` method. + return false; + } + } + private static MarkdownPipelineBuilder RemoveUnusedExtensions(this MarkdownPipelineBuilder pipeline) { pipeline.Extensions.RemoveAll(extension => extension is CustomContainerExtension); diff --git a/src/Docfx.MarkdigEngine/MarkdownServiceParameters.cs b/src/Docfx.MarkdigEngine/MarkdownServiceParameters.cs index 11a4f36f862..0354084712a 100644 --- a/src/Docfx.MarkdigEngine/MarkdownServiceParameters.cs +++ b/src/Docfx.MarkdigEngine/MarkdownServiceParameters.cs @@ -18,12 +18,13 @@ public class MarkdownServiceProperties public bool EnableSourceInfo { get; set; } = true; /// - /// Contains a list of optional Markdig extensions that are not - /// enabled by default by DocFX. + /// List of optional Markdig extensions to add or modify settings. + /// If extension is specified by name. Markdig extension will be added with default configuration. + /// If extension name is specified name with options. Add or Replace markdig extensions with specified options. /// [JsonProperty("markdigExtensions")] [JsonPropertyName("markdigExtensions")] - public string[] MarkdigExtensions { get; set; } + public MarkdigExtensionSetting[] MarkdigExtensions { get; set; } [JsonProperty("fallbackFolders")] [JsonPropertyName("fallbackFolders")] diff --git a/test/Docfx.MarkdigEngine.Extensions.Tests/EmojiTest.cs b/test/Docfx.MarkdigEngine.Extensions.Tests/EmojiTest.cs deleted file mode 100644 index 80284c24baa..00000000000 --- a/test/Docfx.MarkdigEngine.Extensions.Tests/EmojiTest.cs +++ /dev/null @@ -1,18 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using Xunit; - -namespace Docfx.MarkdigEngine.Tests; - -public class EmojiTest -{ - [Fact] - public void EmojiTestGeneral() - { - var content = @"**content :** :smile:"; - var expected = @"

content : 😄

"; - - TestUtility.VerifyMarkup(content, expected); - } -} diff --git a/test/Docfx.MarkdigEngine.Extensions.Tests/GeneralTest.cs b/test/Docfx.MarkdigEngine.Extensions.Tests/GeneralTest.cs index 18aac8810fc..faf4298612c 100644 --- a/test/Docfx.MarkdigEngine.Extensions.Tests/GeneralTest.cs +++ b/test/Docfx.MarkdigEngine.Extensions.Tests/GeneralTest.cs @@ -43,10 +43,10 @@ public void TestDfm_TaskList_ExtensionEnabled()
  • Not contain a special character: \ ! # $ % & * + / = ? ^ ` { } | ~ < > ( ) ' ; : , " @ _
  • "; - TestUtility.VerifyMarkup(source, expected, optionalExtensions: new List - { - "tasklists" - }); + TestUtility.VerifyMarkup(source, expected, optionalExtensions: + [ + "tasklists", + ]); } [Fact] @@ -68,11 +68,11 @@ Term 1 "; - TestUtility.VerifyMarkup(source, expected, optionalExtensions: new List - { + TestUtility.VerifyMarkup(source, expected, optionalExtensions: + [ "tasklists", - "definitionlists" - }); + "definitionlists", + ]); } [Fact] diff --git a/test/Docfx.MarkdigEngine.Extensions.Tests/MarkdigBuiltinExtensionTests/AutoIdentifierTest.cs b/test/Docfx.MarkdigEngine.Extensions.Tests/MarkdigBuiltinExtensionTests/AutoIdentifierTest.cs new file mode 100644 index 00000000000..31501ad3a44 --- /dev/null +++ b/test/Docfx.MarkdigEngine.Extensions.Tests/MarkdigBuiltinExtensionTests/AutoIdentifierTest.cs @@ -0,0 +1,70 @@ +// 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.Nodes; +using Markdig.Extensions.AutoIdentifiers; +using Xunit; + +namespace Docfx.MarkdigEngine.Tests; + +/// +/// Unit tests for markdig . +/// +/// +[Trait("Related", "MarkdigExtension")] +public class AutoIdentifierTest +{ + [Fact] + public void AutoIdentifierTest_DocfxDefault() + { + // docfx use `AutoIdentifierOptions.GitHub` as default options. + var content = @"# This - is a &@! heading _ with . and ! -"; + var expected = @"

    This - is a &@! heading _ with . and ! -

    "; + + TestUtility.VerifyMarkup(content, expected); + TestUtility.VerifyMarkup(content, expected, optionalExtensions: [ + new("AutoIdentifiers", new JsonObject + { + ["options"] = "GitHub", + }) + ]); + } + + [Fact] + public void AutoIdentifierTest_MarkdigDefault() + { + var content = @"# This - is a &@! heading _ with . and ! -"; + var expected = @"

    This - is a &@! heading _ with . and ! -

    "; + + // Default option is used when + TestUtility.VerifyMarkup(content, expected, optionalExtensions: ["AutoIdentifiers"]); + + TestUtility.VerifyMarkup(content, expected, optionalExtensions: [ + new("AutoIdentifiers", new JsonObject + { + ["options"] = "Default", + }) + ]); + + TestUtility.VerifyMarkup(content, expected, optionalExtensions: [ + new("AutoIdentifiers", new JsonObject + { + ["options"] = "AutoLink, AllowOnlyAscii", // Same as Default option. + }) + ]); + } + + [Fact] + public void AutoIdentifierTest_None() + { + var content = @"# This - is a &@! heading _ with . and ! -"; + var expected = @"

    This - is a &@! heading _ with . and ! -

    "; + + TestUtility.VerifyMarkup(content, expected, optionalExtensions: [ + new("AutoIdentifiers", new JsonObject + { + ["options"] = "None", + }) + ]); + } +} diff --git a/test/Docfx.MarkdigEngine.Extensions.Tests/MarkdigBuiltinExtensionTests/AutoLinkTest.cs b/test/Docfx.MarkdigEngine.Extensions.Tests/MarkdigBuiltinExtensionTests/AutoLinkTest.cs new file mode 100644 index 00000000000..0af18b2a842 --- /dev/null +++ b/test/Docfx.MarkdigEngine.Extensions.Tests/MarkdigBuiltinExtensionTests/AutoLinkTest.cs @@ -0,0 +1,50 @@ +// 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; +using System.Text.Json.Nodes; +using Docfx.MarkdigEngine.Extensions; +using Markdig.Extensions.AutoLinks; +using Xunit; + +namespace Docfx.MarkdigEngine.Tests; + +/// +/// Unit tests for markdig . +/// +/// +[Trait("Related", "MarkdigExtension")] +public class AutoLinkTest +{ + [Fact] + public void AutoLinkTest_DocfxDefault() + { + // docfx use `AutoIdentifierOptions.GitHub` as default options. + var content = "This is not a nhttp://www.google.com URL but this is (https://www.google.com)"; + var expected = "

    This is not a nhttp://www.google.com URL but this is (https://www.google.com)

    "; + + TestUtility.VerifyMarkup(content, expected); + TestUtility.VerifyMarkup(content, expected, optionalExtensions: ["AutoLinks"]); + } + + [Fact] + public void AutoLinkTest_Custom() + { + var options = new AutoLinkOptions + { + OpenInNewWindow = true, // Add `target="_blank"` attribute to generated link. + UseHttpsForWWWLinks = false, // Default: false. + ValidPreviousCharacters = "*_~(" // Default: *_~(" + }; + + var content = @"Sample URL (http://www.google.com)"; + var expected = @"

    Sample URL (http://www.google.com)

    "; + + TestUtility.VerifyMarkup(content, expected, optionalExtensions: [ + new("AutoLinks", new JsonObject + { + ["options"] = JsonSerializer.SerializeToNode(options, MarkdigExtensionSettingConverter.DefaultSerializerOptions), + }) + ]); + } +} diff --git a/test/Docfx.MarkdigEngine.Extensions.Tests/MarkdigBuiltinExtensionTests/EmojiTest.cs b/test/Docfx.MarkdigEngine.Extensions.Tests/MarkdigBuiltinExtensionTests/EmojiTest.cs new file mode 100644 index 00000000000..722028c7895 --- /dev/null +++ b/test/Docfx.MarkdigEngine.Extensions.Tests/MarkdigBuiltinExtensionTests/EmojiTest.cs @@ -0,0 +1,64 @@ +// 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.Nodes; +using Markdig.Extensions.Emoji; +using Xunit; + +namespace Docfx.MarkdigEngine.Tests; + +/// +/// Unit tests for markdig . +/// +/// +[Trait("Related", "MarkdigExtension")] +public class EmojiTest +{ + [Fact] + public void EmojiTest_DocfxDefault() + { + var content = @"**content :** :smile:"; + var expected = @"

    content : 😄

    "; + + // By default. `UseEmojiAndSmiley(enableSmileys: false)` option used. + TestUtility.VerifyMarkup(content, expected); + } + + [Fact] + public void EmojiTest_MarkdigDefault() + { + var content = @":)"; + var expected = @"

    😃

    "; + + // `UseEmojiAndSmiley(enableSmileys: true)` option is used when enable `Emojis` extension by name. + TestUtility.VerifyMarkup(content, expected, optionalExtensions: ["Emojis"]); + } + + [Fact] + public void EmojiTest_Smileys_Enabled() + { + var content = @":)"; + var expected = @"

    😃

    "; + + TestUtility.VerifyMarkup(content, expected, optionalExtensions: [ + new("Emojis", new JsonObject + { + ["options"] = true + }), + ]); + } + + [Fact] + public void EmojiTest_Smileys_Disabled() + { + var content = @":)"; + var expected = @"

    :)

    "; + + TestUtility.VerifyMarkup(content, expected, optionalExtensions: [ + new("Emojis", new JsonObject + { + ["options"] = false, + }), + ]); + } +} diff --git a/test/Docfx.MarkdigEngine.Extensions.Tests/MarkdigBuiltinExtensionTests/EmphasisExtraTest.cs b/test/Docfx.MarkdigEngine.Extensions.Tests/MarkdigBuiltinExtensionTests/EmphasisExtraTest.cs new file mode 100644 index 00000000000..60a28684fd5 --- /dev/null +++ b/test/Docfx.MarkdigEngine.Extensions.Tests/MarkdigBuiltinExtensionTests/EmphasisExtraTest.cs @@ -0,0 +1,114 @@ +// 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.Nodes; +using Markdig.Extensions.EmphasisExtras; +using Xunit; + +namespace Docfx.MarkdigEngine.Tests; + +/// +/// Unit tests for markdig . +/// +/// +[Trait("Related", "MarkdigExtension")] +public class EmphasisExtraTest +{ + [Fact] + public void EmphasisExtraTest_DocfxDefault() + { + var content = @"The following text ~~is deleted~~"; + var expected = @"

    The following text is deleted

    "; + + // `Strikethrough` is enabled by default. + TestUtility.VerifyMarkup(content, expected); + } + + [Fact] + public void EmphasisExtraTest_ResetToMarkdigDefault() + { + var content = + """ + The following text ~~is deleted~~ + H~2~O is a liquid. 2^10^ is 1024 + ++Inserted text++ + ==Marked text== + """; + + var expected = + """ +

    + The following text is deleted + H2O is a liquid. 210 is 1024 + Inserted text + Marked text +

    + """; + + // `EmphasisExtraOptions.Default` option is used + TestUtility.VerifyMarkup(content, expected, optionalExtensions: ["EmphasisExtras"]); + } + + [Fact] + public void EmphasisExtraTest_SuperscriptAndSubscript() + { + var content = @"H~2~O is a liquid. 2^10^ is 1024"; + + // `Superscript` and `Subscript` are disabled by default. + { + var expected = $"

    {content}

    "; + TestUtility.VerifyMarkup(content, expected); + } + // `Superscript` and `Subscript` is enabled when using default options or option is explicitly specified. + { + var expected = @"

    H2O is a liquid. 210 is 1024

    "; + TestUtility.VerifyMarkup(content, expected, optionalExtensions: [ + new("EmphasisExtras", new JsonObject + { + ["options"] = "Superscript, Subscript", + })]); + } + } + + [Fact] + public void EmphasisExtraTest_Inserted() + { + var content = @"++Inserted text++"; + + // `Inserted` is disabled by default. + { + var expected = $"

    {content}

    "; + TestUtility.VerifyMarkup(content, expected); + } + // `Inserted` is enabled when using default options or option is explicitly specified. + { + var expected = @"

    Inserted text

    "; + TestUtility.VerifyMarkup(content, expected, optionalExtensions: [ + new("EmphasisExtras", new JsonObject + { + ["options"] = "Inserted", + })]); + } + } + + [Fact] + public void EmphasisExtraTest_Marked() + { + var content = @"==Marked text=="; + + // `Marked` is disabled by default. + { + var expected = $"

    {content}

    "; + TestUtility.VerifyMarkup(content, expected); + } + // `Marked` is enabled when using default options or option is explicitly specified. + { + var expected = @"

    Marked text

    "; + TestUtility.VerifyMarkup(content, expected, optionalExtensions: [ + new("EmphasisExtras", new JsonObject + { + ["options"] = "Marked", + })]); + } + } +} diff --git a/test/Docfx.MarkdigEngine.Extensions.Tests/MarkdigBuiltinExtensionTests/MediaLinksTest.cs b/test/Docfx.MarkdigEngine.Extensions.Tests/MarkdigBuiltinExtensionTests/MediaLinksTest.cs new file mode 100644 index 00000000000..5e2a66c84d1 --- /dev/null +++ b/test/Docfx.MarkdigEngine.Extensions.Tests/MarkdigBuiltinExtensionTests/MediaLinksTest.cs @@ -0,0 +1,53 @@ +// 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; +using System.Text.Json.Nodes; +using Docfx.MarkdigEngine.Extensions; +using Markdig.Extensions.MediaLinks; +using Xunit; + +namespace Docfx.MarkdigEngine.Tests; + +/// +/// Unit tests for markdig . +/// +/// +[Trait("Related", "MarkdigExtension")] +public class MediaLinksTest +{ + [Fact] + public void MediaLinksTest_Default() + { + var content = "![static mp4](https://example.com/video.mp4)"; + + var expected = """

    """; + + TestUtility.VerifyMarkup(content, expected); + TestUtility.VerifyMarkup(content, expected, optionalExtensions: ["MediaLinks"]); + } + + [Fact] + public void MediaLinksTest_Custom() + { + // `ExtensionToMimeType` and `Hosts` property override is not supported. + // Because it getter-only property. and it can't deserialize property value); + var options = new MediaOptions + { + Height = "100", // Default: 500 + Width = "100", // Default: 281 + AddControlsProperty = false, // Default: true + Class = "custom-class", // Default: "" + }; + + var content = "![static mp4](https://example.com/video.mp4)"; + var expected = $"""

    """; + + TestUtility.VerifyMarkup(content, expected, optionalExtensions: [ + new("MediaLinks", new JsonObject + { + ["options"] = JsonSerializer.SerializeToNode(options, MarkdigExtensionSettingConverter.DefaultSerializerOptions), + }) + ]); + } +} diff --git a/test/Docfx.MarkdigEngine.Extensions.Tests/MarkdigBuiltinExtensionTests/PipeTableTest.cs b/test/Docfx.MarkdigEngine.Extensions.Tests/MarkdigBuiltinExtensionTests/PipeTableTest.cs new file mode 100644 index 00000000000..6752955efd0 --- /dev/null +++ b/test/Docfx.MarkdigEngine.Extensions.Tests/MarkdigBuiltinExtensionTests/PipeTableTest.cs @@ -0,0 +1,95 @@ +// 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; +using System.Text.Json.Nodes; +using Docfx.MarkdigEngine.Extensions; +using Markdig.Extensions.Tables; +using Xunit; + +namespace Docfx.MarkdigEngine.Tests; + +/// +/// Unit tests for markdig . +/// +/// +[Trait("Related", "MarkdigExtension")] +public class PipeTableTest +{ + [Fact] + public void PipeTableTest_Default() + { + var content = + """ + a | b + -- | - + 0 | 1 | 2 + """; + + var expected = + """ + + + + + + + + + + + + + + + +
    ab
    012
    + """; + + TestUtility.VerifyMarkup(content, expected); + TestUtility.VerifyMarkup(content, expected, optionalExtensions: ["PipeTables"]); + } + + [Fact] + public void PipeTableTest_Custom() + { + var options = new PipeTableOptions + { + RequireHeaderSeparator = true, // Defaut: true + UseHeaderForColumnCount = true, // Default: false + }; + + var content = + """ + a | b + -- | - + 0 | 1 | IgnoredColumn + """; + + var expected = + """ + + + + + + + + + + + + + +
    ab
    01
    + """; + + TestUtility.VerifyMarkup(content, expected, optionalExtensions: ["gfm-pipetables"]); + TestUtility.VerifyMarkup(content, expected, optionalExtensions: [ + new ("PipeTables", new JsonObject + { + ["options"] = JsonSerializer.SerializeToNode(options, MarkdigExtensionSettingConverter.DefaultSerializerOptions), + }) + ]); + } +} diff --git a/test/Docfx.MarkdigEngine.Extensions.Tests/MarkdigBuiltinExtensionTests/SmartyPantsTest.cs b/test/Docfx.MarkdigEngine.Extensions.Tests/MarkdigBuiltinExtensionTests/SmartyPantsTest.cs new file mode 100644 index 00000000000..0f637be3954 --- /dev/null +++ b/test/Docfx.MarkdigEngine.Extensions.Tests/MarkdigBuiltinExtensionTests/SmartyPantsTest.cs @@ -0,0 +1,61 @@ +// 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; +using System.Text.Json.Nodes; +using System.Text.Json.Serialization; +using Docfx.MarkdigEngine.Extensions; +using Markdig.Extensions.SmartyPants; +using Xunit; + +namespace Docfx.MarkdigEngine.Tests; + + +/// +/// Unit tests for markdig . +/// +/// +[Trait("Related", "MarkdigExtension")] +public class SmartyPantsTest +{ + /// + /// SmartyPants extension is not enabled by docfx default. + /// + [Fact] + public void SmartyPantsTest_DocfxDefault() + { + string content = "This is a \"text 'with\" a another text'"; + string expected = "

    This is a "text 'with" a another text'

    "; + + TestUtility.VerifyMarkup(content, expected); + } + + [Fact] + public void SmartyPantsTest_Default() + { + string content = "This is a \"text 'with\" a another text'"; + string expected = "

    This is a “text 'with” a another text'

    "; + + TestUtility.VerifyMarkup(content, expected, optionalExtensions: ["SmartyPants"]); + } + + // Currently custom mapping is not works as expected. + // Because SmartyPantOptions.Mapping is defined as setter-only property. It's not deserialized by default. + [Fact(Skip = "Currently custom mapping is not supported.")] + public void SmartyPantsTest_Custom() + { + var options = new SmartyPantOptions(); + options.Mapping[SmartyPantType.LeftQuote] = "<<"; + options.Mapping[SmartyPantType.RightQuote] = ">>"; + + string content = "This is a 'text with' a another text'"; + string expected = "

    This is a <> a another text'

    "; + + TestUtility.VerifyMarkup(content, expected, optionalExtensions: [ + new("SmartyPants", new JsonObject + { + ["options"] = JsonSerializer.SerializeToNode(options, MarkdigExtensionSettingConverter.DefaultSerializerOptions), + }) + ]); + } +} diff --git a/test/Docfx.MarkdigEngine.Extensions.Tests/TestUtility.cs b/test/Docfx.MarkdigEngine.Extensions.Tests/TestUtility.cs index d0129598fd5..293bc15f0d6 100644 --- a/test/Docfx.MarkdigEngine.Extensions.Tests/TestUtility.cs +++ b/test/Docfx.MarkdigEngine.Extensions.Tests/TestUtility.cs @@ -19,14 +19,14 @@ public static void VerifyMarkup( string filePath = "test.md", Dictionary tokens = null, Dictionary files = null, - IEnumerable optionalExtensions = null, + MarkdigExtensionSetting[] optionalExtensions = null, Dictionary notes = null, PlantUmlOptions plantUml = null) { errors ??= Array.Empty(); tokens ??= new Dictionary(); files ??= new Dictionary(); - optionalExtensions ??= new List(); + optionalExtensions ??= []; var actualErrors = new List(); var actualDependencies = new HashSet(); diff --git a/test/docfx.Tests/Api.verified.cs b/test/docfx.Tests/Api.verified.cs index 0933d8366bb..10a3ec16426 100644 --- a/test/docfx.Tests/Api.verified.cs +++ b/test/docfx.Tests/Api.verified.cs @@ -3284,7 +3284,7 @@ public MarkdownServiceProperties() { } public string[] FallbackFolders { get; set; } [Newtonsoft.Json.JsonProperty("markdigExtensions")] [System.Text.Json.Serialization.JsonPropertyName("markdigExtensions")] - public string[] MarkdigExtensions { get; set; } + public Docfx.MarkdigEngine.Extensions.MarkdigExtensionSetting[] MarkdigExtensions { get; set; } [Newtonsoft.Json.JsonProperty("plantUml")] [System.Text.Json.Serialization.JsonPropertyName("plantUml")] public Docfx.MarkdigEngine.Extensions.PlantUmlOptions PlantUml { get; set; } @@ -3564,6 +3564,17 @@ public LineNumberExtension(System.Func getFilePath = null) { } public void Setup(Markdig.MarkdownPipelineBuilder pipeline) { } public void Setup(Markdig.MarkdownPipeline pipeline, Markdig.Renderers.IMarkdownRenderer renderer) { } } + [Newtonsoft.Json.JsonConverter(typeof(Docfx.MarkdigEngine.Extensions.MarkdigExtensionSettingConverter))] + [System.Diagnostics.DebuggerDisplay("Name = {Name}")] + public class MarkdigExtensionSetting + { + public MarkdigExtensionSetting(string name, System.Text.Json.Nodes.JsonObject? options = null) { } + public string Name { get; init; } + public System.Text.Json.JsonElement? Options { get; init; } + public T GetOptions(T fallbackValue) { } + public T GetOptionsValue(string key, T fallbackValue) { } + public static Docfx.MarkdigEngine.Extensions.MarkdigExtensionSetting op_Implicit(string name) { } + } public class MarkdownContext { public MarkdownContext(System.Func getToken = null, Docfx.MarkdigEngine.Extensions.MarkdownContext.LogActionDelegate logInfo = null, Docfx.MarkdigEngine.Extensions.MarkdownContext.LogActionDelegate logSuggestion = null, Docfx.MarkdigEngine.Extensions.MarkdownContext.LogActionDelegate logWarning = null, Docfx.MarkdigEngine.Extensions.MarkdownContext.LogActionDelegate logError = null, Docfx.MarkdigEngine.Extensions.MarkdownContext.ReadFileDelegate readFile = null, Docfx.MarkdigEngine.Extensions.MarkdownContext.GetLinkDelegate getLink = null, Docfx.MarkdigEngine.Extensions.MarkdownContext.GetImageLinkDelegate getImageLink = null) { } @@ -3606,7 +3617,7 @@ public static Markdig.MarkdownPipelineBuilder UseLineNumber(this Markdig.Markdow public static Markdig.MarkdownPipelineBuilder UseMonikerRange(this Markdig.MarkdownPipelineBuilder pipeline, Docfx.MarkdigEngine.Extensions.MarkdownContext context) { } public static Markdig.MarkdownPipelineBuilder UseNestedColumn(this Markdig.MarkdownPipelineBuilder pipeline, Docfx.MarkdigEngine.Extensions.MarkdownContext context) { } public static Markdig.MarkdownPipelineBuilder UseNoloc(this Markdig.MarkdownPipelineBuilder pipeline) { } - public static Markdig.MarkdownPipelineBuilder UseOptionalExtensions(this Markdig.MarkdownPipelineBuilder pipeline, System.Collections.Generic.IEnumerable optionalExtensions) { } + public static Markdig.MarkdownPipelineBuilder UseOptionalExtensions(this Markdig.MarkdownPipelineBuilder pipeline, Docfx.MarkdigEngine.Extensions.MarkdigExtensionSetting[] optionalExtensions) { } public static Markdig.MarkdownPipelineBuilder UsePlantUml(this Markdig.MarkdownPipelineBuilder pipeline, Docfx.MarkdigEngine.Extensions.MarkdownContext context, Docfx.MarkdigEngine.Extensions.PlantUmlOptions options = null) { } public static Markdig.MarkdownPipelineBuilder UseQuoteSectionNote(this Markdig.MarkdownPipelineBuilder pipeline, Docfx.MarkdigEngine.Extensions.MarkdownContext context, System.Collections.Generic.Dictionary notes = null) { } public static Markdig.MarkdownPipelineBuilder UseResolveLink(this Markdig.MarkdownPipelineBuilder pipeline, Docfx.MarkdigEngine.Extensions.MarkdownContext context) { }