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    }
74
75    /**
76     * enable color output
77     */
78    public function enable()
79    {
80        $this->enabled = true;
81    }
82
83    /**
84     * disable color output
85     */
86    public function disable()
87    {
88        $this->enabled = false;
89    }
90
91    /**
92     * @return bool is color support enabled?
93     */
94    public function isEnabled()
95    {
96        return $this->enabled;
97    }
98
99    /**
100     * Convenience function to print a line in a given color
101     *
102     * @param string   $line    the line to print, a new line is added automatically
103     * @param string   $color   one of the available color names
104     * @param resource $channel file descriptor to write to
105     *
106     * @throws Exception
107     */
108    public function ptln($line, $color, $channel = STDOUT)
109    {
110        $this->set($color, $channel);
111        fwrite($channel, rtrim($line) . "\n");
112        $this->reset($channel);
113    }
114
115    /**
116     * Returns the given text wrapped in the appropriate color and reset code
117     *
118     * @param string $text string to wrap
119     * @param string $color one of the available color names
120     * @return string the wrapped string
121     * @throws Exception
122     */
123    public function wrap($text, $color)
124    {
125        return $this->getColorCode($color) . $text . $this->getColorCode('reset');
126    }
127
128    /**
129     * Gets the appropriate terminal code for the given color
130     *
131     * @param string $color one of the available color names
132     * @return string color code
133     * @throws Exception
134     */
135    public function getColorCode($color)
136    {
137        if (!$this->enabled) {
138            return '';
139        }
140        if (!isset($this->colors[$color])) {
141            throw new Exception("No such color $color");
142        }
143
144        return $this->colors[$color];
145    }
146
147    /**
148     * Set the given color for consecutive output
149     *
150     * @param string $color one of the supported color names
151     * @param resource $channel file descriptor to write to
152     * @throws Exception
153     */
154    public function set($color, $channel = STDOUT)
155    {
156        fwrite($channel, $this->getColorCode($color));
157    }
158
159    /**
160     * reset the terminal color
161     *
162     * @param resource $channel file descriptor to write to
163     *
164     * @throws Exception
165     */
166    public function reset($channel = STDOUT)
167    {
168        $this->set('reset', $channel);
169    }
170}
171