Passed
Push — master ( 1d5ce8...b3847f )
by Alec
05:11 queued 02:53
created

ConsoleColor::checkUnix()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 1
nc 2
nop 0
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 2
rs 10
c 0
b 0
f 0
1
<?php declare(strict_types=1);
2
3
namespace AlecRabbit\ConsoleColour;
4
5
use AlecRabbit\ConsoleColour\Contracts\ConsoleColorInterface;
6
use JakubOnderka\PhpConsoleColor\InvalidStyleException;
7
8
class ConsoleColor implements ConsoleColorInterface
9
{
10
    /** @var bool */
11
    protected $isSupported;
12
    /** @var bool */
13
    protected $forceStyle = false;
14
    /** @var array */
15
    protected $themes = [];
16
17 70
    public function __construct()
18
    {
19 70
        $this->isSupported = $this->checkIfTerminalColorIsSupported();
20 70
    }
21
22 70
    protected function checkIfTerminalColorIsSupported(): bool
23
    {
24 70
        if (DIRECTORY_SEPARATOR === '\\') {
25
            return $this->checkWindowsSupport();
26
        }
27 70
        return $this->checkUnixSupport();
28
    }
29
30
    /**
31
     * @return bool
32
     */
33
    protected function checkWindowsSupport(): bool
34
    {
35
        if (\function_exists('sapi_windows_vt100_support') && @\sapi_windows_vt100_support(STDOUT)) {
36
            return true;
37
        }
38
        if (\getenv('ANSICON') !== false || \getenv('ConEmuANSI') === 'ON') {
39
            return true;
40
        }
41
        return false;
42
    }
43
44
    /**
45
     * @return bool
46
     */
47 70
    protected function checkUnixSupport(): bool
48
    {
49 70
        return \function_exists('posix_isatty') && @\posix_isatty(STDOUT);
50
    }
51
52
    /** {@inheritdoc} */
53 54
    public function apply($styles, $text): string
54
    {
55 54
        if (!$this->isStyleForced() && !$this->isSupported()) {
56 3
            return $text;
57
        }
58
59
        $sequences =
60 51
            $this->getSequencesFrom(
61 51
                $this->refineStyles($styles)
62
            );
63
64 42
        if (empty($sequences)) {
65 6
            return $text;
66
        }
67
68 36
        return $this->applySequences($text, $sequences);
69
    }
70
71
    /** {@inheritdoc} */
72 57
    public function isStyleForced(): bool
73
    {
74 57
        return $this->forceStyle;
75
    }
76
77
    /** {@inheritdoc} */
78 1
    public function isSupported(): bool
79
    {
80 1
        return $this->isSupported;
81
    }
82
83
    /**
84
     * @param array $styles
85
     * @return array
86
     * @throws InvalidStyleException
87
     */
88 48
    protected function getSequencesFrom(array $styles): array
89
    {
90 48
        $sequences = [[]];
91
92 48
        foreach ($styles as $s) {
93 48
            if (isset($this->themes[$s])) {
94 15
                $sequences[] = $this->themeSequence($s);
95 37
            } elseif ($this->isValidStyle($s)) {
96 31
                $sequences[][] = $this->styleSequence($s);
97
            } else {
98 48
                throw new InvalidStyleException($s);
99
            }
100
        }
101
102
        $sequences =
103 42
            \array_filter(
104 42
                \array_merge(...$sequences),
105
                /**
106
                 * @param mixed $val
107
                 * @return bool
108
                 * @psalm-suppress MissingClosureParamType
109
                 */
110
                function ($val): bool {
111 42
                    return $val !== null;
112 42
                }
113
            );
114
115 42
        return $sequences;
116
    }
117
118
    /**
119
     * @param string $name
120
     * @return string[]|null[]
121
     */
122 15
    protected function themeSequence($name): array
123
    {
124 15
        $sequences = [];
125 15
        foreach ($this->themes[$name] as $style) {
126 15
            $sequences[] = $this->styleSequence($style);
127
        }
128 15
        return $sequences;
129
    }
130
131
    /**
132
     * @param string $style
133
     * @return null|string
134
     */
135 42
    protected function styleSequence($style): ?string
136
    {
137 42
        if (\array_key_exists($style, static::STYLES)) {
138 30
            return static::STYLES[$style];
139
        }
140
141 12
        if (!$this->are256ColorsSupported()) {
142 3
            return null;
143
        }
144
145
        return
146 9
            $this->process256ColorStyle($style);
147
    }
148
149
    /** {@inheritdoc} */
150 2
    public function are256ColorsSupported(): bool
151
    {
152 2
        if (DIRECTORY_SEPARATOR === '\\') {
153
            return $this->are256ColorsWindows();
154
        }
155 2
        return $this->are256ColorsUnix();
156
    }
157
158
    /**
159
     * @return bool
160
     */
161
    protected function are256ColorsWindows(): bool
162
    {
163
        return
164
            \function_exists('sapi_windows_vt100_support') && @\sapi_windows_vt100_support(STDOUT);
165
    }
166
167
    /**
168
     * @return bool
169
     */
170 2
    protected function are256ColorsUnix(): bool
171
    {
172 2
        if (!$terminal = \getenv('TERM')) {
173
            // @codeCoverageIgnoreStart
174
            return false;
175
            // @codeCoverageIgnoreEnd
176
        }
177 2
        return \strpos($terminal, '256color') !== false;
178
    }
179
180
    /**
181
     * @param string $style
182
     * @return bool
183
     */
184 56
    protected function isValidStyle($style): bool
185
    {
186 56
        return \array_key_exists($style, static::STYLES) || \preg_match(self::COLOR256_REGEXP, $style);
187
    }
188
189
    /**
190
     * @param string|array $styles
191
     * @return array
192
     */
193 59
    protected function refineStyles($styles): array
194
    {
195 59
        if (\is_string($styles)) {
196 35
            $styles = [$styles];
197
        }
198 59
        $this->assertStyles($styles);
199 56
        return $styles;
200
    }
201
202
    /**
203
     * @param mixed $styles
204
     */
205 59
    protected function assertStyles($styles): void
206
    {
207 59
        if (!\is_array($styles)) {
208 3
            throw new \InvalidArgumentException('Style must be string or array.');
209
        }
210 56
    }
211
212
    /**
213
     * @param string $text
214
     * @param array $sequences
215
     * @return string
216
     */
217 36
    protected function applySequences(string $text, array $sequences): string
218
    {
219
        return
220 36
            $this->escSequence(\implode(';', $sequences)) .
221 36
            $text .
222 36
            $this->escSequence((string)self::RESET_STYLE);
223
    }
224
225
    /**
226
     * @param string $value
227
     * @return string
228
     */
229 36
    protected function escSequence(string $value): string
230
    {
231 36
        return "\033[{$value}m";
232
    }
233
234
    /** {@inheritdoc} */
235 6
    public function setForceStyle(bool $forceStyle): void
236
    {
237 6
        $this->forceStyle = $forceStyle;
238 6
    }
239
240
    /** {@inheritdoc} */
241
    public function getThemes(): array
242
    {
243
        return $this->themes;
244
    }
245
246
    /** {@inheritdoc} */
247 3
    public function setThemes(array $themes): void
248
    {
249 3
        $this->themes = [];
250 3
        foreach ($themes as $name => $styles) {
251 3
            $this->addTheme($name, $styles);
252
        }
253 3
    }
254
255
    /** {@inheritdoc} */
256 23
    public function addTheme($name, $styles): void
257
    {
258 23
        $styles = $this->refineStyles($styles);
259
260 23
        foreach ($styles as $style) {
261 23
            if (!$this->isValidStyle($style)) {
262 23
                throw new InvalidStyleException($style);
263
            }
264
        }
265
266 20
        $this->themes[$name] = $styles;
267 20
    }
268
269
    /** {@inheritdoc} */
270 3
    public function hasTheme($name): bool
271
    {
272 3
        return isset($this->themes[$name]);
273
    }
274
275
    /** {@inheritdoc} */
276 3
    public function removeTheme($name): void
277
    {
278 3
        unset($this->themes[$name]);
279 3
    }
280
281
    /** {@inheritdoc} */
282 3
    public function getPossibleStyles(): array
283
    {
284 3
        return \array_keys(static::STYLES);
285
    }
286
287
    /**
288
     * @param string $style
289
     * @return string
290
     */
291 9
    protected function process256ColorStyle(string $style): string
292
    {
293 9
        \preg_match(self::COLOR256_REGEXP, $style, $matches);
294
295 9
        $type = $matches[1] === 'bg_' ? self::BACKGROUND : self::FOREGROUND;
296 9
        $value = $matches[2];
297
298 9
        return "$type;5;$value";
299
    }
300
}
301