Code Coverage
 
Lines
Branches
Paths
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
37 / 37
95.83% covered (success)
95.83%
46 / 48
36.96% covered (danger)
36.96%
17 / 46
77.78% covered (warning)
77.78%
7 / 9
CRAP
0.00% covered (danger)
0.00%
0 / 1
Output
100.00% covered (success)
100.00%
37 / 37
95.83% covered (success)
95.83%
46 / 48
36.96% covered (danger)
36.96%
17 / 46
100.00% covered (success)
100.00%
9 / 9
323.65
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 echo
100.00% covered (success)
100.00%
1 / 1
83.33% covered (warning)
83.33%
5 / 6
25.00% covered (danger)
25.00%
1 / 4
100.00% covered (success)
100.00%
1 / 1
6.80
 echoln
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
6 / 6
50.00% covered (danger)
50.00%
2 / 4
100.00% covered (success)
100.00%
1 / 1
4.12
 write
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 color
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
11 / 11
29.41% covered (danger)
29.41%
5 / 17
100.00% covered (success)
100.00%
1 / 1
18.66
 indent
100.00% covered (success)
100.00%
8 / 8
88.89% covered (warning)
88.89%
8 / 9
0.00% covered (danger)
0.00%
0 / 8
100.00% covered (success)
100.00%
1 / 1
4
 formatText
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
9 / 9
50.00% covered (danger)
50.00%
4 / 8
100.00% covered (success)
100.00%
1 / 1
8.12
 getStream
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 hasColorSupport
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
9
1<?php
2
3declare(strict_types=1);
4
5namespace Celemas\Cli;
6
7final class Output
8{
9    private mixed $stream = null;
10    private array $fg = [
11        'black' => [0, 30],
12        'gray' => [1, 30],
13        'grey' => [1, 30],
14        'red' => [0, 31],
15        'lightred' => [1, 31],
16        'green' => [0, 32],
17        'lightgreen' => [1, 32],
18        'brown' => [0, 33],
19        'yellow' => [1, 33],
20        'blue' => [0, 34],
21        'lightblue' => [1, 34],
22        'purple' => [0, 35],
23        'lightpurple' => [1, 35],
24        'magenta' => [0, 35],
25        'lightmagenta' => [1, 35],
26        'cyan' => [0, 36],
27        'lightcyan' => [1, 36],
28        'lightgray' => [0, 37],
29        'lightgrey' => [0, 37],
30        'white' => [1, 37],
31    ];
32    private array $bg = [
33        'black' => 40,
34        'red' => 41,
35        'green' => 42,
36        'yellow' => 43,
37        'blue' => 44,
38        'purple' => 45,
39        'magenta' => 45,
40        'cyan' => 46,
41        'gray' => 47,
42        'grey' => 47,
43        'white' => 47,
44    ];
45
46    public function __construct(
47        protected readonly string $target,
48    ) {}
49
50    public function echo(string $text, string $color = '', string $background = ''): void
51    {
52        $this->write($color || $background ? $this->color($text, $color, $background) : $text);
53    }
54
55    public function echoln(string $text, string $color = '', string $background = ''): void
56    {
57        $this->write(
58            ($color || $background ? $this->color($text, $color, $background) : $text) . PHP_EOL,
59        );
60    }
61
62    private function write(string $text): void
63    {
64        fwrite($this->getStream(), $text);
65        fflush($this->stream);
66    }
67
68    public function color(string $text, string $color = '', string $background = ''): string
69    {
70        if (!$this->hasColorSupport()) {
71            return $text;
72        }
73
74        $colorCode = '';
75        $backgroundCode = '';
76
77        if ($color && array_key_exists($color, $this->fg)) {
78            [$first, $second] = $this->fg[$color];
79            $colorCode = "{$first};{$second}";
80        }
81
82        if ($background && array_key_exists($background, $this->bg)) {
83            $backgroundCode = $this->bg[$background];
84        }
85
86        return $this->formatText($text, $colorCode, $backgroundCode);
87    }
88
89    public function indent(
90        string $text,
91        int $indent,
92        ?int $max = null,
93    ): string {
94        $spaces = str_repeat(' ', $indent);
95
96        /** @psalm-suppress ForbiddenCode */
97        $width = shell_exec('tput cols');
98
99        if ($width === null) {
100            // Need a way to force $width to be null in a sane way
101            // @codeCoverageIgnoreStart
102            $width = 80;
103
104            // @codeCoverageIgnoreEnd
105        }
106
107        $width = (int) $width - $indent;
108
109        if ($max !== null && $max < $width) {
110            $width = $max;
111        }
112
113        $lines = explode("\n", wordwrap($text, $width, break: "\n"));
114
115        return implode("\n", array_map(static fn($line) => $spaces . $line, $lines));
116    }
117
118    private function formatText(string $text, string $colorCode, string|int $backgroundCode): string
119    {
120        if ($colorCode && $backgroundCode) {
121            return "\033[{$colorCode};{$backgroundCode}m{$text}\033[0m";
122        }
123
124        if ($colorCode) {
125            return "\033[{$colorCode}m{$text}\033[0m";
126        }
127
128        if ($backgroundCode) {
129            return "\033[{$backgroundCode}m{$text}\033[0m";
130        }
131
132        return $text;
133    }
134
135    private function getStream(): mixed
136    {
137        if ($this->stream === null) {
138            $this->stream = fopen($this->target, mode: 'w');
139        }
140
141        return $this->stream;
142    }
143
144    private function hasColorSupport(): bool
145    {
146        if (getenv('NO_COLOR') !== false) {
147            return false;
148        }
149
150        // @codeCoverageIgnoreStart
151        if (getenv('FORCE_COLOR') !== false || getenv('COLORTERM') !== false) {
152            return true;
153        }
154
155        // Windows
156        if (DIRECTORY_SEPARATOR === '\\') {
157            if (function_exists('sapi_windows_vt100_support')) {
158                return sapi_windows_vt100_support(STDOUT);
159            }
160
161            return (
162                getenv('ANSICON') !== false
163                || getenv('ConEmuANSI') === 'ON'
164                || getenv('TERM') === 'xterm'
165            );
166        }
167
168        if (function_exists('stream_isatty')) {
169            return stream_isatty(STDOUT);
170        }
171
172        return false;
173
174        // @codeCoverageIgnoreEnd
175    }
176}

Branches

Below are the source code lines that represent each code branch as identified by Xdebug. Please note a branch is not necessarily coterminous with a line, a line may contain multiple branches and therefore show up more than once. Please also be aware that some branches may be implicit rather than explicit, e.g. an if statement always has an else as part of its logical flow even if you didn't write one.

Output->__construct
47        protected readonly string $target,
48    ) {}
Output->color
68    public function color(string $text, string $color = '', string $background = ''): string
69    {
70        if (!$this->hasColorSupport()) {
71            return $text;
74        $colorCode = '';
75        $backgroundCode = '';
76
77        if ($color && array_key_exists($color, $this->fg)) {
77        if ($color && array_key_exists($color, $this->fg)) {
77        if ($color && array_key_exists($color, $this->fg)) {
78            [$first, $second] = $this->fg[$color];
79            $colorCode = "{$first};{$second}";
80        }
81
82        if ($background && array_key_exists($background, $this->bg)) {
82        if ($background && array_key_exists($background, $this->bg)) {
82        if ($background && array_key_exists($background, $this->bg)) {
82        if ($background && array_key_exists($background, $this->bg)) {
83            $backgroundCode = $this->bg[$background];
84        }
85
86        return $this->formatText($text, $colorCode, $backgroundCode);
86        return $this->formatText($text, $colorCode, $backgroundCode);
87    }
Output->echo
50    public function echo(string $text, string $color = '', string $background = ''): void
51    {
52        $this->write($color || $background ? $this->color($text, $color, $background) : $text);
52        $this->write($color || $background ? $this->color($text, $color, $background) : $text);
52        $this->write($color || $background ? $this->color($text, $color, $background) : $text);
52        $this->write($color || $background ? $this->color($text, $color, $background) : $text);
52        $this->write($color || $background ? $this->color($text, $color, $background) : $text);
52        $this->write($color || $background ? $this->color($text, $color, $background) : $text);
53    }
Output->echoln
55    public function echoln(string $text, string $color = '', string $background = ''): void
56    {
57        $this->write(
58            ($color || $background ? $this->color($text, $color, $background) : $text) . PHP_EOL,
58            ($color || $background ? $this->color($text, $color, $background) : $text) . PHP_EOL,
58            ($color || $background ? $this->color($text, $color, $background) : $text) . PHP_EOL,
58            ($color || $background ? $this->color($text, $color, $background) : $text) . PHP_EOL,
58            ($color || $background ? $this->color($text, $color, $background) : $text) . PHP_EOL,
58            ($color || $background ? $this->color($text, $color, $background) : $text) . PHP_EOL,
59        );
60    }
Output->formatText
118    private function formatText(string $text, string $colorCode, string|int $backgroundCode): string
119    {
120        if ($colorCode && $backgroundCode) {
120        if ($colorCode && $backgroundCode) {
120        if ($colorCode && $backgroundCode) {
121            return "\033[{$colorCode};{$backgroundCode}m{$text}\033[0m";
124        if ($colorCode) {
125            return "\033[{$colorCode}m{$text}\033[0m";
128        if ($backgroundCode) {
129            return "\033[{$backgroundCode}m{$text}\033[0m";
132        return $text;
133    }
Output->getStream
137        if ($this->stream === null) {
138            $this->stream = fopen($this->target, mode: 'w');
139        }
140
141        return $this->stream;
141        return $this->stream;
142    }
Output->hasColorSupport
146        if (getenv('NO_COLOR') !== false) {
147            return false;
Output->indent
90        string $text,
91        int $indent,
92        ?int $max = null,
93    ): string {
94        $spaces = str_repeat(' ', $indent);
95
96        /** @psalm-suppress ForbiddenCode */
97        $width = shell_exec('tput cols');
98
99        if ($width === null) {
107        $width = (int) $width - $indent;
108
109        if ($max !== null && $max < $width) {
109        if ($max !== null && $max < $width) {
109        if ($max !== null && $max < $width) {
110            $width = $max;
111        }
112
113        $lines = explode("\n", wordwrap($text, $width, break: "\n"));
113        $lines = explode("\n", wordwrap($text, $width, break: "\n"));
114
115        return implode("\n", array_map(static fn($line) => $spaces . $line, $lines));
115        return implode("\n", array_map(static fn($line) => $spaces . $line, $lines));
115        return implode("\n", array_map(static fn($line) => $spaces . $line, $lines));
115        return implode("\n", array_map(static fn($line) => $spaces . $line, $lines));
116    }
Output->write
62    private function write(string $text): void
63    {
64        fwrite($this->getStream(), $text);
65        fflush($this->stream);
66    }
{closure:/workspace/celemas/cli/src/Output.php:115-115}
115        return implode("\n", array_map(static fn($line) => $spaces . $line, $lines));