2016-12-30 06:09:06 +01:00
|
|
|
<?php
|
2016-08-16 23:23:46 +02:00
|
|
|
|
2017-03-16 16:22:32 +01:00
|
|
|
namespace Amp\ByteStream;
|
2016-08-10 23:48:42 +02:00
|
|
|
|
2019-08-22 23:36:28 +02:00
|
|
|
use Amp\Iterator;
|
2018-10-22 16:56:37 +02:00
|
|
|
use Amp\Loop;
|
2019-08-22 23:36:28 +02:00
|
|
|
use Amp\Producer;
|
2017-04-26 08:27:52 +02:00
|
|
|
use Amp\Promise;
|
2017-04-30 08:31:53 +02:00
|
|
|
use function Amp\call;
|
2016-08-10 23:48:42 +02:00
|
|
|
|
|
|
|
// @codeCoverageIgnoreStart
|
|
|
|
if (\strlen('…') !== 3) {
|
2016-08-16 00:19:32 +02:00
|
|
|
throw new \Error(
|
2017-04-26 08:17:19 +02:00
|
|
|
'The mbstring.func_overload ini setting is enabled. It must be disabled to use the stream package.'
|
2016-08-10 23:48:42 +02:00
|
|
|
);
|
|
|
|
} // @codeCoverageIgnoreEnd
|
|
|
|
|
2018-01-12 19:18:14 +01:00
|
|
|
if (!\defined('STDOUT')) {
|
|
|
|
\define('STDOUT', \fopen('php://stdout', 'w'));
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!\defined('STDERR')) {
|
|
|
|
\define('STDERR', \fopen('php://stderr', 'w'));
|
|
|
|
}
|
|
|
|
|
2016-08-10 23:48:42 +02:00
|
|
|
/**
|
2017-04-30 08:31:53 +02:00
|
|
|
* @param \Amp\ByteStream\InputStream $source
|
|
|
|
* @param \Amp\ByteStream\OutputStream $destination
|
2016-08-10 23:48:42 +02:00
|
|
|
*
|
2017-03-16 16:22:32 +01:00
|
|
|
* @return \Amp\Promise
|
2016-08-10 23:48:42 +02:00
|
|
|
*/
|
2018-09-21 22:45:13 +02:00
|
|
|
function pipe(InputStream $source, OutputStream $destination): Promise
|
|
|
|
{
|
2017-04-30 08:31:53 +02:00
|
|
|
return call(function () use ($source, $destination): \Generator {
|
|
|
|
$written = 0;
|
|
|
|
|
|
|
|
while (($chunk = yield $source->read()) !== null) {
|
|
|
|
$written += \strlen($chunk);
|
2017-10-11 09:50:27 +02:00
|
|
|
$writePromise = $destination->write($chunk);
|
|
|
|
$chunk = null; // free memory
|
|
|
|
yield $writePromise;
|
2017-04-30 08:31:53 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return $written;
|
|
|
|
});
|
2017-05-07 22:14:45 +02:00
|
|
|
}
|
2018-09-21 22:21:44 +02:00
|
|
|
|
|
|
|
/**
|
2019-08-22 23:36:28 +02:00
|
|
|
* @param \Amp\ByteStream\InputStream $source
|
2018-09-21 22:21:44 +02:00
|
|
|
*
|
|
|
|
* @return \Amp\Promise
|
|
|
|
*/
|
2018-09-21 22:45:13 +02:00
|
|
|
function buffer(InputStream $source): Promise
|
|
|
|
{
|
2018-09-21 22:21:44 +02:00
|
|
|
return call(function () use ($source): \Generator {
|
|
|
|
$buffer = "";
|
|
|
|
|
|
|
|
while (($chunk = yield $source->read()) !== null) {
|
|
|
|
$buffer .= $chunk;
|
|
|
|
$chunk = null; // free memory
|
|
|
|
}
|
|
|
|
|
|
|
|
return $buffer;
|
|
|
|
});
|
|
|
|
}
|
2018-10-22 16:56:37 +02:00
|
|
|
|
2019-06-03 23:01:43 +02:00
|
|
|
/**
|
|
|
|
* The php://input input buffer stream for the process associated with the currently active event loop.
|
|
|
|
*
|
|
|
|
* @return ResourceInputStream
|
|
|
|
*/
|
|
|
|
function getInputBufferStream(): ResourceInputStream
|
|
|
|
{
|
|
|
|
static $key = InputStream::class . '\\input';
|
|
|
|
|
|
|
|
$stream = Loop::getState($key);
|
|
|
|
|
|
|
|
if (!$stream) {
|
2019-08-22 21:47:29 +02:00
|
|
|
$stream = new ResourceInputStream(\fopen('php://input', 'rb'));
|
2019-06-03 23:01:43 +02:00
|
|
|
Loop::setState($key, $stream);
|
|
|
|
}
|
|
|
|
|
|
|
|
return $stream;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The php://output output buffer stream for the process associated with the currently active event loop.
|
|
|
|
*
|
|
|
|
* @return ResourceOutputStream
|
|
|
|
*/
|
|
|
|
function getOutputBufferStream(): ResourceOutputStream
|
|
|
|
{
|
|
|
|
static $key = OutputStream::class . '\\output';
|
|
|
|
|
|
|
|
$stream = Loop::getState($key);
|
|
|
|
|
|
|
|
if (!$stream) {
|
2019-08-22 21:47:29 +02:00
|
|
|
$stream = new ResourceOutputStream(\fopen('php://output', 'wb'));
|
2019-06-03 23:01:43 +02:00
|
|
|
Loop::setState($key, $stream);
|
|
|
|
}
|
|
|
|
|
|
|
|
return $stream;
|
|
|
|
}
|
2019-08-22 23:36:28 +02:00
|
|
|
|
2018-10-22 16:56:37 +02:00
|
|
|
/**
|
|
|
|
* The STDIN stream for the process associated with the currently active event loop.
|
|
|
|
*
|
|
|
|
* @return ResourceInputStream
|
|
|
|
*/
|
|
|
|
function getStdin(): ResourceInputStream
|
|
|
|
{
|
|
|
|
static $key = InputStream::class . '\\stdin';
|
|
|
|
|
|
|
|
$stream = Loop::getState($key);
|
|
|
|
|
|
|
|
if (!$stream) {
|
|
|
|
$stream = new ResourceInputStream(\STDIN);
|
|
|
|
Loop::setState($key, $stream);
|
|
|
|
}
|
|
|
|
|
|
|
|
return $stream;
|
|
|
|
}
|
|
|
|
|
2019-08-22 22:20:51 +02:00
|
|
|
/**
|
|
|
|
* Get LineReader instance associated with STDIN
|
|
|
|
*
|
|
|
|
* @return LineReader
|
|
|
|
*/
|
|
|
|
function getStdinLineReader(): LineReader
|
|
|
|
{
|
2019-08-22 22:44:03 +02:00
|
|
|
static $key = LineReader::class . '\\stdin';
|
2019-08-22 22:20:51 +02:00
|
|
|
|
|
|
|
$stream = Loop::getState($key);
|
|
|
|
|
|
|
|
if (!$stream) {
|
|
|
|
$stream = new LineReader(getStdin());
|
|
|
|
Loop::setState($key, $stream);
|
|
|
|
}
|
|
|
|
|
|
|
|
return $stream;
|
|
|
|
}
|
|
|
|
|
2019-08-22 22:58:19 +02:00
|
|
|
/**
|
|
|
|
* Get data eventually buffered by the STDIN LineReader
|
|
|
|
*
|
|
|
|
* @return LineReader
|
|
|
|
*/
|
|
|
|
function getStdinBuffer(): string
|
|
|
|
{
|
|
|
|
return getStdinLineReader()->getBuffer();
|
|
|
|
}
|
|
|
|
|
2019-08-23 00:03:18 +02:00
|
|
|
/**
|
|
|
|
* Clear data eventually buffered by the STDIN LineReader
|
|
|
|
*
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
function clearStdinBuffer()
|
|
|
|
{
|
|
|
|
return getStdinLineReader()->clearBuffer();
|
|
|
|
}
|
|
|
|
|
2018-10-22 16:56:37 +02:00
|
|
|
/**
|
|
|
|
* The STDOUT stream for the process associated with the currently active event loop.
|
|
|
|
*
|
|
|
|
* @return ResourceOutputStream
|
|
|
|
*/
|
|
|
|
function getStdout(): ResourceOutputStream
|
|
|
|
{
|
|
|
|
static $key = OutputStream::class . '\\stdout';
|
|
|
|
|
|
|
|
$stream = Loop::getState($key);
|
|
|
|
|
|
|
|
if (!$stream) {
|
|
|
|
$stream = new ResourceOutputStream(\STDOUT);
|
|
|
|
Loop::setState($key, $stream);
|
|
|
|
}
|
|
|
|
|
|
|
|
return $stream;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The STDERR stream for the process associated with the currently active event loop.
|
|
|
|
*
|
|
|
|
* @return ResourceOutputStream
|
|
|
|
*/
|
|
|
|
function getStderr(): ResourceOutputStream
|
|
|
|
{
|
|
|
|
static $key = OutputStream::class . '\\stderr';
|
|
|
|
|
|
|
|
$stream = Loop::getState($key);
|
|
|
|
|
|
|
|
if (!$stream) {
|
|
|
|
$stream = new ResourceOutputStream(\STDERR);
|
|
|
|
Loop::setState($key, $stream);
|
|
|
|
}
|
|
|
|
|
|
|
|
return $stream;
|
|
|
|
}
|
2019-07-15 16:16:07 +02:00
|
|
|
|
|
|
|
/**
|
2019-08-22 22:42:20 +02:00
|
|
|
* Buffered async version of the readline() function.
|
2019-07-15 16:16:07 +02:00
|
|
|
*
|
2019-08-22 22:42:20 +02:00
|
|
|
* Please note that this function will hungrily eat data from stdin,
|
2019-08-22 22:20:51 +02:00
|
|
|
* buffering data even after the first newline.
|
|
|
|
* Use getStdinLineReader()->getBuffer() to obtain the remaining buffered data.
|
|
|
|
*
|
2019-07-15 16:16:07 +02:00
|
|
|
* @param string $prompt Optional prompt to print to console
|
|
|
|
*
|
|
|
|
* @return \Amp\Promise Will resolve with the read line
|
|
|
|
*/
|
2019-08-22 22:42:20 +02:00
|
|
|
function prompt(string $prompt = ''): Promise
|
2019-07-15 16:16:07 +02:00
|
|
|
{
|
|
|
|
return call(static function () use ($prompt) {
|
|
|
|
if ($prompt) {
|
|
|
|
yield getStdout()->write($prompt);
|
|
|
|
}
|
2019-08-22 22:20:51 +02:00
|
|
|
return getStdinLineReader()->readLine();
|
2019-07-15 16:16:07 +02:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Simple wrapper function to asynchronously write a string to the PHP output buffer.
|
|
|
|
*
|
|
|
|
* @param string $string
|
|
|
|
* @return Promise
|
|
|
|
*/
|
|
|
|
function bufferEcho($string): Promise
|
|
|
|
{
|
|
|
|
return getOutputBufferStream()->write($string);
|
|
|
|
}
|
2019-08-22 23:36:28 +02:00
|
|
|
function parseLineDelimitedJson(InputStream $stream, bool $assoc = false, int $depth = 512, int $options = 0): Iterator
|
|
|
|
{
|
|
|
|
return new Producer(static function (callable $emit) use ($stream, $assoc, $depth, $options) {
|
|
|
|
$reader = new LineReader($stream);
|
|
|
|
|
|
|
|
while (null !== $line = yield $reader->readLine()) {
|
|
|
|
$line = \trim($line);
|
|
|
|
|
|
|
|
if ($line === '') {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
/** @noinspection PhpComposerExtensionStubsInspection */
|
|
|
|
$data = \json_decode($line, $assoc, $depth, $options);
|
|
|
|
/** @noinspection PhpComposerExtensionStubsInspection */
|
|
|
|
$error = \json_last_error();
|
|
|
|
|
|
|
|
/** @noinspection PhpComposerExtensionStubsInspection */
|
|
|
|
if ($error !== \JSON_ERROR_NONE) {
|
|
|
|
/** @noinspection PhpComposerExtensionStubsInspection */
|
|
|
|
throw new StreamException('Failed to parse JSON: ' . \json_last_error_msg(), $error);
|
|
|
|
}
|
|
|
|
|
|
|
|
yield $emit($data);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|