Skip to content

Commit 230a9fb

Browse files
committed
Updates: Remove units, add controversial ability to read lines to iterator to direct usage in app
1 parent 82c1497 commit 230a9fb

File tree

6 files changed

+101
-31
lines changed

6 files changed

+101
-31
lines changed

README.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,17 @@ You can write result to file or another stream, use methods:
4141

4242
```php
4343
->toFile('path/to/your/output.txt'); // Write to file
44-
->toStream($stream); // Write to stream
44+
->toStream($stream); // Write to stream
45+
->toIterator(); // Get iterator containting lines as strings
4546
```
4647

48+
> [!WARNING]
49+
> The `->toIterator()` method reads the every line to memory. Use it carefully - when you load huge file with very long
50+
> lines, it can cause memory issues. Be aware especially when you load unknown files, when file doesn't have line
51+
> separators (e.g. if you accidentally load binary file), whole file will be loaded to memory. To prevent unexpected
52+
> behavior is method limited to 1MB of data. You can change this limit by passing the argument `$maxLineSize`.
53+
54+
4755
## License
4856

4957
The MIT License (MIT). Please see [License File](LICENSE) for more information.

src/Builder/Input.php

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,10 @@
99
use JakubBoucek\Tail\Stream\FileMode;
1010
use JakubBoucek\Tail\Stream\Stream;
1111
use JakubBoucek\Tail\Tail;
12+
use Traversable;
1213

1314
class Input
1415
{
15-
1616
private Stream $input;
1717
private Tail $tail;
1818

@@ -24,12 +24,12 @@ public function __construct(Tail $tail, Stream $input)
2424

2525
public function toFile(string $file): void
2626
{
27-
$this->tail->process($this->input, new File($file, FileMode::Write));
27+
$this->tail->processStream($this->input, new File($file, FileMode::Write));
2828
}
2929

3030
public function toStream($stream): void
3131
{
32-
$this->tail->process($this->input, new ExternalStream($stream));
32+
$this->tail->processStream($this->input, new ExternalStream($stream));
3333
}
3434

3535
public function toOutput(): void
@@ -38,6 +38,11 @@ public function toOutput(): void
3838
? new ExternalStream(STDOUT)
3939
: new File('php://output', FileMode::Write);
4040

41-
$this->tail->process($this->input, $output);
41+
$this->tail->processStream($this->input, $output);
42+
}
43+
44+
public function toIterator(int $maxLineSize = Tail::DefaultMaxLineSize): Traversable
45+
{
46+
return $this->tail->processIterator($this->input, $maxLineSize);
4247
}
4348
}

src/Processor.php

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use JakubBoucek\Tail\Exceptions\IOException;
88
use JakubBoucek\Tail\Stream\Stream;
99
use LogicException;
10+
use Traversable;
1011

1112
class Processor
1213
{
@@ -31,6 +32,38 @@ public function lines(
3132
$outputStream->close();
3233
}
3334

35+
/**
36+
* @param int $count Count if lines
37+
* @param Stream $inputStream Input stream
38+
* @param int $blocksSizes Size of blocks to read during backward search of lines - affects consumed memory
39+
* @param int $maxLineSize Size of maximal size of line - if line is longer, it will be split
40+
* @param string $delimiter Delimiter of lines
41+
* @return Traversable<string> Searched lines as string
42+
*/
43+
public function linesIterator(
44+
int $count,
45+
Stream $inputStream,
46+
int $blocksSizes,
47+
int $maxLineSize,
48+
string $delimiter
49+
): Traversable {
50+
$input = $inputStream->open();
51+
52+
[$start, $end] = $this->searchLines($count, $input, $blocksSizes, $delimiter);
53+
54+
$this->seek($input, $start);
55+
56+
do {
57+
$line = stream_get_line($input, $maxLineSize, $delimiter);
58+
if ($line === false) {
59+
break;
60+
}
61+
yield $line;
62+
} while (ftell($input) < $end);
63+
64+
$inputStream->close();
65+
}
66+
3467
/**
3568
* @param resource $input
3669
*/

src/Tail.php

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,34 +6,35 @@
66

77
use JakubBoucek\Tail\Builder\Setup;
88
use JakubBoucek\Tail\Stream\Stream;
9+
use Traversable;
910

1011
class Tail
1112
{
13+
public const DefaultMaxLineSize = 2 ** 20;
14+
1215
private int $blockSize = 4096;
1316
private string $rowDelimiter = "\n";
1417
private int $count;
15-
private Units $units;
1618

17-
/**
18-
* @param Units $units (unused - for future use)
19-
*/
20-
public function __construct(int $count = 10, Units $units = Units::Lines)
19+
public function __construct(int $count = 10)
2120
{
2221
$this->count = $count;
23-
$this->units = $units;
2422
}
2523

2624
public static function lines(int $count): Setup
2725
{
28-
return new Setup(new Tail($count, Units::Lines));
26+
return new Setup(new Tail($count));
27+
}
28+
29+
public function processStream(Stream $input, Stream $output): void
30+
{
31+
$processor = new Processor();
32+
$processor->lines($this->count, $input, $output, $this->blockSize, $this->rowDelimiter);
2933
}
3034

31-
public function process(Stream $input, Stream $output): void
35+
public function processIterator(Stream $input, int $maxLineSize = self::DefaultMaxLineSize): Traversable
3236
{
3337
$processor = new Processor();
34-
match ($this->units) {
35-
Units::Lines => $processor->lines($this->count, $input, $output, $this->blockSize, $this->rowDelimiter),
36-
//Units::Bytes => $processor->bytes($this->count, $input, $output, $this->bufferSize),
37-
};
38+
return $processor->linesIterator($this->count, $input, $this->blockSize, $maxLineSize, $this->rowDelimiter);
3839
}
3940
}

src/Units.php

Lines changed: 0 additions & 10 deletions
This file was deleted.

tests/ProcessorTest.php

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
require __DIR__ . '/bootstrap.php';
1414

15+
/** @testCase */
1516
class ProcessorTest extends TestCase
1617
{
1718
public function dataSearchInBlock(): array
@@ -130,10 +131,7 @@ public function testLines(
130131
fseek($input, 0);
131132

132133
$processor = new Processor();
133-
// Method is private - use closure
134-
$method = new ReflectionMethod(Processor::class, 'lines');
135-
$closure = $method->getClosure($processor);
136-
$closure($count, $inputSteam, $outputStream, $blocksSizes, $delimiter);
134+
$processor->lines($count, $inputSteam, $outputStream, $blocksSizes, $delimiter);
137135

138136
fseek($output, 0);
139137
$result = stream_get_contents($output);
@@ -143,6 +141,41 @@ public function testLines(
143141

144142
Assert::equal($expected, $result);
145143
}
144+
145+
public function dataLinesIterator(): array
146+
{
147+
return [
148+
['a.......a......a.....a....a', ['......','.....','....'], 'a', 3, 1024, 1_000_000],
149+
['....b....b....b....', ['....','....','....'], 'b', 3, 1024, 1_000_000],
150+
];
151+
}
152+
/**
153+
* @dataProvider dataLinesIterator
154+
*/
155+
public function testLinesIterator(
156+
string $content,
157+
array $expected,
158+
string $delimiter,
159+
int $count,
160+
int $blocksSizes,
161+
int $maxLineSize,
162+
):void
163+
{
164+
$input = fopen('php://memory', 'wb+');
165+
$inputSteam = new ExternalStream($input);
166+
167+
fwrite($input, $content);
168+
fseek($input, 0);
169+
170+
$processor = new Processor();
171+
$iterator = $processor->linesIterator($count, $inputSteam, $blocksSizes, $maxLineSize, $delimiter);
172+
173+
$result = iterator_to_array($iterator);
174+
175+
fclose($input);
176+
177+
Assert::equal($expected, $result);
178+
}
146179
}
147180

148181
(new ProcessorTest())->run();

0 commit comments

Comments
 (0)