refactor!: clean up stdio interface

with an interface no less!
This commit is contained in:
2026-02-11 22:03:44 +01:00
parent ad740afb5f
commit 3a5cad161d
10 changed files with 159 additions and 114 deletions

View File

@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace Nih\CommandBuilder;
use Override;
use Stringable;
use ValueError;
@@ -20,9 +21,9 @@ final class Command implements Stringable
private bool $environmentInherit = true;
private ?string $cwd = null;
private ?Stdio $stdin = null;
private ?Stdio $stdout = null;
private ?Stdio $stderr = null;
private ?StdioInterface $stdin = null;
private ?StdioInterface $stdout = null;
private ?StdioInterface $stderr = null;
/**
* Constructs a new Command for launching the program at path program, with
@@ -202,14 +203,8 @@ final class Command implements Stringable
* Defaults to inherit when used with spawn or status, and defaults to piped
* when used with output.
*/
public function stdin(Stdio $stdin): static
public function stdin(StdioInterface $stdin): static
{
$stdin = match ($stdin->type) {
Stdio::INHERIT => Stdio::stream(STDIN),
Stdio::PIPE => new Stdio(Stdio::PIPE, ['pipe', 'r']),
default => $stdin,
};
$this->stdin = $stdin;
return $this;
}
@@ -220,14 +215,8 @@ final class Command implements Stringable
* Defaults to inherit when used with spawn or status, and defaults to piped
* when used with output.
*/
public function stdout(Stdio $stdout): static
public function stdout(StdioInterface $stdout): static
{
$stdout = match ($stdout->type) {
Stdio::INHERIT => Stdio::stream(STDOUT),
Stdio::PIPE => new Stdio(Stdio::PIPE, ['pipe', 'w']),
default => $stdout,
};
$this->stdout = $stdout;
return $this;
}
@@ -238,14 +227,8 @@ final class Command implements Stringable
* Defaults to inherit when used with spawn or status, and defaults to piped
* when used with output.
*/
public function stderr(Stdio $stderr): static
public function stderr(StdioInterface $stderr): static
{
$stderr = match ($stderr->type) {
Stdio::INHERIT => Stdio::stream(STDERR),
Stdio::PIPE => new Stdio(Stdio::PIPE, ['pipe', 'w']),
default => $stderr,
};
$this->stderr = $stderr;
return $this;
}
@@ -267,20 +250,18 @@ final class Command implements Stringable
* Executes the command as a child process, returning a handle to it.
*
* By default, stdin, stdout and stderr are inherited from the parent.
*
* @param bool $shell Run the command with or without a shell
*/
public function spawn(): Child
{
return $this->spawnWithDescriptorSpec([
$this->stdin instanceof Stdio
? $this->stdin->descriptorSpec
$this->stdin instanceof StdioInterface
? $this->stdin->getDescriptionSpec(0)
: STDIN,
$this->stdout instanceof Stdio
? $this->stdout->descriptorSpec
$this->stdout instanceof StdioInterface
? $this->stdout->getDescriptionSpec(1)
: STDOUT,
$this->stderr instanceof Stdio
? $this->stderr->descriptorSpec
$this->stderr instanceof StdioInterface
? $this->stderr->getDescriptionSpec(2)
: STDERR,
]);
}
@@ -290,8 +271,6 @@ final class Command implements Stringable
* collecting its status.
*
* By default, stdin, stdout and stderr are inherited from the parent.
*
* @param bool $shell Run the command with or without a shell
*/
public function status(): ExitStatus
{
@@ -304,20 +283,18 @@ final class Command implements Stringable
*
* By default, stdout and stderr are captured (and used to provide the
* resulting output). Stdin is not inherited from the parent.
*
* @param bool $shell Run the command with or without a shell
*/
public function output(): Output
{
return $this->spawnWithDescriptorSpec([
$this->stdin instanceof Stdio
? $this->stdin->descriptorSpec
$this->stdin instanceof StdioInterface
? $this->stdin->getDescriptionSpec(0)
: ['pipe', 'r'],
$this->stdout instanceof Stdio
? $this->stdout->descriptorSpec
$this->stdout instanceof StdioInterface
? $this->stdout->getDescriptionSpec(1)
: ['pipe', 'w'],
$this->stderr instanceof Stdio
? $this->stderr->descriptorSpec
$this->stderr instanceof StdioInterface
? $this->stderr->getDescriptionSpec(2)
: ['pipe', 'w'],
])->waitWithOutput();
}
@@ -365,6 +342,7 @@ final class Command implements Stringable
return $this->cwd;
}
#[Override]
public function __toString(): string
{
return implode(' ', [
@@ -375,17 +353,6 @@ final class Command implements Stringable
private function spawnWithDescriptorSpec(array $descriptorSpec): Child
{
// Validate stream resources in descriptor spec.
foreach ($descriptorSpec as $descriptor => $spec) {
if (!is_array($spec) && !is_resource($spec)) {
throw new CommandException(sprintf(
'Descriptor %d is not a valid stream resource: %s',
$descriptor,
get_debug_type($spec),
));
}
}
// Find executable if path is not absolute.
$program = $this->program;
if ($program[0] !== DIRECTORY_SEPARATOR) {