torrentpier-lts/library/Zend/Console/Adapter/WindowsAnsicon.php

302 lines
8.9 KiB
PHP

<?php
/**
* Zend Framework (http://framework.zend.com/)
*
* @link http://github.com/zendframework/zf2 for the canonical source repository
* @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
namespace Zend\Console\Adapter;
use Zend\Console\Charset;
use Zend\Console\Charset\CharsetInterface;
use Zend\Console\Exception;
/**
* MS Windows with ANSICON console adapter
*
* This adapter requires ANSICON extension to be installed. It can be obtained from:
* https://github.com/adoxa/ansicon
*
* ANSICON has to be loaded and enabled before using this adapter. It's best to install
* it using following command:
* ansicon -I
*
* Console should not run in UTF8 code page (65001), because ANSICON does not behave well with it.
* It's best to use non-unicode code page 437, 850, 851, 852 or similar. Run "help mode" for more
* information on how to change Windows console code page.
*/
class WindowsAnsicon extends Posix
{
/**
* Whether or not mbstring is enabled
*
* @var null|bool
*/
protected static $hasMBString;
/**
* Results of mode command
*
* @var mixed
*/
protected $modeResult;
/**
* Determine and return current console width.
*
* @return int
*/
public function getWidth()
{
static $width;
if ($width > 0) {
return $width;
}
// Try to read console size from ANSICON env var
if (preg_match('/\((\d+)x/', getenv('ANSICON'), $matches)) {
$width = $matches[1];
} else {
$width = AbstractAdapter::getWidth();
}
return $width;
}
/**
* Determine and return current console height.
*
* @return false|int
*/
public function getHeight()
{
static $height;
if ($height > 0) {
return $height;
}
// Try to read console size from ANSICON env var
if (preg_match('/\(\d+x(\d+)/', getenv('ANSICON'), $matches)) {
$height = $matches[1];
} else {
$height = AbstractAdapter::getHeight();
}
return $height;
}
/**
* Run and cache results of mode command
*
* @return void
*/
protected function runModeCommand()
{
exec('mode', $output, $return);
if ($return || !count($output)) {
$this->modeResult = '';
} else {
$this->modeResult = trim(implode('', $output));
}
}
/**
* Check if console is UTF-8 compatible
*
* @return bool
*/
public function isUtf8()
{
// Try to read code page info from "mode" command
if ($this->modeResult === null) {
$this->runModeCommand();
}
if (preg_match('/Code page\:\s+(\d+)/', $this->modeResult, $matches)) {
return (int) $matches[1] == 65001;
}
return false;
}
/**
* Return current console window title.
*
* @return string
*/
public function getTitle()
{
// Try to use powershell to retrieve console window title
exec('powershell -command "write $Host.UI.RawUI.WindowTitle"', $output, $result);
if ($result || !$output) {
return '';
}
return trim($output, "\r\n");
}
/**
* Clear console screen
*/
public function clear()
{
echo chr(27) . '[1J' . chr(27) . '[u';
}
/**
* Clear line at cursor position
*/
public function clearLine()
{
echo chr(27) . '[1K';
}
/**
* Set Console charset to use.
*
* @param CharsetInterface $charset
*/
public function setCharset(CharsetInterface $charset)
{
$this->charset = $charset;
}
/**
* Get charset currently in use by this adapter.
*
* @return CharsetInterface $charset
*/
public function getCharset()
{
if ($this->charset === null) {
$this->charset = $this->getDefaultCharset();
}
return $this->charset;
}
/**
* @return Charset\AsciiExtended
*/
public function getDefaultCharset()
{
return new Charset\AsciiExtended();
}
/**
* Read a single character from the console input
*
* @param string|null $mask A list of allowed chars
* @return string
* @throws Exception\RuntimeException
*/
public function readChar($mask = null)
{
// Decide if we can use `choice` tool
$useChoice = $mask !== null && preg_match('/^[a-zA-Z0-9]+$/D', $mask);
if ($useChoice) {
// Use Windows 98+ "choice" command, which allows for reading a
// single character matching a mask, but is limited to lower ASCII
// range.
do {
exec('choice /n /cs /c:' . $mask, $output, $return);
if ($return == 255 || $return < 1 || $return > strlen($mask)) {
throw new Exception\RuntimeException('"choice" command failed to run. Are you using Windows XP or newer?');
}
// Fetch the char from mask
$char = substr($mask, $return - 1, 1);
} while ("" === $char || ($mask !== null && false === strstr($mask, $char)));
return $char;
}
// Try to use PowerShell, giving it console access. Because PowersShell
// interpreter can take a short while to load, we are emptying the
// whole keyboard buffer and picking the last key that has been pressed
// before or after PowerShell command has started. The ASCII code for
// that key is then converted to a character.
if ($mask === null) {
exec(
'powershell -NonInteractive -NoProfile -NoLogo -OutputFormat Text -Command "'
. 'while ($Host.UI.RawUI.KeyAvailable) {$key = $Host.UI.RawUI.ReadKey(\'NoEcho,IncludeKeyDown\');}'
. 'write $key.VirtualKeyCode;'
. '"',
$result,
$return
);
// Retrieve char from the result.
$char = !empty($result) ? implode('', $result) : null;
if (!empty($char) && !$return) {
// We have obtained an ASCII code, convert back to a char ...
$char = chr($char);
// ... and return it...
return $char;
}
} else {
// Windows and DOS will return carriage-return char (ASCII 13) when
// the user presses [ENTER] key, but Console Adapter user might
// have provided a \n Newline (ASCII 10) in the mask, to allow
// [ENTER]. We are going to replace all CR with NL to conform.
$mask = strtr($mask, "\n", "\r");
// Prepare a list of ASCII codes from mask chars
$asciiMask = array_map(function ($char) {
return ord($char);
}, str_split($mask));
$asciiMask = array_unique($asciiMask);
// Char mask filtering is now handled by the PowerShell itself,
// because it's a much faster method than invoking PS interpreter
// after each mismatch. The command should return ASCII code of a
// matching key.
$result = $return = null;
exec(
'powershell -NonInteractive -NoProfile -NoLogo -OutputFormat Text -Command "'
. '[int[]] $mask = '.implode(',', $asciiMask).';'
. 'do {'
. '$key = $Host.UI.RawUI.ReadKey(\'NoEcho,IncludeKeyDown\').VirtualKeyCode;'
. '} while ( !($mask -contains $key) );'
. 'write $key;'
. '"',
$result,
$return
);
$char = !empty($result) ? trim(implode('', $result)) : null;
if (!$return && $char && ($mask === null || in_array($char, $asciiMask))) {
// We have obtained an ASCII code, check if it is a carriage
// return and normalize it as needed
if ($char == 13) {
$char = 10;
}
// Convert to a character
$char = chr($char);
// ... and return it...
return $char;
}
}
// Fall back to standard input, which on Windows does not allow reading
// a single character. This is a limitation of Windows streams
// implementation (not PHP) and this behavior cannot be changed with a
// command like "stty", known to POSIX systems.
$stream = fopen('php://stdin', 'rb');
do {
$char = fgetc($stream);
$char = substr(trim($char), 0, 1);
} while (!$char || ($mask !== null && !stristr($mask, $char)));
fclose($stream);
return $char;
}
}