1 | <?php |
||
2 | /** |
||
3 | * @link https://www.yiiframework.com/ |
||
4 | * @copyright Copyright (c) 2008 Yii Software LLC |
||
5 | * @license https://www.yiiframework.com/license/ |
||
6 | */ |
||
7 | |||
8 | namespace yii\helpers; |
||
9 | |||
10 | use Yii; |
||
11 | use yii\console\Markdown as ConsoleMarkdown; |
||
12 | use yii\base\Model; |
||
13 | |||
14 | /** |
||
15 | * BaseConsole provides concrete implementation for [[Console]]. |
||
16 | * |
||
17 | * Do not use BaseConsole. Use [[Console]] instead. |
||
18 | * |
||
19 | * @author Carsten Brandt <[email protected]> |
||
20 | * @since 2.0 |
||
21 | */ |
||
22 | class BaseConsole |
||
23 | { |
||
24 | // foreground color control codes |
||
25 | const FG_BLACK = 30; |
||
26 | const FG_RED = 31; |
||
27 | const FG_GREEN = 32; |
||
28 | const FG_YELLOW = 33; |
||
29 | const FG_BLUE = 34; |
||
30 | const FG_PURPLE = 35; |
||
31 | const FG_CYAN = 36; |
||
32 | const FG_GREY = 37; |
||
33 | // background color control codes |
||
34 | const BG_BLACK = 40; |
||
35 | const BG_RED = 41; |
||
36 | const BG_GREEN = 42; |
||
37 | const BG_YELLOW = 43; |
||
38 | const BG_BLUE = 44; |
||
39 | const BG_PURPLE = 45; |
||
40 | const BG_CYAN = 46; |
||
41 | const BG_GREY = 47; |
||
42 | // fonts style control codes |
||
43 | const RESET = 0; |
||
44 | const NORMAL = 0; |
||
45 | const BOLD = 1; |
||
46 | const ITALIC = 3; |
||
47 | const UNDERLINE = 4; |
||
48 | const BLINK = 5; |
||
49 | const NEGATIVE = 7; |
||
50 | const CONCEALED = 8; |
||
51 | const CROSSED_OUT = 9; |
||
52 | const FRAMED = 51; |
||
53 | const ENCIRCLED = 52; |
||
54 | const OVERLINED = 53; |
||
55 | |||
56 | |||
57 | /** |
||
58 | * Moves the terminal cursor up by sending ANSI control code CUU to the terminal. |
||
59 | * If the cursor is already at the edge of the screen, this has no effect. |
||
60 | * @param int $rows number of rows the cursor should be moved up |
||
61 | */ |
||
62 | 1 | public static function moveCursorUp($rows = 1) |
|
63 | { |
||
64 | 1 | echo "\033[" . (int) $rows . 'A'; |
|
65 | } |
||
66 | |||
67 | /** |
||
68 | * Moves the terminal cursor down by sending ANSI control code CUD to the terminal. |
||
69 | * If the cursor is already at the edge of the screen, this has no effect. |
||
70 | * @param int $rows number of rows the cursor should be moved down |
||
71 | */ |
||
72 | 1 | public static function moveCursorDown($rows = 1) |
|
73 | { |
||
74 | 1 | echo "\033[" . (int) $rows . 'B'; |
|
75 | } |
||
76 | |||
77 | /** |
||
78 | * Moves the terminal cursor forward by sending ANSI control code CUF to the terminal. |
||
79 | * If the cursor is already at the edge of the screen, this has no effect. |
||
80 | * @param int $steps number of steps the cursor should be moved forward |
||
81 | */ |
||
82 | 1 | public static function moveCursorForward($steps = 1) |
|
83 | { |
||
84 | 1 | echo "\033[" . (int) $steps . 'C'; |
|
85 | } |
||
86 | |||
87 | /** |
||
88 | * Moves the terminal cursor backward by sending ANSI control code CUB to the terminal. |
||
89 | * If the cursor is already at the edge of the screen, this has no effect. |
||
90 | * @param int $steps number of steps the cursor should be moved backward |
||
91 | */ |
||
92 | 1 | public static function moveCursorBackward($steps = 1) |
|
93 | { |
||
94 | 1 | echo "\033[" . (int) $steps . 'D'; |
|
95 | } |
||
96 | |||
97 | /** |
||
98 | * Moves the terminal cursor to the beginning of the next line by sending ANSI control code CNL to the terminal. |
||
99 | * @param int $lines number of lines the cursor should be moved down |
||
100 | */ |
||
101 | 1 | public static function moveCursorNextLine($lines = 1) |
|
102 | { |
||
103 | 1 | echo "\033[" . (int) $lines . 'E'; |
|
104 | } |
||
105 | |||
106 | /** |
||
107 | * Moves the terminal cursor to the beginning of the previous line by sending ANSI control code CPL to the terminal. |
||
108 | * @param int $lines number of lines the cursor should be moved up |
||
109 | */ |
||
110 | 1 | public static function moveCursorPrevLine($lines = 1) |
|
111 | { |
||
112 | 1 | echo "\033[" . (int) $lines . 'F'; |
|
113 | } |
||
114 | |||
115 | /** |
||
116 | * Moves the cursor to an absolute position given as column and row by sending ANSI control code CUP or CHA to the terminal. |
||
117 | * @param int $column 1-based column number, 1 is the left edge of the screen. |
||
118 | * @param int|null $row 1-based row number, 1 is the top edge of the screen. if not set, will move cursor only in current line. |
||
119 | */ |
||
120 | 1 | public static function moveCursorTo($column, $row = null) |
|
121 | { |
||
122 | 1 | if ($row === null) { |
|
123 | 1 | echo "\033[" . (int) $column . 'G'; |
|
124 | } else { |
||
125 | 1 | echo "\033[" . (int) $row . ';' . (int) $column . 'H'; |
|
126 | } |
||
127 | } |
||
128 | |||
129 | /** |
||
130 | * Scrolls whole page up by sending ANSI control code SU to the terminal. |
||
131 | * New lines are added at the bottom. This is not supported by ANSI.SYS used in windows. |
||
132 | * @param int $lines number of lines to scroll up |
||
133 | */ |
||
134 | 1 | public static function scrollUp($lines = 1) |
|
135 | { |
||
136 | 1 | echo "\033[" . (int) $lines . 'S'; |
|
137 | } |
||
138 | |||
139 | /** |
||
140 | * Scrolls whole page down by sending ANSI control code SD to the terminal. |
||
141 | * New lines are added at the top. This is not supported by ANSI.SYS used in windows. |
||
142 | * @param int $lines number of lines to scroll down |
||
143 | */ |
||
144 | 1 | public static function scrollDown($lines = 1) |
|
145 | { |
||
146 | 1 | echo "\033[" . (int) $lines . 'T'; |
|
147 | } |
||
148 | |||
149 | /** |
||
150 | * Saves the current cursor position by sending ANSI control code SCP to the terminal. |
||
151 | * Position can then be restored with [[restoreCursorPosition()]]. |
||
152 | */ |
||
153 | 1 | public static function saveCursorPosition() |
|
154 | { |
||
155 | 1 | echo "\033[s"; |
|
156 | } |
||
157 | |||
158 | /** |
||
159 | * Restores the cursor position saved with [[saveCursorPosition()]] by sending ANSI control code RCP to the terminal. |
||
160 | */ |
||
161 | 1 | public static function restoreCursorPosition() |
|
162 | { |
||
163 | 1 | echo "\033[u"; |
|
164 | } |
||
165 | |||
166 | /** |
||
167 | * Hides the cursor by sending ANSI DECTCEM code ?25l to the terminal. |
||
168 | * Use [[showCursor()]] to bring it back. |
||
169 | * Do not forget to show cursor when your application exits. Cursor might stay hidden in terminal after exit. |
||
170 | */ |
||
171 | 1 | public static function hideCursor() |
|
172 | { |
||
173 | 1 | echo "\033[?25l"; |
|
174 | } |
||
175 | |||
176 | /** |
||
177 | * Will show a cursor again when it has been hidden by [[hideCursor()]] by sending ANSI DECTCEM code ?25h to the terminal. |
||
178 | */ |
||
179 | 1 | public static function showCursor() |
|
180 | { |
||
181 | 1 | echo "\033[?25h"; |
|
182 | } |
||
183 | |||
184 | /** |
||
185 | * Clears entire screen content by sending ANSI control code ED with argument 2 to the terminal. |
||
186 | * Cursor position will not be changed. |
||
187 | * **Note:** ANSI.SYS implementation used in windows will reset cursor position to upper left corner of the screen. |
||
188 | */ |
||
189 | 1 | public static function clearScreen() |
|
190 | { |
||
191 | 1 | echo "\033[2J"; |
|
192 | } |
||
193 | |||
194 | /** |
||
195 | * Clears text from cursor to the beginning of the screen by sending ANSI control code ED with argument 1 to the terminal. |
||
196 | * Cursor position will not be changed. |
||
197 | */ |
||
198 | 1 | public static function clearScreenBeforeCursor() |
|
199 | { |
||
200 | 1 | echo "\033[1J"; |
|
201 | } |
||
202 | |||
203 | /** |
||
204 | * Clears text from cursor to the end of the screen by sending ANSI control code ED with argument 0 to the terminal. |
||
205 | * Cursor position will not be changed. |
||
206 | */ |
||
207 | 1 | public static function clearScreenAfterCursor() |
|
208 | { |
||
209 | 1 | echo "\033[0J"; |
|
210 | } |
||
211 | |||
212 | /** |
||
213 | * Clears the line, the cursor is currently on by sending ANSI control code EL with argument 2 to the terminal. |
||
214 | * Cursor position will not be changed. |
||
215 | */ |
||
216 | 1 | public static function clearLine() |
|
217 | { |
||
218 | 1 | echo "\033[2K"; |
|
219 | } |
||
220 | |||
221 | /** |
||
222 | * Clears text from cursor position to the beginning of the line by sending ANSI control code EL with argument 1 to the terminal. |
||
223 | * Cursor position will not be changed. |
||
224 | */ |
||
225 | 1 | public static function clearLineBeforeCursor() |
|
226 | { |
||
227 | 1 | echo "\033[1K"; |
|
228 | } |
||
229 | |||
230 | /** |
||
231 | * Clears text from cursor position to the end of the line by sending ANSI control code EL with argument 0 to the terminal. |
||
232 | * Cursor position will not be changed. |
||
233 | */ |
||
234 | 1 | public static function clearLineAfterCursor() |
|
235 | { |
||
236 | 1 | echo "\033[0K"; |
|
237 | } |
||
238 | |||
239 | /** |
||
240 | * Returns the ANSI format code. |
||
241 | * |
||
242 | * @param array $format An array containing formatting values. |
||
243 | * You can pass any of the `FG_*`, `BG_*` and `TEXT_*` constants |
||
244 | * and also [[xtermFgColor]] and [[xtermBgColor]] to specify a format. |
||
245 | * @return string The ANSI format code according to the given formatting constants. |
||
246 | */ |
||
247 | 37 | public static function ansiFormatCode($format) |
|
248 | { |
||
249 | 37 | return "\033[" . implode(';', $format) . 'm'; |
|
250 | } |
||
251 | |||
252 | /** |
||
253 | * Echoes an ANSI format code that affects the formatting of any text that is printed afterwards. |
||
254 | * |
||
255 | * @param array $format An array containing formatting values. |
||
256 | * You can pass any of the `FG_*`, `BG_*` and `TEXT_*` constants |
||
257 | * and also [[xtermFgColor]] and [[xtermBgColor]] to specify a format. |
||
258 | * @see ansiFormatCode() |
||
259 | * @see endAnsiFormat() |
||
260 | */ |
||
261 | 1 | public static function beginAnsiFormat($format) |
|
262 | { |
||
263 | 1 | echo "\033[" . implode(';', $format) . 'm'; |
|
264 | } |
||
265 | |||
266 | /** |
||
267 | * Resets any ANSI format set by previous method [[beginAnsiFormat()]] |
||
268 | * Any output after this will have default text format. |
||
269 | * This is equal to calling. |
||
270 | * |
||
271 | * ```php |
||
272 | * echo Console::ansiFormatCode([Console::RESET]) |
||
273 | * ``` |
||
274 | */ |
||
275 | 1 | public static function endAnsiFormat() |
|
276 | { |
||
277 | 1 | echo "\033[0m"; |
|
278 | } |
||
279 | |||
280 | /** |
||
281 | * Will return a string formatted with the given ANSI style. |
||
282 | * |
||
283 | * @param string $string the string to be formatted |
||
284 | * @param array $format An array containing formatting values. |
||
285 | * You can pass any of the `FG_*`, `BG_*` and `TEXT_*` constants |
||
286 | * and also [[xtermFgColor]] and [[xtermBgColor]] to specify a format. |
||
287 | * @return string |
||
288 | */ |
||
289 | 31 | public static function ansiFormat($string, $format = []) |
|
290 | { |
||
291 | 31 | $code = implode(';', $format); |
|
292 | |||
293 | 31 | return "\033[0m" . ($code !== '' ? "\033[" . $code . 'm' : '') . $string . "\033[0m"; |
|
294 | } |
||
295 | |||
296 | /** |
||
297 | * Returns the ansi format code for xterm foreground color. |
||
298 | * |
||
299 | * You can pass the return value of this to one of the formatting methods: |
||
300 | * [[ansiFormat]], [[ansiFormatCode]], [[beginAnsiFormat]]. |
||
301 | * |
||
302 | * @param int $colorCode xterm color code |
||
303 | * @return string |
||
304 | * @see https://en.wikipedia.org/wiki/Talk:ANSI_escape_code#xterm-256colors |
||
305 | */ |
||
306 | 1 | public static function xtermFgColor($colorCode) |
|
307 | { |
||
308 | 1 | return '38;5;' . $colorCode; |
|
309 | } |
||
310 | |||
311 | /** |
||
312 | * Returns the ansi format code for xterm background color. |
||
313 | * |
||
314 | * You can pass the return value of this to one of the formatting methods: |
||
315 | * [[ansiFormat]], [[ansiFormatCode]], [[beginAnsiFormat]]. |
||
316 | * |
||
317 | * @param int $colorCode xterm color code |
||
318 | * @return string |
||
319 | * @see https://en.wikipedia.org/wiki/Talk:ANSI_escape_code#xterm-256colors |
||
320 | */ |
||
321 | 1 | public static function xtermBgColor($colorCode) |
|
322 | { |
||
323 | 1 | return '48;5;' . $colorCode; |
|
324 | } |
||
325 | |||
326 | /** |
||
327 | * Strips ANSI control codes from a string. |
||
328 | * |
||
329 | * @param string $string String to strip |
||
330 | * @return string |
||
331 | */ |
||
332 | 44 | public static function stripAnsiFormat($string) |
|
333 | { |
||
334 | 44 | return preg_replace(self::ansiCodesPattern(), '', (string)$string); |
|
335 | } |
||
336 | |||
337 | /** |
||
338 | * Returns the length of the string without ANSI color codes. |
||
339 | * @param string $string the string to measure |
||
340 | * @return int the length of the string not counting ANSI format characters |
||
341 | */ |
||
342 | 1 | public static function ansiStrlen($string) |
|
343 | { |
||
344 | 1 | return mb_strlen(static::stripAnsiFormat($string)); |
|
345 | } |
||
346 | |||
347 | /** |
||
348 | * Returns the width of the string without ANSI color codes. |
||
349 | * @param string $string the string to measure |
||
350 | * @return int the width of the string not counting ANSI format characters |
||
351 | * @since 2.0.36 |
||
352 | */ |
||
353 | 33 | public static function ansiStrwidth($string) |
|
354 | { |
||
355 | 33 | return mb_strwidth(static::stripAnsiFormat($string), Yii::$app->charset); |
|
356 | } |
||
357 | |||
358 | /** |
||
359 | * Returns the portion with ANSI color codes of string specified by the start and length parameters. |
||
360 | * If string has color codes, then will be return "TEXT_COLOR + TEXT_STRING + DEFAULT_COLOR", |
||
361 | * else will be simple "TEXT_STRING". |
||
362 | * @param string $string |
||
363 | * @param int $start |
||
364 | * @param int $length |
||
365 | * @return string |
||
366 | */ |
||
367 | 33 | public static function ansiColorizedSubstr($string, $start, $length) |
|
368 | { |
||
369 | 33 | if ($start < 0 || $length <= 0) { |
|
370 | 1 | return ''; |
|
371 | } |
||
372 | |||
373 | 33 | $textItems = preg_split(self::ansiCodesPattern(), (string)$string); |
|
374 | |||
375 | 33 | preg_match_all(self::ansiCodesPattern(), (string)$string, $colors); |
|
376 | 33 | $colors = count($colors) ? $colors[0] : []; |
|
377 | 33 | array_unshift($colors, ''); |
|
378 | |||
379 | 33 | $result = ''; |
|
380 | 33 | $curPos = 0; |
|
381 | 33 | $inRange = false; |
|
382 | |||
383 | 33 | foreach ($textItems as $k => $textItem) { |
|
384 | 33 | $color = $colors[$k]; |
|
385 | |||
386 | 33 | if ($curPos <= $start && $start < $curPos + Console::ansiStrwidth($textItem)) { |
|
387 | 33 | $text = mb_substr($textItem, $start - $curPos, null, Yii::$app->charset); |
|
388 | 33 | $inRange = true; |
|
389 | } else { |
||
390 | 21 | $text = $textItem; |
|
391 | } |
||
392 | |||
393 | 33 | if ($inRange) { |
|
394 | 33 | $result .= $color . $text; |
|
395 | 33 | $diff = $length - Console::ansiStrwidth($result); |
|
396 | 33 | if ($diff <= 0) { |
|
397 | 33 | if ($diff < 0) { |
|
398 | 14 | $result = mb_substr($result, 0, $diff, Yii::$app->charset); |
|
399 | } |
||
400 | 33 | $defaultColor = static::renderColoredString('%n'); |
|
401 | 33 | if ($color && $color != $defaultColor) { |
|
402 | 6 | $result .= $defaultColor; |
|
403 | } |
||
404 | 33 | break; |
|
405 | } |
||
406 | } |
||
407 | |||
408 | 31 | $curPos += mb_strlen($textItem, Yii::$app->charset); |
|
409 | } |
||
410 | |||
411 | 33 | return $result; |
|
412 | } |
||
413 | |||
414 | 44 | private static function ansiCodesPattern() |
|
415 | { |
||
416 | 44 | return /** @lang PhpRegExp */ '/\033\[[\d;?]*\w/'; |
|
417 | } |
||
418 | |||
419 | /** |
||
420 | * Converts an ANSI formatted string to HTML. |
||
421 | * |
||
422 | * Note: xTerm 256 bit colors are currently not supported. |
||
423 | * |
||
424 | * @param string $string the string to convert. |
||
425 | * @param array $styleMap an optional mapping of ANSI control codes such as |
||
426 | * FG\_*COLOR* or [[BOLD]] to a set of css style definitions. |
||
427 | * The CSS style definitions are represented as an array where the array keys correspond |
||
428 | * to the css style attribute names and the values are the css values. |
||
429 | * values may be arrays that will be merged and imploded with `' '` when rendered. |
||
430 | * @return string HTML representation of the ANSI formatted string |
||
431 | */ |
||
432 | 15 | public static function ansiToHtml($string, $styleMap = []) |
|
433 | { |
||
434 | 15 | $styleMap = [ |
|
435 | // https://www.w3.org/TR/CSS2/syndata.html#value-def-color |
||
436 | 15 | self::FG_BLACK => ['color' => 'black'], |
|
437 | 15 | self::FG_BLUE => ['color' => 'blue'], |
|
438 | 15 | self::FG_CYAN => ['color' => 'aqua'], |
|
439 | 15 | self::FG_GREEN => ['color' => 'lime'], |
|
440 | 15 | self::FG_GREY => ['color' => 'silver'], |
|
441 | // https://meyerweb.com/eric/thoughts/2014/06/19/rebeccapurple/ |
||
442 | // https://drafts.csswg.org/css-color/#valuedef-rebeccapurple |
||
443 | 15 | self::FG_PURPLE => ['color' => 'rebeccapurple'], |
|
444 | 15 | self::FG_RED => ['color' => 'red'], |
|
445 | 15 | self::FG_YELLOW => ['color' => 'yellow'], |
|
446 | 15 | self::BG_BLACK => ['background-color' => 'black'], |
|
447 | 15 | self::BG_BLUE => ['background-color' => 'blue'], |
|
448 | 15 | self::BG_CYAN => ['background-color' => 'aqua'], |
|
449 | 15 | self::BG_GREEN => ['background-color' => 'lime'], |
|
450 | 15 | self::BG_GREY => ['background-color' => 'silver'], |
|
451 | 15 | self::BG_PURPLE => ['background-color' => 'rebeccapurple'], |
|
452 | 15 | self::BG_RED => ['background-color' => 'red'], |
|
453 | 15 | self::BG_YELLOW => ['background-color' => 'yellow'], |
|
454 | 15 | self::BOLD => ['font-weight' => 'bold'], |
|
455 | 15 | self::ITALIC => ['font-style' => 'italic'], |
|
456 | 15 | self::UNDERLINE => ['text-decoration' => ['underline']], |
|
457 | 15 | self::OVERLINED => ['text-decoration' => ['overline']], |
|
458 | 15 | self::CROSSED_OUT => ['text-decoration' => ['line-through']], |
|
459 | 15 | self::BLINK => ['text-decoration' => ['blink']], |
|
460 | 15 | self::CONCEALED => ['visibility' => 'hidden'], |
|
461 | 15 | ] + $styleMap; |
|
462 | |||
463 | 15 | $tags = 0; |
|
464 | 15 | $result = preg_replace_callback( |
|
465 | 15 | '/\033\[([\d;]+)m/', |
|
466 | 15 | function ($ansi) use (&$tags, $styleMap) { |
|
467 | 14 | $style = []; |
|
468 | 14 | $reset = false; |
|
469 | 14 | $negative = false; |
|
470 | 14 | foreach (explode(';', $ansi[1]) as $controlCode) { |
|
471 | 14 | if ($controlCode == 0) { |
|
472 | 14 | $style = []; |
|
473 | 14 | $reset = true; |
|
474 | 11 | } elseif ($controlCode == self::NEGATIVE) { |
|
475 | 2 | $negative = true; |
|
476 | 10 | } elseif (isset($styleMap[$controlCode])) { |
|
477 | 10 | $style[] = $styleMap[$controlCode]; |
|
478 | } |
||
479 | } |
||
480 | |||
481 | 14 | $return = ''; |
|
482 | 14 | while ($reset && $tags > 0) { |
|
483 | 10 | $return .= '</span>'; |
|
484 | 10 | $tags--; |
|
485 | } |
||
486 | 14 | if (empty($style)) { |
|
487 | 14 | return $return; |
|
488 | } |
||
489 | |||
490 | 10 | $currentStyle = []; |
|
491 | 10 | foreach ($style as $content) { |
|
492 | 10 | $currentStyle = ArrayHelper::merge($currentStyle, $content); |
|
493 | } |
||
494 | |||
495 | // if negative is set, invert background and foreground |
||
496 | 10 | if ($negative) { |
|
497 | 1 | if (isset($currentStyle['color'])) { |
|
498 | 1 | $fgColor = $currentStyle['color']; |
|
499 | 1 | unset($currentStyle['color']); |
|
500 | } |
||
501 | 1 | if (isset($currentStyle['background-color'])) { |
|
502 | 1 | $bgColor = $currentStyle['background-color']; |
|
503 | 1 | unset($currentStyle['background-color']); |
|
504 | } |
||
505 | 1 | if (isset($fgColor)) { |
|
506 | 1 | $currentStyle['background-color'] = $fgColor; |
|
507 | } |
||
508 | 1 | if (isset($bgColor)) { |
|
509 | 1 | $currentStyle['color'] = $bgColor; |
|
510 | } |
||
511 | } |
||
512 | |||
513 | 10 | $styleString = ''; |
|
514 | 10 | foreach ($currentStyle as $name => $value) { |
|
515 | 10 | if (is_array($value)) { |
|
516 | 1 | $value = implode(' ', $value); |
|
517 | } |
||
518 | 10 | $styleString .= "$name: $value;"; |
|
519 | } |
||
520 | 10 | $tags++; |
|
521 | 10 | return "$return<span style=\"$styleString\">"; |
|
522 | 15 | }, |
|
523 | 15 | $string |
|
524 | 15 | ); |
|
525 | 15 | while ($tags > 0) { |
|
526 | $result .= '</span>'; |
||
527 | $tags--; |
||
528 | } |
||
529 | |||
530 | 15 | return $result; |
|
531 | } |
||
532 | |||
533 | /** |
||
534 | * Converts Markdown to be better readable in console environments by applying some ANSI format. |
||
535 | * @param string $markdown the markdown string. |
||
536 | * @return string the parsed result as ANSI formatted string. |
||
537 | */ |
||
538 | 2 | public static function markdownToAnsi($markdown) |
|
539 | { |
||
540 | 2 | $parser = new ConsoleMarkdown(); |
|
541 | 2 | return $parser->parse($markdown); |
|
542 | } |
||
543 | |||
544 | /** |
||
545 | * Converts a string to ansi formatted by replacing patterns like %y (for yellow) with ansi control codes. |
||
546 | * |
||
547 | * Uses almost the same syntax as https://github.com/pear/Console_Color2/blob/master/Console/Color2.php |
||
548 | * The conversion table is: ('bold' meaning 'light' on some |
||
549 | * terminals). It's almost the same conversion table irssi uses. |
||
550 | * <pre> |
||
551 | * text text background |
||
552 | * ------------------------------------------------ |
||
553 | * %k %K %0 black dark grey black |
||
554 | * %r %R %1 red bold red red |
||
555 | * %g %G %2 green bold green green |
||
556 | * %y %Y %3 yellow bold yellow yellow |
||
557 | * %b %B %4 blue bold blue blue |
||
558 | * %m %M %5 magenta bold magenta magenta |
||
559 | * %p %P magenta (think: purple) |
||
560 | * %c %C %6 cyan bold cyan cyan |
||
561 | * %w %W %7 white bold white white |
||
562 | * |
||
563 | * %F Blinking, Flashing |
||
564 | * %U Underline |
||
565 | * %8 Reverse |
||
566 | * %_,%9 Bold |
||
567 | * |
||
568 | * %n Resets the color |
||
569 | * %% A single % |
||
570 | * </pre> |
||
571 | * First param is the string to convert, second is an optional flag if |
||
572 | * colors should be used. It defaults to true, if set to false, the |
||
573 | * color codes will just be removed (And %% will be transformed into %) |
||
574 | * |
||
575 | * @param string $string String to convert |
||
576 | * @param bool $colored Should the string be colored? |
||
577 | * @return string |
||
578 | */ |
||
579 | 37 | public static function renderColoredString($string, $colored = true) |
|
580 | { |
||
581 | // TODO rework/refactor according to https://github.com/yiisoft/yii2/issues/746 |
||
582 | 37 | static $conversions = [ |
|
583 | 37 | '%y' => [self::FG_YELLOW], |
|
584 | 37 | '%g' => [self::FG_GREEN], |
|
585 | 37 | '%b' => [self::FG_BLUE], |
|
586 | 37 | '%r' => [self::FG_RED], |
|
587 | 37 | '%p' => [self::FG_PURPLE], |
|
588 | 37 | '%m' => [self::FG_PURPLE], |
|
589 | 37 | '%c' => [self::FG_CYAN], |
|
590 | 37 | '%w' => [self::FG_GREY], |
|
591 | 37 | '%k' => [self::FG_BLACK], |
|
592 | 37 | '%n' => [0], // reset |
|
593 | 37 | '%Y' => [self::FG_YELLOW, self::BOLD], |
|
594 | 37 | '%G' => [self::FG_GREEN, self::BOLD], |
|
595 | 37 | '%B' => [self::FG_BLUE, self::BOLD], |
|
596 | 37 | '%R' => [self::FG_RED, self::BOLD], |
|
597 | 37 | '%P' => [self::FG_PURPLE, self::BOLD], |
|
598 | 37 | '%M' => [self::FG_PURPLE, self::BOLD], |
|
599 | 37 | '%C' => [self::FG_CYAN, self::BOLD], |
|
600 | 37 | '%W' => [self::FG_GREY, self::BOLD], |
|
601 | 37 | '%K' => [self::FG_BLACK, self::BOLD], |
|
602 | 37 | '%N' => [0, self::BOLD], |
|
603 | 37 | '%3' => [self::BG_YELLOW], |
|
604 | 37 | '%2' => [self::BG_GREEN], |
|
605 | 37 | '%4' => [self::BG_BLUE], |
|
606 | 37 | '%1' => [self::BG_RED], |
|
607 | 37 | '%5' => [self::BG_PURPLE], |
|
608 | 37 | '%6' => [self::BG_CYAN], |
|
609 | 37 | '%7' => [self::BG_GREY], |
|
610 | 37 | '%0' => [self::BG_BLACK], |
|
611 | 37 | '%F' => [self::BLINK], |
|
612 | 37 | '%U' => [self::UNDERLINE], |
|
613 | 37 | '%8' => [self::NEGATIVE], |
|
614 | 37 | '%9' => [self::BOLD], |
|
615 | 37 | '%_' => [self::BOLD], |
|
616 | 37 | ]; |
|
617 | |||
618 | 37 | if ($colored) { |
|
619 | 37 | $string = str_replace('%%', '% ', $string); |
|
620 | 37 | foreach ($conversions as $key => $value) { |
|
621 | 37 | $string = str_replace( |
|
622 | 37 | $key, |
|
623 | 37 | static::ansiFormatCode($value), |
|
624 | 37 | $string |
|
625 | 37 | ); |
|
626 | } |
||
627 | 37 | $string = str_replace('% ', '%', $string); |
|
628 | } else { |
||
629 | 1 | $string = preg_replace('/%((%)|.)/', '$2', $string); |
|
630 | } |
||
631 | |||
632 | 37 | return $string; |
|
633 | } |
||
634 | |||
635 | /** |
||
636 | * Escapes % so they don't get interpreted as color codes when |
||
637 | * the string is parsed by [[renderColoredString]]. |
||
638 | * |
||
639 | * @param string $string String to escape |
||
640 | * |
||
641 | * @return string |
||
642 | */ |
||
643 | public static function escape($string) |
||
644 | { |
||
645 | // TODO rework/refactor according to https://github.com/yiisoft/yii2/issues/746 |
||
646 | return str_replace('%', '%%', $string); |
||
647 | } |
||
648 | |||
649 | /** |
||
650 | * Returns true if the stream supports colorization. ANSI colors are disabled if not supported by the stream. |
||
651 | * |
||
652 | * - windows without ansicon |
||
653 | * - not tty consoles |
||
654 | * |
||
655 | * @param mixed $stream |
||
656 | * @return bool true if the stream supports ANSI colors, otherwise false. |
||
657 | */ |
||
658 | 6 | public static function streamSupportsAnsiColors($stream) |
|
659 | { |
||
660 | 6 | return DIRECTORY_SEPARATOR === '\\' |
|
661 | ? getenv('ANSICON') !== false || getenv('ConEmuANSI') === 'ON' |
||
662 | 6 | : function_exists('posix_isatty') && @posix_isatty($stream); |
|
663 | } |
||
664 | |||
665 | /** |
||
666 | * Returns true if the console is running on windows. |
||
667 | * @return bool |
||
668 | */ |
||
669 | 1 | public static function isRunningOnWindows() |
|
670 | { |
||
671 | 1 | return DIRECTORY_SEPARATOR === '\\'; |
|
672 | } |
||
673 | |||
674 | /** |
||
675 | * Returns terminal screen size. |
||
676 | * |
||
677 | * Usage: |
||
678 | * |
||
679 | * ```php |
||
680 | * list($width, $height) = ConsoleHelper::getScreenSize(); |
||
681 | * ``` |
||
682 | * |
||
683 | * @param bool $refresh whether to force checking and not re-use cached size value. |
||
684 | * This is useful to detect changing window size while the application is running but may |
||
685 | * not get up to date values on every terminal. |
||
686 | * @return array|bool An array of ($width, $height) or false when it was not able to determine size. |
||
687 | */ |
||
688 | 4 | public static function getScreenSize($refresh = false) |
|
689 | { |
||
690 | 4 | static $size; |
|
691 | 4 | static $execDisabled; |
|
692 | |||
693 | 4 | if ($size !== null && ($execDisabled || !$refresh)) { |
|
694 | 4 | return $size; |
|
695 | } |
||
696 | |||
697 | 1 | if ($execDisabled === null) { |
|
698 | 1 | $execDisabled = !function_exists('ini_get') || preg_match('/(\bexec\b)/i', ini_get('disable_functions')); |
|
699 | 1 | if ($execDisabled) { |
|
700 | return $size = false; |
||
701 | } |
||
702 | } |
||
703 | |||
704 | 1 | if (static::isRunningOnWindows()) { |
|
705 | $output = []; |
||
706 | exec('mode con', $output); |
||
707 | if (isset($output[1]) && strpos($output[1], 'CON') !== false) { |
||
708 | return $size = [(int) preg_replace('~\D~', '', $output[4]), (int) preg_replace('~\D~', '', $output[3])]; |
||
709 | } |
||
710 | } else { |
||
711 | // try stty if available |
||
712 | 1 | $stty = []; |
|
713 | 1 | if (exec('stty -a 2>&1', $stty)) { |
|
714 | 1 | $stty = implode(' ', $stty); |
|
715 | |||
716 | // Linux stty output |
||
717 | 1 | if (preg_match('/rows\s+(\d+);\s*columns\s+(\d+);/mi', $stty, $matches)) { |
|
718 | 1 | return $size = [(int) $matches[2], (int) $matches[1]]; |
|
719 | } |
||
720 | |||
721 | // MacOS stty output |
||
722 | if (preg_match('/(\d+)\s+rows;\s*(\d+)\s+columns;/mi', $stty, $matches)) { |
||
723 | return $size = [(int) $matches[2], (int) $matches[1]]; |
||
724 | } |
||
725 | } |
||
726 | |||
727 | // fallback to tput, which may not be updated on terminal resize |
||
728 | if (($width = (int) exec('tput cols 2>&1')) > 0 && ($height = (int) exec('tput lines 2>&1')) > 0) { |
||
729 | return $size = [$width, $height]; |
||
730 | } |
||
731 | |||
732 | // fallback to ENV variables, which may not be updated on terminal resize |
||
733 | if (($width = (int) getenv('COLUMNS')) > 0 && ($height = (int) getenv('LINES')) > 0) { |
||
734 | return $size = [$width, $height]; |
||
735 | } |
||
736 | } |
||
737 | |||
738 | return $size = false; |
||
739 | } |
||
740 | |||
741 | /** |
||
742 | * Word wrap text with indentation to fit the screen size. |
||
743 | * |
||
744 | * If screen size could not be detected, or the indentation is greater than the screen size, the text will not be wrapped. |
||
745 | * |
||
746 | * The first line will **not** be indented, so `Console::wrapText("Lorem ipsum dolor sit amet.", 4)` will result in the |
||
747 | * following output, given the screen width is 16 characters: |
||
748 | * |
||
749 | * ``` |
||
750 | * Lorem ipsum |
||
751 | * dolor sit |
||
752 | * amet. |
||
753 | * ``` |
||
754 | * |
||
755 | * @param string $text the text to be wrapped |
||
756 | * @param int $indent number of spaces to use for indentation. |
||
757 | * @param bool $refresh whether to force refresh of screen size. |
||
758 | * This will be passed to [[getScreenSize()]]. |
||
759 | * @return string the wrapped text. |
||
760 | * @since 2.0.4 |
||
761 | */ |
||
762 | 2 | public static function wrapText($text, $indent = 0, $refresh = false) |
|
763 | { |
||
764 | 2 | $size = static::getScreenSize($refresh); |
|
765 | 2 | if ($size === false || $size[0] <= $indent) { |
|
766 | 2 | return $text; |
|
767 | } |
||
768 | $pad = str_repeat(' ', $indent); |
||
769 | $lines = explode("\n", wordwrap($text, $size[0] - $indent, "\n")); |
||
770 | $first = true; |
||
771 | foreach ($lines as $i => $line) { |
||
772 | if ($first) { |
||
773 | $first = false; |
||
774 | continue; |
||
775 | } |
||
776 | $lines[$i] = $pad . $line; |
||
777 | } |
||
778 | |||
779 | return implode("\n", $lines); |
||
780 | } |
||
781 | |||
782 | /** |
||
783 | * Gets input from STDIN and returns a string right-trimmed for EOLs. |
||
784 | * |
||
785 | * @param bool $raw If set to true, returns the raw string without trimming |
||
786 | * @return string the string read from stdin |
||
787 | */ |
||
788 | public static function stdin($raw = false) |
||
789 | { |
||
790 | return $raw ? fgets(\STDIN) : rtrim(fgets(\STDIN), PHP_EOL); |
||
791 | } |
||
792 | |||
793 | /** |
||
794 | * Prints a string to STDOUT. |
||
795 | * |
||
796 | * @param string $string the string to print |
||
797 | * @return int|bool Number of bytes printed or false on error |
||
798 | */ |
||
799 | public static function stdout($string) |
||
800 | { |
||
801 | return fwrite(\STDOUT, $string); |
||
802 | } |
||
803 | |||
804 | /** |
||
805 | * Prints a string to STDERR. |
||
806 | * |
||
807 | * @param string $string the string to print |
||
808 | * @return int|bool Number of bytes printed or false on error |
||
809 | */ |
||
810 | public static function stderr($string) |
||
811 | { |
||
812 | return fwrite(\STDERR, $string); |
||
813 | } |
||
814 | |||
815 | /** |
||
816 | * Asks the user for input. Ends when the user types a carriage return (PHP_EOL). Optionally, It also provides a |
||
817 | * prompt. |
||
818 | * |
||
819 | * @param string|null $prompt the prompt to display before waiting for input (optional) |
||
820 | * @return string the user's input |
||
821 | */ |
||
822 | 1 | public static function input($prompt = null) |
|
823 | { |
||
824 | 1 | if (isset($prompt)) { |
|
825 | 1 | static::stdout($prompt); |
|
826 | } |
||
827 | |||
828 | 1 | return static::stdin(); |
|
829 | } |
||
830 | |||
831 | /** |
||
832 | * Prints text to STDOUT appended with a carriage return (PHP_EOL). |
||
833 | * |
||
834 | * @param string|null $string the text to print |
||
835 | * @return int|bool number of bytes printed or false on error. |
||
836 | */ |
||
837 | 1 | public static function output($string = null) |
|
838 | { |
||
839 | 1 | return static::stdout($string . PHP_EOL); |
|
840 | } |
||
841 | |||
842 | /** |
||
843 | * Prints text to STDERR appended with a carriage return (PHP_EOL). |
||
844 | * |
||
845 | * @param string|null $string the text to print |
||
846 | * @return int|bool number of bytes printed or false on error. |
||
847 | */ |
||
848 | 1 | public static function error($string = null) |
|
849 | { |
||
850 | 1 | return static::stderr($string . PHP_EOL); |
|
851 | } |
||
852 | |||
853 | /** |
||
854 | * Prompts the user for input and validates it. |
||
855 | * |
||
856 | * @param string $text prompt string |
||
857 | * @param array $options the options to validate the input: |
||
858 | * |
||
859 | * - `required`: whether it is required or not |
||
860 | * - `default`: default value if no input is inserted by the user |
||
861 | * - `pattern`: regular expression pattern to validate user input |
||
862 | * - `validator`: a callable function to validate input. The function must accept two parameters: |
||
863 | * - `input`: the user input to validate |
||
864 | * - `error`: the error value passed by reference if validation failed. |
||
865 | * |
||
866 | * @return string the user input |
||
867 | */ |
||
868 | 1 | public static function prompt($text, $options = []) |
|
869 | { |
||
870 | 1 | $options = ArrayHelper::merge( |
|
871 | 1 | [ |
|
872 | 1 | 'required' => false, |
|
873 | 1 | 'default' => null, |
|
874 | 1 | 'pattern' => null, |
|
875 | 1 | 'validator' => null, |
|
876 | 1 | 'error' => 'Invalid input.', |
|
877 | 1 | ], |
|
878 | 1 | $options |
|
879 | 1 | ); |
|
880 | 1 | $error = null; |
|
881 | |||
882 | top: |
||
883 | 1 | $input = $options['default'] |
|
884 | 1 | ? static::input("$text [" . $options['default'] . '] ') |
|
885 | 1 | : static::input("$text "); |
|
886 | |||
887 | 1 | if ($input === '') { |
|
888 | 1 | if (isset($options['default'])) { |
|
889 | 1 | $input = $options['default']; |
|
890 | 1 | } elseif ($options['required']) { |
|
891 | 1 | static::output($options['error']); |
|
892 | 1 | goto top; |
|
893 | } |
||
894 | 1 | } elseif ($options['pattern'] && !preg_match($options['pattern'], $input)) { |
|
895 | 1 | static::output($options['error']); |
|
896 | 1 | goto top; |
|
897 | 1 | } elseif ($options['validator'] && !call_user_func_array($options['validator'], [$input, &$error])) { |
|
898 | 1 | static::output(isset($error) ? $error : $options['error']); |
|
899 | 1 | goto top; |
|
900 | } |
||
901 | |||
902 | 1 | return $input; |
|
903 | } |
||
904 | |||
905 | /** |
||
906 | * Asks user to confirm by typing y or n. |
||
907 | * |
||
908 | * A typical usage looks like the following: |
||
909 | * |
||
910 | * ```php |
||
911 | * if (Console::confirm("Are you sure?")) { |
||
912 | * echo "user typed yes\n"; |
||
913 | * } else { |
||
914 | * echo "user typed no\n"; |
||
915 | * } |
||
916 | * ``` |
||
917 | * |
||
918 | * @param string $message to print out before waiting for user input |
||
919 | * @param bool $default this value is returned if no selection is made. |
||
920 | * @return bool whether user confirmed |
||
921 | */ |
||
922 | 1 | public static function confirm($message, $default = false) |
|
923 | { |
||
924 | 1 | while (true) { |
|
925 | 1 | static::stdout($message . ' (yes|no) [' . ($default ? 'yes' : 'no') . ']:'); |
|
926 | 1 | $input = trim(static::stdin()); |
|
927 | |||
928 | 1 | if (empty($input)) { |
|
929 | 1 | return $default; |
|
930 | } |
||
931 | |||
932 | 1 | if (!strcasecmp($input, 'y') || !strcasecmp($input, 'yes')) { |
|
933 | 1 | return true; |
|
934 | } |
||
935 | |||
936 | 1 | if (!strcasecmp($input, 'n') || !strcasecmp($input, 'no')) { |
|
937 | 1 | return false; |
|
938 | } |
||
939 | } |
||
940 | } |
||
941 | |||
942 | /** |
||
943 | * Gives the user an option to choose from. Giving '?' as an input will show |
||
944 | * a list of options to choose from and their explanations. |
||
945 | * |
||
946 | * @param string $prompt the prompt message |
||
947 | * @param array $options Key-value array of options to choose from. Key is what is inputed and used, value is |
||
948 | * what's displayed to end user by help command. |
||
949 | * @param string|null $default value to use when the user doesn't provide an option. |
||
950 | * If the default is `null`, the user is required to select an option. |
||
951 | * |
||
952 | * @return string An option character the user chose |
||
953 | * @since 2.0.49 Added the $default argument |
||
954 | */ |
||
955 | 1 | public static function select($prompt, $options = [], $default = null) |
|
956 | { |
||
957 | top: |
||
958 | 1 | static::stdout("$prompt (" . implode(',', array_keys($options)) . ',?)' |
|
959 | 1 | . ($default !== null ? '[' . $default . ']' : '') . ': '); |
|
960 | 1 | $input = static::stdin(); |
|
961 | 1 | if ($input === '?') { |
|
962 | 1 | foreach ($options as $key => $value) { |
|
963 | 1 | static::output(" $key - $value"); |
|
964 | } |
||
965 | 1 | static::output(' ? - Show help'); |
|
966 | 1 | goto top; |
|
967 | 1 | } elseif ($default !== null && $input === '') { |
|
968 | 1 | return $default; |
|
969 | 1 | } elseif (!array_key_exists($input, $options)) { |
|
970 | 1 | goto top; |
|
971 | } |
||
972 | |||
973 | 1 | return $input; |
|
974 | } |
||
975 | |||
976 | private static $_progressStart; |
||
977 | private static $_progressWidth; |
||
978 | private static $_progressPrefix; |
||
979 | private static $_progressEta; |
||
980 | private static $_progressEtaLastDone = 0; |
||
981 | private static $_progressEtaLastUpdate; |
||
982 | |||
983 | /** |
||
984 | * Starts display of a progress bar on screen. |
||
985 | * |
||
986 | * This bar will be updated by [[updateProgress()]] and may be ended by [[endProgress()]]. |
||
987 | * |
||
988 | * The following example shows a simple usage of a progress bar: |
||
989 | * |
||
990 | * ```php |
||
991 | * Console::startProgress(0, 1000); |
||
992 | * for ($n = 1; $n <= 1000; $n++) { |
||
993 | * usleep(1000); |
||
994 | * Console::updateProgress($n, 1000); |
||
995 | * } |
||
996 | * Console::endProgress(); |
||
997 | * ``` |
||
998 | * |
||
999 | * Git clone like progress (showing only status information): |
||
1000 | * |
||
1001 | * ```php |
||
1002 | * Console::startProgress(0, 1000, 'Counting objects: ', false); |
||
1003 | * for ($n = 1; $n <= 1000; $n++) { |
||
1004 | * usleep(1000); |
||
1005 | * Console::updateProgress($n, 1000); |
||
1006 | * } |
||
1007 | * Console::endProgress("done." . PHP_EOL); |
||
1008 | * ``` |
||
1009 | * |
||
1010 | * @param int $done the number of items that are completed. |
||
1011 | * @param int $total the total value of items that are to be done. |
||
1012 | * @param string $prefix an optional string to display before the progress bar. |
||
1013 | * Default to empty string which results in no prefix to be displayed. |
||
1014 | * @param int|float|bool|null $width optional width of the progressbar. This can be an integer representing |
||
1015 | * the number of characters to display for the progress bar or a float between 0 and 1 representing the |
||
1016 | * percentage of screen with the progress bar may take. It can also be set to false to disable the |
||
1017 | * bar and only show progress information like percent, number of items and ETA. |
||
1018 | * If not set, the bar will be as wide as the screen. Screen size will be detected using [[getScreenSize()]]. |
||
1019 | * @see startProgress |
||
1020 | * @see updateProgress |
||
1021 | * @see endProgress |
||
1022 | */ |
||
1023 | public static function startProgress($done, $total, $prefix = '', $width = null) |
||
1024 | { |
||
1025 | self::$_progressStart = time(); |
||
1026 | self::$_progressWidth = $width; |
||
1027 | self::$_progressPrefix = $prefix; |
||
1028 | self::$_progressEta = null; |
||
1029 | self::$_progressEtaLastDone = 0; |
||
1030 | self::$_progressEtaLastUpdate = time(); |
||
1031 | |||
1032 | static::updateProgress($done, $total); |
||
1033 | } |
||
1034 | |||
1035 | /** |
||
1036 | * Updates a progress bar that has been started by [[startProgress()]]. |
||
1037 | * |
||
1038 | * @param int $done the number of items that are completed. |
||
1039 | * @param int $total the total value of items that are to be done. |
||
1040 | * @param string|null $prefix an optional string to display before the progress bar. |
||
1041 | * Defaults to null meaning the prefix specified by [[startProgress()]] will be used. |
||
1042 | * If prefix is specified it will update the prefix that will be used by later calls. |
||
1043 | * @see startProgress |
||
1044 | * @see endProgress |
||
1045 | */ |
||
1046 | public static function updateProgress($done, $total, $prefix = null) |
||
1047 | { |
||
1048 | if ($prefix === null) { |
||
1049 | $prefix = self::$_progressPrefix; |
||
1050 | } else { |
||
1051 | self::$_progressPrefix = $prefix; |
||
1052 | } |
||
1053 | $width = static::getProgressbarWidth($prefix); |
||
1054 | $percent = ($total == 0) ? 1 : $done / $total; |
||
1055 | $info = sprintf('%d%% (%d/%d)', $percent * 100, $done, $total); |
||
1056 | self::setETA($done, $total); |
||
1057 | $info .= self::$_progressEta === null ? ' ETA: n/a' : sprintf(' ETA: %d sec.', self::$_progressEta); |
||
1058 | |||
1059 | // Number extra characters outputted. These are opening [, closing ], and space before info |
||
1060 | // Since Windows uses \r\n\ for line endings, there's one more in the case |
||
1061 | $extraChars = static::isRunningOnWindows() ? 4 : 3; |
||
1062 | $width -= $extraChars + static::ansiStrlen($info); |
||
1063 | // skipping progress bar on very small display or if forced to skip |
||
1064 | if ($width < 5) { |
||
1065 | static::stdout("\r$prefix$info "); |
||
1066 | } else { |
||
1067 | if ($percent < 0) { |
||
1068 | $percent = 0; |
||
1069 | } elseif ($percent > 1) { |
||
1070 | $percent = 1; |
||
1071 | } |
||
1072 | $bar = floor($percent * $width); |
||
1073 | $status = str_repeat('=', $bar); |
||
0 ignored issues
–
show
Bug
introduced
by
![]() |
|||
1074 | if ($bar < $width) { |
||
1075 | $status .= '>'; |
||
1076 | $status .= str_repeat(' ', $width - $bar - 1); |
||
1077 | } |
||
1078 | static::stdout("\r$prefix" . "[$status] $info"); |
||
1079 | } |
||
1080 | flush(); |
||
1081 | } |
||
1082 | |||
1083 | /** |
||
1084 | * Return width of the progressbar |
||
1085 | * @param string $prefix an optional string to display before the progress bar. |
||
1086 | * @see updateProgress |
||
1087 | * @return int screen width |
||
1088 | * @since 2.0.14 |
||
1089 | */ |
||
1090 | private static function getProgressbarWidth($prefix) |
||
1091 | { |
||
1092 | $width = self::$_progressWidth; |
||
1093 | |||
1094 | if ($width === false) { |
||
1095 | return 0; |
||
1096 | } |
||
1097 | |||
1098 | $screenSize = static::getScreenSize(true); |
||
1099 | if ($screenSize === false && $width < 1) { |
||
0 ignored issues
–
show
|
|||
1100 | return 0; |
||
1101 | } |
||
1102 | |||
1103 | if ($width === null) { |
||
1104 | $width = $screenSize[0]; |
||
1105 | } elseif ($width > 0 && $width < 1) { |
||
1106 | $width = floor($screenSize[0] * $width); |
||
1107 | } |
||
1108 | |||
1109 | $width -= static::ansiStrlen($prefix); |
||
1110 | |||
1111 | return $width; |
||
1112 | } |
||
1113 | |||
1114 | /** |
||
1115 | * Calculate $_progressEta, $_progressEtaLastUpdate and $_progressEtaLastDone |
||
1116 | * @param int $done the number of items that are completed. |
||
1117 | * @param int $total the total value of items that are to be done. |
||
1118 | * @see updateProgress |
||
1119 | * @since 2.0.14 |
||
1120 | */ |
||
1121 | private static function setETA($done, $total) |
||
1122 | { |
||
1123 | if ($done > $total || $done == 0) { |
||
1124 | self::$_progressEta = null; |
||
1125 | self::$_progressEtaLastUpdate = time(); |
||
1126 | return; |
||
1127 | } |
||
1128 | |||
1129 | if ($done < $total && (time() - self::$_progressEtaLastUpdate > 1 && $done > self::$_progressEtaLastDone)) { |
||
1130 | $rate = (time() - (self::$_progressEtaLastUpdate ?: self::$_progressStart)) / ($done - self::$_progressEtaLastDone); |
||
1131 | self::$_progressEta = $rate * ($total - $done); |
||
1132 | self::$_progressEtaLastUpdate = time(); |
||
1133 | self::$_progressEtaLastDone = $done; |
||
1134 | } |
||
1135 | } |
||
1136 | |||
1137 | /** |
||
1138 | * Ends a progress bar that has been started by [[startProgress()]]. |
||
1139 | * |
||
1140 | * @param string|bool $remove This can be `false` to leave the progress bar on screen and just print a newline. |
||
1141 | * If set to `true`, the line of the progress bar will be cleared. This may also be a string to be displayed instead |
||
1142 | * of the progress bar. |
||
1143 | * @param bool $keepPrefix whether to keep the prefix that has been specified for the progressbar when progressbar |
||
1144 | * gets removed. Defaults to true. |
||
1145 | * @see startProgress |
||
1146 | * @see updateProgress |
||
1147 | */ |
||
1148 | public static function endProgress($remove = false, $keepPrefix = true) |
||
1149 | { |
||
1150 | if ($remove === false) { |
||
1151 | static::stdout(PHP_EOL); |
||
1152 | } else { |
||
1153 | if (static::streamSupportsAnsiColors(STDOUT)) { |
||
1154 | static::clearLine(); |
||
1155 | } |
||
1156 | static::stdout("\r" . ($keepPrefix ? self::$_progressPrefix : '') . (is_string($remove) ? $remove : '')); |
||
1157 | } |
||
1158 | flush(); |
||
1159 | |||
1160 | self::$_progressStart = null; |
||
1161 | self::$_progressWidth = null; |
||
1162 | self::$_progressPrefix = ''; |
||
1163 | self::$_progressEta = null; |
||
1164 | self::$_progressEtaLastDone = 0; |
||
1165 | self::$_progressEtaLastUpdate = null; |
||
1166 | } |
||
1167 | |||
1168 | /** |
||
1169 | * Generates a summary of the validation errors. |
||
1170 | * @param Model|Model[] $models the model(s) whose validation errors are to be displayed. |
||
1171 | * @param array $options the tag options in terms of name-value pairs. The following options are specially handled: |
||
1172 | * |
||
1173 | * - showAllErrors: boolean, if set to true every error message for each attribute will be shown otherwise |
||
1174 | * only the first error message for each attribute will be shown. Defaults to `false`. |
||
1175 | * |
||
1176 | * @return string the generated error summary |
||
1177 | * @since 2.0.14 |
||
1178 | */ |
||
1179 | 1 | public static function errorSummary($models, $options = []) |
|
1180 | { |
||
1181 | 1 | $showAllErrors = ArrayHelper::remove($options, 'showAllErrors', false); |
|
1182 | 1 | $lines = self::collectErrors($models, $showAllErrors); |
|
1183 | |||
1184 | 1 | return implode(PHP_EOL, $lines); |
|
1185 | } |
||
1186 | |||
1187 | /** |
||
1188 | * Return array of the validation errors |
||
1189 | * @param Model|Model[] $models the model(s) whose validation errors are to be displayed. |
||
1190 | * @param $showAllErrors boolean, if set to true every error message for each attribute will be shown otherwise |
||
1191 | * only the first error message for each attribute will be shown. |
||
1192 | * @return array of the validation errors |
||
1193 | * @since 2.0.14 |
||
1194 | */ |
||
1195 | 1 | private static function collectErrors($models, $showAllErrors) |
|
1196 | { |
||
1197 | 1 | $lines = []; |
|
1198 | 1 | if (!is_array($models)) { |
|
1199 | 1 | $models = [$models]; |
|
1200 | } |
||
1201 | |||
1202 | 1 | foreach ($models as $model) { |
|
1203 | 1 | $lines = array_unique(array_merge($lines, $model->getErrorSummary($showAllErrors))); |
|
1204 | } |
||
1205 | |||
1206 | 1 | return $lines; |
|
1207 | } |
||
1208 | } |
||
1209 |