mirror of
https://github.com/Stone-Red-Code/CuteUtils.git
synced 2026-09-04 00:46:11 +02:00
@@ -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>
|
||||||
|
|
||||||
@@ -14,9 +14,9 @@
|
|||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.13.0" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.0.1" />
|
||||||
<PackageReference Include="MSTest.TestAdapter" Version="3.8.3" />
|
<PackageReference Include="MSTest.TestAdapter" Version="4.0.2" />
|
||||||
<PackageReference Include="MSTest.TestFramework" Version="3.8.3" />
|
<PackageReference Include="MSTest.TestFramework" Version="4.0.2" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -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());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -36,7 +36,7 @@ public class WaitTests
|
|||||||
await Task.Delay(200);
|
await Task.Delay(200);
|
||||||
flag = true;
|
flag = true;
|
||||||
});
|
});
|
||||||
Wait.Until(() => flag, TimeSpan.FromMilliseconds(50), TimeSpan.FromSeconds(2));
|
Wait.Until(() => flag, TimeSpan.FromMilliseconds(50));
|
||||||
Assert.IsTrue(flag);
|
Assert.IsTrue(flag);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -87,7 +87,7 @@ public class WaitTests
|
|||||||
handler(42);
|
handler(42);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
int result = await Wait.WaitForEventAsync((Action<Action<int>>)subscribe, TimeSpan.FromSeconds(2));
|
int result = await Wait.ForEventAsync((Action<Action<int>>)subscribe, TimeSpan.FromSeconds(2));
|
||||||
Assert.AreEqual(42, result);
|
Assert.AreEqual(42, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -100,7 +100,7 @@ public class WaitTests
|
|||||||
}
|
}
|
||||||
_ = await Assert.ThrowsExactlyAsync<TimeoutException>(async () =>
|
_ = await Assert.ThrowsExactlyAsync<TimeoutException>(async () =>
|
||||||
{
|
{
|
||||||
_ = await Wait.WaitForEventAsync((Action<Action<int>>)subscribe, TimeSpan.FromMilliseconds(200));
|
_ = await Wait.ForEventAsync((Action<Action<int>>)subscribe, TimeSpan.FromMilliseconds(200));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -115,7 +115,7 @@ public class WaitTests
|
|||||||
handler();
|
handler();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
Wait.WaitForEvent(subscribe, TimeSpan.FromSeconds(2));
|
Wait.ForEvent(subscribe, TimeSpan.FromSeconds(2));
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
@@ -127,7 +127,7 @@ public class WaitTests
|
|||||||
}
|
}
|
||||||
_ = Assert.ThrowsExactly<TimeoutException>(() =>
|
_ = Assert.ThrowsExactly<TimeoutException>(() =>
|
||||||
{
|
{
|
||||||
Wait.WaitForEvent(subscribe, TimeSpan.FromMilliseconds(200));
|
Wait.ForEvent(subscribe, TimeSpan.FromMilliseconds(200));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -142,7 +142,7 @@ public class WaitTests
|
|||||||
handler("hello");
|
handler("hello");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
string result = Wait.WaitForEvent((Action<Action<string>>)subscribe, TimeSpan.FromSeconds(2));
|
string result = Wait.ForEvent((Action<Action<string>>)subscribe, TimeSpan.FromSeconds(2));
|
||||||
Assert.AreEqual("hello", result);
|
Assert.AreEqual("hello", result);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -155,7 +155,7 @@ public class WaitTests
|
|||||||
}
|
}
|
||||||
_ = Assert.ThrowsExactly<TimeoutException>(() =>
|
_ = Assert.ThrowsExactly<TimeoutException>(() =>
|
||||||
{
|
{
|
||||||
_ = Wait.WaitForEvent((Action<Action<string>>)subscribe, TimeSpan.FromMilliseconds(200));
|
_ = 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.");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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,7 +29,7 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="System.Reactive.Linq" Version="6.0.1" />
|
<PackageReference Include="System.Reactive.Linq" Version="6.1.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
namespace CuteUtils.FluentMath.TypeExtentions;
|
namespace CuteUtils.FluentMath.TypeExtensions;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// DecimalFluent class
|
/// DecimalFluent class
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
namespace CuteUtils.FluentMath.TypeExtentions;
|
namespace CuteUtils.FluentMath.TypeExtensions;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// DoubleFluent class
|
/// DoubleFluent class
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
namespace CuteUtils.FluentMath.TypeExtentions;
|
namespace CuteUtils.FluentMath.TypeExtensions;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// IntegerFluent class
|
/// IntegerFluent class
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
namespace CuteUtils.FluentMath.TypeExtentions;
|
namespace CuteUtils.FluentMath.TypeExtensions;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// IntegerFluent class
|
/// IntegerFluent class
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
namespace CuteUtils.FluentMath.TypeExtentions;
|
namespace CuteUtils.FluentMath.TypeExtensions;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// FloatFluent class
|
/// FloatFluent class
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,169 +1,172 @@
|
|||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
namespace CuteUtils.Misc;
|
namespace CuteUtils.Misc;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// <see cref="string"/> Extensions
|
/// Provides extension methods for working with <see cref="string"/> values.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static class StringExt
|
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>
|
/// <summary>
|
||||||
/// Removes all invalid chars from the specified <see cref="string"/>
|
/// Converts the string into a valid file name by removing invalid characters
|
||||||
|
/// and optionally removing spaces. Diacritics (accents) are also removed.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="str"></param>
|
/// <param name="str">The input string.</param>
|
||||||
/// <param name="allowSpaces"></param>
|
/// <param name="allowSpaces">Whether spaces should be preserved.</param>
|
||||||
/// <returns></returns>
|
/// <returns>A sanitized string that is safe to use as a file name.</returns>
|
||||||
public static string ToFileName(this string str, bool allowSpaces = false)
|
public static string ToFileName(this string str, bool allowSpaces = false)
|
||||||
{
|
{
|
||||||
char[] invalidChars = Path.GetInvalidFileNameChars();
|
string path = Sanitize(str, Path.GetInvalidFileNameChars(), allowSpaces);
|
||||||
|
|
||||||
if (!allowSpaces)
|
if (path.Length > 255)
|
||||||
{
|
{
|
||||||
str = str.Replace(" ", string.Empty);
|
path = path[..255];
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (char item in invalidChars)
|
return path;
|
||||||
{
|
|
||||||
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>
|
/// <summary>
|
||||||
/// Removes all invalid chars from the specified <see cref="string"/>
|
/// Converts the string into a valid file system path segment by removing invalid characters
|
||||||
|
/// and optionally removing spaces. Diacritics (accents) are also removed.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="str"></param>
|
/// <param name="str">The input string.</param>
|
||||||
/// <param name="allowSpaces"></param>
|
/// <param name="allowSpaces">Whether spaces should be preserved.</param>
|
||||||
/// <returns></returns>
|
/// <returns>A sanitized string that is safe to use as a path segment.</returns>
|
||||||
public static string ToPath(this string str, bool allowSpaces = false)
|
public static string ToPath(this string str, bool allowSpaces = false)
|
||||||
{
|
{
|
||||||
char[] invalidChars = Path.GetInvalidPathChars();
|
return Sanitize(str, Path.GetInvalidPathChars(), allowSpaces);
|
||||||
|
|
||||||
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>
|
/// <summary>
|
||||||
/// Truncates a <see cref="string"/> to the specified length.
|
/// Returns a truncated version of the string with a maximum length.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="str"></param>
|
/// <param name="str">The input string.</param>
|
||||||
/// <param name="length"></param>
|
/// <param name="length">The maximum allowed length.</param>
|
||||||
/// <returns></returns>
|
/// <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)
|
public static string Truncate(this string str, int length)
|
||||||
{
|
{
|
||||||
if (str.Length > length && length > 0)
|
return (length > 0 && str.Length > length) ? str[..length] : str;
|
||||||
{
|
|
||||||
return str[..length];
|
|
||||||
}
|
|
||||||
|
|
||||||
return str;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Truncates a <see cref="string"/> to the specified length.
|
/// Returns a truncated version of the string with a maximum length,
|
||||||
|
/// optionally appending an ellipsis ("...") if the string is shortened.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="str"></param>
|
/// <param name="str">The input string.</param>
|
||||||
/// <param name="length"></param>
|
/// <param name="length">The maximum allowed length.</param>
|
||||||
/// <param name="ellipsis"></param>
|
/// <param name="ellipsis">Whether to append "..." when truncated.</param>
|
||||||
/// <returns></returns>
|
/// <returns>The truncated string, with optional ellipsis.</returns>
|
||||||
public static string Truncate(this string str, int length, bool ellipsis)
|
public static string Truncate(this string str, int length, bool ellipsis)
|
||||||
{
|
{
|
||||||
if (str.Length > length && length > 0)
|
if (length <= 0 || str.Length <= length)
|
||||||
{
|
{
|
||||||
if (ellipsis && length > 3)
|
return str;
|
||||||
{
|
|
||||||
return $"{str[..(length - 3)]}...";
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return str[..length];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return str;
|
if (ellipsis && length > 3)
|
||||||
|
{
|
||||||
|
return $"{str[..(length - 3)]}...";
|
||||||
|
}
|
||||||
|
|
||||||
|
return str[..length];
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Uses the correct newline <see cref="string"/> defined for this environment.
|
/// Normalizes newline characters in the string to the current environment's newline format.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="str"></param>
|
/// <param name="str">The input string.</param>
|
||||||
/// <returns></returns>
|
/// <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)
|
public static string CorrectNewLine(this string str)
|
||||||
{
|
{
|
||||||
if (Environment.OSVersion.Platform == PlatformID.Unix)
|
return str
|
||||||
{
|
.Replace("\r\n", "\n")
|
||||||
str = str.Replace("\r\n", "\n");
|
.Replace("\r", "\n")
|
||||||
}
|
.Replace("\n", Environment.NewLine);
|
||||||
else
|
|
||||||
{
|
|
||||||
str = str.Replace("\n", "\r\n"); //Ik that this can produce wrong results
|
|
||||||
}
|
|
||||||
|
|
||||||
return str;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Removes all white spaces from the specified <see cref="string"/>
|
/// Removes all whitespace characters from the string.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="str"></param>
|
/// <param name="str">The input string.</param>
|
||||||
/// <returns></returns>
|
/// <returns>The string with all whitespace removed.</returns>
|
||||||
public static string RemoveWhitespaces(this string str)
|
public static string RemoveWhitespaces(this string str)
|
||||||
{
|
{
|
||||||
StringBuilder result = new StringBuilder();
|
return new string(str.Where(c => !char.IsWhiteSpace(c)).ToArray());
|
||||||
foreach (char c in str)
|
|
||||||
{
|
|
||||||
if (!char.IsWhiteSpace(c))
|
|
||||||
{
|
|
||||||
_ = result.Append(c);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result.ToString();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Reverses the specified <see cref="string"/>
|
/// Returns a new string with the characters reversed.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="str"></param>
|
/// <param name="str">The input string.</param>
|
||||||
/// <returns></returns>
|
/// <returns>The reversed string.</returns>
|
||||||
public static string Reverse(this string str)
|
public static string Reverse(this string str)
|
||||||
{
|
{
|
||||||
char[] array = str.ToCharArray();
|
char[] arr = str.ToCharArray();
|
||||||
Array.Reverse(array);
|
Array.Reverse(arr);
|
||||||
return new string(array);
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -124,7 +124,7 @@ public static class Try
|
|||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
lastException = ex;
|
lastException = ex;
|
||||||
System.Threading.Thread.Sleep(delay.Value);
|
Thread.Sleep(delay.Value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
throw new InvalidOperationException($"Failed after {maxRetries} attempts.", lastException);
|
throw new InvalidOperationException($"Failed after {maxRetries} attempts.", lastException);
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
namespace CuteUtils.Misc;
|
using System.Diagnostics;
|
||||||
|
|
||||||
|
namespace CuteUtils.Misc;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Provides utility methods for waiting on conditions or events with optional timeouts and intervals.
|
/// Provides utility methods for waiting on conditions or events with optional timeouts and intervals.
|
||||||
@@ -16,15 +18,20 @@ public static class Wait
|
|||||||
{
|
{
|
||||||
timeout ??= TimeSpan.MaxValue;
|
timeout ??= TimeSpan.MaxValue;
|
||||||
interval ??= TimeSpan.FromMilliseconds(100);
|
interval ??= TimeSpan.FromMilliseconds(100);
|
||||||
DateTime endTime = DateTime.UtcNow.Add(timeout.Value);
|
|
||||||
while (!condition() && DateTime.UtcNow < endTime)
|
Stopwatch sw = Stopwatch.StartNew();
|
||||||
|
|
||||||
|
while (sw.Elapsed < timeout.Value)
|
||||||
{
|
{
|
||||||
|
if (condition())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
await Task.Delay(interval.Value);
|
await Task.Delay(interval.Value);
|
||||||
}
|
}
|
||||||
if (!condition())
|
|
||||||
{
|
throw new TimeoutException("The condition was not met within the specified timeout.");
|
||||||
throw new TimeoutException("The condition was not met within the specified timeout.");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -38,15 +45,20 @@ public static class Wait
|
|||||||
{
|
{
|
||||||
timeout ??= TimeSpan.MaxValue;
|
timeout ??= TimeSpan.MaxValue;
|
||||||
interval ??= TimeSpan.FromMilliseconds(100);
|
interval ??= TimeSpan.FromMilliseconds(100);
|
||||||
DateTime endTime = DateTime.UtcNow.Add(timeout.Value);
|
|
||||||
while (!condition() && DateTime.UtcNow < endTime)
|
Stopwatch sw = Stopwatch.StartNew();
|
||||||
|
|
||||||
|
while (sw.Elapsed < timeout.Value)
|
||||||
{
|
{
|
||||||
System.Threading.Thread.Sleep(interval.Value);
|
if (condition())
|
||||||
}
|
{
|
||||||
if (!condition())
|
return;
|
||||||
{
|
}
|
||||||
throw new TimeoutException("The condition was not met within the specified timeout.");
|
|
||||||
|
Thread.Sleep(interval.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
throw new TimeoutException("The condition was not met within the specified timeout.");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -88,7 +100,7 @@ public static class Wait
|
|||||||
/// <typeparam name="T">The type of the event argument.</typeparam>
|
/// <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>
|
/// <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>
|
/// <exception cref="TimeoutException">Thrown if the event is not raised within the timeout.</exception>
|
||||||
public static async Task<T> WaitForEventAsync<T>(Action<Action<T>> subscribe, TimeSpan? timeout = null)
|
public static async Task<T> ForEventAsync<T>(Action<Action<T>> subscribe, TimeSpan? timeout = null)
|
||||||
{
|
{
|
||||||
timeout ??= TimeSpan.MaxValue;
|
timeout ??= TimeSpan.MaxValue;
|
||||||
|
|
||||||
@@ -121,7 +133,7 @@ public static class Wait
|
|||||||
/// <param name="subscribe">An action that subscribes a handler to the event.</param>
|
/// <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>
|
/// <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>
|
/// <exception cref="TimeoutException">Thrown if the event is not raised within the timeout.</exception>
|
||||||
public static void WaitForEvent(Action<Action> subscribe, TimeSpan? timeout = null)
|
public static void ForEvent(Action<Action> subscribe, TimeSpan? timeout = null)
|
||||||
{
|
{
|
||||||
timeout ??= TimeSpan.MaxValue;
|
timeout ??= TimeSpan.MaxValue;
|
||||||
TaskCompletionSource tcs = new TaskCompletionSource();
|
TaskCompletionSource tcs = new TaskCompletionSource();
|
||||||
@@ -152,7 +164,7 @@ public static class Wait
|
|||||||
/// <typeparam name="T">The type of the event argument.</typeparam>
|
/// <typeparam name="T">The type of the event argument.</typeparam>
|
||||||
/// <returns>The event argument when the event is raised.</returns>
|
/// <returns>The event argument when the event is raised.</returns>
|
||||||
/// <exception cref="TimeoutException">Thrown if the event is not raised within the timeout.</exception>
|
/// <exception cref="TimeoutException">Thrown if the event is not raised within the timeout.</exception>
|
||||||
public static T WaitForEvent<T>(Action<Action<T>> subscribe, TimeSpan? timeout = null)
|
public static T ForEvent<T>(Action<Action<T>> subscribe, TimeSpan? timeout = null)
|
||||||
{
|
{
|
||||||
timeout ??= TimeSpan.MaxValue;
|
timeout ??= TimeSpan.MaxValue;
|
||||||
TaskCompletionSource<T> tcs = new TaskCompletionSource<T>();
|
TaskCompletionSource<T> tcs = new TaskCompletionSource<T>();
|
||||||
|
|||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user