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
64 changes: 46 additions & 18 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,36 +3,64 @@ on:
push:
branches:
- master
- 5.x
pull_request:
branches:
- master
- 5.x

env:
DOTNET_CLI_TELEMETRY_OPTOUT: 1
DOTNET_SKIP_FIRST_TIME_EXPERIENCE: 1

jobs:
job:
strategy:
matrix:
include:
- os: ubuntu-latest
artifact-name: Linux
#- os: macos-11
# artifact-name: Darwin
#- os: windows-2022
# artifact-name: Win64
runs-on: ${{ matrix.os }}
continue-on-error: true
# Compiles every target framework (including the downlevel net46 and netstandard2.0
# builds) and runs the full test suite on every framework that can execute on Linux.
# net6.0 resolves the library's netstandard2.0 build, so that build is fully tested.
build-and-test:
runs-on: ubuntu-latest
steps:
- name: checkout repo
- name: Checkout repo
uses: actions/checkout@v4
- name: Install .NET 9.0.x
- name: Install .NET SDKs
uses: actions/setup-dotnet@v4
with:
dotnet-version: 9.0.x
dotnet-version: |
6.0.x
8.0.x
9.0.x
10.0.x
- name: Display dotnet info
run: dotnet --list-sdks
- name: Run tests
run: dotnet test src/ReverseMarkdown.Test/ReverseMarkdown.Test.csproj --framework net9.0
run: dotnet --list-sdks
# Build all target frameworks: net46;netstandard2.0;net8.0;net9.0;net10.0.
# net46 is built on Linux via the Microsoft.NETFramework.ReferenceAssemblies package.
- name: Build library (all target frameworks)
run: dotnet build src/ReverseMarkdown/ReverseMarkdown.csproj -c Release
- name: Test net6.0 (netstandard2.0 build)
run: dotnet test src/ReverseMarkdown.Test/ReverseMarkdown.Test.csproj --framework net6.0 -c Release
- name: Test net8.0
run: dotnet test src/ReverseMarkdown.Test/ReverseMarkdown.Test.csproj --framework net8.0 -c Release
- name: Test net9.0
run: dotnet test src/ReverseMarkdown.Test/ReverseMarkdown.Test.csproj --framework net9.0 -c Release
- name: Test net10.0
run: dotnet test src/ReverseMarkdown.Test/ReverseMarkdown.Test.csproj --framework net10.0 -c Release

# Runs the full suite on real .NET Framework so the net46 build is exercised at runtime
# (net48 -> library net46 build). Windows-only, since .NET Framework cannot run on Linux.
net-framework:
runs-on: windows-latest
steps:
- name: Checkout repo
uses: actions/checkout@v4
- name: Install .NET SDKs
uses: actions/setup-dotnet@v4
with:
dotnet-version: |
6.0.x
9.0.x
- name: Display dotnet info
run: dotnet --list-sdks
- name: Test net48 (net46 build)
run: dotnet test src/ReverseMarkdown.Test/ReverseMarkdown.Test.csproj --framework net48 -c Release
- name: Test net6.0 (netstandard2.0 build)
run: dotnet test src/ReverseMarkdown.Test/ReverseMarkdown.Test.csproj --framework net6.0 -c Release
9 changes: 7 additions & 2 deletions .github/workflows/nuget-publish.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,16 @@ jobs:
- name: Checkout code
uses: actions/checkout@v4

- name: Install .NET 9.0.x
- name: Install .NET SDKs
uses: actions/setup-dotnet@v4
with:
dotnet-version: 9.0.x
dotnet-version: |
8.0.x
9.0.x
10.0.x

# Packs all target frameworks: net46;netstandard2.0;net8.0;net9.0;net10.0.
# net46 is built on Linux via the Microsoft.NETFramework.ReferenceAssemblies package.
- name: Run Pack
run: dotnet pack src/ReverseMarkdown/ReverseMarkdown.csproj -c Release
shell: bash
Expand Down

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

43 changes: 27 additions & 16 deletions src/ReverseMarkdown.Test/ConverterTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@
using System.Linq;
using System.Text.Json;
using System.Threading.Tasks;
using VerifyTests;
using VerifyXunit;
using Xunit;
using Xunit.Abstractions;

Expand All @@ -14,13 +12,24 @@ namespace ReverseMarkdown.Test
public class ConverterTests
{
private readonly ITestOutputHelper _testOutputHelper;
private readonly VerifySettings _verifySettings;

public ConverterTests(ITestOutputHelper testOutputHelper)
{
_testOutputHelper = testOutputHelper;
_verifySettings = new VerifySettings();
_verifySettings.DisableRequireUniquePrefix();
}

// Reads a checked-in snapshot file (formerly compared by Verify) and normalises
// line endings + trailing newline so it can be asserted on every target framework.
private static string LoadVerified(string testName, string extension)
{
var path = Path.Combine(GetProjectDirectory().FullName,
$"ConverterTests.{testName}.verified.{extension}");
return Normalize(File.ReadAllText(path));
}

private static string Normalize(string text)
{
return text.Replace("\r\n", "\n").Replace("\r", "\n").TrimEnd('\n');
}

private static readonly Lazy<Dictionary<string, string>> CaseHtmlById = new(() =>
Expand Down Expand Up @@ -197,7 +206,7 @@ public void WhenOutputLineEndingConfigured_ThenNormalizeOutputLineEndings()

var result = converter.Convert(html);

Assert.Equal(result, result.ReplaceLineEndings("\n"));
Assert.Equal(result, result.Replace("\r\n", "\n").Replace("\r", "\n"));
Assert.DoesNotContain("\r", result);
}

Expand Down Expand Up @@ -591,15 +600,14 @@ public void WhenBase64ImgTag_WithSaveToFileAndNonExistentDirectory_ThenCreateDir


[Fact]
public Task When_Underline_Tag_With_AliasConverter_Register_ThenConvertToItalics()
public void When_Underline_Tag_With_AliasConverter_Register_ThenConvertToItalics()
{
var html = LoadHtml("Underline_Tag_Alias");
var converter = new Converter();
converter.Register("u", new ReverseMarkdown.Converters.AliasConverter(converter, "em"));
var result = converter.Convert(html);
var settings = new VerifySettings();
settings.DisableRequireUniquePrefix();
return Verifier.Verify(result, settings: settings, extension: "md");
var expected = LoadVerified(nameof(When_Underline_Tag_With_AliasConverter_Register_ThenConvertToItalics), "md");
Assert.Equal(expected, Normalize(result));
}


Expand All @@ -611,16 +619,16 @@ public Task When_Underline_Tag_With_AliasConverter_Register_ThenConvertToItalics


[Fact]
public Task Check_Converter_With_Unknown_Tag_Raise_Option()
public void Check_Converter_With_Unknown_Tag_Raise_Option()
{
var html = LoadHtml("Unknown_Tag_Raise");
var config = new Config
{
UnknownTags = Config.UnknownTagsOption.Raise
};
var converter = new Converter(config);
return Verifier.Throws(() => converter.Convert(html), settings: _verifySettings)
.IgnoreMember<Exception>(e => e.StackTrace);
var ex = Assert.Throws<UnknownTagException>(() => converter.Convert(html));
Assert.Equal("Unknown tag: unknown-tag", ex.Message);
}


Expand Down Expand Up @@ -936,7 +944,10 @@ private static IEnumerable<CaseData> ApplyTagFilter(IEnumerable<CaseData> cases)
return cases;
}

var tags = filter.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
var tags = filter.Split(',')
.Select(tag => tag.Trim())
.Where(tag => tag.Length > 0)
.ToArray();
return cases.Where(testCase => testCase.Tags.Any(tag => tags.Contains(tag, StringComparer.OrdinalIgnoreCase)));
}

Expand All @@ -958,11 +969,11 @@ private static string LoadExpected(CaseData testCase)

var expected = File.ReadAllText(path);
if (expected.EndsWith("\r\n", StringComparison.Ordinal)) {
return expected[..^2];
return expected.Substring(0, expected.Length - 2);
}

if (expected.EndsWith("\n", StringComparison.Ordinal)) {
return expected[..^1];
return expected.Substring(0, expected.Length - 1);
}

return expected;
Expand Down
42 changes: 32 additions & 10 deletions src/ReverseMarkdown.Test/ReverseMarkdown.Test.csproj
Original file line number Diff line number Diff line change
@@ -1,23 +1,51 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net8.0;net9.0;net10.0</TargetFrameworks>
<!--
The suite runs on every framework the library supports, so the downlevel builds
(net48 -> net46, net6.0 -> netstandard2.0) get full runtime coverage. Verify was
removed in favour of plain assertions / checked-in snapshot files so the tests can
run on net48 and net6.0 without framework-specific tooling.
-->
<TargetFrameworks>net48;net6.0;net8.0;net9.0;net10.0</TargetFrameworks>
<IsTestProject>true</IsTestProject>
<LangVersion>preview</LangVersion>
<SuppressTfmSupportBuildErrors>true</SuppressTfmSupportBuildErrors>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="xunit" Version="2.9.3" />
<PackageReference Include="Markdig" Version="1.0.0" />
</ItemGroup>

<!-- Modern frameworks: latest test tooling. -->
<ItemGroup Condition="'$(TargetFramework)' == 'net8.0' or '$(TargetFramework)' == 'net9.0' or '$(TargetFramework)' == 'net10.0'">
<PackageReference Include="coverlet.collector" Version="8.0.0">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.0.1" />
<PackageReference Include="xunit" Version="2.9.3" />
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.5">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Verify.Xunit" Version="31.12.5" />
<PackageReference Include="Markdig" Version="1.0.0" />
</ItemGroup>

<!-- Downlevel frameworks: last test tooling that supports the net48/net6.0 hosts.
coverlet.collector is intentionally omitted here: its transitive
Microsoft.Diagnostics.NETCore.Client wants Microsoft.Bcl.AsyncInterfaces 9.0,
which isn't deployed on the net6.0 host and silently breaks test discovery. -->
<ItemGroup Condition="'$(TargetFramework)' == 'net48' or '$(TargetFramework)' == 'net6.0'">
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>

<!-- net48 lacks an in-box System.Text.Json and .NET Framework reference assemblies. -->
<ItemGroup Condition="'$(TargetFramework)' == 'net48'">
<PackageReference Include="System.Text.Json" Version="8.0.5" />
<PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.3" PrivateAssets="all" />
</ItemGroup>

<ItemGroup>
Expand All @@ -44,12 +72,6 @@
<None Update="ConverterTests.Bug294_Table_bug_with_row_superfluous_newlines.verified.md">
<DependentUpon>ConverterTests.cs</DependentUpon>
</None>
<None Update="ConverterTests.Check_Converter_With_Unknown_Tag_Raise_Option.verified.txt">
<DependentUpon>ConverterTests.cs</DependentUpon>
</None>
<None Update="ConverterTests.Check_Converter_With_Unknown_Tag_Raise_Option.DotNet6_0.received.txt">
<DependentUpon>ConverterTests.cs</DependentUpon>
</None>
<None Update="ConverterTests.When_Anchor_Text_with_Underscore_Do_Not_Escape.verified.md">
<DependentUpon>ConverterTests.cs</DependentUpon>
</None>
Expand Down
1 change: 0 additions & 1 deletion src/ReverseMarkdown.Test/Snippets.Usage.verified.txt

This file was deleted.

6 changes: 2 additions & 4 deletions src/ReverseMarkdown.Test/Snippets.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
using System;
using System.Threading.Tasks;
using ReverseMarkdown;
using VerifyXunit;
using Xunit;

public class Snippets
{
[Fact]
public async Task Usage()
public void Usage()
{
#region Usage

Expand All @@ -19,7 +17,7 @@ public async Task Usage()

#endregion

await Verifier.Verify(result);
Assert.Equal("This a sample **paragraph** from [my site](http://test.com)", result);
}

[Fact]
Expand Down
11 changes: 11 additions & 0 deletions src/ReverseMarkdown/Cleaner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
namespace ReverseMarkdown;

public static partial class Cleaner {
#if NET7_0_OR_GREATER
[GeneratedRegex(@"\*(\s\*)+")]
private static partial Regex SlackBoldCleaner();

Expand All @@ -14,6 +15,16 @@ public static partial class Cleaner {

[GeneratedRegex(@"[\u0020\u00A0]")]
private static partial Regex NonBreakingSpaces();
#else
private static readonly Regex _slackBoldCleaner = new(@"\*(\s\*)+", RegexOptions.Compiled);
private static Regex SlackBoldCleaner() => _slackBoldCleaner;

private static readonly Regex _slackItalicCleaner = new(@"_(\s_)+", RegexOptions.Compiled);
private static Regex SlackItalicCleaner() => _slackItalicCleaner;

private static readonly Regex _nonBreakingSpaces = new(@"[\u0020\u00A0]", RegexOptions.Compiled);
private static Regex NonBreakingSpaces() => _nonBreakingSpaces;
#endif

private static readonly StringReplaceValues TagBorders = new() {
["\n\t"] = string.Empty,
Expand Down
4 changes: 2 additions & 2 deletions src/ReverseMarkdown/Converters/Code.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@ public override void Convert(TextWriter writer, HtmlNode node)
var content = node.InnerHtml;
var fence = CreateCommonMarkCodeFence(content);
writer.Write(fence);
var needsBacktickPadding = content.Length > 0 && (content[0] == '`' || content[^1] == '`');
var needsBacktickPadding = content.Length > 0 && (content[0] == '`' || content[content.Length - 1] == '`');
var needsWhitespacePadding = content.Length > 0 &&
(char.IsWhiteSpace(content[0]) || char.IsWhiteSpace(content[^1]));
(char.IsWhiteSpace(content[0]) || char.IsWhiteSpace(content[content.Length - 1]));
var needsPadding = needsBacktickPadding || needsWhitespacePadding;

if (needsPadding) {
Expand Down
2 changes: 1 addition & 1 deletion src/ReverseMarkdown/Converters/H.cs
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ private static string EscapeTrailingHashes(string content)
var hashCount = content.Length - 1 - index;
var escapedHashes = new string('#', hashCount);
escapedHashes = escapedHashes.Replace("#", "\\#");
return content[..(index + 1)] + escapedHashes;
return content.Substring(0, index + 1) + escapedHashes;
}
}
}
6 changes: 6 additions & 0 deletions src/ReverseMarkdown/Converters/Pre.cs
Original file line number Diff line number Diff line change
Expand Up @@ -129,8 +129,14 @@ private static string GetLanguageFromHighlightClassAttribute(HtmlNode node)
/// Extracts class attribute syntax using: highlight-json, highlight-source-json, language-json, brush: language
/// Returns the Language in Match.Groups[2]
/// </summary>
#if NET7_0_OR_GREATER
[GeneratedRegex(@"(highlight-source-|language-|highlight-|brush:\s)([^\s]+)")]
private static partial Regex ClassRegex();
#else
private static readonly Regex _classRegex =
new(@"(highlight-source-|language-|highlight-|brush:\s)([^\s]+)", RegexOptions.Compiled);
private static Regex ClassRegex() => _classRegex;
#endif

/// <summary>
/// Checks class attribute for language class identifiers for various
Expand Down
Loading
Loading