Code Coverage |
||||||||||||||||
Lines |
Branches |
Paths |
Functions and Methods |
Classes and Traits |
||||||||||||
| Total | |
100.00% |
40 / 40 |
|
96.43% |
54 / 56 |
|
59.38% |
19 / 32 |
|
83.33% |
10 / 12 |
CRAP | |
0.00% |
0 / 1 |
| Stdio | |
100.00% |
40 / 40 |
|
96.43% |
54 / 56 |
|
59.38% |
19 / 32 |
|
100.00% |
12 / 12 |
128.79 | |
100.00% |
1 / 1 |
| __construct | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| write | |
100.00% |
5 / 5 |
|
100.00% |
4 / 4 |
|
0.00% |
0 / 2 |
|
100.00% |
1 / 1 |
1 | |||
| read | |
100.00% |
3 / 3 |
|
100.00% |
6 / 6 |
|
50.00% |
2 / 4 |
|
100.00% |
1 / 1 |
8.12 | |||
| colors | |
100.00% |
11 / 11 |
|
92.31% |
12 / 13 |
|
71.43% |
5 / 7 |
|
100.00% |
1 / 1 |
10.89 | |||
| interactive | |
100.00% |
1 / 1 |
|
66.67% |
2 / 3 |
|
50.00% |
1 / 2 |
|
100.00% |
1 / 1 |
2.50 | |||
| width | |
100.00% |
6 / 6 |
|
100.00% |
9 / 9 |
|
40.00% |
2 / 5 |
|
100.00% |
1 / 1 |
13.78 | |||
| line | |
100.00% |
1 / 1 |
|
100.00% |
4 / 4 |
|
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
2 | |||
| stty | n/a |
0 / 0 |
n/a |
0 / 0 |
n/a |
0 / 0 |
n/a |
0 / 0 |
3 | |||||||
| stream | |
100.00% |
1 / 1 |
|
100.00% |
4 / 4 |
|
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
2 | |||
| stdout | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| stderr | |
100.00% |
3 / 3 |
|
100.00% |
4 / 4 |
|
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
2 | |||
| stdin | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| open | |
100.00% |
6 / 6 |
|
100.00% |
6 / 6 |
|
0.00% |
0 / 3 |
|
100.00% |
1 / 1 |
2 | |||
| 1 | <?php |
| 2 | |
| 3 | declare(strict_types=1); |
| 4 | |
| 5 | namespace Celema\Console; |
| 6 | |
| 7 | use Override; |
| 8 | use RuntimeException; |
| 9 | |
| 10 | /** |
| 11 | * The process streams, or other targets such as files or `/dev/tty`. |
| 12 | * |
| 13 | * Targets open on first use; one that cannot be opened throws a |
| 14 | * RuntimeException then. The output and the error output share one |
| 15 | * stream when their targets are the same. Colors are detected per |
| 16 | * stream unless `$colors` decides for both. |
| 17 | * |
| 18 | * @api |
| 19 | */ |
| 20 | final class Stdio implements Terminal |
| 21 | { |
| 22 | /** @var null|resource */ |
| 23 | private mixed $stream = null; |
| 24 | |
| 25 | /** @var null|resource */ |
| 26 | private mixed $errorStream = null; |
| 27 | |
| 28 | /** @var null|resource */ |
| 29 | private mixed $inputStream = null; |
| 30 | |
| 31 | private ?int $width = null; |
| 32 | |
| 33 | public function __construct( |
| 34 | private readonly string $output = 'php://stdout', |
| 35 | private readonly string $errorOutput = 'php://stderr', |
| 36 | private readonly string $input = 'php://stdin', |
| 37 | private readonly ?bool $colors = null, |
| 38 | ) {} |
| 39 | |
| 40 | #[Override] |
| 41 | public function write(string $text, bool $error = false): void |
| 42 | { |
| 43 | $stream = $this->stream($error); |
| 44 | |
| 45 | // A reader that went away, like `| head -1`, fails every write; the |
| 46 | // output is lost anyway and must not add a notice per line. |
| 47 | set_error_handler(static fn(): bool => true); |
| 48 | |
| 49 | try { |
| 50 | fwrite($stream, $text); |
| 51 | fflush($stream); |
| 52 | } finally { |
| 53 | restore_error_handler(); |
| 54 | } |
| 55 | } |
| 56 | |
| 57 | /** |
| 58 | * Hidden input switches the terminal echo off with stty and throws a |
| 59 | * RuntimeException if that fails. On Windows, or without a terminal, |
| 60 | * the line is read visibly. |
| 61 | */ |
| 62 | #[Override] |
| 63 | public function read(bool $hidden = false): ?string |
| 64 | { |
| 65 | $stream = $this->stdin(); |
| 66 | |
| 67 | // No stty on Windows: hidden input reads visibly there. |
| 68 | if ($hidden && DIRECTORY_SEPARATOR !== '\\' && stream_isatty($stream)) { |
| 69 | // @codeCoverageIgnoreStart |
| 70 | // Needs a real terminal; HiddenInputTest drives this in a child |
| 71 | // process, outside the coverage run. |
| 72 | $previous = $this->stty($stream, '-g'); |
| 73 | $this->stty($stream, '-echo'); |
| 74 | |
| 75 | try { |
| 76 | return $this->line(fgets($stream)); |
| 77 | } finally { |
| 78 | // Restore the saved terminal state rather than assuming |
| 79 | // echo was on, even when reading throws. |
| 80 | $this->stty($stream, $previous === '' ? 'echo' : $previous); |
| 81 | $this->write(PHP_EOL); |
| 82 | } |
| 83 | |
| 84 | // @codeCoverageIgnoreEnd |
| 85 | } |
| 86 | |
| 87 | return $this->line(fgets($stream)); |
| 88 | } |
| 89 | |
| 90 | /** |
| 91 | * `NO_COLOR` turns colors off, `FORCE_COLOR` on (`0` or `false` off); |
| 92 | * otherwise only a terminal gets them, unless `TERM` is `dumb`. |
| 93 | */ |
| 94 | #[Override] |
| 95 | public function colors(bool $error = false): bool |
| 96 | { |
| 97 | if ($this->colors !== null) { |
| 98 | return $this->colors; |
| 99 | } |
| 100 | |
| 101 | $noColor = getenv('NO_COLOR'); |
| 102 | |
| 103 | if ($noColor !== false && $noColor !== '') { |
| 104 | return false; |
| 105 | } |
| 106 | |
| 107 | $force = getenv('FORCE_COLOR'); |
| 108 | |
| 109 | if ($force !== false) { |
| 110 | return $force !== '0' && strtolower($force) !== 'false'; |
| 111 | } |
| 112 | |
| 113 | $stream = $this->stream($error); |
| 114 | $terminal = stream_isatty($stream) && getenv('TERM') !== 'dumb'; |
| 115 | |
| 116 | // @codeCoverageIgnoreStart |
| 117 | if (DIRECTORY_SEPARATOR === '\\' && $terminal) { |
| 118 | // VT100 processing is off by default in cmd/PowerShell; |
| 119 | // enabling it reports whether the console supports it. |
| 120 | return sapi_windows_vt100_support($stream, enable: true); |
| 121 | } |
| 122 | |
| 123 | // @codeCoverageIgnoreEnd |
| 124 | |
| 125 | return $terminal; |
| 126 | } |
| 127 | |
| 128 | /** |
| 129 | * True when both the input and the output are terminals. |
| 130 | */ |
| 131 | #[Override] |
| 132 | public function interactive(): bool |
| 133 | { |
| 134 | return stream_isatty($this->stdin()) && stream_isatty($this->stdout()); |
| 135 | } |
| 136 | |
| 137 | /** |
| 138 | * `COLUMNS`, else the terminal's width, else 80; read once. |
| 139 | */ |
| 140 | #[Override] |
| 141 | public function width(): int |
| 142 | { |
| 143 | if ($this->width !== null) { |
| 144 | return $this->width; |
| 145 | } |
| 146 | |
| 147 | $columns = (int) getenv('COLUMNS'); |
| 148 | |
| 149 | // No tput on Windows; shell_exec would leak its error output. |
| 150 | if ($columns < 1 && DIRECTORY_SEPARATOR !== '\\' && stream_isatty($this->stdout())) { |
| 151 | // @codeCoverageIgnoreStart |
| 152 | /** @psalm-suppress ForbiddenCode A constant command; no input reaches the shell */ |
| 153 | $columns = (int) shell_exec('tput cols 2>/dev/null'); |
| 154 | |
| 155 | // @codeCoverageIgnoreEnd |
| 156 | } |
| 157 | |
| 158 | if ($columns < 1) { |
| 159 | // @codeCoverageIgnoreStart |
| 160 | $columns = 80; |
| 161 | |
| 162 | // @codeCoverageIgnoreEnd |
| 163 | } |
| 164 | |
| 165 | return $this->width = $columns; |
| 166 | } |
| 167 | |
| 168 | private function line(string|false $line): ?string |
| 169 | { |
| 170 | return $line === false ? null : rtrim($line, characters: "\r\n"); |
| 171 | } |
| 172 | |
| 173 | /** |
| 174 | * Runs stty on the input terminal and returns its output. |
| 175 | * |
| 176 | * The stream becomes stty's STDIN: the process STDIN may be another |
| 177 | * file, e.g. when only the prompts read from `/dev/tty`. Failures |
| 178 | * throw, so a hidden prompt never reads while echo is still on. |
| 179 | * |
| 180 | * @codeCoverageIgnore |
| 181 | * |
| 182 | * @param resource $stream |
| 183 | */ |
| 184 | private function stty(mixed $stream, string $arg): string |
| 185 | { |
| 186 | set_error_handler(static fn(): bool => true); |
| 187 | |
| 188 | try { |
| 189 | $process = proc_open(['stty', $arg], [0 => $stream, 1 => ['pipe', 'w'], 2 => ['pipe', 'w']], $pipes); |
| 190 | } finally { |
| 191 | restore_error_handler(); |
| 192 | } |
| 193 | |
| 194 | if ($process === false) { |
| 195 | throw new RuntimeException('Could not run stty to hide the input'); |
| 196 | } |
| 197 | |
| 198 | $output = (string) stream_get_contents($pipes[1]); |
| 199 | $error = trim((string) stream_get_contents($pipes[2])); |
| 200 | |
| 201 | if (proc_close($process) !== 0) { |
| 202 | throw new RuntimeException('Could not switch the terminal echo for hidden input: ' . $error); |
| 203 | } |
| 204 | |
| 205 | return trim($output); |
| 206 | } |
| 207 | |
| 208 | /** @return resource */ |
| 209 | private function stream(bool $error): mixed |
| 210 | { |
| 211 | return $error ? $this->stderr() : $this->stdout(); |
| 212 | } |
| 213 | |
| 214 | /** @return resource */ |
| 215 | private function stdout(): mixed |
| 216 | { |
| 217 | return $this->stream ??= $this->open($this->output, 'w'); |
| 218 | } |
| 219 | |
| 220 | /** @return resource */ |
| 221 | private function stderr(): mixed |
| 222 | { |
| 223 | // One stream for a shared target: a second one would truncate |
| 224 | // the file and overwrite what the first wrote. |
| 225 | return $this->errorStream ??= $this->errorOutput === $this->output |
| 226 | ? $this->stdout() |
| 227 | : $this->open($this->errorOutput, 'w'); |
| 228 | } |
| 229 | |
| 230 | /** @return resource */ |
| 231 | private function stdin(): mixed |
| 232 | { |
| 233 | return $this->inputStream ??= $this->open($this->input, 'r'); |
| 234 | } |
| 235 | |
| 236 | /** @return resource */ |
| 237 | private function open(string $target, string $mode): mixed |
| 238 | { |
| 239 | set_error_handler(static fn(): bool => true); |
| 240 | |
| 241 | try { |
| 242 | $stream = fopen($target, $mode); |
| 243 | } finally { |
| 244 | restore_error_handler(); |
| 245 | } |
| 246 | |
| 247 | if ($stream === false) { |
| 248 | throw new RuntimeException("Could not open stream '{$target}'"); |
| 249 | } |
| 250 | |
| 251 | return $stream; |
| 252 | } |
| 253 | } |
Below are the source code lines that represent each code path as identified by Xdebug. Please note a path is not
necessarily coterminous with a line, a line may contain multiple paths and therefore show up more than once.
Please also be aware that some paths may include implicit rather than explicit branches, e.g. an if statement
always has an else as part of its logical flow even if you didn't write one.
| 34 | private readonly string $output = 'php://stdout', |
| 35 | private readonly string $errorOutput = 'php://stderr', |
| 36 | private readonly string $input = 'php://stdin', |
| 37 | private readonly ?bool $colors = null, |
| 38 | ) {} |
| 95 | public function colors(bool $error = false): bool |
| 96 | { |
| 97 | if ($this->colors !== null) { |
| 98 | return $this->colors; |
| 95 | public function colors(bool $error = false): bool |
| 96 | { |
| 97 | if ($this->colors !== null) { |
| 101 | $noColor = getenv('NO_COLOR'); |
| 102 | |
| 103 | if ($noColor !== false && $noColor !== '') { |
| 103 | if ($noColor !== false && $noColor !== '') { |
| 103 | if ($noColor !== false && $noColor !== '') { |
| 104 | return false; |
| 95 | public function colors(bool $error = false): bool |
| 96 | { |
| 97 | if ($this->colors !== null) { |
| 101 | $noColor = getenv('NO_COLOR'); |
| 102 | |
| 103 | if ($noColor !== false && $noColor !== '') { |
| 103 | if ($noColor !== false && $noColor !== '') { |
| 103 | if ($noColor !== false && $noColor !== '') { |
| 107 | $force = getenv('FORCE_COLOR'); |
| 108 | |
| 109 | if ($force !== false) { |
| 110 | return $force !== '0' && strtolower($force) !== 'false'; |
| 110 | return $force !== '0' && strtolower($force) !== 'false'; |
| 110 | return $force !== '0' && strtolower($force) !== 'false'; |
| 95 | public function colors(bool $error = false): bool |
| 96 | { |
| 97 | if ($this->colors !== null) { |
| 101 | $noColor = getenv('NO_COLOR'); |
| 102 | |
| 103 | if ($noColor !== false && $noColor !== '') { |
| 103 | if ($noColor !== false && $noColor !== '') { |
| 103 | if ($noColor !== false && $noColor !== '') { |
| 107 | $force = getenv('FORCE_COLOR'); |
| 108 | |
| 109 | if ($force !== false) { |
| 110 | return $force !== '0' && strtolower($force) !== 'false'; |
| 110 | return $force !== '0' && strtolower($force) !== 'false'; |
| 95 | public function colors(bool $error = false): bool |
| 96 | { |
| 97 | if ($this->colors !== null) { |
| 101 | $noColor = getenv('NO_COLOR'); |
| 102 | |
| 103 | if ($noColor !== false && $noColor !== '') { |
| 103 | if ($noColor !== false && $noColor !== '') { |
| 104 | return false; |
| 95 | public function colors(bool $error = false): bool |
| 96 | { |
| 97 | if ($this->colors !== null) { |
| 101 | $noColor = getenv('NO_COLOR'); |
| 102 | |
| 103 | if ($noColor !== false && $noColor !== '') { |
| 103 | if ($noColor !== false && $noColor !== '') { |
| 107 | $force = getenv('FORCE_COLOR'); |
| 108 | |
| 109 | if ($force !== false) { |
| 110 | return $force !== '0' && strtolower($force) !== 'false'; |
| 110 | return $force !== '0' && strtolower($force) !== 'false'; |
| 110 | return $force !== '0' && strtolower($force) !== 'false'; |
| 95 | public function colors(bool $error = false): bool |
| 96 | { |
| 97 | if ($this->colors !== null) { |
| 101 | $noColor = getenv('NO_COLOR'); |
| 102 | |
| 103 | if ($noColor !== false && $noColor !== '') { |
| 103 | if ($noColor !== false && $noColor !== '') { |
| 107 | $force = getenv('FORCE_COLOR'); |
| 108 | |
| 109 | if ($force !== false) { |
| 110 | return $force !== '0' && strtolower($force) !== 'false'; |
| 110 | return $force !== '0' && strtolower($force) !== 'false'; |
| 134 | return stream_isatty($this->stdin()) && stream_isatty($this->stdout()); |
| 134 | return stream_isatty($this->stdin()) && stream_isatty($this->stdout()); |
| 134 | return stream_isatty($this->stdin()) && stream_isatty($this->stdout()); |
| 135 | } |
| 134 | return stream_isatty($this->stdin()) && stream_isatty($this->stdout()); |
| 134 | return stream_isatty($this->stdin()) && stream_isatty($this->stdout()); |
| 135 | } |
| 168 | private function line(string|false $line): ?string |
| 169 | { |
| 170 | return $line === false ? null : rtrim($line, characters: "\r\n"); |
| 170 | return $line === false ? null : rtrim($line, characters: "\r\n"); |
| 170 | return $line === false ? null : rtrim($line, characters: "\r\n"); |
| 171 | } |
| 168 | private function line(string|false $line): ?string |
| 169 | { |
| 170 | return $line === false ? null : rtrim($line, characters: "\r\n"); |
| 170 | return $line === false ? null : rtrim($line, characters: "\r\n"); |
| 170 | return $line === false ? null : rtrim($line, characters: "\r\n"); |
| 171 | } |
| 237 | private function open(string $target, string $mode): mixed |
| 238 | { |
| 239 | set_error_handler(static fn(): bool => true); |
| 240 | |
| 241 | try { |
| 242 | $stream = fopen($target, $mode); |
| 243 | } finally { |
| 244 | restore_error_handler(); |
| 237 | private function open(string $target, string $mode): mixed |
| 238 | { |
| 239 | set_error_handler(static fn(): bool => true); |
| 240 | |
| 241 | try { |
| 242 | $stream = fopen($target, $mode); |
| 243 | } finally { |
| 243 | } finally { |
| 247 | if ($stream === false) { |
| 248 | throw new RuntimeException("Could not open stream '{$target}'"); |
| 237 | private function open(string $target, string $mode): mixed |
| 238 | { |
| 239 | set_error_handler(static fn(): bool => true); |
| 240 | |
| 241 | try { |
| 242 | $stream = fopen($target, $mode); |
| 243 | } finally { |
| 243 | } finally { |
| 247 | if ($stream === false) { |
| 251 | return $stream; |
| 252 | } |
| 63 | public function read(bool $hidden = false): ?string |
| 64 | { |
| 65 | $stream = $this->stdin(); |
| 66 | |
| 67 | // No stty on Windows: hidden input reads visibly there. |
| 68 | if ($hidden && DIRECTORY_SEPARATOR !== '\\' && stream_isatty($stream)) { |
| 68 | if ($hidden && DIRECTORY_SEPARATOR !== '\\' && stream_isatty($stream)) { |
| 68 | if ($hidden && DIRECTORY_SEPARATOR !== '\\' && stream_isatty($stream)) { |
| 68 | if ($hidden && DIRECTORY_SEPARATOR !== '\\' && stream_isatty($stream)) { |
| 68 | if ($hidden && DIRECTORY_SEPARATOR !== '\\' && stream_isatty($stream)) { |
| 87 | return $this->line(fgets($stream)); |
| 88 | } |
| 63 | public function read(bool $hidden = false): ?string |
| 64 | { |
| 65 | $stream = $this->stdin(); |
| 66 | |
| 67 | // No stty on Windows: hidden input reads visibly there. |
| 68 | if ($hidden && DIRECTORY_SEPARATOR !== '\\' && stream_isatty($stream)) { |
| 68 | if ($hidden && DIRECTORY_SEPARATOR !== '\\' && stream_isatty($stream)) { |
| 68 | if ($hidden && DIRECTORY_SEPARATOR !== '\\' && stream_isatty($stream)) { |
| 68 | if ($hidden && DIRECTORY_SEPARATOR !== '\\' && stream_isatty($stream)) { |
| 87 | return $this->line(fgets($stream)); |
| 88 | } |
| 63 | public function read(bool $hidden = false): ?string |
| 64 | { |
| 65 | $stream = $this->stdin(); |
| 66 | |
| 67 | // No stty on Windows: hidden input reads visibly there. |
| 68 | if ($hidden && DIRECTORY_SEPARATOR !== '\\' && stream_isatty($stream)) { |
| 68 | if ($hidden && DIRECTORY_SEPARATOR !== '\\' && stream_isatty($stream)) { |
| 68 | if ($hidden && DIRECTORY_SEPARATOR !== '\\' && stream_isatty($stream)) { |
| 68 | if ($hidden && DIRECTORY_SEPARATOR !== '\\' && stream_isatty($stream)) { |
| 87 | return $this->line(fgets($stream)); |
| 88 | } |
| 63 | public function read(bool $hidden = false): ?string |
| 64 | { |
| 65 | $stream = $this->stdin(); |
| 66 | |
| 67 | // No stty on Windows: hidden input reads visibly there. |
| 68 | if ($hidden && DIRECTORY_SEPARATOR !== '\\' && stream_isatty($stream)) { |
| 68 | if ($hidden && DIRECTORY_SEPARATOR !== '\\' && stream_isatty($stream)) { |
| 68 | if ($hidden && DIRECTORY_SEPARATOR !== '\\' && stream_isatty($stream)) { |
| 87 | return $this->line(fgets($stream)); |
| 88 | } |
| 225 | return $this->errorStream ??= $this->errorOutput === $this->output |
| 226 | ? $this->stdout() |
| 225 | return $this->errorStream ??= $this->errorOutput === $this->output |
| 226 | ? $this->stdout() |
| 227 | : $this->open($this->errorOutput, 'w'); |
| 228 | } |
| 225 | return $this->errorStream ??= $this->errorOutput === $this->output |
| 225 | return $this->errorStream ??= $this->errorOutput === $this->output |
| 226 | ? $this->stdout() |
| 227 | : $this->open($this->errorOutput, 'w'); |
| 225 | return $this->errorStream ??= $this->errorOutput === $this->output |
| 226 | ? $this->stdout() |
| 227 | : $this->open($this->errorOutput, 'w'); |
| 228 | } |
| 233 | return $this->inputStream ??= $this->open($this->input, 'r'); |
| 234 | } |
| 217 | return $this->stream ??= $this->open($this->output, 'w'); |
| 218 | } |
| 209 | private function stream(bool $error): mixed |
| 210 | { |
| 211 | return $error ? $this->stderr() : $this->stdout(); |
| 211 | return $error ? $this->stderr() : $this->stdout(); |
| 211 | return $error ? $this->stderr() : $this->stdout(); |
| 212 | } |
| 209 | private function stream(bool $error): mixed |
| 210 | { |
| 211 | return $error ? $this->stderr() : $this->stdout(); |
| 211 | return $error ? $this->stderr() : $this->stdout(); |
| 211 | return $error ? $this->stderr() : $this->stdout(); |
| 212 | } |
| 143 | if ($this->width !== null) { |
| 144 | return $this->width; |
| 143 | if ($this->width !== null) { |
| 147 | $columns = (int) getenv('COLUMNS'); |
| 148 | |
| 149 | // No tput on Windows; shell_exec would leak its error output. |
| 150 | if ($columns < 1 && DIRECTORY_SEPARATOR !== '\\' && stream_isatty($this->stdout())) { |
| 150 | if ($columns < 1 && DIRECTORY_SEPARATOR !== '\\' && stream_isatty($this->stdout())) { |
| 150 | if ($columns < 1 && DIRECTORY_SEPARATOR !== '\\' && stream_isatty($this->stdout())) { |
| 150 | if ($columns < 1 && DIRECTORY_SEPARATOR !== '\\' && stream_isatty($this->stdout())) { |
| 150 | if ($columns < 1 && DIRECTORY_SEPARATOR !== '\\' && stream_isatty($this->stdout())) { |
| 158 | if ($columns < 1) { |
| 165 | return $this->width = $columns; |
| 166 | } |
| 143 | if ($this->width !== null) { |
| 147 | $columns = (int) getenv('COLUMNS'); |
| 148 | |
| 149 | // No tput on Windows; shell_exec would leak its error output. |
| 150 | if ($columns < 1 && DIRECTORY_SEPARATOR !== '\\' && stream_isatty($this->stdout())) { |
| 150 | if ($columns < 1 && DIRECTORY_SEPARATOR !== '\\' && stream_isatty($this->stdout())) { |
| 150 | if ($columns < 1 && DIRECTORY_SEPARATOR !== '\\' && stream_isatty($this->stdout())) { |
| 150 | if ($columns < 1 && DIRECTORY_SEPARATOR !== '\\' && stream_isatty($this->stdout())) { |
| 158 | if ($columns < 1) { |
| 165 | return $this->width = $columns; |
| 166 | } |
| 143 | if ($this->width !== null) { |
| 147 | $columns = (int) getenv('COLUMNS'); |
| 148 | |
| 149 | // No tput on Windows; shell_exec would leak its error output. |
| 150 | if ($columns < 1 && DIRECTORY_SEPARATOR !== '\\' && stream_isatty($this->stdout())) { |
| 150 | if ($columns < 1 && DIRECTORY_SEPARATOR !== '\\' && stream_isatty($this->stdout())) { |
| 150 | if ($columns < 1 && DIRECTORY_SEPARATOR !== '\\' && stream_isatty($this->stdout())) { |
| 150 | if ($columns < 1 && DIRECTORY_SEPARATOR !== '\\' && stream_isatty($this->stdout())) { |
| 158 | if ($columns < 1) { |
| 165 | return $this->width = $columns; |
| 166 | } |
| 143 | if ($this->width !== null) { |
| 147 | $columns = (int) getenv('COLUMNS'); |
| 148 | |
| 149 | // No tput on Windows; shell_exec would leak its error output. |
| 150 | if ($columns < 1 && DIRECTORY_SEPARATOR !== '\\' && stream_isatty($this->stdout())) { |
| 150 | if ($columns < 1 && DIRECTORY_SEPARATOR !== '\\' && stream_isatty($this->stdout())) { |
| 150 | if ($columns < 1 && DIRECTORY_SEPARATOR !== '\\' && stream_isatty($this->stdout())) { |
| 158 | if ($columns < 1) { |
| 165 | return $this->width = $columns; |
| 166 | } |
| 41 | public function write(string $text, bool $error = false): void |
| 42 | { |
| 43 | $stream = $this->stream($error); |
| 44 | |
| 45 | // A reader that went away, like `| head -1`, fails every write; the |
| 46 | // output is lost anyway and must not add a notice per line. |
| 47 | set_error_handler(static fn(): bool => true); |
| 48 | |
| 49 | try { |
| 50 | fwrite($stream, $text); |
| 51 | fflush($stream); |
| 52 | } finally { |
| 53 | restore_error_handler(); |
| 41 | public function write(string $text, bool $error = false): void |
| 42 | { |
| 43 | $stream = $this->stream($error); |
| 44 | |
| 45 | // A reader that went away, like `| head -1`, fails every write; the |
| 46 | // output is lost anyway and must not add a notice per line. |
| 47 | set_error_handler(static fn(): bool => true); |
| 48 | |
| 49 | try { |
| 50 | fwrite($stream, $text); |
| 51 | fflush($stream); |
| 52 | } finally { |
| 52 | } finally { |
| 55 | } |
| 239 | set_error_handler(static fn(): bool => true); |
| 47 | set_error_handler(static fn(): bool => true); |