3 * Zend Framework (http://framework.zend.com/)
5 * @link http://github.com/zendframework/zf2 for the canonical source repository
6 * @copyright Copyright (c) 2005-2013 Zend Technologies USA Inc. (http://www.zend.com)
7 * @license http://framework.zend.com/license/new-bsd New BSD License
10 namespace Zend\Console\Adapter
;
12 use Zend\Console\Charset
;
13 use Zend\Console\Charset\CharsetInterface
;
14 use Zend\Console\Exception
;
17 * MS Windows with ANSICON console adapter
19 * This adapter requires ANSICON extension to be installed. It can be obtained from:
20 * https://github.com/adoxa/ansicon
22 * ANSICON has to be loaded and enabled before using this adapter. It's best to install
23 * it using following command:
26 * Console should not run in UTF8 code page (65001), because ANSICON does not behave well with it.
27 * It's best to use non-unicode code page 437, 850, 851, 852 or similar. Run "help mode" for more
28 * information on how to change Windows console code page.
30 class WindowsAnsicon
extends Posix
33 * Whether or not mbstring is enabled
37 protected static $hasMBString;
40 * Results of mode command
44 protected $modeResult;
47 * Determine and return current console width.
51 public function getWidth()
58 // Try to read console size from ANSICON env var
59 if (preg_match('/\((\d+)x/', getenv('ANSICON'), $matches)) {
62 $width = AbstractAdapter
::getWidth();
69 * Determine and return current console height.
73 public function getHeight()
80 // Try to read console size from ANSICON env var
81 if (preg_match('/\(\d+x(\d+)/', getenv('ANSICON'), $matches)) {
82 $height = $matches[1];
84 $height = AbstractAdapter
::getHeight();
90 * Run and cache results of mode command
94 protected function runModeCommand()
96 exec('mode', $output, $return);
97 if ($return ||
!count($output)) {
98 $this->modeResult
= '';
100 $this->modeResult
= trim(implode('', $output));
105 * Check if console is UTF-8 compatible
109 public function isUtf8()
111 // Try to read code page info from "mode" command
112 if ($this->modeResult
=== null) {
113 $this->runModeCommand();
116 if (preg_match('/Code page\:\s+(\d+)/', $this->modeResult
, $matches)) {
117 return (int) $matches[1] == 65001;
124 * Return current console window title.
128 public function getTitle()
130 // Try to use powershell to retrieve console window title
131 exec('powershell -command "write $Host.UI.RawUI.WindowTitle"', $output, $result);
132 if ($result ||
!$output) {
136 return trim($output, "\r\n");
140 * Clear console screen
142 public function clear()
144 echo chr(27) . '[1J' . chr(27) . '[u';
148 * Clear line at cursor position
150 public function clearLine()
152 echo chr(27) . '[1K';
156 * Set Console charset to use.
158 * @param CharsetInterface $charset
160 public function setCharset(CharsetInterface
$charset)
162 $this->charset
= $charset;
166 * Get charset currently in use by this adapter.
169 * @return CharsetInterface $charset
171 public function getCharset()
173 if ($this->charset
=== null) {
174 $this->charset
= $this->getDefaultCharset();
177 return $this->charset
;
181 * @return Charset\AsciiExtended
183 public function getDefaultCharset()
185 return new Charset\
AsciiExtended();
189 * Read a single character from the console input
191 * @param string|null $mask A list of allowed chars
193 * @throws Exception\RuntimeException
195 public function readChar($mask = null)
197 // Decide if we can use `choice` tool
198 $useChoice = $mask !== null && preg_match('/^[a-zA-Z0-9]+$/D', $mask);
201 // Use Windows 98+ "choice" command, which allows for reading a
202 // single character matching a mask, but is limited to lower ASCII
205 exec('choice /n /cs /c:' . $mask, $output, $return);
206 if ($return == 255 ||
$return < 1 ||
$return > strlen($mask)) {
207 throw new Exception\
RuntimeException('"choice" command failed to run. Are you using Windows XP or newer?');
210 // Fetch the char from mask
211 $char = substr($mask, $return - 1, 1);
212 } while ("" === $char ||
($mask !== null && false === strstr($mask, $char)));
217 // Try to use PowerShell, giving it console access. Because PowersShell
218 // interpreter can take a short while to load, we are emptying the
219 // whole keyboard buffer and picking the last key that has been pressed
220 // before or after PowerShell command has started. The ASCII code for
221 // that key is then converted to a character.
222 if ($mask === null) {
224 'powershell -NonInteractive -NoProfile -NoLogo -OutputFormat Text -Command "'
225 . 'while ($Host.UI.RawUI.KeyAvailable) {$key = $Host.UI.RawUI.ReadKey(\'NoEcho,IncludeKeyDown\');}'
226 . 'write $key.VirtualKeyCode;'
232 // Retrieve char from the result.
233 $char = !empty($result) ?
implode('', $result) : null;
235 if (!empty($char) && !$return) {
236 // We have obtained an ASCII code, convert back to a char ...
239 // ... and return it...
243 // Windows and DOS will return carriage-return char (ASCII 13) when
244 // the user presses [ENTER] key, but Console Adapter user might
245 // have provided a \n Newline (ASCII 10) in the mask, to allow
246 // [ENTER]. We are going to replace all CR with NL to conform.
247 $mask = strtr($mask, "\n", "\r");
249 // Prepare a list of ASCII codes from mask chars
250 $asciiMask = array_map(function ($char) {
252 }, str_split($mask));
253 $asciiMask = array_unique($asciiMask);
255 // Char mask filtering is now handled by the PowerShell itself,
256 // because it's a much faster method than invoking PS interpreter
257 // after each mismatch. The command should return ASCII code of a
259 $result = $return = null;
261 'powershell -NonInteractive -NoProfile -NoLogo -OutputFormat Text -Command "'
262 . '[int[]] $mask = '.join(',', $asciiMask).';'
264 . '$key = $Host.UI.RawUI.ReadKey(\'NoEcho,IncludeKeyDown\').VirtualKeyCode;'
265 . '} while( !($mask -contains $key) );'
272 $char = !empty($result) ?
trim(implode('', $result)) : null;
274 if (!$return && $char && ($mask === null ||
in_array($char, $asciiMask))) {
275 // We have obtained an ASCII code, check if it is a carriage
276 // return and normalize it as needed
281 // Convert to a character
284 // ... and return it...
289 // Fall back to standard input, which on Windows does not allow reading
290 // a single character. This is a limitation of Windows streams
291 // implementation (not PHP) and this behavior cannot be changed with a
292 // command like "stty", known to POSIX systems.
293 $stream = fopen('php://stdin', 'rb');
295 $char = fgetc($stream);
296 $char = substr(trim($char), 0, 1);
297 } while (!$char ||
($mask !== null && !stristr($mask, $char)));