Passed
Push — master ( c7da00...5ce46b )
by Alec
02:32
created

ConsoleColor::getSequencesFrom()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 28
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 13
CRAP Score 4

Importance

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