Skip to content

Commit 8e31b18

Browse files
committed
Feat: StringCaseExtensions
1 parent 9286992 commit 8e31b18

File tree

10 files changed

+266
-8
lines changed

10 files changed

+266
-8
lines changed
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
// ---------------------------------------------------------------------------------------------------------------------
2+
// Imports
3+
// ---------------------------------------------------------------------------------------------------------------------
4+
using System.Text.RegularExpressions;
5+
6+
namespace CodeOfChaos.Extensions;
7+
// ---------------------------------------------------------------------------------------------------------------------
8+
// Code
9+
// ---------------------------------------------------------------------------------------------------------------------
10+
public static partial class StringCaseExtensions {
11+
[GeneratedRegex("(?<=[a-z])(?=[A-Z])|[^a-zA-Z0-9]+|(?<=[^\\d])(?=\\d)|(?<=\\d)(?=[^\\d])")]
12+
// [GeneratedRegex("(?<=[a-z])(?=[A-Z0-9])|(?<=[0-9])(?=[a-zA-Z])|[^a-zA-Z0-9]+")]
13+
private static partial Regex NonAlphanumericRegex { get; }
14+
15+
// -----------------------------------------------------------------------------------------------------------------
16+
// Methods
17+
// -----------------------------------------------------------------------------------------------------------------
18+
// Convert to PascalCase
19+
public static string ToPascalCase(this string input) {
20+
if (string.IsNullOrEmpty(input))
21+
return input;
22+
23+
string[] words = NonAlphanumericRegex.Split(input);
24+
25+
for (int i = 0; i < words.Length; i++) {
26+
string word = words[i];
27+
if (word.Length > 0) {
28+
words[i] = char.ToUpper(word[0]) + word[1..].ToLower();
29+
}
30+
}
31+
32+
return string.Concat(words);
33+
}
34+
35+
// Convert to camelCase
36+
public static string ToCamelCase(this string input) {
37+
if (string.IsNullOrEmpty(input))
38+
return input;
39+
40+
41+
string[] words = NonAlphanumericRegex.Split(input);
42+
43+
for (int i = 0; i < words.Length; i++) {
44+
string word = words[i];
45+
if (string.IsNullOrEmpty(word))
46+
continue;
47+
48+
words[i] = i == 0
49+
? char.ToLower(word[0]) + word[1..]
50+
: char.ToUpper(word[0]) + word[1..];
51+
}
52+
53+
return string.Concat(words);
54+
55+
}
56+
57+
58+
// Convert to kebab-case
59+
public static string ToKebabCase(this string input) {
60+
if (string.IsNullOrEmpty(input))
61+
return input;
62+
63+
string[] words = NonAlphanumericRegex.Split(input);
64+
for (int i = 0; i < words.Length; i++) {
65+
words[i] = words[i].ToLower();
66+
}
67+
68+
return string.Join("-", words.Where(word => word.Length > 0));
69+
}
70+
71+
// Convert to snake_case
72+
public static string ToSnakeCase(this string input) {
73+
if (string.IsNullOrEmpty(input))
74+
return input;
75+
76+
string[] words = NonAlphanumericRegex.Split(input);
77+
for (int i = 0; i < words.Length; i++) {
78+
words[i] = words[i].ToLower();
79+
}
80+
81+
return string.Join("_", words.Where(word => word.Length > 0));
82+
}
83+
84+
// Convert to period.separated.case
85+
public static string ToPeriodSeparatedCase(this string input) {
86+
if (string.IsNullOrEmpty(input))
87+
return input;
88+
89+
string[] words = NonAlphanumericRegex.Split(input);
90+
for (int i = 0; i < words.Length; i++) {
91+
words[i] = words[i].ToLower();
92+
}
93+
94+
return string.Join(".", words.Where(word => word.Length > 0));
95+
}
96+
}

tests/Tests.CodeOfChaos.Extensions.Analyzers/Tests.CodeOfChaos.Extensions.Analyzers.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111

1212
<ItemGroup>
1313
<PackageReference Include="CodeOfChaos.Testing.TUnit" Version="0.8.1" />
14-
<PackageReference Include="TUnit" Version="0.18.52" />
14+
<PackageReference Include="TUnit" Version="0.18.60" />
1515
</ItemGroup>
1616

1717
<ItemGroup>

tests/Tests.CodeOfChaos.Extensions.AspNetCore/Tests.CodeOfChaos.Extensions.AspNetCore.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
<ItemGroup>
1414
<PackageReference Include="JetBrains.Annotations" Version="2024.3.0"/>
1515
<PackageReference Include="Moq" Version="4.20.72"/>
16-
<PackageReference Include="TUnit" Version="0.18.52" />
16+
<PackageReference Include="TUnit" Version="0.18.60" />
1717
<PackageReference Include="Bogus" Version="35.6.2" />
1818
</ItemGroup>
1919

tests/Tests.CodeOfChaos.Extensions.DependencyInjection.Generators/Tests.CodeOfChaos.Extensions.DependencyInjection.Generators.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="4.13.0" />
2020
<PackageReference Include="Moq" Version="4.20.72" />
2121
<PackageReference Include="System.Formats.Asn1" Version="9.0.3" />
22-
<PackageReference Include="TUnit" Version="0.18.52" />
22+
<PackageReference Include="TUnit" Version="0.18.60" />
2323
</ItemGroup>
2424

2525
<ItemGroup>

tests/Tests.CodeOfChaos.Extensions.DependencyInjection/Tests.CodeOfChaos.Extensions.DependencyInjection.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
<PackageReference Include="JetBrains.Annotations" Version="2024.3.0"/>
1616
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.3" />
1717
<PackageReference Include="Moq" Version="4.20.72" />
18-
<PackageReference Include="TUnit" Version="0.18.52" />
18+
<PackageReference Include="TUnit" Version="0.18.60" />
1919
</ItemGroup>
2020

2121
<ItemGroup>

tests/Tests.CodeOfChaos.Extensions.EntityFrameworkCore/Tests.CodeOfChaos.Extensions.EntityFrameworkCore.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
<ItemGroup>
1414
<PackageReference Include="JetBrains.Annotations" Version="2024.3.0"/>
1515
<PackageReference Include="Moq" Version="4.20.72"/>
16-
<PackageReference Include="TUnit" Version="0.18.52" />
16+
<PackageReference Include="TUnit" Version="0.18.60" />
1717
<PackageReference Include="Bogus" Version="35.6.2" />
1818
</ItemGroup>
1919

tests/Tests.CodeOfChaos.Extensions.FluentValidation/Tests.CodeOfChaos.Extensions.FluentValidation.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
<ItemGroup>
1414
<PackageReference Include="JetBrains.Annotations" Version="2024.3.0"/>
1515
<PackageReference Include="Moq" Version="4.20.72"/>
16-
<PackageReference Include="TUnit" Version="0.18.52" />
16+
<PackageReference Include="TUnit" Version="0.18.60" />
1717
<PackageReference Include="Bogus" Version="35.6.2" />
1818
</ItemGroup>
1919

tests/Tests.CodeOfChaos.Extensions.Serilog/Tests.CodeOfChaos.Extensions.Serilog.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
<ItemGroup>
1414
<PackageReference Include="JetBrains.Annotations" Version="2024.3.0"/>
1515
<PackageReference Include="Moq" Version="4.20.72"/>
16-
<PackageReference Include="TUnit" Version="0.18.52" />
16+
<PackageReference Include="TUnit" Version="0.18.60" />
1717
<PackageReference Include="Bogus" Version="35.6.2" />
1818
</ItemGroup>
1919

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
// ---------------------------------------------------------------------------------------------------------------------
2+
// Imports
3+
// ---------------------------------------------------------------------------------------------------------------------
4+
using CodeOfChaos.Extensions;
5+
6+
namespace Tests.CodeOfChaos.Extensions;
7+
8+
// ---------------------------------------------------------------------------------------------------------------------
9+
// Code
10+
// ---------------------------------------------------------------------------------------------------------------------
11+
public class StringCaseExtensionTests {
12+
13+
// Tests for ToCamelCase
14+
[Test]
15+
[Arguments("a", "a")]
16+
[Arguments("A", "a")]
17+
[Arguments("aA", "aA")]
18+
[Arguments("aa", "aa")]
19+
[Arguments("aaA", "aaA")]
20+
[Arguments("a1a", "a1A")]
21+
[Arguments("AlphaBeta", "alphaBeta")]
22+
[Arguments("AlphaBetaGamma", "alphaBetaGamma")]
23+
[Arguments("AlphaBetaGammaDelta", "alphaBetaGammaDelta")]
24+
[Arguments("snake_case", "snakeCase")]
25+
[Arguments("snake_case_with_numbers_123", "snakeCaseWithNumbers123")]
26+
[Arguments("snake_case_with_numbers_123_and_more", "snakeCaseWithNumbers123AndMore")]
27+
[Arguments("period.case", "periodCase")]
28+
[Arguments("period.case.with.numbers.123", "periodCaseWithNumbers123")]
29+
[Arguments("period.case.with.numbers.123.and.more", "periodCaseWithNumbers123AndMore")]
30+
[Arguments("kebab-case", "kebabCase")]
31+
[Arguments("kebab-case-with-numbers-123", "kebabCaseWithNumbers123")]
32+
[Arguments("kebab-case-with-numbers-123-and-more", "kebabCaseWithNumbers123AndMore")]
33+
public async Task ToCamelCase_ShouldWork(string input, string expected) {
34+
// Arrange
35+
36+
// Act
37+
var result = input.ToCamelCase();
38+
39+
// Assert
40+
await Assert.That(result).IsEqualTo(expected);
41+
}
42+
43+
// Tests for ToPascalCase
44+
[Test]
45+
[Arguments("a", "A")]
46+
[Arguments("A", "A")]
47+
[Arguments("aA", "AA")]
48+
[Arguments("aa", "Aa")]
49+
[Arguments("aaA", "AaA")]
50+
[Arguments("a1a", "A1A")]
51+
[Arguments("AlphaBeta", "AlphaBeta")]
52+
[Arguments("AlphaBetaGamma", "AlphaBetaGamma")]
53+
[Arguments("AlphaBetaGammaDelta", "AlphaBetaGammaDelta")]
54+
[Arguments("snake_case", "SnakeCase")]
55+
[Arguments("snake_case_with_numbers_123", "SnakeCaseWithNumbers123")]
56+
[Arguments("snake_case_with_numbers_123_and_more", "SnakeCaseWithNumbers123AndMore")]
57+
[Arguments("period.case", "PeriodCase")]
58+
[Arguments("period.case.with.numbers.123", "PeriodCaseWithNumbers123")]
59+
[Arguments("period.case.with.numbers.123.and.more", "PeriodCaseWithNumbers123AndMore")]
60+
[Arguments("kebab-case", "KebabCase")]
61+
[Arguments("kebab-case-with-numbers-123", "KebabCaseWithNumbers123")]
62+
[Arguments("kebab-case-with-numbers-123-and-more", "KebabCaseWithNumbers123AndMore")]
63+
public async Task ToPascalCase_ShouldWork(string input, string expected) {
64+
// Arrange
65+
66+
// Act
67+
var result = input.ToPascalCase();
68+
69+
// Assert
70+
await Assert.That(result).IsEqualTo(expected);
71+
}
72+
73+
// Tests for ToKebabCase
74+
[Test]
75+
[Arguments("a", "a")]
76+
[Arguments("A", "a")]
77+
[Arguments("aA", "a-a")]
78+
[Arguments("aa", "aa")]
79+
[Arguments("aaA", "aa-a")]
80+
[Arguments("a1a", "a-1-a")]
81+
[Arguments("AlphaBeta", "alpha-beta")]
82+
[Arguments("AlphaBetaGamma", "alpha-beta-gamma")]
83+
[Arguments("AlphaBetaGammaDelta", "alpha-beta-gamma-delta")]
84+
[Arguments("snake_case", "snake-case")]
85+
[Arguments("snake_case_with_numbers_123", "snake-case-with-numbers-123")]
86+
[Arguments("snake_case_with_numbers_123_and_more", "snake-case-with-numbers-123-and-more")]
87+
[Arguments("period.case", "period-case")]
88+
[Arguments("period.case.with.numbers.123", "period-case-with-numbers-123")]
89+
[Arguments("period.case.with.numbers.123.and.more", "period-case-with-numbers-123-and-more")]
90+
[Arguments("kebab-case", "kebab-case")]
91+
[Arguments("kebab-case-with-numbers-123", "kebab-case-with-numbers-123")]
92+
[Arguments("kebab-case-with-numbers-123-and-more", "kebab-case-with-numbers-123-and-more")]
93+
public async Task ToKebabCase_ShouldWork(string input, string expected) {
94+
// Arrange
95+
96+
// Act
97+
var result = input.ToKebabCase();
98+
99+
// Assert
100+
await Assert.That(result).IsEqualTo(expected);
101+
}
102+
103+
// Tests for ToSnakeCase
104+
[Test]
105+
[Arguments("a", "a")]
106+
[Arguments("A", "a")]
107+
[Arguments("aA", "a_a")]
108+
[Arguments("aa", "aa")]
109+
[Arguments("aaA", "aa_a")]
110+
[Arguments("a1a", "a_1_a")]
111+
[Arguments("AlphaBeta", "alpha_beta")]
112+
[Arguments("AlphaBetaGamma", "alpha_beta_gamma")]
113+
[Arguments("AlphaBetaGammaDelta", "alpha_beta_gamma_delta")]
114+
[Arguments("snake_case", "snake_case")]
115+
[Arguments("snake_case_with_numbers_123", "snake_case_with_numbers_123")]
116+
[Arguments("snake_case_with_numbers_123_and_more", "snake_case_with_numbers_123_and_more")]
117+
[Arguments("period.case", "period_case")]
118+
[Arguments("period.case.with.numbers.123", "period_case_with_numbers_123")]
119+
[Arguments("period.case.with.numbers.123.and.more", "period_case_with_numbers_123_and_more")]
120+
[Arguments("kebab-case", "kebab_case")]
121+
[Arguments("kebab-case-with-numbers-123", "kebab_case_with_numbers_123")]
122+
[Arguments("kebab-case-with-numbers-123-and-more", "kebab_case_with_numbers_123_and_more")]
123+
public async Task ToSnakeCase_ShouldWork(string input, string expected) {
124+
// Arrange
125+
126+
// Act
127+
var result = input.ToSnakeCase();
128+
129+
// Assert
130+
await Assert.That(result).IsEqualTo(expected);
131+
}
132+
133+
// Tests for ToPeriodSeparatedCase
134+
[Test]
135+
[Arguments("a", "a")]
136+
[Arguments("A", "a")]
137+
[Arguments("aA", "a.a")]
138+
[Arguments("aa", "aa")]
139+
[Arguments("aaA", "aa.a")]
140+
[Arguments("a1a", "a.1.a")]
141+
[Arguments("AlphaBeta", "alpha.beta")]
142+
[Arguments("AlphaBetaGamma", "alpha.beta.gamma")]
143+
[Arguments("AlphaBetaGammaDelta", "alpha.beta.gamma.delta")]
144+
[Arguments("snake_case", "snake.case")]
145+
[Arguments("snake_case_with_numbers_123", "snake.case.with.numbers.123")]
146+
[Arguments("snake_case_with_numbers_123_and_more", "snake.case.with.numbers.123.and.more")]
147+
[Arguments("period.case", "period.case")]
148+
[Arguments("period.case.with.numbers.123", "period.case.with.numbers.123")]
149+
[Arguments("period.case.with.numbers.123.and.more", "period.case.with.numbers.123.and.more")]
150+
[Arguments("kebab-case", "kebab.case")]
151+
[Arguments("kebab-case-with-numbers-123", "kebab.case.with.numbers.123")]
152+
[Arguments("kebab-case-with-numbers-123-and-more", "kebab.case.with.numbers.123.and.more")]
153+
public async Task ToPeriodSeparatedCase_ShouldWork(string input, string expected) {
154+
// Arrange
155+
156+
// Act
157+
var result = input.ToPeriodSeparatedCase();
158+
159+
// Assert
160+
await Assert.That(result).IsEqualTo(expected);
161+
}
162+
}

tests/Tests.CodeOfChaos.Extensions/Tests.CodeOfChaos.Extensions.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
<ItemGroup>
1414
<PackageReference Include="JetBrains.Annotations" Version="2024.3.0"/>
1515
<PackageReference Include="Moq" Version="4.20.72"/>
16-
<PackageReference Include="TUnit" Version="0.18.52" />
16+
<PackageReference Include="TUnit" Version="0.18.60" />
1717
<PackageReference Include="Bogus" Version="35.6.2" />
1818
</ItemGroup>
1919

0 commit comments

Comments
 (0)