-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Feat: Enable ReactProcessor to process regular iterables (#52)
- Loading branch information
Showing
9 changed files
with
296 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace BenTools\ETL\Iterator; | ||
|
||
use Iterator; | ||
use OutOfRangeException; | ||
|
||
use function BenTools\ETL\iterable_to_iterator; | ||
|
||
/** | ||
* @internal | ||
* | ||
* @template T | ||
*/ | ||
final class ConsumableIterator | ||
{ | ||
private readonly Iterator $iterator; | ||
private bool $started = false; | ||
private bool $ended = false; | ||
|
||
/** | ||
* @param iterable<T> $items | ||
*/ | ||
public function __construct(iterable $items) | ||
{ | ||
$this->iterator = iterable_to_iterator($items); | ||
} | ||
|
||
public function consume(): mixed | ||
{ | ||
if ($this->ended) { | ||
throw new OutOfRangeException('This iterator has no more items.'); // @codeCoverageIgnore | ||
} | ||
|
||
if (!$this->started) { | ||
$this->iterator->rewind(); | ||
$this->started = true; | ||
} | ||
|
||
$value = $this->iterator->current(); | ||
$this->iterator->next(); | ||
|
||
if (!$this->iterator->valid()) { | ||
$this->ended = true; | ||
} | ||
|
||
return $value; | ||
} | ||
|
||
public function isComplete(): bool | ||
{ | ||
return $this->ended; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace BenTools\ETL\Iterator; | ||
|
||
use Evenement\EventEmitterTrait; | ||
use React\EventLoop\Loop; | ||
use React\Stream\ReadableStreamInterface; | ||
use React\Stream\Util; | ||
use React\Stream\WritableStreamInterface; | ||
|
||
/** | ||
* @internal | ||
* | ||
* @template T | ||
*/ | ||
final class IteratorStream implements ReadableStreamInterface | ||
{ | ||
use EventEmitterTrait; | ||
|
||
/** | ||
* @var ConsumableIterator<T> | ||
*/ | ||
public readonly ConsumableIterator $iterator; | ||
public bool $paused = false; | ||
|
||
/** | ||
* @param iterable<T> $items | ||
*/ | ||
public function __construct(iterable $items) | ||
{ | ||
$this->iterator = new ConsumableIterator($items); | ||
$this->resume(); | ||
} | ||
|
||
public function isReadable(): bool | ||
{ | ||
return !$this->iterator->isComplete(); | ||
} | ||
|
||
public function pause(): void | ||
{ | ||
$this->paused = true; | ||
} | ||
|
||
public function resume(): void | ||
{ | ||
$this->paused = false; | ||
$this->process(); | ||
} | ||
|
||
private function process(): void | ||
{ | ||
if (!$this->iterator->isComplete()) { | ||
Loop::futureTick(function () { | ||
if (!$this->paused) { | ||
$this->emit('data', [$this->iterator->consume()]); | ||
} | ||
$this->process(); | ||
}); | ||
} else { | ||
$this->emit('end'); | ||
$this->close(); | ||
} | ||
} | ||
|
||
/** | ||
* @param array<string, mixed> $options | ||
*/ | ||
public function pipe(WritableStreamInterface $dest, array $options = []): WritableStreamInterface | ||
{ | ||
Util::pipe($this, $dest, $options); | ||
|
||
return $dest; | ||
} | ||
|
||
public function close(): void | ||
{ | ||
$this->emit('close'); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace BenTools\ETL\Tests\Stubs; | ||
|
||
use Evenement\EventEmitterTrait; | ||
use React\Stream\WritableStreamInterface; | ||
|
||
final class WritableStreamStub implements WritableStreamInterface | ||
{ | ||
use EventEmitterTrait; | ||
|
||
/** | ||
* @var list<mixed> | ||
*/ | ||
public array $data = []; | ||
|
||
public function isWritable(): bool | ||
{ | ||
return true; | ||
} | ||
|
||
public function write($data): bool | ||
{ | ||
$this->data[] = $data; | ||
|
||
return true; | ||
} | ||
|
||
public function end($data = null): void | ||
{ | ||
} | ||
|
||
public function close(): void | ||
{ | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace BenTools\ETL\Tests\Unit\Iterator; | ||
|
||
use BenTools\ETL\Iterator\IteratorStream; | ||
use BenTools\ETL\Tests\Stubs\WritableStreamStub; | ||
use React\EventLoop\Factory; | ||
use React\EventLoop\Loop; | ||
|
||
use function beforeEach; | ||
use function expect; | ||
|
||
beforeEach(fn () => Loop::set(Factory::create())); | ||
|
||
it('is readable during iteration', function () { | ||
$items = ['foo', 'bar']; | ||
$stream = new IteratorStream($items); | ||
|
||
for ($i = 0; $i < 2; ++$i) { | ||
expect($stream->isReadable())->toBeTrue(); | ||
$stream->iterator->consume(); | ||
} | ||
|
||
expect($stream->isReadable())->toBeFalse(); | ||
Loop::stop(); | ||
}); | ||
|
||
it('can be paused and resumed', function () { | ||
$stream = new IteratorStream([]); | ||
expect($stream->paused)->toBeFalse(); | ||
|
||
// When | ||
$stream->pause(); | ||
|
||
// Then | ||
expect($stream->paused)->toBeTrue(); | ||
|
||
// When | ||
$stream->resume(); | ||
|
||
// Then | ||
expect($stream->paused)->toBeFalse(); | ||
}); | ||
|
||
it('can pipe data', function () { | ||
$items = ['foo', 'bar', 'baz']; | ||
$stream = new IteratorStream($items); | ||
$dest = new WritableStreamStub(); | ||
$stream->pipe($dest); | ||
|
||
// When | ||
Loop::run(); | ||
|
||
// Then | ||
expect($dest->data)->toBe($items); | ||
}); |