Ionite
1 year ago
10 changed files with 369 additions and 18 deletions
@ -0,0 +1,49 @@ |
|||||||
|
""" |
||||||
|
Startup site customization for Stability Matrix. |
||||||
|
|
||||||
|
Currently this installs an audit hook to notify the parent process when input() is called, |
||||||
|
so we can prompt the user to enter something. |
||||||
|
""" |
||||||
|
|
||||||
|
import sys |
||||||
|
|
||||||
|
# Application Program Command escape sequence |
||||||
|
# This wraps messages sent to the parent process. |
||||||
|
esc_apc = "\x9F" |
||||||
|
esc_prefix = "[SM;" |
||||||
|
esc_st = "\x9C" |
||||||
|
|
||||||
|
|
||||||
|
def send_apc(msg: str): |
||||||
|
"""Send an Application Program Command to the parent process.""" |
||||||
|
sys.stdout.flush() |
||||||
|
sys.stdout.write(esc_apc + esc_prefix + msg + esc_st) |
||||||
|
sys.stdout.flush() |
||||||
|
|
||||||
|
|
||||||
|
def send_apc_input(prompt: str): |
||||||
|
"""Apc message for input() prompt.""" |
||||||
|
send_apc('{"type":"input","data":"' + str(prompt) + '"}') |
||||||
|
|
||||||
|
|
||||||
|
def audit(event: str, *args): |
||||||
|
"""Main audit hook function.""" |
||||||
|
# https://docs.python.org/3/library/functions.html#input |
||||||
|
# input() raises audit event `builtins.input` with args (prompt: str) *before* reading from stdin. |
||||||
|
# `builtins.input/result` raised after reading from stdin. |
||||||
|
|
||||||
|
if event == "builtins.input": |
||||||
|
try: |
||||||
|
prompts = args[0] if args else () |
||||||
|
prompt = "".join(prompts) |
||||||
|
send_apc_input(prompt) |
||||||
|
except Exception: |
||||||
|
pass |
||||||
|
|
||||||
|
|
||||||
|
# Reconfigure stdout to UTF-8 |
||||||
|
# noinspection PyUnresolvedReferences |
||||||
|
sys.stdout.reconfigure(encoding="utf-8") |
||||||
|
|
||||||
|
# Install the audit hook |
||||||
|
sys.addaudithook(audit) |
@ -0,0 +1,12 @@ |
|||||||
|
using System.Text.Json.Serialization; |
||||||
|
|
||||||
|
namespace StabilityMatrix.Core.Processes; |
||||||
|
|
||||||
|
public readonly struct ApcMessage |
||||||
|
{ |
||||||
|
[JsonPropertyName("type")] |
||||||
|
public required ApcType Type { get; init; } |
||||||
|
|
||||||
|
[JsonPropertyName("data")] |
||||||
|
public required string Data { get; init; } |
||||||
|
} |
@ -0,0 +1,55 @@ |
|||||||
|
using System.Diagnostics; |
||||||
|
using System.Diagnostics.CodeAnalysis; |
||||||
|
using System.Text; |
||||||
|
using System.Text.Json; |
||||||
|
|
||||||
|
namespace StabilityMatrix.Core.Processes; |
||||||
|
|
||||||
|
/// <summary> |
||||||
|
/// Parse escaped messages from subprocess |
||||||
|
/// The message standard: |
||||||
|
/// - Message events are prefixed with char 'APC' (9F) |
||||||
|
/// - Followed by '[SM;' |
||||||
|
/// - Json dict string of 2 strings, 'type' and 'data' |
||||||
|
/// - Ends with char 'ST' (9C) |
||||||
|
/// </summary> |
||||||
|
[SuppressMessage("ReSharper", "MemberCanBePrivate.Global")] |
||||||
|
internal static class ApcParser |
||||||
|
{ |
||||||
|
public const char ApcEscape = (char) 0x9F; |
||||||
|
public const string IdPrefix = "[SM;"; |
||||||
|
public const char StEscape = (char) 0x9C; |
||||||
|
|
||||||
|
/// <summary> |
||||||
|
/// Attempts to extract an APC message from the given text |
||||||
|
/// </summary> |
||||||
|
/// <returns>ApcMessage struct</returns> |
||||||
|
public static bool TryParse(string text, out ApcMessage? message) |
||||||
|
{ |
||||||
|
message = null; |
||||||
|
var startIndex = text.IndexOf(ApcEscape); |
||||||
|
if (startIndex == -1) return false; |
||||||
|
|
||||||
|
// Check the IdPrefix follows the ApcEscape |
||||||
|
var idIndex = text.IndexOf(IdPrefix, startIndex + 1, StringComparison.Ordinal); |
||||||
|
if (idIndex == -1) return false; |
||||||
|
|
||||||
|
// Get the end index (ST escape) |
||||||
|
var stIndex = text.IndexOf(StEscape, idIndex + IdPrefix.Length); |
||||||
|
if (stIndex == -1) return false; |
||||||
|
|
||||||
|
// Extract the json string (between idIndex and stIndex) |
||||||
|
var json = text.Substring(idIndex + IdPrefix.Length, stIndex - idIndex - IdPrefix.Length); |
||||||
|
|
||||||
|
try |
||||||
|
{ |
||||||
|
message = JsonSerializer.Deserialize<ApcMessage>(json); |
||||||
|
return true; |
||||||
|
} |
||||||
|
catch (Exception e) |
||||||
|
{ |
||||||
|
Debug.WriteLine($"Failed to parse APC message: {e.Message}"); |
||||||
|
return false; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,11 @@ |
|||||||
|
using System.Runtime.Serialization; |
||||||
|
using System.Text.Json.Serialization; |
||||||
|
|
||||||
|
namespace StabilityMatrix.Core.Processes; |
||||||
|
|
||||||
|
[JsonConverter(typeof(JsonStringEnumConverter))] |
||||||
|
public enum ApcType |
||||||
|
{ |
||||||
|
[EnumMember(Value = "input")] |
||||||
|
Input = 1, |
||||||
|
} |
Loading…
Reference in new issue