Skip to content

Commit 9e813c9

Browse files
Fixed issue with legacy .NET capture.
1 parent 283a11d commit 9e813c9

7 files changed

Lines changed: 147 additions & 87 deletions

File tree

Source/Extensions.Split.cs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
namespace Open.Text;
22

3+
internal static class SingleEmpty
4+
{
5+
public static readonly IReadOnlyList<string> Instance = Array.AsReadOnly(new[] { string.Empty });
6+
}
37
public static partial class TextExtensions
48
{
59
static ReadOnlySpan<char> FirstSplitSpan(StringSegment source, int start, int i, int n, out int nextIndex)
@@ -363,8 +367,6 @@ static IEnumerable<ReadOnlyMemory<char>> SplitAsMemoryCore(string source, string
363367
}
364368
}
365369

366-
static readonly IReadOnlyList<string> SingleEmpty = new List<string> { string.Empty }.AsReadOnly();
367-
368370
/// <summary>
369371
/// Splits a sequence of characters into strings using the character provided.
370372
/// </summary>
@@ -379,7 +381,7 @@ public static IReadOnlyList<string> Split(this ReadOnlySpan<char> source,
379381
switch (options)
380382
{
381383
case StringSplitOptions.None when source.Length == 0:
382-
return SingleEmpty;
384+
return SingleEmpty.Instance;
383385

384386
case StringSplitOptions.RemoveEmptyEntries when source.Length == 0:
385387
return Array.Empty<string>();
@@ -427,7 +429,7 @@ public static IReadOnlyList<string> Split(this ReadOnlySpan<char> source,
427429
switch (options)
428430
{
429431
case StringSplitOptions.None when source.IsEmpty:
430-
return SingleEmpty;
432+
return SingleEmpty.Instance;
431433

432434
case StringSplitOptions.RemoveEmptyEntries when source.IsEmpty:
433435
return Array.Empty<string>();

Source/Extensions._.cs

Lines changed: 37 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -7,26 +7,41 @@ namespace Open.Text;
77
public static partial class TextExtensions
88
{
99
private const uint BYTE_RED = 1024;
10-
[SuppressMessage("Style",
11-
"IDE0300:Simplify collection initialization",
12-
Justification = "Can cause NullReferenceException when initializing a static class.")]
13-
private static readonly string[] _byte_labels = new[] { "KB", "MB", "GB", "TB", "PB" };
14-
[SuppressMessage("Style",
15-
"IDE0300:Simplify collection initialization",
16-
Justification = "Can cause NullReferenceException when initializing a static class.")]
17-
private static readonly string[] _number_labels = new[] { "K", "M", "B" };
1810

19-
/// <summary>
20-
/// Compiled pattern for finding alpha-numeric sequences.
21-
/// </summary>
22-
public static readonly Regex ValidAlphaNumericOnlyPattern
23-
= new(@"^\w+$", RegexOptions.Compiled);
11+
private static IEnumerable<string> ByteLabels {
12+
get {
13+
yield return "KB";
14+
yield return "MB";
15+
yield return "GB";
16+
yield return "TB";
17+
yield return "PB";
18+
}
19+
}
2420

25-
/// <summary>
26-
/// Compiled pattern for finding alpha-numeric sequences and possible surrounding white-space.
27-
/// </summary>
28-
public static readonly Regex ValidAlphaNumericOnlyUntrimmedPattern
29-
= new(@"^\s*\w+\s*$", RegexOptions.Compiled);
21+
private static IEnumerable<string> NumberLabels
22+
{
23+
get
24+
{
25+
yield return "K";
26+
yield return "M";
27+
yield return "G";
28+
}
29+
}
30+
31+
/// <inheritdoc cref="RegexPatterns.ValidAlphaNumericOnlyPattern"/>
32+
[Obsolete("Use RegexPatterns.ValidAlphaNumericOnlyPattern.")]
33+
public static Regex ValidAlphaNumericOnlyPattern
34+
=> RegexPatterns.ValidAlphaNumericOnlyPattern;
35+
36+
/// <inheritdoc cref="RegexPatterns.ValidAlphaNumericOnlyUntrimmedPattern"/>
37+
[Obsolete("Use RegexPatterns.ValidAlphaNumericOnlyUntrimmedPattern.")]
38+
public static Regex ValidAlphaNumericOnlyUntrimmedPattern
39+
=> RegexPatterns.ValidAlphaNumericOnlyUntrimmedPattern;
40+
41+
/// <inheritdoc cref="RegexPatterns.WhiteSpacePattern"/>
42+
[Obsolete("Use RegexPatterns.WhiteSpacePattern.")]
43+
public static Regex WhiteSpacePattern
44+
=> RegexPatterns.WhiteSpacePattern;
3045

3146
/// <summary>
3247
/// Converts a string to title-case.
@@ -121,60 +136,7 @@ public static string ToFormat<T>(this T? value, string? format = null, CultureIn
121136
[ExcludeFromCodeCoverage]
122137
public static bool IsAlphaNumeric(this string source, bool trim = false)
123138
=> !string.IsNullOrWhiteSpace(source)
124-
&& (trim ? ValidAlphaNumericOnlyUntrimmedPattern : ValidAlphaNumericOnlyPattern).IsMatch(source);
125-
126-
#region Regex helper methods.
127-
private static readonly Func<Capture, string> _textDelegate = (Func<Capture, string>)
128-
typeof(Capture).GetProperty("Text", BindingFlags.Instance | BindingFlags.NonPublic)!
129-
.GetGetMethod(nonPublic: true)!
130-
.CreateDelegate(typeof(Func<Capture, string>));
131-
132-
/// <summary>
133-
/// Returns a ReadOnlySpan of the capture without creating a new string.
134-
/// </summary>
135-
/// <remarks>This is a stop-gap until .NET 6 releases the .ValueSpan property.</remarks>
136-
/// <param name="capture">The capture to get the span from.</param>
137-
public static ReadOnlySpan<char> AsSpan(this Capture capture)
138-
=> capture is null
139-
? throw new ArgumentNullException(nameof(capture))
140-
: _textDelegate.Invoke(capture).AsSpan(capture.Index, capture.Length);
141-
142-
/// <summary>
143-
/// Gets a group by name.
144-
/// </summary>
145-
/// <param name="groups">The group collection to get the group from.</param>
146-
/// <param name="groupName">The declared name of the group.</param>
147-
/// <returns>The value of the requested group or null if not found.</returns>
148-
/// <exception cref="ArgumentNullException">Groups or groupName is null.</exception>
149-
public static string? GetValue(this GroupCollection groups, string groupName)
150-
{
151-
if (groups is null)
152-
throw new ArgumentNullException(nameof(groups));
153-
if (groupName is null)
154-
throw new ArgumentNullException(nameof(groupName));
155-
Contract.EndContractBlock();
156-
157-
var group = groups[groupName];
158-
return group.Success
159-
? group.Value
160-
: null;
161-
}
162-
163-
/// <returns>The value of the requested group or an empty span if not found.</returns>
164-
/// <inheritdoc cref="GetValue(GroupCollection, string)" />
165-
public static ReadOnlySpan<char> GetValueSpan(this GroupCollection groups, string groupName)
166-
{
167-
if (groups is null)
168-
throw new ArgumentNullException(nameof(groups));
169-
if (groupName is null)
170-
throw new ArgumentNullException(nameof(groupName));
171-
Contract.EndContractBlock();
172-
173-
var group = groups[groupName];
174-
return group.Success
175-
? group.AsSpan()
176-
: [];
177-
}
139+
&& (trim ? RegexPatterns.ValidAlphaNumericOnlyUntrimmedPattern : RegexPatterns.ValidAlphaNumericOnlyPattern).IsMatch(source);
178140

179141
/// <summary>
180142
/// Returns the available matches as StringSegments.
@@ -203,7 +165,6 @@ static IEnumerable<StringSegment> AsSegmentsCore(Regex pattern, string input)
203165
}
204166
}
205167
}
206-
#endregion
207168

208169
#region Numeric string formatting.
209170
/// <summary>
@@ -251,7 +212,7 @@ public static string ToByteString(this double bytes, string decimalFormat = "N1"
251212
return string.Format(cultureInfo, bytes == 1 ? BYTE : BYTES, bytes);
252213

253214
var label = string.Empty;
254-
foreach (var s in _byte_labels)
215+
foreach (var s in ByteLabels)
255216
{
256217
label = s;
257218
bytes /= BYTE_RED;
@@ -284,7 +245,7 @@ public static string ToMetricString(this double number, string decimalFormat = "
284245
return number.ToString(decimalFormat, cultureInfo ?? CultureInfo.InvariantCulture);
285246

286247
var label = string.Empty;
287-
foreach (var s in _number_labels)
248+
foreach (var s in NumberLabels)
288249
{
289250
label = s;
290251
number /= 1000;
@@ -309,11 +270,6 @@ public static string ToMetricString(this int number, string decimalFormat = "N1"
309270
=> ToMetricString((double)number, decimalFormat, cultureInfo);
310271
#endregion
311272

312-
/// <summary>
313-
/// Compiled Regex for finding white-space.
314-
/// </summary>
315-
public static readonly Regex WhiteSpacePattern = new(@"\s+", RegexOptions.Compiled);
316-
317273
/// <summary>
318274
/// Replaces any white-space with the specified string.
319275
/// Collapses multiple white-space characters to a single space if no replacement specified.
@@ -328,7 +284,7 @@ public static string ReplaceWhiteSpace(this string source, string replace = " ")
328284
if (replace is null) throw new ArgumentNullException(nameof(replace));
329285
Contract.EndContractBlock();
330286

331-
return WhiteSpacePattern.Replace(source, replace);
287+
return RegexPatterns.WhiteSpacePattern.Replace(source, replace);
332288
}
333289

334290
/// <summary>

Source/Open.Text.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
<RepositoryUrl>https://github.com/Open-NET-Libraries/Open.Text</RepositoryUrl>
2121
<RepositoryType>git</RepositoryType>
2222
<PackageTags>string, span, enum, readonlyspan, text, format, split, trim, equals, trimmed equals, first, last, preceding, following, stringbuilder, extensions, stringcomparable, spancomparable, stringsegment, splitassegment</PackageTags>
23-
<Version>7.1.0</Version>
23+
<Version>8.0.0</Version>
2424
<PackageReleaseNotes></PackageReleaseNotes>
2525
<PackageLicenseExpression>MIT</PackageLicenseExpression>
2626
<PublishRepositoryUrl>true</PublishRepositoryUrl>

Source/RegexExtensions.cs

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
namespace Open.Text;
2+
3+
/// <summary>
4+
/// A set of regular expression extensions.
5+
/// </summary>
6+
public static class RegexExtensions
7+
{
8+
private static Func<Capture, string> GetOriginalTextDelegate()
9+
{
10+
var textProp = typeof(Capture).GetProperty("Text", BindingFlags.Instance | BindingFlags.NonPublic);
11+
if(textProp is not null)
12+
{
13+
var method = textProp.GetGetMethod(nonPublic: true)
14+
?? throw new InvalidOperationException("Could not find the Text property getter.");
15+
16+
return (Func<Capture, string>)method.CreateDelegate(typeof(Func<Capture, string>));
17+
}
18+
19+
// Some older versions of .NET use this instead.
20+
var textField = typeof(Capture).GetField("_text", BindingFlags.Instance | BindingFlags.NonPublic);
21+
return textField is not null
22+
? (capture => (string)textField.GetValue(capture)!)
23+
: throw new NotSupportedException("Capture: could not find the Text property or _text field.");
24+
}
25+
26+
private static readonly Func<Capture, string> _textDelegate = GetOriginalTextDelegate();
27+
28+
29+
/// <summary>
30+
/// Returns a ReadOnlySpan of the capture without creating a new string.
31+
/// </summary>
32+
/// <remarks>This is a stop-gap until .NET 6 releases the .ValueSpan property.</remarks>
33+
/// <param name="capture">The capture to get the span from.</param>
34+
public static ReadOnlySpan<char> AsSpan(this Capture capture)
35+
=> capture is null
36+
? throw new ArgumentNullException(nameof(capture))
37+
: _textDelegate(capture).AsSpan(capture.Index, capture.Length);
38+
39+
/// <summary>
40+
/// Gets a group by name.
41+
/// </summary>
42+
/// <param name="groups">The group collection to get the group from.</param>
43+
/// <param name="groupName">The declared name of the group.</param>
44+
/// <returns>The value of the requested group or null if not found.</returns>
45+
/// <exception cref="ArgumentNullException">Groups or groupName is null.</exception>
46+
public static string? GetValue(this GroupCollection groups, string groupName)
47+
{
48+
if (groups is null)
49+
throw new ArgumentNullException(nameof(groups));
50+
if (groupName is null)
51+
throw new ArgumentNullException(nameof(groupName));
52+
Contract.EndContractBlock();
53+
54+
var group = groups[groupName];
55+
return group.Success
56+
? group.Value
57+
: null;
58+
}
59+
60+
/// <returns>The value of the requested group or an empty span if not found.</returns>
61+
/// <inheritdoc cref="GetValue(GroupCollection, string)" />
62+
public static ReadOnlySpan<char> GetValueSpan(this GroupCollection groups, string groupName)
63+
{
64+
if (groups is null)
65+
throw new ArgumentNullException(nameof(groups));
66+
if (groupName is null)
67+
throw new ArgumentNullException(nameof(groupName));
68+
Contract.EndContractBlock();
69+
70+
var group = groups[groupName];
71+
return group.Success
72+
? group.AsSpan()
73+
: [];
74+
}
75+
}

Source/RegexPatterns.cs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
namespace Open.Text;
2+
3+
/// <summary>
4+
/// A set of commonly used regular expression patterns.
5+
/// </summary>
6+
public static class RegexPatterns
7+
{
8+
/// <summary>
9+
/// Compiled pattern for finding alpha-numeric sequences.
10+
/// </summary>
11+
public static readonly Regex ValidAlphaNumericOnlyPattern
12+
= new(@"^\w+$", RegexOptions.Compiled);
13+
14+
/// <summary>
15+
/// Compiled pattern for finding alpha-numeric sequences and possible surrounding white-space.
16+
/// </summary>
17+
public static readonly Regex ValidAlphaNumericOnlyUntrimmedPattern
18+
= new(@"^\s*\w+\s*$", RegexOptions.Compiled);
19+
20+
/// <summary>
21+
/// Compiled Regex for finding white-space.
22+
/// </summary>
23+
public static readonly Regex WhiteSpacePattern = new(@"\s+", RegexOptions.Compiled);
24+
}

Tests/FormattingTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ public static void ToByteString(string expected, double bytes)
3131
[InlineData("999.1", 999.1)]
3232
[InlineData("1.1K", 1110)]
3333
[InlineData("1.1M", 1110 * 1000)]
34-
[InlineData("1.1B", 1110 * 1000 * 1000)]
34+
[InlineData("1.1G", 1110 * 1000 * 1000)]
3535
public static void ToMetricString(string expected, double bytes)
3636
=> Assert.Equal(expected, bytes.ToMetricString());
3737
}

Tests/SplitTests.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,8 @@ public static void Split(string sequence, StringSplitOptions options = StringSpl
9494
Assert.Equal(segments, span.Split(',', options));
9595
Assert.Equal(segments, span.Split(",", options));
9696

97+
#pragma warning disable CS0618 // Type or member is obsolete
98+
// Use obsolete values to ensure they still work.
9799
Assert.Equal(
98100
TextExtensions
99101
.ValidAlphaNumericOnlyPattern
@@ -104,6 +106,7 @@ public static void Split(string sequence, StringSplitOptions options = StringSpl
104106
.ValidAlphaNumericOnlyPattern
105107
.AsSegments(sequence)
106108
.Select(m => m.Value));
109+
#pragma warning restore CS0618 // Type or member is obsolete
107110

108111
var stringSegment = sequence.AsSegment();
109112
var ss = stringSegment.SplitAsSegments(",", options).Select(s => s.Value).ToArray();

0 commit comments

Comments
 (0)