diff --git a/MfGames.Markdown.Gemtext.sln b/MfGames.Markdown.sln similarity index 54% rename from MfGames.Markdown.Gemtext.sln rename to MfGames.Markdown.sln index 27d23ab..f4fd007 100644 --- a/MfGames.Markdown.Gemtext.sln +++ b/MfGames.Markdown.sln @@ -9,6 +9,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MfGames.Markdown.Gemtext", EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MfGames.Markdown.Gemtext.Tests", "tests\MfGames.Markdown.Gemtext.Tests\MfGames.Markdown.Gemtext.Tests.csproj", "{D2703B25-9AF7-49FF-93A4-CB124560F2A9}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{B4837F83-8560-4AC9-B1E3-6B8C0545A65B}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MfGames.Markdown", "src\MfGames.Markdown\MfGames.Markdown.csproj", "{482E332F-9E72-4F02-B528-9CF04AE05E82}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MfGames.Markdown.Tests", "tests\MfGames.Markdown.Tests\MfGames.Markdown.Tests.csproj", "{3E2AD98D-D1A1-48DF-B94F-A470982DAC9B}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -46,9 +52,35 @@ Global {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 + {482E332F-9E72-4F02-B528-9CF04AE05E82}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {482E332F-9E72-4F02-B528-9CF04AE05E82}.Debug|Any CPU.Build.0 = Debug|Any CPU + {482E332F-9E72-4F02-B528-9CF04AE05E82}.Debug|x64.ActiveCfg = Debug|Any CPU + {482E332F-9E72-4F02-B528-9CF04AE05E82}.Debug|x64.Build.0 = Debug|Any CPU + {482E332F-9E72-4F02-B528-9CF04AE05E82}.Debug|x86.ActiveCfg = Debug|Any CPU + {482E332F-9E72-4F02-B528-9CF04AE05E82}.Debug|x86.Build.0 = Debug|Any CPU + {482E332F-9E72-4F02-B528-9CF04AE05E82}.Release|Any CPU.ActiveCfg = Release|Any CPU + {482E332F-9E72-4F02-B528-9CF04AE05E82}.Release|Any CPU.Build.0 = Release|Any CPU + {482E332F-9E72-4F02-B528-9CF04AE05E82}.Release|x64.ActiveCfg = Release|Any CPU + {482E332F-9E72-4F02-B528-9CF04AE05E82}.Release|x64.Build.0 = Release|Any CPU + {482E332F-9E72-4F02-B528-9CF04AE05E82}.Release|x86.ActiveCfg = Release|Any CPU + {482E332F-9E72-4F02-B528-9CF04AE05E82}.Release|x86.Build.0 = Release|Any CPU + {3E2AD98D-D1A1-48DF-B94F-A470982DAC9B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3E2AD98D-D1A1-48DF-B94F-A470982DAC9B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3E2AD98D-D1A1-48DF-B94F-A470982DAC9B}.Debug|x64.ActiveCfg = Debug|Any CPU + {3E2AD98D-D1A1-48DF-B94F-A470982DAC9B}.Debug|x64.Build.0 = Debug|Any CPU + {3E2AD98D-D1A1-48DF-B94F-A470982DAC9B}.Debug|x86.ActiveCfg = Debug|Any CPU + {3E2AD98D-D1A1-48DF-B94F-A470982DAC9B}.Debug|x86.Build.0 = Debug|Any CPU + {3E2AD98D-D1A1-48DF-B94F-A470982DAC9B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3E2AD98D-D1A1-48DF-B94F-A470982DAC9B}.Release|Any CPU.Build.0 = Release|Any CPU + {3E2AD98D-D1A1-48DF-B94F-A470982DAC9B}.Release|x64.ActiveCfg = Release|Any CPU + {3E2AD98D-D1A1-48DF-B94F-A470982DAC9B}.Release|x64.Build.0 = Release|Any CPU + {3E2AD98D-D1A1-48DF-B94F-A470982DAC9B}.Release|x86.ActiveCfg = Release|Any CPU + {3E2AD98D-D1A1-48DF-B94F-A470982DAC9B}.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} + {D2703B25-9AF7-49FF-93A4-CB124560F2A9} = {B4837F83-8560-4AC9-B1E3-6B8C0545A65B} + {482E332F-9E72-4F02-B528-9CF04AE05E82} = {954BA984-D50E-4D1C-880F-EAE678EF6945} + {3E2AD98D-D1A1-48DF-B94F-A470982DAC9B} = {B4837F83-8560-4AC9-B1E3-6B8C0545A65B} EndGlobalSection EndGlobal diff --git a/MfGames.Markdown.Gemtext.sln.DotSettings b/MfGames.Markdown.sln.DotSettings similarity index 100% rename from MfGames.Markdown.Gemtext.sln.DotSettings rename to MfGames.Markdown.sln.DotSettings diff --git a/README.md b/README.md index 573a992..65d9a68 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,33 @@ -# MfGames.Markdown.Gemtext CIL +# MfGames.Markdown CIL -An extension for [Markdig](https://github.com/xoofx/markdig) that converts Markdown into Gemtext. +This is a set of libraries for working with Markdown in C#. Most of the code are extensions in [MarkDig](https://github.com/xoofx/markdig), an extensible library for converting Markdown. + +The library includes the following: + +- An extension for converting wiki links, such as `[[MfGames]]` into a link based on the title. This is for both HTML and Gemtext. +- A output library for using MarkDig to generate Gemtext for Gemini pods. + +The documentation is rather light at the moment, but the tests are set up to show these can be used. + +## Usage + +These library are not on nuget.org (for various reasons). To use them, set up your NuGet.config to pull them from their repository. + +``` + + + + + + + + + + + + + + + + +``` diff --git a/scripts/build.sh b/scripts/build.sh index 66f32c5..d469e80 100755 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -1,7 +1,12 @@ #!/usr/bin/env sh +# Set up logging. +log() { echo "🛠️ $(basename $0): $@"; } + +# Move into the top level. cd $(dirname $0)/.. + ./scripts/setup.sh || exit 1 -echo "$(basename $0): building project" +log "building project" dotnet build diff --git a/scripts/ci-build.sh b/scripts/ci-build.sh deleted file mode 100755 index f49cd78..0000000 --- a/scripts/ci-build.sh +++ /dev/null @@ -1,3 +0,0 @@ -npm install --ci -dotnet restore -dotnet build diff --git a/scripts/ci-release.sh b/scripts/ci-release.sh deleted file mode 100755 index 72699fd..0000000 --- a/scripts/ci-release.sh +++ /dev/null @@ -1,5 +0,0 @@ -npm install --ci -dotnet restore -dotnet build -npm install --ci -npx semantic-release diff --git a/scripts/ci-test.sh b/scripts/ci-test.sh deleted file mode 100755 index cc073a6..0000000 --- a/scripts/ci-test.sh +++ /dev/null @@ -1,9 +0,0 @@ -npm install --ci -npx commitlint-gitlab-ci -x @commitlint/config-conventional -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" -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 diff --git a/scripts/release.sh b/scripts/release.sh index 94ed900..5cb4879 100755 --- a/scripts/release.sh +++ b/scripts/release.sh @@ -1,39 +1,44 @@ #!/usr/bin/env sh +# Set up logging. +log() { echo "️🚢 $(basename $0): $@"; } + +# Move into the root folder and make sure we're set up. cd $(dirname $0)/.. + ./scripts/setup.sh || exit 1 # Verify the input. if [ "x$GITEA_TOKEN" = "x" ] then - echo "the environment variable GITEA_TOKEN is not defined" + log "the environment variable GITEA_TOKEN is not defined" exit 1 fi # Clean up everything from the previous runs. -echo "$(basename $0): cleaning project" +log "cleaning project" dotnet clean # Version the file based on the Git repository. -echo "$(basename $0): setting project version" +log "setting project version" (cd src && dotnet dotnet-gitversion /updateprojectfiles) SEMVER="v$(dotnet gitversion /output json | jq -r .SemVer)" if [ "x$SEMVER" = "x" ] then - echo "$(basename $0): cannot figure out the semantic version" + log "cannot figure out the semantic version" exit 1 fi # Build to pick up the new version. -echo "$(basename $0): building project $SEMVER" +log "building project $SEMVER" dotnet build || exit 1 # Create and publish the NuGet packages. -echo "$(basename $0): creating NuGet packages" +log "creating NuGet packages" dotnet pack -p:IncludeSymbols=true -p:SymbolPackageFormat=snupkg || exit 1 -echo "$(basename $0): publishing NuGet package" +log "publishing NuGet package" dotnet nuget remove source mfgames.com >& /dev/null dotnet nuget add source --name mfgames.com --username dmoonfire --password $GITEA_TOKEN https://src.mfgames.com/api/packages/mfgames-cil/nuget/index.json --store-password-in-clear-text || exit 1 dotnet nuget push --skip-duplicate --source mfgames.com src/*/bin/Debug/*.nupkg || exit 1 @@ -41,11 +46,11 @@ dotnet nuget push --skip-duplicate --source mfgames.com src/*/bin/Debug/*.nupkg # Tag and push, but only if we don't have a tag. if ! git tag | grep $SEMVER >& /dev/null then - echo "$(basename $0): tagging and pushing" + log "tagging and pushing" git remote add publish https://dmoonfire:$GITEA_TOKEN@src.mfgames.com/mfgames-cil/$(basename $(git config --get remote.origin.url)) git tag $SEMVER git push publish $SEMVER || exit 1 git remote remove publish else - echo "$(basename $0): not tagging, already exists" + log "not tagging, already exists" fi diff --git a/scripts/setup.sh b/scripts/setup.sh index 3ee77ae..bc35967 100755 --- a/scripts/setup.sh +++ b/scripts/setup.sh @@ -1,5 +1,8 @@ #!/usr/bin/env sh +# Set up logging. +log() { echo "📦️ $(basename $0): $@"; + # Normalize our environment. cd $(dirname $0)/.. @@ -8,7 +11,7 @@ for e in dotnet lefthook prettier nixfmt do if ! which $e >& /dev/null then - echo "Cannot find '$e' in the path" + log "Cannot find '$e' in the path" exit 1 fi done @@ -16,12 +19,12 @@ done # Make sure we have lefthook is installed. if [ ! -f .git/hooks/pre-commit ] then - echo "$(basename $0): installing lefthook" + log "installing lefthook" lefthook install fi # Make sure our tools are installed. -echo "$(basename $0): install .NET tools" +log "install .NET tools" dotnet tool restore # Everything is good. diff --git a/src/MfGames.Markdown.Gemtext/Extensions/GemtextSmartyPantsExtension.cs b/src/MfGames.Markdown.Gemtext/Extensions/GemtextSmartyPantsExtension.cs index edd82f6..d386a5e 100644 --- a/src/MfGames.Markdown.Gemtext/Extensions/GemtextSmartyPantsExtension.cs +++ b/src/MfGames.Markdown.Gemtext/Extensions/GemtextSmartyPantsExtension.cs @@ -2,6 +2,7 @@ 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; @@ -44,7 +45,7 @@ namespace MfGames.Markdown.Gemtext.Extensions } if (!gemtextRenderer.ObjectRenderers - .Contains()) + .Contains()) { gemtextRenderer.ObjectRenderers.Add( new GemtextSmartyPantRenderer(this.Options)); diff --git a/src/MfGames.Markdown.Gemtext/Extensions/HtmlAsCodeBlocks.cs b/src/MfGames.Markdown.Gemtext/Extensions/HtmlAsCodeBlocks.cs index 9791cec..204222d 100644 --- a/src/MfGames.Markdown.Gemtext/Extensions/HtmlAsCodeBlocks.cs +++ b/src/MfGames.Markdown.Gemtext/Extensions/HtmlAsCodeBlocks.cs @@ -1,5 +1,6 @@ using Markdig; using Markdig.Renderers; + using MfGames.Markdown.Gemtext.Renderers; namespace MfGames.Markdown.Gemtext.Extensions diff --git a/src/MfGames.Markdown.Gemtext/Extensions/IncreaseHeaderDepthsAfterFirst.cs b/src/MfGames.Markdown.Gemtext/Extensions/IncreaseHeaderDepthsAfterFirst.cs index cf95b38..648415d 100644 --- a/src/MfGames.Markdown.Gemtext/Extensions/IncreaseHeaderDepthsAfterFirst.cs +++ b/src/MfGames.Markdown.Gemtext/Extensions/IncreaseHeaderDepthsAfterFirst.cs @@ -1,5 +1,6 @@ using Markdig; using Markdig.Renderers; + using MfGames.Markdown.Gemtext.Renderers; using MfGames.Markdown.Gemtext.Renderers.Gemtext.Blocks; @@ -26,7 +27,8 @@ namespace MfGames.Markdown.Gemtext.Extensions return; } - var heading = gemtext.ObjectRenderers.Find(); + HeadingRenderer? heading = + gemtext.ObjectRenderers.Find(); if (heading != null) { diff --git a/src/MfGames.Markdown.Gemtext/Extensions/SetBlockLinkHandling.cs b/src/MfGames.Markdown.Gemtext/Extensions/SetBlockLinkHandling.cs index d795b24..bf5fb05 100644 --- a/src/MfGames.Markdown.Gemtext/Extensions/SetBlockLinkHandling.cs +++ b/src/MfGames.Markdown.Gemtext/Extensions/SetBlockLinkHandling.cs @@ -1,5 +1,6 @@ using Markdig; using Markdig.Renderers; + using MfGames.Markdown.Gemtext.Renderers; namespace MfGames.Markdown.Gemtext.Extensions diff --git a/src/MfGames.Markdown.Gemtext/Extensions/SetInlineFormatting.cs b/src/MfGames.Markdown.Gemtext/Extensions/SetInlineFormatting.cs index 0aec2ba..6c96c23 100644 --- a/src/MfGames.Markdown.Gemtext/Extensions/SetInlineFormatting.cs +++ b/src/MfGames.Markdown.Gemtext/Extensions/SetInlineFormatting.cs @@ -1,5 +1,6 @@ using Markdig; using Markdig.Renderers; + using MfGames.Markdown.Gemtext.Renderers; namespace MfGames.Markdown.Gemtext.Extensions diff --git a/src/MfGames.Markdown.Gemtext/MarkdownGemtext.cs b/src/MfGames.Markdown.Gemtext/MarkdownGemtext.cs index a4f1b21..cc7a90c 100644 --- a/src/MfGames.Markdown.Gemtext/MarkdownGemtext.cs +++ b/src/MfGames.Markdown.Gemtext/MarkdownGemtext.cs @@ -1,8 +1,10 @@ using System; using System.IO; + using Markdig; using Markdig.Parsers; using Markdig.Syntax; + using MfGames.Markdown.Gemtext.Renderers; namespace MfGames.Markdown.Gemtext diff --git a/src/MfGames.Markdown.Gemtext/MfGames.Markdown.Gemtext.csproj b/src/MfGames.Markdown.Gemtext/MfGames.Markdown.Gemtext.csproj index 3df902a..49f9f72 100644 --- a/src/MfGames.Markdown.Gemtext/MfGames.Markdown.Gemtext.csproj +++ b/src/MfGames.Markdown.Gemtext/MfGames.Markdown.Gemtext.csproj @@ -1,25 +1,24 @@ - - net6.0 - enable - + + net6.0 + enable + - - true - Dylan Moonfire - Moonfire Games - https://gitlab.com/mfgames-cil/mfgames-markdown-gemtext-cil - Git - ecs - https://gitlab.com/mfgames-cil/mfgames-markdown-gemtext-cil - MIT - A Markdown extension to render Markdown in Gemtext. - + + true + Dylan Moonfire + Moonfire Games + https://gitlab.com/mfgames-cil/mfgames-markdown-cil + Git + https://gitlab.com/mfgames-cil/mfgames-markdown-cil + MIT + A MarkDig extension to render Markdown in Gemtext. + - - - - + + + + diff --git a/src/MfGames.Markdown.Gemtext/Renderers/Gemtext/Blocks/ListRenderer.cs b/src/MfGames.Markdown.Gemtext/Renderers/Gemtext/Blocks/ListRenderer.cs index 45d8004..2c1a666 100644 --- a/src/MfGames.Markdown.Gemtext/Renderers/Gemtext/Blocks/ListRenderer.cs +++ b/src/MfGames.Markdown.Gemtext/Renderers/Gemtext/Blocks/ListRenderer.cs @@ -16,7 +16,7 @@ namespace MfGames.Markdown.Gemtext.Renderers.Gemtext.Blocks renderer.EnsureTwoLines(); // Go through each list item and write them out. - foreach (var item in listBlock) + foreach (Block? item in listBlock) { // If the list only contains a link, then we just render the // link instead. diff --git a/src/MfGames.Markdown.Gemtext/Renderers/Gemtext/Blocks/MarkdownDocumentRenderer.cs b/src/MfGames.Markdown.Gemtext/Renderers/Gemtext/Blocks/MarkdownDocumentRenderer.cs index 7cf2514..e0d4ee2 100644 --- a/src/MfGames.Markdown.Gemtext/Renderers/Gemtext/Blocks/MarkdownDocumentRenderer.cs +++ b/src/MfGames.Markdown.Gemtext/Renderers/Gemtext/Blocks/MarkdownDocumentRenderer.cs @@ -1,4 +1,5 @@ using Markdig.Syntax; + using MfGames.Markdown.Gemtext.Renderers.Gemtext.Inlines; namespace MfGames.Markdown.Gemtext.Renderers.Gemtext.Blocks diff --git a/src/MfGames.Markdown.Gemtext/Renderers/Gemtext/Blocks/ParagraphRenderer.cs b/src/MfGames.Markdown.Gemtext/Renderers/Gemtext/Blocks/ParagraphRenderer.cs index 4dbcd7b..6bfdf46 100644 --- a/src/MfGames.Markdown.Gemtext/Renderers/Gemtext/Blocks/ParagraphRenderer.cs +++ b/src/MfGames.Markdown.Gemtext/Renderers/Gemtext/Blocks/ParagraphRenderer.cs @@ -1,4 +1,5 @@ using Markdig.Syntax; + using MfGames.Markdown.Gemtext.Renderers.Gemtext.Inlines; namespace MfGames.Markdown.Gemtext.Renderers.Gemtext.Blocks diff --git a/src/MfGames.Markdown.Gemtext/Renderers/Gemtext/Inlines/GemtextSmartyPantRenderer.cs b/src/MfGames.Markdown.Gemtext/Renderers/Gemtext/Inlines/GemtextSmartyPantRenderer.cs index b2f54f8..b30150f 100644 --- a/src/MfGames.Markdown.Gemtext/Renderers/Gemtext/Inlines/GemtextSmartyPantRenderer.cs +++ b/src/MfGames.Markdown.Gemtext/Renderers/Gemtext/Inlines/GemtextSmartyPantRenderer.cs @@ -1,5 +1,6 @@ using System; using System.Net; + using Markdig.Extensions.SmartyPants; namespace MfGames.Markdown.Gemtext.Renderers.Gemtext.Inlines diff --git a/src/MfGames.Markdown.Gemtext/Renderers/Gemtext/Inlines/LinkInlineRenderer.cs b/src/MfGames.Markdown.Gemtext/Renderers/Gemtext/Inlines/LinkInlineRenderer.cs index ce72e8c..118ddcf 100644 --- a/src/MfGames.Markdown.Gemtext/Renderers/Gemtext/Inlines/LinkInlineRenderer.cs +++ b/src/MfGames.Markdown.Gemtext/Renderers/Gemtext/Inlines/LinkInlineRenderer.cs @@ -1,4 +1,5 @@ using System.IO; + using Markdig.Syntax.Inlines; namespace MfGames.Markdown.Gemtext.Renderers.Gemtext.Inlines @@ -25,7 +26,7 @@ namespace MfGames.Markdown.Gemtext.Renderers.Gemtext.Inlines // write out each link which is already formatted. renderer.WriteLine(); - foreach (var link in renderer.GatheredLinks) + foreach (string? link in renderer.GatheredLinks) { renderer.WriteLine(); renderer.Write(link); diff --git a/src/MfGames.Markdown.Gemtext/Renderers/Gemtext/MarkdigExtensions.cs b/src/MfGames.Markdown.Gemtext/Renderers/Gemtext/MarkdigExtensions.cs index 0d2b247..1d372ad 100644 --- a/src/MfGames.Markdown.Gemtext/Renderers/Gemtext/MarkdigExtensions.cs +++ b/src/MfGames.Markdown.Gemtext/Renderers/Gemtext/MarkdigExtensions.cs @@ -1,4 +1,5 @@ using System.Linq; + using Markdig.Syntax; using Markdig.Syntax.Inlines; diff --git a/src/MfGames.Markdown.Gemtext/Renderers/GemtextRenderer.cs b/src/MfGames.Markdown.Gemtext/Renderers/GemtextRenderer.cs index 19b75ab..84182e3 100644 --- a/src/MfGames.Markdown.Gemtext/Renderers/GemtextRenderer.cs +++ b/src/MfGames.Markdown.Gemtext/Renderers/GemtextRenderer.cs @@ -1,9 +1,11 @@ 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; diff --git a/src/MfGames.Markdown/Extensions/WikiLink.cs b/src/MfGames.Markdown/Extensions/WikiLink.cs new file mode 100644 index 0000000..4f86161 --- /dev/null +++ b/src/MfGames.Markdown/Extensions/WikiLink.cs @@ -0,0 +1,11 @@ +using Markdig.Syntax.Inlines; + +namespace MfGames.Markdown.Extensions; + +public class WikiLink : LinkInline +{ + public WikiLink() + { + this.IsClosed = false; + } +} diff --git a/src/MfGames.Markdown/Extensions/WikiLinkExtension.cs b/src/MfGames.Markdown/Extensions/WikiLinkExtension.cs new file mode 100644 index 0000000..45e0a4e --- /dev/null +++ b/src/MfGames.Markdown/Extensions/WikiLinkExtension.cs @@ -0,0 +1,47 @@ +using Markdig; +using Markdig.Parsers.Inlines; +using Markdig.Renderers; + +namespace MfGames.Markdown.Extensions; + +/// +/// Translate `[[Bob]]` into `/bob/`. +/// +public class WikiLinkExtension : IMarkdownExtension +{ + public WikiLinkExtension() + : this(null) + { + } + + public WikiLinkExtension(WikiLinkOptions? options) + { + this.Options = options ?? new WikiLinkOptions(); + } + + public WikiLinkOptions Options { get; set; } + + /// + public void Setup(MarkdownPipelineBuilder pipeline) + { + WikiLinkInlineParser? parser = pipeline.InlineParsers + .FindExact(); + + if (parser != null) + { + return; + } + + parser = new WikiLinkInlineParser(this.Options); + pipeline.InlineParsers.InsertBefore(parser); + } + + /// + public void Setup( + MarkdownPipeline pipeline, + IMarkdownRenderer renderer) + { + // No setup needed here because we're using LinkInline which does the + // bulk of the work. + } +} diff --git a/src/MfGames.Markdown/Extensions/WikiLinkInlineParser.cs b/src/MfGames.Markdown/Extensions/WikiLinkInlineParser.cs new file mode 100644 index 0000000..c63655c --- /dev/null +++ b/src/MfGames.Markdown/Extensions/WikiLinkInlineParser.cs @@ -0,0 +1,112 @@ +using System.Linq; + +using Markdig.Helpers; +using Markdig.Parsers; +using Markdig.Syntax.Inlines; + +namespace MfGames.Markdown.Extensions; + +public class WikiLinkInlineParser : InlineParser +{ + private readonly WikiLinkOptions options; + + public WikiLinkInlineParser(WikiLinkOptions options) + { + this.options = options; + this.OpeningCharacters = new[] { '[' }; + } + + /// + public override bool Match( + InlineProcessor processor, + ref StringSlice slice) + { + // We are looking for the `[[` opening for the tag and that the first + // one isn't escaped. + if (IsNotDelimiter(slice, '[')) + { + return false; + } + + // We need to loop over the entire link, including the `[[` and `]]` + // while keeping track since we'll swallow additional characters beyond + // the link. + int linkStart = slice.Start; + int linkEnd = slice.Start; + + slice.Start += 2; + + // Our content starts after the double '[['. + int contentStart = slice.Start; + + // We need to find the end of the link (the `]]`). + while (IsNotDelimiter(slice, ']')) + { + slice.NextChar(); + linkEnd = slice.Start; + } + + // Pull out the components before we adjust for the ']]' for the end. + int contentEnd = linkEnd; + + // Finish skipping over the `]]`. + slice.NextChar(); + slice.NextChar(); + + // Format the label and the URL. + string content = slice.Text.Substring( + contentStart, + contentEnd - contentStart); + string[] contentParts = content.Split('|', 2); + string label = contentParts.Last(); + string url = this.options.GetUrl(contentParts.First()); + + // Add in any trailing components. This merges the `'s` from + // `[[Dale]]'s` into the label. + while (this.options.IsTrailingLink(slice.CurrentChar)) + { + label += slice.CurrentChar; + slice.NextChar(); + linkEnd++; + } + + // Create the link that we're replacing. + WikiLink link = new() + { + Span = + { + Start = processor.GetSourcePosition( + linkStart, + out int line, + out int column), + }, + Line = line, + Column = column, + Url = url, + IsClosed = true, + }; + + link.AppendChild( + new LiteralInline() + { + Line = line, + Column = column, + Content = new StringSlice(label), + IsClosed = true, + }); + + // Replace the inline and then indicate we have a match. + processor.Inline = link; + + return true; + } + + private static bool IsNotDelimiter( + StringSlice slice, + char delimiter) + { + return slice.CurrentChar != delimiter + || slice.PeekChar() != delimiter + || slice.PeekCharExtra(-1) == '\\'; + } +} diff --git a/src/MfGames.Markdown/Extensions/WikiLinkOptions.cs b/src/MfGames.Markdown/Extensions/WikiLinkOptions.cs new file mode 100644 index 0000000..15a7890 --- /dev/null +++ b/src/MfGames.Markdown/Extensions/WikiLinkOptions.cs @@ -0,0 +1,31 @@ +using System; + +using Markdig.Helpers; + +namespace MfGames.Markdown.Extensions; + +public class WikiLinkOptions +{ + public WikiLinkOptions() + { + this.GetUrl = a => a; + this.IsTrailingLink = a => a.IsAlpha() || a == '\''; + } + + /// + /// The callback to determine the link from the given wiki link. This does + /// not include trailing additions or use the label (e.g., `(ab|cd)` would + /// get `ab` as the parameters of this function. + /// + public Func GetUrl { get; set; } + + /// + /// + /// A callback to determine if the text after the link should be merged + /// with the link label. This allows links such as [[Dale]]'s to be turned + /// into "Dale's" but pointing to "Dale" as a page. + /// + /// The default is to include any character or the apostrophe. + /// + public Func IsTrailingLink { get; set; } +} diff --git a/src/MfGames.Markdown/MfGames.Markdown.csproj b/src/MfGames.Markdown/MfGames.Markdown.csproj new file mode 100644 index 0000000..a4d17ee --- /dev/null +++ b/src/MfGames.Markdown/MfGames.Markdown.csproj @@ -0,0 +1,23 @@ + + + + net6.0 + enable + + + + true + Dylan Moonfire + Moonfire Games + https://gitlab.com/mfgames-cil/mfgames-markdown-cil + Git + https://gitlab.com/mfgames-cil/mfgames-markdown-cil + MIT + Various extensions for MarkDig and classes for working with Markdown. + + + + + + + diff --git a/tests/MfGames.Markdown.Gemtext.Tests/CodeInlineTests.cs b/tests/MfGames.Markdown.Gemtext.Tests/CodeInlineTests.cs index 0797ea6..7dca6cb 100644 --- a/tests/MfGames.Markdown.Gemtext.Tests/CodeInlineTests.cs +++ b/tests/MfGames.Markdown.Gemtext.Tests/CodeInlineTests.cs @@ -1,10 +1,11 @@ using Markdig; -using MfGames.Markdown.Gemtext; + using MfGames.Markdown.Gemtext.Extensions; using MfGames.Markdown.Gemtext.Renderers; + using Xunit; -namespace MfGames.Markdown.Gemini.Tests +namespace MfGames.Markdown.Gemtext.Tests { public class CodeInlineTests { diff --git a/tests/MfGames.Markdown.Gemtext.Tests/HeaderTests.cs b/tests/MfGames.Markdown.Gemtext.Tests/HeaderTests.cs index 9a9f93a..2ca7aa3 100644 --- a/tests/MfGames.Markdown.Gemtext.Tests/HeaderTests.cs +++ b/tests/MfGames.Markdown.Gemtext.Tests/HeaderTests.cs @@ -1,7 +1,6 @@ -using MfGames.Markdown.Gemtext; using Xunit; -namespace MfGames.Markdown.Gemini.Tests +namespace MfGames.Markdown.Gemtext.Tests { public class HeaderTests { diff --git a/tests/MfGames.Markdown.Gemtext.Tests/ImageLinkTests.cs b/tests/MfGames.Markdown.Gemtext.Tests/ImageLinkTests.cs index 44c72cb..30ab6b0 100644 --- a/tests/MfGames.Markdown.Gemtext.Tests/ImageLinkTests.cs +++ b/tests/MfGames.Markdown.Gemtext.Tests/ImageLinkTests.cs @@ -1,7 +1,6 @@ -using MfGames.Markdown.Gemtext; using Xunit; -namespace MfGames.Markdown.Gemini.Tests +namespace MfGames.Markdown.Gemtext.Tests { public class ImageLinkTests { diff --git a/tests/MfGames.Markdown.Gemtext.Tests/IncreaseHeaderDepthsAfterFirstTests.cs b/tests/MfGames.Markdown.Gemtext.Tests/IncreaseHeaderDepthsAfterFirstTests.cs index f257a57..2849b07 100644 --- a/tests/MfGames.Markdown.Gemtext.Tests/IncreaseHeaderDepthsAfterFirstTests.cs +++ b/tests/MfGames.Markdown.Gemtext.Tests/IncreaseHeaderDepthsAfterFirstTests.cs @@ -1,9 +1,10 @@ using Markdig; -using MfGames.Markdown.Gemtext; + using MfGames.Markdown.Gemtext.Extensions; + using Xunit; -namespace MfGames.Markdown.Gemini.Tests +namespace MfGames.Markdown.Gemtext.Tests { public class IncreaseHeaderDepthsAfterFirstTests { diff --git a/tests/MfGames.Markdown.Gemtext.Tests/LinkTests.cs b/tests/MfGames.Markdown.Gemtext.Tests/LinkTests.cs index b620bf4..1a2e1ce 100644 --- a/tests/MfGames.Markdown.Gemtext.Tests/LinkTests.cs +++ b/tests/MfGames.Markdown.Gemtext.Tests/LinkTests.cs @@ -1,7 +1,6 @@ -using MfGames.Markdown.Gemtext; using Xunit; -namespace MfGames.Markdown.Gemini.Tests +namespace MfGames.Markdown.Gemtext.Tests { public class LinkTests { diff --git a/tests/MfGames.Markdown.Gemtext.Tests/ListTests.cs b/tests/MfGames.Markdown.Gemtext.Tests/ListTests.cs index e378d2a..20492e5 100644 --- a/tests/MfGames.Markdown.Gemtext.Tests/ListTests.cs +++ b/tests/MfGames.Markdown.Gemtext.Tests/ListTests.cs @@ -1,7 +1,6 @@ -using MfGames.Markdown.Gemtext; using Xunit; -namespace MfGames.Markdown.Gemini.Tests +namespace MfGames.Markdown.Gemtext.Tests { public class ListTests { diff --git a/tests/MfGames.Markdown.Gemtext.Tests/MfGames.Markdown.Gemtext.Tests.csproj b/tests/MfGames.Markdown.Gemtext.Tests/MfGames.Markdown.Gemtext.Tests.csproj index 0c0ca7b..ab8ab07 100644 --- a/tests/MfGames.Markdown.Gemtext.Tests/MfGames.Markdown.Gemtext.Tests.csproj +++ b/tests/MfGames.Markdown.Gemtext.Tests/MfGames.Markdown.Gemtext.Tests.csproj @@ -1,26 +1,27 @@ - - net6.0 - MfGames.Markdown.Gemini.Tests - + + net6.0 + - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + - - - + + + + diff --git a/tests/MfGames.Markdown.Gemtext.Tests/ParagraphLinkHandlingTests.cs b/tests/MfGames.Markdown.Gemtext.Tests/ParagraphLinkHandlingTests.cs index cec7342..acdbb1d 100644 --- a/tests/MfGames.Markdown.Gemtext.Tests/ParagraphLinkHandlingTests.cs +++ b/tests/MfGames.Markdown.Gemtext.Tests/ParagraphLinkHandlingTests.cs @@ -1,10 +1,11 @@ using Markdig; -using MfGames.Markdown.Gemtext; + using MfGames.Markdown.Gemtext.Extensions; using MfGames.Markdown.Gemtext.Renderers; + using Xunit; -namespace MfGames.Markdown.Gemini.Tests +namespace MfGames.Markdown.Gemtext.Tests { /// /// Tests the various functionality of the ParagraphLinkHandling and diff --git a/tests/MfGames.Markdown.Gemtext.Tests/PlainTextTests.cs b/tests/MfGames.Markdown.Gemtext.Tests/PlainTextTests.cs index 046d5f9..7726bf6 100644 --- a/tests/MfGames.Markdown.Gemtext.Tests/PlainTextTests.cs +++ b/tests/MfGames.Markdown.Gemtext.Tests/PlainTextTests.cs @@ -1,7 +1,6 @@ -using MfGames.Markdown.Gemtext; using Xunit; -namespace MfGames.Markdown.Gemini.Tests +namespace MfGames.Markdown.Gemtext.Tests { public class PlainTextTests { diff --git a/tests/MfGames.Markdown.Gemtext.Tests/PythonInspired/BaseUrlPythonTests.cs b/tests/MfGames.Markdown.Gemtext.Tests/PythonInspired/BaseUrlPythonTests.cs index a0ea55e..d4805b5 100644 --- a/tests/MfGames.Markdown.Gemtext.Tests/PythonInspired/BaseUrlPythonTests.cs +++ b/tests/MfGames.Markdown.Gemtext.Tests/PythonInspired/BaseUrlPythonTests.cs @@ -1,7 +1,6 @@ -using MfGames.Markdown.Gemtext; using Xunit; -namespace MfGames.Markdown.Gemini.Tests.PythonInspired +namespace MfGames.Markdown.Gemtext.Tests.PythonInspired { /// /// https://github.com/makeworld-the-better-one/md2gemini/blob/master/tests/test_base_url.py diff --git a/tests/MfGames.Markdown.Gemtext.Tests/PythonInspired/CodeBlockPythonTests.cs b/tests/MfGames.Markdown.Gemtext.Tests/PythonInspired/CodeBlockPythonTests.cs index ced92ff..78bb932 100644 --- a/tests/MfGames.Markdown.Gemtext.Tests/PythonInspired/CodeBlockPythonTests.cs +++ b/tests/MfGames.Markdown.Gemtext.Tests/PythonInspired/CodeBlockPythonTests.cs @@ -1,7 +1,6 @@ -using MfGames.Markdown.Gemtext; using Xunit; -namespace MfGames.Markdown.Gemini.Tests.PythonInspired +namespace MfGames.Markdown.Gemtext.Tests.PythonInspired { /// /// https://github.com/makeworld-the-better-one/md2gemini/blob/master/tests/test_codeblock.py diff --git a/tests/MfGames.Markdown.Gemtext.Tests/PythonInspired/ListPythonTests.cs b/tests/MfGames.Markdown.Gemtext.Tests/PythonInspired/ListPythonTests.cs index a82f4c1..46f6850 100644 --- a/tests/MfGames.Markdown.Gemtext.Tests/PythonInspired/ListPythonTests.cs +++ b/tests/MfGames.Markdown.Gemtext.Tests/PythonInspired/ListPythonTests.cs @@ -1,7 +1,6 @@ -using MfGames.Markdown.Gemtext; using Xunit; -namespace MfGames.Markdown.Gemini.Tests.PythonInspired +namespace MfGames.Markdown.Gemtext.Tests.PythonInspired { /// /// https://github.com/makeworld-the-better-one/md2gemini/blob/master/tests/test_list.py diff --git a/tests/MfGames.Markdown.Gemtext.Tests/PythonInspired/PlainTextPythonTests.cs b/tests/MfGames.Markdown.Gemtext.Tests/PythonInspired/PlainTextPythonTests.cs index 737c649..20a4b36 100644 --- a/tests/MfGames.Markdown.Gemtext.Tests/PythonInspired/PlainTextPythonTests.cs +++ b/tests/MfGames.Markdown.Gemtext.Tests/PythonInspired/PlainTextPythonTests.cs @@ -1,9 +1,10 @@ using Markdig; -using MfGames.Markdown.Gemtext; + using MfGames.Markdown.Gemtext.Extensions; + using Xunit; -namespace MfGames.Markdown.Gemini.Tests.PythonInspired +namespace MfGames.Markdown.Gemtext.Tests.PythonInspired { /// /// Runs through various plain input tests. diff --git a/tests/MfGames.Markdown.Gemtext.Tests/PythonInspired/StripHtmlPythonTests.cs b/tests/MfGames.Markdown.Gemtext.Tests/PythonInspired/StripHtmlPythonTests.cs index 4c2faec..eeac5b1 100644 --- a/tests/MfGames.Markdown.Gemtext.Tests/PythonInspired/StripHtmlPythonTests.cs +++ b/tests/MfGames.Markdown.Gemtext.Tests/PythonInspired/StripHtmlPythonTests.cs @@ -1,12 +1,11 @@ using Markdig; -using MfGames.Markdown.Gemtext; using MfGames.Markdown.Gemtext.Extensions; using MfGames.Markdown.Gemtext.Renderers; using Xunit; -namespace MfGames.Markdown.Gemini.Tests.PythonInspired +namespace MfGames.Markdown.Gemtext.Tests.PythonInspired { /// /// https://github.com/makeworld-the-better-one/md2gemini/blob/master/tests/test_strip_html.py diff --git a/tests/MfGames.Markdown.Gemtext.Tests/QuoteTests.cs b/tests/MfGames.Markdown.Gemtext.Tests/QuoteTests.cs index 185872e..7a49ef1 100644 --- a/tests/MfGames.Markdown.Gemtext.Tests/QuoteTests.cs +++ b/tests/MfGames.Markdown.Gemtext.Tests/QuoteTests.cs @@ -1,10 +1,11 @@ using Markdig; using Markdig.Extensions.SmartyPants; -using MfGames.Markdown.Gemtext; + using MfGames.Markdown.Gemtext.Extensions; + using Xunit; -namespace MfGames.Markdown.Gemini.Tests +namespace MfGames.Markdown.Gemtext.Tests { public class QuoteTests { diff --git a/tests/MfGames.Markdown.Gemtext.Tests/TableTests.cs b/tests/MfGames.Markdown.Gemtext.Tests/TableTests.cs index bf35239..bed860e 100644 --- a/tests/MfGames.Markdown.Gemtext.Tests/TableTests.cs +++ b/tests/MfGames.Markdown.Gemtext.Tests/TableTests.cs @@ -2,12 +2,11 @@ using ConsoleTableExt; using Markdig; -using MfGames.Markdown.Gemtext; using MfGames.Markdown.Gemtext.Extensions; using Xunit; -namespace MfGames.Markdown.Gemini.Tests +namespace MfGames.Markdown.Gemtext.Tests { public class TableTests { diff --git a/tests/MfGames.Markdown.Gemtext.Tests/WikiLinkTests.cs b/tests/MfGames.Markdown.Gemtext.Tests/WikiLinkTests.cs new file mode 100644 index 0000000..bec7944 --- /dev/null +++ b/tests/MfGames.Markdown.Gemtext.Tests/WikiLinkTests.cs @@ -0,0 +1,56 @@ +using Markdig; + +using MfGames.Markdown.Extensions; +using MfGames.Markdown.Gemtext.Extensions; +using MfGames.Markdown.Gemtext.Renderers; +using MfGames.TestSetup; + +using Xunit; +using Xunit.Abstractions; + +namespace MfGames.Markdown.Gemtext.Tests; + +/// +/// Tests the functionality of the WriteFiles(). +/// +public class WikiLinkTest : TestBase +{ + public WikiLinkTest(ITestOutputHelper output) + : base(output) + { + } + + [Fact] + public void PossessiveLabeledWikiLinkGemtext() + { + MarkdownPipeline pipeline = CreatePipeline(); + string result = MarkdownGemtext + .ToGemtext( + "[[Test|Label]]'s", + pipeline) + .Replace("%5E", "^"); + + Assert.Equal("=> ^test$ Label's", result); + } + + private static MarkdownPipeline CreatePipeline() + { + // Convert all titles to slugs. + WikiLinkOptions options = new() + { + GetUrl = (title) => string.Format( + "^{0}$", + title.ToLowerInvariant().Replace(" ", "-")), + }; + + // Create the pipeline and return the results. + MarkdownPipelineBuilder builder = new MarkdownPipelineBuilder() + .Use(new WikiLinkExtension(options)) + .Use(new SetBlockLinkHandling(BlockLinkHandling.DocumentEnd)); + + // Build the pipeline and return it. + MarkdownPipeline pipeline = builder.Build(); + + return pipeline; + } +} diff --git a/tests/MfGames.Markdown.Tests/MfGames.Markdown.Tests.csproj b/tests/MfGames.Markdown.Tests/MfGames.Markdown.Tests.csproj new file mode 100644 index 0000000..52d337f --- /dev/null +++ b/tests/MfGames.Markdown.Tests/MfGames.Markdown.Tests.csproj @@ -0,0 +1,28 @@ + + + + net6.0 + enable + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + diff --git a/tests/MfGames.Markdown.Tests/WikiLinkTests.cs b/tests/MfGames.Markdown.Tests/WikiLinkTests.cs new file mode 100644 index 0000000..67aca7f --- /dev/null +++ b/tests/MfGames.Markdown.Tests/WikiLinkTests.cs @@ -0,0 +1,188 @@ +using Markdig; + +using MfGames.Markdown.Extensions; +using MfGames.TestSetup; + +using Xunit; +using Xunit.Abstractions; + +namespace MfGames.Markdown.Tests; + +/// +/// Tests the functionality of the WriteFiles(). +/// +public class WikiLinkTest : TestBase +{ + public WikiLinkTest(ITestOutputHelper output) + : base(output) + { + } + + [Fact] + public void BareStringHtml() + { + MarkdownPipeline pipeline = CreatePipeline(); + string result = Markdig.Markdown.ToHtml( + "This is text.", + pipeline); + + Assert.Equal("

This is text.

\n", result); + } + + [Fact] + public void BlankStringHtml() + { + MarkdownPipeline pipeline = CreatePipeline(); + string result = Markdig.Markdown.ToHtml( + "", + pipeline); + + Assert.Equal("", result); + } + + [Fact] + public void LabeledWikiLinkHtml() + { + MarkdownPipeline pipeline = CreatePipeline(); + string result = Markdig.Markdown + .ToHtml( + "[[Test|Label]]", + pipeline) + .Replace("%5E", "^"); + + Assert.Equal("

Label

\n", result); + } + + [Fact] + public void PossessiveLabeledWikiLinkHtml() + { + MarkdownPipeline pipeline = CreatePipeline(); + string result = Markdig.Markdown + .ToHtml( + "[[Test|Label]]'s", + pipeline) + .Replace("%5E", "^"); + + Assert.Equal("

Label's

\n", result); + } + + [Fact] + public void PossessiveWikiLinkHtml() + { + MarkdownPipeline pipeline = CreatePipeline(); + string result = Markdig.Markdown + .ToHtml( + "[[Test]]'s", + pipeline) + .Replace("%5E", "^"); + + Assert.Equal("

Test's

\n", result); + } + + [Fact] + public void PunctuationLabeledWikiLinkHtml() + { + MarkdownPipeline pipeline = CreatePipeline(); + string result = Markdig.Markdown + .ToHtml( + "[[Test|Label]].", + pipeline) + .Replace("%5E", "^"); + + Assert.Equal("

Label.

\n", result); + } + + [Fact] + public void PunctuationWikiLinkHtml() + { + MarkdownPipeline pipeline = CreatePipeline(); + string result = Markdig.Markdown + .ToHtml( + "[[Test]].", + pipeline) + .Replace("%5E", "^"); + + Assert.Equal("

Test.

\n", result); + } + + [Fact] + public void SentenceWikiLinkHtml() + { + MarkdownPipeline pipeline = CreatePipeline(); + string result = Markdig.Markdown + .ToHtml( + "This is a [[test]] of this system.", + pipeline) + .Replace("%5E", "^"); + + Assert.Equal( + "

This is a test of this system.

\n", + result); + } + + [Fact] + public void SimpleWikiLinkHtml() + { + MarkdownPipeline pipeline = CreatePipeline(); + string result = Markdig.Markdown + .ToHtml( + "[[Test]]", + pipeline) + .Replace("%5E", "^"); + + Assert.Equal("

Test

\n", result); + } + + [Fact] + public void StandardLinkHtml() + { + MarkdownPipeline pipeline = CreatePipeline(); + string result = Markdig.Markdown.ToHtml( + "[Test](https://test/)", + pipeline); + + Assert.Equal("

Test

\n", result); + } + + [Fact] + public void TrailingLabeledWikiLinkHtml() + { + MarkdownPipeline pipeline = CreatePipeline(); + string result = Markdig.Markdown + .ToHtml( + "[[Test|Label]]s", + pipeline) + .Replace("%5E", "^"); + + Assert.Equal("

Labels

\n", result); + } + + [Fact] + public void TrailingWikiLinkHtml() + { + MarkdownPipeline pipeline = CreatePipeline(); + string result = Markdig.Markdown + .ToHtml( + "[[Test]]s", + pipeline) + .Replace("%5E", "^"); + + Assert.Equal("

Tests

\n", result); + } + + private static MarkdownPipeline CreatePipeline() + { + WikiLinkOptions options = new() + { + GetUrl = (title) => string.Format( + "^{0}$", + title.ToLowerInvariant().Replace(" ", "-")), + }; + + MarkdownPipelineBuilder? builder = new MarkdownPipelineBuilder() + .Use(new WikiLinkExtension(options)); + MarkdownPipeline? pipeline = builder.Build(); + + return pipeline; + } +}