Skip to content

Commit 7bf1a46

Browse files
authored
Merge pull request #50 from lahma/ZStringWriter
Add ZStringWriter
2 parents 11760ec + ed9afed commit 7bf1a46

File tree

2 files changed

+244
-0
lines changed

2 files changed

+244
-0
lines changed

src/ZString/ZStringWriter.cs

Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
using System;
2+
using System.Globalization;
3+
using System.IO;
4+
using System.Text;
5+
using System.Threading;
6+
using System.Threading.Tasks;
7+
8+
namespace Cysharp.Text
9+
{
10+
/// <summary>
11+
/// A <see cref="TextWriter"/> implementation that is backed with <see cref="Utf16ValueStringBuilder"/>.
12+
/// </summary>
13+
/// <remarks>
14+
/// It's important to make sure the writer is always properly disposed.
15+
/// </remarks>
16+
public sealed class ZStringWriter : TextWriter
17+
{
18+
private Utf16ValueStringBuilder sb;
19+
private bool isOpen;
20+
private UnicodeEncoding encoding;
21+
22+
/// <summary>
23+
/// Creates a new instance using <see cref="CultureInfo.CurrentCulture"/> as format provider.
24+
/// </summary>
25+
public ZStringWriter() : this(CultureInfo.CurrentCulture)
26+
{
27+
}
28+
29+
/// <summary>
30+
/// Creates a new instance with given format provider.
31+
/// </summary>
32+
public ZStringWriter(IFormatProvider formatProvider) : base(formatProvider)
33+
{
34+
sb = ZString.CreateStringBuilder();
35+
isOpen = true;
36+
}
37+
38+
/// <summary>
39+
/// Disposes this instance, operations are no longer allowed.
40+
/// </summary>
41+
public override void Close()
42+
{
43+
Dispose(true);
44+
}
45+
46+
protected override void Dispose(bool disposing)
47+
{
48+
sb.Dispose();
49+
isOpen = false;
50+
base.Dispose(disposing);
51+
}
52+
53+
public override Encoding Encoding => encoding = encoding ?? new UnicodeEncoding(false, false);
54+
55+
public override void Write(char value)
56+
{
57+
AssertNotDisposed();
58+
59+
sb.Append(value);
60+
}
61+
62+
public override void Write(char[] buffer, int index, int count)
63+
{
64+
if (buffer == null)
65+
{
66+
throw new ArgumentNullException(nameof(buffer));
67+
}
68+
if (index < 0)
69+
{
70+
throw new ArgumentOutOfRangeException(nameof(index));
71+
}
72+
if (count < 0)
73+
{
74+
throw new ArgumentOutOfRangeException(nameof(count));
75+
}
76+
if (buffer.Length - index < count)
77+
{
78+
throw new ArgumentException();
79+
}
80+
AssertNotDisposed();
81+
82+
sb.Append(buffer.AsSpan(index, count));
83+
}
84+
85+
public override void Write(string value)
86+
{
87+
AssertNotDisposed();
88+
89+
if (value != null)
90+
{
91+
sb.Append(value);
92+
}
93+
}
94+
95+
public override Task WriteAsync(char value)
96+
{
97+
Write(value);
98+
return Task.CompletedTask;
99+
}
100+
101+
public override Task WriteAsync(string value)
102+
{
103+
Write(value);
104+
return Task.CompletedTask;
105+
}
106+
107+
public override Task WriteAsync(char[] buffer, int index, int count)
108+
{
109+
Write(buffer, index, count);
110+
return Task.CompletedTask;
111+
}
112+
113+
public override Task WriteLineAsync(char value)
114+
{
115+
WriteLine(value);
116+
return Task.CompletedTask;
117+
}
118+
119+
public override Task WriteLineAsync(string value)
120+
{
121+
WriteLine(value);
122+
return Task.CompletedTask;
123+
}
124+
125+
public override Task WriteLineAsync(char[] buffer, int index, int count)
126+
{
127+
WriteLine(buffer, index, count);
128+
return Task.CompletedTask;
129+
}
130+
131+
public override void Write(bool value)
132+
{
133+
AssertNotDisposed();
134+
sb.Append(value);
135+
}
136+
137+
public override void Write(decimal value)
138+
{
139+
AssertNotDisposed();
140+
sb.Append(value);
141+
}
142+
143+
/// <summary>
144+
/// No-op.
145+
/// </summary>
146+
public override Task FlushAsync()
147+
{
148+
return Task.CompletedTask;
149+
}
150+
151+
/// <summary>
152+
/// Materializes the current state from underlying string builder.
153+
/// </summary>
154+
public override string ToString()
155+
{
156+
return sb.ToString();
157+
}
158+
159+
#if !NETSTANDARD2_0
160+
161+
public override void Write(ReadOnlySpan<char> buffer)
162+
{
163+
AssertNotDisposed();
164+
165+
sb.Append(buffer);
166+
}
167+
168+
public override void WriteLine(ReadOnlySpan<char> buffer)
169+
{
170+
AssertNotDisposed();
171+
172+
sb.Append(buffer);
173+
WriteLine();
174+
}
175+
176+
public override Task WriteAsync(ReadOnlyMemory<char> buffer, CancellationToken cancellationToken = default)
177+
{
178+
if (cancellationToken.IsCancellationRequested)
179+
{
180+
return Task.FromCanceled(cancellationToken);
181+
}
182+
183+
Write(buffer.Span);
184+
return Task.CompletedTask;
185+
}
186+
187+
public override Task WriteLineAsync(ReadOnlyMemory<char> buffer, CancellationToken cancellationToken = default)
188+
{
189+
if (cancellationToken.IsCancellationRequested)
190+
{
191+
return Task.FromCanceled(cancellationToken);
192+
}
193+
194+
WriteLine(buffer.Span);
195+
return Task.CompletedTask;
196+
}
197+
#endif
198+
199+
private void AssertNotDisposed()
200+
{
201+
if (!isOpen)
202+
{
203+
throw new ObjectDisposedException(nameof(sb));
204+
}
205+
}
206+
}
207+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
using System;
2+
using Cysharp.Text;
3+
using Xunit;
4+
5+
namespace ZStringTests
6+
{
7+
public class ZStringWriterTest
8+
{
9+
[Fact]
10+
public void DoubleDisposeTest()
11+
{
12+
var sb = new ZStringWriter();
13+
sb.Dispose();
14+
sb.Dispose(); // call more than once
15+
}
16+
17+
[Fact]
18+
public void BasicWrites()
19+
{
20+
using (var writer = new ZStringWriter())
21+
{
22+
writer.Write("text1".AsSpan());
23+
writer.Write("text2");
24+
writer.Write('c');
25+
writer.Write(true);
26+
writer.Write(123);
27+
writer.Write(456f);
28+
writer.Write(789d);
29+
writer.Write("end".AsMemory());
30+
writer.WriteLine();
31+
32+
var expected = "text1text2cTrue123456789end" + Environment.NewLine;
33+
Assert.Equal(expected, writer.ToString());
34+
}
35+
}
36+
}
37+
}

0 commit comments

Comments
 (0)