Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
13 / 13
100.00% covered (success)
100.00%
7 / 7
CRAP
100.00% covered (success)
100.00%
1 / 1
InputStreamHandler
100.00% covered (success)
100.00%
13 / 13
100.00% covered (success)
100.00%
7 / 7
13
100.00% covered (success)
100.00%
1 / 1
 call
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 __invoke
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getLinesFromInputStream
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
2
 shouldTerminate
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
2
 readInputStream
n/a
0 / 0
n/a
0 / 0
3
 mockInput
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 terminationMessage
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getShortcut
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
2
1<?php
2
3declare(strict_types=1);
4
5namespace Hyde\Publications\Commands\Helpers;
6
7use Hyde\Hyde;
8
9use function trim;
10use function fgets;
11use function sprintf;
12use function explode;
13use function array_shift;
14use function str_contains;
15
16/**
17 * Collects an array of lines from the standard input stream. Feed is terminated by a blank line.
18 *
19 * @internal
20 *
21 * @see \Hyde\Publications\Testing\Feature\InputStreamHandlerTest
22 */
23class InputStreamHandler
24{
25    public const TERMINATION_SEQUENCE = '<<<';
26    public const END_OF_TRANSMISSION = "\x04";
27
28    private static ?array $mockedStreamBuffer = null;
29
30    public static function call(): array
31    {
32        return (new self())->__invoke();
33    }
34
35    public function __invoke(): array
36    {
37        return $this->getLinesFromInputStream();
38    }
39
40    protected function getLinesFromInputStream(): array
41    {
42        $lines = [];
43        do {
44            $line = Hyde::stripNewlines($this->readInputStream());
45            if ($this->shouldTerminate($line)) {
46                break;
47            }
48            $lines[] = trim($line);
49        } while (true);
50
51        return $lines;
52    }
53
54    protected function shouldTerminate(string $line): bool
55    {
56        // We could add an option to terminate with two blank lines (useful for tags input, but is default to false, for example to accept paragraphs)
57
58        return $line === self::TERMINATION_SEQUENCE || str_contains($line, self::END_OF_TRANSMISSION);
59    }
60
61    /** @codeCoverageIgnore Allows for mocking of the standard input stream */
62    protected function readInputStream(): string
63    {
64        if (self::$mockedStreamBuffer !== null) {
65            return array_shift(self::$mockedStreamBuffer) ?? '';
66        }
67
68        return fgets(STDIN) ?: self::END_OF_TRANSMISSION;
69    }
70
71    /** @internal Allows for mocking of the standard input stream */
72    public static function mockInput(string $input): void
73    {
74        self::$mockedStreamBuffer = explode("\n", $input);
75    }
76
77    public static function terminationMessage(): string
78    {
79        return sprintf('Terminate with <comment>%s</comment> or press %s to finish', self::TERMINATION_SEQUENCE, self::getShortcut());
80    }
81
82    protected static function getShortcut(): string
83    {
84        return '<comment>Ctrl+D</comment>'.(PHP_OS_FAMILY === 'Windows' ? ' then <comment>Enter</comment>' : '');
85    }
86}