1<?php 2 3namespace splitbrain\phpcli; 4 5/** 6 * Class Colors 7 * 8 * Handles color output on (Linux) terminals 9 * 10 * @author Andreas Gohr <andi@splitbrain.org> 11 * @license MIT 12 */ 13class Colors 14{ 15 // these constants make IDE autocompletion easier, but color names can also be passed as strings 16 const C_RESET = 'reset'; 17 const C_BLACK = 'black'; 18 const C_DARKGRAY = 'darkgray'; 19 const C_BLUE = 'blue'; 20 const C_LIGHTBLUE = 'lightblue'; 21 const C_GREEN = 'green'; 22 const C_LIGHTGREEN = 'lightgreen'; 23 const C_CYAN = 'cyan'; 24 const C_LIGHTCYAN = 'lightcyan'; 25 const C_RED = 'red'; 26 const C_LIGHTRED = 'lightred'; 27 const C_PURPLE = 'purple'; 28 const C_LIGHTPURPLE = 'lightpurple'; 29 const C_BROWN = 'brown'; 30 const C_YELLOW = 'yellow'; 31 const C_LIGHTGRAY = 'lightgray'; 32 const C_WHITE = 'white'; 33 34 // Regex pattern to match color codes 35 const C_CODE_REGEX = "/(\33\[[0-9;]+m)/"; 36 37 /** @var array known color names */ 38 protected $colors = array( 39 self::C_RESET => "\33[0m", 40 self::C_BLACK => "\33[0;30m", 41 self::C_DARKGRAY => "\33[1;30m", 42 self::C_BLUE => "\33[0;34m", 43 self::C_LIGHTBLUE => "\33[1;34m", 44 self::C_GREEN => "\33[0;32m", 45 self::C_LIGHTGREEN => "\33[1;32m", 46 self::C_CYAN => "\33[0;36m", 47 self::C_LIGHTCYAN => "\33[1;36m", 48 self::C_RED => "\33[0;31m", 49 self::C_LIGHTRED => "\33[1;31m", 50 self::C_PURPLE => "\33[0;35m", 51 self::C_LIGHTPURPLE => "\33[1;35m", 52 self::C_BROWN => "\33[0;33m", 53 self::C_YELLOW => "\33[1;33m", 54 self::C_LIGHTGRAY => "\33[0;37m", 55 self::C_WHITE => "\33[1;37m", 56 ); 57 58 /** @var bool should colors be used? */ 59 protected $enabled = true; 60 61 /** 62 * Constructor 63 * 64 * Tries to disable colors for non-terminals 65 */ 66 public function __construct() 67 { 68 if (function_exists('posix_isatty') && !posix_isatty(STDOUT)) { 69 $this->enabled = false; 70 return; 71 } 72 if (!getenv('TERM')) { 73 $this->enabled = false; 74 return; 75 } 76 if (getenv('NO_COLOR')) { // https://no-color.org/ 77 $this->enabled = false; 78 return; 79 } 80 } 81 82 /** 83 * enable color output 84 */ 85 public function enable() 86 { 87 $this->enabled = true; 88 } 89 90 /** 91 * disable color output 92 */ 93 public function disable() 94 { 95 $this->enabled = false; 96 } 97 98 /** 99 * @return bool is color support enabled? 100 */ 101 public function isEnabled() 102 { 103 return $this->enabled; 104 } 105 106 /** 107 * Convenience function to print a line in a given color 108 * 109 * @param string $line the line to print, a new line is added automatically 110 * @param string $color one of the available color names 111 * @param resource $channel file descriptor to write to 112 * 113 * @throws Exception 114 */ 115 public function ptln($line, $color, $channel = STDOUT) 116 { 117 $this->set($color, $channel); 118 fwrite($channel, rtrim($line) . "\n"); 119 $this->reset($channel); 120 } 121 122 /** 123 * Returns the given text wrapped in the appropriate color and reset code 124 * 125 * @param string $text string to wrap 126 * @param string $color one of the available color names 127 * @return string the wrapped string 128 * @throws Exception 129 */ 130 public function wrap($text, $color) 131 { 132 return $this->getColorCode($color) . $text . $this->getColorCode('reset'); 133 } 134 135 /** 136 * Gets the appropriate terminal code for the given color 137 * 138 * @param string $color one of the available color names 139 * @return string color code 140 * @throws Exception 141 */ 142 public function getColorCode($color) 143 { 144 if (!$this->enabled) { 145 return ''; 146 } 147 if (!isset($this->colors[$color])) { 148 throw new Exception("No such color $color"); 149 } 150 151 return $this->colors[$color]; 152 } 153 154 /** 155 * Set the given color for consecutive output 156 * 157 * @param string $color one of the supported color names 158 * @param resource $channel file descriptor to write to 159 * @throws Exception 160 */ 161 public function set($color, $channel = STDOUT) 162 { 163 fwrite($channel, $this->getColorCode($color)); 164 } 165 166 /** 167 * reset the terminal color 168 * 169 * @param resource $channel file descriptor to write to 170 * 171 * @throws Exception 172 */ 173 public function reset($channel = STDOUT) 174 { 175 $this->set('reset', $channel); 176 } 177} 178