From 8f76128d6c3c04cb5019ad6d98f103a5b3cd7edc Mon Sep 17 00:00:00 2001 From: Alexandre Daubois Date: Sun, 19 Sep 2021 15:58:56 +0200 Subject: [PATCH] [Console] Add support of RGB functional notation for output colors --- src/Symfony/Component/Console/CHANGELOG.md | 1 + src/Symfony/Component/Console/Color.php | 25 +++++++++++++++ .../Component/Console/Tests/ColorTest.php | 32 +++++++++++++++++++ 3 files changed, 58 insertions(+) diff --git a/src/Symfony/Component/Console/CHANGELOG.md b/src/Symfony/Component/Console/CHANGELOG.md index fedb08823e15b..66f9dfd8061a0 100644 --- a/src/Symfony/Component/Console/CHANGELOG.md +++ b/src/Symfony/Component/Console/CHANGELOG.md @@ -6,6 +6,7 @@ CHANGELOG * Add `TesterTrait::assertCommandIsSuccessful()` to test command * Deprecate `HelperSet::setCommand()` and `getCommand()` without replacement + * Add `rgb(r, g, b)` notation support for output colors 5.3 --- diff --git a/src/Symfony/Component/Console/Color.php b/src/Symfony/Component/Console/Color.php index 22a4ce9ffbbb9..6d57d0652737b 100644 --- a/src/Symfony/Component/Console/Color.php +++ b/src/Symfony/Component/Console/Color.php @@ -49,6 +49,8 @@ final class Color 'conceal' => ['set' => 8, 'unset' => 28], ]; + private const RGB_FUNCTIONAL_NOTATION_REGEX = '/^rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)$/'; + private $foreground; private $background; private $options = []; @@ -116,6 +118,10 @@ private function parseColor(string $color, bool $background = false): string return ''; } + if (str_starts_with($color, 'rgb(')) { + $color = $this->rgbToHex($color); + } + if ('#' === $color[0]) { $color = substr($color, 1); @@ -177,4 +183,23 @@ private function getSaturation(int $r, int $g, int $b): int return (int) $diff * 100 / $v; } + + private function rgbToHex(string $color): string + { + if (!preg_match(self::RGB_FUNCTIONAL_NOTATION_REGEX, $color, $matches)) { + throw new InvalidArgumentException(sprintf('Invalid RGB functional notation; should be of the form "rgb(r, g, b)", got "%s".', $color)); + } + + $rgb = \array_slice($matches, 1); + + $hexString = array_map(function ($element) { + if ($element > 255) { + throw new InvalidArgumentException(sprintf('Invalid color component; value should be between 0 and 255, got %d.', $element)); + } + + return str_pad(dechex((int) $element), 2, '0', \STR_PAD_LEFT); + }, $rgb); + + return '#'.implode('', $hexString); + } } diff --git a/src/Symfony/Component/Console/Tests/ColorTest.php b/src/Symfony/Component/Console/Tests/ColorTest.php index c9615aa8d6133..c643664cec57d 100644 --- a/src/Symfony/Component/Console/Tests/ColorTest.php +++ b/src/Symfony/Component/Console/Tests/ColorTest.php @@ -13,6 +13,7 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\Console\Color; +use Symfony\Component\Console\Exception\InvalidArgumentException; class ColorTest extends TestCase { @@ -42,6 +43,9 @@ public function testTrueColors() $color = new Color('#ffffff', '#000000'); $this->assertSame("\033[38;2;255;255;255;48;2;0;0;0m \033[39;49m", $color->apply(' ')); + + $color = new Color('rgb(255, 255, 255)', 'rgb(0, 0, 0)'); + $this->assertSame("\033[38;2;255;255;255;48;2;0;0;0m \033[39;49m", $color->apply(' ')); } public function testDegradedTrueColors() @@ -59,4 +63,32 @@ public function testDegradedTrueColors() putenv('COLORTERM='.$colorterm); } } + + /** + * @dataProvider provideMalformedRgbStrings + */ + public function testMalformedRgbString(string $color, string $exceptionMessage) + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage($exceptionMessage); + + new Color($color); + } + + public function provideMalformedRgbStrings(): \Generator + { + yield ['rgb()', 'Invalid RGB functional notation; should be of the form "rgb(r, g, b)", got "rgb()".']; + + yield ['rgb(0, 0)', 'Invalid RGB functional notation; should be of the form "rgb(r, g, b)", got "rgb(0, 0)".']; + + yield ['rgb(0, 0, 0, 0)', 'Invalid RGB functional notation; should be of the form "rgb(r, g, b)", got "rgb(0, 0, 0, 0)".']; + + yield ['rgb(-1, 0, 0)', 'Invalid RGB functional notation; should be of the form "rgb(r, g, b)", got "rgb(-1, 0, 0)".']; + + yield ['rgb(invalid, 0, 0)', 'Invalid RGB functional notation; should be of the form "rgb(r, g, b)", got "rgb(invalid, 0, 0)".']; + + yield ['rgb(256, 0, 0)', 'Invalid color component; value should be between 0 and 255, got 256.']; + + yield ['rgb(0, 0, 0', 'Invalid RGB functional notation; should be of the form "rgb(r, g, b)", got "rgb(0, 0, 0".']; + } }