diff --git a/src/ZString/ZStringWriter.cs b/src/ZString/ZStringWriter.cs new file mode 100644 index 0000000..862e56c --- /dev/null +++ b/src/ZString/ZStringWriter.cs @@ -0,0 +1,207 @@ +using System; +using System.Globalization; +using System.IO; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace Cysharp.Text +{ + /// + /// A implementation that is backed with . + /// + /// + /// It's important to make sure the writer is always properly disposed. + /// + public sealed class ZStringWriter : TextWriter + { + private Utf16ValueStringBuilder sb; + private bool isOpen; + private UnicodeEncoding encoding; + + /// + /// Creates a new instance using as format provider. + /// + public ZStringWriter() : this(CultureInfo.CurrentCulture) + { + } + + /// + /// Creates a new instance with given format provider. + /// + public ZStringWriter(IFormatProvider formatProvider) : base(formatProvider) + { + sb = ZString.CreateStringBuilder(); + isOpen = true; + } + + /// + /// Disposes this instance, operations are no longer allowed. + /// + public override void Close() + { + Dispose(true); + } + + protected override void Dispose(bool disposing) + { + sb.Dispose(); + isOpen = false; + base.Dispose(disposing); + } + + public override Encoding Encoding => encoding = encoding ?? new UnicodeEncoding(false, false); + + public override void Write(char value) + { + AssertNotDisposed(); + + sb.Append(value); + } + + public override void Write(char[] buffer, int index, int count) + { + if (buffer == 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(); + } + AssertNotDisposed(); + + sb.Append(buffer.AsSpan(index, count)); + } + + public override void Write(string value) + { + AssertNotDisposed(); + + if (value != null) + { + sb.Append(value); + } + } + + public override Task WriteAsync(char value) + { + Write(value); + return Task.CompletedTask; + } + + public override Task WriteAsync(string value) + { + Write(value); + return Task.CompletedTask; + } + + public override Task WriteAsync(char[] buffer, int index, int count) + { + Write(buffer, index, count); + return Task.CompletedTask; + } + + public override Task WriteLineAsync(char value) + { + WriteLine(value); + return Task.CompletedTask; + } + + public override Task WriteLineAsync(string value) + { + WriteLine(value); + return Task.CompletedTask; + } + + public override Task WriteLineAsync(char[] buffer, int index, int count) + { + WriteLine(buffer, index, count); + return Task.CompletedTask; + } + + public override void Write(bool value) + { + AssertNotDisposed(); + sb.Append(value); + } + + public override void Write(decimal value) + { + AssertNotDisposed(); + sb.Append(value); + } + + /// + /// No-op. + /// + public override Task FlushAsync() + { + return Task.CompletedTask; + } + + /// + /// Materializes the current state from underlying string builder. + /// + public override string ToString() + { + return sb.ToString(); + } + +#if !NETSTANDARD2_0 + + public override void Write(ReadOnlySpan buffer) + { + AssertNotDisposed(); + + sb.Append(buffer); + } + + public override void WriteLine(ReadOnlySpan buffer) + { + AssertNotDisposed(); + + sb.Append(buffer); + WriteLine(); + } + + public override Task WriteAsync(ReadOnlyMemory buffer, CancellationToken cancellationToken = default) + { + if (cancellationToken.IsCancellationRequested) + { + return Task.FromCanceled(cancellationToken); + } + + Write(buffer.Span); + return Task.CompletedTask; + } + + public override Task WriteLineAsync(ReadOnlyMemory buffer, CancellationToken cancellationToken = default) + { + if (cancellationToken.IsCancellationRequested) + { + return Task.FromCanceled(cancellationToken); + } + + WriteLine(buffer.Span); + return Task.CompletedTask; + } +#endif + + private void AssertNotDisposed() + { + if (!isOpen) + { + throw new ObjectDisposedException(nameof(sb)); + } + } + } +} diff --git a/tests/ZString.Tests/ZStringWriterTest.cs b/tests/ZString.Tests/ZStringWriterTest.cs new file mode 100644 index 0000000..3053934 --- /dev/null +++ b/tests/ZString.Tests/ZStringWriterTest.cs @@ -0,0 +1,37 @@ +using System; +using Cysharp.Text; +using Xunit; + +namespace ZStringTests +{ + public class ZStringWriterTest + { + [Fact] + public void DoubleDisposeTest() + { + var sb = new ZStringWriter(); + sb.Dispose(); + sb.Dispose(); // call more than once + } + + [Fact] + public void BasicWrites() + { + using (var writer = new ZStringWriter()) + { + writer.Write("text1".AsSpan()); + writer.Write("text2"); + writer.Write('c'); + writer.Write(true); + writer.Write(123); + writer.Write(456f); + writer.Write(789d); + writer.Write("end".AsMemory()); + writer.WriteLine(); + + var expected = "text1text2cTrue123456789end" + Environment.NewLine; + Assert.Equal(expected, writer.ToString()); + } + } + } +} \ No newline at end of file