diff --git a/DNX.Helpers/DNX.Helpers.csproj b/DNX.Helpers/DNX.Helpers.csproj
index 8160ec6..fac36a8 100644
--- a/DNX.Helpers/DNX.Helpers.csproj
+++ b/DNX.Helpers/DNX.Helpers.csproj
@@ -1,6 +1,7 @@
netstandard2.0
+ latest
true
.NET Extensions and helpers for Core and Common .NET types
Martin Smith
@@ -12,9 +13,9 @@
https://raw.githubusercontent.com/martinsmith1968/DNX.Helpers/master/images/favicon-32x32.png
DNX helpers extensions string array linq
Interpolation to a working version and some preparation for moving to .NET Standard
- 2.0.8
- 2.0.8
- 2.0.8
+ 2.1.0
+ 2.1.0
+ 2.1.0
bin\DNX.Helpers.xml
@@ -70,4 +71,4 @@
GuardBuiltInTypesTemplate.tt
-
\ No newline at end of file
+
diff --git a/DNX.Helpers/Internal/RunSafely.cs b/DNX.Helpers/Internal/RunSafely.cs
new file mode 100644
index 0000000..193984e
--- /dev/null
+++ b/DNX.Helpers/Internal/RunSafely.cs
@@ -0,0 +1,113 @@
+using System;
+using System.Threading.Tasks;
+
+namespace DNX.Helpers.Internal
+{
+ ///
+ /// RunSafely - execute code with defaulted exception handling
+ ///
+ public class RunSafely
+ {
+ ///
+ /// Executes the specified action.
+ ///
+ /// The action.
+ /// The exception handler.
+ public static void Execute(Action action, Action exceptionHandler = null)
+ {
+ try
+ {
+ action.Invoke();
+ }
+ catch (Exception ex)
+ {
+ Execute(() => exceptionHandler?.Invoke(ex));
+ }
+ }
+
+ ///
+ /// Executes the specified function.
+ ///
+ ///
+ /// The function.
+ /// The exception handler.
+ /// T.
+ public static T Execute(Func func, Action exceptionHandler = null)
+ {
+ return Execute(func, default, exceptionHandler);
+ }
+
+ ///
+ /// Executes the specified function.
+ ///
+ ///
+ /// The function.
+ /// The default value.
+ /// The exception handler.
+ /// T.
+ public static T Execute(Func func, T defaultValue, Action exceptionHandler = null)
+ {
+ try
+ {
+ return func.Invoke();
+ }
+ catch (Exception ex)
+ {
+ Execute(() => exceptionHandler?.Invoke(ex));
+
+ return defaultValue;
+ }
+ }
+
+ ///
+ /// execute as an asynchronous operation.
+ ///
+ /// The task.
+ /// The exception handler.
+ public static async Task ExecuteAsync(Task task, Action exceptionHandler = null)
+ {
+ try
+ {
+ await task.ConfigureAwait(false);
+ }
+ catch (Exception ex)
+ {
+ Execute(() => exceptionHandler?.Invoke(ex));
+ }
+ }
+
+ ///
+ /// execute as an asynchronous operation.
+ ///
+ ///
+ /// The task.
+ /// The exception handler.
+ /// Task<T>.
+ public static async Task ExecuteAsync(Task task, Action exceptionHandler = null)
+ {
+ return await ExecuteAsync(task, default, exceptionHandler);
+ }
+
+ ///
+ /// execute as an asynchronous operation.
+ ///
+ ///
+ /// The task.
+ /// The default value.
+ /// The exception handler.
+ /// Task<T>.
+ public static async Task ExecuteAsync(Task task, T defaultValue, Action exceptionHandler = null)
+ {
+ try
+ {
+ return await task.ConfigureAwait(false);
+ }
+ catch (Exception ex)
+ {
+ Execute(() => exceptionHandler?.Invoke(ex));
+
+ return defaultValue;
+ }
+ }
+ }
+}
diff --git a/DNX.Helpers/Strings/StringExtensions.cs b/DNX.Helpers/Strings/StringExtensions.cs
index 1ef8ea6..f85f94f 100644
--- a/DNX.Helpers/Strings/StringExtensions.cs
+++ b/DNX.Helpers/Strings/StringExtensions.cs
@@ -6,6 +6,8 @@
using System.Text.RegularExpressions;
using DNX.Helpers.Linq;
+// ReSharper disable InconsistentNaming
+
// ReSharper disable InvertIf
// ReSharper disable LoopCanBeConvertedToQuery
@@ -32,6 +34,10 @@ public enum SplitDelimiterType
///
public static class StringExtensions
{
+ private const string SI_WORDIFY_REGEX_TEXT = "(?<=[a-z])(?[A-Z])|(?<=.)(?[A-Z])(?=[a-z])";
+
+ private static readonly Regex WordifyRegex = new Regex(SI_WORDIFY_REGEX_TEXT, RegexOptions.Compiled);
+
///
/// Formats the specified arguments and text
///
@@ -643,5 +649,53 @@ public static bool IsValidNumber(this string text, CultureInfo cultureInfo)
return Regex.IsMatch(text, pattern);
}
+
+ ///
+ /// Add spaces to separate the capitalized words in the string,
+ /// i.e. insert a space before each uppercase letter that is
+ /// either preceded by a lowercase letter or followed by a
+ /// lowercase letter (but not for the first char in string).
+ /// This keeps groups of uppercase letters - e.g. preservedWords - together.
+ ///
+ /// A string in PascalCase
+ ///
+ public static string Wordify(this string titleCaseString)
+ {
+ return titleCaseString.Wordify(null);
+ }
+
+ ///
+ /// Add spaces to separate the capitalized words in the string,
+ /// i.e. insert a space before each uppercase letter that is
+ /// either preceded by a lowercase letter or followed by a
+ /// lowercase letter (but not for the first char in string).
+ /// This keeps groups of uppercase letters - e.g. acronyms - together.
+ ///
+ /// A string in PascalCase
+ /// The preservedWords - beyond acronyms
+ /// System.String.
+ public static string Wordify(this string titleCaseString, IList preservedWords)
+ {
+ if (string.IsNullOrWhiteSpace(titleCaseString))
+ return titleCaseString;
+
+ var text = WordifyRegex
+ .Replace(titleCaseString, " ${x}")
+ .Replace(" ", " ");
+
+ if (preservedWords.HasAny())
+ {
+ var acronymDict = preservedWords
+ .Distinct()
+ .ToDictionary(x => x.Wordify(), x => x)
+ ;
+
+ text = acronymDict.Aggregate(text,
+ (current, dict) => current.Replace(dict.Key, dict.Value)
+ );
+ }
+
+ return text;
+ }
}
}
diff --git a/Test.DNX.Helpers/Internal/RunSafelyTests.cs b/Test.DNX.Helpers/Internal/RunSafelyTests.cs
new file mode 100644
index 0000000..d432daa
--- /dev/null
+++ b/Test.DNX.Helpers/Internal/RunSafelyTests.cs
@@ -0,0 +1,313 @@
+using System;
+using System.Threading.Tasks;
+using DNX.Helpers.Internal;
+using NUnit.Framework;
+using Shouldly;
+
+// ReSharper disable InconsistentNaming
+
+namespace Test.DNX.Helpers.Internal
+{
+ public class RunSafelyTests
+ {
+ public class Execute_Tests
+ {
+ [Test]
+ public void Can_run_simple_action_that_succeeds()
+ {
+ // Arrange
+ var guid = Guid.NewGuid();
+ var value = Guid.Empty;
+
+ // Act
+ RunSafely.Execute(() => value = guid);
+
+ // Assert
+ value.ShouldBe(guid);
+ }
+
+ [Test]
+ public void Can_handle_simple_action_that_fails()
+ {
+ // Arrange
+ var dividend = 1000;
+ var divisor = 0;
+ var value = 0;
+
+ // Act
+ RunSafely.Execute(() => value = dividend / divisor);
+
+ // Assert
+ value.ShouldBe(0);
+ }
+
+ [Test]
+ public void Can_handle_simple_action_that_fails_and_extract_exception()
+ {
+ // Arrange
+ var value = int.MaxValue;
+ var guid = Guid.NewGuid();
+ var message = "";
+
+ // Act
+ RunSafely.Execute(() => throw new Exception(guid.ToString()), ex => message = ex.Message);
+
+ // Assert
+ value.ShouldBe(int.MaxValue);
+ message.ShouldNotBeNullOrEmpty();
+ message.ShouldContain(guid.ToString());
+ }
+
+ [Test]
+ public void Can_handle_simple_func_that_fails_and_extract_exception()
+ {
+ // Arrange
+ var dividend = 1000;
+ var divisor = 0;
+ var value = 0;
+ var message = "";
+
+ // Act
+ RunSafely.Execute(() => value = dividend / divisor, ex => message = ex.Message);
+
+ // Assert
+ value.ShouldBe(0);
+ message.ShouldNotBeNullOrEmpty();
+ message.ShouldContain("divide by zero");
+ }
+ }
+
+ public class ExecuteT_Tests
+ {
+ [Test]
+ public void Can_run_simple_func_that_succeeds()
+ {
+ // Arrange
+ var guid = Guid.NewGuid();
+
+ // Act
+ var value = RunSafely.Execute(() => guid);
+
+ // Assert
+ value.ShouldBe(guid);
+ }
+
+ [Test]
+ public void Can_handle_simple_func_that_fails()
+ {
+ // Arrange
+ var dividend = 1000;
+ var divisor = 0;
+
+ // Act
+ var value = RunSafely.Execute(() => dividend / divisor);
+
+ // Assert
+ value.ShouldBe(default);
+ }
+
+ [Test]
+ public void Can_handle_simple_func_with_default_that_fails()
+ {
+ // Arrange
+ var dividend = 1000;
+ var divisor = 0;
+ var defaultResult = 500;
+
+ // Act
+ var value = RunSafely.Execute(() => dividend / divisor, defaultResult);
+
+ // Assert
+ value.ShouldBe(defaultResult);
+ }
+
+ [Test]
+ public void Can_handle_simple_func_that_fails_and_extract_exception()
+ {
+ // Arrange
+ var dividend = 1000;
+ var divisor = 0;
+ var message = "";
+
+ // Act
+ var value = RunSafely.Execute(() => dividend / divisor, ex => message = ex.Message);
+
+ // Assert
+ value.ShouldBe(default(int));
+ message.ShouldNotBeNullOrEmpty();
+ message.ShouldContain("divide by zero");
+ }
+
+ [Test]
+ public void Can_handle_simple_func_with_default_that_fails_and_extract_exception()
+ {
+ // Arrange
+ var dividend = 1000;
+ var divisor = 0;
+ var defaultResult = 500;
+ var message = "";
+
+ // Act
+ var value = RunSafely.Execute(() => dividend / divisor, defaultResult, ex => message = ex.Message);
+
+ // Assert
+ value.ShouldBe(defaultResult);
+ message.ShouldNotBeNullOrEmpty();
+ message.ShouldContain("divide by zero");
+ }
+ }
+
+ public class ExecuteAsync_Tests
+ {
+ [Test]
+ public async Task Can_run_simple_task_that_succeeds()
+ {
+ // Arrange
+ var dividend = 1000;
+ var divisor = 20;
+ var value = 0;
+
+ var task = new Task(() => value = dividend / divisor);
+ task.Start();
+
+ // Act
+ await RunSafely.ExecuteAsync(task);
+
+ // Assert
+ value.ShouldBe(dividend / divisor);
+ }
+
+ [Test]
+ public async Task Can_handle_simple_action_that_fails()
+ {
+ // Arrange
+ var dividend = 1000;
+ var divisor = 0;
+ var value = 0;
+
+ var task = new Task(() => value = dividend / divisor);
+ task.Start();
+
+ // Act
+ await RunSafely.ExecuteAsync(task);
+
+ // Assert
+ value.ShouldBe(0);
+ }
+
+ [Test]
+ public async Task Can_handle_simple_action_that_fails_and_extract_exception()
+ {
+ // Arrange
+ var dividend = 1000;
+ var divisor = 0;
+ var value = 0;
+ var message = "";
+
+ var task = new Task(() => value = dividend / divisor);
+ task.Start();
+
+ // Act
+ await RunSafely.ExecuteAsync(task, ex => message = ex.Message);
+
+ // Assert
+ value.ShouldBe(0);
+ message.ShouldNotBeNullOrEmpty();
+ message.ShouldContain("divide by zero");
+ }
+ }
+
+ public class ExecuteAsyncT_Tests
+ {
+ private async Task DivideAsync(int dividend, int divisor)
+ {
+ var quotient = await Task.Run(() => dividend / divisor);
+
+ return quotient;
+ }
+
+ [Test]
+ public async Task Can_run_simple_func_that_succeeds()
+ {
+ // Arrange
+ var dividend = 1000;
+ var divisor = 50;
+
+ // Act
+ var value = await RunSafely.ExecuteAsync(DivideAsync(dividend, divisor));
+
+ // Assert
+ value.ShouldBe(dividend / divisor);
+ }
+
+ [Test]
+ public async Task Can_run_simple_func_that_fails()
+ {
+ // Arrange
+ var dividend = 1000;
+ var divisor = 0;
+ var value = 0;
+
+ // Act
+ value = await RunSafely.ExecuteAsync(DivideAsync(dividend, divisor));
+
+ // Assert
+ value.ShouldBe(default);
+ }
+
+ [Test]
+ public async Task Can_run_simple_func_with_default_that_fails()
+ {
+ // Arrange
+ var dividend = 1000;
+ var divisor = 0;
+ var defaultResult = 500;
+ var value = 0;
+
+ // Act
+ value = await RunSafely.ExecuteAsync(DivideAsync(dividend, divisor), defaultResult);
+
+ // Assert
+ value.ShouldBe(defaultResult);
+ }
+
+ [Test]
+ public async Task Can_handle_simple_func_that_fails_and_extract_exception()
+ {
+ // Arrange
+ var dividend = 1000;
+ var divisor = 0;
+ var value = 0;
+ var message = "";
+
+ // Act
+ value = await RunSafely.ExecuteAsync(DivideAsync(dividend, divisor), ex => message = ex.Message);
+
+ // Assert
+ value.ShouldBe(default);
+ message.ShouldNotBeNullOrEmpty();
+ message.ShouldContain("divide by zero");
+ }
+
+ [Test]
+ public async Task Can_handle_simple_func_with_default_that_fails_and_extract_exception()
+ {
+ // Arrange
+ var dividend = 1000;
+ var divisor = 0;
+ var defaultResult = 500;
+ var value = 0;
+ var message = "";
+
+ // Act
+ value = await RunSafely.ExecuteAsync(DivideAsync(dividend, divisor), defaultResult,
+ ex => message = ex.Message);
+
+ // Assert
+ value.ShouldBe(defaultResult);
+ message.ShouldNotBeNullOrEmpty();
+ message.ShouldContain("divide by zero");
+ }
+ }
+ }
+}
diff --git a/Test.DNX.Helpers/Strings/StringExtensionsTests.cs b/Test.DNX.Helpers/Strings/StringExtensionsTests.cs
index c6a0bfe..2ab9bc3 100644
--- a/Test.DNX.Helpers/Strings/StringExtensionsTests.cs
+++ b/Test.DNX.Helpers/Strings/StringExtensionsTests.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Globalization;
+using System.Linq;
using DNX.Helpers.Strings;
using NUnit.Framework;
using Shouldly;
@@ -544,5 +545,31 @@ public string Test_CoalesceNullOrWhitespace(string a, string b, string c)
return result;
}
+
+ [TestCase(null, null, null)]
+ [TestCase("", null, "")]
+ [TestCase("TwoWords", null, "Two Words")]
+ [TestCase("ThreeWordsTogether", null, "Three Words Together")]
+ [TestCase("pascalCase", null, "pascal Case")]
+ [TestCase("Already Spaced", null, "Already Spaced")]
+ [TestCase("AnEntireSentenceSquashedTogetherIntoOneSingleWord", null, "An Entire Sentence Squashed Together Into One Single Word")]
+ [TestCase("IsITVDramaBetterThanBBCDrama", null, "Is ITV Drama Better Than BBC Drama")]
+ [TestCase("IsTheHoLAnOutdatedInstitution", null, "Is The Ho L An Outdated Institution")]
+ [TestCase("IsTheHoLAnOutdatedInstitution", "HoL", "Is The HoL An Outdated Institution")]
+ [TestCase("VirusesLikeH1N1AndH5N1WereWarningsForThePanDemicToCome", null, "Viruses Like H1N1 And H5N1 Were Warnings For The Pan Demic To Come")]
+ [TestCase("VirusesLikeH1N1AndH5N1WereWarningsForThePanDemicToCome", "H1N1|H5N1|PanDemic", "Viruses Like H1N1 And H5N1 Were Warnings For The PanDemic To Come")]
+ public void Wordify_Tests(string text, string preservedWordsText, string expected)
+ {
+ // Arrange
+ var preservedWords = preservedWordsText?
+ .Split("|")
+ .ToArray();
+
+ // Act
+ var result = text.Wordify(preservedWords);
+
+ // Assert
+ result.ShouldBe(expected);
+ }
}
}
diff --git a/Test.DNX.Helpers/Test.DNX.Helpers.csproj b/Test.DNX.Helpers/Test.DNX.Helpers.csproj
index 7498d91..2fed06d 100644
--- a/Test.DNX.Helpers/Test.DNX.Helpers.csproj
+++ b/Test.DNX.Helpers/Test.DNX.Helpers.csproj
@@ -1,6 +1,7 @@
netcoreapp2.1
+ latest
Martin Smith
Tests for DNX.Helpers
Copyright © 2016-2019 DNX Solutions Ltd
@@ -8,22 +9,25 @@
https://github.com/martinsmith1968/DNX.Helpers
favicon-32x32.png
- 2.0.8
- 2.0.8
- 2.0.8
+ 2.1.0
+ 2.1.0
+ 2.1.0
-
+
-
+
-
-
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+