From 1b1556b7bbba8e8721867bfbeba632031b25dc52 Mon Sep 17 00:00:00 2001 From: Oliver Booth Date: Wed, 20 Apr 2022 13:08:51 +0100 Subject: [PATCH] Add StringBuilderReader Implements a TextReader which reads from a StringBuilder, allowing consumers to iterate through it without consuming the result of StringBuilder.ToString() --- X10D/src/ExceptionMessages.Designer.cs | 9 + X10D/src/ExceptionMessages.resx | 3 + .../StringBuilderReader.cs | 214 ++++++++++++++++++ 3 files changed, 226 insertions(+) create mode 100644 X10D/src/StringBuilderExtensions/StringBuilderReader.cs diff --git a/X10D/src/ExceptionMessages.Designer.cs b/X10D/src/ExceptionMessages.Designer.cs index d575442..a22c995 100644 --- a/X10D/src/ExceptionMessages.Designer.cs +++ b/X10D/src/ExceptionMessages.Designer.cs @@ -60,6 +60,15 @@ namespace X10D { } } + /// + /// Looks up a localized string similar to The buffer is too small to contain the data.. + /// + internal static string BufferTooSmall { + get { + return ResourceManager.GetString("BufferTooSmall", resourceCulture); + } + } + /// /// Looks up a localized string similar to count must be greater than or equal to 0.. /// diff --git a/X10D/src/ExceptionMessages.resx b/X10D/src/ExceptionMessages.resx index a86e79e..867da37 100644 --- a/X10D/src/ExceptionMessages.resx +++ b/X10D/src/ExceptionMessages.resx @@ -23,6 +23,9 @@ PublicKeyToken=b77a5c561934e089 + + The buffer is too small to contain the data. + {{0}} is not a class. diff --git a/X10D/src/StringBuilderExtensions/StringBuilderReader.cs b/X10D/src/StringBuilderExtensions/StringBuilderReader.cs new file mode 100644 index 0000000..c07fb12 --- /dev/null +++ b/X10D/src/StringBuilderExtensions/StringBuilderReader.cs @@ -0,0 +1,214 @@ +using System.Text; + +namespace X10D; + +// NOTE: the overriden async overloads simply wrap the result of their sync counterparts because StringBuilder isn't inherently +// async. calling Task.FromResult (or creating a new ValueTask) is sufficient enough in this case, because there is simply no +// other way to have native async reading of a StringBuilder + +/// +/// Represents a reads from a . +/// +public class StringBuilderReader : TextReader +{ + private readonly StringBuilder _stringBuilder; + private int _index; + + /// + /// Initializes a new instance of the class. + /// + /// The to wrap. + /// is . + public StringBuilderReader(StringBuilder stringBuilder) + { + _stringBuilder = stringBuilder ?? throw new ArgumentNullException(nameof(stringBuilder)); + } + + /// + public override int Read() + { + if (_index >= _stringBuilder.Length) + { + return -1; + } + + return _stringBuilder[_index++]; + } + + /// + public override int Read(char[] buffer, int index, int count) + { + if (buffer is null) + { + throw new ArgumentNullException(nameof(buffer)); + } + + if (index < 0) + { + throw new ArgumentOutOfRangeException(nameof(index)); + } + + if (count < 0) + { + throw new ArgumentOutOfRangeException(nameof(count)); + } + + if (buffer.Length - index < count) + { + throw new ArgumentException(ExceptionMessages.BufferTooSmall, nameof(buffer)); + } + + if (_index >= _stringBuilder.Length) + { + return -1; + } + + int length = Math.Min(_stringBuilder.Length - _index, count); + _stringBuilder.CopyTo(_index, buffer, index, length); + _index += length; + return length; + } + + /// + public override int Read(Span buffer) + { + int count = Math.Min(buffer.Length, _stringBuilder.Length - _index); + for (var index = 0; index < count; index++) + { + buffer[index] = _stringBuilder[index + _index]; + } + + _index += count; + return count; + } + + /// + public override Task ReadAsync(char[] buffer, int index, int count) + { + return Task.FromResult(Read(buffer, index, count)); + } + + // except not really 🔽 + /// + /// Asynchronously reads the characters from the current stream into a memory block. + /// + /// + /// When this method returns, contains the specified memory block of characters replaced by the characters read from the + /// current source. + /// + /// Ignored. + /// + /// A value task that represents the asynchronous read operation. The value of the type parameter contains the number of + /// characters that have been read, or 0 if at the end of the stream and no data was read. The number will be less than or + /// equal to the buffer length, depending on whether the data is available within the stream. + /// + public override ValueTask ReadAsync(Memory buffer, CancellationToken cancellationToken = default) + { + return new ValueTask(Read(buffer.Span)); + } + + /// + public override int ReadBlock(Span buffer) + { + return Read(buffer); + } + + /// + public override Task ReadBlockAsync(char[] buffer, int index, int count) + { + return Task.FromResult(ReadBlock(buffer, index, count)); + } + + // except not really 🔽 + /// + /// Asynchronously reads the characters from the current stream and writes the data to a buffer. + /// + /// + /// When this method returns, contains the specified memory block of characters replaced by the characters read from the + /// current source. + /// + /// Ignored. + /// + /// A value task that represents the asynchronous read operation. The value of the type parameter contains the total + /// number of characters read into the buffer. The result value can be less than the number of characters requested if the + /// number of characters currently available is less than the requested number, or it can be 0 (zero) if the end of the + /// stream has been reached. + /// + public override ValueTask ReadBlockAsync(Memory buffer, CancellationToken cancellationToken = default) + { + return new ValueTask(ReadBlock(buffer.Span)); + } + + /// + public override Task ReadLineAsync() + { + return Task.FromResult(ReadLine()); + } + + /// + public override Task ReadToEndAsync() + { + return Task.FromResult(ReadToEnd()); + } + + /// + public override int Peek() + { + if (_index >= _stringBuilder.Length) + { + return -1; + } + + return _stringBuilder[_index]; + } + + /// + public override int ReadBlock(char[] buffer, int index, int count) + { + if (_index >= _stringBuilder.Length) + { + return -1; + } + + int length = Math.Min(count, _stringBuilder.Length - _index); + _stringBuilder.CopyTo(_index, buffer, index, length); + _index += length; + return length; + } + + /// + public override string? ReadLine() + { + if (_index >= _stringBuilder.Length) + { + return null; + } + + int start = _index; + while (_index < _stringBuilder.Length && _stringBuilder[_index] != '\n') + { + _index++; + } + + if (_index < _stringBuilder.Length) + { + _index++; + } + + return _stringBuilder.ToString(start, _index - start - 1); + } + + /// + public override string ReadToEnd() + { + var value = _stringBuilder.ToString(_index, _stringBuilder.Length - _index); + _index = _stringBuilder.Length; + return value; + } + + /// + public override void Close() + { + _index = _stringBuilder.Length; + } +}