-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathTerminalFormatter.php
More file actions
199 lines (171 loc) · 7.9 KB
/
TerminalFormatter.php
File metadata and controls
199 lines (171 loc) · 7.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
<?php
declare(strict_types=1);
namespace MagicPush\CliToolkit;
use LogicException;
class TerminalFormatter {
/*
* Different codes are supported by different terminal types:
* something may work everywhere, something - only for particular terminals.
*
* Sources:
* * https://misc.flogisoft.com/bash/tip_colors_and_formatting
* * https://en.wikipedia.org/wiki/ANSI_escape_code
*/
/**
* Resets style, font color and background color.
*
* In some terminals may not reset {@see STYLE_INVERT}. Use explicitly {@see RESET_STYLE_INVERT} in this case.
*/
final public const int RESET_ALL = 0;
// FONT STYLES
final public const int STYLE_BOLD = 1;
final public const int STYLE_DIM = 2;
final public const int STYLE_ITALIC = 3;
final public const int STYLE_UNDERLINED = 4;
final public const int STYLE_BLINK = 5;
final public const int STYLE_INVERT = 7;
final public const int STYLE_STRIKE = 9;
final public const int STYLE_OVERLINED = 53;
/** Visual effect only: you will see "hidden" contents after copy-pasting it right in the same terminal. */
final public const int STYLE_HIDDEN = 8;
/**
* Adds a double underline.
*
* In some terminals may reset bold / bright instead.
*/
final public const int STYLE_UNDERLINED_DOUBLE = 21;
final public const int RESET_STYLE_BOLD_OR_DIM = 22;
final public const int RESET_STYLE_ITALIC = 23;
final public const int RESET_STYLE_UNDERLINED = 24;
final public const int RESET_STYLE_BLINK = 25;
final public const int RESET_STYLE_INVERT = 27;
final public const int RESET_STYLE_HIDDEN = 28;
final public const int RESET_STYLE_STRIKE = 29;
final public const int RESET_STYLE_OVERLINED = 55;
// FONT COLORS
final public const int FONT_BLACK = 30;
final public const int FONT_RED = 31;
final public const int FONT_GREEN = 32;
final public const int FONT_YELLOW = 33;
final public const int FONT_BLUE = 34;
final public const int FONT_MAGENTA = 35;
final public const int FONT_CYAN = 36;
final public const int FONT_LIGHT_GRAY = 37;
final public const int FONT_DARK_GRAY = 90;
final public const int FONT_LIGHT_RED = 91;
final public const int FONT_LIGHT_GREEN = 92;
final public const int FONT_LIGHT_YELLOW = 93;
final public const int FONT_LIGHT_BLUE = 94;
final public const int FONT_LIGHT_MAGENTA = 95;
final public const int FONT_LIGHT_CYAN = 96;
final public const int FONT_WHITE = 97;
/**
* Special code that allows to set a font color with additional codes.
*
* Possible patterns:
* * 256-color palette - `38;5;N`, where `N` is 0-255, one of the palette colors.
* * RGB: `38;2;R;G;B`, where `R`, `G`, and `B` are 0-255 for red, green, and blue color strength respectively.
*/
final public const int FONT_CUSTOM = 38;
final public const int RESET_FONT = 39;
/**
* Special code that allows to set an underline color with additional codes.
*
* Possible patterns:
* * 256-color palette - `58;5;N`, where `N` is 0-255, one of the palette colors.
* * RGB: `58;2;R;G;B`, where `R`, `G`, and `B` are 0-255 for red, green, and blue color strength respectively.
*
* Enable {@see TerminalFormatter::STYLE_UNDERLINED} to notice the effect.
*
* **Warning!** Some terminals do not support underline custom color in a nasty way: the code is ignored
* and the sequence after the code is treated as just other codes like styles, fonts or backgrounds.
* For instance, the sequence after `58` - `2;0;208;98` ("emerald green" in RGB) - will be treated as a list of
* separate codes, where '0' means not 'R' (red) part, but an independent {@see RESET_ALL} code.
*/
final public const int FONT_UNDERLINED_CUSTOM = 58;
/**
* Special code that disables underline custom color ({@see TerminalFormatter::FONT_UNDERLINED_CUSTOM}).
*
* On the contrary, {@see TerminalFormatter::RESET_STYLE_UNDERLINED}
* disables the underline itself ({@see TerminalFormatter::STYLE_UNDERLINED}).
*/
final public const int RESET_FONT_UNDERLINED_CUSTOM = 59;
// BACKGROUND COLORS
final public const int BACKGROUND_BLACK = 40;
final public const int BACKGROUND_RED = 41;
final public const int BACKGROUND_GREEN = 42;
final public const int BACKGROUND_YELLOW = 43;
final public const int BACKGROUND_BLUE = 44;
final public const int BACKGROUND_MAGENTA = 45;
final public const int BACKGROUND_CYAN = 46;
final public const int BACKGROUND_LIGHT_GRAY = 47;
final public const int BACKGROUND_DARK_GRAY = 100;
final public const int BACKGROUND_LIGHT_RED = 101;
final public const int BACKGROUND_LIGHT_GREEN = 102;
final public const int BACKGROUND_LIGHT_YELLOW = 103;
final public const int BACKGROUND_LIGHT_BLUE = 104;
final public const int BACKGROUND_LIGHT_MAGENTA = 105;
final public const int BACKGROUND_LIGHT_CYAN = 106;
final public const int BACKGROUND_WHITE = 107;
/**
* Special code that allows to set a background color with additional codes.
*
* Possible patterns:
* * 256-color palette - `48;5;N`, where `N` is 0-255, one of the palette colors.
* * RGB: `48;2;R;G;B`, where `R`, `G`, and `B` are 0-255 for red, green, and blue color strength respectively.
*/
final public const int BACKGROUND_CUSTOM = 48;
final public const int RESET_BACKGROUND = 49;
/**
* @param bool $isDisabled If the font functionality is disabled. Useful in combination with {@see stream_isatty()}
* or {@see posix_isatty()} when redirecting a stream with some formatted text into a file
* or somewhere else (like `less` tool), where font escape sequences are not processed.
*/
public function __construct(protected readonly bool $isDisabled = false) { }
/**
* Returns a **new** instance for {@see STDOUT} output.
*
* Applying font escape sequences in other cases (like outputting {@see STDERR} or sending output to a file)
* is disabled.
*/
public static function createForStdOut(): static {
return new static(!stream_isatty(STDOUT));
}
/**
* Return an instance for {@see STDERR} output.
*
* Applying font escape sequences in other cases (like outputting {@see STDOUT} or sending output to a file)
* is disabled.
*/
public static function createForStdErr(): static {
return new static(!stream_isatty(STDERR));
}
/**
* @param int[] $formatCodes
*/
public function apply(string $text, array $formatCodes): string {
if ($this->isDisabled || !$formatCodes) {
return $text;
}
$enableFormatString = "\e[" . implode(';', $formatCodes) . 'm';
$disableFormatString = "\e[" . static::RESET_ALL . 'm';
$disableFormatPattern = "\e\[" . static::RESET_ALL . 'm';
// Retain existing font escape sequence found in the string
// and ensure current stylizing for other parts of the string:
$preStylizedText = preg_replace(
"/{$disableFormatPattern}/",
$disableFormatString . $enableFormatString,
$text,
);
if (null === $preStylizedText) {
throw new LogicException("Unable to parse a string: '{$text}'");
}
return $enableFormatString . $preStylizedText . $disableFormatString;
}
/**
* Returns the length of a given multibyte string, not counting font escape sequences.
*/
public static function mbStrlenNoFormat(string $text): int {
return mb_strlen(preg_replace('/\e\[((\d*;)*\d*)?m/', '', $text));
}
}