Test Failed
Push — stable ( d1d5ee...9b430e )
by Nuno
16:38
created

ConsoleColor::themeSequence()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
dl 0
loc 9
ccs 0
cts 5
cp 0
rs 9.9666
c 0
b 0
f 0
cc 2
nc 2
nop 1
crap 6
1
<?php
2
3
/**
4
 * This file is part of Collision.
5
 *
6
 * (c) Nuno Maduro <[email protected]>
7
 *
8
 *  For the full copyright and license information, please view the LICENSE
9
 *  file that was distributed with this source code.
10
 */
11
12
namespace NunoMaduro\Collision;
13
14
use NunoMaduro\Collision\Exceptions\ShouldNotHappen;
15
16
/**
17
 * This is an Collision Console Color implementation.
18
 *
19
 * Code originally from { JakubOnderka\\PhpConsoleColor }. But the package got deprecated.
20
 *
21
 * @internal
22
 *
23
 * @final
24
 */
25
class ConsoleColor
26
{
27
    const FOREGROUND = 38;
28
    const BACKGROUND = 48;
29
30
    const COLOR256_REGEXP = '~^(bg_)?color_([0-9]{1,3})$~';
31
32
    const RESET_STYLE = 0;
33
34
    /** @var bool */
35
    private $isSupported;
36
37
    /** @var bool */
38
    private $forceStyle = false;
39
40
    /** @var array */
41
    private $styles = [
42
        'none'      => null,
43
        'bold'      => '1',
44
        'dark'      => '2',
45
        'italic'    => '3',
46
        'underline' => '4',
47
        'blink'     => '5',
48
        'reverse'   => '7',
49
        'concealed' => '8',
50
51
        'default'    => '39',
52
        'black'      => '30',
53
        'red'        => '31',
54
        'green'      => '32',
55
        'yellow'     => '33',
56
        'blue'       => '34',
57
        'magenta'    => '35',
58
        'cyan'       => '36',
59
        'light_gray' => '37',
60
61
        'dark_gray'     => '90',
62
        'light_red'     => '91',
63
        'light_green'   => '92',
64
        'light_yellow'  => '93',
65
        'light_blue'    => '94',
66
        'light_magenta' => '95',
67
        'light_cyan'    => '96',
68
        'white'         => '97',
69
70
        'bg_default'    => '49',
71
        'bg_black'      => '40',
72
        'bg_red'        => '41',
73
        'bg_green'      => '42',
74
        'bg_yellow'     => '43',
75
        'bg_blue'       => '44',
76
        'bg_magenta'    => '45',
77
        'bg_cyan'       => '46',
78
        'bg_light_gray' => '47',
79
80
        'bg_dark_gray'     => '100',
81
        'bg_light_red'     => '101',
82
        'bg_light_green'   => '102',
83
        'bg_light_yellow'  => '103',
84
        'bg_light_blue'    => '104',
85
        'bg_light_magenta' => '105',
86
        'bg_light_cyan'    => '106',
87
        'bg_white'         => '107',
88
    ];
89
90
    /** @var array */
91
    private $themes = [];
92
93 8
    public function __construct()
94
    {
95 8
        $this->isSupported = $this->isSupported();
96 8
    }
97
98
    /**
99
     * @param string|array $style
100
     * @param string       $text
101
     *
102
     * @return string
103
     *
104
     * @throws InvalidStyleException
105
     * @throws \InvalidArgumentException
106
     */
107 4
    public function apply($style, $text)
108
    {
109 4
        if (!$this->isStyleForced() && !$this->isSupported()) {
110 4
            return $text;
111
        }
112
113
        if (is_string($style)) {
114
            $style = [$style];
115
        }
116
        if (!is_array($style)) {
117
            throw new \InvalidArgumentException('Style must be string or array.');
118
        }
119
120
        $sequences = [];
121
122
        foreach ($style as $s) {
123
            if (isset($this->themes[$s])) {
124
                $sequences = array_merge($sequences, $this->themeSequence($s));
125
            } elseif ($this->isValidStyle($s)) {
126
                $sequences[] = $this->styleSequence($s);
127
            } else {
128
                throw new ShouldNotHappen();
129
            }
130
        }
131
132
        $sequences = array_filter($sequences, function ($val) {
133
            return $val !== null;
134
        });
135
136
        if (empty($sequences)) {
137
            return $text;
138
        }
139
140
        return $this->escSequence(implode(';', $sequences)) . $text . $this->escSequence(self::RESET_STYLE);
141
    }
142
143
    /**
144
     * @param bool $forceStyle
145
     */
146
    public function setForceStyle($forceStyle)
147
    {
148
        $this->forceStyle = (bool) $forceStyle;
149
    }
150
151
    /**
152
     * @return bool
153
     */
154 4
    public function isStyleForced()
155
    {
156 4
        return $this->forceStyle;
157
    }
158
159
    public function setThemes(array $themes)
160
    {
161
        $this->themes = [];
162
        foreach ($themes as $name => $styles) {
163
            $this->addTheme($name, $styles);
164
        }
165
    }
166
167
    /**
168
     * @param string       $name
169
     * @param array|string $styles
170
     */
171 13
    public function addTheme($name, $styles)
172
    {
173 13
        if (is_string($styles)) {
174 13
            $styles = [$styles];
175
        }
176 13
        if (!is_array($styles)) {
177
            throw new \InvalidArgumentException('Style must be string or array.');
178
        }
179
180 13
        foreach ($styles as $style) {
181 13
            if (!$this->isValidStyle($style)) {
182 13
                throw new InvalidStyleException($style);
183
            }
184
        }
185
186 13
        $this->themes[$name] = $styles;
187 13
    }
188
189
    /**
190
     * @return array
191
     */
192
    public function getThemes()
193
    {
194
        return $this->themes;
195
    }
196
197
    /**
198
     * @param string $name
199
     *
200
     * @return bool
201
     */
202 13
    public function hasTheme($name)
203
    {
204 13
        return isset($this->themes[$name]);
205
    }
206
207
    /**
208
     * @param string $name
209
     */
210
    public function removeTheme($name)
211
    {
212
        unset($this->themes[$name]);
213
    }
214
215
    /**
216
     * @return bool
217
     */
218 8
    public function isSupported()
219
    {
220 8
        if (DIRECTORY_SEPARATOR === '\\') {
221 View Code Duplication
            if (function_exists('sapi_windows_vt100_support') && @sapi_windows_vt100_support(STDOUT)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
222
                return true;
223
            } elseif (getenv('ANSICON') !== false || getenv('ConEmuANSI') === 'ON') {
224
                return true;
225
            }
226
227
            return false;
228
        } else {
229 8
            return function_exists('posix_isatty') && @posix_isatty(STDOUT);
230
        }
231
    }
232
233
    /**
234
     * @return bool
235
     */
236
    public function are256ColorsSupported()
237
    {
238 View Code Duplication
        if (DIRECTORY_SEPARATOR === '\\') {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
239
            return function_exists('sapi_windows_vt100_support') && @sapi_windows_vt100_support(STDOUT);
240
        } else {
241
            return strpos(getenv('TERM'), '256color') !== false;
242
        }
243
    }
244
245
    /**
246
     * @return array
247
     */
248
    public function getPossibleStyles()
249
    {
250
        return array_keys($this->styles);
251
    }
252
253
    /**
254
     * @param string $name
255
     *
256
     * @return string[]
257
     */
258
    private function themeSequence($name)
259
    {
260
        $sequences = [];
261
        foreach ($this->themes[$name] as $style) {
262
            $sequences[] = $this->styleSequence($style);
263
        }
264
265
        return $sequences;
266
    }
267
268
    /**
269
     * @param string $style
270
     *
271
     * @return string
272
     */
273
    private function styleSequence($style)
274
    {
275
        if (array_key_exists($style, $this->styles)) {
276
            return $this->styles[$style];
277
        }
278
279
        if (!$this->are256ColorsSupported()) {
280
            return null;
281
        }
282
283
        preg_match(self::COLOR256_REGEXP, $style, $matches);
284
285
        $type  = $matches[1] === 'bg_' ? self::BACKGROUND : self::FOREGROUND;
286
        $value = $matches[2];
287
288
        return "$type;5;$value";
289
    }
290
291
    /**
292
     * @param string $style
293
     *
294
     * @return bool
295
     */
296 13
    private function isValidStyle($style)
297
    {
298 13
        return array_key_exists($style, $this->styles) || preg_match(self::COLOR256_REGEXP, $style);
299
    }
300
301
    /**
302
     * @param string|int $value
303
     *
304
     * @return string
305
     */
306
    private function escSequence($value)
307
    {
308
        return "\033[{$value}m";
309
    }
310
}
311