Add console support

git-svn-id: https://svn.fournier38.fr/svn/ProgSVN/trunk@4227 bf3deb0d-5f1a-0410-827f-c0cc1f45334c
This commit is contained in:
2018-06-08 14:44:13 +00:00
parent c81a7f3e2f
commit 8baf1bdf54

340
console.php Normal file
View File

@@ -0,0 +1,340 @@
<?php
/** DomFramework
* @package domframework
* @author Dominique Fournier <dominique@fournier38.fr>
*/
/** Allow to manage a linux Console to have a minimal but working text interface
* When using this class, you must use the $console::echo method and not
* display directely on screen
*/
class console
{
// PROPERTIES
// {{{
/** Save the initial stty value
*/
private $initSttyState;
/** Line Content
*/
private $lineContent = "";
/** If true, display each char the user has pressed (echo mode)
*/
private $echoMode = true;
/** List of non printable chars in decimal. The non printable chars are not
* displayed but are correctely captured
*/
private $nonWriteableChar = array (1, 2, 4, 6, 8, 9, 16, 18,
20, 21, 22, 23, 24, 25,
127);
// }}}
/** The constructor init the console.
* Check if we have the rights to execute, if wa have in cli...
*/
public function __construct ()
// {{{
{
if (! function_exists ("exec"))
throw $this->ConsoleException ("No exec support in PHP");
$this->initSttyState = exec ("stty -g");
// Set the terminal to return the value each time a key is pressed.
// Do not display anything, so we don't see the characters when the user is
// deleting.
exec ("stty -echo -icanon min 1 time 0");
}
// }}}
/** The destructor return the terminal to initial state
*/
public function __destruct ()
// {{{
{
exec ("stty $this->initSttyState");
}
// }}}
/** Each time a key is pressed by the user, display the value on screen (echo)
*/
public function setEcho ()
// {{{
{
$this->echoMode = true;
}
// }}}
/** Each time a key is pressed by the user, DO NOT display the value on screen
* (echo disabled)
*/
public function unsetEcho ()
// {{{
{
$this->echoMode = false;
}
// }}}
/** Display a text on screen
* @param string $message The message to display
*/
public function echo ($message)
// {{{
{
echo $message;
$this->lineContent .= $message;
}
// }}}
/** Wait one valid character from the user.
* The non printable chars are not displayed, nor returned
* The ESC Sequences are skipped
* @return the pressed char
*/
public function getc ()
// {{{
{
$char = $this->getKey ();
while (in_array (ord ($char), $this->nonWriteableChar))
$char = $this->getKey ();
return $char;
}
// }}}
/** Wait one key pressed by the user. If the key pressed is an ESC sequence,
* return this sequence
* The non printable chars are not displayed, but are correctely returned
* @return the pressed char
*/
public function getKey ()
// {{{
{
$char = fgetc (STDIN);
if ($char === chr (27))
{
$sequence = $char;
// Start an ESC CSI sequence. Do not display it, just return it, complete.
// ESC [ is followed by any number (including none) of "parameter bytes"
// in the range 0x300x3F (ASCII 09:;<=>?), then by any number of
// "intermediate bytes" in the range 0x200x2F (ASCII space and
// !"#$%&'()*+,-./), then finally by a single "final byte" in the range
// 0x400x7E (ASCII @AZ[\]^_`az{|}~).[14]:5.4
$char = fgetc (STDIN);
if ($char !== chr (91))
$this->consoleException ("Invalid ESC CSI sequence provided");
$sequence .= $char;
$char = fgetc (STDIN);
while (ord ($char) < 64 || ord ($char) > 126)
{
$sequence .= $char;
$char = fgetc (STDIN);
}
$sequence .= $char;
return $sequence;
}
if ($this->echoMode && ! in_array (ord ($char), $this->nonWriteableChar))
echo $char;
return $char;
}
// }}}
/** Get the line of characters pressed by the user and return the result.
* Stop when the user valid by \n or one of the stoppers chars. If the
* stoppers chars are set, the last char of the returned string is the
* stopper char. If not set, the method do not return \n at the end.
* Manage correctely the backspace, the Ctrl+W to remove word...
* @param string $stopperChar The stopper chars.
* @return The typed string
*/
public function gets ($stopperChar = false)
// {{{
{
// Gets can not delete chars before the call. Keep the prompt (if exists)
$prompt = $this->lineContent;
$minLength = strlen ($this->lineContent);
// The cursor position from first char of line.
$cursorPos = $minLength;
$string = "";
while (1)
{
$char = $this->getKey ();
if ($stopperChar === false && $char === "\n")
{
// End of process without stopperChars
$this->lineContent = "";
break;
}
if ($stopperChar !== false && in_array ($char, str_split ($stopperChar)))
{
// End of process with stopperChars
$this->lineContent = "";
$string .= $char;
break;
}
elseif (ord($char) === 21)
{
// Empty line next to prompt (Ctrl+U)
// {{{
$string = substr ($string, $cursorPos - $minLength);
echo "\r".str_repeat (" ", strlen ($this->lineContent))."\r";
$this->lineContent = $prompt.$string;
echo $this->lineContent;
$cursorPos = $minLength;
// Move the cursor on the correct position
echo "\r".str_repeat (chr (27).chr (91).chr (67), $cursorPos);
// }}}
}
elseif (ord($char) === 23)
{
// Remove the last word (Ctrl+W)
// TODO : Cursor management !!
$tmp = substr ($string, 0, $cursorPos - $minLength);
$end = substr ($string, $cursorPos - $minLength);
$tmp = rtrim ($tmp);
$pos = strrpos ($tmp, " ");
$string = substr ($string, 0, $pos).$end;
$cursorPos = $minLength + $pos;
if ($this->echoMode)
{
echo "\r".str_repeat (" ", strlen ($this->lineContent))."\r";
$this->lineContent = $prompt.$string;
echo $this->lineContent;
// Move the cursor on the correct position
echo "\r".str_repeat (chr (27).chr (91).chr (67), $cursorPos);
}
}
elseif (ord($char) === 127)
{
// Remove the previous char (Backspace)
// {{{
if (strlen ($this->lineContent) < $minLength + 1)
continue;
if ($cursorPos < $minLength +1)
continue;
$cursorPos--;
$strArr = str_split ($string);
unset ($strArr[$cursorPos - $minLength]);
$string = implode ($strArr);
if ($this->echoMode)
{
echo "\r".str_repeat (" ", strlen ($this->lineContent))."\r";
$this->lineContent = $prompt.$string;
echo $this->lineContent;
// Move the cursor on the correct position
echo "\r".str_repeat (chr (27).chr (91).chr (67), $cursorPos);
}
// }}}
}
elseif (ord ($char{0}) === 27)
{
// ESC SEQUENCE
/*file_put_contents ("/tmp/debug", "SEQ CHAR=", FILE_APPEND);
foreach (str_split ($char) as $key)
file_put_contents ("/tmp/debug", ord ($key)." ", FILE_APPEND);
file_put_contents ("/tmp/debug", "\n", FILE_APPEND);*/
if ($char === chr (27).chr (91).chr (67))
{
// Cursor right
// {{{
if ($cursorPos < strlen ($this->lineContent))
{
echo $char;
$cursorPos++;
}
// }}}
}
elseif ($char === chr (27).chr (91).chr (68))
{
// Cursor left
// {{{
if ($cursorPos > $minLength)
{
echo $char;
$cursorPos--;
}
// }}}
}
elseif ($char === chr (27).chr (91).chr (70))
{
// End key
// {{{
$cursorPos = $minLength + strlen ($string);
// Move the cursor on the correct position
echo "\r".str_repeat (chr (27).chr (91).chr (67), $cursorPos);
// }}}
}
elseif ($char === chr (27).chr (91).chr (72))
{
// Home key
// {{{
$cursorPos = $minLength;
// Move the cursor on the correct position
echo "\r".str_repeat (chr (27).chr (91).chr (67), $cursorPos);
// }}}
}
elseif ($char === chr (27).chr (91).chr (51).chr (126))
{
// Remove the char under the cursor (Delete)
// {{{
if ($cursorPos >= strlen ($this->lineContent))
continue;
$cursorPos;
$strArr = str_split ($string);
unset ($strArr[$cursorPos - $minLength]);
$string = implode ($strArr);
if ($this->echoMode)
{
echo "\r".str_repeat (" ", strlen ($this->lineContent))."\r";
$this->lineContent = $prompt.$string;
echo $this->lineContent;
// Move the cursor on the correct position
echo "\r".str_repeat (chr (27).chr (91).chr (67), $cursorPos);
}
// }}}
}
}
else
{
// Normal char : Add it to the string
// {{{
$strArr = str_split ($string);
$firstArr = array_slice ($strArr, 0, $cursorPos - $minLength);
$lastArr = array_slice ($strArr, $cursorPos - $minLength);
$insertArr = array ($char);
$strArr = array_merge ($firstArr, $insertArr, $lastArr);
$string = implode ($strArr);
$cursorPos++;
echo "\r";
$this->lineContent = $prompt.$string;
echo $this->lineContent;
// Move the cursor on the correct position
echo "\r".str_repeat (chr (27).chr (91).chr (67), $cursorPos);
// }}}
}
}
return $string;
}
// }}}
/** Clear the existing line
*/
public function clearLine ()
// {{{
{
echo "\r".str_repeat (" ", strlen ($this->lineContent))."\r";
$this->lineContent = "";
}
// }}}
/** Error management
* @param string $message The message to throw in the exception
*/
public function consoleException ($message)
// {{{
{
throw new \Exception ($message, 500);
}
// }}}
}