refactor!: clean up stdio interface
with an interface no less!
This commit is contained in:
116
src/Stdio.php
116
src/Stdio.php
@@ -4,78 +4,128 @@ declare(strict_types=1);
|
||||
|
||||
namespace Nih\CommandBuilder;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use Override;
|
||||
use Stringable;
|
||||
|
||||
/**
|
||||
* Describes what to do with a standard I/O stream for a child process when
|
||||
* passed to the stdin, stdout, and stderr methods of {@see Command}.
|
||||
*
|
||||
* @psalm-suppress UnusedClass
|
||||
*/
|
||||
final class Stdio
|
||||
abstract class Stdio
|
||||
{
|
||||
public const INHERIT = 0;
|
||||
public const PIPE = 1;
|
||||
public const FILE = 2;
|
||||
public const STREAM = 3;
|
||||
|
||||
/**
|
||||
* @param null
|
||||
* |resource
|
||||
* |array{0: 'file', 1: string, 2: string}
|
||||
* |array{0: 'pipe',1: string} $descriptorSpec
|
||||
*/
|
||||
public function __construct(
|
||||
public readonly int $type,
|
||||
public readonly mixed $descriptorSpec,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* The child inherits from the corresponding parent descriptor.
|
||||
*/
|
||||
public static function inherit(): self
|
||||
public static function inherit(): StdioInterface
|
||||
{
|
||||
return new self(self::INHERIT, null);
|
||||
return new class implements StdioInterface {
|
||||
#[Override]
|
||||
public function getDescriptionSpec(int $fd): mixed
|
||||
{
|
||||
return match ($fd) {
|
||||
0 => STDIN,
|
||||
1 => STDOUT,
|
||||
2 => STDERR,
|
||||
default => ['php://fd/' . $fd, 'w+'],
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* A new pipe should be arranged to connect the parent and child processes.
|
||||
*/
|
||||
public static function piped(): self
|
||||
public static function piped(): StdioInterface
|
||||
{
|
||||
return new self(self::PIPE, ['pipe', 'r']);
|
||||
return new class implements StdioInterface {
|
||||
#[Override]
|
||||
public function getDescriptionSpec(int $fd): mixed
|
||||
{
|
||||
return match ($fd) {
|
||||
0 => ['pipe', 'r'],
|
||||
1, 2 => ['pipe', 'w'],
|
||||
default => ['pipe', 'w+'],
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* This stream will be ignored. This is the equivalent of attaching the
|
||||
* stream to `/dev/null`.
|
||||
*/
|
||||
public static function null(): self
|
||||
public static function null(): StdioInterface
|
||||
{
|
||||
return self::file('/dev/null', 'a+');
|
||||
return new class implements StdioInterface {
|
||||
#[Override]
|
||||
public function getDescriptionSpec(int $fd): mixed
|
||||
{
|
||||
// TODO: Support windows (I think you just write to `nul` in any
|
||||
// directory?)
|
||||
return match ($fd) {
|
||||
0 => ['file', '/dev/null', 'r'],
|
||||
1, 2 => ['file', '/dev/null', 'w'],
|
||||
default => ['file', '/dev/null', 'w+'],
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Like piped, but instead of capturing the stream into a handle, read
|
||||
* and/or write from/to a file.
|
||||
*/
|
||||
public static function file(string|Stringable $file, string $mode): self
|
||||
public static function file(string|Stringable $filename, string $mode): StdioInterface
|
||||
{
|
||||
return new self(self::FILE, ['file', (string) $file, $mode]);
|
||||
return new class((string) $filename, $mode) implements StdioInterface {
|
||||
public function __construct(
|
||||
private string $filename,
|
||||
private string $mode,
|
||||
) {}
|
||||
|
||||
#[Override]
|
||||
public function getDescriptionSpec(int $fd): mixed
|
||||
{
|
||||
return ['file', $this->filename, $this->mode];
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Like piped, but instead of capturing the stream into a handle, read
|
||||
* and/or write from/to a stream.
|
||||
* and/or write from/to a stream resource.
|
||||
*
|
||||
* @param resource|StreamReadable|StreamWritable $stream
|
||||
* @param resource $stream
|
||||
*
|
||||
* @throws InvalidArgumentException When stream is not a live stream resource
|
||||
*/
|
||||
public static function stream($stream): self
|
||||
public static function stream($stream): StdioInterface
|
||||
{
|
||||
if (is_object($stream)) {
|
||||
$stream = $stream->stream;
|
||||
$isResource = is_resource($stream)
|
||||
|| ($stream !== null
|
||||
&& !is_scalar($stream)
|
||||
&& !is_array($stream)
|
||||
&& !is_object($stream));
|
||||
|
||||
if (!$isResource) {
|
||||
throw new InvalidArgumentException('not a resource');
|
||||
}
|
||||
|
||||
return new self(self::STREAM, $stream);
|
||||
}
|
||||
if (get_resource_type($stream) !== 'stream') {
|
||||
throw new InvalidArgumentException('resource is not a stream or was closed');
|
||||
}
|
||||
|
||||
return new class($stream) implements StdioInterface {
|
||||
public function __construct(private mixed $stream) {}
|
||||
|
||||
#[Override]
|
||||
public function getDescriptionSpec(int $fd): mixed
|
||||
{
|
||||
return $this->stream;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user