//
// StringExtensions.cs
//
// Copyright (c) František Boháček. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace NosSmooth.Cryptography.Extensions;
///
/// Extension methods for string.
///
public static class StringExtensions
{
///
/// Split a string into lines without allocations.
///
/// The string to split.
/// An enumerator with lines.
public static LineSplitEnumerator SplitLines(this string str)
{
// LineSplitEnumerator is a struct so there is no allocation here
return new LineSplitEnumerator(str.AsSpan());
}
///
/// Split a string without any allocation.
///
/// The string to split.
/// The char to split with.
/// The split enumerator.
public static SplitEnumerator SplitAllocationless(this string str, char split)
{
return new SplitEnumerator(str.AsSpan(), split);
}
///
/// An enumerator of a string split.
///
public ref struct SplitEnumerator
{
private readonly char _split;
private ReadOnlySpan _str;
///
/// Initializes a new instance of the struct.
///
/// The string.
/// The split char.
public SplitEnumerator(ReadOnlySpan str, char split)
{
_str = str;
_split = split;
Current = default;
}
///
/// Gets this enumerator.
///
/// This.
public SplitEnumerator GetEnumerator()
=> this;
///
/// Move to next line.
///
/// Whether move was successful.
public bool MoveNext()
{
var span = _str;
if (span.Length == 0)
{
return false;
}
var index = span.IndexOf(_split);
if (index == -1)
{
_str = ReadOnlySpan.Empty;
Current = span;
return true;
}
Current = span.Slice(0, index);
_str = span.Slice(index + 1);
return true;
}
///
/// Current line.
///
public ReadOnlySpan Current { get; private set; }
}
///
/// An enumerator of a string lines.
///
public ref struct LineSplitEnumerator
{
private ReadOnlySpan _str;
///
/// Initializes a new instance of the struct.
///
/// The string.
public LineSplitEnumerator(ReadOnlySpan str)
{
_str = str;
Current = default;
}
///
/// Gets this enumerator.
///
/// This.
public LineSplitEnumerator GetEnumerator()
=> this;
///
/// Move to next line.
///
/// Whether move was successful.
public bool MoveNext()
{
var span = _str;
if (span.Length == 0)
{
return false;
}
var index = span.IndexOfAny('\r', '\n');
if (index == -1)
{
_str = ReadOnlySpan.Empty;
Current = new LineSplitEntry(span, ReadOnlySpan.Empty);
return true;
}
if (index < span.Length - 1 && span[index] == '\r')
{
var next = span[index + 1];
if (next == '\n')
{
Current = new LineSplitEntry(span.Slice(0, index), span.Slice(index, 2));
_str = span.Slice(index + 2);
return true;
}
}
Current = new LineSplitEntry(span.Slice(0, index), span.Slice(index, 1));
_str = span.Slice(index + 1);
return true;
}
///
/// Current line.
///
public LineSplitEntry Current { get; private set; }
}
///
/// A line.
///
public readonly ref struct LineSplitEntry
{
///
/// Initializes a new instance of the struct.
///
/// The line.
/// The line separator.
public LineSplitEntry(ReadOnlySpan line, ReadOnlySpan separator)
{
Line = line;
Separator = separator;
}
///
/// Gets the line.
///
public ReadOnlySpan Line { get; }
///
/// Gets the separator of the line.
///
public ReadOnlySpan Separator { get; }
///
/// This method allow to deconstruct the type, so you can write any of the following code
/// foreach (var entry in str.SplitLines()) { _ = entry.Line; }
/// foreach (var (line, endOfLine) in str.SplitLines()) { _ = line; }
/// https://learn.microsoft.com/en-us/dotnet/csharp/fundamentals/functional/deconstruct?WT.mc_id=DT-MVP-5003978#deconstructing-user-defined-types.
///
/// The line.
/// The line separator.
public void Deconstruct(out ReadOnlySpan line, out ReadOnlySpan separator)
{
line = Line;
separator = Separator;
}
///
/// An implicit cast to ReadOnySpan.
///
/// The entry to cast.
/// The read only span of the entry.
public static implicit operator ReadOnlySpan(LineSplitEntry entry)
=> entry.Line;
}
}