using System.Text.RegularExpressions;
using StabilityMatrix.Core.Extensions;
namespace StabilityMatrix.Core.Processes;
public readonly record struct ProcessOutput
{
///
/// Parsed text with escape sequences and line endings removed
///
public required string Text { get; init; }
///
/// Optional Raw output,
/// mainly for debug and logging.
///
public string? RawText { get; init; }
///
/// True if output from stderr, false for stdout.
///
public bool IsStdErr { get; init; }
///
/// Count of newlines to append to the output.
/// (Currently not used)
///
public int NewLines { get; init; }
///
/// Instruction to clear last n lines
/// From carriage return '\r'
///
public int CarriageReturn { get; init; }
///
/// Instruction to move write cursor up n lines
/// From Ansi sequence ESC[#A where # is count of lines
///
public int CursorUp { get; init; }
///
/// Apc message sent from the subprocess
///
public ApcMessage? ApcMessage { get; init; }
public static ProcessOutput FromStdOutLine(string text)
{
return FromLine(text, false);
}
public static ProcessOutput FromStdErrLine(string text)
{
return FromLine(text, true);
}
private static ProcessOutput FromLine(string text, bool isStdErr)
{
// Parse APC message
if (ApcParser.TryParse(text, out var message))
{
// Override and return
return new ProcessOutput
{
RawText = text,
Text = text,
IsStdErr = isStdErr,
ApcMessage = message
};
}
// Normal parsing
var originalText = text;
// Remove \r from the beginning of the line, and add them to count
var clearLines = 0;
// Skip if starts with \r\n on windows
if (!text.StartsWith(Environment.NewLine))
{
clearLines += text.CountStart('\r');
text = text.TrimStart('\r');
}
// Also detect Ansi escape for cursor up, treat as clear lines also
var cursorUp = 0;
if (text.Contains('\u001b'))
{
var match = Regex.Match(text, @"\x1B\[(\d+)?A");
if (match.Success)
{
// Default to 1 if no count
cursorUp = int.TryParse(match.Groups[1].Value, out var n) ? n : 1;
// Set text to be everything up to the match
text = text[..match.Index];
}
}
return new ProcessOutput
{
RawText = originalText,
Text = text,
IsStdErr = isStdErr,
CarriageReturn = clearLines,
CursorUp = cursorUp,
};
}
}