diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7369064..f9d37b4 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,6 +9,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
Nothing yet.
+## [1.20.4] - 2025-04-16
+
+### Added
+
+- Method `StringExtensions.Humanize`.
+
## [1.20.3] - 2025-04-16
### Fixed
@@ -281,7 +287,8 @@ Nothing yet.
- Implemented StringExtensions.
-[unreleased]: https://github.com/Logitar/Logitar.NET/compare/v1.20.3...HEAD
+[unreleased]: https://github.com/Logitar/Logitar.NET/compare/v1.20.4...HEAD
+[1.20.4]: https://github.com/Logitar/Logitar.NET/compare/v1.20.3...v1.20.4
[1.20.3]: https://github.com/Logitar/Logitar.NET/compare/v1.20.2...v1.20.3
[1.20.2]: https://github.com/Logitar/Logitar.NET/compare/v1.20.1...v1.20.2
[1.20.1]: https://github.com/Logitar/Logitar.NET/compare/v1.20.0...v1.20.1
diff --git a/src/Logitar/Logitar.csproj b/src/Logitar/Logitar.csproj
index 202b522..8426b0b 100644
--- a/src/Logitar/Logitar.csproj
+++ b/src/Logitar/Logitar.csproj
@@ -15,14 +15,14 @@
README.md
git
https://github.com/Logitar/Logitar.NET
- 7.0.0.0
+ 7.1.0.0
$(AssemblyVersion)
LICENSE
True
- 7.0.0
+ 7.1.0
en-CA
True
- Removed `ExceptionDetail` and dependency from `System.Text.Json`.
+ Added `StringExtensions.Humanize` method.
logitar net framework
https://github.com/Logitar/Logitar.NET/tree/dev/src/Logitar
@@ -53,6 +53,7 @@
+
diff --git a/src/Logitar/StringExtensions.cs b/src/Logitar/StringExtensions.cs
index b836454..654d1f1 100644
--- a/src/Logitar/StringExtensions.cs
+++ b/src/Logitar/StringExtensions.cs
@@ -33,6 +33,41 @@ public static string FromUriSafeBase64(this string s)
/// The Uri-safe base6 version of the string.
public static string ToUriSafeBase64(this string s) => s.Replace('+', '-').Replace('/', '_').TrimEnd('=');
+ ///
+ /// Humanizes the specified string by inserting spaces between words. Words are separated by case-change, such as in camelCase and PascalCase.
+ ///
+ /// The camelCase or PascalCase string.
+ /// The humanized string.
+ public static string Humanize(this string s)
+ {
+ List words = new(capacity: s.Length);
+
+ StringBuilder word = new();
+ for (int i = 0; i < s.Length; i++)
+ {
+ char? previous = (i > 0) ? s[i - 1] : null;
+ char current = s[i];
+ char? next = (i < s.Length - 1) ? s[i + 1] : null;
+
+ if (char.IsUpper(current) && ((previous.HasValue && char.IsLower(previous.Value)) || (next.HasValue && char.IsLower(next.Value))))
+ {
+ if (word.Length > 0)
+ {
+ words.Add(word.ToString());
+ word.Clear();
+ }
+ }
+
+ word.Append(current);
+ }
+ if (word.Length > 0)
+ {
+ words.Add(word.ToString());
+ }
+
+ return string.Join(' ', words);
+ }
+
///
/// Returns a new string where every character of the specified string has been replaced by the specified mask character (defaults to '*').
///
diff --git a/tests/Logitar.UnitTests/StringExtensionsTests.cs b/tests/Logitar.UnitTests/StringExtensionsTests.cs
index f873149..f1d9a61 100644
--- a/tests/Logitar.UnitTests/StringExtensionsTests.cs
+++ b/tests/Logitar.UnitTests/StringExtensionsTests.cs
@@ -31,6 +31,17 @@ public void FromUriSafeBase64_uri_safe_base64_is_correctly_parsed(string base64)
Assert.Equal(bytes, other);
}
+ [Theory(DisplayName = "Humanize: it should return the correct humanized string.")]
+ [InlineData("", "")]
+ [InlineData(" ", " ")]
+ [InlineData("helloWorld", "hello World")]
+ [InlineData("HelloWorld1!", "Hello World1!")]
+ [InlineData("UserID", "User ID")]
+ public void Humanize_it_returns_the_correct_humanized_string(string input, string expected)
+ {
+ Assert.Equal(expected, input.Humanize());
+ }
+
[Theory(DisplayName = "Mask: it returns the correct masked string.")]
[InlineData(" ")]
[InlineData("P@s$W0rD", '-')]