feat: CI/CD pipeline setup complete - Ansible playbooks updated, secrets configured, workflow ready

This commit is contained in:
2025-10-31 01:39:24 +01:00
parent 55c04e4fd0
commit e26eb2aa12
601 changed files with 44184 additions and 32477 deletions

View File

@@ -0,0 +1,242 @@
<?php
declare(strict_types=1);
namespace App\Framework\Console\Components;
/**
* Parses ANSI escape sequences for mouse and keyboard events
*/
final class InputParser
{
/**
* Read and parse input from STDIN using stream_select()
*
* @return MouseEvent|KeyEvent|null Returns parsed event or null if no input available
*/
public function readEvent(): MouseEvent|KeyEvent|null
{
// Use stream_select for non-blocking I/O
$read = [STDIN];
$write = null;
$except = null;
// Wait up to 0.01 seconds (10ms) for input
$result = stream_select($read, $write, $except, 0, 10000);
if ($result === false || $result === 0 || !in_array(STDIN, $read, true)) {
return null;
}
$originalBlocking = stream_get_meta_data(STDIN)['blocked'] ?? true;
stream_set_blocking(STDIN, false);
try {
$firstChar = fgetc(STDIN);
if ($firstChar === false) {
return null;
}
// Check for escape sequence
if ($firstChar === "\033") {
return $this->parseEscapeSequence($firstChar);
}
// Check for Ctrl+C (ASCII 3)
if ($firstChar === "\003") {
stream_set_blocking(STDIN, $originalBlocking);
return new KeyEvent(key: 'C', ctrl: true);
}
// Regular character
stream_set_blocking(STDIN, $originalBlocking);
return new KeyEvent(key: $firstChar);
} finally {
stream_set_blocking(STDIN, $originalBlocking);
}
}
/**
* Parse escape sequence (mouse or keyboard)
*/
private function parseEscapeSequence(string $firstChar): MouseEvent|KeyEvent|null
{
$sequence = $firstChar;
// Read next character with timeout
$next = $this->readCharWithTimeout();
if ($next === null) {
return new KeyEvent(key: "\033");
}
$sequence .= $next;
// Mouse events start with \e[<
if ($next === '[') {
$third = $this->readCharWithTimeout();
if ($third === '<') {
return $this->parseMouseEvent($sequence . $third);
}
// Keyboard escape sequence like \e[A (arrow up)
if ($third !== null) {
$sequence .= $third;
return $this->parseKeyboardSequence($sequence);
}
}
// Just escape key
return new KeyEvent(key: "\033");
}
/**
* Parse SGR mouse event: \e[<b;x;yM (press) or \e[<b;x;ym (release)
*/
private function parseMouseEvent(string $prefix): MouseEvent|null
{
$buffer = '';
$timeout = 10000; // 10ms
$startTime = microtime(true) * 1000;
// Read until we get 'M' or 'm'
while (true) {
$char = fgetc(STDIN);
if ($char === false) {
// Check timeout
$elapsed = (microtime(true) * 1000) - $startTime;
if ($elapsed > $timeout) {
return null;
}
usleep(1000);
continue;
}
$buffer .= $char;
// Mouse event ends with 'M' (press) or 'm' (release)
if ($char === 'M' || $char === 'm') {
break;
}
// Safety: limit buffer size
if (strlen($buffer) > 20) {
return null;
}
}
// Parse format: b;x;y where b is button code, x and y are coordinates
// Remove the final M/m
$data = substr($buffer, 0, -1);
$parts = explode(';', $data);
if (count($parts) < 3) {
return null;
}
$buttonCode = (int) $parts[0];
$x = (int) $parts[1];
$y = (int) $parts[2];
// Decode button and modifiers
// Bit flags in button code:
// Bit 0-1: Button (0=left, 1=middle, 2=right, 3=release)
// Bit 2: Shift
// Bit 3: Meta/Alt
// Bit 4: Ctrl
// Bit 5-6: Polarity (64=scroll up, 65=scroll down)
$button = $buttonCode & 0x03;
$shift = ($buttonCode & 0x04) !== 0;
$alt = ($buttonCode & 0x08) !== 0;
$ctrl = ($buttonCode & 0x10) !== 0;
// Handle scroll events (button codes 64 and 65)
if ($buttonCode >= 64 && $buttonCode <= 65) {
$button = $buttonCode;
}
$pressed = $buffer[-1] === 'M';
return new MouseEvent(
x: $x,
y: $y,
button: $button,
pressed: $pressed,
shift: $shift,
ctrl: $ctrl,
alt: $alt
);
}
/**
* Parse keyboard escape sequence (arrow keys, function keys, etc.)
*/
private function parseKeyboardSequence(string $sequence): KeyEvent
{
// Map common escape sequences
$keyMap = [
"\033[A" => 'ArrowUp',
"\033[B" => 'ArrowDown',
"\033[C" => 'ArrowRight',
"\033[D" => 'ArrowLeft',
"\033[H" => 'Home',
"\033[F" => 'End',
"\033[5~" => 'PageUp',
"\033[6~" => 'PageDown',
"\033[3~" => 'Delete',
"\n" => 'Enter',
"\r" => 'Enter',
"\033" => 'Escape',
"\t" => 'Tab',
];
// Check if we need to read more characters
if (strlen($sequence) >= 3 && in_array($sequence[2], ['5', '6', '3'], true)) {
$fourth = $this->readCharWithTimeout();
if ($fourth !== null) {
$sequence .= $fourth;
}
}
// Check for Enter key
if ($sequence === "\n" || $sequence === "\r") {
return new KeyEvent(key: 'Enter', code: "\n");
}
// Check for Escape
if ($sequence === "\033") {
return new KeyEvent(key: 'Escape', code: "\033");
}
// Map to known key
if (isset($keyMap[$sequence])) {
return new KeyEvent(key: $keyMap[$sequence], code: $sequence);
}
// Unknown sequence, return as-is
return new KeyEvent(key: $sequence, code: $sequence);
}
/**
* Read a single character with small timeout
*/
private function readCharWithTimeout(int $timeoutMs = 10): ?string
{
$startTime = microtime(true) * 1000;
while (true) {
$char = fgetc(STDIN);
if ($char !== false) {
return $char;
}
$elapsed = (microtime(true) * 1000) - $startTime;
if ($elapsed > $timeoutMs) {
return null;
}
usleep(1000); // 1ms
}
}
}