30 Commits
Author SHA1 Message Date
Stone_Red fcc33fd379 Merge pull request #8 from Stone-Red-Code/develop
Develop
2025-11-25 19:25:16 +01:00
Stone_Red f9bbb6c739 Merge branch 'main' into develop 2025-11-25 19:24:38 +01:00
Stone_Red 131eb22264 Merge remote-tracking branch 'origin/develop' into develop 2025-11-25 19:24:06 +01:00
Stone_Red 7dd6a3dca6 Update version 2025-11-25 19:23:58 +01:00
Stone_Red 82301b3685 Merge pull request #7 from Stone-Red-Code/develop
Develop
2025-11-25 19:21:33 +01:00
Stone_Red d47e41d1d5 Merge branch 'main' into develop 2025-11-25 19:20:17 +01:00
Stone_Red b136dda50b Update project to .NET 10 2025-11-25 19:19:41 +01:00
Stone_Red d588b6049b Add & improve string extensions and add tests 2025-11-25 19:19:32 +01:00
Stone_Red 7184b6b15c Improve wait and try utilities 2025-11-25 19:19:02 +01:00
Stone_Red 864604b36c Fix FluentMath namespace 2025-11-25 19:18:12 +01:00
Stone_Red 981ec07ce1 Add XML helper 2025-11-25 19:17:31 +01:00
Stone_Red 066175ed24 Merge pull request #6 from Stone-Red-Code/develop
Develop
2025-05-21 13:13:11 +02:00
Stone_Red 50266440fa Merge branch 'main' into develop 2025-05-21 13:10:42 +02:00
Stone_Red c49e29ac7d Update version 2025-05-21 13:10:20 +02:00
Stone_Red f366290d5d Add missing try tests and fix warnings 2025-05-21 13:08:59 +02:00
Stone_Red d5581cae96 Add rename SafeRetry to Try and add Catch methods 2025-05-21 13:08:44 +02:00
Stone_Red e7e953d6b1 Merge pull request #5 from Stone-Red-Code/develop
Develop
2025-05-17 16:39:23 +02:00
Stone_Red 5f4d77d3b3 Merge branch 'main' into develop 2025-05-17 16:38:44 +02:00
Stone_Red 5291a75b67 Revert Ubuntu version in deploy-nuget.yml 2025-05-17 16:38:09 +02:00
Stone_Red a14d97f578 Merge remote-tracking branch 'origin/develop' into develop 2025-05-17 16:36:51 +02:00
Stone_Red 1e5162eb14 Remove Vsxmd because it causes build errors on linux 2025-05-17 16:36:40 +02:00
Stone_Red c5773eab97 Merge pull request #4 from Stone-Red-Code/develop
Develop
2025-05-17 16:32:56 +02:00
Stone_Red abff6a79fa Change Ubuntu version in deploy-nuget.yml 2025-05-17 16:31:35 +02:00
Stone_Red 536ff7c838 Merge branch 'main' into develop 2025-05-17 16:26:06 +02:00
Stone_Red df93a5cce0 Update libraries and add SafeRetry and WaitUntil utilites 2025-05-17 16:24:15 +02:00
Stone_Red 07ec1bd851 Update System.Reactive.Linq library 2024-07-26 23:26:56 +02:00
Stone_Red 45571d7123 Fix multiple spelling mistakes and inconsistencies 2024-07-26 23:25:08 +02:00
Stone_Red e9d1f26df2 Fix headings in README.md 2024-04-18 09:02:50 +02:00
Stone_Red 61ea228325 Merge pull request #3 from Stone-Red-Code/Stone-Red-Code-patch-2
Update README.md
2024-04-18 09:01:52 +02:00
Stone_Red 462df1833a Update README.md 2024-04-18 09:01:40 +02:00
24 changed files with 1680 additions and 335 deletions
+7
View File
@@ -1,2 +1,9 @@
# CuteUtils # CuteUtils
> A "cute" utility library for C# > A "cute" utility library for C#
## Setup
**Package Manager:** `Install-Package CuteUtils`\
**.NET CLI:** `dotnet add package CuteUtils`
## Wiki
**Reference:** https://github.com/Stone-Red-Code/CuteUtils/wiki/Reference
+8 -5
View File
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net8.0</TargetFramework> <TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
@@ -10,10 +10,13 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="coverlet.collector" Version="6.0.0" /> <PackageReference Include="coverlet.collector" Version="6.0.4">
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" /> <PrivateAssets>all</PrivateAssets>
<PackageReference Include="MSTest.TestAdapter" Version="3.1.1" /> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PackageReference Include="MSTest.TestFramework" Version="3.1.1" /> </PackageReference>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.0.1" />
<PackageReference Include="MSTest.TestAdapter" Version="4.0.2" />
<PackageReference Include="MSTest.TestFramework" Version="4.0.2" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@@ -0,0 +1,8 @@
// This file is used by Code Analysis to maintain SuppressMessage
// attributes that are applied to this project.
// Project-level suppressions either have no target or are given
// a specific target and scoped to a namespace, type, member, etc.
using System.Diagnostics.CodeAnalysis;
[assembly: SuppressMessage("Blocker Code Smell", "S2699:Tests should include assertions", Justification = "<Pending>")]
@@ -3,7 +3,7 @@
namespace CuteUtils.Tests.Misc; namespace CuteUtils.Tests.Misc;
[TestClass] [TestClass]
public class BoolExtentionsTests public class BoolExtensionsTests
{ {
[TestMethod] [TestMethod]
public void OneWayTrue_ShouldSetValueToTrue_WhenInputIsTrue() public void OneWayTrue_ShouldSetValueToTrue_WhenInputIsTrue()
@@ -5,7 +5,7 @@ using System.Text;
namespace CuteUtils.Tests.Misc; namespace CuteUtils.Tests.Misc;
[TestClass] [TestClass]
public class CollectionExtentionsTests public class CollectionExtensionsTests
{ {
private StringBuilder consoleOutput = null!; private StringBuilder consoleOutput = null!;
+161
View File
@@ -0,0 +1,161 @@
using CuteUtils.Misc;
namespace CuteUtils.Tests.Misc;
[TestClass]
public class StringExtTests_Coverage
{
[TestMethod]
public void ToFileName_EmptyAndNull_ReturnsEmptyOrThrows()
{
Assert.AreEqual("", "".ToFileName());
Assert.AreEqual("", "".ToFileName(allowSpaces: false));
string? nullStr = null;
_ = Assert.ThrowsExactly<NullReferenceException>(() => nullStr!.ToFileName());
}
[TestMethod]
public void ToFileName_NormalizesUnicodeAccents()
{
string input = "Ångström Îñţérñåţîöñåļ";
string output = input.ToFileName();
Assert.IsFalse(output.Any(c => c > 127)); // no extended unicode
Assert.Contains("An", output); // Å -> A + n (typical decompose)
}
[TestMethod]
public void ToFileName_LongString_TrimsProperly()
{
string longInput = new('x', 500);
string result = longInput.ToFileName();
Assert.IsLessThanOrEqualTo(255, result.Length, "File name should be trimmed to FS-safe length");
}
[TestMethod]
public void ToPath_EmptyAndNull_ReturnsEmptyOrThrows()
{
Assert.AreEqual("", "".ToPath());
string? nullStr = null;
_ = Assert.ThrowsExactly<NullReferenceException>(() => nullStr!.ToPath());
}
[TestMethod]
public void ToPath_RemovesBothFileAndPathInvalidChars()
{
string input = "t<>e\"s|t/dir\\name:*?";
string output = input.ToPath();
foreach (char c in Path.GetInvalidPathChars())
{
Assert.DoesNotContain(c, output);
}
}
[TestMethod]
public void Truncate_HandlesNull_Throws()
{
string? nullStr = null;
_ = Assert.ThrowsExactly<NullReferenceException>(() => nullStr!.Truncate(5));
}
[TestMethod]
public void Truncate_LongString_Performance()
{
string longStr = new('x', 10_000);
string result = longStr.Truncate(100);
Assert.AreEqual(100, result.Length);
}
[TestMethod]
public void CorrectNewLine_EmptyString_ReturnsEmpty()
{
Assert.AreEqual("", "".CorrectNewLine());
}
[TestMethod]
public void CorrectNewLine_Mixed_Newlines_Normalizes()
{
string mixed = "a\r\nb\nc\rd";
string result = mixed.CorrectNewLine();
if (Environment.OSVersion.Platform == PlatformID.Unix)
{
Assert.AreEqual("a\nb\nc\nd", result);
}
else
{
Assert.AreEqual("a\r\nb\r\nc\r\nd", result);
}
}
[TestMethod]
public void RemoveWhitespaces_UnicodeSpaces()
{
string input = "a\u2003b\u3000c"; // em-space, ideographic space
Assert.AreEqual("abc", input.RemoveWhitespaces());
}
[TestMethod]
public void Reverse_EmptyAndSingleCharacter()
{
Assert.AreEqual("", "".Reverse());
Assert.AreEqual("a", "a".Reverse());
}
[TestMethod]
public void Reverse_UnicodeCombiningCharacters()
{
string input = "e\u0301"; // é composed as e + diacritic
string result = input.Reverse();
Assert.AreEqual("\u0301e", result);
}
[TestMethod]
public void ReplaceCaseInsensitive_UnicodeCase()
{
string input = "Straße"; // Eszett
string replaced = input.ReplaceCaseInsensitive("ß", "ss");
Assert.AreEqual("Strasse", replaced);
}
[TestMethod]
public void ReplaceCaseInsensitive_ReplacesOverlappingCorrectly()
{
string input = "aaaa";
string replaced = input.ReplaceCaseInsensitive("aa", "b");
Assert.AreEqual("bb", replaced); // correct non-overlapping behavior
}
[TestMethod]
public void RemoveDiacritics_RemovesAccents_PreservesBaseCharacters()
{
// composed character
string caf = "café";
Assert.AreEqual("cafe", caf.RemoveDiacritics());
// another composed example (Å -> A or Ang depending on normalization)
string ang = "Ångström";
Assert.AreEqual("Angstrom", ang.RemoveDiacritics());
// decomposed form: e + combining acute accent
string decomposed = "e\u0301"; // e + ◌́
Assert.AreEqual("e", decomposed.RemoveDiacritics());
// empty string stays empty
Assert.AreEqual(string.Empty, string.Empty.RemoveDiacritics());
// null behavior: current implementation will throw NullReferenceException
string? nullStr = null;
_ = Assert.ThrowsExactly<NullReferenceException>(() => nullStr!.RemoveDiacritics());
}
}
+352
View File
@@ -0,0 +1,352 @@
using CuteUtils.Misc;
namespace CuteUtils.Tests.Misc;
[TestClass]
public class TryTests
{
[TestMethod]
public async Task RetryAsyncT_SucceedsFirstTry()
{
int callCount = 0;
int result = await Try.RetryAsync(async () =>
{
callCount++;
await Task.Delay(10);
return 42;
});
Assert.AreEqual(1, callCount);
Assert.AreEqual(42, result);
}
[TestMethod]
public async Task RetryAsyncT_RetriesAndSucceeds()
{
int callCount = 0;
int result = await Try.RetryAsync(async () =>
{
callCount++;
if (callCount < 3)
{
throw new InvalidOperationException();
}
await Task.Delay(10);
return 99;
}, maxRetries: 5, delay: TimeSpan.FromMilliseconds(1));
Assert.AreEqual(3, callCount);
Assert.AreEqual(99, result);
}
[TestMethod]
public async Task RetryAsyncT_ThrowsAfterMaxRetries()
{
int callCount = 0;
_ = await Assert.ThrowsExactlyAsync<InvalidOperationException>(async () =>
{
_ = await Try.RetryAsync<int>(() =>
{
callCount++;
throw new InvalidOperationException("fail");
}, maxRetries: 2, delay: TimeSpan.FromMilliseconds(1));
});
Assert.AreEqual(2, callCount);
}
[TestMethod]
public async Task RetryAsync_SucceedsFirstTry()
{
int callCount = 0;
await Try.RetryAsync(async () =>
{
callCount++;
await Task.Delay(10);
});
Assert.AreEqual(1, callCount);
}
[TestMethod]
public async Task RetryAsync_RetriesAndSucceeds()
{
int callCount = 0;
await Try.RetryAsync(async () =>
{
callCount++;
if (callCount < 2)
{
throw new Exception();
}
await Task.Delay(10);
}, maxRetries: 3, delay: TimeSpan.FromMilliseconds(1));
Assert.AreEqual(2, callCount);
}
[TestMethod]
public async Task RetryAsync_ThrowsAfterMaxRetries()
{
int callCount = 0;
_ = await Assert.ThrowsExactlyAsync<InvalidOperationException>(async () =>
{
await Try.RetryAsync(() =>
{
callCount++;
throw new Exception();
}, maxRetries: 2, delay: TimeSpan.FromMilliseconds(1));
});
Assert.AreEqual(2, callCount);
}
[TestMethod]
public void RetryT_SucceedsFirstTry()
{
int callCount = 0;
int result = Try.Retry(() =>
{
callCount++;
return 7;
});
Assert.AreEqual(1, callCount);
Assert.AreEqual(7, result);
}
[TestMethod]
public void RetryT_RetriesAndSucceeds()
{
int callCount = 0;
int result = Try.Retry(() =>
{
callCount++;
if (callCount < 4)
{
throw new Exception();
}
return 123;
}, maxRetries: 5, delay: TimeSpan.FromMilliseconds(1));
Assert.AreEqual(4, callCount);
Assert.AreEqual(123, result);
}
[TestMethod]
public void RetryT_ThrowsAfterMaxRetries()
{
int callCount = 0;
_ = Assert.ThrowsExactly<InvalidOperationException>(() =>
{
_ = Try.Retry<int>(() =>
{
callCount++;
throw new Exception();
}, maxRetries: 3, delay: TimeSpan.FromMilliseconds(1));
});
Assert.AreEqual(3, callCount);
}
[TestMethod]
public void Retry_SucceedsFirstTry()
{
int callCount = 0;
Try.Retry(() =>
{
callCount++;
});
Assert.AreEqual(1, callCount);
}
[TestMethod]
public void Retry_RetriesAndSucceeds()
{
int callCount = 0;
Try.Retry(() =>
{
callCount++;
if (callCount < 2)
{
throw new Exception();
}
}, maxRetries: 3, delay: TimeSpan.FromMilliseconds(1));
Assert.AreEqual(2, callCount);
}
[TestMethod]
public void Retry_ThrowsAfterMaxRetries()
{
int callCount = 0;
_ = Assert.ThrowsExactly<InvalidOperationException>(() =>
{
Try.Retry(() =>
{
callCount++;
throw new Exception();
}, maxRetries: 2, delay: TimeSpan.FromMilliseconds(1));
});
Assert.AreEqual(2, callCount);
}
[TestMethod]
public async Task RetryAsyncT_ThrowsArgumentNullException()
{
_ = await Assert.ThrowsExactlyAsync<ArgumentNullException>(async () =>
{
_ = await Try.RetryAsync<int>(null!);
});
}
[TestMethod]
public async Task RetryAsync_ThrowsArgumentNullException()
{
_ = await Assert.ThrowsExactlyAsync<ArgumentNullException>(async () =>
{
await Try.RetryAsync(null!);
});
}
[TestMethod]
public void RetryT_ThrowsArgumentNullException()
{
_ = Assert.ThrowsExactly<ArgumentNullException>(() =>
{
_ = Try.Retry<int>(null!);
});
}
[TestMethod]
public void Retry_ThrowsArgumentNullException()
{
_ = Assert.ThrowsExactly<ArgumentNullException>(() =>
{
Try.Retry(null!);
});
}
[TestClass]
public class TryCatchTests
{
[TestMethod]
public void Catch_Action_NoException()
{
bool executed = false;
_ = Try.Catch(() => executed = true);
Assert.IsTrue(executed);
}
[TestMethod]
public void Catch_Action_ExceptionHandled()
{
Try.Catch(() => throw new InvalidOperationException());
}
[TestMethod]
public void Catch_Action_OutParam_ExceptionHandled()
{
Try.Catch(() => throw new InvalidOperationException(), out Exception? exception);
Assert.IsNotNull(exception);
Assert.IsInstanceOfType(exception, typeof(InvalidOperationException));
}
[TestMethod]
public void Catch_TFunc_NoException()
{
int result = Try.Catch(() => 42)!;
Assert.AreEqual(42, result);
}
[TestMethod]
public void Catch_TFunc_ExceptionHandled_ReturnsDefault()
{
int? result = Try.Catch<int>(() => throw new InvalidOperationException());
Assert.AreEqual(default(int), result);
}
[TestMethod]
public void Catch_TFunc_OutParam_ExceptionHandled()
{
int? result = Try.Catch<int?>(() => throw new InvalidOperationException(), out Exception? exception);
Assert.IsNull(result);
Assert.IsNotNull(exception);
}
[TestMethod]
public async Task CatchAsync_Delegate_NoException()
{
bool executed = false;
await Try.CatchAsync(async () =>
{
executed = true;
await Task.Delay(10);
});
Assert.IsTrue(executed);
}
[TestMethod]
public async Task CatchAsync_Delegate_ExceptionHandled()
{
await Try.CatchAsync(async () =>
{
await Task.Delay(10);
throw new InvalidOperationException();
});
}
[TestMethod]
public async Task CatchAsync_TFunc_NoException()
{
int result = await Try.CatchAsync(async () =>
{
await Task.Delay(10);
return 99;
});
Assert.AreEqual(99, result);
}
[TestMethod]
public async Task CatchAsync_TFunc_ExceptionHandled_ReturnsDefault()
{
int result = await Try.CatchAsync<int>(async () =>
{
await Task.Delay(10);
throw new InvalidOperationException();
});
Assert.AreEqual(default, result);
}
[TestMethod]
public async Task CatchAsync_Task_NoException()
{
await Try.CatchAsync(Task.Delay(10));
}
[TestMethod]
public async Task CatchAsync_Task_ExceptionHandled()
{
await Try.CatchAsync(Task.Run(() => throw new InvalidOperationException()));
}
[TestMethod]
public async Task CatchAsync_TaskT_NoException()
{
string? result = await Try.CatchAsync(Task.FromResult("Hello"));
Assert.AreEqual("Hello", result);
}
[TestMethod]
public async Task CatchAsync_TaskT_ExceptionHandled_ReturnsDefault()
{
string? result = await Try.CatchAsync<string>(Task.Run<string>(() => ""[0].ToString()));
Assert.IsNull(result);
}
}
}
+161
View File
@@ -0,0 +1,161 @@
using CuteUtils.Misc;
namespace CuteUtils.Tests.Misc;
[TestClass]
public class WaitTests
{
[TestMethod]
public async Task UntilAsync_ConditionMet_CompletesSuccessfully()
{
bool flag = false;
_ = Task.Run(async () =>
{
await Task.Delay(200);
flag = true;
});
await Wait.UntilAsync(() => flag, TimeSpan.FromMilliseconds(50), TimeSpan.FromSeconds(2));
Assert.IsTrue(flag);
}
[TestMethod]
public async Task UntilAsync_ConditionNotMet_ThrowsTimeoutException()
{
_ = await Assert.ThrowsExactlyAsync<TimeoutException>(async () =>
{
await Wait.UntilAsync(() => false, TimeSpan.FromMilliseconds(50), TimeSpan.FromMilliseconds(200));
});
}
[TestMethod]
public void Until_ConditionMet_CompletesSuccessfully()
{
bool flag = false;
_ = Task.Run(async () =>
{
await Task.Delay(200);
flag = true;
});
Wait.Until(() => flag, TimeSpan.FromMilliseconds(50));
Assert.IsTrue(flag);
}
[TestMethod]
public void Until_ConditionNotMet_ThrowsTimeoutException()
{
_ = Assert.ThrowsExactly<TimeoutException>(() =>
{
Wait.Until(() => false, TimeSpan.FromMilliseconds(50), TimeSpan.FromMilliseconds(200));
});
}
[TestMethod]
public async Task WaitForEventAsync_EventRaised_CompletesSuccessfully()
{
void subscribe(Action handler)
{
_ = Task.Run(async () =>
{
await Task.Delay(200);
handler();
});
}
await Wait.WaitForEventAsync(subscribe, TimeSpan.FromSeconds(2));
}
[TestMethod]
public async Task WaitForEventAsync_EventNotRaised_ThrowsTimeoutException()
{
void subscribe(Action handler)
{
// Intentionally left empty for negative test case (event never raised)
}
_ = await Assert.ThrowsExactlyAsync<TimeoutException>(async () =>
{
await Wait.WaitForEventAsync(subscribe, TimeSpan.FromMilliseconds(200));
});
}
[TestMethod]
public async Task WaitForEventAsyncT_EventRaised_CompletesWithValue()
{
void subscribe(Action<int> handler)
{
_ = Task.Run(async () =>
{
await Task.Delay(200);
handler(42);
});
}
int result = await Wait.ForEventAsync((Action<Action<int>>)subscribe, TimeSpan.FromSeconds(2));
Assert.AreEqual(42, result);
}
[TestMethod]
public async Task WaitForEventAsyncT_EventNotRaised_ThrowsTimeoutException()
{
void subscribe(Action<int> handler)
{
// Intentionally left empty for negative test case (event never raised)
}
_ = await Assert.ThrowsExactlyAsync<TimeoutException>(async () =>
{
_ = await Wait.ForEventAsync((Action<Action<int>>)subscribe, TimeSpan.FromMilliseconds(200));
});
}
[TestMethod]
public void WaitForEvent_EventRaised_CompletesSuccessfully()
{
void subscribe(Action handler)
{
_ = Task.Run(async () =>
{
await Task.Delay(200);
handler();
});
}
Wait.ForEvent(subscribe, TimeSpan.FromSeconds(2));
}
[TestMethod]
public void WaitForEvent_EventNotRaised_ThrowsTimeoutException()
{
void subscribe(Action handler)
{
// Intentionally left empty for negative test case (event never raised)
}
_ = Assert.ThrowsExactly<TimeoutException>(() =>
{
Wait.ForEvent(subscribe, TimeSpan.FromMilliseconds(200));
});
}
[TestMethod]
public void WaitForEventT_EventRaised_CompletesWithValue()
{
void subscribe(Action<string> handler)
{
_ = Task.Run(async () =>
{
await Task.Delay(200);
handler("hello");
});
}
string result = Wait.ForEvent((Action<Action<string>>)subscribe, TimeSpan.FromSeconds(2));
Assert.AreEqual("hello", result);
}
[TestMethod]
public void WaitForEventT_EventNotRaised_ThrowsTimeoutException()
{
void subscribe(Action<string> handler)
{
// Intentionally left empty for negative test case (event never raised)
}
_ = Assert.ThrowsExactly<TimeoutException>(() =>
{
_ = Wait.ForEvent((Action<Action<string>>)subscribe, TimeSpan.FromMilliseconds(200));
});
}
}
@@ -0,0 +1,69 @@
using CuteUtils.Misc;
namespace CuteUtils.Tests.Misc;
[TestClass]
public class XmlHelperTests
{
public class TestModel
{
public string? Name { get; set; }
public int Age { get; set; }
public NestedModel? Child { get; set; }
}
public class NestedModel
{
public string? Value { get; set; }
}
[TestMethod]
public void SerializeDeserialize_RoundTrip_RetainsData()
{
TestModel model = new TestModel
{
Name = "Alice",
Age = 30,
Child = new NestedModel { Value = "Inner" }
};
string xml = XmlHelper.Serialize(model);
Assert.IsFalse(string.IsNullOrWhiteSpace(xml), "Serialized XML should not be empty.");
Assert.Contains("Alice", xml, "XML should contain the Name value.");
Assert.Contains("30", xml, "XML should contain the Age value.");
Assert.Contains("Inner", xml, "XML should contain the Nested Value.");
TestModel? deserialized = XmlHelper.Deserialize<TestModel>(xml);
Assert.IsNotNull(deserialized, "Deserialized object should not be null.");
Assert.AreEqual(model.Name, deserialized!.Name);
Assert.AreEqual(model.Age, deserialized.Age);
Assert.AreEqual(model.Child?.Value, deserialized.Child?.Value);
}
[TestMethod]
public void Serialize_OmitsNamespaceDeclarations()
{
TestModel model = new TestModel { Name = "Bob", Age = 25 };
string xml = XmlHelper.Serialize(model);
// Expect no namespace attributes like xmlns or common schema prefixes
Assert.DoesNotContain("xmlns", xml, "XML should not contain namespace declarations.");
Assert.DoesNotContain("xsi:", xml, "XML should not contain xsi prefix.");
Assert.DoesNotContain("xsd:", xml, "XML should not contain xsd prefix.");
}
[TestMethod]
public void Deserialize_NullXml_ThrowsArgumentNullException()
{
_ = Assert.ThrowsExactly<ArgumentNullException>(() => XmlHelper.Deserialize<TestModel>(null!));
}
[TestMethod]
public void GetXmlSchema_ReturnsSchemaContainingTypeName()
{
string schema = XmlHelper.GetXmlSchema<TestModel>();
Assert.IsFalse(string.IsNullOrWhiteSpace(schema), "Schema should not be empty.");
Assert.IsGreaterThanOrEqualTo(0, schema.IndexOf("schema", StringComparison.OrdinalIgnoreCase), "Schema should contain 'schema' element.");
Assert.Contains("TestModel", schema, "Schema should reference the TestModel type.");
}
}
+3 -7
View File
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net8.0</TargetFramework> <TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<GeneratePackageOnBuild>True</GeneratePackageOnBuild> <GeneratePackageOnBuild>True</GeneratePackageOnBuild>
@@ -29,11 +29,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="System.Reactive.Linq" Version="6.0.0" /> <PackageReference Include="System.Reactive.Linq" Version="6.1.0" />
<PackageReference Include="Vsxmd" Version="1.4.5">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup> </ItemGroup>
<!-- <!--
@@ -44,7 +40,7 @@
--> -->
<PropertyGroup> <PropertyGroup>
<UserTempFolder>$([System.IO.Path]::GetTempPath())</UserTempFolder> <UserTempFolder>$([System.IO.Path]::GetTempPath())</UserTempFolder>
<Version>1.0.0.0</Version> <Version>2.0.0.0</Version>
<Description>A "cute" utility library for C#</Description> <Description>A "cute" utility library for C#</Description>
<RepositoryUrl>https://github.com/Stone-Red-Code/CuteUtils</RepositoryUrl> <RepositoryUrl>https://github.com/Stone-Red-Code/CuteUtils</RepositoryUrl>
<PackageTags>Utility, Helper</PackageTags> <PackageTags>Utility, Helper</PackageTags>
@@ -1,4 +1,4 @@
namespace CuteUtils.FluentMath.TypeExtentions; namespace CuteUtils.FluentMath.TypeExtensions;
/// <summary> /// <summary>
/// DecimalFluent class /// DecimalFluent class
@@ -56,7 +56,7 @@ public static class DecimalFluent
} }
/// <summary> /// <summary>
/// Adds the two nums /// Adds the two numbers
/// </summary> /// </summary>
/// <param name="num"></param> /// <param name="num"></param>
/// <param name="value"></param> /// <param name="value"></param>
@@ -67,7 +67,7 @@ public static class DecimalFluent
} }
/// <summary> /// <summary>
/// Subtracts the two nums /// Subtracts the two numbers
/// </summary> /// </summary>
/// <param name="num"></param> /// <param name="num"></param>
/// <param name="value"></param> /// <param name="value"></param>
@@ -78,7 +78,7 @@ public static class DecimalFluent
} }
/// <summary> /// <summary>
/// Multiples the two nums /// Multiples the two numbers
/// </summary> /// </summary>
/// <param name="num"></param> /// <param name="num"></param>
/// <param name="value"></param> /// <param name="value"></param>
@@ -89,7 +89,7 @@ public static class DecimalFluent
} }
/// <summary> /// <summary>
/// Divides the two nums /// Divides the two numbers
/// </summary> /// </summary>
/// <param name="num"></param> /// <param name="num"></param>
/// <param name="value"></param> /// <param name="value"></param>
@@ -136,15 +136,15 @@ public static class DecimalFluent
} }
/// <inheritdoc cref="Math.Round(decimal,int)"/> /// <inheritdoc cref="Math.Round(decimal,int)"/>
public static decimal Round(this decimal num, int digits) public static decimal Round(this decimal num, int decimals)
{ {
return Math.Round(num, digits); return Math.Round(num, decimals);
} }
/// <inheritdoc cref="Math.Round(decimal,int,MidpointRounding)"/> /// <inheritdoc cref="Math.Round(decimal,int,MidpointRounding)"/>
public static decimal Round(this decimal num, int digits, MidpointRounding mode) public static decimal Round(this decimal num, int decimals, MidpointRounding mode)
{ {
return Math.Round(num, digits, mode); return Math.Round(num, decimals, mode);
} }
/// <inheritdoc cref="Math.Sign(decimal)"/> /// <inheritdoc cref="Math.Sign(decimal)"/>
@@ -1,4 +1,4 @@
namespace CuteUtils.FluentMath.TypeExtentions; namespace CuteUtils.FluentMath.TypeExtensions;
/// <summary> /// <summary>
/// DoubleFluent class /// DoubleFluent class
@@ -190,9 +190,9 @@ public static class DoubleFluent
} }
/// <inheritdoc cref="Math.IEEERemainder(double,double)"/> /// <inheritdoc cref="Math.IEEERemainder(double,double)"/>
public static double IEEERemainder(this double num, double valuee) public static double IEEERemainder(this double num, double value)
{ {
return Math.IEEERemainder(num, valuee); return Math.IEEERemainder(num, value);
} }
/// <inheritdoc cref="Math.Log(double)"/> /// <inheritdoc cref="Math.Log(double)"/>
@@ -1,4 +1,4 @@
namespace CuteUtils.FluentMath.TypeExtentions; namespace CuteUtils.FluentMath.TypeExtensions;
/// <summary> /// <summary>
/// IntegerFluent class /// IntegerFluent class
@@ -56,7 +56,7 @@ public static class Int16Fluent
} }
/// <summary> /// <summary>
/// Adds the two nums /// Adds the two numbers
/// </summary> /// </summary>
/// <param name="num"></param> /// <param name="num"></param>
/// <param name="value"></param> /// <param name="value"></param>
@@ -67,7 +67,7 @@ public static class Int16Fluent
} }
/// <summary> /// <summary>
/// Subtracts the two nums /// Subtracts the two numbers
/// </summary> /// </summary>
/// <param name="num"></param> /// <param name="num"></param>
/// <param name="value"></param> /// <param name="value"></param>
@@ -78,7 +78,7 @@ public static class Int16Fluent
} }
/// <summary> /// <summary>
/// Multiples the two nums /// Multiples the two numbers
/// </summary> /// </summary>
/// <param name="num"></param> /// <param name="num"></param>
/// <param name="value"></param> /// <param name="value"></param>
@@ -89,7 +89,7 @@ public static class Int16Fluent
} }
/// <summary> /// <summary>
/// Divides the two nums /// Divides the two numbers
/// </summary> /// </summary>
/// <param name="num"></param> /// <param name="num"></param>
/// <param name="value"></param> /// <param name="value"></param>
@@ -1,4 +1,4 @@
namespace CuteUtils.FluentMath.TypeExtentions; namespace CuteUtils.FluentMath.TypeExtensions;
/// <summary> /// <summary>
/// IntegerFluent class /// IntegerFluent class
@@ -56,7 +56,7 @@ public static class Int32Fluent
} }
/// <summary> /// <summary>
/// Adds the two nums /// Adds the two numbers
/// </summary> /// </summary>
/// <param name="num"></param> /// <param name="num"></param>
/// <param name="value"></param> /// <param name="value"></param>
@@ -67,7 +67,7 @@ public static class Int32Fluent
} }
/// <summary> /// <summary>
/// Subtracts the two nums /// Subtracts the two numbers
/// </summary> /// </summary>
/// <param name="num"></param> /// <param name="num"></param>
/// <param name="value"></param> /// <param name="value"></param>
@@ -78,7 +78,7 @@ public static class Int32Fluent
} }
/// <summary> /// <summary>
/// Multiples the two nums /// Multiples the two numbers
/// </summary> /// </summary>
/// <param name="num"></param> /// <param name="num"></param>
/// <param name="value"></param> /// <param name="value"></param>
@@ -89,7 +89,7 @@ public static class Int32Fluent
} }
/// <summary> /// <summary>
/// Divides the two nums /// Divides the two numbers
/// </summary> /// </summary>
/// <param name="num"></param> /// <param name="num"></param>
/// <param name="value"></param> /// <param name="value"></param>
@@ -1,4 +1,4 @@
namespace CuteUtils.FluentMath.TypeExtentions; namespace CuteUtils.FluentMath.TypeExtensions;
/// <summary> /// <summary>
/// IntegerFluent class /// IntegerFluent class
@@ -56,7 +56,7 @@ public static class Int64Fluent
} }
/// <summary> /// <summary>
/// Adds the two nums /// Adds the two numbers
/// </summary> /// </summary>
/// <param name="num"></param> /// <param name="num"></param>
/// <param name="value"></param> /// <param name="value"></param>
@@ -67,7 +67,7 @@ public static class Int64Fluent
} }
/// <summary> /// <summary>
/// Subtracts the two nums /// Subtracts the two numbers
/// </summary> /// </summary>
/// <param name="num"></param> /// <param name="num"></param>
/// <param name="value"></param> /// <param name="value"></param>
@@ -78,7 +78,7 @@ public static class Int64Fluent
} }
/// <summary> /// <summary>
/// Multiples the two nums /// Multiples the two numbers
/// </summary> /// </summary>
/// <param name="num"></param> /// <param name="num"></param>
/// <param name="value"></param> /// <param name="value"></param>
@@ -89,7 +89,7 @@ public static class Int64Fluent
} }
/// <summary> /// <summary>
/// Divides the two nums /// Divides the two numbers
/// </summary> /// </summary>
/// <param name="num"></param> /// <param name="num"></param>
/// <param name="value"></param> /// <param name="value"></param>
@@ -1,4 +1,4 @@
namespace CuteUtils.FluentMath.TypeExtentions; namespace CuteUtils.FluentMath.TypeExtensions;
/// <summary> /// <summary>
/// FloatFluent class /// FloatFluent class
@@ -56,7 +56,7 @@ public static class SingleFluent
} }
/// <summary> /// <summary>
/// Adds the two nums /// Adds the two numbers
/// </summary> /// </summary>
/// <param name="num"></param> /// <param name="num"></param>
/// <param name="value"></param> /// <param name="value"></param>
@@ -67,7 +67,7 @@ public static class SingleFluent
} }
/// <summary> /// <summary>
/// Subtracts the two nums /// Subtracts the two numbers
/// </summary> /// </summary>
/// <param name="num"></param> /// <param name="num"></param>
/// <param name="value"></param> /// <param name="value"></param>
@@ -78,7 +78,7 @@ public static class SingleFluent
} }
/// <summary> /// <summary>
/// Multiples the two nums /// Multiples the two numbers
/// </summary> /// </summary>
/// <param name="num"></param> /// <param name="num"></param>
/// <param name="value"></param> /// <param name="value"></param>
@@ -89,7 +89,7 @@ public static class SingleFluent
} }
/// <summary> /// <summary>
/// Divides the two nums /// Divides the two numbers
/// </summary> /// </summary>
/// <param name="num"></param> /// <param name="num"></param>
/// <param name="value"></param> /// <param name="value"></param>
+2 -2
View File
@@ -19,7 +19,7 @@ public class LogFormatBuilder
/// <summary> /// <summary>
/// Creates a new <see cref="LogFormatBuilder"/> instance. /// Creates a new <see cref="LogFormatBuilder"/> instance.
/// </summary> /// </summary>
/// <param name="value">The inital format.</param> /// <param name="value">The initial format.</param>
public LogFormatBuilder(string value) public LogFormatBuilder(string value)
{ {
_ = stringBuilder.Append(value); _ = stringBuilder.Append(value);
@@ -55,7 +55,7 @@ public class LogFormatBuilder
} }
/// <summary> /// <summary>
/// Appends the log datie time to the log format. /// Appends the log date time to the log format.
/// </summary> /// </summary>
/// <param name="format">The format to apply.</param> /// <param name="format">The format to apply.</param>
/// <param name="padding">The padding to apply.</param> /// <param name="padding">The padding to apply.</param>
+132 -117
View File
@@ -10,138 +10,153 @@ namespace CuteUtils.Misc;
/// </summary> /// </summary>
public static class ConsoleExt public static class ConsoleExt
{ {
/// <summary> extension(Console)
/// Writes the specified value to the console with the specified color.
/// </summary>
/// <param name="value">The value to write.</param>
/// <param name="color">The color of the text.</param>
public static void Write(object value, ConsoleColor color)
{ {
lock (Console.Out) /// <summary>
/// Writes the specified value to the console with the specified color.
/// </summary>
/// <param name="value">The value to write.</param>
/// <param name="color">The color of the text.</param>
public static void Write(object value, ConsoleColor color)
{ {
ConsoleColor oldColor = Console.ForegroundColor; lock (Console.Out)
Console.ForegroundColor = color; {
Console.Write(value); ConsoleColor oldColor = Console.ForegroundColor;
Console.ForegroundColor = oldColor; Console.ForegroundColor = color;
Console.Write(value);
Console.ForegroundColor = oldColor;
}
} }
}
/// <summary> /// <inheritdoc cref="Write(object, ConsoleColor)"/>
/// Writes the specified value to the console with the specified color and appends a new line. public static void WriteColor(object value, ConsoleColor color)
/// </summary>
/// <param name="value">The value to write.</param>
/// <param name="color">The color of the text.</param>
public static void WriteLine(object value, ConsoleColor color)
{
lock (Console.Out)
{ {
ConsoleColor oldColor = Console.ForegroundColor; Write(value, color);
Console.ForegroundColor = color;
Console.WriteLine(value);
Console.ForegroundColor = oldColor;
} }
}
/// <summary> /// <summary>
/// Reads the next line of characters from the standard input stream and tries to convert it to the specified type. /// Writes the specified value to the console with the specified color and appends a new line.
/// </summary> /// </summary>
/// <typeparam name="T">The type to convert the input string to.</typeparam> /// <param name="value">The value to write.</param>
/// <returns>The input string converted to the specified type.</returns> /// <param name="color">The color of the text.</param>
/// <exception cref="NotSupportedException">Thrown if the conversion is not supported.</exception> public static void WriteLine(object value, ConsoleColor color)
public static T ReadLine<T>()
{
string attemptedValue = Console.ReadLine() ?? string.Empty;
Type type = typeof(T);
TypeConverter converter = TypeDescriptor.GetConverter(type);
return (T)converter.ConvertFromString(attemptedValue)!;
}
/// <summary>
/// Reads the next line of characters from the standard input stream and tries to convert it to the specified type.
/// </summary>
/// <typeparam name="T">The type to convert the input string to.</typeparam>
/// <param name="input">The input string converted to the specified type.</param>
/// <returns><see langword="true"/> if the conversion was successful. Otherwise <see langword="false"/>.</returns>
public static bool TryReadLine<T>([NotNullWhen(true)] out T? input)
{
string attemptedValue = Console.ReadLine() ?? string.Empty;
Type type = typeof(T);
TypeConverter converter = TypeDescriptor.GetConverter(type);
if (converter != null && converter.IsValid(attemptedValue))
{ {
input = (T)converter.ConvertFromString(attemptedValue)!; lock (Console.Out)
return true; {
ConsoleColor oldColor = Console.ForegroundColor;
Console.ForegroundColor = color;
Console.WriteLine(value);
Console.ForegroundColor = oldColor;
}
} }
else
/// <inheritdoc cref="WriteLine(object, ConsoleColor)"/>
public static void WriteLineColor(object value, ConsoleColor color)
{ {
input = default; WriteLine(value, color);
return false;
} }
}
/// <summary> /// <summary>
/// Obtains the next character or function key pressed by the user and converts it to the specified type. /// Reads the next line of characters from the standard input stream and tries to convert it to the specified type.
/// The pressed key is displayed in the console window. /// </summary>
/// </summary> /// <typeparam name="T">The type to convert the input string to.</typeparam>
/// <typeparam name="T">The type to convert the input character to.</typeparam> /// <returns>The input string converted to the specified type.</returns>
/// <returns>The input character converted to the specified type.</returns> /// <exception cref="NotSupportedException">Thrown if the conversion is not supported.</exception>
/// <exception cref="NotSupportedException">Thrown if the conversion is not supported.</exception> public static T ReadLine<T>()
public static T ReadKey<T>()
{
string attemptedValue = Console.ReadKey().KeyChar.ToString();
Type type = typeof(T);
TypeConverter converter = TypeDescriptor.GetConverter(type);
return (T)converter.ConvertFromString(attemptedValue)!;
}
/// <summary>
/// Obtains the next character or function key pressed by the user and tries to convert it to the specified type.
/// The pressed key is displayed in the console window.
/// </summary>
/// <param name="input">The input character converted to the specified type.</param>
/// <typeparam name="T">The type to convert the input character to.</typeparam>
/// <returns><see langword="true"/> if the conversion was successful. Otherwise <see langword="false"/>.</returns>
public static bool TryReadKey<T>([NotNullWhen(true)] out T? input)
{
string attemptedValue = Console.ReadKey().KeyChar.ToString();
Type type = typeof(T);
TypeConverter converter = TypeDescriptor.GetConverter(type);
if (converter != null && converter.IsValid(attemptedValue))
{ {
input = (T)converter.ConvertFromString(attemptedValue)!; string attemptedValue = Console.ReadLine() ?? string.Empty;
return true; Type type = typeof(T);
} TypeConverter converter = TypeDescriptor.GetConverter(type);
else
{
input = default;
return false;
}
}
/// <summary> return (T)converter.ConvertFromString(attemptedValue)!;
/// Suspends execution of the current method until the user presses a key.
/// </summary>
/// <param name="key">The key that has to be pressed.</param>
/// <param name="message">The message that will be displayed.</param>
public static void Pause(ConsoleKey key, string? message = null)
{
Console.WriteLine(message ?? $"Press {key} to continue...");
ConsoleKey? consoleKey = null;
while (consoleKey != key)
{
consoleKey = Console.ReadKey(true).Key;
} }
}
/// <summary> /// <summary>
/// Suspends execution of the current method until the user presses a key. /// Reads the next line of characters from the standard input stream and tries to convert it to the specified type.
/// </summary> /// </summary>
/// <param name="message">The message that will be displayed.</param> /// <typeparam name="T">The type to convert the input string to.</typeparam>
public static void Pause(string message = "Press any key to continue...") /// <param name="input">The input string converted to the specified type.</param>
{ /// <returns><see langword="true"/> if the conversion was successful. Otherwise <see langword="false"/>.</returns>
Console.WriteLine(message); public static bool TryReadLine<T>([NotNullWhen(true)] out T? input)
_ = Console.ReadKey(true); {
string attemptedValue = Console.ReadLine() ?? string.Empty;
Type type = typeof(T);
TypeConverter converter = TypeDescriptor.GetConverter(type);
if (converter != null && converter.IsValid(attemptedValue))
{
input = (T)converter.ConvertFromString(attemptedValue)!;
return true;
}
else
{
input = default;
return false;
}
}
/// <summary>
/// Obtains the next character or function key pressed by the user and converts it to the specified type.
/// The pressed key is displayed in the console window.
/// </summary>
/// <typeparam name="T">The type to convert the input character to.</typeparam>
/// <returns>The input character converted to the specified type.</returns>
/// <exception cref="NotSupportedException">Thrown if the conversion is not supported.</exception>
public static T ReadKey<T>()
{
string attemptedValue = Console.ReadKey().KeyChar.ToString();
Type type = typeof(T);
TypeConverter converter = TypeDescriptor.GetConverter(type);
return (T)converter.ConvertFromString(attemptedValue)!;
}
/// <summary>
/// Obtains the next character or function key pressed by the user and tries to convert it to the specified type.
/// The pressed key is displayed in the console window.
/// </summary>
/// <param name="input">The input character converted to the specified type.</param>
/// <typeparam name="T">The type to convert the input character to.</typeparam>
/// <returns><see langword="true"/> if the conversion was successful. Otherwise <see langword="false"/>.</returns>
public static bool TryReadKey<T>([NotNullWhen(true)] out T? input)
{
string attemptedValue = Console.ReadKey().KeyChar.ToString();
Type type = typeof(T);
TypeConverter converter = TypeDescriptor.GetConverter(type);
if (converter != null && converter.IsValid(attemptedValue))
{
input = (T)converter.ConvertFromString(attemptedValue)!;
return true;
}
else
{
input = default;
return false;
}
}
/// <summary>
/// Suspends execution of the current method until the user presses a key.
/// </summary>
/// <param name="key">The key that has to be pressed.</param>
/// <param name="message">The message that will be displayed.</param>
public static void Pause(ConsoleKey key, string? message = null)
{
Console.WriteLine(message ?? $"Press {key} to continue...");
ConsoleKey? consoleKey = null;
while (consoleKey != key)
{
consoleKey = Console.ReadKey(true).Key;
}
}
/// <summary>
/// Suspends execution of the current method until the user presses a key.
/// </summary>
/// <param name="message">The message that will be displayed.</param>
public static void Pause(string message = "Press any key to continue...")
{
Console.WriteLine(message);
_ = Console.ReadKey(true);
}
} }
} }
+172
View File
@@ -0,0 +1,172 @@
using System.Globalization;
using System.Text;
using System.Text.RegularExpressions;
namespace CuteUtils.Misc;
/// <summary>
/// Provides extension methods for working with <see cref="string"/> values.
/// </summary>
public static class StringExt
{
internal static string Sanitize(string str, char[] invalidChars, bool allowSpaces)
{
if (!allowSpaces)
{
str = str.Replace(" ", string.Empty);
}
foreach (char c in invalidChars)
{
str = str.Replace(c.ToString(), string.Empty);
}
return str.RemoveDiacritics();
}
/// <summary>
/// Converts the string into a valid file name by removing invalid characters
/// and optionally removing spaces. Diacritics (accents) are also removed.
/// </summary>
/// <param name="str">The input string.</param>
/// <param name="allowSpaces">Whether spaces should be preserved.</param>
/// <returns>A sanitized string that is safe to use as a file name.</returns>
public static string ToFileName(this string str, bool allowSpaces = false)
{
string path = Sanitize(str, Path.GetInvalidFileNameChars(), allowSpaces);
if (path.Length > 255)
{
path = path[..255];
}
return path;
}
/// <summary>
/// Converts the string into a valid file system path segment by removing invalid characters
/// and optionally removing spaces. Diacritics (accents) are also removed.
/// </summary>
/// <param name="str">The input string.</param>
/// <param name="allowSpaces">Whether spaces should be preserved.</param>
/// <returns>A sanitized string that is safe to use as a path segment.</returns>
public static string ToPath(this string str, bool allowSpaces = false)
{
return Sanitize(str, Path.GetInvalidPathChars(), allowSpaces);
}
/// <summary>
/// Returns a truncated version of the string with a maximum length.
/// </summary>
/// <param name="str">The input string.</param>
/// <param name="length">The maximum allowed length.</param>
/// <returns>
/// The truncated string if the input exceeds <paramref name="length"/>;
/// otherwise, the original string.
/// </returns>
public static string Truncate(this string str, int length)
{
return (length > 0 && str.Length > length) ? str[..length] : str;
}
/// <summary>
/// Returns a truncated version of the string with a maximum length,
/// optionally appending an ellipsis ("...") if the string is shortened.
/// </summary>
/// <param name="str">The input string.</param>
/// <param name="length">The maximum allowed length.</param>
/// <param name="ellipsis">Whether to append "..." when truncated.</param>
/// <returns>The truncated string, with optional ellipsis.</returns>
public static string Truncate(this string str, int length, bool ellipsis)
{
if (length <= 0 || str.Length <= length)
{
return str;
}
if (ellipsis && length > 3)
{
return $"{str[..(length - 3)]}...";
}
return str[..length];
}
/// <summary>
/// Normalizes newline characters in the string to the current environment's newline format.
/// </summary>
/// <param name="str">The input string.</param>
/// <returns>
/// A string where all newline sequences are converted to
/// <see cref="Environment.NewLine"/>.
/// </returns>
/// <remarks>
/// This method safely normalizes mixed newline styles (CR, LF, CRLF).
/// </remarks>
public static string CorrectNewLine(this string str)
{
return str
.Replace("\r\n", "\n")
.Replace("\r", "\n")
.Replace("\n", Environment.NewLine);
}
/// <summary>
/// Removes all whitespace characters from the string.
/// </summary>
/// <param name="str">The input string.</param>
/// <returns>The string with all whitespace removed.</returns>
public static string RemoveWhitespaces(this string str)
{
return new string(str.Where(c => !char.IsWhiteSpace(c)).ToArray());
}
/// <summary>
/// Returns a new string with the characters reversed.
/// </summary>
/// <param name="str">The input string.</param>
/// <returns>The reversed string.</returns>
public static string Reverse(this string str)
{
char[] arr = str.ToCharArray();
Array.Reverse(arr);
return new string(arr);
}
/// <summary>
/// Replaces all occurrences of a substring with another string,
/// using a case-insensitive comparison.
/// </summary>
/// <param name="input">The input string.</param>
/// <param name="search">The substring to search for.</param>
/// <param name="replacement">The replacement text.</param>
/// <returns>The modified string.</returns>
public static string ReplaceCaseInsensitive(this string input, string search, string replacement)
{
return Regex.Replace(input, Regex.Escape(search), replacement.Replace("$", "$$"), RegexOptions.IgnoreCase);
}
/// <summary>
/// Removes diacritic marks (accents) from characters in the string.
/// </summary>
/// <param name="str">The input string.</param>
/// <returns>The string with diacritics removed.</returns>
/// <remarks>
/// This is useful for normalization and for generating file-safe names.
/// </remarks>
public static string RemoveDiacritics(this string str)
{
string normalized = str.Normalize(NormalizationForm.FormD);
StringBuilder sb = new StringBuilder(normalized.Length);
foreach (char c in normalized)
{
if (CharUnicodeInfo.GetUnicodeCategory(c) != UnicodeCategory.NonSpacingMark)
{
_ = sb.Append(c);
}
}
return sb.ToString().Normalize(NormalizationForm.FormC);
}
}
+283
View File
@@ -0,0 +1,283 @@
using System.Diagnostics;
namespace CuteUtils.Misc;
/// <summary>
/// Provides methods for safely retrying actions and functions with optional delay and retry count.
/// </summary>
public static class Try
{
/// <summary>
/// Retries an asynchronous function returning a value, up to a specified number of times, with an optional delay between attempts.
/// </summary>
/// <typeparam name="T">The type of the result returned by the function.</typeparam>
/// <param name="action">The asynchronous function to execute.</param>
/// <param name="maxRetries">The maximum number of retry attempts. Default is 3.</param>
/// <param name="delay">The delay between retry attempts. Default is 1 second.</param>
/// <returns>The result of the function if successful.</returns>
/// <exception cref="ArgumentNullException">Thrown if <paramref name="action"/> is null.</exception>
/// <exception cref="InvalidOperationException">Thrown if all retry attempts fail.</exception>
public static async Task<T> RetryAsync<T>(Func<Task<T>> action, int maxRetries = 3, TimeSpan? delay = null)
{
ArgumentNullException.ThrowIfNull(action);
delay ??= TimeSpan.FromSeconds(1);
Exception? lastException = null;
for (int i = 0; i < maxRetries; i++)
{
try
{
return await action();
}
catch (Exception ex)
{
lastException = ex;
await Task.Delay(delay.Value);
}
}
throw new InvalidOperationException($"Failed after {maxRetries} attempts.", lastException);
}
/// <summary>
/// Retries an asynchronous action up to a specified number of times, with an optional delay between attempts.
/// </summary>
/// <param name="action">The asynchronous action to execute.</param>
/// <param name="maxRetries">The maximum number of retry attempts. Default is 3.</param>
/// <param name="delay">The delay between retry attempts. Default is 1 second.</param>
/// <returns>A task representing the asynchronous operation.</returns>
/// <exception cref="ArgumentNullException">Thrown if <paramref name="action"/> is null.</exception>
/// <exception cref="InvalidOperationException">Thrown if all retry attempts fail.</exception>
public static async Task RetryAsync(Func<Task> action, int maxRetries = 3, TimeSpan? delay = null)
{
ArgumentNullException.ThrowIfNull(action);
delay ??= TimeSpan.FromSeconds(1);
Exception? lastException = null;
for (int i = 0; i < maxRetries; i++)
{
try
{
await action();
return;
}
catch (Exception ex)
{
lastException = ex;
await Task.Delay(delay.Value);
}
}
throw new InvalidOperationException($"Failed after {maxRetries} attempts.", lastException);
}
/// <summary>
/// Retries a synchronous function returning a value, up to a specified number of times, with an optional delay between attempts.
/// </summary>
/// <typeparam name="T">The type of the result returned by the function.</typeparam>
/// <param name="action">The function to execute.</param>
/// <param name="maxRetries">The maximum number of retry attempts. Default is 3.</param>
/// <param name="delay">The delay between retry attempts. Default is 1 second.</param>
/// <returns>The result of the function if successful.</returns>
/// <exception cref="ArgumentNullException">Thrown if <paramref name="action"/> is null.</exception>
/// <exception cref="InvalidOperationException">Thrown if all retry attempts fail.</exception>
public static T Retry<T>(Func<T> action, int maxRetries = 3, TimeSpan? delay = null)
{
ArgumentNullException.ThrowIfNull(action);
delay ??= TimeSpan.FromSeconds(1);
Exception? lastException = null;
for (int i = 0; i < maxRetries; i++)
{
try
{
return action();
}
catch (Exception ex)
{
lastException = ex;
System.Threading.Thread.Sleep(delay.Value);
}
}
throw new InvalidOperationException($"Failed after {maxRetries} attempts.", lastException);
}
/// <summary>
/// Retries a synchronous action up to a specified number of times, with an optional delay between attempts.
/// </summary>
/// <param name="action">The action to execute.</param>
/// <param name="maxRetries">The maximum number of retry attempts. Default is 3.</param>
/// <param name="delay">The delay between retry attempts. Default is 1 second.</param>
/// <exception cref="ArgumentNullException">Thrown if <paramref name="action"/> is null.</exception>
/// <exception cref="InvalidOperationException">Thrown if all retry attempts fail.</exception>
public static void Retry(Action action, int maxRetries = 3, TimeSpan? delay = null)
{
ArgumentNullException.ThrowIfNull(action);
delay ??= TimeSpan.FromSeconds(1);
Exception? lastException = null;
for (int i = 0; i < maxRetries; i++)
{
try
{
action();
return;
}
catch (Exception ex)
{
lastException = ex;
Thread.Sleep(delay.Value);
}
}
throw new InvalidOperationException($"Failed after {maxRetries} attempts.", lastException);
}
/// <summary>
/// Executes a synchronous action and catches any exception, writing it to the debug output.
/// </summary>
/// <param name="action">The action to execute.</param>
public static void Catch(Action action)
{
try
{
action();
}
catch (Exception ex)
{
Debug.WriteLine(ex);
}
}
/// <summary>
/// Executes a synchronous action and catches any exception, writing it to the debug output and returning it via an out parameter.
/// </summary>
/// <param name="action">The action to execute.</param>
/// <param name="exception">When this method returns, contains the exception that was thrown, or null if no exception was thrown.</param>
public static void Catch(Action action, out Exception? exception)
{
exception = null;
try
{
action();
}
catch (Exception ex)
{
Debug.WriteLine(ex);
exception = ex;
}
}
/// <summary>
/// Executes a synchronous function and catches any exception, writing it to the debug output. Returns the default value if an exception occurs.
/// </summary>
/// <typeparam name="T">The return type of the function.</typeparam>
/// <param name="action">The function to execute.</param>
/// <returns>The result of the function, or the default value of <typeparamref name="T"/> if an exception occurs.</returns>
public static T? Catch<T>(Func<T> action)
{
try
{
return action();
}
catch (Exception ex)
{
Debug.WriteLine(ex);
return default;
}
}
/// <summary>
/// Executes a synchronous function and catches any exception, writing it to the debug output and returning the exception via an out parameter.
/// Returns the default value if an exception occurs.
/// </summary>
/// <typeparam name="T">The return type of the function.</typeparam>
/// <param name="action">The function to execute.</param>
/// <param name="exception">When this method returns, contains the exception that was thrown, or null if no exception was thrown.</param>
/// <returns>The result of the function, or the default value of <typeparamref name="T"/> if an exception occurs.</returns>
public static T? Catch<T>(Func<T> action, out Exception? exception)
{
exception = null;
try
{
return action();
}
catch (Exception ex)
{
Debug.WriteLine(ex);
exception = ex;
return default;
}
}
/// <summary>
/// Executes an asynchronous action and catches any exception, writing it to the debug output.
/// </summary>
/// <param name="action">The asynchronous action to execute.</param>
/// <returns>A task that represents the asynchronous operation.</returns>
public static async Task CatchAsync(Func<Task> action)
{
try
{
await action();
}
catch (Exception ex)
{
Debug.WriteLine(ex);
}
}
/// <summary>
/// Executes an asynchronous function and catches any exception, writing it to the debug output.
/// Returns the default value if an exception occurs.
/// </summary>
/// <typeparam name="T">The return type of the function.</typeparam>
/// <param name="action">The asynchronous function to execute.</param>
/// <returns>The result of the function, or the default value of <typeparamref name="T"/> if an exception occurs.</returns>
public static async Task<T?> CatchAsync<T>(Func<Task<T>> action)
{
try
{
return await action();
}
catch (Exception ex)
{
Debug.WriteLine(ex);
return default;
}
}
/// <summary>
/// Waits for an asynchronous task to complete and catches any exception, writing it to the debug output.
/// </summary>
/// <param name="task">The task to await.</param>
/// <returns>A task that represents the asynchronous operation.</returns>
public static async Task CatchAsync(Task task)
{
try
{
await task;
}
catch (Exception ex)
{
Debug.WriteLine(ex);
}
}
/// <summary>
/// Waits for an asynchronous task that returns a result and catches any exception, writing it to the debug output.
/// Returns the default value if an exception occurs.
/// </summary>
/// <typeparam name="T">The return type of the task.</typeparam>
/// <param name="task">The task to await.</param>
/// <returns>The result of the task, or the default value of <typeparamref name="T"/> if an exception occurs.</returns>
public static async Task<T?> CatchAsync<T>(Task<T> task)
{
try
{
return await task;
}
catch (Exception ex)
{
Debug.WriteLine(ex);
return default;
}
}
}
+193
View File
@@ -0,0 +1,193 @@
using System.Diagnostics;
namespace CuteUtils.Misc;
/// <summary>
/// Provides utility methods for waiting on conditions or events with optional timeouts and intervals.
/// </summary>
public static class Wait
{
/// <summary>
/// Asynchronously waits until the specified condition is met or the timeout is reached.
/// </summary>
/// <param name="condition">A function that returns true when the wait should end.</param>
/// <param name="interval">The interval to check the condition. Defaults to 100ms.</param>
/// <param name="timeout">The maximum time to wait. Defaults to infinite.</param>
/// <exception cref="TimeoutException">Thrown if the condition is not met within the timeout.</exception>
public static async Task UntilAsync(Func<bool> condition, TimeSpan? interval = null, TimeSpan? timeout = null)
{
timeout ??= TimeSpan.MaxValue;
interval ??= TimeSpan.FromMilliseconds(100);
Stopwatch sw = Stopwatch.StartNew();
while (sw.Elapsed < timeout.Value)
{
if (condition())
{
return;
}
await Task.Delay(interval.Value);
}
throw new TimeoutException("The condition was not met within the specified timeout.");
}
/// <summary>
/// Synchronously waits until the specified condition is met or the timeout is reached.
/// </summary>
/// <param name="condition">A function that returns true when the wait should end.</param>
/// <param name="interval">The interval to check the condition. Defaults to 100ms.</param>
/// <param name="timeout">The maximum time to wait. Defaults to infinite.</param>
/// <exception cref="TimeoutException">Thrown if the condition is not met within the timeout.</exception>
public static void Until(Func<bool> condition, TimeSpan? interval = null, TimeSpan? timeout = null)
{
timeout ??= TimeSpan.MaxValue;
interval ??= TimeSpan.FromMilliseconds(100);
Stopwatch sw = Stopwatch.StartNew();
while (sw.Elapsed < timeout.Value)
{
if (condition())
{
return;
}
Thread.Sleep(interval.Value);
}
throw new TimeoutException("The condition was not met within the specified timeout.");
}
/// <summary>
/// Asynchronously waits for an event to be raised or the timeout to be reached.
/// </summary>
/// <param name="subscribe">An action that subscribes a handler to the event.</param>
/// <param name="timeout">The maximum time to wait. Defaults to infinite.</param>
/// <exception cref="TimeoutException">Thrown if the event is not raised within the timeout.</exception>
public static async Task WaitForEventAsync(Action<Action> subscribe, TimeSpan? timeout = null)
{
timeout ??= TimeSpan.MaxValue;
TaskCompletionSource tcs = new TaskCompletionSource();
void Handler()
{
_ = tcs.TrySetResult();
}
subscribe(Handler);
using CancellationTokenSource cts = new CancellationTokenSource(timeout.Value);
using (cts.Token.Register(() => tcs.TrySetCanceled(cts.Token)))
{
try
{
await tcs.Task.ConfigureAwait(false);
}
catch (TaskCanceledException)
{
throw new TimeoutException("The event was not raised within the specified timeout.");
}
}
}
/// <summary>
/// Asynchronously waits for an event to be raised or the timeout to be reached.
/// </summary>
/// <param name="subscribe">An action that subscribes a handler to the event.</param>
/// <param name="timeout">The maximum time to wait. Defaults to infinite.</param>
/// <typeparam name="T">The type of the event argument.</typeparam>
/// <returns>A task that completes with the event argument when the event is raised.</returns>
/// <exception cref="TimeoutException">Thrown if the event is not raised within the timeout.</exception>
public static async Task<T> ForEventAsync<T>(Action<Action<T>> subscribe, TimeSpan? timeout = null)
{
timeout ??= TimeSpan.MaxValue;
TaskCompletionSource<T> tcs = new TaskCompletionSource<T>();
void Handler(T arg)
{
_ = tcs.TrySetResult(arg);
}
subscribe(Handler);
using CancellationTokenSource cts = new CancellationTokenSource(timeout.Value);
using (cts.Token.Register(() => tcs.TrySetCanceled(cts.Token)))
{
try
{
return await tcs.Task;
}
catch (TaskCanceledException)
{
throw new TimeoutException("The event was not raised within the specified timeout.");
}
}
}
/// <summary>
/// Synchronously waits for an event to be raised or the timeout to be reached.
/// </summary>
/// <param name="subscribe">An action that subscribes a handler to the event.</param>
/// <param name="timeout">The maximum time to wait. Defaults to infinite.</param>
/// <exception cref="TimeoutException">Thrown if the event is not raised within the timeout.</exception>
public static void ForEvent(Action<Action> subscribe, TimeSpan? timeout = null)
{
timeout ??= TimeSpan.MaxValue;
TaskCompletionSource tcs = new TaskCompletionSource();
void Handler()
{
_ = tcs.TrySetResult();
}
subscribe(Handler);
using CancellationTokenSource cts = new CancellationTokenSource(timeout.Value);
using (cts.Token.Register(() => tcs.TrySetCanceled(cts.Token)))
{
try
{
tcs.Task.Wait();
}
catch (AggregateException ex) when (ex.InnerException is TaskCanceledException)
{
throw new TimeoutException("The event was not raised within the specified timeout.", ex);
}
}
}
/// <summary>
/// Synchronously waits for an event to be raised or the timeout to be reached.
/// </summary>
/// <param name="subscribe">An action that subscribes a handler to the event.</param>
/// <param name="timeout">The maximum time to wait. Defaults to infinite.</param>
/// <typeparam name="T">The type of the event argument.</typeparam>
/// <returns>The event argument when the event is raised.</returns>
/// <exception cref="TimeoutException">Thrown if the event is not raised within the timeout.</exception>
public static T ForEvent<T>(Action<Action<T>> subscribe, TimeSpan? timeout = null)
{
timeout ??= TimeSpan.MaxValue;
TaskCompletionSource<T> tcs = new TaskCompletionSource<T>();
void Handler(T arg)
{
_ = tcs.TrySetResult(arg);
}
subscribe(Handler);
using CancellationTokenSource cts = new CancellationTokenSource(timeout.Value);
using (cts.Token.Register(() => tcs.TrySetCanceled(cts.Token)))
{
try
{
return tcs.Task.GetAwaiter().GetResult();
}
catch (AggregateException ex) when (ex.InnerException is TaskCanceledException)
{
throw new TimeoutException("The event was not raised within the specified timeout.", ex);
}
catch (TaskCanceledException ex)
{
throw new TimeoutException("The event was not raised within the specified timeout.", ex);
}
}
}
}
+94
View File
@@ -0,0 +1,94 @@
using System.Xml;
using System.Xml.Schema;
using System.Xml.Serialization;
namespace CuteUtils.Misc;
/// <summary>
/// Provides simple XML serialization and deserialization helpers and schema generation helpers.
/// </summary>
public static class XmlHelper
{
/// <summary>
/// Deserializes an XML string into an instance of <typeparamref name="T"/>.
/// </summary>
/// <typeparam name="T">The target reference type to deserialize into. Must be a class.</typeparam>
/// <param name="xml">The XML string to deserialize. Must not be null.</param>
/// <param name="settings">Optional <see cref="XmlReaderSettings"/> to control reader behavior. If null, default settings are used.</param>
/// <returns>An instance of <typeparamref name="T"/> if deserialization succeeds; otherwise, <c>null</c>.</returns>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="xml"/> is null.</exception>
/// <exception cref="InvalidOperationException">May be thrown by <see cref="XmlSerializer"/> when deserialization fails.</exception>
public static T? Deserialize<T>(string xml, XmlReaderSettings? settings = null) where T : class
{
ArgumentNullException.ThrowIfNull(xml);
XmlSerializer serializer = new XmlSerializer(typeof(T));
using StringReader stringReader = new StringReader(xml);
using XmlReader xmlReader = XmlReader.Create(stringReader, settings);
return serializer.Deserialize(xmlReader) as T;
}
/// <summary>
/// Serializes an object to its XML representation.
/// </summary>
/// <typeparam name="T">The type of the object to serialize.</typeparam>
/// <param name="obj">The object instance to serialize. Must not be null.</param>
/// <param name="settings">Optional <see cref="XmlWriterSettings"/> to control writer behavior. If null, default settings are used.</param>
/// <param name="xmlSerializerNamespaces">Optional <see cref="XmlSerializerNamespaces"/> to control namespace declarations. If null, an empty namespace is used to omit declarations.</param>
/// <returns>A string containing the XML representation of <paramref name="obj"/>.</returns>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="obj"/> is null.</exception>
/// <exception cref="InvalidOperationException">May be thrown by <see cref="XmlSerializer"/> when serialization fails.</exception>
public static string Serialize<T>(T obj, XmlWriterSettings? settings = null, XmlSerializerNamespaces? xmlSerializerNamespaces = null)
{
ArgumentNullException.ThrowIfNull(obj);
settings ??= new XmlWriterSettings();
if (xmlSerializerNamespaces == null)
{
xmlSerializerNamespaces = new XmlSerializerNamespaces();
xmlSerializerNamespaces.Add(string.Empty, string.Empty);
}
XmlSerializer xmlSerializer = new(typeof(T));
// Omit namespace declarations by adding an empty namespace
xmlSerializerNamespaces.Add(string.Empty, string.Empty);
using StringWriter stringWriter = new StringWriter();
using XmlWriter xmlWriter = XmlWriter.Create(stringWriter, settings);
xmlSerializer.Serialize(xmlWriter, obj, xmlSerializerNamespaces);
return stringWriter.ToString();
}
/// <summary>
/// Generates XML schema definitions for the specified type <typeparamref name="T"/>.
/// </summary>
/// <typeparam name="T">The type for which to generate XML schema.</typeparam>
/// <param name="settings">Optional <see cref="XmlWriterSettings"/> to control output formatting. If null, default settings are used.</param>
/// <returns>A string containing one or more XML schema documents representing <typeparamref name="T"/>.</returns>
/// <exception cref="InvalidOperationException">May be thrown when schema export fails.</exception>
public static string GetXmlSchema<T>(XmlWriterSettings? settings = null)
{
settings ??= new XmlWriterSettings();
XmlSchemas schemas = [];
XmlSchemaExporter exporter = new XmlSchemaExporter(schemas);
XmlTypeMapping mapping = new XmlReflectionImporter().ImportTypeMapping(typeof(T));
exporter.ExportTypeMapping(mapping);
using StringWriter stringWriter = new StringWriter();
using XmlWriter xmlWriter = XmlWriter.Create(stringWriter, settings);
foreach (XmlSchema schema in schemas)
{
schema.Write(xmlWriter);
}
return stringWriter.ToString();
}
}
@@ -5,7 +5,7 @@ namespace CuteUtils.Reflection;
/// <summary> /// <summary>
/// Provides extension methods for reflection operations. /// Provides extension methods for reflection operations.
/// </summary> /// </summary>
public static class ReflectionExtentions public static class ReflectionExtensions
{ {
/// <summary> /// <summary>
/// Creates a new instance of the specified type and copies the properties from the source object to the new instance. /// Creates a new instance of the specified type and copies the properties from the source object to the new instance.
-169
View File
@@ -1,169 +0,0 @@
using System.Globalization;
using System.Text;
namespace CuteUtils;
/// <summary>
/// <see cref="string"/> Extensions
/// </summary>
public static class StringExt
{
/// <summary>
/// Removes all invalid chars from the specified <see cref="string"/>
/// </summary>
/// <param name="str"></param>
/// <param name="allowSpaces"></param>
/// <returns></returns>
public static string ToFileName(this string str, bool allowSpaces = false)
{
char[] invalidChars = Path.GetInvalidFileNameChars();
if (!allowSpaces)
{
str = str.Replace(" ", string.Empty);
}
foreach (char item in invalidChars)
{
str = str.Replace(item.ToString(), string.Empty);
}
string normalizedString = str.Normalize(NormalizationForm.FormD);
StringBuilder stringBuilder = new StringBuilder();
foreach (char c in normalizedString)
{
UnicodeCategory unicodeCategory = CharUnicodeInfo.GetUnicodeCategory(c);
if (unicodeCategory != UnicodeCategory.NonSpacingMark)
{
_ = stringBuilder.Append(c);
}
}
return stringBuilder.ToString().Normalize(NormalizationForm.FormC);
}
/// <summary>
/// Removes all invalid chars from the specified <see cref="string"/>
/// </summary>
/// <param name="str"></param>
/// <param name="allowSpaces"></param>
/// <returns></returns>
public static string ToPath(this string str, bool allowSpaces = false)
{
char[] invalidChars = Path.GetInvalidPathChars();
if (!allowSpaces)
{
str = str.Replace(" ", string.Empty);
}
foreach (char item in invalidChars)
{
str = str.Replace(item.ToString(), string.Empty);
}
string normalizedString = str.Normalize(NormalizationForm.FormD);
StringBuilder stringBuilder = new StringBuilder();
foreach (char c in normalizedString)
{
UnicodeCategory unicodeCategory = CharUnicodeInfo.GetUnicodeCategory(c);
if (unicodeCategory != UnicodeCategory.NonSpacingMark)
{
_ = stringBuilder.Append(c);
}
}
return stringBuilder.ToString().Normalize(NormalizationForm.FormC);
}
/// <summary>
/// Truncates a <see cref="string"/> to the specified length.
/// </summary>
/// <param name="str"></param>
/// <param name="length"></param>
/// <returns></returns>
public static string Truncate(this string str, int length)
{
if (str.Length > length && length > 0)
{
return str[..length];
}
return str;
}
/// <summary>
/// Truncates a <see cref="string"/> to the specified length.
/// </summary>
/// <param name="str"></param>
/// <param name="length"></param>
/// <param name="ellipsis"></param>
/// <returns></returns>
public static string Truncate(this string str, int length, bool ellipsis)
{
if (str.Length > length && length > 0)
{
if (ellipsis && length > 3)
{
return $"{str[..(length - 3)]}...";
}
else
{
return str[..length];
}
}
return str;
}
/// <summary>
/// Uses the correct newline <see cref="string"/> defined for this environment.
/// </summary>
/// <param name="str"></param>
/// <returns></returns>
public static string CorrectNewLine(this string str)
{
if (Environment.OSVersion.Platform == PlatformID.Unix)
{
str = str.Replace("\r\n", "\n");
}
else
{
str = str.Replace("\n", "\r\n"); //Ik that this can produce wrong results
}
return str;
}
/// <summary>
/// Removes all white spaces from the specified <see cref="string"/>
/// </summary>
/// <param name="str"></param>
/// <returns></returns>
public static string RemoveWhitespaces(this string str)
{
StringBuilder result = new StringBuilder();
foreach (char c in str)
{
if (!char.IsWhiteSpace(c))
{
_ = result.Append(c);
}
}
return result.ToString();
}
/// <summary>
/// Reverses the specified <see cref="string"/>
/// </summary>
/// <param name="str"></param>
/// <returns></returns>
public static string Reverse(this string str)
{
char[] array = str.ToCharArray();
Array.Reverse(array);
return new string(array);
}
}