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 /** @var array known color names */ 35 protected $colors = array( 36 self::C_RESET => "\33[0m", 37 self::C_BLACK => "\33[0;30m", 38 self::C_DARKGRAY => "\33[1;30m", 39 self::C_BLUE => "\33[0;34m", 40 self::C_LIGHTBLUE => "\33[1;34m", 41 self::C_GREEN => "\33[0;32m", 42 self::C_LIGHTGREEN => "\33[1;32m", 43 self::C_CYAN => "\33[0;36m", 44 self::C_LIGHTCYAN => "\33[1;36m", 45 self::C_RED => "\33[0;31m", 46 self::C_LIGHTRED => "\33[1;31m", 47 self::C_PURPLE => "\33[0;35m", 48 self::C_LIGHTPURPLE => "\33[1;35m", 49 self::C_BROWN => "\33[0;33m", 50 self::C_YELLOW => "\33[1;33m", 51 self::C_LIGHTGRAY => "\33[0;37m", 52 self::C_WHITE => "\33[1;37m", 53 ); 54 55 /** @var bool should colors be used? */ 56 protected $enabled = true; 57 58 /** 59 * Constructor 60 * 61 * Tries to disable colors for non-terminals 62 */ 63 public function __construct() 64 { 65 if (function_exists('posix_isatty') && !posix_isatty(STDOUT)) { 66 $this->enabled = false; 67 return; 68 } 69 if (!getenv('TERM')) { 70 $this->enabled = false; 71 return; 72 } 73 if (getenv('NO_COLOR')) { // https://no-color.org/ 74 $this->enabled = false; 75 return; 76 } 77 } 78 79 /** 80 * enable color output 81 */ 82 public function enable() 83 { 84 $this->enabled = true; 85 } 86 87 /** 88 * disable color output 89 */ 90 public function disable() 91 { 92 $this->enabled = false; 93 } 94 95 /** 96 * @return bool is color support enabled? 97 */ 98 public function isEnabled() 99 { 100 return $this->enabled; 101 } 102 103 /** 104 * Convenience function to print a line in a given color 105 * 106 * @param string $line the line to print, a new line is added automatically 107 * @param string $color one of the available color names 108 * @param resource $channel file descriptor to write to 109 * 110 * @throws Exception 111 */ 112 public function ptln($line, $color, $channel = STDOUT) 113 { 114 $this->set($color, $channel); 115 fwrite($channel, rtrim($line) . "\n"); 116 $this->reset($channel); 117 } 118 119 /** 120 * Returns the given text wrapped in the appropriate color and reset code 121 * 122 * @param string $text string to wrap 123 * @param string $color one of the available color names 124 * @return string the wrapped string 125 * @throws Exception 126 */ 127 public function wrap($text, $color) 128 { 129 return $this->getColorCode($color) . $text . $this->getColorCode('reset'); 130 } 131 132 /** 133 * Gets the appropriate terminal code for the given color 134 * 135 * @param string $color one of the available color names 136 * @return string color code 137 * @throws Exception 138 */ 139 public function getColorCode($color) 140 { 141 if (!$this->enabled) { 142 return ''; 143 } 144 if (!isset($this->colors[$color])) { 145 throw new Exception("No such color $color"); 146 } 147 148 return $this->colors[$color]; 149 } 150 151 /** 152 * Set the given color for consecutive output 153 * 154 * @param string $color one of the supported color names 155 * @param resource $channel file descriptor to write to 156 * @throws Exception 157 */ 158 public function set($color, $channel = STDOUT) 159 { 160 fwrite($channel, $this->getColorCode($color)); 161 } 162 163 /** 164 * reset the terminal color 165 * 166 * @param resource $channel file descriptor to write to 167 * 168 * @throws Exception 169 */ 170 public function reset($channel = STDOUT) 171 { 172 $this->set('reset', $channel); 173 } 174} 175