Skip to content

Commit

Permalink
feat(shell): introduce stream_unpack function
Browse files Browse the repository at this point in the history
Signed-off-by: azjezz <azjezz@protonmail.com>
  • Loading branch information
azjezz committed May 7, 2022
1 parent 659a5f4 commit 9e95551
Show file tree
Hide file tree
Showing 5 changed files with 63 additions and 26 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,3 +71,5 @@
* **BC** - `$escape_arguments` argument of `Shell\execute` function has been removed.
* introduced a new `Psl\Shell\ErrorOutputBehavior` enum
* added a new `$error_output_behavior` argument to `Shell\execute` function, which can be used to return the command error output content, as well as the standard output content.
* introduced a new `Psl\Shell\unpack` function to unpack packed result of `Shell\execute` ( see `Psl\Shell\ErrorOutputBehavior::Packed` ).
* introduced a new `Psl\Shell\stream_unpack` function to unpack packed result of `Shell\execute` chunk by chunk, maintaing order ( see `Psl\Shell\ErrorOutputBehavior::Packed` ).
3 changes: 2 additions & 1 deletion docs/component/shell.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
#### `Functions`

- [execute](./../../src/Psl/Shell/execute.php#L41)
- [unpack](./../../src/Psl/Shell/unpack.php#L20)
- [stream_unpack](./../../src/Psl/Shell/stream_unpack.php#L30)
- [unpack](./../../src/Psl/Shell/unpack.php#L16)

#### `Enums`

Expand Down
1 change: 1 addition & 0 deletions src/Psl/Internal/Loader.php
Original file line number Diff line number Diff line change
Expand Up @@ -379,6 +379,7 @@ final class Loader
'Psl\Encoding\Hex\decode',
'Psl\Shell\execute',
'Psl\Shell\unpack',
'Psl\Shell\stream_unpack',
'Psl\Shell\Internal\escape_argument',
'Psl\Html\encode',
'Psl\Html\encode_special_characters',
Expand Down
56 changes: 56 additions & 0 deletions src/Psl/Shell/stream_unpack.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<?php

declare(strict_types=1);

namespace Psl\Shell;

use Generator;
use Psl\Str;

use function unpack as byte_unpack;

/**
* Stream unpack the result of `Shell\execute()` when using `ErrorOutputBehavior::Packed` error output behavior,
* maintaining the outputting order, chunk by chunk.
*
* @param string $content
*
* @throws Exception\InvalidArgumentException If $content is invalid.
*
* @return Generator<1|2, string, void, void> Generator where the key is either 1 ( representing the standard output ),
* or 2 ( representing the standard error output ), and the value is the output chunk.
*
* Example:
*
* Shell\stream_unpack(
* Shell\execute('php', ['-r', 'fwrite(STDOUT, "a"); fwrite(STDERR, "b"); fwrite(STDOUT, "c");'], null, [], ErrorOutputBehavior::Packed),
* );
* => Generator(1 => "a", 2 => "b", 1 => "c")
*/
function stream_unpack(string $content): Generator
{
while ($content !== '') {
if (Str\Byte\length($content) < 5) {
throw new Exception\InvalidArgumentException('$content contains an invalid header value.');
}

$headers = byte_unpack('C1type/N1size', Str\Byte\slice($content, 0, 5));
/** @var int<0, max> $type */
$type = (int) $headers['type'];
/** @var int<0, max> $size */
$size = (int) $headers['size'];

if ($size > (Str\Byte\length($content) - 5)) {
throw new Exception\InvalidArgumentException('$content contains an invalid header value.');
}

$chunk = Str\Byte\slice($content, 5, $size);
$content = Str\Byte\slice($content, $size + 5);

if ($type === 1 || $type === 2) {
yield $type => $chunk;
} else {
throw new Exception\InvalidArgumentException('$content contains an invalid header value.');
}
}
}
27 changes: 2 additions & 25 deletions src/Psl/Shell/unpack.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,6 @@

namespace Psl\Shell;

use Psl\Str;

use function unpack as byte_unpack;

/**
* Unpack the result of `Shell\execute()` when using `ErrorOutputBehavior::Packed` error output behavior.
*
Expand All @@ -20,30 +16,11 @@
function unpack(string $content): array
{
$result = ['', ''];
while ($content !== '') {
if (Str\Byte\length($content) < 5) {
throw new Exception\InvalidArgumentException('$content contains an invalid header value.');
}

$headers = byte_unpack('C1type/N1size', Str\Byte\slice($content, 0, 5));
/** @var int<0, max> $type */
$type = (int) $headers['type'];
/** @var int<0, max> $size */
$size = (int) $headers['size'];

if ($size > (Str\Byte\length($content) - 5)) {
throw new Exception\InvalidArgumentException('$content contains an invalid header value.');
}

$chunk = Str\Byte\slice($content, 5, $size);
$content = Str\Byte\slice($content, $size + 5);

foreach (stream_unpack($content) as $type => $chunk) {
if ($type === 1) {
$result[0] .= $chunk;
} elseif ($type === 2) {
$result[1] .= $chunk;
} else {
throw new Exception\InvalidArgumentException('$content contains an invalid header value.');
$result[1] .= $chunk;
}
}

Expand Down

0 comments on commit 9e95551

Please sign in to comment.