From ccc2462b8fe7802d88ae986943c80187ffa914b3 Mon Sep 17 00:00:00 2001 From: Martijn Laarman Date: Sat, 6 Jan 2024 12:49:03 +0100 Subject: [PATCH] ensure lineout does not mix stderr and stdout --- src/Proc/BufferedObservableProcess.cs | 11 +++--- src/Proc/ObservableProcess.cs | 23 +++++++++-- src/Proc/Proc.Start.cs | 5 ++- src/Proc/Std/ConsoleOutColorWriter.cs | 57 +++++++++++++-------------- 4 files changed, 56 insertions(+), 40 deletions(-) diff --git a/src/Proc/BufferedObservableProcess.cs b/src/Proc/BufferedObservableProcess.cs index b229975..0cb5345 100644 --- a/src/Proc/BufferedObservableProcess.cs +++ b/src/Proc/BufferedObservableProcess.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics; using System.Reactive.Disposables; using System.Reactive.Linq; using System.Threading; @@ -16,9 +17,7 @@ namespace ProcNet /// /// This catches all cases where would fall short to capture all the process output. /// -#pragma warning disable 1574 - /// contains the current char[] buffer which could be fed to directly. -#pragma warning restore 1574 + /// contains the current char[] buffer which could be fed to directly. /// /// Note that there is a subclass that allows you to subscribe console output per line /// instead whilst still reading the actual process output using asynchronous stream readers. @@ -36,11 +35,11 @@ public BufferedObservableProcess(StartArguments startArguments) : base(startArgu private TimeSpan? WaitForStreamReadersTimeout => StartArguments.WaitForStreamReadersTimeout; /// - /// Expert level setting: the maximum number of characters to read per itteration. Defaults to 256 + /// Expert level setting: the maximum number of characters to read per iteration. Defaults to 1024 /// - public int BufferSize { get; set; } = 256; + public int BufferSize { get; set; } = 1024; - private CancellationTokenSource _ctx = new CancellationTokenSource(); + private CancellationTokenSource _ctx = new(); private Task _stdOutSubscription; private Task _stdErrSubscription; private IObserver _observer; diff --git a/src/Proc/ObservableProcess.cs b/src/Proc/ObservableProcess.cs index a890229..c8efa90 100644 --- a/src/Proc/ObservableProcess.cs +++ b/src/Proc/ObservableProcess.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; @@ -89,11 +90,25 @@ public IDisposable Subscribe(IObserver observerLines, IObserver o.EndsWithNewLine || o.StartsWithCarriage || BufferBoundary(_bufferStdOutRemainder, _bufferStdErrRemainder)); var buffered = published.Buffer(boundaries); var newlines = buffered - .Select(c => + .SelectMany(c => { - if (c.Count == 0) return null; - var line = new string(c.SelectMany(o => o.Characters).ToArray()); - return new LineOut(c.First().Error, line.TrimEnd(NewlineChars)); + if (c == null || c.Count == 0) + return Array.Empty(); + if (c.Count == 1) + { + var cOut = c.First(); + var chars = new string(cOut.Characters).TrimEnd(NewlineChars); + return new[] { new LineOut(cOut.Error, chars) }; + } + + return c + .GroupBy(l => l.Error) + .Select(g => (g.Key, new string(g.SelectMany(o => o.Characters).ToArray()))) + .SelectMany(kv => + kv.Item2.TrimEnd(NewlineChars).Split(NewlineChars) + .Select(line => new LineOut(kv.Key, line.TrimEnd(NewlineChars))) + ) + .ToArray(); }) .TakeWhile(KeepBufferingLines) .Where(l => l != null) diff --git a/src/Proc/Proc.Start.cs b/src/Proc/Proc.Start.cs index e148220..17c5310 100644 --- a/src/Proc/Proc.Start.cs +++ b/src/Proc/Proc.Start.cs @@ -92,7 +92,10 @@ public static ProcessCaptureResult Start(StartArguments arguments, TimeSpan time var consoleOut = new List(); composite.Add(process); composite.Add(process.SubscribeLinesAndCharacters( - consoleOut.Add, + l => + { + consoleOut.Add(l); + }, e => seenException = e, consoleOutWriter.Write, consoleOutWriter.Write diff --git a/src/Proc/Std/ConsoleOutColorWriter.cs b/src/Proc/Std/ConsoleOutColorWriter.cs index c3d7bcb..70d21df 100644 --- a/src/Proc/Std/ConsoleOutColorWriter.cs +++ b/src/Proc/Std/ConsoleOutColorWriter.cs @@ -1,44 +1,43 @@ using System; -namespace ProcNet.Std +namespace ProcNet.Std; + +public class ConsoleOutColorWriter : ConsoleOutWriter { - public class ConsoleOutColorWriter : ConsoleOutWriter - { - public static ConsoleOutColorWriter Default { get; } = new ConsoleOutColorWriter(); + public static ConsoleOutColorWriter Default { get; } = new(); - private readonly object _lock = new object(); - public override void Write(ConsoleOut consoleOut) + private readonly object _lock = new(); + public override void Write(ConsoleOut consoleOut) + { + lock (_lock) { - lock (_lock) + Console.ResetColor(); + if (consoleOut.Error) { - if (consoleOut.Error) - { - Console.ForegroundColor = ConsoleColor.Red; - consoleOut.CharsOrString(ErrorCharacters, ErrorLine); - } - else - { - Console.ResetColor(); - consoleOut.CharsOrString(OutCharacters, OutLine); - } - Console.ResetColor(); + Console.ForegroundColor = ConsoleColor.Yellow; + consoleOut.CharsOrString(ErrorCharacters, ErrorLine); } + else + consoleOut.CharsOrString(OutCharacters, OutLine); + + Console.ResetColor(); } + } - public override void Write(Exception e) + public override void Write(Exception e) + { + if (!(e is CleanExitExceptionBase ee)) throw e; + lock (_lock) { - if (!(e is CleanExitExceptionBase ee)) throw e; - lock (_lock) + Console.ResetColor(); + Console.ForegroundColor = ConsoleColor.Red; + Console.Write(e.Message); + if (!string.IsNullOrEmpty(ee.HelpText)) { - Console.ForegroundColor = ConsoleColor.Red; - Console.Write(e.Message); - if (!string.IsNullOrEmpty(ee.HelpText)) - { - Console.ForegroundColor = ConsoleColor.DarkRed; - Console.Write(ee.HelpText); - } - Console.ResetColor(); + Console.ForegroundColor = ConsoleColor.DarkRed; + Console.Write(ee.HelpText); } + Console.ResetColor(); } } }