From 5d936b53850368b745d638a6db92d7f454a9bd4a Mon Sep 17 00:00:00 2001 From: Oliver Booth Date: Sun, 27 Aug 2023 03:37:01 +0100 Subject: [PATCH] feat: add markdown formatting methods --- CHANGELOG.md | 2 + X10D.Tests/src/Text/MarkdownTests.cs | 122 ++++++++++++++++ X10D/src/Text/MarkdownExtensions.cs | 205 +++++++++++++++++++++++++++ 3 files changed, 329 insertions(+) create mode 100644 X10D.Tests/src/Text/MarkdownTests.cs create mode 100644 X10D/src/Text/MarkdownExtensions.cs diff --git a/CHANGELOG.md b/CHANGELOG.md index 77ec19979..f3d880ee2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - X10D: Added `TextWriter.WriteLineNoAlloc(long[, ReadOnlySpan[, IFormatProvider]])`. - X10D: Added `TextWriter.WriteLineNoAlloc(ulong[, ReadOnlySpan[, IFormatProvider]])`. - X10D: Added `string.ConcatIf`. +- X10D: Added `string.MDBold`, `string.MDCode`, `string.MDCodeBlock([string])`, `string.MDHeading(int)`, +`string.MDItalic`, `string.MDLink`, `string.MDStrikeOut`, and `string.MDUnderline` for Markdown formatting. - X10D.Unity: Added `RaycastHit.GetComponent` and `RaycastHit.TryGetComponent`. - X10D.Unity: Added `DebugUtility.DrawFunction`, and `DebugUtility.DrawUnjoinedPolyhedron` on which it relies. diff --git a/X10D.Tests/src/Text/MarkdownTests.cs b/X10D.Tests/src/Text/MarkdownTests.cs new file mode 100644 index 000000000..8d221454f --- /dev/null +++ b/X10D.Tests/src/Text/MarkdownTests.cs @@ -0,0 +1,122 @@ +using NUnit.Framework; +using X10D.Text; + +namespace X10D.Tests.Text; + +[TestFixture] +internal class MarkdownTests +{ + [Test] + public void MDBold_ShouldThrowArgumentNullException_GivenNull() + { + Assert.Throws(() => ((string)null!).MDBold()); + } + + [Test] + public void MDBold_ShouldReturnBoldText_GivenText() + { + Assert.That("Hello, world!".MDBold(), Is.EqualTo("**Hello, world!**")); + } + + [Test] + public void MDCode_ShouldThrowArgumentNullException_GivenNull() + { + Assert.Throws(() => ((string)null!).MDCode()); + } + + [Test] + public void MDCode_ShouldReturnCodeText_GivenText() + { + Assert.That("Hello, world!".MDCode(), Is.EqualTo("`Hello, world!`")); + } + + [Test] + public void MDCodeBlock_ShouldThrowArgumentNullException_GivenNull() + { + Assert.Throws(() => ((string)null!).MDCodeBlock()); + } + + [Test] + public void MDCodeBlock_ShouldReturnCodeBlockText_GivenText() + { + Assert.That("Hello, world!".MDCodeBlock(), Is.EqualTo($"```{Environment.NewLine}Hello, world!{Environment.NewLine}```")); + } + + [Test] + public void MDCodeBlock_ShouldReturnCodeBlockText_GivenTextAndLanguage() + { + Assert.That("Hello, world!".MDCodeBlock("csharp"), Is.EqualTo($"```csharp{Environment.NewLine}Hello, world!{Environment.NewLine}```")); + } + + [Test] + public void MDHeading_ShouldThrowArgumentNullException_GivenNull() + { + Assert.Throws(() => ((string)null!).MDHeading(1)); + } + + [Test] + public void MDHeading_ShouldThrowArgumentOutOfRangeException_GivenInvalidHeading() + { + Assert.Throws(() => "Hello, world!".MDHeading(0)); + Assert.Throws(() => "Hello, world!".MDHeading(7)); + } + + [Test] + public void MDHeading_ShouldReturnHeadingText_GivenText() + { + Assert.That("Hello, world!".MDHeading(1), Is.EqualTo("# Hello, world!")); + Assert.That("Hello, world!".MDHeading(2), Is.EqualTo("## Hello, world!")); + Assert.That("Hello, world!".MDHeading(3), Is.EqualTo("### Hello, world!")); + Assert.That("Hello, world!".MDHeading(4), Is.EqualTo("#### Hello, world!")); + Assert.That("Hello, world!".MDHeading(5), Is.EqualTo("##### Hello, world!")); + Assert.That("Hello, world!".MDHeading(6), Is.EqualTo("###### Hello, world!")); + } + + [Test] + public void MDItalic_ShouldThrowArgumentNullException_GivenNull() + { + Assert.Throws(() => ((string)null!).MDItalic()); + } + + [Test] + public void MDItalic_ShouldReturnItalicTextWithAsterisk_GivenText() + { + Assert.That("Hello, world!".MDItalic(), Is.EqualTo("*Hello, world!*")); + } + + [Test] + public void MDItalic_ShouldReturnItalicTextWithAsterisk_GivenText_AndFalseUnderscoreFlag() + { + Assert.That("Hello, world!".MDItalic(false), Is.EqualTo("*Hello, world!*")); + } + + [Test] + public void MDItalic_ShouldReturnItalicTextWithUnderscores_GivenText_AndTrueUnderscoreFlag() + { + Assert.That("Hello, world!".MDItalic(true), Is.EqualTo("_Hello, world!_")); + } + + [Test] + public void MDStrikeOut_ShouldThrowArgumentNullException_GivenNull() + { + Assert.Throws(() => ((string)null!).MDStrikeOut()); + } + + [Test] + public void MDStrikeOut_ShouldReturnStrikeOutText_GivenText() + { + Assert.That("Hello, world!".MDStrikeOut(), Is.EqualTo("~~Hello, world!~~")); + } + + [Test] + public void MDUnderline_ShouldThrowArgumentNullException_GivenNull() + { + Assert.Throws(() => ((string)null!).MDUnderline()); + } + + [Test] + public void MDUnderline_ShouldReturnUnderlineText_GivenText() + { + Assert.That("Hello, world!".MDUnderline(), Is.EqualTo("__Hello, world!__")); + } +} diff --git a/X10D/src/Text/MarkdownExtensions.cs b/X10D/src/Text/MarkdownExtensions.cs new file mode 100644 index 000000000..0e91ab114 --- /dev/null +++ b/X10D/src/Text/MarkdownExtensions.cs @@ -0,0 +1,205 @@ +namespace X10D.Text; + +/// +/// Markdown-related extension methods for . +/// +public static class MarkdownExtensions +{ + /// + /// Formats the specified text as bold, using Markdown. + /// + /// The value to surround with bold. + /// The formatted text. + /// is null. + public static string MDBold(this string value) + { + if (value is null) + { + throw new ArgumentNullException(nameof(value)); + } + + return $"**{value}**"; + } + + /// + /// Formats the specified text as code, using Markdown. + /// + /// The value to surround with code. + /// The formatted text. + /// is null. + public static string MDCode(this string value) + { + if (value is null) + { + throw new ArgumentNullException(nameof(value)); + } + + return $"`{value}`"; + } + + /// + /// Formats the specified text as a code block, using Markdown. + /// + /// The value to surround with code blocks. + /// The formatted text. + /// is null. + public static string MDCodeBlock(this string value) + { + return MDCodeBlock(value, string.Empty); + } + + /// + /// Formats the specified text as a code block, using Markdown. + /// + /// The value to surround with code blocks. + /// The language to use for syntax highlighting. + /// The formatted text. + /// is null. + public static string MDCodeBlock(this string value, string language) + { + if (value is null) + { + throw new ArgumentNullException(nameof(value)); + } + + return $"```{language}{Environment.NewLine}{value}{Environment.NewLine}```"; + } + + /// + /// Formats the specified text as a heading, using Markdown. + /// + /// The value to surround with italics. + /// The level of the heading. + /// The formatted text. + /// is null. + /// is less than 1 or greater than 6. + public static string MDHeading(this string value, int level) + { + if (value is null) + { + throw new ArgumentNullException(nameof(value)); + } + + if (level is < 1 or > 6) + { + throw new ArgumentOutOfRangeException(nameof(level)); + } + + return $"{'#'.Repeat(level)} {value}"; + } + + /// + /// Formats the specified text as italics, using Markdown. + /// + /// The value to surround with italics. + /// The formatted text. + /// is null. + /// + /// Markdown has two methods of italicizing text: * and _. This method uses asterisks by default. To + /// use underscores, use and pass as the second argument. + /// + public static string MDItalic(this string value) + { + return MDItalic(value, false); + } + + /// + /// Formats the specified text as italics, using Markdown. + /// + /// The value to surround with italics. + /// Whether to use underscores instead of asterisks for italicizing. + /// The formatted text. + /// is null. + public static string MDItalic(this string value, bool useUnderscores) + { + if (value is null) + { + throw new ArgumentNullException(nameof(value)); + } + + return useUnderscores ? $"_{value}_" : $"*{value}*"; + } + + /// + /// Formats the specified text as a link, using Markdown. + /// + /// The label to use for the link. + /// The URL to link to. + /// The formatted text. + /// is null. + public static string MDLink(this string? label, string url) + { + if (url is null) + { + throw new ArgumentNullException(nameof(url)); + } + + return string.IsNullOrWhiteSpace(label) ? url : $"[{label}]({url})"; + } + + /// + /// Formats the specified text as a link, using Markdown. + /// + /// The label to use for the link. + /// The URL to link to. + /// The formatted text. + /// is null. + public static string MDLink(this string? label, Uri url) + { + if (url is null) + { + throw new ArgumentNullException(nameof(url)); + } + + return string.IsNullOrWhiteSpace(label) ? url.ToString() : $"[{label}]({url})"; + } + + /// + /// Formats the specified text as a link, using Markdown. + /// + /// The URL to link to. + /// The label to use for the link. + /// The formatted text. + /// is null. + public static string MDLink(this Uri url, string? label) + { + if (url is null) + { + throw new ArgumentNullException(nameof(url)); + } + + return string.IsNullOrWhiteSpace(label) ? url.ToString() : $"[{label}]({url})"; + } + + /// + /// Formats the specified text as striked out, using Markdown. + /// + /// The value to surround with strikeout. + /// The formatted text. + /// is null. + public static string MDStrikeOut(this string value) + { + if (value is null) + { + throw new ArgumentNullException(nameof(value)); + } + + return $"~~{value}~~"; + } + + /// + /// Formats the specified text as underlined, using Markdown. + /// + /// The value to surround with underline. + /// The formatted text. + /// is null. + public static string MDUnderline(this string value) + { + if (value is null) + { + throw new ArgumentNullException(nameof(value)); + } + + return $"__{value}__"; + } +}