Skip to content

Commit 0a5e358

Browse files
committed
enhance console formatter output style to allow normal, bright, and mode colors
1 parent 5cf0a2e commit 0a5e358

File tree

4 files changed

+208
-49
lines changed

4 files changed

+208
-49
lines changed

src/Symfony/Component/Console/Formatter/OutputFormatter.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ public function format($message)
133133
$message = (string) $message;
134134
$offset = 0;
135135
$output = '';
136-
$tagRegex = '[a-z][a-z0-9,_=;-]*+';
136+
$tagRegex = '[a-z][a-z0-9,_=;:-]*+';
137137
preg_match_all("#<(($tagRegex) | /($tagRegex)?)>#ix", $message, $matches, PREG_OFFSET_CAPTURE);
138138
foreach ($matches[0] as $i => $match) {
139139
$pos = $match[1];

src/Symfony/Component/Console/Formatter/OutputFormatterStyle.php

+128-42
Original file line numberDiff line numberDiff line change
@@ -20,29 +20,43 @@
2020
*/
2121
class OutputFormatterStyle implements OutputFormatterStyleInterface
2222
{
23-
private static $availableForegroundColors = array(
24-
'black' => array('set' => 30, 'unset' => 39),
25-
'red' => array('set' => 31, 'unset' => 39),
26-
'green' => array('set' => 32, 'unset' => 39),
27-
'yellow' => array('set' => 33, 'unset' => 39),
28-
'blue' => array('set' => 34, 'unset' => 39),
29-
'magenta' => array('set' => 35, 'unset' => 39),
30-
'cyan' => array('set' => 36, 'unset' => 39),
31-
'white' => array('set' => 37, 'unset' => 39),
32-
'default' => array('set' => 39, 'unset' => 39),
23+
private const FOREGROUND_COLOR_FORMATS = array(
24+
'normal' => '3%d',
25+
'bright' => '9%d',
26+
'mode' => '38;5;%d',
3327
);
34-
private static $availableBackgroundColors = array(
35-
'black' => array('set' => 40, 'unset' => 49),
36-
'red' => array('set' => 41, 'unset' => 49),
37-
'green' => array('set' => 42, 'unset' => 49),
38-
'yellow' => array('set' => 43, 'unset' => 49),
39-
'blue' => array('set' => 44, 'unset' => 49),
40-
'magenta' => array('set' => 45, 'unset' => 49),
41-
'cyan' => array('set' => 46, 'unset' => 49),
42-
'white' => array('set' => 47, 'unset' => 49),
43-
'default' => array('set' => 49, 'unset' => 49),
28+
private const FOREGROUND_COLOR_RANGE = array(0, 255);
29+
private const FOREGROUND_COLOR_UNSET = 39;
30+
private const FOREGROUND_COLOR_NAMES = array(
31+
'black' => 0,
32+
'red' => 1,
33+
'green' => 2,
34+
'yellow' => 3,
35+
'blue' => 4,
36+
'magenta' => 5,
37+
'cyan' => 6,
38+
'white' => 7,
39+
'default' => 9,
4440
);
45-
private static $availableOptions = array(
41+
private const BACKGROUND_COLOR_FORMATS = array(
42+
'normal' => '4%d',
43+
'bright' => '10%d',
44+
'mode' => '48;5;%d',
45+
);
46+
private const BACKGROUND_COLOR_RANGE = array(0, 255);
47+
private const BACKGROUND_COLOR_UNSET = 49;
48+
private const BACKGROUND_COLOR_NAMES = array(
49+
'black' => 0,
50+
'red' => 1,
51+
'green' => 2,
52+
'yellow' => 3,
53+
'blue' => 4,
54+
'magenta' => 5,
55+
'cyan' => 6,
56+
'white' => 7,
57+
'default' => 9,
58+
);
59+
private const FORMATTING_OPTIONS = array(
4660
'bold' => array('set' => 1, 'unset' => 22),
4761
'underscore' => array('set' => 4, 'unset' => 24),
4862
'blink' => array('set' => 5, 'unset' => 25),
@@ -89,15 +103,11 @@ public function setForeground($color = null)
89103
return;
90104
}
91105

92-
if (!isset(static::$availableForegroundColors[$color])) {
93-
throw new InvalidArgumentException(sprintf(
94-
'Invalid foreground color specified: "%s". Expected one of (%s)',
95-
$color,
96-
implode(', ', array_keys(static::$availableForegroundColors))
97-
));
106+
if (null === $foreground = $this->resolveColorInstruction($color, 'foreground')) {
107+
throw new InvalidArgumentException($this->createColorSetterExceptionMessage($color, 'foreground'));
98108
}
99109

100-
$this->foreground = static::$availableForegroundColors[$color];
110+
$this->foreground = $foreground;
101111
}
102112

103113
/**
@@ -115,15 +125,11 @@ public function setBackground($color = null)
115125
return;
116126
}
117127

118-
if (!isset(static::$availableBackgroundColors[$color])) {
119-
throw new InvalidArgumentException(sprintf(
120-
'Invalid background color specified: "%s". Expected one of (%s)',
121-
$color,
122-
implode(', ', array_keys(static::$availableBackgroundColors))
123-
));
128+
if (null === $background = $this->resolveColorInstruction($color, 'background')) {
129+
throw new InvalidArgumentException($this->createColorSetterExceptionMessage($color, 'background'));
124130
}
125131

126-
$this->background = static::$availableBackgroundColors[$color];
132+
$this->background = $background;
127133
}
128134

129135
/**
@@ -135,16 +141,16 @@ public function setBackground($color = null)
135141
*/
136142
public function setOption($option)
137143
{
138-
if (!isset(static::$availableOptions[$option])) {
144+
if (!isset(self::FORMATTING_OPTIONS[$option])) {
139145
throw new InvalidArgumentException(sprintf(
140146
'Invalid option specified: "%s". Expected one of (%s)',
141147
$option,
142-
implode(', ', array_keys(static::$availableOptions))
148+
implode(', ', array_keys(self::FORMATTING_OPTIONS))
143149
));
144150
}
145151

146-
if (!in_array(static::$availableOptions[$option], $this->options)) {
147-
$this->options[] = static::$availableOptions[$option];
152+
if (!in_array(self::FORMATTING_OPTIONS[$option], $this->options)) {
153+
$this->options[] = self::FORMATTING_OPTIONS[$option];
148154
}
149155
}
150156

@@ -157,15 +163,15 @@ public function setOption($option)
157163
*/
158164
public function unsetOption($option)
159165
{
160-
if (!isset(static::$availableOptions[$option])) {
166+
if (!isset(self::FORMATTING_OPTIONS[$option])) {
161167
throw new InvalidArgumentException(sprintf(
162168
'Invalid option specified: "%s". Expected one of (%s)',
163169
$option,
164-
implode(', ', array_keys(static::$availableOptions))
170+
implode(', ', array_keys(self::FORMATTING_OPTIONS))
165171
));
166172
}
167173

168-
$pos = array_search(static::$availableOptions[$option], $this->options);
174+
$pos = array_search(self::FORMATTING_OPTIONS[$option], $this->options);
169175
if (false !== $pos) {
170176
unset($this->options[$pos]);
171177
}
@@ -216,4 +222,84 @@ public function apply($text)
216222

217223
return sprintf("\033[%sm%s\033[%sm", implode(';', $setCodes), $text, implode(';', $unsetCodes));
218224
}
225+
226+
/**
227+
* @param string $input
228+
* @param string $context
229+
*
230+
* @return array|null
231+
*/
232+
private function resolveColorInstruction(string $input, string $context): ?array
233+
{
234+
list($names, $range, $unset, $formats) = $this->resolveColorAttributes($context);
235+
list($type, $name) = $this->resolveColorInput($input, $names);
236+
237+
if (('normal' === $type || 'bright' === $type) && isset($names[$name])) {
238+
$color = $names[$name];
239+
}
240+
241+
if ('mode' === $type && in_array($name, range(...$range), true)) {
242+
$color = $name;
243+
}
244+
245+
return isset($color) ? array('unset' => $unset, 'set' => sprintf($formats[$type], $color)) : null;
246+
}
247+
248+
/**
249+
* @param string $color
250+
* @param string $context
251+
*
252+
* @return string
253+
*/
254+
private function createColorSetterExceptionMessage(string $color, string $context): string
255+
{
256+
list($names, $range) = $this->resolveColorAttributes($context);
257+
list($colorType, $colorName) = $this->resolveColorInput($color, $names);
258+
259+
return sprintf('Invalid %s color specified: "%s:%s". Expected "mode:[%d-%d]" or "(normal:|bright:)?(%s)"',
260+
$context, $colorType, $colorName, $range[0], $range[1], implode(',', array_keys($names)));
261+
}
262+
263+
/**
264+
* @param string $context
265+
*
266+
* @return array
267+
*/
268+
private function resolveColorAttributes(string $context): array
269+
{
270+
if ('foreground' === $context) {
271+
return array(
272+
self::FOREGROUND_COLOR_NAMES,
273+
self::FOREGROUND_COLOR_RANGE,
274+
self::FOREGROUND_COLOR_UNSET,
275+
self::FOREGROUND_COLOR_FORMATS,
276+
);
277+
}
278+
279+
return array(
280+
self::BACKGROUND_COLOR_NAMES,
281+
self::BACKGROUND_COLOR_RANGE,
282+
self::BACKGROUND_COLOR_UNSET,
283+
self::BACKGROUND_COLOR_FORMATS,
284+
);
285+
}
286+
287+
/**
288+
* @param string $color
289+
* @param string[] $names
290+
*
291+
* @return string[]
292+
*/
293+
private function resolveColorInput(string $color, array $names): array
294+
{
295+
if (1 === preg_match(sprintf('{^(?<type>normal|bright):(?<name>%s)$}', implode('|', array_keys($names))), $color, $matched)) {
296+
return array($matched['type'], $matched['name']);
297+
}
298+
299+
if (1 === preg_match('{^(?<type>mode):(?<mode>[0-9]{1,3})$}', $color, $matched)) {
300+
return array($matched['type'], (int) $matched['mode']);
301+
}
302+
303+
return array('normal', $color);
304+
}
219305
}

src/Symfony/Component/Console/Tests/Formatter/OutputFormatterStyleTest.php

+66-6
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,14 @@ public function testForeground()
3232
{
3333
$style = new OutputFormatterStyle();
3434

35+
$style->setForeground(null);
36+
$this->assertEquals('foo', $style->apply('foo'));
37+
3538
$style->setForeground('black');
3639
$this->assertEquals("\033[30mfoo\033[39m", $style->apply('foo'));
3740

38-
$style->setForeground('blue');
39-
$this->assertEquals("\033[34mfoo\033[39m", $style->apply('foo'));
41+
$style->setForeground('red');
42+
$this->assertEquals("\033[31mfoo\033[39m", $style->apply('foo'));
4043

4144
$style->setForeground('default');
4245
$this->assertEquals("\033[39mfoo\033[39m", $style->apply('foo'));
@@ -45,15 +48,45 @@ public function testForeground()
4548
$style->setForeground('undefined-color');
4649
}
4750

51+
public function testForegroundBright()
52+
{
53+
$style = new OutputFormatterStyle();
54+
55+
$style->setForeground('bright:green');
56+
$this->assertEquals("\033[92mfoo\033[39m", $style->apply('foo'));
57+
58+
$style->setForeground('bright:yellow');
59+
$this->assertEquals("\033[93mfoo\033[39m", $style->apply('foo'));
60+
61+
$this->{method_exists($this, $_ = 'expectException') ? $_ : 'setExpectedException'}('InvalidArgumentException');
62+
$style->setForeground('bright:undefined-color');
63+
}
64+
65+
public function testForegroundMode()
66+
{
67+
$style = new OutputFormatterStyle();
68+
69+
for ($i = 0; $i <= 255; ++$i) {
70+
$style->setForeground(sprintf('mode:%d', $i));
71+
$this->assertEquals(sprintf("\033[38;5;%dmfoo\033[39m", $i), $style->apply('foo'));
72+
}
73+
74+
$this->{method_exists($this, $_ = 'expectException') ? $_ : 'setExpectedException'}('InvalidArgumentException');
75+
$style->setForeground('mode:500');
76+
}
77+
4878
public function testBackground()
4979
{
5080
$style = new OutputFormatterStyle();
5181

52-
$style->setBackground('black');
53-
$this->assertEquals("\033[40mfoo\033[49m", $style->apply('foo'));
82+
$style->setBackground(null);
83+
$this->assertEquals('foo', $style->apply('foo'));
5484

55-
$style->setBackground('yellow');
56-
$this->assertEquals("\033[43mfoo\033[49m", $style->apply('foo'));
85+
$style->setBackground('blue');
86+
$this->assertEquals("\033[44mfoo\033[49m", $style->apply('foo'));
87+
88+
$style->setBackground('magenta');
89+
$this->assertEquals("\033[45mfoo\033[49m", $style->apply('foo'));
5790

5891
$style->setBackground('default');
5992
$this->assertEquals("\033[49mfoo\033[49m", $style->apply('foo'));
@@ -62,6 +95,33 @@ public function testBackground()
6295
$style->setBackground('undefined-color');
6396
}
6497

98+
public function testBackgroundBright()
99+
{
100+
$style = new OutputFormatterStyle();
101+
102+
$style->setBackground('bright:cyan');
103+
$this->assertEquals("\033[106mfoo\033[49m", $style->apply('foo'));
104+
105+
$style->setBackground('bright:white');
106+
$this->assertEquals("\033[107mfoo\033[49m", $style->apply('foo'));
107+
108+
$this->{method_exists($this, $_ = 'expectException') ? $_ : 'setExpectedException'}('InvalidArgumentException');
109+
$style->setBackground('bright:undefined-color');
110+
}
111+
112+
public function testBackgroundMode()
113+
{
114+
$style = new OutputFormatterStyle();
115+
116+
for ($i = 0; $i <= 255; ++$i) {
117+
$style->setBackground(sprintf('mode:%d', $i));
118+
$this->assertEquals(sprintf("\033[48;5;%dmfoo\033[49m", $i), $style->apply('foo'));
119+
}
120+
121+
$this->{method_exists($this, $_ = 'expectException') ? $_ : 'setExpectedException'}('InvalidArgumentException');
122+
$style->setBackground('mode:500');
123+
}
124+
65125
public function testOptions()
66126
{
67127
$style = new OutputFormatterStyle();

src/Symfony/Component/Console/Tests/Formatter/OutputFormatterTest.php

+13
Original file line numberDiff line numberDiff line change
@@ -188,11 +188,24 @@ public function provideInlineStyleOptionsCases()
188188
array('<unknown=_unknown_>'),
189189
array('<unknown=_unknown_;a=1;b>'),
190190
array('<fg=green;>', "\033[32m[test]\033[39m", '[test]'),
191+
array('<fg=normal:green;>', "\033[32m[test]\033[39m", '[test]'),
192+
array('<fg=bright:green;>', "\033[92m[test]\033[39m", '[test]'),
191193
array('<fg=green;bg=blue;>', "\033[32;44ma\033[39;49m", 'a'),
194+
array('<fg=green;bg=normal:blue;>', "\033[32;44ma\033[39;49m", 'a'),
195+
array('<fg=green;bg=bright:blue;>', "\033[32;104ma\033[39;49m", 'a'),
192196
array('<fg=green;options=bold>', "\033[32;1mb\033[39;22m", 'b'),
197+
array('<fg=normal:green;options=bold>', "\033[32;1mb\033[39;22m", 'b'),
198+
array('<fg=bright:green;options=bold>', "\033[92;1mb\033[39;22m", 'b'),
199+
array('<fg=green;bg=blue;options=bold>', "\033[32;44;1mb\033[39;49;22m", 'b'),
200+
array('<fg=green;bg=normal:blue;options=bold>', "\033[32;44;1mb\033[39;49;22m", 'b'),
201+
array('<fg=green;bg=bright:blue;options=bold>', "\033[32;104;1mb\033[39;49;22m", 'b'),
202+
array('<fg=bright:green;bg=bright:blue;options=bold>', "\033[92;104;1mb\033[39;49;22m", 'b'),
193203
array('<fg=green;options=reverse;>', "\033[32;7m<a>\033[39;27m", '<a>'),
194204
array('<fg=green;options=bold,underscore>', "\033[32;1;4mz\033[39;22;24m", 'z'),
195205
array('<fg=green;options=bold,underscore,reverse;>', "\033[32;1;4;7md\033[39;22;24;27m", 'd'),
206+
array('<fg=mode:160;>', "\033[38;5;160m[test]\033[39m", '[test]'),
207+
array('<fg=mode:160;bg=mode:253>', "\033[38;5;160;48;5;253m[test]\033[39;49m", '[test]'),
208+
array('<fg=mode:160;bg=mode:253;options=reverse>', "\033[38;5;160;48;5;253;7m[test]\033[39;49;27m", '[test]'),
196209
);
197210
}
198211

0 commit comments

Comments
 (0)