feat: initial commit

This commit is contained in:
Dylan R. E. Moonfire 2021-09-06 23:56:25 -05:00
commit 9fe90f820c
61 changed files with 16325 additions and 0 deletions

122
.editorconfig Normal file
View File

@ -0,0 +1,122 @@
# EditorConfig is awesome: https://EditorConfig.org
root = true
[*]
charset=utf-8
end_of_line = lf
insert_final_newline=true
indent_style=space
indent_size=4
# Microsoft .NET properties
csharp_new_line_before_members_in_object_initializers=false
csharp_preferred_modifier_order=public, private, protected, internal, new, abstract, virtual, sealed, override, static, readonly, extern, unsafe, volatile, async:suggestion
csharp_space_after_cast=false
csharp_style_var_elsewhere=false:hint
csharp_style_var_for_built_in_types=false:hint
csharp_style_var_when_type_is_apparent=true:hint
csharp_preserve_single_line_statements=false
csharp_preserve_single_line_blocks=true
dotnet_style_predefined_type_for_locals_parameters_members=true:hint
dotnet_style_predefined_type_for_member_access=true:hint
dotnet_style_qualification_for_event=true:hint
dotnet_style_qualification_for_field=true:hint
dotnet_style_qualification_for_method=true:hint
dotnet_style_qualification_for_property=true:hint
dotnet_style_require_accessibility_modifiers=for_non_interface_members:hint
# ReSharper properties
resharper_alignment_tab_fill_style=optimal_fill
resharper_apply_on_completion=true
resharper_blank_lines_after_control_transfer_statements=1
resharper_blank_lines_around_single_line_auto_property=1
resharper_blank_lines_around_single_line_property=1
resharper_blank_lines_before_single_line_comment=1
resharper_blank_lines_between_using_groups=1
resharper_braces_for_for=required
resharper_braces_for_foreach=required
resharper_braces_for_ifelse=required
resharper_braces_for_while=required
resharper_can_use_global_alias=false
resharper_csharp_blank_lines_around_single_line_field=1
resharper_csharp_blank_lines_around_single_line_invocable=1
resharper_csharp_indent_style=tab
resharper_csharp_insert_final_newline=true
resharper_csharp_keep_blank_lines_in_code=1
resharper_csharp_keep_blank_lines_in_declarations=1
resharper_csharp_new_line_before_while=true
resharper_csharp_use_indent_from_vs=false
resharper_csharp_wrap_arguments_style=chop_if_long
resharper_csharp_wrap_extends_list_style=chop_if_long
resharper_csharp_wrap_parameters_style=chop_if_long
resharper_css_insert_final_newline=false
resharper_enforce_line_ending_style=true
resharper_html_insert_final_newline=false
resharper_indent_nested_fixed_stmt=true
resharper_js_indent_style=tab
resharper_js_insert_final_newline=true
resharper_js_keep_blank_lines_in_code=1
resharper_js_stick_comment=false
resharper_js_use_indent_from_vs=false
resharper_js_wrap_before_binary_opsign=true
resharper_js_wrap_chained_method_calls=chop_if_long
resharper_keep_blank_lines_between_declarations=1
resharper_min_blank_lines_after_imports=1
resharper_place_attribute_on_same_line=False
resharper_place_constructor_initializer_on_same_line=false
resharper_place_type_constraints_on_same_line=false
resharper_protobuf_insert_final_newline=false
resharper_qualified_using_at_nested_scope=true
resharper_resx_insert_final_newline=false
resharper_space_within_single_line_array_initializer_braces=true
resharper_use_indents_from_main_language_in_file=false
resharper_vb_insert_final_newline=false
resharper_wrap_after_declaration_lpar=true
resharper_wrap_after_invocation_lpar=true
resharper_wrap_before_extends_colon=true
resharper_wrap_before_first_type_parameter_constraint=true
resharper_wrap_before_type_parameter_langle=true
resharper_xmldoc_indent_child_elements=ZeroIndent
resharper_xmldoc_indent_text=ZeroIndent
resharper_xmldoc_insert_final_newline=false
resharper_xml_insert_final_newline=false
# ReSharper inspection severities
resharper_check_namespace_highlighting=none
resharper_convert_to_auto_property_highlighting=none
resharper_localizable_element_highlighting=none
resharper_redundant_comma_in_attribute_list_highlighting=none
resharper_redundant_comma_in_enum_declaration_highlighting=none
resharper_redundant_comma_in_initializer_highlighting=none
resharper_string_compare_to_is_culture_specific_highlighting=none
resharper_string_index_of_is_culture_specific_1_highlighting=none
resharper_use_null_propagation_highlighting=none
resharper_use_object_or_collection_initializer_highlighting=hint
resharper_use_string_interpolation_highlighting=hint
# Matches the exact files either package.json or .travis.yml
[{package.json,.travis.yml}]
indent_style=space
indent_size=2
tab_width=2
[*.{cs,js,json,jsx,proto,resjson,ts,tsx}]
indent_style=space
indent_size=space
tab_width=4
[*.{asax,ascx,aspx,cshtml,css,htm,html,master,razor,skin,vb,xaml,xamlx,xoml}]
indent_style=space
indent_size=4
tab_width=4
[*.{appxmanifest,build,config,csproj,dbml,discomap,dtd,jsproj,lsproj,njsproj,nuspec,proj,props,resw,resx,StyleCop,targets,tasks,vbproj,xml,xsd}]
indent_style=space
indent_size=2
tab_width=2
[*.proto]
indent_style=space
indent_size=2
tab_width=2

1
.envrc Normal file
View File

@ -0,0 +1 @@
use asdf

13
.gitignore vendored Normal file
View File

@ -0,0 +1,13 @@
launchSettings.json
*~
*.user
Directory.Build.props
obj/
[Bb]in/
.vs/
.vscode/
.idea/
_ReSharper.Caches/
node_modules/

51
.gitlab-ci.yml Normal file
View File

@ -0,0 +1,51 @@
stages:
- build
default:
before_script:
- curl -sL https://deb.nodesource.com/setup_15.x | bash -
- apt-get install -y nodejs
build:
image: mcr.microsoft.com/dotnet/sdk:5.0
stage: build
script:
# Set up the environment.
- npx npm install --ci
- npx commitlint-gitlab-ci -x @commitlint/config-conventional
# Build and test everything.
- dotnet restore
- dotnet build
- 'dotnet test --test-adapter-path:. --logger:"junit;LogFilePath=../artifacts/{assembly}-test-result.xml;MethodFormat=Default;FailureBodyFormat=Verbose" --collect:"XPlat Code Coverage"'
# Summarize the output for Gitlab CI reporting.
- dotnet new tool-manifest
- dotnet tool install dotnet-reportgenerator-globaltool
- dotnet tool run reportgenerator -reports:src/*/TestResults/*/coverage.cobertura.xml -targetdir:./coverage "-reporttypes:Cobertura;TextSummary"
- grep "Line coverage" coverage/Summary.txt
# Perform the release.
- npx semantic-release
rules:
- if: '$CI_COMMIT_TITLE =~ /^chore\(release\)/'
when: never
- if: '$CI_COMMIT_TAG'
when: never
- if: '$CI_COMMIT_BRANCH != $CI_DEFAULT_BRANCH'
when: never
- when: on_success
artifacts:
when: always
paths:
- ./**/*test-result.xml
- ./coverage/Cobertura.xml
- ./coverage/Summary.*
- ./**/*.nupkg
reports:
junit:
- ./**/*test-result.xml
cobertura:
- ./coverage/Cobertura.xml

4
.husky/commit-msg Executable file
View File

@ -0,0 +1,4 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
npx --no-install commitlint --edit $1

3
.tool-versions Normal file
View File

@ -0,0 +1,3 @@
dotnet-core 5.0.100
yarn 1.22.10
nodejs 15.0.1

21
LICENSE.txt Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) Moonfire Games
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -0,0 +1,54 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.26124.0
MinimumVisualStudioVersion = 15.0.26124.0
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{954BA984-D50E-4D1C-880F-EAE678EF6945}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MfGames.Markdown.Gemtext", "src\MfGames.Markdown.Gemtext\MfGames.Markdown.Gemtext.csproj", "{2A1DE43D-544D-4CE0-ACC8-D15497BBCECA}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MfGames.Markdown.Gemtext.Tests", "src\MfGames.Markdown.Gemtext.Tests\MfGames.Markdown.Gemtext.Tests.csproj", "{D2703B25-9AF7-49FF-93A4-CB124560F2A9}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Debug|x64 = Debug|x64
Debug|x86 = Debug|x86
Release|Any CPU = Release|Any CPU
Release|x64 = Release|x64
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{2A1DE43D-544D-4CE0-ACC8-D15497BBCECA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2A1DE43D-544D-4CE0-ACC8-D15497BBCECA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2A1DE43D-544D-4CE0-ACC8-D15497BBCECA}.Debug|x64.ActiveCfg = Debug|Any CPU
{2A1DE43D-544D-4CE0-ACC8-D15497BBCECA}.Debug|x64.Build.0 = Debug|Any CPU
{2A1DE43D-544D-4CE0-ACC8-D15497BBCECA}.Debug|x86.ActiveCfg = Debug|Any CPU
{2A1DE43D-544D-4CE0-ACC8-D15497BBCECA}.Debug|x86.Build.0 = Debug|Any CPU
{2A1DE43D-544D-4CE0-ACC8-D15497BBCECA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2A1DE43D-544D-4CE0-ACC8-D15497BBCECA}.Release|Any CPU.Build.0 = Release|Any CPU
{2A1DE43D-544D-4CE0-ACC8-D15497BBCECA}.Release|x64.ActiveCfg = Release|Any CPU
{2A1DE43D-544D-4CE0-ACC8-D15497BBCECA}.Release|x64.Build.0 = Release|Any CPU
{2A1DE43D-544D-4CE0-ACC8-D15497BBCECA}.Release|x86.ActiveCfg = Release|Any CPU
{2A1DE43D-544D-4CE0-ACC8-D15497BBCECA}.Release|x86.Build.0 = Release|Any CPU
{D2703B25-9AF7-49FF-93A4-CB124560F2A9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D2703B25-9AF7-49FF-93A4-CB124560F2A9}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D2703B25-9AF7-49FF-93A4-CB124560F2A9}.Debug|x64.ActiveCfg = Debug|Any CPU
{D2703B25-9AF7-49FF-93A4-CB124560F2A9}.Debug|x64.Build.0 = Debug|Any CPU
{D2703B25-9AF7-49FF-93A4-CB124560F2A9}.Debug|x86.ActiveCfg = Debug|Any CPU
{D2703B25-9AF7-49FF-93A4-CB124560F2A9}.Debug|x86.Build.0 = Debug|Any CPU
{D2703B25-9AF7-49FF-93A4-CB124560F2A9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D2703B25-9AF7-49FF-93A4-CB124560F2A9}.Release|Any CPU.Build.0 = Release|Any CPU
{D2703B25-9AF7-49FF-93A4-CB124560F2A9}.Release|x64.ActiveCfg = Release|Any CPU
{D2703B25-9AF7-49FF-93A4-CB124560F2A9}.Release|x64.Build.0 = Release|Any CPU
{D2703B25-9AF7-49FF-93A4-CB124560F2A9}.Release|x86.ActiveCfg = Release|Any CPU
{D2703B25-9AF7-49FF-93A4-CB124560F2A9}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{2A1DE43D-544D-4CE0-ACC8-D15497BBCECA} = {954BA984-D50E-4D1C-880F-EAE678EF6945}
{D2703B25-9AF7-49FF-93A4-CB124560F2A9} = {954BA984-D50E-4D1C-880F-EAE678EF6945}
EndGlobalSection
EndGlobal

File diff suppressed because it is too large Load Diff

4
README.md Normal file
View File

@ -0,0 +1,4 @@
MfGames.Markdown.Gemtext CIL
============================
An extension for [Markdig](https://github.com/xoofx/markdig) that converts Markdown into Gemtext.

3
commitlint.config.js Normal file
View File

@ -0,0 +1,3 @@
module.exports = {
extends: ["@commitlint/config-conventional"],
};

12304
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

21
package.json Normal file
View File

@ -0,0 +1,21 @@
{
"name": "mfgames-markdown-gemtext-cil",
"version": "1.0.0",
"private": true,
"scripts": {
"prepare": "husky install"
},
"devDependencies": {
"@commitlint/cli": "^13.1.0",
"@commitlint/config-conventional": "^13.1.0",
"@semantic-release/changelog": "^5.0.1",
"@semantic-release/git": "^9.0.0",
"@semantic-release/gitlab": "^6.2.2",
"@semantic-release/npm": "^7.1.3",
"commitlint-gitlab-ci": "^0.0.4",
"husky": "^7.0.2",
"semantic-release": "^17.4.7",
"semantic-release-dotnet": "^1.0.0",
"semantic-release-nuget": "^1.1.0"
}
}

20
release.config.js Normal file
View File

@ -0,0 +1,20 @@
module.exports = {
branches: ["main"],
message: "chore(release): v${nextRelease.version}\n\n${nextRelease.notes}",
plugins: [
"@semantic-release/commit-analyzer",
"@semantic-release/release-notes-generator",
"@semantic-release/npm",
"semantic-release-dotnet",
[
"semantic-release-nuget",
{
packArguments: ["--include-symbols", "--include-source"],
pushFiles: ["src/*/bin/Debug/*.nupkg"],
},
],
"@semantic-release/changelog",
"@semantic-release/git",
"@semantic-release/gitlab",
],
};

View File

@ -0,0 +1,39 @@
using Markdig;
using MfGames.Markdown.Gemtext;
using MfGames.Markdown.Gemtext.Extensions;
using MfGames.Markdown.Gemtext.Renderers;
using Xunit;
namespace MfGames.Markdown.Gemini.Tests
{
public class CodeInlineTests
{
[Fact]
public void Default()
{
string input = "Normal `code` quote.";
string expected = "Normal code quote.";
string actual = MarkdownGemtext.ToGemtext(input);
Assert.Equal(expected, actual);
}
[Fact]
public void Normalized()
{
string input = "Normal `code` quote.";
string expected = "Normal `code` quote.";
string actual = MarkdownGemtext.ToGemtext(
input,
new MarkdownPipelineBuilder()
.Use(
new SetInlineFormatting
{
Code = InlineFormatting.Normalize,
})
.Build());
Assert.Equal(expected, actual);
}
}
}

View File

@ -0,0 +1,107 @@
using MfGames.Markdown.Gemtext;
using Xunit;
namespace MfGames.Markdown.Gemini.Tests
{
public class HeaderTests
{
[Fact]
public void BetweenParagraphs()
{
string input = string.Join(
"\n",
"Paragraph One",
"",
"# Header 1",
"",
"Paragraph Two");
string expected = string.Join(
"\n",
"Paragraph One",
"",
"# Header 1",
"",
"Paragraph Two");
string actual = MarkdownGemtext.ToGemtext(input);
Assert.Equal(expected, actual);
}
[Fact]
public void FollowedByText()
{
string input = string.Join(
"\n",
"# Header 1",
"Text Block");
string expected = string.Join(
"\n",
"# Header 1",
"",
"Text Block");
string actual = MarkdownGemtext.ToGemtext(input);
Assert.Equal(expected, actual);
}
[Fact]
public void H1()
{
string input = "# Header 1";
string expected = "# Header 1";
string actual = MarkdownGemtext.ToGemtext(input);
Assert.Equal(expected, actual);
}
[Fact]
public void H2()
{
string input = "## Header 2";
string expected = "## Header 2";
string actual = MarkdownGemtext.ToGemtext(input);
Assert.Equal(expected, actual);
}
[Fact]
public void H3()
{
string input = "### Header 3";
string expected = "### Header 3";
string actual = MarkdownGemtext.ToGemtext(input);
Assert.Equal(expected, actual);
}
[Fact]
public void H4()
{
string input = "#### Header 4";
string expected = "Header 4";
string actual = MarkdownGemtext.ToGemtext(input);
Assert.Equal(expected, actual);
}
[Fact]
public void H5()
{
string input = "##### Header 5";
string expected = "Header 5";
string actual = MarkdownGemtext.ToGemtext(input);
Assert.Equal(expected, actual);
}
[Fact]
public void H6()
{
string input = "###### Header 6";
string expected = "Header 6";
string actual = MarkdownGemtext.ToGemtext(input);
Assert.Equal(expected, actual);
}
}
}

View File

@ -0,0 +1,28 @@
using MfGames.Markdown.Gemtext;
using Xunit;
namespace MfGames.Markdown.Gemini.Tests
{
public class ImageLinkTests
{
[Fact]
public void NamedImageLink()
{
string input = "![test](image.jpg)";
string expected = "=> image.jpg test";
string actual = MarkdownGemtext.ToGemtext(input);
Assert.Equal(expected, actual);
}
[Fact]
public void SimpleImageLink()
{
string input = "![](image.jpg)";
string expected = "=> image.jpg";
string actual = MarkdownGemtext.ToGemtext(input);
Assert.Equal(expected, actual);
}
}
}

View File

@ -0,0 +1,106 @@
using Markdig;
using MfGames.Markdown.Gemtext;
using MfGames.Markdown.Gemtext.Extensions;
using Xunit;
namespace MfGames.Markdown.Gemini.Tests
{
public class IncreaseHeaderDepthsAfterFirstTests
{
private readonly string one1s;
private readonly string two1s;
public IncreaseHeaderDepthsAfterFirstTests()
{
this.two1s = string.Join(
"\n",
"# Heading 1",
"",
"Line",
"",
"# Heading 2");
this.one1s = string.Join(
"\n",
"# Heading 1",
"",
"Line",
"",
"## Heading 2");
}
[Fact]
public void WithOne1s()
{
string expected = string.Join(
"\n",
"# Heading 1",
"",
"Line",
"",
"## Heading 2");
string actual = MarkdownGemtext.ToGemtext(
this.one1s,
new MarkdownPipelineBuilder()
.Use<IncreaseHeaderDepthsAfterFirst>()
.Build());
Assert.Equal(expected, actual);
}
[Fact]
public void WithoutOne1s()
{
string expected = string.Join(
"\n",
"# Heading 1",
"",
"Line",
"",
"## Heading 2");
string actual = MarkdownGemtext.ToGemtext(
this.one1s,
new MarkdownPipelineBuilder()
.Build());
Assert.Equal(expected, actual);
}
[Fact]
public void WithoutTwo1s()
{
string expected = string.Join(
"\n",
"# Heading 1",
"",
"Line",
"",
"# Heading 2");
string actual = MarkdownGemtext.ToGemtext(
this.two1s,
new MarkdownPipelineBuilder()
.Build());
Assert.Equal(expected, actual);
}
[Fact]
public void WithTwo1s()
{
string expected = string.Join(
"\n",
"# Heading 1",
"",
"Line",
"",
"## Heading 2");
string actual = MarkdownGemtext.ToGemtext(
this.two1s,
new MarkdownPipelineBuilder()
.Use<IncreaseHeaderDepthsAfterFirst>()
.Build());
Assert.Equal(expected, actual);
}
}
}

View File

@ -0,0 +1,64 @@
using MfGames.Markdown.Gemtext;
using Xunit;
namespace MfGames.Markdown.Gemini.Tests
{
public class LinkTests
{
[Fact]
public void BareLink()
{
string input = "[](/url)";
string expected = "=> /url";
string actual = MarkdownGemtext.ToGemtext(input);
Assert.Equal(expected, actual);
}
[Fact]
public void ListOfLinks()
{
string input = string.Join(
"\n",
"- [](/a)",
"- [Two](/b)",
"- [Three](/c/d)");
string expected = string.Join(
"\n",
"=> /a",
"=> /b Two",
"=> /c/d Three");
string actual = MarkdownGemtext.ToGemtext(input);
Assert.Equal(expected, actual);
}
[Fact]
public void MixedLinkOfLists()
{
string input = string.Join(
"\n",
"- [](/a)",
"- Two",
"- [Three](/c/d)");
string expected = string.Join(
"\n",
"=> /a",
"* Two",
"=> /c/d Three");
string actual = MarkdownGemtext.ToGemtext(input);
Assert.Equal(expected, actual);
}
[Fact]
public void PlainLink()
{
string input = "[test](/url)";
string expected = "=> /url test";
string actual = MarkdownGemtext.ToGemtext(input);
Assert.Equal(expected, actual);
}
}
}

View File

@ -0,0 +1,26 @@
using MfGames.Markdown.Gemtext;
using Xunit;
namespace MfGames.Markdown.Gemini.Tests
{
public class ListTests
{
[Fact(Skip = "Not ready to figure this out.")]
public void NestedLists()
{
string input = string.Join(
"\n",
"- A",
" - B",
"- C");
string expected = string.Join(
"\n",
"* A",
"* B",
"* C");
string actual = MarkdownGemtext.ToGemtext(input);
Assert.Equal(expected, actual);
}
}
}

View File

@ -0,0 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<RootNamespace>MfGames.Markdown.Gemini.Tests</RootNamespace>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.10.0"/>
<PackageReference Include="xunit" Version="2.4.1"/>
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\MfGames.Markdown.Gemtext\MfGames.Markdown.Gemtext.csproj"/>
</ItemGroup>
</Project>

View File

@ -0,0 +1,164 @@
using Markdig;
using MfGames.Markdown.Gemtext;
using MfGames.Markdown.Gemtext.Extensions;
using MfGames.Markdown.Gemtext.Renderers;
using Xunit;
namespace MfGames.Markdown.Gemini.Tests
{
/// <summary>
/// Tests the various functionality of the ParagraphLinkHandling and
/// EndLinkInlineFormatting logic works.
/// </summary>
public class ParagraphLinkHandlingTests
{
private readonly string input;
public ParagraphLinkHandlingTests()
{
this.input = string.Join(
"\n",
"This is a paragraph with an [inline](https://example.com) link.",
"Here is [another](https://example.org/) link, part of the same paragraph.",
"",
"This is a second paragraph, with a different [link](https://duck.com) in it.");
}
[Fact]
public void DefaultHandling()
{
string expected = string.Join(
"\n",
"This is a paragraph with an",
"=> https://example.com inline",
"link. Here is",
"=> https://example.org/ another",
"link, part of the same paragraph.",
"",
"This is a second paragraph, with a different",
"=> https://duck.com link",
"in it.");
string actual = MarkdownGemtext.ToGemtext(
this.input,
new MarkdownPipelineBuilder()
.Build());
Assert.Equal(expected, actual);
}
[Fact]
public void DocumentEndHandling()
{
string expected = string.Join(
"\n",
"This is a paragraph with an inline[1] link. Here is another[2] link, part of the same paragraph.",
"",
"This is a second paragraph, with a different link[3] in it.",
"",
"=> https://example.com 1: https://example.com",
"=> https://example.org/ 2: https://example.org/",
"=> https://duck.com 3: https://duck.com");
string actual = MarkdownGemtext.ToGemtext(
this.input,
new MarkdownPipelineBuilder()
.Use(
new SetBlockLinkHandling(
BlockLinkHandling.DocumentEnd))
.Build());
Assert.Equal(expected, actual);
}
[Fact]
public void ParagraphEndHandling()
{
string expected = string.Join(
"\n",
"This is a paragraph with an inline[1] link. Here is another[2] link, part of the same paragraph.",
"",
"=> https://example.com 1: https://example.com",
"=> https://example.org/ 2: https://example.org/",
"",
"This is a second paragraph, with a different link[3] in it.",
"",
"=> https://duck.com 3: https://duck.com");
string actual = MarkdownGemtext.ToGemtext(
this.input,
new MarkdownPipelineBuilder()
.Use(
new SetBlockLinkHandling(
BlockLinkHandling.ParagraphEnd))
.Build());
Assert.Equal(expected, actual);
}
[Fact]
public void ParagraphEndHandlingWithFootnote()
{
string expected = string.Join(
"\n",
"This is a paragraph with an inline[10] link. Here is another[11] link, part of the same paragraph.",
"",
"=> https://example.com 10: https://example.com",
"=> https://example.org/ 11: https://example.org/",
"",
"This is a second paragraph, with a different link[12] in it.",
"",
"=> https://duck.com 12: https://duck.com");
string actual = MarkdownGemtext.ToGemtext(
this.input,
new MarkdownPipelineBuilder()
.Use(
new SetBlockLinkHandling(
BlockLinkHandling.ParagraphEnd,
nextFootnoteNumber: 10))
.Build());
Assert.Equal(expected, actual);
}
[Fact]
public void ParagraphEndHandlingWithTextLinks()
{
string expected = string.Join(
"\n",
"This is a paragraph with an inline link. Here is another link, part of the same paragraph.",
"",
"=> https://example.com inline",
"=> https://example.org/ another",
"",
"This is a second paragraph, with a different link in it.",
"",
"=> https://duck.com link");
string actual = MarkdownGemtext.ToGemtext(
this.input,
new MarkdownPipelineBuilder()
.Use(
new SetBlockLinkHandling(
BlockLinkHandling.ParagraphEnd,
EndLinkInlineFormatting.Text))
.Build());
Assert.Equal(expected, actual);
}
[Fact]
public void RemoveHandling()
{
string expected = string.Join(
"\n",
"This is a paragraph with an inline link. "
+ "Here is another link, part of the same paragraph.",
"",
"This is a second paragraph, with a different link in it.");
string actual = MarkdownGemtext.ToGemtext(
this.input,
new MarkdownPipelineBuilder()
.Use(new SetBlockLinkHandling(BlockLinkHandling.Remove))
.Build());
Assert.Equal(expected, actual);
}
}
}

View File

@ -0,0 +1,60 @@
using MfGames.Markdown.Gemtext;
using Xunit;
namespace MfGames.Markdown.Gemini.Tests
{
public class PlainTextTests
{
[Fact]
public void NormalText()
{
string input = "This is input.";
string expected = "This is input.";
string actual = MarkdownGemtext.ToGemtext(input);
Assert.Equal(expected, actual);
}
[Fact]
public void Paragraphs()
{
// Okay, this one isn't based on the test_plain.py, but seems
// useful to have while converting logic.
string input = string.Join(
"\n",
"Paragraph One",
string.Empty,
"Paragraph Two");
string expected = string.Join(
"\n",
"Paragraph One",
string.Empty,
"Paragraph Two");
string actual = MarkdownGemtext.ToGemtext(input);
Assert.Equal(expected, actual);
}
[Fact]
public void SoftBreakParagraphs()
{
// Okay, this one isn't based on the test_plain.py, but seems
// useful to have while converting logic.
string input = string.Join(
"\n",
"Paragraph",
"One",
" Three",
string.Empty,
"Paragraph Two");
string expected = string.Join(
"\n",
"Paragraph One Three",
string.Empty,
"Paragraph Two");
string actual = MarkdownGemtext.ToGemtext(input);
Assert.Equal(expected, actual);
}
}
}

View File

@ -0,0 +1,31 @@
using MfGames.Markdown.Gemtext;
using Xunit;
namespace MfGames.Markdown.Gemini.Tests.PythonInspired
{
/// <summary>
/// https://github.com/makeworld-the-better-one/md2gemini/blob/master/tests/test_base_url.py
/// </summary>
public class BaseUrlPythonTests
{
[Fact]
public void AbsoluteUrl()
{
string input = "[test](https://duck.com/test)";
string expected = "=> https://duck.com/test test";
string actual = MarkdownGemtext.ToGemtext(input);
Assert.Equal(expected, actual);
}
[Fact]
public void RootUrl()
{
string input = "[test](/url)";
string expected = "=> /url test";
string actual = MarkdownGemtext.ToGemtext(input);
Assert.Equal(expected, actual);
}
}
}

View File

@ -0,0 +1,81 @@
using MfGames.Markdown.Gemtext;
using Xunit;
namespace MfGames.Markdown.Gemini.Tests.PythonInspired
{
/// <summary>
/// https://github.com/makeworld-the-better-one/md2gemini/blob/master/tests/test_codeblock.py
/// </summary>
public class CodeBlockPythonTests
{
[Fact]
public void EndsWithNoNewLines()
{
string input = string.Join(
"\n",
"Non code block",
"",
" code block here");
string expected = string.Join(
"\n",
"Non code block",
"",
"```",
"code block here",
"```");
string actual = MarkdownGemtext.ToGemtext(input);
Assert.Equal(expected, actual);
}
[Fact]
public void NoExtraNewline()
{
string input = string.Join(
"\n",
"Non code block",
"",
" code block here",
"",
"More non code");
string expected = string.Join(
"\n",
"Non code block",
"",
"```",
"code block here",
"```",
"",
"More non code");
string actual = MarkdownGemtext.ToGemtext(input);
Assert.Equal(expected, actual);
}
[Fact(Skip = "Not sure how to implement or justification of this one")]
public void WithExtraNewline()
{
string input = string.Join(
"\n",
"Non code block",
"",
" code block here",
"",
"",
"More non code");
string expected = string.Join(
"\n",
"Non code block",
"",
"```",
"code block here",
"",
"```",
"",
"More non code");
string actual = MarkdownGemtext.ToGemtext(input);
Assert.Equal(expected, actual);
}
}
}

View File

@ -0,0 +1,47 @@
using MfGames.Markdown.Gemtext;
using Xunit;
namespace MfGames.Markdown.Gemini.Tests.PythonInspired
{
/// <summary>
/// https://github.com/makeworld-the-better-one/md2gemini/blob/master/tests/test_list.py
/// </summary>
public class ListPythonTests
{
[Theory]
[InlineData(1)]
[InlineData(2)]
[InlineData(3)]
[InlineData(4)]
[InlineData(5)]
public void ListWithNewlineAndSpaces(int request)
{
string padding = new string(' ', request);
string input = $"- hello\n{padding}world\n- oh";
string expected = "* hello world\n* oh";
string actual = MarkdownGemtext.ToGemtext(input);
Assert.Equal(expected, actual);
}
[Fact]
public void NormalList()
{
string input = "- hi\n- oh";
string expected = "* hi\n* oh";
string actual = MarkdownGemtext.ToGemtext(input);
Assert.Equal(expected, actual);
}
[Fact]
public void SeparateLists()
{
string input = "* hello\n\n- oh";
string expected = "* hello\n\n* oh";
string actual = MarkdownGemtext.ToGemtext(input);
Assert.Equal(expected, actual);
}
}
}

View File

@ -0,0 +1,104 @@
using Markdig;
using MfGames.Markdown.Gemtext;
using MfGames.Markdown.Gemtext.Extensions;
using Xunit;
namespace MfGames.Markdown.Gemini.Tests.PythonInspired
{
/// <summary>
/// Runs through various plain input tests.
/// Based on
/// https://github.com/makeworld-the-better-one/md2gemini/blob/master/tests/test_plain.py
/// </summary>
public class PlainTextPythonTests
{
[Fact]
public void ConvertHtmlBlock()
{
string input = string.Join(
"\n",
"<div>",
"<tag>html is here</tag>",
"</div>",
"",
"Text after");
string expected = string.Join(
"\n",
"```html",
"<div>",
"<tag>html is here</tag>",
"</div>",
"```",
"",
"Text after");
string actual = MarkdownGemtext.ToGemtext(
input,
new MarkdownPipelineBuilder()
.Use<HtmlAsCodeBlocks>()
.Build());
Assert.Equal(expected, actual);
}
[Fact]
public void EmptyInput()
{
string input = "";
string expected = "";
string actual = MarkdownGemtext.ToGemtext(input);
Assert.Equal(expected, actual);
}
[Fact]
public void RemoveBold()
{
string input = "Sentence with **bold** in it.";
string expected = "Sentence with bold in it.";
string actual = MarkdownGemtext.ToGemtext(input);
Assert.Equal(expected, actual);
}
[Fact]
public void RemoveBold2()
{
string input = "Sentence with __bold__ in it.";
string expected = "Sentence with bold in it.";
string actual = MarkdownGemtext.ToGemtext(input);
Assert.Equal(expected, actual);
}
[Fact]
public void RemoveInlineHtml()
{
string input =
"<i>test1</i> <b>test2</b> <em>test3</em> <made-up>test4</made-up>";
string expected = "test1 test2 test3 test4";
string actual = MarkdownGemtext.ToGemtext(input);
Assert.Equal(expected, actual);
}
[Fact]
public void RemoveItalics()
{
string input = "Sentence with *italics* in it.";
string expected = "Sentence with italics in it.";
string actual = MarkdownGemtext.ToGemtext(input);
Assert.Equal(expected, actual);
}
[Fact]
public void RemoveItalics2()
{
string input = "Sentence with _italics_ in it.";
string expected = "Sentence with italics in it.";
string actual = MarkdownGemtext.ToGemtext(input);
Assert.Equal(expected, actual);
}
}
}

View File

@ -0,0 +1,6 @@
# Python-Inpsired Test
These tests come
from [md2gemini](https://github.com/makeworld-the-better-one/md2gemini/tree/master/tests)
but the logic for how to generate them does not. We are simply using them as a
basic test case and then alterating Markdig to match.

View File

@ -0,0 +1,110 @@
using Markdig;
using MfGames.Markdown.Gemtext;
using MfGames.Markdown.Gemtext.Extensions;
using MfGames.Markdown.Gemtext.Renderers;
using Xunit;
namespace MfGames.Markdown.Gemini.Tests.PythonInspired
{
/// <summary>
/// https://github.com/makeworld-the-better-one/md2gemini/blob/master/tests/test_strip_html.py
/// </summary>
public class StripHtmlPythonTests
{
[Fact]
public void KeepBold()
{
string input = "Sentence with **bold** in it.";
string expected = "Sentence with **bold** in it.";
var inlineFormatting = new SetInlineFormatting()
{
Default = InlineFormatting.Normalize,
};
string actual = MarkdownGemtext.ToGemtext(
input,
new MarkdownPipelineBuilder()
.Use(inlineFormatting)
.Build());
Assert.Equal(expected, actual);
}
[Fact]
public void KeepBold2()
{
string input = "Sentence with __bold__ in it.";
string expected = "Sentence with **bold** in it.";
var inlineFormatting = new SetInlineFormatting()
{
Default = InlineFormatting.Normalize,
};
string actual = MarkdownGemtext.ToGemtext(
input,
new MarkdownPipelineBuilder()
.Use(inlineFormatting)
.Build());
Assert.Equal(expected, actual);
}
[Fact]
public void KeepItalics()
{
string input = "Sentence with *italics* in it.";
string expected = "Sentence with *italics* in it.";
var inlineFormatting = new SetInlineFormatting()
{
Default = InlineFormatting.Normalize,
};
string actual = MarkdownGemtext.ToGemtext(
input,
new MarkdownPipelineBuilder()
.Use(inlineFormatting)
.Build());
Assert.Equal(expected, actual);
}
[Fact]
public void KeepItalics2()
{
string input = "Sentence with _italics_ in it.";
string expected = "Sentence with *italics* in it.";
var inlineFormatting = new SetInlineFormatting()
{
Default = InlineFormatting.Normalize,
};
string actual = MarkdownGemtext.ToGemtext(
input,
new MarkdownPipelineBuilder()
.Use(inlineFormatting)
.Build());
Assert.Equal(expected, actual);
}
[Fact]
public void RemoveHtmlBlock()
{
string input =
"<div>\n<tag>html is here</tag>\n</div>\n\nText after";
string expected = "Text after";
string actual = MarkdownGemtext.ToGemtext(input);
Assert.Equal(expected, actual);
}
[Fact]
public void RemoveInlineHtml()
{
string input =
"<i>test1</i> <b>test2</b> <em>test3</em> <made-up>test4</made-up>";
string expected = "test1 test2 test3 test4";
string actual = MarkdownGemtext.ToGemtext(input);
Assert.Equal(expected, actual);
}
}
}

View File

@ -0,0 +1,37 @@
using Markdig;
using Markdig.Extensions.SmartyPants;
using MfGames.Markdown.Gemtext;
using MfGames.Markdown.Gemtext.Extensions;
using Xunit;
namespace MfGames.Markdown.Gemini.Tests
{
public class QuoteTests
{
[Fact]
public void NormalDouble()
{
string input = "Normal \"double\" quote.";
string expected = "Normal \"double\" quote.";
string actual = MarkdownGemtext.ToGemtext(input);
Assert.Equal(expected, actual);
}
[Fact]
public void SmartyDouble()
{
GemtextSmartyPantsExtension smartyPants =
new(new SmartyPantOptions());
string input = "Normal \"double\" quote.";
string expected = "Normal “double” quote.";
string actual = MarkdownGemtext.ToGemtext(
input,
new MarkdownPipelineBuilder()
.Use(smartyPants)
.Build());
Assert.Equal(expected, actual);
}
}
}

View File

@ -0,0 +1,31 @@
using MfGames.Markdown.Gemtext;
using Xunit;
namespace MfGames.Markdown.Gemini.Tests
{
public class TableTests
{
[Fact(Skip = "Tables are out of scope at this point")]
public void SimpleImageLink()
{
string input = string.Join(
"\n",
"a|b|c",
"-|-|",
"1|2|3",
"4|5|6");
string expected = string.Join(
"\n",
"┌───┬───┬───┐",
"│ a │ b │ c │",
"╞═══╪═══╪═══╡",
"│ 1 │ 2 │ 3 │",
"├───┼───┼───┤",
"│ 4 │ 5 │ 6 │",
"└───┴───┴───┘");
string actual = MarkdownGemtext.ToGemtext(input);
Assert.Equal(expected, actual);
}
}
}

View File

@ -0,0 +1,54 @@
using Markdig;
using Markdig.Extensions.SmartyPants;
using Markdig.Parsers.Inlines;
using Markdig.Renderers;
using MfGames.Markdown.Gemtext.Renderers;
using MfGames.Markdown.Gemtext.Renderers.Gemtext.Inlines;
namespace MfGames.Markdown.Gemtext.Extensions
{
/// <summary>
/// Extension to enable SmartyPants, but for Gemtext.
/// </summary>
public class GemtextSmartyPantsExtension : IMarkdownExtension
{
/// <summary>
/// Initializes a new instance of the <see cref="SmartyPantsExtension" /> class.
/// </summary>
/// <param name="options">The options.</param>
public GemtextSmartyPantsExtension(SmartyPantOptions? options)
{
this.Options = options ?? new SmartyPantOptions();
}
/// <summary>
/// Gets the options.
/// </summary>
public SmartyPantOptions Options { get; }
public void Setup(MarkdownPipelineBuilder pipeline)
{
if (!pipeline.InlineParsers.Contains<SmartyPantsInlineParser>())
{
// Insert the parser after the code span parser
pipeline.InlineParsers.InsertAfter<CodeInlineParser>(
new SmartyPantsInlineParser());
}
}
public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer)
{
if (renderer is not GemtextRenderer gemtextRenderer)
{
return;
}
if (!gemtextRenderer.ObjectRenderers
.Contains<GemtextSmartyPantRenderer>())
{
gemtextRenderer.ObjectRenderers.Add(
new GemtextSmartyPantRenderer(this.Options));
}
}
}
}

View File

@ -0,0 +1,28 @@
using Markdig;
using Markdig.Renderers;
using MfGames.Markdown.Gemtext.Renderers;
namespace MfGames.Markdown.Gemtext.Extensions
{
/// <summary>
/// Extension method to retain HTML blocks as a code fenced block with
/// "html" as the data.
/// </summary>
/// <seealso cref="IMarkdownExtension" />
public class HtmlAsCodeBlocks : IMarkdownExtension
{
/// <inheritdoc />
public void Setup(MarkdownPipelineBuilder pipeline)
{
}
/// <inheritdoc />
public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer)
{
if (renderer is GemtextRenderer gemtext)
{
gemtext.HtmlBlockFormatting = HtmlBlockFormatting.CodeBlock;
}
}
}
}

View File

@ -0,0 +1,37 @@
using Markdig;
using Markdig.Renderers;
using MfGames.Markdown.Gemtext.Renderers;
using MfGames.Markdown.Gemtext.Renderers.Gemtext.Blocks;
namespace MfGames.Markdown.Gemtext.Extensions
{
/// <summary>
/// Extension method to control the depth of the headers in a file so that
/// the first one (maybe a header) is H1 but the others are decreased to
/// H2 or lower depending on their initial level.
/// </summary>
/// <seealso cref="IMarkdownExtension" />
public class IncreaseHeaderDepthsAfterFirst : IMarkdownExtension
{
/// <inheritdoc />
public void Setup(MarkdownPipelineBuilder pipeline)