feat: initial commit
This commit is contained in:
commit
9fe90f820c
61 changed files with 16325 additions and 0 deletions
122
.editorconfig
Normal file
122
.editorconfig
Normal 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
1
.envrc
Normal file
|
@ -0,0 +1 @@
|
|||
use asdf
|
13
.gitignore
vendored
Normal file
13
.gitignore
vendored
Normal 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
51
.gitlab-ci.yml
Normal 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
4
.husky/commit-msg
Executable file
|
@ -0,0 +1,4 @@
|
|||
#!/bin/sh
|
||||
. "$(dirname "$0")/_/husky.sh"
|
||||
|
||||
npx --no-install commitlint --edit $1
|
3
.tool-versions
Normal file
3
.tool-versions
Normal file
|
@ -0,0 +1,3 @@
|
|||
dotnet-core 5.0.100
|
||||
yarn 1.22.10
|
||||
nodejs 15.0.1
|
21
LICENSE.txt
Normal file
21
LICENSE.txt
Normal 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.
|
54
MfGames.Markdown.Gemtext.sln
Normal file
54
MfGames.Markdown.Gemtext.sln
Normal 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
|
1370
MfGames.Markdown.Gemtext.sln.DotSettings
Normal file
1370
MfGames.Markdown.Gemtext.sln.DotSettings
Normal file
File diff suppressed because it is too large
Load diff
4
README.md
Normal file
4
README.md
Normal 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
3
commitlint.config.js
Normal file
|
@ -0,0 +1,3 @@
|
|||
module.exports = {
|
||||
extends: ["@commitlint/config-conventional"],
|
||||
};
|
12304
package-lock.json
generated
Normal file
12304
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
21
package.json
Normal file
21
package.json
Normal 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
20
release.config.js
Normal 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",
|
||||
],
|
||||
};
|
39
src/MfGames.Markdown.Gemtext.Tests/CodeInlineTests.cs
Normal file
39
src/MfGames.Markdown.Gemtext.Tests/CodeInlineTests.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
107
src/MfGames.Markdown.Gemtext.Tests/HeaderTests.cs
Normal file
107
src/MfGames.Markdown.Gemtext.Tests/HeaderTests.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
28
src/MfGames.Markdown.Gemtext.Tests/ImageLinkTests.cs
Normal file
28
src/MfGames.Markdown.Gemtext.Tests/ImageLinkTests.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
64
src/MfGames.Markdown.Gemtext.Tests/LinkTests.cs
Normal file
64
src/MfGames.Markdown.Gemtext.Tests/LinkTests.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
26
src/MfGames.Markdown.Gemtext.Tests/ListTests.cs
Normal file
26
src/MfGames.Markdown.Gemtext.Tests/ListTests.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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>
|
164
src/MfGames.Markdown.Gemtext.Tests/ParagraphLinkHandlingTests.cs
Normal file
164
src/MfGames.Markdown.Gemtext.Tests/ParagraphLinkHandlingTests.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
60
src/MfGames.Markdown.Gemtext.Tests/PlainTextTests.cs
Normal file
60
src/MfGames.Markdown.Gemtext.Tests/PlainTextTests.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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.
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
37
src/MfGames.Markdown.Gemtext.Tests/QuoteTests.cs
Normal file
37
src/MfGames.Markdown.Gemtext.Tests/QuoteTests.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
31
src/MfGames.Markdown.Gemtext.Tests/TableTests.cs
Normal file
31
src/MfGames.Markdown.Gemtext.Tests/TableTests.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
28
src/MfGames.Markdown.Gemtext/Extensions/HtmlAsCodeBlocks.cs
Normal file
28
src/MfGames.Markdown.Gemtext/Extensions/HtmlAsCodeBlocks.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer)
|
||||
{
|
||||
if (renderer is not GemtextRenderer gemtext)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var heading = gemtext.ObjectRenderers.Find<HeadingRenderer>();
|
||||
|
||||
if (heading != null)
|
||||
{
|
||||
heading.IncreaseHeaderDepthAfterFirst = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
using Markdig;
|
||||
using Markdig.Renderers;
|
||||
using MfGames.Markdown.Gemtext.Renderers;
|
||||
|
||||
namespace MfGames.Markdown.Gemtext.Extensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Extension method to control how links are processed inside blocks.
|
||||
/// </summary>
|
||||
/// <seealso cref="IMarkdownExtension" />
|
||||
public class SetBlockLinkHandling : IMarkdownExtension
|
||||
{
|
||||
public SetBlockLinkHandling(
|
||||
BlockLinkHandling? blockLinkHandling = null,
|
||||
EndLinkInlineFormatting? endLinkInlineFormatting = null,
|
||||
int? nextFootnoteNumber = null)
|
||||
{
|
||||
this.BlockLinkHandling = blockLinkHandling;
|
||||
this.EndLinkInlineFormatting = endLinkInlineFormatting;
|
||||
this.NextFootnoteNumber = nextFootnoteNumber;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets how block links are handled. If this is null, then no
|
||||
/// change is made to the current renderer.
|
||||
/// </summary>
|
||||
public BlockLinkHandling? BlockLinkHandling { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets how links are formatted if they are gathered to the
|
||||
/// end of the paragraph or document. If this is null, then no change
|
||||
/// will be made.
|
||||
/// </summary>
|
||||
public EndLinkInlineFormatting? EndLinkInlineFormatting { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the next footnote number. If this is null, then no
|
||||
/// change will be made.
|
||||
/// </summary>
|
||||
public int? NextFootnoteNumber { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Setup(MarkdownPipelineBuilder pipeline)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer)
|
||||
{
|
||||
if (renderer is not GemtextRenderer gemtext)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
gemtext.BlockLinkHandling = this.BlockLinkHandling
|
||||
?? gemtext.BlockLinkHandling;
|
||||
gemtext.EndLinkInlineFormatting = this.EndLinkInlineFormatting
|
||||
?? gemtext.EndLinkInlineFormatting;
|
||||
gemtext.NextFootnoteNumber = this.NextFootnoteNumber
|
||||
?? gemtext.NextFootnoteNumber;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
using Markdig;
|
||||
using Markdig.Renderers;
|
||||
using MfGames.Markdown.Gemtext.Renderers;
|
||||
|
||||
namespace MfGames.Markdown.Gemtext.Extensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Extension method to turn all inline formatting from the default of
|
||||
/// removing to render in normalizing in rendered form.
|
||||
/// </summary>
|
||||
/// <seealso cref="IMarkdownExtension" />
|
||||
public class SetInlineFormatting : IMarkdownExtension
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the override formatting for code lines
|
||||
/// (backtick) spans.
|
||||
/// </summary>
|
||||
public InlineFormatting? Code { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Sets or sets the override formatting for all inlines.
|
||||
/// </summary>
|
||||
public InlineFormatting? Default { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the override formatting for emphasis (italic
|
||||
/// and bold) spans.
|
||||
/// </summary>
|
||||
public InlineFormatting? Emphasis { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Setup(MarkdownPipelineBuilder pipeline)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer)
|
||||
{
|
||||
if (renderer is not GemtextRenderer gemtext)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
gemtext.InlineFormatting = this.Default ?? gemtext.InlineFormatting;
|
||||
gemtext.EmphasisFormatting = this.Emphasis;
|
||||
gemtext.CodeFormatting = this.Code;
|
||||
}
|
||||
}
|
||||
}
|
83
src/MfGames.Markdown.Gemtext/MarkdownGemtext.cs
Normal file
83
src/MfGames.Markdown.Gemtext/MarkdownGemtext.cs
Normal file
|
@ -0,0 +1,83 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using Markdig;
|
||||
using Markdig.Parsers;
|
||||
using Markdig.Syntax;
|
||||
using MfGames.Markdown.Gemtext.Renderers;
|
||||
|
||||
namespace MfGames.Markdown.Gemtext
|
||||
{
|
||||
/// <summary>
|
||||
/// The static class that corresponds to Markdig.Markdown. This is written
|
||||
/// with the same pattern, but since `Markdown` is a static, we can't tack
|
||||
/// onto that.
|
||||
/// </summary>
|
||||
public static class MarkdownGemtext
|
||||
{
|
||||
private static readonly MarkdownPipeline DefaultPipeline;
|
||||
|
||||
static MarkdownGemtext()
|
||||
{
|
||||
DefaultPipeline = new MarkdownPipelineBuilder()
|
||||
.Build();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts the given Markdown
|
||||
/// </summary>
|
||||
/// <param name="markdown">A Markdown text.</param>
|
||||
/// <param name="pipeline">The pipeline used for the conversion.</param>
|
||||
/// <param name="context">A parser context used for the parsing.</param>
|
||||
/// <returns>The result of the conversion</returns>
|
||||
public static string ToGemtext(
|
||||
string markdown,
|
||||
MarkdownPipeline? pipeline = null,
|
||||
MarkdownParserContext? context = null)
|
||||
{
|
||||
if (markdown == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(markdown));
|
||||
}
|
||||
|
||||
pipeline ??= DefaultPipeline;
|
||||
|
||||
MarkdownDocument document = MarkdownParser
|
||||
.Parse(markdown, pipeline, context);
|
||||
|
||||
return ToGemtext(document, pipeline);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a Markdown document to HTML.
|
||||
/// </summary>
|
||||
/// <param name="document">A Markdown document.</param>
|
||||
/// <param name="pipeline">The pipeline used for the conversion.</param>
|
||||
/// <returns>The result of the conversion</returns>
|
||||
/// <exception cref="ArgumentNullException">if markdown document variable is null</exception>
|
||||
public static string ToGemtext(
|
||||
this MarkdownDocument document,
|
||||
MarkdownPipeline? pipeline = null)
|
||||
{
|
||||
// Make sure we have sane parameters.
|
||||
if (document == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(document));
|
||||
}
|
||||
|
||||
pipeline ??= DefaultPipeline;
|
||||
|
||||
// Set up the writer to contain the markdown and the Gemtext
|
||||
// renderer.
|
||||
var writer = new StringWriter();
|
||||
GemtextRenderer renderer = new(writer);
|
||||
|
||||
pipeline.Setup(renderer);
|
||||
|
||||
// Render the Markdown into Gemtext and re turn the results.
|
||||
renderer.Render(document);
|
||||
renderer.Writer.Flush();
|
||||
|
||||
return renderer.Writer.ToString() ?? string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
12
src/MfGames.Markdown.Gemtext/MfGames.Markdown.Gemtext.csproj
Normal file
12
src/MfGames.Markdown.Gemtext/MfGames.Markdown.Gemtext.csproj
Normal file
|
@ -0,0 +1,12 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Markdig" Version="0.25.0"/>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
34
src/MfGames.Markdown.Gemtext/Renderers/BlockLinkHandling.cs
Normal file
34
src/MfGames.Markdown.Gemtext/Renderers/BlockLinkHandling.cs
Normal file
|
@ -0,0 +1,34 @@
|
|||
namespace MfGames.Markdown.Gemtext.Renderers
|
||||
{
|
||||
/// <summary>
|
||||
/// Describes how links are processed within a paragraph.
|
||||
/// </summary>
|
||||
public enum BlockLinkHandling
|
||||
{
|
||||
/// <summary>
|
||||
/// Indicates that the paragraph should be broken apart and the link
|
||||
/// included on its own line in the middle of the paragraph.
|
||||
/// </summary>
|
||||
InsertLine,
|
||||
|
||||
/// <summary>
|
||||
/// Indicates that all the links in a paragraph should be gathered
|
||||
/// and then emitted at the end of the paragraph. The text of the link
|
||||
/// will be left in the paragraph.
|
||||
/// </summary>
|
||||
ParagraphEnd,
|
||||
|
||||
/// <summary>
|
||||
/// Indicates that all the links in the document should be gathered
|
||||
/// and then emitted at the end of the document. The text of the link
|
||||
/// will be left in the paragraph.
|
||||
/// </summary>
|
||||
DocumentEnd,
|
||||
|
||||
/// <summary>
|
||||
/// Indicates that the links themselves should be removed and just the
|
||||
/// text included in the paragraph.
|
||||
/// </summary>
|
||||
Remove,
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
namespace MfGames.Markdown.Gemtext.Renderers
|
||||
{
|
||||
/// <summary>
|
||||
/// Describes how a paragraph link is formatted inside the text. This is
|
||||
/// only used for `ParagraphLinkHandling.ParagraphEnd` and
|
||||
/// `ParagraphLinkHandling.DocumentEnd`.
|
||||
/// </summary>
|
||||
public enum EndLinkInlineFormatting
|
||||
{
|
||||
/// <summary>
|
||||
/// Indicates that a footnote notation (`[1]`) will be insert into the
|
||||
/// text and then the link will be displayed with the URL when gathered.
|
||||
/// </summary>
|
||||
Footnote,
|
||||
|
||||
/// <summary>
|
||||
/// Indicates that the text is put in as-is into the gathered link with
|
||||
/// no footnote given in the block.
|
||||
/// </summary>
|
||||
Text,
|
||||
}
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
using Markdig.Syntax;
|
||||
|
||||
namespace MfGames.Markdown.Gemtext.Renderers.Gemtext.Blocks
|
||||
{
|
||||
/// <summary>
|
||||
/// An Gemtext renderer for a <see cref="CodeBlock" /> and
|
||||
/// <see cref="FencedCodeBlock" />.
|
||||
/// </summary>
|
||||
/// <seealso cref="GemtextObjectRenderer{CodeBlock}" />
|
||||
public class CodeBlockRenderer : GemtextObjectRenderer<CodeBlock>
|
||||
{
|
||||
protected override void Write(GemtextRenderer renderer, CodeBlock obj)
|
||||
{
|
||||
// We need to have two lines above this.
|
||||
renderer.EnsureTwoLines();
|
||||
|
||||
// Code blocks are always fenced, but we allow for additional text
|
||||
// at the end of them which is only in `FencedCodeBlock`.
|
||||
if (obj is FencedCodeBlock fenced)
|
||||
{
|
||||
renderer.WriteLine("```" + fenced.Info);
|
||||
}
|
||||
else
|
||||
{
|
||||
renderer.WriteLine("```");
|
||||
}
|
||||
|
||||
renderer.WriteLeafRawLines(obj, true);
|
||||
renderer.Write("```");
|
||||
|
||||
// If we aren't at the end of the container, then add some spacing.
|
||||
if (!renderer.IsLastInContainer)
|
||||
{
|
||||
renderer.WriteLine();
|
||||
renderer.WriteLine();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
using Markdig.Syntax;
|
||||
|
||||
namespace MfGames.Markdown.Gemtext.Renderers.Gemtext.Blocks
|
||||
{
|
||||
/// <summary>
|
||||
/// An Gemtext renderer for a <see cref="HeadingBlock" />.
|
||||
/// </summary>
|
||||
/// <seealso cref="GemtextObjectRenderer{HeadingBlock}" />
|
||||
public class HeadingRenderer : GemtextObjectRenderer<HeadingBlock>
|
||||
{
|
||||
private int currentHeading;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the header depths are
|
||||
/// increased after the first one.
|
||||
/// </summary>
|
||||
public bool IncreaseHeaderDepthAfterFirst { get; set; }
|
||||
|
||||
protected override void Write(
|
||||
GemtextRenderer renderer,
|
||||
HeadingBlock obj)
|
||||
{
|
||||
// Figure out the level we should be processing.
|
||||
int level = obj.Level;
|
||||
|
||||
if (this.currentHeading++ > 0 && this.IncreaseHeaderDepthAfterFirst)
|
||||
{
|
||||
// Check the second header we see. If this header is H2 or
|
||||
// higher, then we assume that the file has been already updated
|
||||
// to handle the heading and we stop processing.
|
||||
if (this.currentHeading == 2 && level != 1)
|
||||
{
|
||||
this.IncreaseHeaderDepthAfterFirst = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
// We are bumping the heading levels up.
|
||||
level++;
|
||||
}
|
||||
}
|
||||
|
||||
// Write out the prefix of the header.
|
||||
string prefix = level switch
|
||||
{
|
||||
1 => "# ",
|
||||
2 => "## ",
|
||||
3 => "### ",
|
||||
_ => "",
|
||||
};
|
||||
|
||||
renderer.EnsureTwoLines();
|
||||
renderer.Write(prefix);
|
||||
renderer.WriteLeafInline(obj);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
using Markdig.Syntax;
|
||||
|
||||
namespace MfGames.Markdown.Gemtext.Renderers.Gemtext.Blocks
|
||||
{
|
||||
/// <summary>
|
||||
/// A Gemtext renderer for a <see cref="GemtextBlock" />.
|
||||
/// </summary>
|
||||
/// <seealso cref="GemtextObjectRenderer{GemtextBlock}" />
|
||||
public class HtmlBlockRenderer : GemtextObjectRenderer<HtmlBlock>
|
||||
{
|
||||
protected override void Write(GemtextRenderer renderer, HtmlBlock obj)
|
||||
{
|
||||
// If we are stripping out HTML blocks (default), then nothing to
|
||||
// do with rendering.
|
||||
if (renderer.HtmlBlockFormatting == HtmlBlockFormatting.Remove)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Otherwise, we treat this as a fenced code block.
|
||||
renderer.EnsureTwoLines();
|
||||
renderer.WriteLine("```html");
|
||||
renderer.WriteLeafRawLines(obj, true);
|
||||
renderer.WriteLine("```");
|
||||
|
||||
// If we aren't at the end of the container, then add some spacing.
|
||||
if (!renderer.IsLastInContainer)
|
||||
{
|
||||
renderer.WriteLine();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
using Markdig.Syntax;
|
||||
|
||||
namespace MfGames.Markdown.Gemtext.Renderers.Gemtext.Blocks
|
||||
{
|
||||
/// <summary>
|
||||
/// A Gemtext renderer for a <see cref="ListBlock" />.
|
||||
/// </summary>
|
||||
/// <seealso cref="GemtextObjectRenderer{ListBlock}" />
|
||||
public class ListRenderer : GemtextObjectRenderer<ListBlock>
|
||||
{
|
||||
protected override void Write(
|
||||
GemtextRenderer renderer,
|
||||
ListBlock listBlock)
|
||||
{
|
||||
// Lists need to be separated from the rest.
|
||||
renderer.EnsureTwoLines();
|
||||
|
||||
// Go through each list item and write them out.
|
||||
foreach (var item in listBlock)
|
||||
{
|
||||
// If the list only contains a link, then we just render the
|
||||
// link instead.
|
||||
var listItem = (ListItemBlock)item;
|
||||
|
||||
if (!listItem.OnlyHasSingleLink())
|
||||
{
|
||||
renderer.EnsureLine();
|
||||
renderer.Write("* ");
|
||||
}
|
||||
|
||||
renderer.WriteChildren(listItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
using Markdig.Syntax;
|
||||
using MfGames.Markdown.Gemtext.Renderers.Gemtext.Inlines;
|
||||
|
||||
namespace MfGames.Markdown.Gemtext.Renderers.Gemtext.Blocks
|
||||
{
|
||||
/// <summary>
|
||||
/// A Gemtext renderer for a <see cref="MarkdownDocument" />.
|
||||
/// </summary>
|
||||
/// <seealso cref="GemtextObjectRenderer{ParagraphBlock}" />
|
||||
public class MarkdownDocumentRenderer
|
||||
: GemtextObjectRenderer<MarkdownDocument>
|
||||
{
|
||||
protected override void Write(
|
||||
GemtextRenderer renderer,
|
||||
MarkdownDocument obj)
|
||||
{
|
||||
// Simply write out the contents.
|
||||
renderer.WriteChildren(obj);
|
||||
|
||||
// If we get to the end of the document and we have gathered links,
|
||||
// and we are in DocumentEnd mode, then write out the links. We
|
||||
// don't test for the mode here because if there are links, we
|
||||
// should write them out.
|
||||
LinkInlineRenderer.WriteGatheredLinks(renderer);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
using Markdig.Syntax;
|
||||
using MfGames.Markdown.Gemtext.Renderers.Gemtext.Inlines;
|
||||
|
||||
namespace MfGames.Markdown.Gemtext.Renderers.Gemtext.Blocks
|
||||
{
|
||||
/// <summary>
|
||||
/// A Gemtext renderer for a <see cref="ParagraphBlock" />.
|
||||
/// </summary>
|
||||
/// <seealso cref="GemtextObjectRenderer{ParagraphBlock}" />
|
||||
public class ParagraphRenderer : GemtextObjectRenderer<ParagraphBlock>
|
||||
{
|
||||
protected override void Write(
|
||||
GemtextRenderer renderer,
|
||||
ParagraphBlock obj)
|
||||
{
|
||||
// If we aren't the first in the container, we need to break apart
|
||||
// the lines to make it easier to read.
|
||||
if (!renderer.IsFirstInContainer)
|
||||
{
|
||||
renderer.EnsureTwoLines();
|
||||
}
|
||||
|
||||
// We need to save the state of the link rendering while handling
|
||||
// this block.
|
||||
if (obj.OnlyHasSingleLink())
|
||||
{
|
||||
renderer.WriteLeafInline(obj);
|
||||
}
|
||||
else
|
||||
{
|
||||
renderer.WhileLinkInsideBlock(
|
||||
() => renderer.WriteLeafInline(obj));
|
||||
}
|
||||
|
||||
// If we get to the end of the paragraph and we have gathered links,
|
||||
// and we are in ParagraphEnd mode, then write out the links.
|
||||
if (renderer.BlockLinkHandling == BlockLinkHandling.ParagraphEnd)
|
||||
{
|
||||
LinkInlineRenderer.WriteGatheredLinks(renderer);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
using Markdig.Syntax;
|
||||
|
||||
namespace MfGames.Markdown.Gemtext.Renderers.Gemtext.Blocks
|
||||
{
|
||||
/// <summary>
|
||||
/// A Gemtext renderer for a <see cref="QuoteBlock" />.
|
||||
/// </summary>
|
||||
/// <seealso cref="GemtextObjectRenderer{QuoteBlock}" />
|
||||
public class QuoteBlockRenderer : GemtextObjectRenderer<QuoteBlock>
|
||||
{
|
||||
protected override void Write(GemtextRenderer renderer, QuoteBlock obj)
|
||||
{
|
||||
string quoteIndent = obj.QuoteChar + " ";
|
||||
|
||||
renderer.EnsureTwoLines();
|
||||
renderer.PushIndent(quoteIndent);
|
||||
renderer.WriteChildren(obj);
|
||||
renderer.PopIndent();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
using Markdig.Syntax;
|
||||
|
||||
namespace MfGames.Markdown.Gemtext.Renderers.Gemtext.Blocks
|
||||
{
|
||||
/// <summary>
|
||||
/// A Gemtext renderer for a <see cref="ThematicBreakBlock" />.
|
||||
/// </summary>
|
||||
/// <seealso cref="GemtextObjectRenderer{ThematicBreakBlock}" />
|
||||
public class ThematicBreakRenderer
|
||||
: GemtextObjectRenderer<ThematicBreakBlock>
|
||||
{
|
||||
protected override void Write(
|
||||
GemtextRenderer renderer,
|
||||
ThematicBreakBlock obj)
|
||||
{
|
||||
renderer.EnsureTwoLines();
|
||||
renderer.WriteLine("---");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
using Markdig.Renderers;
|
||||
using Markdig.Syntax;
|
||||
using Markdig.Syntax.Inlines;
|
||||
|
||||
namespace MfGames.Markdown.Gemtext.Renderers.Gemtext
|
||||
{
|
||||
/// <summary>
|
||||
/// A base class for Gemtext rendering <see cref="Block" /> and
|
||||
/// <see cref="Inline" /> Markdown objects.
|
||||
/// </summary>
|
||||
/// <typeparam name="TObject">The type of the object.</typeparam>
|
||||
/// <seealso cref="IMarkdownObjectRenderer" />
|
||||
public abstract class GemtextObjectRenderer<TObject>
|
||||
: MarkdownObjectRenderer<GemtextRenderer, TObject>
|
||||
where TObject : MarkdownObject
|
||||
{
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
using Markdig.Syntax.Inlines;
|
||||
|
||||
namespace MfGames.Markdown.Gemtext.Renderers.Gemtext.Inlines
|
||||
{
|
||||
/// <summary>
|
||||
/// A Gemtext renderer for a <see cref="CodeInline" />.
|
||||
/// </summary>
|
||||
/// <seealso cref="GemtextObjectRenderer{CodeInline}" />
|
||||
public class CodeInlineRenderer : GemtextObjectRenderer<CodeInline>
|
||||
{
|
||||
protected override void Write(GemtextRenderer renderer, CodeInline obj)
|
||||
{
|
||||
const string Delimiter = "`";
|
||||
InlineFormatting formatting = renderer.CodeFormattingResolved;
|
||||
bool normalize = formatting == InlineFormatting.Normalize;
|
||||
|
||||
if (normalize)
|
||||
{
|
||||
renderer.Write(Delimiter);
|
||||
}
|
||||
|
||||
renderer.Write(obj.Content);
|
||||
|
||||
if (normalize)
|
||||
{
|
||||
renderer.Write(Delimiter);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
using Markdig.Syntax.Inlines;
|
||||
|
||||
namespace MfGames.Markdown.Gemtext.Renderers.Gemtext.Inlines
|
||||
{
|
||||
/// <summary>
|
||||
/// A Gemtext renderer for a <see cref="DelimiterInline" />.
|
||||
/// </summary>
|
||||
/// <seealso cref="GemtextObjectRenderer{DelimiterInline}" />
|
||||
public class DelimiterInlineRenderer
|
||||
: GemtextObjectRenderer<DelimiterInline>
|
||||
{
|
||||
protected override void Write(
|
||||
GemtextRenderer renderer,
|
||||
DelimiterInline obj)
|
||||
{
|
||||
renderer.Write(obj.ToLiteral());
|
||||
renderer.WriteChildren(obj);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
using Markdig.Syntax.Inlines;
|
||||
|
||||
namespace MfGames.Markdown.Gemtext.Renderers.Gemtext.Inlines
|
||||
{
|
||||
/// <summary>
|
||||
/// A Gemtext renderer for an <see cref="EmphasisInline" />.
|
||||
/// </summary>
|
||||
/// <seealso cref="GemtextObjectRenderer{EmphasisInline}" />
|
||||
public class EmphasisInlineRenderer : GemtextObjectRenderer<EmphasisInline>
|
||||
{
|
||||
protected override void Write(
|
||||
GemtextRenderer renderer,
|
||||
EmphasisInline obj)
|
||||
{
|
||||
InlineFormatting formatting = renderer.EmphasisFormattingResolved;
|
||||
bool normalize = formatting == InlineFormatting.Normalize;
|
||||
string delimiter = new string('*', obj.DelimiterCount);
|
||||
|
||||
if (normalize)
|
||||
{
|
||||
renderer.Write(delimiter);
|
||||
}
|
||||
|
||||
renderer.WriteChildren(obj);
|
||||
|
||||
if (normalize)
|
||||
{
|
||||
renderer.Write(delimiter);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
using System;
|
||||
using System.Net;
|
||||
using Markdig.Extensions.SmartyPants;
|
||||
|
||||
namespace MfGames.Markdown.Gemtext.Renderers.Gemtext.Inlines
|
||||
{
|
||||
/// <summary>
|
||||
/// A Gemtext renderer for a <see cref="SmartyPant" />.
|
||||
/// </summary>
|
||||
/// <seealso cref="GemtextObjectRenderer{GemtextEntityInline}" />
|
||||
public class GemtextSmartyPantRenderer
|
||||
: GemtextObjectRenderer<SmartyPant>
|
||||
{
|
||||
private static readonly SmartyPantOptions DefaultOptions = new();
|
||||
|
||||
private readonly SmartyPantOptions options;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="HtmlSmartyPantRenderer" /> class.
|
||||
/// </summary>
|
||||
/// <param name="options">The options.</param>
|
||||
/// <exception cref="ArgumentNullException"></exception>
|
||||
public GemtextSmartyPantRenderer(SmartyPantOptions? options)
|
||||
{
|
||||
this.options = options
|
||||
?? throw new ArgumentNullException(nameof(options));
|
||||
}
|
||||
|
||||
protected override void Write(GemtextRenderer renderer, SmartyPant obj)
|
||||
{
|
||||
if (!this.options.Mapping.TryGetValue(obj.Type, out string? text))
|
||||
{
|
||||
DefaultOptions.Mapping.TryGetValue(obj.Type, out text);
|
||||
}
|
||||
|
||||
string? unicode = WebUtility.HtmlDecode(text);
|
||||
|
||||
renderer.Write(unicode);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
using Markdig.Syntax.Inlines;
|
||||
|
||||
namespace MfGames.Markdown.Gemtext.Renderers.Gemtext.Inlines
|
||||
{
|
||||
/// <summary>
|
||||
/// A Gemtext renderer for a <see cref="GemtextEntityInline" />.
|
||||
/// </summary>
|
||||
/// <seealso cref="GemtextObjectRenderer{GemtextEntityInline}" />
|
||||
public class HtmlEntityInlineRenderer
|
||||
: GemtextObjectRenderer<HtmlEntityInline>
|
||||
{
|
||||
protected override void Write(
|
||||
GemtextRenderer renderer,
|
||||
HtmlEntityInline obj)
|
||||
{
|
||||
renderer.Write(obj.Transcoded);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
using Markdig.Syntax.Inlines;
|
||||
|
||||
namespace MfGames.Markdown.Gemtext.Renderers.Gemtext.Inlines
|
||||
{
|
||||
/// <summary>
|
||||
/// A Gemtext renderer for a <see cref="LineBreakInline" />.
|
||||
/// </summary>
|
||||
/// <seealso cref="GemtextObjectRenderer{LineBreakInline}" />
|
||||
public class LineBreakInlineRenderer
|
||||
: GemtextObjectRenderer<LineBreakInline>
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether to render this softline break as a
|
||||
/// Gemtext hardline break tag (<br />)
|
||||
/// </summary>
|
||||
public bool RenderAsHardlineBreak { get; set; }
|
||||
|
||||
protected override void Write(
|
||||
GemtextRenderer renderer,
|
||||
LineBreakInline obj)
|
||||
{
|
||||
if (obj.IsHard || this.RenderAsHardlineBreak)
|
||||
{
|
||||
renderer.EnsureTwoLines();
|
||||
}
|
||||
else
|
||||
{
|
||||
renderer.Write(" ");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,124 @@
|
|||
using System.IO;
|
||||
using Markdig.Syntax.Inlines;
|
||||
|
||||
namespace MfGames.Markdown.Gemtext.Renderers.Gemtext.Inlines
|
||||
{
|
||||
/// <summary>
|
||||
/// A Gemtext renderer for a <see cref="LinkInline" />.
|
||||
/// </summary>
|
||||
/// <seealso cref="GemtextObjectRenderer{LinkInline}" />
|
||||
public class LinkInlineRenderer : GemtextObjectRenderer<LinkInline>
|
||||
{
|
||||
/// <summary>
|
||||
/// Writes out any gathered links in a block.
|
||||
/// </summary>
|
||||
/// <param name="renderer">The renderer being used.</param>
|
||||
public static void WriteGatheredLinks(GemtextRenderer renderer)
|
||||
{
|
||||
// If we have no gathered links, then there is nothing to do.
|
||||
if (renderer.GatheredLinks.Count <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Put some space between the previous object and this one, then
|
||||
// write out each link which is already formatted.
|
||||
renderer.WriteLine();
|
||||
|
||||
foreach (var link in renderer.GatheredLinks)
|
||||
{
|
||||
renderer.WriteLine();
|
||||
renderer.Write(link);
|
||||
}
|
||||
|
||||
// Clear out the list of links.
|
||||
renderer.GatheredLinks.Clear();
|
||||
}
|
||||
|
||||
protected override void Write(GemtextRenderer renderer, LinkInline link)
|
||||
{
|
||||
// Figure out the various states we have.
|
||||
bool outside = !renderer.LinkInsideBlock;
|
||||
bool insert = !outside
|
||||
&& renderer.BlockLinkHandling == BlockLinkHandling.InsertLine;
|
||||
bool gather = !outside
|
||||
&& renderer.BlockLinkHandling switch
|
||||
{
|
||||
BlockLinkHandling.DocumentEnd => true,
|
||||
BlockLinkHandling.ParagraphEnd => true,
|
||||
_ => false,
|
||||
};
|
||||
bool hasText = link.FirstChild != null;
|
||||
bool footnotes = renderer.EndLinkInlineFormatting
|
||||
== EndLinkInlineFormatting.Footnote;
|
||||
|
||||
// Bare links and ones where we insert into the paragraph have
|
||||
// their own line.
|
||||
string? url = link.GetDynamicUrl != null
|
||||
? link.GetDynamicUrl() ?? link.Url
|
||||
: link.Url;
|
||||
|
||||
if (outside || insert)
|
||||
{
|
||||
// Make sure we are at the beginning of the line before
|
||||
// rendering the link.
|
||||
renderer.EnsureLine();
|
||||
renderer.Write("=> ");
|
||||
renderer.Write(url);
|
||||
|
||||
// If we have text, we need a space after the URL and before
|
||||
// the text.
|
||||
if (hasText)
|
||||
{
|
||||
renderer.Write(" ");
|
||||
}
|
||||
}
|
||||
|
||||
// Render the text for the link if we have it.
|
||||
if (hasText)
|
||||
{
|
||||
renderer.WriteChildren(link);
|
||||
}
|
||||
|
||||
// If we are gathering, then write out a footnote.
|
||||
if (gather)
|
||||
{
|
||||
int footnoteNumber = renderer.NextFootnoteNumber++;
|
||||
string linkText = footnotes
|
||||
? footnoteNumber + ": " + url
|
||||
: GetLinkText(link);
|
||||
|
||||
if (footnotes)
|
||||
{
|
||||
renderer.Write($"[{footnoteNumber}]");
|
||||
}
|
||||
|
||||
renderer.GatheredLinks.Add("=> " + url + " " + linkText);
|
||||
}
|
||||
|
||||
// If we are inserting a line in the paragraph, we need a final
|
||||
// newline so the text of the paragraph continues on the next
|
||||
// line.
|
||||
if (insert)
|
||||
{
|
||||
renderer.WriteLine();
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetLinkText(LinkInline link)
|
||||
{
|
||||
// This little bit of nasty code basically spins up a new renderer
|
||||
// to get the text of the link by itself. Then we return that
|
||||
// directly so it can be rendered as a link.
|
||||
StringWriter writer = new();
|
||||
GemtextRenderer renderer = new(writer);
|
||||
|
||||
renderer.WriteChildren(link);
|
||||
writer.Close();
|
||||
|
||||
string text = writer.ToString();
|
||||
|
||||
return text;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
using Markdig.Syntax.Inlines;
|
||||
|
||||
namespace MfGames.Markdown.Gemtext.Renderers.Gemtext.Inlines
|
||||
{
|
||||
/// <summary>
|
||||
/// A Gemtext renderer for a <see cref="LiteralInline" />.
|
||||
/// </summary>
|
||||
/// <seealso cref="GemtextObjectRenderer{LiteralInline}" />
|
||||
public class LiteralInlineRenderer : GemtextObjectRenderer<LiteralInline>
|
||||
{
|
||||
protected override void Write(
|
||||
GemtextRenderer renderer,
|
||||
LiteralInline obj)
|
||||
{
|
||||
// If we are inside a paragraph and we are doing inline formatting,
|
||||
// then we need to trim the text if we are before or after a link.
|
||||
string content = obj.Content.ToString();
|
||||
BlockLinkHandling handling = renderer.BlockLinkHandling;
|
||||
bool isInsert = handling == BlockLinkHandling.InsertLine;
|
||||
bool inBlock = renderer.LinkInsideBlock;
|
||||
|
||||
if (inBlock && isInsert)
|
||||
{
|
||||
if (obj.PreviousSibling is LinkInline)
|
||||
{
|
||||
content = content.TrimStart();
|
||||
}
|
||||
|
||||
if (obj.NextSibling is LinkInline)
|
||||
{
|
||||
content = content.TrimEnd();
|
||||
}
|
||||
}
|
||||
|
||||
// Write out the manipulated content.
|
||||
renderer.Write(content);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
using System.Linq;
|
||||
using Markdig.Syntax;
|
||||
using Markdig.Syntax.Inlines;
|
||||
|
||||
namespace MfGames.Markdown.Gemtext.Renderers.Gemtext
|
||||
{
|
||||
/// <summary>
|
||||
/// Various useful extension methods for Markdig classes.
|
||||
/// </summary>
|
||||
public static class MarkdigExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Determines if the paragraph only contains a link.
|
||||
/// </summary>
|
||||
/// <param name="obj">The object to inspect.</param>
|
||||
/// <returns>True if there is only a link in the paragraph.</returns>
|
||||
public static bool OnlyHasSingleLink(this ParagraphBlock obj)
|
||||
{
|
||||
return obj.Inline != null
|
||||
&& obj.Inline.Count() == 1
|
||||
&& obj.Inline.FirstChild is LinkInline;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines if the list item only contains a link.
|
||||
/// </summary>
|
||||
/// <param name="obj">The object to inspect.</param>
|
||||
/// <returns>True if there is only a link in the paragraph.</returns>
|
||||
public static bool OnlyHasSingleLink(this ListItemBlock obj)
|
||||
{
|
||||
return obj.Count == 1
|
||||
&& obj.LastChild is ParagraphBlock paragraphBlock
|
||||
&& paragraphBlock.OnlyHasSingleLink();
|
||||
}
|
||||
}
|
||||
}
|
185
src/MfGames.Markdown.Gemtext/Renderers/GemtextRenderer.cs
Normal file
185
src/MfGames.Markdown.Gemtext/Renderers/GemtextRenderer.cs
Normal file
|
@ -0,0 +1,185 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using Markdig.Helpers;
|
||||
using Markdig.Renderers;
|
||||
using Markdig.Syntax;
|
||||
using MfGames.Markdown.Gemtext.Renderers.Gemtext.Blocks;
|
||||
using MfGames.Markdown.Gemtext.Renderers.Gemtext.Inlines;
|
||||
|
||||
namespace MfGames.Markdown.Gemtext.Renderers
|
||||
{
|
||||
public class GemtextRenderer : TextRendererBase<GemtextRenderer>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public GemtextRenderer(TextWriter writer)
|
||||
: base(writer)
|
||||
{
|
||||
// Set up our default values.
|
||||
this.NextFootnoteNumber = 1;
|
||||
this.GatheredLinks = new List<string>();
|
||||
|
||||
// Default block renderers.
|
||||
this.ObjectRenderers.Add(new CodeBlockRenderer());
|
||||
this.ObjectRenderers.Add(new HeadingRenderer());
|
||||
this.ObjectRenderers.Add(new HtmlBlockRenderer());
|
||||
this.ObjectRenderers.Add(new ListRenderer());
|
||||
this.ObjectRenderers.Add(new MarkdownDocumentRenderer());
|
||||
this.ObjectRenderers.Add(new ParagraphRenderer());
|
||||
this.ObjectRenderers.Add(new QuoteBlockRenderer());
|
||||
this.ObjectRenderers.Add(new ThematicBreakRenderer());
|
||||
|
||||
// Default inline renderers.
|
||||
this.ObjectRenderers.Add(new CodeInlineRenderer());
|
||||
this.ObjectRenderers.Add(new DelimiterInlineRenderer());
|
||||
this.ObjectRenderers.Add(new EmphasisInlineRenderer());
|
||||
this.ObjectRenderers.Add(new HtmlEntityInlineRenderer());
|
||||
this.ObjectRenderers.Add(new LineBreakInlineRenderer());
|
||||
this.ObjectRenderers.Add(new LinkInlineRenderer());
|
||||
this.ObjectRenderers.Add(new LiteralInlineRenderer());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets how to handle links inside paragraphs and other blocks.
|
||||
/// </summary>
|
||||
public BlockLinkHandling BlockLinkHandling { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the optional formatting for code inlines (backticks).
|
||||
/// If this is unset, then `InlineFormatting` will be used.
|
||||
/// </summary>
|
||||
public InlineFormatting? CodeFormatting { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the actual formatting for code inlines (backticks) which
|
||||
/// is either `CodeInlineFormatting` or `InlineFormatting` if that
|
||||
/// is not set.
|
||||
/// </summary>
|
||||
public InlineFormatting CodeFormattingResolved =>
|
||||
this.CodeFormatting ?? this.InlineFormatting;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the optional formatting for emphasis (which includes
|
||||
/// italics and bolds). If this is unset, then `InlineFormatting`
|
||||
/// will be used.
|
||||
/// </summary>
|
||||
public InlineFormatting? EmphasisFormatting { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the actual formatting for emphasis which is either
|
||||
/// `EmphasisFormatting` or `InlineFormatting` if that isn't set.
|
||||
/// </summary>
|
||||
public InlineFormatting EmphasisFormattingResolved =>
|
||||
this.EmphasisFormatting ?? this.InlineFormatting;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the formatting for how links that are gathered at the
|
||||
/// end of a paragraph or document are formatted inside the paragraph.
|
||||
/// </summary>
|
||||
public EndLinkInlineFormatting EndLinkInlineFormatting { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current list of formatted links that have been gathered
|
||||
/// up to this point for rendering.
|
||||
/// </summary>
|
||||
public List<string> GatheredLinks { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the formatting rule for HTML blocks.
|
||||
/// </summary>
|
||||
public HtmlBlockFormatting HtmlBlockFormatting { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the default formatting for all inlines.
|
||||
/// </summary>
|
||||
public InlineFormatting InlineFormatting { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// An internal processing flag that determines if the rendered link
|
||||
/// is inside a block or not to trigger extra handling.
|
||||
/// </summary>
|
||||
public bool LinkInsideBlock { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the next footnote while rendering links.
|
||||
/// </summary>
|
||||
public int NextFootnoteNumber { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Ensures there are two blank lines before an element.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public GemtextRenderer EnsureTwoLines()
|
||||
{
|
||||
if (this.previousWasLine)
|
||||
{
|
||||
return this;
|
||||
}
|
||||
|
||||
this.WriteLine();
|
||||
this.WriteLine();
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A wrapper method to push the state of LinkInsideBlock while
|
||||
/// performing an action.
|
||||
/// </summary>
|
||||
/// <param name="action">The action to perform.</param>
|
||||
public void WhileLinkInsideBlock(Action action)
|
||||
{
|
||||
bool oldState = this.LinkInsideBlock;
|
||||
this.LinkInsideBlock = true;
|
||||
action();
|
||||
this.LinkInsideBlock = oldState;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes the lines of a <see cref="LeafBlock" />
|
||||
/// </summary>
|
||||
/// <param name="leafBlock">The leaf block.</param>
|
||||
/// <param name="writeEndOfLines">if set to <c>true</c> write end of lines.</param>
|
||||
/// <returns>This instance</returns>
|
||||
public GemtextRenderer WriteLeafRawLines(
|
||||
LeafBlock leafBlock,
|
||||
bool writeEndOfLines)
|
||||
{
|
||||
// Make sure we have sane input.
|
||||
if (leafBlock == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(leafBlock));
|
||||
}
|
||||
|
||||
// If we have nothing to write, then don't do anything. Even though
|
||||
// Markdig says this can't be null, `leafBlock.Lines` may be null
|
||||
// according to the comments.
|
||||
// ReSharper disable once ConditionIsAlwaysTrueOrFalse
|
||||
if (leafBlock.Lines.Lines == null)
|
||||
{
|
||||
return this;
|
||||
}
|
||||
|
||||
// Go through the block and write out each of the lines.
|
||||
StringLineGroup lines = leafBlock.Lines;
|
||||
StringLine[] slices = lines.Lines;
|
||||
|
||||
for (int i = 0; i < lines.Count; i++)
|
||||
{
|
||||
if (!writeEndOfLines && i > 0)
|
||||
{
|
||||
this.WriteLine();
|
||||
}
|
||||
|
||||
this.Write(ref slices[i].Slice);
|
||||
|
||||
if (writeEndOfLines)
|
||||
{
|
||||
this.WriteLine();
|
||||
}
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
namespace MfGames.Markdown.Gemtext.Renderers
|
||||
{
|
||||
/// <summary>
|
||||
/// Describes the ways of formatting a HTML block.
|
||||
/// </summary>
|
||||
public enum HtmlBlockFormatting
|
||||
{
|
||||
/// <summary>
|
||||
/// Indicates that HTML code blocks should just be removed.
|
||||
/// </summary>
|
||||
Remove,
|
||||
|
||||
/// <summary>
|
||||
/// Indicates that HTML code blocks should be treated as blocks with
|
||||
/// "html" as the type.
|
||||
/// </summary>
|
||||
CodeBlock,
|
||||
}
|
||||
}
|
21
src/MfGames.Markdown.Gemtext/Renderers/InlineFormatting.cs
Normal file
21
src/MfGames.Markdown.Gemtext/Renderers/InlineFormatting.cs
Normal file
|
@ -0,0 +1,21 @@
|
|||
namespace MfGames.Markdown.Gemtext.Renderers
|
||||
{
|
||||
/// <summary>
|
||||
/// Describes the ways of formatting inline elements such as emphasis,
|
||||
/// strong, and other elements.
|
||||
/// </summary>
|
||||
public enum InlineFormatting
|
||||
{
|
||||
/// <summary>
|
||||
/// Indicates that the inline should be remove and only the text
|
||||
/// rendered.
|
||||
/// </summary>
|
||||
Remove,
|
||||
|
||||
/// <summary>
|
||||
/// Indicates that the inline should be left in place in a normalized
|
||||
/// form (such as converting `_italics_` into `*italics*`).
|
||||
/// </summary>
|
||||
Normalize,
|
||||
}
|
||||
}
|
Reference in a new issue