1<?php 2 3/** 4 * @see https://github.com/laminas/laminas-stdlib for the canonical source repository 5 * @copyright https://github.com/laminas/laminas-stdlib/blob/master/COPYRIGHT.md 6 * @license https://github.com/laminas/laminas-stdlib/blob/master/LICENSE.md New BSD License 7 */ 8 9namespace Laminas\Stdlib; 10 11use function function_exists; 12use function fwrite; 13use function getenv; 14use function posix_isatty; 15use function preg_replace; 16use function sprintf; 17use function str_replace; 18 19use const DIRECTORY_SEPARATOR; 20use const PHP_EOL; 21use const STDERR; 22use const STDOUT; 23 24/** 25 * Utilities for console tooling. 26 * 27 * Provides the following facilities: 28 * 29 * - Colorize strings using markup (e.g., `<info>message</info>`, 30 * `<error>message</error>`) 31 * - Write output to a specified stream, optionally with colorization. 32 * - Write a line of output to a specified stream, optionally with 33 * colorization, using the system EOL sequence.. 34 * - Write an error message to STDERR. 35 * 36 * Colorization will only occur when expected sequences are discovered, and 37 * then, only if the console terminal allows it. 38 * 39 * Essentially, provides the bare minimum to allow you to provide messages to 40 * the current console. 41 */ 42class ConsoleHelper 43{ 44 const COLOR_GREEN = "\033[32m"; 45 const COLOR_RED = "\033[31m"; 46 const COLOR_RESET = "\033[0m"; 47 48 const HIGHLIGHT_INFO = 'info'; 49 const HIGHLIGHT_ERROR = 'error'; 50 51 private $highlightMap = [ 52 self::HIGHLIGHT_INFO => self::COLOR_GREEN, 53 self::HIGHLIGHT_ERROR => self::COLOR_RED, 54 ]; 55 56 /** 57 * @var string Exists only for testing. 58 */ 59 private $eol = PHP_EOL; 60 61 /** 62 * @var resource Exists only for testing. 63 */ 64 private $stderr = STDERR; 65 66 /** 67 * @var bool 68 */ 69 private $supportsColor; 70 71 /** 72 * @param resource $resource 73 */ 74 public function __construct($resource = STDOUT) 75 { 76 $this->supportsColor = $this->detectColorCapabilities($resource); 77 } 78 79 /** 80 * Colorize a string for use with the terminal. 81 * 82 * Takes strings formatted as `<key>string</key>` and formats them per the 83 * $highlightMap; if color support is disabled, simply removes the formatting 84 * tags. 85 * 86 * @param string $string 87 * @return string 88 */ 89 public function colorize($string) 90 { 91 $reset = $this->supportsColor ? self::COLOR_RESET : ''; 92 foreach ($this->highlightMap as $key => $color) { 93 $pattern = sprintf('#<%s>(.*?)</%s>#s', $key, $key); 94 $color = $this->supportsColor ? $color : ''; 95 $string = preg_replace($pattern, $color . '$1' . $reset, $string); 96 } 97 return $string; 98 } 99 100 /** 101 * @param string $string 102 * @param bool $colorize Whether or not to colorize the string 103 * @param resource $resource Defaults to STDOUT 104 * @return void 105 */ 106 public function write($string, $colorize = true, $resource = STDOUT) 107 { 108 if ($colorize) { 109 $string = $this->colorize($string); 110 } 111 112 $string = $this->formatNewlines($string); 113 114 fwrite($resource, $string); 115 } 116 117 /** 118 * @param string $string 119 * @param bool $colorize Whether or not to colorize the line 120 * @param resource $resource Defaults to STDOUT 121 * @return void 122 */ 123 public function writeLine($string, $colorize = true, $resource = STDOUT) 124 { 125 $this->write($string . $this->eol, $colorize, $resource); 126 } 127 128 /** 129 * Emit an error message. 130 * 131 * Wraps the message in `<error></error>`, and passes it to `writeLine()`, 132 * using STDERR as the resource; emits an additional empty line when done, 133 * also to STDERR. 134 * 135 * @param string $message 136 * @return void 137 */ 138 public function writeErrorMessage($message) 139 { 140 $this->writeLine(sprintf('<error>%s</error>', $message), true, $this->stderr); 141 $this->writeLine('', false, $this->stderr); 142 } 143 144 /** 145 * @param resource $resource 146 * @return bool 147 */ 148 private function detectColorCapabilities($resource = STDOUT) 149 { 150 if ('\\' === DIRECTORY_SEPARATOR) { 151 // Windows 152 return false !== getenv('ANSICON') 153 || 'ON' === getenv('ConEmuANSI') 154 || 'xterm' === getenv('TERM'); 155 } 156 157 return function_exists('posix_isatty') && posix_isatty($resource); 158 } 159 160 /** 161 * Ensure newlines are appropriate for the current terminal. 162 * 163 * @param string 164 * @return string 165 */ 166 private function formatNewlines($string) 167 { 168 $string = str_replace($this->eol, "\0PHP_EOL\0", $string); 169 $string = preg_replace("/(\r\n|\n|\r)/", $this->eol, $string); 170 return str_replace("\0PHP_EOL\0", $this->eol, $string); 171 } 172} 173