ConsoleColor   A
last analyzed

Complexity

Total Complexity 42

Size/Duplication

Total Lines 286
Duplicated Lines 3.5 %

Coupling/Cohesion

Components 1
Dependencies 1

Test Coverage

Coverage 29.63%

Importance

Changes 0
Metric Value
wmc 42
lcom 1
cbo 1
dl 10
loc 286
ccs 24
cts 81
cp 0.2963
rs 9.0399
c 0
b 0
f 0

16 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
B apply() 0 35 9
A setForceStyle() 0 4 1
A isStyleForced() 0 4 1
A setThemes() 0 7 2
A addTheme() 0 17 5
A getThemes() 0 4 1
A hasTheme() 0 4 1
A removeTheme() 0 4 1
B isSupported() 5 14 7
A are256ColorsSupported() 5 8 3
A getPossibleStyles() 0 4 1
A themeSequence() 0 9 2
A styleSequence() 0 17 4
A isValidStyle() 0 4 2
A escSequence() 0 4 1

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like ConsoleColor often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use ConsoleColor, and based on these observations, apply Extract Interface, too.

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