1 | <?php |
||
2 | /** |
||
3 | * @link http://www.yiiframework.com/ |
||
4 | * @copyright Copyright (c) 2008 Yii Software LLC |
||
5 | * @license http://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 | 1 | } |
|
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 | 1 | } |
|
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 | 1 | } |
|
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 | 1 | } |
|
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 | 1 | } |
|
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 | 1 | } |
|
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 | 1 | } |
|
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 | 1 | } |
|
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 | 1 | } |
|
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 | 1 | } |
|
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 | 1 | } |
|
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 | 1 | } |
|
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 | 1 | } |
|
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 | 1 | } |
|
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 | 1 | } |
|
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 | 1 | } |
|
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 | 1 | } |
|
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 | 1 | } |
|
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 | 1 | } |
|
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 | 32 | public static function ansiFormatCode($format) |
|
248 | { |
||
249 | 32 | 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 | 1 | } |
|
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 | 1 | } |
|
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 | 25 | public static function ansiFormat($string, $format = []) |
|
290 | { |
||
291 | 25 | $code = implode(';', $format); |
|
292 | |||
293 | 25 | 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 http://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 http://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 | 39 | public static function stripAnsiFormat($string) |
|
333 | { |
||
334 | 39 | return preg_replace(self::ansiCodesPattern(), '', $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 | public static function ansiStrlen($string) |
||
343 | { |
||
344 | 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 | 29 | public static function ansiStrwidth($string) |
|
354 | { |
||
355 | 29 | 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 | 29 | public static function ansiColorizedSubstr($string, $start, $length) |
|
368 | { |
||
369 | 29 | if ($start < 0 || $length <= 0) { |
|
370 | 1 | return ''; |
|
371 | } |
||
372 | |||
373 | 29 | $textItems = preg_split(self::ansiCodesPattern(), $string); |
|
374 | |||
375 | 29 | preg_match_all(self::ansiCodesPattern(), $string, $colors); |
|
376 | 29 | $colors = count($colors) ? $colors[0] : []; |
|
377 | 29 | array_unshift($colors, ''); |
|
378 | |||
379 | 29 | $result = ''; |
|
380 | 29 | $curPos = 0; |
|
381 | 29 | $inRange = false; |
|
382 | |||
383 | 29 | foreach ($textItems as $k => $textItem) { |
|
384 | 29 | $color = $colors[$k]; |
|
385 | |||
386 | 29 | if ($curPos <= $start && $start < $curPos + Console::ansiStrwidth($textItem)) { |
|
387 | 29 | $text = mb_substr($textItem, $start - $curPos, null, Yii::$app->charset); |
|
388 | 29 | $inRange = true; |
|
389 | } else { |
||
390 | 19 | $text = $textItem; |
|
391 | } |
||
392 | |||
393 | 29 | if ($inRange) { |
|
394 | 29 | $result .= $color . $text; |
|
395 | 29 | $diff = $length - Console::ansiStrwidth($result); |
|
396 | 29 | if ($diff <= 0) { |
|
397 | 29 | if ($diff < 0) { |
|
398 | 12 | $result = mb_substr($result, 0, $diff, Yii::$app->charset); |
|
399 | } |
||
400 | 29 | $defaultColor = static::renderColoredString('%n'); |
|
401 | 29 | if ($color && $color != $defaultColor) { |
|
402 | 7 | $result .= $defaultColor; |
|
403 | } |
||
404 | 29 | break; |
|
405 | } |
||
406 | } |
||
407 | |||
408 | 27 | $curPos += mb_strlen($textItem, Yii::$app->charset); |
|
409 | } |
||
410 | |||
411 | 29 | return $result; |
|
412 | } |
||
413 | |||
414 | 39 | private static function ansiCodesPattern() |
|
415 | { |
||
416 | 39 | 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 | $styleMap = [ |
||
435 | // http://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 | // http://meyerweb.com/eric/thoughts/2014/06/19/rebeccapurple/ |
||
442 | // http://dev.w3.org/csswg/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 | 14 | $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 | ); |
||
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 | 32 | public static function renderColoredString($string, $colored = true) |
|
580 | { |
||
581 | // TODO rework/refactor according to https://github.com/yiisoft/yii2/issues/746 |
||
582 | 32 | static $conversions = [ |
|
583 | '%y' => [self::FG_YELLOW], |
||
584 | '%g' => [self::FG_GREEN], |
||
585 | '%b' => [self::FG_BLUE], |
||
586 | '%r' => [self::FG_RED], |
||
587 | '%p' => [self::FG_PURPLE], |
||
588 | '%m' => [self::FG_PURPLE], |
||
589 | '%c' => [self::FG_CYAN], |
||
590 | '%w' => [self::FG_GREY], |
||
591 | '%k' => [self::FG_BLACK], |
||
592 | '%n' => [0], // reset |
||
593 | '%Y' => [self::FG_YELLOW, self::BOLD], |
||
594 | '%G' => [self::FG_GREEN, self::BOLD], |
||
595 | '%B' => [self::FG_BLUE, self::BOLD], |
||
596 | '%R' => [self::FG_RED, self::BOLD], |
||
597 | '%P' => [self::FG_PURPLE, self::BOLD], |
||
598 | '%M' => [self::FG_PURPLE, self::BOLD], |
||
599 | '%C' => [self::FG_CYAN, self::BOLD], |
||
600 | '%W' => [self::FG_GREY, self::BOLD], |
||
601 | '%K' => [self::FG_BLACK, self::BOLD], |
||
602 | '%N' => [0, self::BOLD], |
||
603 | '%3' => [self::BG_YELLOW], |
||
604 | '%2' => [self::BG_GREEN], |
||
605 | '%4' => [self::BG_BLUE], |
||
606 | '%1' => [self::BG_RED], |
||
607 | '%5' => [self::BG_PURPLE], |
||
608 | '%6' => [self::BG_CYAN], |
||
609 | '%7' => [self::BG_GREY], |
||
610 | '%0' => [self::BG_BLACK], |
||
611 | '%F' => [self::BLINK], |
||
612 | '%U' => [self::UNDERLINE], |
||
613 | '%8' => [self::NEGATIVE], |
||
614 | '%9' => [self::BOLD], |
||
615 | '%_' => [self::BOLD], |
||
616 | ]; |
||
617 | |||
618 | 32 | if ($colored) { |
|
619 | 32 | $string = str_replace('%%', '% ', $string); |
|
620 | 32 | foreach ($conversions as $key => $value) { |
|
621 | 32 | $string = str_replace( |
|
622 | 32 | $key, |
|
623 | 32 | static::ansiFormatCode($value), |
|
624 | 32 | $string |
|
625 | ); |
||
626 | } |
||
627 | 32 | $string = str_replace('% ', '%', $string); |
|
628 | } else { |
||
629 | 1 | $string = preg_replace('/%((%)|.)/', '$2', $string); |
|
630 | } |
||
631 | |||
632 | 32 | 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 | if ($size !== null && !$refresh) { |
|
692 | 4 | return $size; |
|
693 | } |
||
694 | |||
695 | 1 | if (static::isRunningOnWindows()) { |
|
696 | $output = []; |
||
697 | exec('mode con', $output); |
||
698 | if (isset($output[1]) && strpos($output[1], 'CON') !== false) { |
||
699 | return $size = [(int) preg_replace('~\D~', '', $output[4]), (int) preg_replace('~\D~', '', $output[3])]; |
||
700 | } |
||
701 | } else { |
||
702 | // try stty if available |
||
703 | 1 | $stty = []; |
|
704 | 1 | if (exec('stty -a 2>&1', $stty)) { |
|
705 | 1 | $stty = implode(' ', $stty); |
|
706 | |||
707 | // Linux stty output |
||
708 | 1 | if (preg_match('/rows\s+(\d+);\s*columns\s+(\d+);/mi', $stty, $matches)) { |
|
709 | return $size = [(int) $matches[2], (int) $matches[1]]; |
||
710 | } |
||
711 | |||
712 | // MacOS stty output |
||
713 | 1 | if (preg_match('/(\d+)\s+rows;\s*(\d+)\s+columns;/mi', $stty, $matches)) { |
|
714 | return $size = [(int) $matches[2], (int) $matches[1]]; |
||
715 | } |
||
716 | } |
||
717 | |||
718 | // fallback to tput, which may not be updated on terminal resize |
||
719 | 1 | if (($width = (int) exec('tput cols 2>&1')) > 0 && ($height = (int) exec('tput lines 2>&1')) > 0) { |
|
720 | return $size = [$width, $height]; |
||
721 | } |
||
722 | |||
723 | // fallback to ENV variables, which may not be updated on terminal resize |
||
724 | 1 | if (($width = (int) getenv('COLUMNS')) > 0 && ($height = (int) getenv('LINES')) > 0) { |
|
725 | return $size = [$width, $height]; |
||
726 | } |
||
727 | } |
||
728 | |||
729 | 1 | return $size = false; |
|
730 | } |
||
731 | |||
732 | /** |
||
733 | * Word wrap text with indentation to fit the screen size. |
||
734 | * |
||
735 | * If screen size could not be detected, or the indentation is greater than the screen size, the text will not be wrapped. |
||
736 | * |
||
737 | * The first line will **not** be indented, so `Console::wrapText("Lorem ipsum dolor sit amet.", 4)` will result in the |
||
738 | * following output, given the screen width is 16 characters: |
||
739 | * |
||
740 | * ``` |
||
741 | * Lorem ipsum |
||
742 | * dolor sit |
||
743 | * amet. |
||
744 | * ``` |
||
745 | * |
||
746 | * @param string $text the text to be wrapped |
||
747 | * @param int $indent number of spaces to use for indentation. |
||
748 | * @param bool $refresh whether to force refresh of screen size. |
||
749 | * This will be passed to [[getScreenSize()]]. |
||
750 | * @return string the wrapped text. |
||
751 | * @since 2.0.4 |
||
752 | */ |
||
753 | 2 | public static function wrapText($text, $indent = 0, $refresh = false) |
|
754 | { |
||
755 | 2 | $size = static::getScreenSize($refresh); |
|
756 | 2 | if ($size === false || $size[0] <= $indent) { |
|
757 | 2 | return $text; |
|
758 | } |
||
759 | $pad = str_repeat(' ', $indent); |
||
760 | $lines = explode("\n", wordwrap($text, $size[0] - $indent, "\n")); |
||
761 | $first = true; |
||
762 | foreach ($lines as $i => $line) { |
||
763 | if ($first) { |
||
764 | $first = false; |
||
765 | continue; |
||
766 | } |
||
767 | $lines[$i] = $pad . $line; |
||
768 | } |
||
769 | |||
770 | return implode("\n", $lines); |
||
771 | } |
||
772 | |||
773 | /** |
||
774 | * Gets input from STDIN and returns a string right-trimmed for EOLs. |
||
775 | * |
||
776 | * @param bool $raw If set to true, returns the raw string without trimming |
||
777 | * @return string the string read from stdin |
||
778 | */ |
||
779 | public static function stdin($raw = false) |
||
780 | { |
||
781 | return $raw ? fgets(\STDIN) : rtrim(fgets(\STDIN), PHP_EOL); |
||
782 | } |
||
783 | |||
784 | /** |
||
785 | * Prints a string to STDOUT. |
||
786 | * |
||
787 | * @param string $string the string to print |
||
788 | * @return int|bool Number of bytes printed or false on error |
||
789 | */ |
||
790 | public static function stdout($string) |
||
791 | { |
||
792 | return fwrite(\STDOUT, $string); |
||
793 | } |
||
794 | |||
795 | /** |
||
796 | * Prints a string to STDERR. |
||
797 | * |
||
798 | * @param string $string the string to print |
||
799 | * @return int|bool Number of bytes printed or false on error |
||
800 | */ |
||
801 | public static function stderr($string) |
||
802 | { |
||
803 | return fwrite(\STDERR, $string); |
||
804 | } |
||
805 | |||
806 | /** |
||
807 | * Asks the user for input. Ends when the user types a carriage return (PHP_EOL). Optionally, It also provides a |
||
808 | * prompt. |
||
809 | * |
||
810 | * @param string $prompt the prompt to display before waiting for input (optional) |
||
811 | * @return string the user's input |
||
812 | */ |
||
813 | public static function input($prompt = null) |
||
814 | { |
||
815 | if (isset($prompt)) { |
||
816 | static::stdout($prompt); |
||
817 | } |
||
818 | |||
819 | return static::stdin(); |
||
820 | } |
||
821 | |||
822 | /** |
||
823 | * Prints text to STDOUT appended with a carriage return (PHP_EOL). |
||
824 | * |
||
825 | * @param string $string the text to print |
||
826 | * @return int|bool number of bytes printed or false on error. |
||
827 | */ |
||
828 | public static function output($string = null) |
||
829 | { |
||
830 | return static::stdout($string . PHP_EOL); |
||
831 | } |
||
832 | |||
833 | /** |
||
834 | * Prints text to STDERR appended with a carriage return (PHP_EOL). |
||
835 | * |
||
836 | * @param string $string the text to print |
||
837 | * @return int|bool number of bytes printed or false on error. |
||
838 | */ |
||
839 | public static function error($string = null) |
||
840 | { |
||
841 | return static::stderr($string . PHP_EOL); |
||
842 | } |
||
843 | |||
844 | /** |
||
845 | * Prompts the user for input and validates it. |
||
846 | * |
||
847 | * @param string $text prompt string |
||
848 | * @param array $options the options to validate the input: |
||
849 | * |
||
850 | * - `required`: whether it is required or not |
||
851 | * - `default`: default value if no input is inserted by the user |
||
852 | * - `pattern`: regular expression pattern to validate user input |
||
853 | * - `validator`: a callable function to validate input. The function must accept two parameters: |
||
854 | * - `input`: the user input to validate |
||
855 | * - `error`: the error value passed by reference if validation failed. |
||
856 | * |
||
857 | * @return string the user input |
||
858 | */ |
||
859 | public static function prompt($text, $options = []) |
||
860 | { |
||
861 | $options = ArrayHelper::merge( |
||
862 | [ |
||
863 | 'required' => false, |
||
864 | 'default' => null, |
||
865 | 'pattern' => null, |
||
866 | 'validator' => null, |
||
867 | 'error' => 'Invalid input.', |
||
868 | ], |
||
869 | $options |
||
870 | ); |
||
871 | $error = null; |
||
872 | |||
873 | top: |
||
874 | $input = $options['default'] |
||
875 | ? static::input("$text [" . $options['default'] . '] ') |
||
876 | : static::input("$text "); |
||
877 | |||
878 | if ($input === '') { |
||
879 | if (isset($options['default'])) { |
||
880 | $input = $options['default']; |
||
881 | } elseif ($options['required']) { |
||
882 | static::output($options['error']); |
||
883 | goto top; |
||
884 | } |
||
885 | } elseif ($options['pattern'] && !preg_match($options['pattern'], $input)) { |
||
886 | static::output($options['error']); |
||
887 | goto top; |
||
888 | } elseif ($options['validator'] && |
||
889 | !call_user_func_array($options['validator'], [$input, &$error]) |
||
890 | ) { |
||
891 | static::output(isset($error) ? $error : $options['error']); |
||
892 | goto top; |
||
893 | } |
||
894 | |||
895 | return $input; |
||
896 | } |
||
897 | |||
898 | /** |
||
899 | * Asks user to confirm by typing y or n. |
||
900 | * |
||
901 | * A typical usage looks like the following: |
||
902 | * |
||
903 | * ```php |
||
904 | * if (Console::confirm("Are you sure?")) { |
||
905 | * echo "user typed yes\n"; |
||
906 | * } else { |
||
907 | * echo "user typed no\n"; |
||
908 | * } |
||
909 | * ``` |
||
910 | * |
||
911 | * @param string $message to print out before waiting for user input |
||
912 | * @param bool $default this value is returned if no selection is made. |
||
913 | * @return bool whether user confirmed |
||
914 | */ |
||
915 | public static function confirm($message, $default = false) |
||
916 | { |
||
917 | while (true) { |
||
918 | static::stdout($message . ' (yes|no) [' . ($default ? 'yes' : 'no') . ']:'); |
||
919 | $input = trim(static::stdin()); |
||
920 | |||
921 | if (empty($input)) { |
||
922 | return $default; |
||
923 | } |
||
924 | |||
925 | if (!strcasecmp($input, 'y') || !strcasecmp($input, 'yes')) { |
||
926 | return true; |
||
927 | } |
||
928 | |||
929 | if (!strcasecmp($input, 'n') || !strcasecmp($input, 'no')) { |
||
930 | return false; |
||
931 | } |
||
932 | } |
||
933 | } |
||
934 | |||
935 | /** |
||
936 | * Gives the user an option to choose from. Giving '?' as an input will show |
||
937 | * a list of options to choose from and their explanations. |
||
938 | * |
||
939 | * @param string $prompt the prompt message |
||
940 | * @param array $options Key-value array of options to choose from. Key is what is inputed and used, value is |
||
941 | * what's displayed to end user by help command. |
||
942 | * |
||
943 | * @return string An option character the user chose |
||
944 | */ |
||
945 | public static function select($prompt, $options = []) |
||
946 | { |
||
947 | top: |
||
948 | static::stdout("$prompt [" . implode(',', array_keys($options)) . ',?]: '); |
||
949 | $input = static::stdin(); |
||
950 | if ($input === '?') { |
||
951 | foreach ($options as $key => $value) { |
||
952 | static::output(" $key - $value"); |
||
953 | } |
||
954 | static::output(' ? - Show help'); |
||
955 | goto top; |
||
956 | } elseif (!array_key_exists($input, $options)) { |
||
957 | goto top; |
||
958 | } |
||
959 | |||
960 | return $input; |
||
961 | } |
||
962 | |||
963 | private static $_progressStart; |
||
964 | private static $_progressWidth; |
||
965 | private static $_progressPrefix; |
||
966 | private static $_progressEta; |
||
967 | private static $_progressEtaLastDone = 0; |
||
968 | private static $_progressEtaLastUpdate; |
||
969 | |||
970 | /** |
||
971 | * Starts display of a progress bar on screen. |
||
972 | * |
||
973 | * This bar will be updated by [[updateProgress()]] and may be ended by [[endProgress()]]. |
||
974 | * |
||
975 | * The following example shows a simple usage of a progress bar: |
||
976 | * |
||
977 | * ```php |
||
978 | * Console::startProgress(0, 1000); |
||
979 | * for ($n = 1; $n <= 1000; $n++) { |
||
980 | * usleep(1000); |
||
981 | * Console::updateProgress($n, 1000); |
||
982 | * } |
||
983 | * Console::endProgress(); |
||
984 | * ``` |
||
985 | * |
||
986 | * Git clone like progress (showing only status information): |
||
987 | * |
||
988 | * ```php |
||
989 | * Console::startProgress(0, 1000, 'Counting objects: ', false); |
||
990 | * for ($n = 1; $n <= 1000; $n++) { |
||
991 | * usleep(1000); |
||
992 | * Console::updateProgress($n, 1000); |
||
993 | * } |
||
994 | * Console::endProgress("done." . PHP_EOL); |
||
995 | * ``` |
||
996 | * |
||
997 | * @param int $done the number of items that are completed. |
||
998 | * @param int $total the total value of items that are to be done. |
||
999 | * @param string $prefix an optional string to display before the progress bar. |
||
1000 | * Default to empty string which results in no prefix to be displayed. |
||
1001 | * @param int|bool $width optional width of the progressbar. This can be an integer representing |
||
1002 | * the number of characters to display for the progress bar or a float between 0 and 1 representing the |
||
1003 | * percentage of screen with the progress bar may take. It can also be set to false to disable the |
||
1004 | * bar and only show progress information like percent, number of items and ETA. |
||
1005 | * If not set, the bar will be as wide as the screen. Screen size will be detected using [[getScreenSize()]]. |
||
1006 | * @see startProgress |
||
1007 | * @see updateProgress |
||
1008 | * @see endProgress |
||
1009 | */ |
||
1010 | public static function startProgress($done, $total, $prefix = '', $width = null) |
||
1011 | { |
||
1012 | self::$_progressStart = time(); |
||
1013 | self::$_progressWidth = $width; |
||
1014 | self::$_progressPrefix = $prefix; |
||
1015 | self::$_progressEta = null; |
||
1016 | self::$_progressEtaLastDone = 0; |
||
1017 | self::$_progressEtaLastUpdate = time(); |
||
1018 | |||
1019 | static::updateProgress($done, $total); |
||
1020 | } |
||
1021 | |||
1022 | /** |
||
1023 | * Updates a progress bar that has been started by [[startProgress()]]. |
||
1024 | * |
||
1025 | * @param int $done the number of items that are completed. |
||
1026 | * @param int $total the total value of items that are to be done. |
||
1027 | * @param string $prefix an optional string to display before the progress bar. |
||
1028 | * Defaults to null meaning the prefix specified by [[startProgress()]] will be used. |
||
1029 | * If prefix is specified it will update the prefix that will be used by later calls. |
||
1030 | * @see startProgress |
||
1031 | * @see endProgress |
||
1032 | */ |
||
1033 | public static function updateProgress($done, $total, $prefix = null) |
||
1034 | { |
||
1035 | if ($prefix === null) { |
||
1036 | $prefix = self::$_progressPrefix; |
||
1037 | } else { |
||
1038 | self::$_progressPrefix = $prefix; |
||
1039 | } |
||
1040 | $width = static::getProgressbarWidth($prefix); |
||
1041 | $percent = ($total == 0) ? 1 : $done / $total; |
||
1042 | $info = sprintf('%d%% (%d/%d)', $percent * 100, $done, $total); |
||
1043 | self::setETA($done, $total); |
||
1044 | $info .= self::$_progressEta === null ? ' ETA: n/a' : sprintf(' ETA: %d sec.', self::$_progressEta); |
||
1045 | |||
1046 | // Number extra characters outputted. These are opening [, closing ], and space before info |
||
1047 | // Since Windows uses \r\n\ for line endings, there's one more in the case |
||
1048 | $extraChars = static::isRunningOnWindows() ? 4 : 3; |
||
1049 | $width -= $extraChars + static::ansiStrlen($info); |
||
1050 | // skipping progress bar on very small display or if forced to skip |
||
1051 | if ($width < 5) { |
||
1052 | static::stdout("\r$prefix$info "); |
||
1053 | } else { |
||
1054 | if ($percent < 0) { |
||
1055 | $percent = 0; |
||
1056 | } elseif ($percent > 1) { |
||
1057 | $percent = 1; |
||
1058 | } |
||
1059 | $bar = floor($percent * $width); |
||
1060 | $status = str_repeat('=', $bar); |
||
1061 | if ($bar < $width) { |
||
1062 | $status .= '>'; |
||
1063 | $status .= str_repeat(' ', $width - $bar - 1); |
||
1064 | } |
||
1065 | static::stdout("\r$prefix" . "[$status] $info"); |
||
1066 | } |
||
1067 | flush(); |
||
1068 | } |
||
1069 | |||
1070 | /** |
||
1071 | * Return width of the progressbar |
||
1072 | * @param string $prefix an optional string to display before the progress bar. |
||
1073 | * @see updateProgress |
||
1074 | * @return int screen width |
||
1075 | * @since 2.0.14 |
||
1076 | */ |
||
1077 | private static function getProgressbarWidth($prefix) |
||
1078 | { |
||
1079 | $width = self::$_progressWidth; |
||
1080 | |||
1081 | if ($width === false) { |
||
1082 | return 0; |
||
1083 | } |
||
1084 | |||
1085 | $screenSize = static::getScreenSize(true); |
||
1086 | if ($screenSize === false && $width < 1) { |
||
0 ignored issues
–
show
introduced
by
Loading history...
|
|||
1087 | return 0; |
||
1088 | } |
||
1089 | |||
1090 | if ($width === null) { |
||
1091 | $width = $screenSize[0]; |
||
1092 | } elseif ($width > 0 && $width < 1) { |
||
1093 | $width = floor($screenSize[0] * $width); |
||
1094 | } |
||
1095 | |||
1096 | $width -= static::ansiStrlen($prefix); |
||
1097 | |||
1098 | return $width; |
||
1099 | } |
||
1100 | |||
1101 | /** |
||
1102 | * Calculate $_progressEta, $_progressEtaLastUpdate and $_progressEtaLastDone |
||
1103 | * @param int $done the number of items that are completed. |
||
1104 | * @param int $total the total value of items that are to be done. |
||
1105 | * @see updateProgress |
||
1106 | * @since 2.0.14 |
||
1107 | */ |
||
1108 | private static function setETA($done, $total) |
||
1109 | { |
||
1110 | if ($done > $total || $done == 0) { |
||
1111 | self::$_progressEta = null; |
||
1112 | self::$_progressEtaLastUpdate = time(); |
||
1113 | return; |
||
1114 | } |
||
1115 | |||
1116 | if ($done < $total && (time() - self::$_progressEtaLastUpdate > 1 && $done > self::$_progressEtaLastDone)) { |
||
1117 | $rate = (time() - (self::$_progressEtaLastUpdate ?: self::$_progressStart)) / ($done - self::$_progressEtaLastDone); |
||
1118 | self::$_progressEta = $rate * ($total - $done); |
||
1119 | self::$_progressEtaLastUpdate = time(); |
||
1120 | self::$_progressEtaLastDone = $done; |
||
1121 | } |
||
1122 | } |
||
1123 | |||
1124 | /** |
||
1125 | * Ends a progress bar that has been started by [[startProgress()]]. |
||
1126 | * |
||
1127 | * @param string|bool $remove This can be `false` to leave the progress bar on screen and just print a newline. |
||
1128 | * If set to `true`, the line of the progress bar will be cleared. This may also be a string to be displayed instead |
||
1129 | * of the progress bar. |
||
1130 | * @param bool $keepPrefix whether to keep the prefix that has been specified for the progressbar when progressbar |
||
1131 | * gets removed. Defaults to true. |
||
1132 | * @see startProgress |
||
1133 | * @see updateProgress |
||
1134 | */ |
||
1135 | public static function endProgress($remove = false, $keepPrefix = true) |
||
1136 | { |
||
1137 | if ($remove === false) { |
||
1138 | static::stdout(PHP_EOL); |
||
1139 | } else { |
||
1140 | if (static::streamSupportsAnsiColors(STDOUT)) { |
||
1141 | static::clearLine(); |
||
1142 | } |
||
1143 | static::stdout("\r" . ($keepPrefix ? self::$_progressPrefix : '') . (is_string($remove) ? $remove : '')); |
||
1144 | } |
||
1145 | flush(); |
||
1146 | |||
1147 | self::$_progressStart = null; |
||
1148 | self::$_progressWidth = null; |
||
1149 | self::$_progressPrefix = ''; |
||
1150 | self::$_progressEta = null; |
||
1151 | self::$_progressEtaLastDone = 0; |
||
1152 | self::$_progressEtaLastUpdate = null; |
||
1153 | } |
||
1154 | |||
1155 | /** |
||
1156 | * Generates a summary of the validation errors. |
||
1157 | * @param Model|Model[] $models the model(s) whose validation errors are to be displayed. |
||
1158 | * @param array $options the tag options in terms of name-value pairs. The following options are specially handled: |
||
1159 | * |
||
1160 | * - showAllErrors: boolean, if set to true every error message for each attribute will be shown otherwise |
||
1161 | * only the first error message for each attribute will be shown. Defaults to `false`. |
||
1162 | * |
||
1163 | * @return string the generated error summary |
||
1164 | * @since 2.0.14 |
||
1165 | */ |
||
1166 | 1 | public static function errorSummary($models, $options = []) |
|
1167 | { |
||
1168 | 1 | $showAllErrors = ArrayHelper::remove($options, 'showAllErrors', false); |
|
1169 | 1 | $lines = self::collectErrors($models, $showAllErrors); |
|
1170 | |||
1171 | 1 | return implode(PHP_EOL, $lines); |
|
1172 | } |
||
1173 | |||
1174 | /** |
||
1175 | * Return array of the validation errors |
||
1176 | * @param Model|Model[] $models the model(s) whose validation errors are to be displayed. |
||
1177 | * @param $showAllErrors boolean, if set to true every error message for each attribute will be shown otherwise |
||
1178 | * only the first error message for each attribute will be shown. |
||
1179 | * @return array of the validation errors |
||
1180 | * @since 2.0.14 |
||
1181 | */ |
||
1182 | 1 | private static function collectErrors($models, $showAllErrors) |
|
1183 | { |
||
1184 | 1 | $lines = []; |
|
1185 | 1 | if (!is_array($models)) { |
|
1186 | 1 | $models = [$models]; |
|
1187 | } |
||
1188 | |||
1189 | 1 | foreach ($models as $model) { |
|
1190 | 1 | $lines = array_unique(array_merge($lines, $model->getErrorSummary($showAllErrors))); |
|
1191 | } |
||
1192 | |||
1193 | 1 | return $lines; |
|
1194 | } |
||
1195 | } |
||
1196 |