8
8
9
9
namespace Toolkit \Cli ;
10
10
11
+ use InvalidArgumentException ;
12
+ use RuntimeException ;
11
13
use function array_filter ;
12
14
use function array_keys ;
13
15
use function implode ;
14
16
use function is_array ;
15
17
use function is_string ;
16
- use function preg_match_all ;
17
18
use function preg_replace ;
18
19
use function sprintf ;
19
20
use function str_replace ;
@@ -50,6 +51,18 @@ class Color
50
51
public const RESET = 0 ;
51
52
public const NORMAL = 0 ;
52
53
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
+
53
66
// Foreground color
54
67
public const FG_BLACK = 30 ;
55
68
public const FG_RED = 31 ;
@@ -94,13 +107,41 @@ class Color
94
107
public const BOLD = 1 ; // 加粗
95
108
public const FUZZY = 2 ; // 模糊(不是所有的终端仿真器都支持)
96
109
public const ITALIC = 3 ; // 斜体(不是所有的终端仿真器都支持)
97
- public const UNDERSCORE = 4 ; // 下划线
110
+ public const UNDERSCORE = 4 ; // 下划线
98
111
public const BLINK = 5 ; // 闪烁
99
- public const REVERSE = 7 ; // 颠倒的 交换背景色与前景色
112
+ public const REVERSE = 7 ; // 颠倒的 交换背景色与前景色
100
113
public const CONCEALED = 8 ; // 隐匿的
101
114
102
115
/**
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
104
145
* custom style: fg;bg;opt
105
146
*
106
147
* @var array
@@ -179,6 +220,11 @@ class Color
179
220
// CLI color template
180
221
public const COLOR_TPL = "\033[%sm%s \033[0m " ;
181
222
223
+ /**
224
+ * @var bool
225
+ */
226
+ private static $ noColor = false ;
227
+
182
228
/**
183
229
* @param string $method
184
230
* @param array $args
@@ -194,6 +240,22 @@ public static function __callStatic(string $method, array $args)
194
240
return '' ;
195
241
}
196
242
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
+
197
259
/**
198
260
* Apply style for text
199
261
*
@@ -262,7 +324,7 @@ public static function render(string $text, $style = null): string
262
324
return $ text ;
263
325
}
264
326
265
- if (!Cli::isSupportColor ()) {
327
+ if (!Cli::isSupportColor () || self :: isNoColor () ) {
266
328
return self ::clearColor ($ text );
267
329
}
268
330
@@ -294,30 +356,87 @@ public static function render(string $text, $style = null): string
294
356
*/
295
357
public static function parseTag (string $ text )
296
358
{
297
- if (!$ text || false === strpos ($ text , '</ ' )) {
298
- return $ text ;
299
- }
359
+ return ColorTag::parse ($ text );
360
+ }
300
361
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
+ }
304
416
}
305
417
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 ];
308
423
}
309
424
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
+ }
314
429
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 ())));
317
434
}
435
+
436
+ $ values [] = static ::$ knownOptions [$ option ];
318
437
}
319
438
320
- return $ text ;
439
+ return implode ( ' ; ' , $ values ) ;
321
440
}
322
441
323
442
/**
@@ -353,4 +472,24 @@ public static function getStyles(): array
353
472
return !strpos ($ style , '_ ' );
354
473
});
355
474
}
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
+ }
356
495
}
0 commit comments