• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

azjezz / psl / 23100766910

15 Mar 2026 01:36AM UTC coverage: 95.57% (-0.06%) from 95.628%
23100766910

push

github

azjezz
bc(io): `BufferedReadHandleInterface::readLine()` now always splits on `n` instead of `PHP_EOL`

Signed-off-by: azjezz <azjezz@protonmail.com>

13 of 21 new or added lines in 7 files covered. (61.9%)

4 existing lines in 4 files now uncovered.

10462 of 10947 relevant lines covered (95.57%)

32.64 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

41.96
/src/Psl/Encoding/Hex/EncodingReadHandle.php
1
<?php
2

3
declare(strict_types=1);
4

5
namespace Psl\Encoding\Hex;
6

7
use Psl;
8
use Psl\Async\CancellationTokenInterface;
9
use Psl\Async\NullCancellationToken;
10
use Psl\IO;
11

12
use function strlen;
13
use function strpos;
14
use function substr;
15

16
/**
17
 * A read handle that hex-encodes raw binary data from an inner readable handle.
18
 *
19
 * Reads chunks from the inner handle, encodes each via {@see encode()},
20
 * and buffers the encoded output for consumption.
21
 */
22
final class EncodingReadHandle implements IO\BufferedReadHandleInterface
23
{
24
    use IO\ReadHandleConvenienceMethodsTrait;
25

26
    private string $buffer = '';
27
    private bool $eof = false;
28

29
    public function __construct(
30
        private readonly IO\ReadHandleInterface $handle,
31
    ) {}
6✔
32

33
    /**
34
     * {@inheritDoc}
35
     */
36
    public function reachedEndOfDataSource(): bool
37
    {
38
        return $this->eof && $this->buffer === '';
3✔
39
    }
40

41
    /**
42
     * {@inheritDoc}
43
     */
44
    public function tryRead(null|int $max_bytes = null): string
45
    {
46
        if ($this->buffer === '' && !$this->eof) {
×
47
            $this->fillBuffer();
×
48
        }
49

50
        if ($this->buffer === '') {
×
51
            return '';
×
52
        }
53

54
        if (null === $max_bytes || $max_bytes >= strlen($this->buffer)) {
×
55
            $result = $this->buffer;
×
56
            $this->buffer = '';
×
57
            return $result;
×
58
        }
59

60
        $result = substr($this->buffer, 0, $max_bytes);
×
61
        $this->buffer = substr($this->buffer, $max_bytes);
×
62
        return $result;
×
63
    }
64

65
    /**
66
     * {@inheritDoc}
67
     */
68
    public function read(
69
        null|int $max_bytes = null,
70
        CancellationTokenInterface $cancellation = new NullCancellationToken(),
71
    ): string {
72
        if ($this->eof && $this->buffer === '') {
3✔
73
            return '';
×
74
        }
75

76
        if ($this->buffer === '') {
3✔
77
            $this->fillBuffer($cancellation);
3✔
78
        }
79

80
        if ($this->buffer === '') {
3✔
81
            return '';
3✔
82
        }
83

84
        if (null === $max_bytes || $max_bytes >= strlen($this->buffer)) {
2✔
85
            $result = $this->buffer;
2✔
86
            $this->buffer = '';
2✔
87
            return $result;
2✔
88
        }
89

90
        $result = substr($this->buffer, 0, $max_bytes);
×
91
        $this->buffer = substr($this->buffer, $max_bytes);
×
92
        return $result;
×
93
    }
94

95
    public function readByte(CancellationTokenInterface $cancellation = new NullCancellationToken()): string
96
    {
97
        if ($this->buffer === '' && !$this->eof) {
1✔
98
            $this->fillBuffer($cancellation);
1✔
99
        }
100

101
        if ($this->buffer === '') {
1✔
102
            throw new IO\Exception\RuntimeException('Reached EOF without any more data.');
×
103
        }
104

105
        $ret = $this->buffer[0];
1✔
106
        if ($ret === $this->buffer) {
1✔
107
            $this->buffer = '';
1✔
108
            return $ret;
1✔
109
        }
110

111
        $this->buffer = substr($this->buffer, 1);
1✔
112
        return $ret;
1✔
113
    }
114

115
    public function readLine(CancellationTokenInterface $cancellation = new NullCancellationToken()): null|string
116
    {
117
        $line = $this->readUntil("\n", $cancellation);
1✔
118
        if ($line !== null) {
1✔
NEW
119
            if ($line !== '' && $line[-1] === "\r") {
×
NEW
120
                return substr($line, 0, -1);
×
121
            }
122

UNCOV
123
            return $line;
×
124
        }
125

126
        // No EOL found; return whatever remains, or null if empty
127
        if ($this->buffer === '' && !$this->eof) {
1✔
128
            $this->fillBuffer($cancellation);
×
129
        }
130

131
        if ($this->buffer === '') {
1✔
132
            return null;
×
133
        }
134

135
        $result = $this->buffer;
1✔
136
        $this->buffer = '';
1✔
137
        return $result;
1✔
138
    }
139

140
    public function readUntil(
141
        string $suffix,
142
        CancellationTokenInterface $cancellation = new NullCancellationToken(),
143
    ): null|string {
144
        $suffix_len = strlen($suffix);
2✔
145
        $idx = strpos($this->buffer, $suffix);
2✔
146
        if ($idx !== false) {
2✔
147
            $result = substr($this->buffer, 0, $idx);
×
148
            $this->buffer = substr($this->buffer, $idx + $suffix_len);
×
149
            return $result;
×
150
        }
151

152
        while (!$this->eof) {
2✔
153
            $offset = strlen($this->buffer) - $suffix_len + 1;
2✔
154
            $offset = $offset > 0 ? $offset : 0;
2✔
155

156
            $this->fillBuffer($cancellation);
2✔
157

158
            $idx = strpos($this->buffer, $suffix, $offset);
2✔
159
            if ($idx !== false) {
2✔
160
                $result = substr($this->buffer, 0, $idx);
1✔
161
                $this->buffer = substr($this->buffer, $idx + $suffix_len);
1✔
162
                return $result;
1✔
163
            }
164
        }
165

166
        return null;
1✔
167
    }
168

169
    public function readUntilBounded(
170
        string $suffix,
171
        int $max_bytes,
172
        CancellationTokenInterface $cancellation = new NullCancellationToken(),
173
    ): null|string {
174
        $suffix_len = strlen($suffix);
×
175
        $idx = strpos($this->buffer, $suffix);
×
176
        if ($idx !== false) {
×
177
            if ($idx > $max_bytes) {
×
178
                throw new IO\Exception\OverflowException(Psl\Str\format(
×
179
                    'Exceeded maximum byte limit (%d) before encountering the suffix ("%s").',
×
180
                    $max_bytes,
×
181
                    $suffix,
×
182
                ));
×
183
            }
184

185
            $result = substr($this->buffer, 0, $idx);
×
186
            $this->buffer = substr($this->buffer, $idx + $suffix_len);
×
187
            return $result;
×
188
        }
189

190
        if (strlen($this->buffer) > $max_bytes) {
×
191
            throw new IO\Exception\OverflowException(Psl\Str\format(
×
192
                'Exceeded maximum byte limit (%d) before encountering the suffix ("%s").',
×
193
                $max_bytes,
×
194
                $suffix,
×
195
            ));
×
196
        }
197

198
        while (!$this->eof) {
×
199
            $offset = strlen($this->buffer) - $suffix_len + 1;
×
200
            $offset = $offset > 0 ? $offset : 0;
×
201

202
            $this->fillBuffer($cancellation);
×
203

204
            $idx = strpos($this->buffer, $suffix, $offset);
×
205
            if ($idx !== false) {
×
206
                if ($idx > $max_bytes) {
×
207
                    throw new IO\Exception\OverflowException(Psl\Str\format(
×
208
                        'Exceeded maximum byte limit (%d) before encountering the suffix ("%s").',
×
209
                        $max_bytes,
×
210
                        $suffix,
×
211
                    ));
×
212
                }
213

214
                $result = substr($this->buffer, 0, $idx);
×
215
                $this->buffer = substr($this->buffer, $idx + $suffix_len);
×
216
                return $result;
×
217
            }
218

219
            if (strlen($this->buffer) > $max_bytes) {
×
220
                throw new IO\Exception\OverflowException(Psl\Str\format(
×
221
                    'Exceeded maximum byte limit (%d) before encountering the suffix ("%s").',
×
222
                    $max_bytes,
×
223
                    $suffix,
×
224
                ));
×
225
            }
226
        }
227

228
        return null;
×
229
    }
230

231
    private function fillBuffer(CancellationTokenInterface $cancellation = new NullCancellationToken()): void
232
    {
233
        if ($this->eof) {
6✔
234
            return;
×
235
        }
236

237
        $chunk = $this->handle->read(4096, $cancellation);
6✔
238
        if ($chunk === '' && $this->handle->reachedEndOfDataSource()) {
6✔
239
            $this->eof = true;
4✔
240
            return;
4✔
241
        }
242

243
        if ($chunk !== '') {
5✔
244
            $this->buffer .= encode($chunk);
5✔
245
        }
246
    }
247
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc