-
-
Notifications
You must be signed in to change notification settings - Fork 6
Add async implementations to the HtmlFormatter. #376
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 7 commits
40a43b9
298dc15
db731be
5a4766f
1cd8889
5e7c77a
b30e88e
e267276
c61a2c1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -24,11 +24,21 @@ public override void Write(string value) | |
| Write(value.ToCharArray(), 0, value.Length); | ||
| } | ||
|
|
||
| public override async Task WriteAsync(string value) | ||
| { | ||
| await WriteAsync(value.ToCharArray(), 0, value.Length); | ||
| } | ||
|
|
||
| public override void Write(char[] value) | ||
| { | ||
| Write(value, 0, value.GetLength(0)); | ||
| } | ||
|
|
||
| //public async Task WriteAsync(char[] value) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can be removed? |
||
| //{ | ||
| // await WriteAsync(value, 0, value.GetLength(0)); | ||
| //} | ||
|
|
||
| public override void Write(char[] source, int offset, int length) | ||
| { | ||
| if (offset + length > source.GetLength(0)) | ||
|
|
@@ -62,6 +72,38 @@ public override void Write(char[] source, int offset, int length) | |
| } | ||
| } | ||
|
|
||
| public override async Task WriteAsync(char[] source, int offset, int length) | ||
| { | ||
| if (offset + length > source.GetLength(0)) | ||
| throw new ArgumentException("Cannot read past the end of the input source char array."); | ||
|
|
||
| char[] destination = PrepareBuffer(); | ||
| int flushAt = BUFFER_SIZE - 2; | ||
| int written = 0; | ||
| for (int i = offset; i < offset + length; i++) | ||
| { | ||
| char c = source[i]; | ||
|
|
||
| // Flush buffer if (nearly) full | ||
| if (written >= flushAt) | ||
| { | ||
| await Writer.WriteAsync(destination, 0, written); | ||
| written = 0; | ||
| } | ||
|
|
||
| // Write with escapes | ||
| if (c == '/') | ||
| { | ||
| destination[written++] = '\\'; | ||
| } | ||
| destination[written++] = c; | ||
| } | ||
| // Flush any remaining | ||
| if (written > 0) | ||
| { | ||
| await Writer.WriteAsync(destination, 0, written); | ||
| } | ||
| } | ||
| private char[] PrepareBuffer() | ||
| { | ||
| // Reuse the same buffer, avoids repeated array allocation | ||
|
|
@@ -74,6 +116,11 @@ public override void Flush() | |
| Writer.Flush(); | ||
| } | ||
|
|
||
| public override async Task FlushAsync() | ||
| { | ||
| await Writer.FlushAsync(); | ||
| } | ||
|
|
||
| public override void Close() | ||
| { | ||
| Writer.Close(); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,23 +6,46 @@ namespace Cucumber.HtmlFormatter; | |
| public class MessagesToHtmlWriter : IDisposable | ||
| { | ||
| private StreamWriter writer; | ||
| private Func<StreamWriter, Envelope, Task> asyncStreamSerializer; | ||
| private Action<StreamWriter, Envelope> streamSerializer; | ||
| private string template; | ||
| private JsonInHtmlWriter JsonInHtmlWriter; | ||
| private bool streamClosed = false; | ||
| private bool preMessageWritten = false; | ||
| private bool firstMessageWritten = false; | ||
| private bool postMessageWritten = false; | ||
| private bool isAsyncInitialized = false; | ||
|
|
||
| [Obsolete("Cucumber.HtmlFormatter moving to async only operations. Please use the MessagesToHtmlWriter(Stream, Func<StreamWriter, Envelope, Task>) constructor", false)] | ||
| public MessagesToHtmlWriter(Stream stream, Action<StreamWriter, Envelope> streamSerializer) : this(new StreamWriter(stream), streamSerializer) | ||
| { | ||
| } | ||
| public MessagesToHtmlWriter(Stream stream, Func<StreamWriter, Envelope, Task> asyncStreamSerializer) : this(new StreamWriter(stream), asyncStreamSerializer) { } | ||
|
|
||
| [Obsolete("Cucumber.HtmlFormatter moving to async only operations. Please use the MessagesToHtmlWriter(StreamWriter, Func<StreamWriter, Envelope, Task>) constructor", false)] | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This looks like a deprecation. So this needs its own |
||
| public MessagesToHtmlWriter(StreamWriter writer, Action<StreamWriter, Envelope> streamSerializer) | ||
| { | ||
| this.writer = writer; | ||
| this.streamSerializer = streamSerializer; | ||
| // Create async wrapper for sync serializer | ||
| this.asyncStreamSerializer = (w, e) => | ||
| { | ||
| streamSerializer(w, e); | ||
| return Task.CompletedTask; | ||
| }; | ||
| template = GetResource("index.mustache.html"); | ||
| JsonInHtmlWriter = new JsonInHtmlWriter(writer); | ||
| isAsyncInitialized = false; | ||
| } | ||
| public MessagesToHtmlWriter(StreamWriter writer, Func<StreamWriter, Envelope, Task> asyncStreamSerializer) | ||
| { | ||
| this.writer = writer; | ||
| this.asyncStreamSerializer = asyncStreamSerializer; | ||
| // Create sync wrapper for async serializer (will block) | ||
| this.streamSerializer = (w, e) => asyncStreamSerializer(w, e).GetAwaiter().GetResult(); | ||
| template = GetResource("index.mustache.html"); | ||
| JsonInHtmlWriter = new JsonInHtmlWriter(writer); | ||
| isAsyncInitialized = true; | ||
| } | ||
|
|
||
| private void WritePreMessage() | ||
|
|
@@ -32,15 +55,35 @@ private void WritePreMessage() | |
| WriteTemplateBetween(writer, template, "{{css}}", "{{messages}}"); | ||
| } | ||
|
|
||
| private async Task WritePreMessageAsync() | ||
| { | ||
| await WriteTemplateBetweenAsync(writer, template, null, "{{css}}"); | ||
| await WriteResourceAsync(writer, "main.css"); | ||
| await WriteTemplateBetweenAsync(writer, template, "{{css}}", "{{messages}}"); | ||
| } | ||
|
|
||
| private void WritePostMessage() | ||
| { | ||
| WriteTemplateBetween(writer, template, "{{messages}}", "{{script}}"); | ||
| WriteResource(writer, "main.js"); | ||
| WriteTemplateBetween(writer, template, "{{script}}", null); | ||
| } | ||
|
|
||
| private async Task WritePostMessageAsync() | ||
| { | ||
| await WriteTemplateBetweenAsync(writer, template, "{{messages}}", "{{script}}"); | ||
| await WriteResourceAsync(writer, "main.js"); | ||
| await WriteTemplateBetweenAsync(writer, template, "{{script}}", null); | ||
| } | ||
|
|
||
| public void Write(Envelope envelope) | ||
| { | ||
| if (isAsyncInitialized) | ||
| { | ||
| // Log a warning or use other diagnostics | ||
| System.Diagnostics.Debug.WriteLine("Warning: Using synchronous Write when initialized with async serializer"); | ||
| } | ||
|
|
||
| if (streamClosed) { throw new IOException("Stream closed"); } | ||
|
|
||
| if (!preMessageWritten) | ||
|
|
@@ -62,6 +105,37 @@ public void Write(Envelope envelope) | |
| streamSerializer(JsonInHtmlWriter, envelope); | ||
| JsonInHtmlWriter.Flush(); | ||
| } | ||
| public async Task WriteAsync(Envelope envelope) | ||
| { | ||
| if (!isAsyncInitialized) | ||
| { | ||
| // Log a warning or use other diagnostics | ||
| System.Diagnostics.Debug.WriteLine("Warning: Using asynchronous WriteAsync when initialized with sync serializer"); | ||
| } | ||
|
|
||
| if (streamClosed) { throw new IOException("Stream closed"); } | ||
|
|
||
| if (!preMessageWritten) | ||
| { | ||
| await WritePreMessageAsync(); | ||
| preMessageWritten = true; | ||
| await writer.FlushAsync(); | ||
| } | ||
| if (!firstMessageWritten) | ||
| { | ||
| firstMessageWritten = true; | ||
| } | ||
| else | ||
| { | ||
| await writer.WriteAsync(","); | ||
| await writer.FlushAsync(); | ||
| } | ||
|
|
||
| // Use the synchronous serializer in an async context | ||
| await asyncStreamSerializer(JsonInHtmlWriter, envelope); | ||
| await JsonInHtmlWriter.FlushAsync(); | ||
| } | ||
|
|
||
| public void Dispose() | ||
| { | ||
| if (streamClosed) { return; } | ||
|
|
@@ -86,27 +160,68 @@ public void Dispose() | |
| streamClosed = true; | ||
| } | ||
| } | ||
|
|
||
| public async Task DisposeAsync() | ||
| { | ||
| if (streamClosed) { return; } | ||
|
|
||
| if (!preMessageWritten) | ||
| { | ||
| await WritePreMessageAsync(); | ||
| preMessageWritten = true; | ||
| } | ||
| if (!postMessageWritten) | ||
| { | ||
| await WritePostMessageAsync(); | ||
| postMessageWritten = true; | ||
| } | ||
| try | ||
| { | ||
| await writer.FlushAsync(); | ||
| writer.Close(); | ||
| } | ||
| finally | ||
| { | ||
| streamClosed = true; | ||
| } | ||
| } | ||
|
|
||
| private void WriteResource(StreamWriter writer, string v) | ||
| { | ||
| var resource = GetResource(v); | ||
| writer.Write(resource); | ||
| } | ||
|
|
||
| private async Task WriteResourceAsync(StreamWriter writer, string v) | ||
| { | ||
| var resource = GetResource(v); | ||
| await writer.WriteAsync(resource); | ||
| } | ||
| private void WriteTemplateBetween(StreamWriter writer, string template, string? begin, string? end) | ||
| { | ||
| int beginIndex = begin == null ? 0 : template.IndexOf(begin) + begin.Length; | ||
| int endIndex = end == null ? template.Length : template.IndexOf(end); | ||
| int lengthToWrite = endIndex - beginIndex; | ||
| int beginIndex, lengthToWrite; | ||
| CalculateBeginAndLength(template, begin, end, out beginIndex, out lengthToWrite); | ||
| writer.Write(template.Substring(beginIndex, lengthToWrite)); | ||
| } | ||
|
|
||
| private static void CalculateBeginAndLength(string template, string? begin, string? end, out int beginIndex, out int lengthToWrite) | ||
| { | ||
| beginIndex = begin == null ? 0 : template.IndexOf(begin) + begin.Length; | ||
| int endIndex = end == null ? template.Length : template.IndexOf(end); | ||
| lengthToWrite = endIndex - beginIndex; | ||
| } | ||
|
|
||
| private async Task WriteTemplateBetweenAsync(StreamWriter writer, string template, string? begin, string? end) | ||
| { | ||
| int beginIndex, lengthToWrite; | ||
| CalculateBeginAndLength(template, begin, end, out beginIndex, out lengthToWrite); | ||
| await writer.WriteAsync(template.Substring(beginIndex, lengthToWrite)); | ||
| } | ||
| private string GetResource(string name) | ||
| { | ||
| var assembly = typeof(MessagesToHtmlWriter).Assembly; | ||
| var resourceStream = assembly.GetManifestResourceStream("Cucumber.HtmlFormatter.Resources." + name); | ||
| var resource = new StreamReader(resourceStream).ReadToEnd(); | ||
| return resource; | ||
| } | ||
|
|
||
|
|
||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've changed this to added. It doesn't look like anything was removed. Is that correct w.r.t the guidance on http://semver.org?