PageRenderTime 41ms CodeModel.GetById 13ms RepoModel.GetById 1ms app.codeStats 0ms

/library/Zend/Console/Adapter/Windows.php

http://github.com/zendframework/zf2
PHP | 356 lines | 181 code | 47 blank | 128 comment | 45 complexity | 9e9969ddda87f5866826baae4c00a593 MD5 | raw file
Possible License(s): BSD-3-Clause
  1. <?php
  2. /**
  3. * Zend Framework (http://framework.zend.com/)
  4. *
  5. * @link http://github.com/zendframework/zf2 for the canonical source repository
  6. * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
  7. * @license http://framework.zend.com/license/new-bsd New BSD License
  8. */
  9. namespace Zend\Console\Adapter;
  10. use Zend\Console\Charset;
  11. use Zend\Console\Exception;
  12. class Windows extends Virtual
  13. {
  14. /**
  15. * Whether or not mbstring is enabled
  16. *
  17. * @var null|bool
  18. */
  19. protected static $hasMBString;
  20. /**
  21. * Results of probing system capabilities
  22. *
  23. * @var mixed
  24. */
  25. protected $probeResult;
  26. /**
  27. * Results of mode command
  28. *
  29. * @var mixed
  30. */
  31. protected $modeResult;
  32. /**
  33. * Determine and return current console width.
  34. *
  35. * @return int
  36. */
  37. public function getWidth()
  38. {
  39. static $width;
  40. if ($width > 0) {
  41. return $width;
  42. }
  43. // Try to read console size from "mode" command
  44. if ($this->probeResult === null) {
  45. $this->runProbeCommand();
  46. }
  47. if (count($this->probeResult) && (int) $this->probeResult[0]) {
  48. $width = (int) $this->probeResult[0];
  49. } else {
  50. $width = parent::getWidth();
  51. }
  52. return $width;
  53. }
  54. /**
  55. * Determine and return current console height.
  56. *
  57. * @return int
  58. */
  59. public function getHeight()
  60. {
  61. static $height;
  62. if ($height > 0) {
  63. return $height;
  64. }
  65. // Try to read console size from "mode" command
  66. if ($this->probeResult === null) {
  67. $this->runProbeCommand();
  68. }
  69. if (count($this->probeResult) && (int) $this->probeResult[1]) {
  70. $height = (int) $this->probeResult[1];
  71. } else {
  72. $height = parent::getheight();
  73. }
  74. return $height;
  75. }
  76. /**
  77. * Probe for system capabilities and cache results
  78. *
  79. * Run a Windows Powershell command that determines parameters of console window. The command is fed through
  80. * standard input (with echo) to prevent Powershell from creating a sub-thread and hanging PHP when run through
  81. * a debugger/IDE.
  82. *
  83. * @return void
  84. */
  85. protected function runProbeCommand()
  86. {
  87. exec(
  88. 'echo $size = $Host.ui.rawui.windowsize; write $($size.width) $($size.height) | powershell -NonInteractive -NoProfile -NoLogo -OutputFormat Text -Command -',
  89. $output,
  90. $return
  91. );
  92. if ($return || empty($output)) {
  93. $this->probeResult = '';
  94. } else {
  95. $this->probeResult = $output;
  96. }
  97. }
  98. /**
  99. * Run and cache results of mode command
  100. *
  101. * @return void
  102. */
  103. protected function runModeCommand()
  104. {
  105. exec('mode', $output, $return);
  106. if ($return || !count($output)) {
  107. $this->modeResult = '';
  108. } else {
  109. $this->modeResult = trim(implode('', $output));
  110. }
  111. }
  112. /**
  113. * Check if console is UTF-8 compatible
  114. *
  115. * @return bool
  116. */
  117. public function isUtf8()
  118. {
  119. // Try to read code page info from "mode" command
  120. if ($this->modeResult === null) {
  121. $this->runModeCommand();
  122. }
  123. if (preg_match('/Code page\:\s+(\d+)/', $this->modeResult, $matches)) {
  124. return (int) $matches[1] == 65001;
  125. }
  126. return false;
  127. }
  128. /**
  129. * Return current console window title.
  130. *
  131. * @return string
  132. */
  133. public function getTitle()
  134. {
  135. // Try to use powershell to retrieve console window title
  136. exec('powershell -command "write $Host.UI.RawUI.WindowTitle"', $output, $result);
  137. if ($result || !$output) {
  138. return '';
  139. }
  140. return trim($output, "\r\n");
  141. }
  142. /**
  143. * Set Console charset to use.
  144. *
  145. * @param Charset\CharsetInterface $charset
  146. */
  147. public function setCharset(Charset\CharsetInterface $charset)
  148. {
  149. $this->charset = $charset;
  150. }
  151. /**
  152. * Get charset currently in use by this adapter.
  153. *
  154. * @return Charset\CharsetInterface $charset
  155. */
  156. public function getCharset()
  157. {
  158. if ($this->charset === null) {
  159. $this->charset = $this->getDefaultCharset();
  160. }
  161. return $this->charset;
  162. }
  163. /**
  164. * @return Charset\AsciiExtended
  165. */
  166. public function getDefaultCharset()
  167. {
  168. return new Charset\AsciiExtended;
  169. }
  170. /**
  171. * Switch to utf-8 encoding
  172. *
  173. * @return void
  174. */
  175. protected function switchToUtf8()
  176. {
  177. shell_exec('mode con cp select=65001');
  178. }
  179. /**
  180. * Clear console screen
  181. */
  182. public function clear()
  183. {
  184. // Attempt to clear the screen using PowerShell command
  185. exec("powershell -NonInteractive -NoProfile -NoLogo -OutputFormat Text -Command Clear-Host", $output, $return);
  186. if ($return) {
  187. // Could not run powershell... fall back to filling the buffer with newlines
  188. echo str_repeat("\r\n", $this->getHeight());
  189. }
  190. }
  191. /**
  192. * Clear line at cursor position
  193. */
  194. public function clearLine()
  195. {
  196. echo "\r" . str_repeat(' ', $this->getWidth()) . "\r";
  197. }
  198. /**
  199. * Read a single character from the console input
  200. *
  201. * @param string|null $mask A list of allowed chars
  202. * @throws Exception\RuntimeException
  203. * @return string
  204. */
  205. public function readChar($mask = null)
  206. {
  207. // Decide if we can use `choice` tool
  208. $useChoice = $mask !== null && preg_match('/^[a-zA-Z0-9]+$/D', $mask);
  209. if ($useChoice) {
  210. // Use Windows 95+ "choice" command, which allows for reading a
  211. // single character matching a mask, but is limited to lower ASCII
  212. // range.
  213. do {
  214. exec('choice /n /cs /c:' . $mask, $output, $return);
  215. if ($return == 255 || $return < 1 || $return > strlen($mask)) {
  216. throw new Exception\RuntimeException('"choice" command failed to run. Are you using Windows XP or newer?');
  217. }
  218. // Fetch the char from mask
  219. $char = substr($mask, $return - 1, 1);
  220. } while ("" === $char || ($mask !== null && false === strstr($mask, $char)));
  221. return $char;
  222. }
  223. // Try to use PowerShell, giving it console access. Because PowersShell
  224. // interpreter can take a short while to load, we are emptying the
  225. // whole keyboard buffer and picking the last key that has been pressed
  226. // before or after PowerShell command has started. The ASCII code for
  227. // that key is then converted to a character.
  228. if ($mask === null) {
  229. exec(
  230. 'powershell -NonInteractive -NoProfile -NoLogo -OutputFormat Text -Command "'
  231. . 'while ($Host.UI.RawUI.KeyAvailable) {$key = $Host.UI.RawUI.ReadKey(\'NoEcho,IncludeKeyDown\');}'
  232. . 'write $key.VirtualKeyCode;'
  233. . '"',
  234. $result,
  235. $return
  236. );
  237. // Retrieve char from the result.
  238. $char = !empty($result) ? implode('', $result) : null;
  239. if (!empty($char) && !$return) {
  240. // We have obtained an ASCII code, convert back to a char ...
  241. $char = chr($char);
  242. // ... and return it...
  243. return $char;
  244. }
  245. } else {
  246. // Windows and DOS will return carriage-return char (ASCII 13) when
  247. // the user presses [ENTER] key, but Console Adapter user might
  248. // have provided a \n Newline (ASCII 10) in the mask, to allow [ENTER].
  249. // We are going to replace all CR with NL to conform.
  250. $mask = strtr($mask, "\n", "\r");
  251. // Prepare a list of ASCII codes from mask chars
  252. $asciiMask = array_map(function ($char) {
  253. return ord($char);
  254. }, str_split($mask));
  255. $asciiMask = array_unique($asciiMask);
  256. // Char mask filtering is now handled by the PowerShell itself,
  257. // because it's a much faster method than invoking PS interpreter
  258. // after each mismatch. The command should return ASCII code of a
  259. // matching key.
  260. $result = $return = null;
  261. exec(
  262. 'powershell -NonInteractive -NoProfile -NoLogo -OutputFormat Text -Command "'
  263. . '[int[]] $mask = ' . implode(',', $asciiMask) . ';'
  264. . 'do {'
  265. . '$key = $Host.UI.RawUI.ReadKey(\'NoEcho,IncludeKeyDown\').VirtualKeyCode;'
  266. . '} while ( !($mask -contains $key) );'
  267. . 'write $key;'
  268. . '"',
  269. $result,
  270. $return
  271. );
  272. $char = !empty($result) ? trim(implode('', $result)) : null;
  273. if (!$return && $char && ($mask === null || in_array($char, $asciiMask))) {
  274. // Normalize CR to LF
  275. if ($char == 13) {
  276. $char = 10;
  277. }
  278. // Convert to a char
  279. $char = chr($char);
  280. // ... and return it...
  281. return $char;
  282. }
  283. }
  284. // Fall back to standard input, which on Windows does not allow reading
  285. // a single character. This is a limitation of Windows streams
  286. // implementation (not PHP) and this behavior cannot be changed with a
  287. // command like "stty", known to POSIX systems.
  288. $stream = fopen('php://stdin', 'rb');
  289. do {
  290. $char = fgetc($stream);
  291. $char = substr(trim($char), 0, 1);
  292. } while (!$char || ($mask !== null && !stristr($mask, $char)));
  293. fclose($stream);
  294. return $char;
  295. }
  296. /**
  297. * Read a single line from the console input.
  298. *
  299. * @param int $maxLength Maximum response length
  300. * @return string
  301. */
  302. public function readLine($maxLength = 2048)
  303. {
  304. $f = fopen('php://stdin', 'r');
  305. $line = rtrim(fread($f, $maxLength), "\r\n");
  306. fclose($f);
  307. return $line;
  308. }
  309. }