From f49188b42801fde970f11dc7e2681942bc363667 Mon Sep 17 00:00:00 2001 From: Oliver Booth Date: Thu, 30 Mar 2023 02:09:04 +0100 Subject: [PATCH] feat: add string.EnsureEndsWith and string.EnsureStartsWith --- CHANGELOG.md | 1 + X10D.Tests/src/Text/StringTests.cs | 70 +++++++++++ X10D/src/Text/StringExtensions.cs | 193 ++++++++++++++++++++++++++++- 3 files changed, 263 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 33fb935..7969e28 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -69,6 +69,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - X10D: Added `Span.Split(Span)`. - X10D: Added `string.CountSubstring(char)`. - X10D: Added `string.CountSubstring(string[, StringComparison])`. +- X10D: Added `string.EnsureStartsWith()` and `string.EnsureEndsWith()`. - X10D: Added `string.IsEmpty()`. - X10D: Added `string.IsWhiteSpace()`. - X10D: Added `string.IsNullOrEmpty()`. diff --git a/X10D.Tests/src/Text/StringTests.cs b/X10D.Tests/src/Text/StringTests.cs index a5a8fbf..be148c6 100644 --- a/X10D.Tests/src/Text/StringTests.cs +++ b/X10D.Tests/src/Text/StringTests.cs @@ -142,6 +142,76 @@ public class StringTests ((string?)null!).CountSubstring(string.Empty, StringComparison.OrdinalIgnoreCase)); } + [TestMethod] + public void EnsureEndsWith_ShouldPrependChar_GivenEndsWithReturnFalse() + { + const string value = "Hello Worl"; + const char substring = 'd'; + + Assert.AreEqual("Hello World", value.EnsureEndsWith(substring)); + } + + [TestMethod] + public void EnsureEndsWith_ShouldReturnChar_GivenEndsWithReturnTrue() + { + const string value = "A"; + const char substring = 'A'; + + Assert.AreEqual(value, value.EnsureEndsWith(substring)); + } + + [TestMethod] + public void EnsureStartsWith_ShouldPrependChar_GivenEndsWithReturnFalse() + { + const string value = "B"; + const char substring = 'A'; + + Assert.AreEqual("AB", value.EnsureStartsWith(substring)); + } + + [TestMethod] + public void EnsureStartsWith_ShouldReturnChar_GivenEndsWithReturnTrue() + { + const string value = "A"; + const char substring = 'A'; + + Assert.AreEqual(value, value.EnsureStartsWith(substring)); + } + + [TestMethod] + public void EnsureEndsWith_ShouldAppendSubstring_GivenEndsWithReturnFalse() + { + const string value = "Hello "; + const string substring = "World"; + + Assert.AreEqual("Hello World", value.EnsureEndsWith(substring)); + } + + [TestMethod] + public void EnsureEndsWith_ShouldReturnString_GivenEndsWithReturnTrue() + { + const string substring = "World"; + + Assert.AreEqual(substring, substring.EnsureEndsWith(substring)); + } + + [TestMethod] + public void EnsureStartsWith_ShouldAppendSubstring_GivenEndsWithReturnFalse() + { + const string value = "World"; + const string substring = "Hello "; + + Assert.AreEqual("Hello World", value.EnsureStartsWith(substring)); + } + + [TestMethod] + public void EnsureStartsWith_ShouldReturnString_GivenEndsWithReturnTrue() + { + const string substring = "World"; + + Assert.AreEqual(substring, substring.EnsureStartsWith(substring)); + } + [TestMethod] public void EnumParse_ShouldReturnCorrectValue_GivenString() { diff --git a/X10D/src/Text/StringExtensions.cs b/X10D/src/Text/StringExtensions.cs index 23ffd1e..43aed4e 100644 --- a/X10D/src/Text/StringExtensions.cs +++ b/X10D/src/Text/StringExtensions.cs @@ -1,4 +1,4 @@ -using System.Diagnostics.CodeAnalysis; +using System.Diagnostics.CodeAnalysis; using System.Diagnostics.Contracts; using System.Runtime.CompilerServices; using System.Text; @@ -251,6 +251,118 @@ public static class StringExtensions return haystack.AsSpan().CountSubstring(needle, comparison); } + /// + /// Ensures that the current string starts with a specified substring. + /// + /// The string to check. + /// The substring to prepend, if the current string does not already start with it. + /// The combined string. + public static string EnsureEndsWith(this string? value, char substring) + { + return EnsureEndsWith(value, substring, StringComparison.Ordinal); + } + + /// + /// Ensures that the current string starts with a specified substring. + /// + /// The string to check. + /// The substring to prepend, if the current string does not already start with it. + /// One of the enumeration values that determines how the substring is compared. + /// The combined string. + public static string EnsureEndsWith(this string? value, char substring, StringComparison comparisonType) + { + return EnsureEndsWith(value, substring.ToString(), comparisonType); + } + + /// + /// Ensures that the current string starts with a specified substring. + /// + /// The string to check. + /// The substring to prepend, if the current string does not already start with it. + /// The combined string. + public static string EnsureEndsWith(this string? value, string substring) + { + return EnsureEndsWith(value, substring, StringComparison.Ordinal); + } + + /// + /// Ensures that the current string starts with a specified substring. + /// + /// The string to check. + /// The substring to prepend, if the current string does not already start with it. + /// One of the enumeration values that determines how the substring is compared. + /// The combined string. + public static string EnsureEndsWith(this string? value, string substring, StringComparison comparisonType) + { + if (string.IsNullOrEmpty(value)) + { + return substring; + } + + if (value.EndsWith(substring, comparisonType)) + { + return value; + } + + return value + substring; + } + + /// + /// Ensures that the current string starts with a specified substring. + /// + /// The string to check. + /// The substring to prepend, if the current string does not already start with it. + /// The combined string. + public static string EnsureStartsWith(this string? value, char substring) + { + return EnsureStartsWith(value, substring, StringComparison.Ordinal); + } + + /// + /// Ensures that the current string starts with a specified substring. + /// + /// The string to check. + /// The substring to prepend, if the current string does not already start with it. + /// One of the enumeration values that determines how the substring is compared. + /// The combined string. + public static string EnsureStartsWith(this string? value, char substring, StringComparison comparisonType) + { + return EnsureStartsWith(value, substring.ToString(), comparisonType); + } + + /// + /// Ensures that the current string starts with a specified substring. + /// + /// The string to check. + /// The substring to prepend, if the current string does not already start with it. + /// The combined string. + public static string EnsureStartsWith(this string? value, string substring) + { + return EnsureStartsWith(value, substring, StringComparison.Ordinal); + } + + /// + /// Ensures that the current string starts with a specified substring. + /// + /// The string to check. + /// The substring to prepend, if the current string does not already start with it. + /// One of the enumeration values that determines how the substring is compared. + /// The combined string. + public static string EnsureStartsWith(this string? value, string substring, StringComparison comparisonType) + { + if (string.IsNullOrEmpty(value)) + { + return substring; + } + + if (value.StartsWith(substring, comparisonType)) + { + return value; + } + + return substring + value; + } + /// /// Parses a into an . /// @@ -444,6 +556,7 @@ public static class StringExtensions /// /// if all alpha characters in this string are lowercase; otherwise, . /// + /// is . [Pure] #if NETSTANDARD2_1 [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -921,6 +1034,84 @@ public static class StringExtensions } } + /// + /// Determines whether the beginning of this string instance matches any of the specified strings using the current + /// culture for comparison. + /// + /// The value to compare. + /// An array of string to compare. + /// + /// if starts with any of the ; + /// otherwise, . + /// + /// + /// , or at least one of its elements, is . + /// + public static bool StartsWithAny(this string? value, params string[] startValues) + { +#if NET6_0_OR_GREATER + ArgumentNullException.ThrowIfNull(startValues); +#else + if (startValues is null) + { + throw new ArgumentNullException(nameof(startValues)); + } +#endif + + if (startValues.Length == 0 || string.IsNullOrWhiteSpace(value)) + { + return false; + } + + return value.StartsWithAny(StringComparison.CurrentCulture, startValues); + } + + /// + /// Determines whether the beginning of this string instance matches any of the specified strings when compared using the + /// specified comparison option. + /// + /// The value to compare. + /// One of the enumeration values that determines how this string and value are compared. + /// An array of string to compare. + /// + /// if starts with any of the ; + /// otherwise, . + /// + /// + /// , or at least one of its elements, is . + /// + public static bool StartsWithAny(this string? value, StringComparison comparison, params string[] startValues) + { +#if NET6_0_OR_GREATER + ArgumentNullException.ThrowIfNull(startValues); +#else + if (startValues is null) + { + throw new ArgumentNullException(nameof(startValues)); + } +#endif + + if (startValues.Length == 0 || string.IsNullOrWhiteSpace(value)) + { + return false; + } + + for (var index = 0; index < startValues.Length; index++) + { + if (startValues[index] is null) + { + throw new ArgumentNullException(nameof(startValues)); + } + + if (value.StartsWith(startValues[index], comparison)) + { + return true; + } + } + + return false; + } + /// /// Normalizes a string which may be either or empty to a specified alternative. ///