Skip to content

Commit 3c76510

Browse files
committed
upsome for color
1 parent aefb4b4 commit 3c76510

File tree

2 files changed

+245
-21
lines changed

2 files changed

+245
-21
lines changed

Diff for: src/Color.php

+159-20
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,13 @@
88

99
namespace Toolkit\Cli;
1010

11+
use InvalidArgumentException;
12+
use RuntimeException;
1113
use function array_filter;
1214
use function array_keys;
1315
use function implode;
1416
use function is_array;
1517
use function is_string;
16-
use function preg_match_all;
1718
use function preg_replace;
1819
use function sprintf;
1920
use function str_replace;
@@ -50,6 +51,18 @@ class Color
5051
public const RESET = 0;
5152
public const NORMAL = 0;
5253

54+
/** Foreground base value */
55+
public const FG_BASE = 30;
56+
57+
/** Background base value */
58+
public const BG_BASE = 40;
59+
60+
/** Extra Foreground base value */
61+
public const FG_EXTRA = 90;
62+
63+
/** Extra Background base value */
64+
public const BG_EXTRA = 100;
65+
5366
// Foreground color
5467
public const FG_BLACK = 30;
5568
public const FG_RED = 31;
@@ -94,13 +107,41 @@ class Color
94107
public const BOLD = 1; // 加粗
95108
public const FUZZY = 2; // 模糊(不是所有的终端仿真器都支持)
96109
public const ITALIC = 3; // 斜体(不是所有的终端仿真器都支持)
97-
public const UNDERSCORE = 4; // 下划线
110+
public const UNDERSCORE = 4; // 下划线
98111
public const BLINK = 5; // 闪烁
99-
public const REVERSE = 7; // 颠倒的 交换背景色与前景色
112+
public const REVERSE = 7; // 颠倒的 交换背景色与前景色
100113
public const CONCEALED = 8; // 隐匿的
101114

102115
/**
103-
* some styles
116+
* @var array Known color list
117+
*/
118+
private static $knownColors = [
119+
'black' => 0,
120+
'red' => 1,
121+
'green' => 2,
122+
'yellow' => 3,
123+
'blue' => 4,
124+
'magenta' => 5, // 洋红色 洋红 品红色
125+
'cyan' => 6, // 青色 青绿色 蓝绿色
126+
'white' => 7,
127+
'normal' => 9,
128+
];
129+
130+
/**
131+
* @var array Known style option
132+
*/
133+
private static $knownOptions = [
134+
'bold' => self::BOLD, // 加粗
135+
'fuzzy' => self::FUZZY, // 模糊(不是所有的终端仿真器都支持)
136+
'italic' => self::ITALIC, // 斜体(不是所有的终端仿真器都支持)
137+
'underscore' => self::UNDERSCORE, // 下划线
138+
'blink' => self::BLINK, // 闪烁
139+
'reverse' => self::REVERSE, // 颠倒的 交换背景色与前景色
140+
'concealed' => self::CONCEALED, // 隐匿的
141+
];
142+
143+
/**
144+
* There are some internal styles
104145
* custom style: fg;bg;opt
105146
*
106147
* @var array
@@ -179,6 +220,11 @@ class Color
179220
// CLI color template
180221
public const COLOR_TPL = "\033[%sm%s\033[0m";
181222

223+
/**
224+
* @var bool
225+
*/
226+
private static $noColor = false;
227+
182228
/**
183229
* @param string $method
184230
* @param array $args
@@ -194,6 +240,22 @@ public static function __callStatic(string $method, array $args)
194240
return '';
195241
}
196242

243+
/**
244+
* @return bool
245+
*/
246+
public static function isNoColor(): bool
247+
{
248+
return self::$noColor;
249+
}
250+
251+
/**
252+
* @param bool $noColor
253+
*/
254+
public static function setNoColor(bool $noColor): void
255+
{
256+
self::$noColor = $noColor;
257+
}
258+
197259
/**
198260
* Apply style for text
199261
*
@@ -262,7 +324,7 @@ public static function render(string $text, $style = null): string
262324
return $text;
263325
}
264326

265-
if (!Cli::isSupportColor()) {
327+
if (!Cli::isSupportColor() || self::isNoColor()) {
266328
return self::clearColor($text);
267329
}
268330

@@ -294,30 +356,87 @@ public static function render(string $text, $style = null): string
294356
*/
295357
public static function parseTag(string $text)
296358
{
297-
if (!$text || false === strpos($text, '</')) {
298-
return $text;
299-
}
359+
return ColorTag::parse($text);
360+
}
300361

301-
// if don't support output color text, clear color tag.
302-
if (!Cli::isSupportColor()) {
303-
return static::clearColor($text);
362+
/**
363+
* Create a color style code from a parameter string.
364+
*
365+
* @param string $string e.g 'fg=white;bg=black;options=bold,underscore;extra=1'
366+
*
367+
* @return string
368+
*/
369+
public static function stringToCode(string $string): string
370+
{
371+
$options = [];
372+
373+
$extra = false;
374+
$parts = explode(';', str_replace(' ', '', $string));
375+
376+
$fg = $bg = '';
377+
foreach ($parts as $part) {
378+
$subParts = explode('=', $part);
379+
if (count($subParts) < 2) {
380+
continue;
381+
}
382+
383+
switch ($subParts[0]) {
384+
case 'fg':
385+
$fg = $subParts[1];
386+
387+
// check color name is valid
388+
if (!isset(static::$knownColors[$fg])) {
389+
$errTpl = 'Invalid foreground color "%1$s" [%2$s]';
390+
$names = implode(', ', self::getKnownColors());
391+
throw new InvalidArgumentException(sprintf($errTpl, $fg, $names));
392+
}
393+
394+
break;
395+
case 'bg':
396+
$bg = $subParts[1];
397+
398+
// check color name is valid
399+
if (!isset(static::$knownColors[$bg])) {
400+
$errTpl = 'Invalid background color "%1$s" [%2$s]';
401+
$names = implode(', ', self::getKnownColors());
402+
throw new InvalidArgumentException(sprintf($errTpl, $bg, $names));
403+
}
404+
405+
break;
406+
case 'extra':
407+
$extra = (bool)$subParts[1];
408+
break;
409+
case 'options':
410+
$options = explode(',', $subParts[1]);
411+
break;
412+
default:
413+
throw new RuntimeException('Invalid option');
414+
break;
415+
}
304416
}
305417

306-
if (!preg_match_all(ColorTag::MATCH_TAG, $text, $matches)) {
307-
return $text;
418+
$values = [];
419+
420+
// get fg color code
421+
if ($fg) {
422+
$values[] = ($extra ? self::FG_EXTRA : self::FG_BASE) + static::$knownColors[$fg];
308423
}
309424

310-
foreach ((array)$matches[0] as $i => $m) {
311-
if ($style = self::STYLES[$matches[1][$i]] ?? null) {
312-
$tag = $matches[1][$i];
313-
$match = $matches[2][$i];
425+
// get bg color code
426+
if ($bg) {
427+
$values[] = ($extra ? self::BG_EXTRA : self::BG_BASE) + static::$knownColors[$bg];
428+
}
314429

315-
$repl = sprintf("\033[%sm%s\033[0m", $style, $match);
316-
$text = str_replace("<$tag>$match</$tag>", $repl, $text);
430+
$errTpl = 'Invalid option "%1$s" [%2$s]';
431+
foreach ($options as $option) {
432+
if (!isset(static::$knownOptions[$option])) {
433+
throw new InvalidArgumentException(sprintf($errTpl, $option, implode(', ', self::getKnownOptions())));
317434
}
435+
436+
$values[] = static::$knownOptions[$option];
318437
}
319438

320-
return $text;
439+
return implode(';', $values);
321440
}
322441

323442
/**
@@ -353,4 +472,24 @@ public static function getStyles(): array
353472
return !strpos($style, '_');
354473
});
355474
}
475+
476+
/**
477+
* @param bool $onlyName
478+
*
479+
* @return array
480+
*/
481+
public static function getKnownColors(bool $onlyName = true): array
482+
{
483+
return $onlyName ? array_keys(static::$knownColors) : static::$knownColors;
484+
}
485+
486+
/**
487+
* @param bool $onlyName
488+
*
489+
* @return array
490+
*/
491+
public static function getKnownOptions(bool $onlyName = true): array
492+
{
493+
return $onlyName ? array_keys(static::$knownOptions) : static::$knownOptions;
494+
}
356495
}

Diff for: src/ColorTag.php

+86-1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010

1111
use function preg_match_all;
1212
use function preg_replace;
13+
use function sprintf;
14+
use function str_replace;
1315
use function strpos;
1416

1517
/**
@@ -25,6 +27,40 @@ class ColorTag
2527
// Regex to match tags/
2628
public const MATCH_TAG = '/<([a-zA-Z=;_]+)>(.*?)<\/\\1>/s';
2729

30+
// color
31+
public const BLACK = 'black';
32+
33+
public const RED = 'red';
34+
35+
public const GREEN = 'green';
36+
37+
public const YELLOW = 'yellow'; // BROWN
38+
39+
public const BLUE = 'blue';
40+
41+
public const MAGENTA = 'magenta';
42+
43+
public const CYAN = 'cyan';
44+
45+
public const WHITE = 'white';
46+
47+
public const NORMAL = 'normal';
48+
49+
// color option
50+
public const BOLD = 'bold'; // 加粗
51+
52+
public const FUZZY = 'fuzzy'; // 模糊(不是所有的终端仿真器都支持)
53+
54+
public const ITALIC = 'italic'; // 斜体(不是所有的终端仿真器都支持)
55+
56+
public const UNDERSCORE = 'underscore'; // 下划线
57+
58+
public const BLINK = 'blink'; // 闪烁
59+
60+
public const REVERSE = 'reverse'; // 颠倒的 交换背景色与前景色
61+
62+
public const CONCEALED = 'concealed'; // 隐匿的
63+
2864
/**
2965
* Alias of the wrap()
3066
*
@@ -69,9 +105,58 @@ public static function matchAll(string $text): array
69105
return $matches;
70106
}
71107

108+
/**
109+
* @param string $text
110+
*
111+
* @return string
112+
*/
72113
public static function parse(string $text): string
73114
{
74-
return '';
115+
if (!$text || false === strpos($text, '</')) {
116+
return $text;
117+
}
118+
119+
// if don't support output color text, clear color tag.
120+
if (!Cli::isSupportColor() || Color::isNoColor()) {
121+
return self::strip($text);
122+
}
123+
124+
// match color tags
125+
if (!$matches = self::matchAll($text)) {
126+
return $text;
127+
}
128+
129+
foreach ((array)$matches[0] as $i => $m) {
130+
$key = $matches[1][$i];
131+
132+
if (isset(Color::STYLES[$key])) {
133+
$text = self::replaceColor($text, $key, $matches[2][$i], Color::STYLES[$key]);
134+
135+
/** Custom style format @see Color::stringToCode() */
136+
} elseif (strpos($key, '=')) {
137+
$text = self::replaceColor($text, $key, $matches[2][$i], Color::stringToCode($key));
138+
}
139+
}
140+
141+
return $text;
142+
}
143+
144+
/**
145+
* Replace color tags in a string.
146+
*
147+
* @param string $text
148+
* @param string $tag The matched tag.
149+
* @param string $match The matched text
150+
* @param string $colorCode The color style to apply.
151+
*
152+
* @return string
153+
*/
154+
public static function replaceColor(string $text, string $tag, string $match, string $colorCode): string
155+
{
156+
$replace = Color::isNoColor() ? $match : sprintf("\033[%sm%s\033[0m", $colorCode, $match);
157+
158+
return str_replace("<$tag>$match</$tag>", $replace, $text);
159+
// return sprintf("\033[%sm%s\033[%sm", implode(';', $setCodes), $text, implode(';', $unsetCodes));
75160
}
76161

77162
/**

0 commit comments

Comments
 (0)