88
99namespace Toolkit \Cli ;
1010
11+ use InvalidArgumentException ;
12+ use RuntimeException ;
1113use function array_filter ;
1214use function array_keys ;
1315use function implode ;
1416use function is_array ;
1517use function is_string ;
16- use function preg_match_all ;
1718use function preg_replace ;
1819use function sprintf ;
1920use 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}
0 commit comments